Skip to content

Latest commit

 

History

History
255 lines (198 loc) · 9.17 KB

File metadata and controls

255 lines (198 loc) · 9.17 KB

AGENTS.md — Framo

Guide for AI agents working on this codebase.

Project Overview

Framo is a single-binary Rust application for creating disposable email addresses and watching inboxes for OTP codes in real time. It has two runtime modes:

  • TUI mode (framo) — Interactive terminal dashboard built with ratatui + crossterm
  • API server mode (framo serve) — HTTP REST + WebSocket server built with axum

Both modes share the same email_service layer for provider communication.

Quick Reference

cargo build                  # Build (dev)
cargo build --release        # Build (release)
cargo clippy                 # Lint
cargo fmt                    # Format
cargo test                   # Run tests
framo                        # Launch TUI
framo serve                  # Start API server (default port 3000)
framo serve --port 8080      # Start API server on custom port
framo --test                 # Smoke test both email providers

Project Structure

src/
  main.rs                    # Entry point, subcommand routing (TUI / serve / --test)
  config.rs                  # Constants: domains, URLs, poll interval, home dir
  models.rs                  # EmailAccount, EmailMessage, InboxResponse
  browser.rs                 # HTTP session with Livewire v2/v3 protocol support
  utils.rs                   # OTP extraction, clipboard, HTML strip, date parsing, username gen
  email_service/
    mod.rs                   # Provider enum dispatch (Imail / PostInbox)
    imail.rs                 # Imail provider (imail.edu.vn) — Livewire v2
    postinbox.rs             # PostInbox provider (postinbox.org) — Livewire v3
  dashboard/
    mod.rs                   # TUI event loop, terminal setup/teardown
    state.rs                 # DashboardState, Watcher, WatchStatus, ViewMode, InputMode
    events.rs                # Key → Action mapping, cursor/navigation logic
    actions.rs               # Async actions: add, delete, refresh, clipboard, batch
    jobs.rs                  # Background inbox polling loop (watch_inbox)
    render.rs                # Full TUI rendering: header, table, inbox, sidebar, status
    table.rs                 # Watcher table row rendering
  api/
    mod.rs                   # AppState, ApiWatcher, router setup, run_server()
    routes.rs                # REST handlers: CRUD, domains, config, health
    ws.rs                    # WebSocket handler, broadcast, watch_inbox_loop

Architecture

Runtime Modes

main.rs
├── framo              → dashboard::Dashboard::new().run()
├── framo serve        → api::run_server(port)
└── framo --test       → test_providers()

Email Service Layer

Two providers, both implementing the same interface via Provider enum:

Provider Base URL Protocol Domains
Imail imail.edu.vn Livewire v2 apple.edu.pl, nik.edu.pl, mailer.edu.pl
PostInbox postinbox.org Livewire v3 unbox.edu.pl, blogerspace.com, pulecheese.net, expertmail.cv, goodmail.cv, dokumail.biz, mailinux.me

Provider selection is automatic based on domain via ProviderKind::from_domain().

How Providers Work

Both providers scrape web pages built with Laravel Livewire:

  1. Visit homepage → extract CSRF token + Livewire component snapshots from HTML
  2. Send Livewire POST requests with snapshot + updates to create email / fetch inbox
  3. Parse JSON responses to get messages
  4. Imail uses /livewire/message/{component} (v2), PostInbox uses /livewire/update (v3)

Shared State Pattern

Both TUI and API use the same pattern:

  • SharedWatchers = Arc<Mutex<Vec<Watcher>>> — shared watcher list
  • TaskRegistry = Arc<Mutex<HashMap<WatcherId, JoinHandle<()>>>> — background task handles
  • Background tokio::spawn tasks poll inbox every 3 seconds (POLL_INTERVAL)
  • OTP detection stops the polling loop automatically

REST API Reference

All endpoints bind to 127.0.0.1 (localhost only, no auth).

Endpoints

Method Path Description
GET /api/health Health check + version
GET /api/config Server config: version, poll interval, providers, domains
GET /api/domains All available domains with provider info and priority flag
POST /api/emails Create email watcher(s)
GET /api/emails List all active watchers
GET /api/emails/:id Get watcher detail
PATCH /api/emails/:id Update watcher (reset_otp, stop)
DELETE /api/emails/:id Delete single watcher
DELETE /api/emails Delete all watchers (bulk)
GET /api/emails/:id/refresh Force refresh inbox
GET /api/emails/:id/messages Get watcher + message metadata
GET /ws WebSocket connection for real-time events

