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>
87 lines
4.8 KiB
Markdown
87 lines
4.8 KiB
Markdown
# 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:
|
||
- **`doctor`** — `mxlogin` 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.
|
||
- **`config`** — `set`/`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 1–4 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.
|