Replace KeyFromEnv with AgentKeyFromEnv/AdminKeyFromEnv reading EMCLI_KEY
and EMCLI_ADMIN_KEY; add NewDEK for envelope encryption. Seal/Open double
as DEK wrap/unwrap.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NewAccountForm prefilled defaults for mode, IMAP port, and both securities but
left SMTP port blank. Default it to 465 to match `account add --smtp-port`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
emcli had only raw flag usage and no command listing; `--help` on agent commands
even emitted a JSON error envelope and exited 2. Add real help:
- Top-level `emcli` / `help` / `-h` / `--help` prints a grouped command catalogue
(agent vs admin) with one-line summaries and the EMCLI_KEY/EMCLI_DB env vars.
- `emcli help <command>` prints that command's synopsis + summary.
- `emcli <command> --help` prints synopsis + summary + flags and exits 0. Agent
commands keep stdout JSON-free (usage goes to stderr); admin commands print to
stdout. Help works without EMCLI_KEY (no DB access).
- help.go holds the command catalogue; flag.ErrHelp is handled as success, and
admin handlers short-circuit help before opening the store.
Unknown commands still error (exit 2). Full suite passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the admin/diagnostics surface from SPEC §7.2:
- doctor [--account]: per-account IMAP + (RW) SMTP connectivity/auth checks via
new mail.CheckIMAP/CheckSMTP (connect+auth only, no mail). Exit non-zero on any
failure; secrets never printed.
- store.UpdateAccount: partial edit, re-encrypts password/secrets only when a
non-empty value is supplied (blank keeps existing). RecentAuditFor(account).
- config set/get (validates audit_retention_days), audit list [--account][--limit],
account edit (flag partial-update) / remove [--yes].
- internal/tui: bubbletea AccountForm with pure, fully-tested Fields (validation +
store.Account assembly + edit prefill). init / bare `account add` / `account edit
--name X` drop into the TUI; flag forms remain for scripting.
Built test-first; full suite green incl -race. Validated live against the mxlogin
(password) and Gmail (app-password) accounts. Live validation caught a real bug:
doctor authenticated with empty passwords because it iterated ListAccounts (which
strips secrets) — fixed to re-fetch via GetAccount, locked in by a regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
If header/body parsing errored mid-fetch we returned without draining the
message channel, so the UidFetch goroutine could block on a full channel.
Both fetch paths now break, drain remaining messages, then read the done
error. Verified with the race detector.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
list and search now fetch BODY.PEEK[HEADER] + BODYSTRUCTURE instead of the
whole RFC822 message, so listing a large mailbox no longer downloads every
message body and attachment. Header parsing reuses the same go-message path
(RFC2047 decoding/formatting preserved); has_attachments is derived from the
BODYSTRUCTURE tree. FetchFull keeps fetching the full message for get.
Validated end-to-end against a live IMAP account: list/search/get output
identical to the prior full-fetch behaviour, has_attachments correct.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pass 0 (unlimited) to m.Search so the mail layer returns all matching
headers; the existing post-filter loop already caps at the caller's
limit, mirroring ListCmd. Add TestSearchLimitCountsVisibleOnly to prove
filtering happens before the cap.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Thread uidv through setup's return value (new uint32 before the cleanup
func) so AckCmd no longer makes a redundant SelectFolder round-trip that
silently returned 0 on failure and recorded acks under the wrong
UID-validity epoch. All four callers updated; read-only callers ignore
the value with _.
- Cap search results to limit (keep most-recent UIDs)
- Propagate io.ReadAll errors from body reads in fetchByUIDSet
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SQLite PRAGMAs are connection-scoped, but database/sql uses a connection
pool. Without pinning to one connection, new pooled connections won't have
foreign_keys enabled, breaking ON DELETE CASCADE enforcement.
Also mark modernc.org/sqlite as a direct dependency in go.mod.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>