Request/Response Examples

POST /api/emails — Create email(s)

// Request body (all optional)
{ "domain": "unbox.edu.pl", "username": "myuser", "count": 3 }

// Response 201
{ "created": [{ "id": 1, "email": "myuser@unbox.edu.pl" }] }

GET /api/domains — List domains

{
  "domains": [
    { "domain": "unbox.edu.pl", "provider": "postinbox", "base_url": "https://postinbox.org", "priority": true },
    { "domain": "apple.edu.pl", "provider": "imail", "base_url": "https://imail.edu.vn", "priority": true }
  ],
  "total": 10
}

GET /api/config — Server config

{
  "version": "2.0.0",
  "poll_interval_secs": 3,
  "default_domain": "unbox.edu.pl",
  "providers": [
    { "name": "imail", "base_url": "https://imail.edu.vn", "domains": ["nik.edu.pl", "apple.edu.pl", "mailer.edu.pl"] },
    { "name": "postinbox", "base_url": "https://postinbox.org", "domains": ["unbox.edu.pl", "blogerspace.com", "..."] }
  ]
}

PATCH /api/emails/:id — Update watcher

// Request body
{ "action": "reset_otp" }   // Reset OTP, resume watching
{ "action": "stop" }         // Stop polling, mark as done

// Response 200
{ "updated": true, "id": 1, "action": "reset_otp", "email": "abc@unbox.edu.pl" }

WebSocket Events (/ws)

Connect to ws://127.0.0.1:3000/ws.

Server → Client events:

{"event": "init",            "data": {"watchers": [...]}}
{"event": "email_creating",  "data": {"id": 1, "email": "abc@unbox.edu.pl"}}
{"event": "email_created",   "data": {"id": 1, "email": "abc@unbox.edu.pl"}}
{"event": "otp_detected",    "data": {"id": 1, "email": "...", "otp": "123456"}}
{"event": "new_message",     "data": {"id": 1, "email": "...", "from": "...", "subject": "...", "date": "..."}}
{"event": "status_changed",  "data": {"id": 1, "status": "error", "error": "..."}}
{"event": "email_deleted",   "data": {"id": 1, "email": "..."}}
{"event": "email_refreshing","data": {"id": 1, "email": "..."}}

Client → Server commands:

{"action": "ping"}   → {"event": "pong", "data": {}}
{"action": "list"}   → {"event": "watchers_list", "data": {"watchers": [...]}}

Watcher Object Schema

{
  "id": 1,
  "email": "abc@unbox.edu.pl",
  "otp": "123456",
  "messages": 3,
  "status": "watching",
  "provider": "postinbox",
  "updated_at": 1716835200,
  "error": null
}

Status values: creating, watching, done, error

Key Dependencies

Crate Purpose
tokio Async runtime (full features)
axum HTTP + WebSocket server (api mode)
tower-http CORS middleware
wreq HTTP client with cookies (provider requests)
ratatui + crossterm TUI rendering
serde + serde_json JSON serialization
regex OTP extraction, HTML parsing, CSRF token extraction
html-escape HTML entity decoding
arboard Clipboard access
tracing + tracing-subscriber Logging to ~/.framo/logs.log
rand Username generation, random domain selection
dirs Home directory resolution
anyhow Error handling
futures-util WebSocket stream extension (.next())

Code Patterns

  • Error handling: anyhow::Result everywhere, bail! for early returns
  • Async: Tokio spawn for background tasks, Mutex for shared state
  • Logging: tracing::info!/warn!/error! for structured logging, env filter via FRAMO_LOG
  • State sharing: Arc<Mutex<T>> pattern for watchers and task registry
  • Broadcast: tokio::sync::broadcast channel for WebSocket event distribution
  • Provider abstraction: Enum dispatch (Provider::Imail / Provider::PostInbox) with shared interface
  • No comments in code: Follow existing style — no comments unless explicitly requested

CI Pipeline

.github/workflows/ci.yml:

  • lint: cargo fmt --check + cargo clippy -- -D warnings
  • build-and-test: cargo test + cargo build --release on Windows, Linux, macOS

All clippy warnings must be resolved before merge. Existing warnings in dashboard/mod.rs and utils.rs are pre-existing collapsible-if patterns.

Logging

Logs are written to ~/.framo/logs.log. Control verbosity with:

FRAMO_LOG=debug framo serve    # Debug logging
FRAMO_LOG=trace framo          # Trace logging

Data Directory

All runtime data stored in ~/.framo/:

  • logs.log — application logs
  • Future: config files, local app data