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>
4.5 KiB
emcli — Phase 4 Plan: Admin TUI + doctor
Date: 2026-06-22
Depends on: Phases 1–3 complete (read, send, Gmail-via-app-password).
Scope (SPEC §7.2): doctor connectivity/auth diagnostics; admin completeness
(account edit/remove, config set/get, audit list); and a bubbletea TUI for init and
interactive account add/edit. (OAuth consent in init is omitted — OAuth deferred in Phase 3.)
Building blocks already present
- store:
GetSetting/SetSetting,RecentAudit(limit),DeleteAccount,AddAccount,GetAccount,ListAccounts. mail:Dial(connect+login),SendSMTP. - Need new: store
UpdateAccount+ account-filtered audit; mailCheckIMAP/CheckSMTP(connect+auth only, no traffic); the TUI form.
Tasks
1. store: UpdateAccount + account-filtered audit
UpdateAccount(a Account) error— updates mutable fields by name; re-encrypts the password only if a non-empty one is supplied (blank = keep existing); same for OAuth secrets.RecentAuditFor(account string, limit int)(account""= all) foraudit list --account. Tests: update changes fields + preserves password when blank; password re-encrypts when set; audit filter returns only the named account.
2. mail: CheckIMAP / CheckSMTP (for doctor)
CheckIMAP(IMAPConfig) error—Dial(logs in) thenLogout. Surfaces connect/auth failure.CheckSMTP(SMTPConfig) error— dial (tls/starttls),Auth(SASL PLAIN),Quit. No mail sent. Tests: both fail cleanly on an unroutable host (error, no panic). Live auth covered in task 8.
3. cli: doctor
emcli doctor [--account <name>] — human-readable. Verifies EMCLI_KEY + DB open (via
openStore), then per account prints IMAP and (RW + smtp set) SMTP as ok/FAIL: <reason>.
Exit non-zero if any check fails. Secrets never printed.
Tests: table rendering + pass/fail aggregation with injected check funcs (no network).
4. cli: config set/get + audit list
emcli config set <key> <value>/emcli config get <key>— wraps settings; known keyaudit_retention_days(validate integer ≥ 0 on set).emcli audit list [--account <name>] [--limit N]— table of recent entries (default 50). Tests: set→get round-trip; retention validation; audit list renders rows.
5. cli: account edit / account remove
account edit --name <n> [--mode ...] [--imap-host ...] …— flag-based partial update viaUpdateAccount; only provided flags change (others preserved). Bareaccount edit --name <n>with no other flags drops into the TUI form (task 6).account remove --name <n> [--yes]—DeleteAccount; require--yesor interactive confirm. Tests: edit changes only supplied fields; remove deletes; remove missing → error.
6. tui: bubbletea account form (testable model)
New internal/tui package (keeps bubbletea out of cli's testable core, no store dependency).
AccountForm— a bubbleteaModeloverbubbles/textinputfields: name, mode, imap host/port/security, smtp host/port/security, username, password, whitelist-in/out toggles, process-backlog. Validates; produces astore.Account(+PasswordSet bool).- Driven by
Update(tea.Msg); exposesAccount(),Done(),Cancelled(),Err()so the logic is unit-testable by feeding key messages — no terminal needed. Tests: feed keystrokes → assert assembled Account; required-field validation blocks submit; edit-mode prefill round-trips; blank password in edit ⇒PasswordSet == false.
7. cli: wire init + interactive add/edit
emcli init— if no accounts exist, run the TUI form, persist the first account, setaudit_retention_daysdefault. Idempotent-ish: warns if an account already exists.account addwith no flags → TUI form;account edit --name Xwith no other flags → TUI prefilled. Flag forms remain for scripting.runTUIAccountglue persists the form'sstore.Accountvia Add/Update.
8. Build/vet/test (incl -race) + live doctor validation
CGO_ENABLED=0 go build, go vet, go test ./.../-race. Live: run doctor against the
mxlogin (password) and Gmail (app-password) accounts — assert IMAP+SMTP ok; assert a bad
password reports FAIL (auth), not a crash.
9. Status report + commit/push
PHASE4-STATUS.md; commit to main; push via tea token.
Out of scope
- OAuth consent in
init(Phase 3 deferred OAuth). - Mouse/advanced TUI theming; a minimal, keyboard-driven lipgloss-styled form is enough.