Files
emcli/specifications/plans/2026-06-22-phase4-admin-tui-doctor.md
steve a837b25d73 feat(admin): Phase 4 — doctor, admin completeness, and bubbletea TUI
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>
2026-06-22 20:09:43 +01:00

4.5 KiB
Raw Permalink Blame History

emcli — Phase 4 Plan: Admin TUI + doctor

Date: 2026-06-22 Depends on: Phases 13 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; mail CheckIMAP/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) for audit 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) errorDial (logs in) then Logout. 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 key audit_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 via UpdateAccount; only provided flags change (others preserved). Bare account edit --name <n> with no other flags drops into the TUI form (task 6).
  • account remove --name <n> [--yes]DeleteAccount; require --yes or 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 bubbletea Model over bubbles/textinput fields: name, mode, imap host/port/security, smtp host/port/security, username, password, whitelist-in/out toggles, process-backlog. Validates; produces a store.Account (+ PasswordSet bool).
  • Driven by Update(tea.Msg); exposes Account(), 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, set audit_retention_days default. Idempotent-ish: warns if an account already exists.
  • account add with no flags → TUI form; account edit --name X with no other flags → TUI prefilled. Flag forms remain for scripting.
  • runTUIAccount glue persists the form's store.Account via 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.