a837b25d73
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>
76 lines
4.5 KiB
Markdown
76 lines
4.5 KiB
Markdown
# 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; 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.
|