Skip to main content

agent_first_mail/cli/
root.rs

1#[cfg(feature = "ui")]
2use super::UiCommand;
3use super::{
4    ArchiveAction, CaseCommand, ConfigAction, ContactCommand, DoctorAction, LogAction,
5    MessageAction, PurgeAction, PushAction, RemoteAction, RenderAction, SkillAction, TriageAction,
6};
7use clap::{Parser, Subcommand};
8
9#[derive(Parser, Debug)]
10#[command(name = "afmail", disable_version_flag = true, verbatim_doc_comment)]
11#[doc = r#"Agent-First Mail: local-first email case workspace for agents.
12
13### Interface Policy
14
15- Files are the read interface; CLI is for effects.
16- One workspace represents one mailbox account.
17- Message commands use `afmail message ACTION MESSAGE_ID ...`.
18- Case commands use `afmail case ACTION CASE_REF ...`.
19- Active and archived cases are readable with `afmail case show REF` and
20  `afmail archive case show REF`.
21- stdout carries structured Agent-First Data events; stderr is not a protocol channel.
22
23### Workspace Shape
24
25```text
26.afmail/messages/        raw .eml plus durable remote sidecars
27messages/               rebuildable parsed message cache
28.afmail/logs/events.jsonl append-only audit log
29.afmail/transactions/    transient local write transaction sentinels
30.afmail/workspace.progress.json latest push/pull runtime snapshot
31templates/               user-editable generated Markdown templates
32triage/message_*.md      active unprocessed message views
33spam/*.md                generated spam review views
34trash/*.md               generated trash review views
35deleted/*.md             generated remote-deleted review views
36cases/<group>/<case_uid>-<name>/case.md generated case entry view
37cases/<group>/<case_uid>-<name>/data/ canonical case state
38cases/<group>/<case_uid>-<name>/views/ generated case detail views
39contacts/<group>/<contact_uid>-<name>.md canonical contact cards
40archive/contacts/<contact_uid>-<name>.md archived contact cards
41archive/cases/<case_uid>-<name>/ archived case workspaces
42archive/notifications/<archive_uid>-<name>/archive.md generated archive entry view
43archive/notifications/<archive_uid>-<name>/data/ canonical archive state
44archive/notifications/<archive_uid>-<name>/views/ generated archive detail views
45```
46
47### Examples
48
49```text
50afmail init
51afmail init email-a
52afmail skill status
53afmail skill install --agent codex --scope workspace
54afmail status
55afmail doctor
56afmail pull
57afmail pull sent archive
58afmail remote folders
59afmail case create --name 应用反馈-肥料登记 --message message_inbox_607146690_21 --group open --reason "new feedback thread"
60afmail case show c20260603001
61afmail case add c20260603001 message_inbox_607146690_22 --reason "follow-up belongs to same feedback case"
62afmail archive message create --name 服务通知 --message message_inbox_607146690_23 --summary "billing notification" --reason "billing notification"
63afmail archive message show a20260603001
64afmail archive message restore a20260603001 message_inbox_607146690_23 --reason "needs triage again"
65afmail message spam message_inbox_607146690_23 --reason "phishing attempt"
66afmail message trash message_inbox_607146690_24 --reason "duplicate no longer needed"
67afmail render refresh
68afmail doctor repair --confirm
69afmail case move c20260603001 waiting
70afmail case archive c20260603001 --reason "feedback handled"
71afmail archive case restore c20260603001 --group open --reason "customer replied"
72afmail case tag c20260603001 legal --reason "legal review needed"
73afmail contact create --name "Zhang San" --email zhang@example.com --org Acme --role Buyer
74afmail contact list --group people
75afmail contact email add p20260603001 zhang-alt@example.com
76afmail contact rename p20260603001 --name "Zhang Sanfeng"
77afmail contact extract --from-triage
78afmail contact archive p20260603001 --reason "left the company"
79afmail case draft reply c20260603001 message_inbox_607146690_22
80afmail case draft attach c20260603001 reply-message_inbox_607146690_22.md ./screenshot.png
81afmail case draft change c20260603001 reply-message_inbox_607146690_22.md --body "Thanks, I will check this."
82afmail case draft validate c20260603001 reply-message_inbox_607146690_22.md
83afmail case draft send c20260603001 reply-message_inbox_607146690_22.md
84afmail case draft remove c20260603001 reply-message_inbox_607146690_22.md --reason "mistaken draft"
85afmail push list
86afmail push
87afmail push --confirm
88afmail purge
89afmail purge spam --older-than-days 30
90afmail purge deleted
91afmail log list --limit 20
92afmail message show message_inbox_607146690_21
93afmail message attachment fetch message_inbox_607146690_21 2
94```
95
96### Exit Codes
97
98- `0`: command completed successfully
99- `1`: runtime/store/protocol error
100- `2`: invalid CLI arguments
101"#]
102pub struct Cli {
103    /// Output format: json (default), yaml, plain.
104    #[arg(long, default_value = "json", global = true)]
105    pub output: String,
106
107    /// Log categories (comma-separated): startup, request, progress, retry.
108    #[arg(long, value_delimiter = ',', global = true)]
109    pub log: Vec<String>,
110
111    /// Enable all log categories.
112    #[arg(long, global = true)]
113    pub verbose: bool,
114
115    /// Print structured version event.
116    #[arg(short = 'V', long, global = true)]
117    pub version: bool,
118
119    #[command(subcommand)]
120    pub command: Option<Command>,
121}
122
123#[derive(Subcommand, Debug)]
124pub enum Command {
125    /// Initialize the current directory or a child path as an afmail workspace.
126    Init {
127        /// Optional workspace path to initialize under the current directory.
128        path: Option<String>,
129    },
130    /// Read configured IMAP mailbox ids into local message files without changing remote mail.
131    Pull {
132        /// Configured mailbox ids to pull. With none, pulls actions.pull.default_mailbox_ids.
133        ids: Vec<String>,
134    },
135    /// Read or update local afmail configuration.
136    Config {
137        #[command(subcommand)]
138        action: ConfigAction,
139    },
140    /// Inspect remote IMAP state for configuring mailboxes.
141    Remote {
142        #[command(subcommand)]
143        action: RemoteAction,
144    },
145    /// Push queued local work or manage the local push queue.
146    Push {
147        /// Show planned push actions without IMAP/SMTP writes. This is the default.
148        #[arg(long)]
149        dry_run: bool,
150        /// Apply queued IMAP/SMTP effects.
151        #[arg(long)]
152        confirm: bool,
153        #[command(subcommand)]
154        action: Option<PushAction>,
155    },
156    /// Report workspace health, counts, and latest pull/push progress.
157    Status,
158    /// Check afmail workspace consistency without inspecting Git.
159    Doctor {
160        #[command(subcommand)]
161        action: Option<DoctorAction>,
162    },
163    /// Permanently delete old local spam, trash, and remote-deleted records.
164    Purge {
165        /// Only purge messages in a discard state at least this many days ago. Defaults to 30.
166        #[arg(long = "older-than-days", value_name = "DAYS")]
167        older_than_days: Option<u64>,
168        #[command(subcommand)]
169        action: Option<PurgeAction>,
170    },
171    /// Manage the Agent-First Mail skill for Codex, Claude Code, opencode, and Hermes.
172    Skill {
173        #[command(subcommand)]
174        action: SkillAction,
175    },
176    /// Inspect the triage queue.
177    Triage {
178        #[command(subcommand)]
179        action: TriageAction,
180    },
181    /// Operate on any local message id.
182    Message {
183        #[command(subcommand)]
184        action: MessageAction,
185    },
186    /// Create a case or operate on an existing case ref.
187    Case {
188        #[command(subcommand)]
189        action: CaseCommand,
190    },
191    /// Create a contact or operate on an existing contact ref.
192    Contact {
193        #[command(subcommand)]
194        action: ContactCommand,
195    },
196    /// Inspect and manage archived cases and direct-message archive categories.
197    Archive {
198        #[command(subcommand)]
199        action: ArchiveAction,
200    },
201    /// Rebuild generated read views from local workspace state.
202    Render {
203        #[command(subcommand)]
204        action: RenderAction,
205    },
206    /// Inspect the workspace audit log.
207    Log {
208        #[command(subcommand)]
209        action: LogAction,
210    },
211    /// Emit or open an Agent-First UI operation surface for this workspace.
212    #[cfg(feature = "ui")]
213    Ui(UiCommand),
214}