Files
emcli/specifications/PHASE4-STATUS.md
T
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.8 KiB
Raw Permalink Blame History

emcli — Phase 4 Status Report

Date: 2026-06-22 Branch: main Phase 4 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.

TL;DR

Phase 4 is complete and validated live. Built test-first; the binary builds as a single static CGO-free executable, go vet is clean, and the full suite passes including -race. doctor and the admin commands were exercised against the two real accounts (mxlogin password auth + Gmail app-password). A real bug — doctor running checks with stripped (empty) credentials — was caught by live validation, reproduced with a regression test, and fixed.

What was built

Area Change Status
store UpdateAccount (partial edit; re-encrypts password/secrets only when non-empty, blank keeps existing); RecentAuditFor(account, limit).
mail CheckIMAP (connect+login+logout) and CheckSMTP (connect+SASL-PLAIN auth+quit) — no mail transferred.
cli doctor [--account] (per-account IMAP/SMTP ok/FAIL, exit non-zero on any failure, no secrets); config set/get (validates audit_retention_days); audit list [--account] [--limit]; account edit (flag partial-update) / account remove [--yes].
tui (new pkg) AccountForm bubbletea model over bubbles/textinput, with pure, fully-tested Fields (validation + store.Account assembly + edit prefill).
cli wiring init (create/open DB, seed audit_retention_days=90, add first account via TUI); bare account add → TUI; account edit --name X (only --name) → TUI prefilled.

Commands added

emcli doctor [--account <name>]
emcli config set <key> <value>        # e.g. audit_retention_days
emcli config get <key>
emcli audit list [--account <name>] [--limit N]
emcli account edit --name <n> [--mode|--imap-host|--smtp-host|…]   # flag partial-update
emcli account edit --name <n>                                       # interactive (TUI)
emcli account remove --name <n> --yes
emcli account add                                                   # interactive (TUI)
emcli init                                                          # interactive (TUI)

Live validation

Against the two real accounts (mxlogin password auth, gmail app-password) in an isolated DB:

  • doctormxlogin and gmail both report IMAP ok / SMTP ok; a deliberately bad-password account reports IMAP FAIL: Invalid credentials (clean error, no crash); SMTP shown n/a for RO. Exit 1 when any check fails, 0 when --account targets a passing one.
  • configset/get round-trip; audit_retention_days=-5 rejected (exit 2).
  • audit list — rendered a real list/allowed row.
  • account edit (flag) — set a subject-regex on mxlogin; a follow-up doctor still passed, proving UpdateAccount preserved the encrypted password through the edit.
  • account remove --yes — deleted an account; gone from account list.
  • TUI (init/interactive) requires a real terminal; without a TTY it fails cleanly (could not open a new TTY), no panic. Drive it interactively to use.

Bug found and fixed during live validation

doctor initially authenticated with empty passwords for every account — it iterated ListAccounts(), which deliberately strips secrets, and passed those credential-less structs to the live checks. Caught immediately against real servers ("Empty username or password"). Fixed by re-fetching each account with GetAccount (which decrypts) before checking; locked in with TestDoctorUsesDecryptedCredentials.

Verification

CGO_ENABLED=0 go build ./...   → OK, single static binary
go vet ./...                   → clean
go test ./...                  → all packages pass (incl. new internal/tui)
go test -race ./...            → all packages pass

New tests: store update/audit-filter; mail check-fails-cleanly; cli doctor (all-ok, failure, RO-skip, account-filter, decrypted-creds regression), config/audit/edit/remove via Run(); tui Fields validation/assembly/prefill and form submit/cancel.

Known limitations / deferred

  • TUI is a minimal keyboard-driven form (bool fields entered as y/n, enums as text); no mouse or theming. Sufficient for admin use.
  • OAuth consent in init omitted (OAuth deferred in Phase 3).
  • Carry-over Minor items from Phase 1 (audit-row completeness, some CLI polish) remain open.

Project status

Phases 14 complete: read path, send path, Gmail (app password), and admin/TUI/doctor. The core emcli surface from the SPEC is implemented and validated live, with OAuth2 (§10) the one deliberately-deferred item.