# 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 ]` — human-readable. Verifies `EMCLI_KEY` + DB open (via `openStore`), then per account prints IMAP and (RW + smtp set) SMTP as `ok`/`FAIL: `. 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 ` / `emcli config get ` — wraps settings; known key `audit_retention_days` (validate integer ≥ 0 on set). - `emcli audit list [--account ] [--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 [--mode ...] [--imap-host ...] …` — flag-based partial update via `UpdateAccount`; only provided flags change (others preserved). Bare `account edit --name ` with no other flags drops into the TUI form (task 6). - `account remove --name [--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.