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

76 lines
4.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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) error``Dial` (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.