Commit Graph

79 Commits

Author SHA1 Message Date
steve 1a03ce1c69 docs(cli): help reflects positional admin grammar + aliases
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:19:42 +01:00
steve ca49a42d40 feat(tui): drop whitelist toggles from account form (managed via whitelist group)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 12:16:20 +01:00
steve c826042625 feat(cli): top-level ls alias for list
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:12:03 +01:00
steve 44a9211a6f feat(cli): doctor accepts positional account alongside --account 2026-06-27 12:08:21 +01:00
steve 9a8765d4e4 feat(cli): positional account grammar, account show, TTY remove confirm; drop whitelist flags
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 12:04:04 +01:00
steve 1bf5bf3c47 feat(cli): positional whitelist grammar with required direction, enable/disable, validation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 11:56:54 +01:00
steve f407fc126d feat(cli): positional config grammar with registry + config list 2026-06-27 11:49:56 +01:00
steve 56ecdf246c feat(store): add SetWhitelistEnabled 2026-06-27 11:47:01 +01:00
steve a5bfaa4fe3 feat(cli): add config settings registry 2026-06-27 11:45:10 +01:00
steve 85642d5b12 feat(policy): add ValidWhitelistAddress
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:42:44 +01:00
steve 68c926f83b feat(cli): add normalizeVerb alias helper 2026-06-27 11:40:42 +01:00
steve 2c7b8d3610 docs: implementation plan for human CLI grammar redesign
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:39:03 +01:00
steve 7a4d2881ba docs: spec for human-facing CLI grammar redesign
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:21:23 +01:00
steve 3c5e0a26f3 chore(release): default installer to v0.5.2
release / release (push) Successful in 43s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.5.2
2026-06-23 23:05:31 +01:00
steve 456d25d4f3 fix(cli): clearer whitelist usage errors
`whitelist <in|out> <add|remove|list>` has two positional slots; omitting
either let a --flag slide into the slot and produced a misleading
"--account is required". Validate the direction and the subcommand up
front, before flag parsing, so the real mistake is reported.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 23:04:40 +01:00
steve 3bea73f857 fix(store): expand a leading ~ in EMCLI_DB
A literal "~/..." in EMCLI_DB has no shell to expand it, so SQLite opened
it relative to the cwd and silently created a stray "~" directory tree.
Expand a leading "~" or "~/" to the user's home dir; "~user", mid-path
tildes, and absolute/relative paths are left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 23:04:40 +01:00
steve c651b00d08 chore(release): default installer to v0.5.1
release / release (push) Failing after 3m14s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.5.1
2026-06-23 22:11:48 +01:00
steve 8ed10dd503 docs: agent can discover accounts via account list
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:39:12 +01:00
steve 2140d9e173 feat(cli): agent-readable account list (reduced JSON view)
account list now routes to the agent role; an agent (EMCLI_KEY only) gets a
JSON envelope of name/from/can_send, while the admin keeps the full text
table. account add/edit/remove stay admin-only.

Also emit the agent path's missing-key/open failure as a JSON Failure
envelope (per spec), and update the stale run_test case that asserted the
old admin-only behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:37:37 +01:00
steve 64ff32ab29 docs(plan): agent-readable account list
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:34:33 +01:00
steve 7039371f70 docs(spec): agent-readable account list (reduced JSON view)
Let an agent holding only EMCLI_KEY discover accounts via `account list`,
exposing name/from/can_send (not host/username); admin keeps the full
text table. account add/edit/remove stay admin-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:29:07 +01:00
steve e1b4ec38e5 docs(manual): document --from on account edit and the send-as address
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:49:46 +01:00
steve bd06b4b900 chore(release): default installer to v0.5.0
release / release (push) Successful in 40s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.5.0
2026-06-23 20:38:01 +01:00
steve 8e5c06a4cb style: fix test name typo, table-test reporting, validator wording
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:32:33 +01:00
steve 32f5a8d933 fix(cli): clarify edit --from help; test edit --from validation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:29:37 +01:00
steve b6e68ddeae feat(cli): configurable send-as From address (flags, TUI, validation)
- tui.ValidFromAddress: exported validator; blank passes, malformed rejects
- Fields.FromAddress: new field, round-trips through ToAccount/FieldsFromAccount
- Fields.Validate: calls ValidFromAddress before returning nil
- TUI form: from_address fieldDef between username and password
- send.go: From set via acc.SendFrom() instead of acc.Username
- admin.go account add: --from flag with pre-parse validation
- admin.go account edit: --from flag; validate before Visit, apply in Visit
- USER-MANUAL.md: --from flag added to account add flags table

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:25:14 +01:00
steve 6a99e5bb6e feat(mail): derive bare envelope sender from display-name From
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:20:54 +01:00
steve c5e42ffbae fix(store): surface invalid schema_version; split migration test assertion
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:19:35 +01:00
steve cdffb15004 feat(store): add account from_address field + v2 migration
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:16:15 +01:00
steve a4c49d4aca docs: implementation plan for send-as From address
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:12:28 +01:00
steve 852bb1dc5b docs: design for send-as From address field
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:06:38 +01:00
steve 76ada04442 refactor(cli): wire commandRole into dispatch; doc + comment cleanup
Resolve final-review findings: commandRole is now the single source of
truth (Run resolves role once and threads it to handlers, replacing
hardcoded openStore roles). Tighten crypto/SKILL/SPEC/USER-MANUAL wording
and document init's agent-key-on-first-init-only semantics.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 07:18:27 +01:00
steve add9515b5c docs: document two-key privilege model
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 23:11:18 +01:00
steve 456e15a2f8 test(cli): check setup errors + report all admin refusals
Address review: fail fast on store.Open/key-loader errors in test setup;
use t.Errorf+continue so every admin command is checked, not just the first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 23:06:47 +01:00
steve 5c7dd252db test(cli): prove agent key cannot run admin commands
Initialize a DB, drop EMCLI_ADMIN_KEY, attempt every admin command with
only EMCLI_KEY: each is refused and the DB is byte-for-byte unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 23:03:17 +01:00
steve 9d946b1b03 feat(cli): two-key role routing + init bootstrap
openStore(role) selects the DEK wrap slot; admin commands require
EMCLI_ADMIN_KEY (admin slot only, no agent fallback); init writes both
slots from both keys. Test helpers seed the wrap slots.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 22:59:16 +01:00
steve cb0425f18d feat(store): envelope DEK with admin/agent wrap slots
Open() now opens LOCKED; InitKeys generates a DEK sealed under both KEKs;
Unlock loads it from the role's slot (admin slot has no agent fallback).
s.key becomes the DEK, so account/mail crypto is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 22:52:21 +01:00
steve c52f30898b feat(crypto): named-var key loaders (admin/agent) + NewDEK
Replace KeyFromEnv with AgentKeyFromEnv/AdminKeyFromEnv reading EMCLI_KEY
and EMCLI_ADMIN_KEY; add NewDEK for envelope encryption. Seal/Open double
as DEK wrap/unwrap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 22:47:05 +01:00
steve 77ba5a146f docs(plan): two-key privilege separation implementation plan
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 22:43:16 +01:00
steve 2bc2c1b50e docs(spec): two-key privilege separation design
Enforce the agent/admin trust boundary with two env keys (EMCLI_ADMIN_KEY,
EMCLI_KEY) via envelope encryption: one DEK wrapped per role. Admin commands
unwrap the admin slot only (no agent fallback), so a forced agent holding
EMCLI_KEY cannot authorize config changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 22:34:26 +01:00
steve c946516d01 chore(skill): point installer default at v0.4.1
release / release (push) Successful in 3m21s
Bump EMCLI_VERSION default (install.sh + AGENTIC-MANUAL.md + RELEASING.md) so
agents install the v0.4.1 binary (help for all commands, SMTP-port form default,
skill split). Drop the stale "placeholder until first release" note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.4.1
2026-06-22 21:17:02 +01:00
steve b3390a0a20 fix(tui): default SMTP port to 465 in the account form
NewAccountForm prefilled defaults for mode, IMAP port, and both securities but
left SMTP port blank. Default it to 465 to match `account add --smtp-port`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:15:25 +01:00
steve 1b2fe99055 feat(cli): add help for all commands
emcli had only raw flag usage and no command listing; `--help` on agent commands
even emitted a JSON error envelope and exited 2. Add real help:

- Top-level `emcli` / `help` / `-h` / `--help` prints a grouped command catalogue
  (agent vs admin) with one-line summaries and the EMCLI_KEY/EMCLI_DB env vars.
- `emcli help <command>` prints that command's synopsis + summary.
- `emcli <command> --help` prints synopsis + summary + flags and exits 0. Agent
  commands keep stdout JSON-free (usage goes to stderr); admin commands print to
  stdout. Help works without EMCLI_KEY (no DB access).
- help.go holds the command catalogue; flag.ErrHelp is handled as success, and
  admin handlers short-circuit help before opening the store.

Unknown commands still error (exit 2). Full suite passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:11:40 +01:00
steve 7087533644 docs(skill): split setup into AGENTIC-MANUAL.md; keep SKILL.md lean
The SKILL.md body loads into context on every activation, so one-time install/
setup prose was wasted context once emcli is running. Move it out:

- New AGENTIC-MANUAL.md: get-the-files bootstrap, binary install (incl. options
  and build-from-source, folding in the old references/install.md), EMCLI_KEY,
  account discovery. Fetched only during first-time setup.
- SKILL.md trimmed (182→~145 lines) to the recurring path: security model, a short
  "Files & first run" pointer + per-session preflight, the list/get/ack/send
  workflow, JSON envelope, command table, enforcement, do/don't.
- Remove references/install.md (folded in); fix RELEASING.md pointer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:01:46 +01:00
steve 93dbebb982 docs(skill): make SKILL.md self-bootstrapping from the repo
An agent pointed at the repo may load only SKILL.md and then guess a wrong path
for the installer (it fetched /scripts/install.sh at repo root → 404; the file is
under skills/emcli/). Fix:

- Add a "First: get this skill's files" section: the supporting scripts/ and
  references/ files, the absolute raw base URL to fetch them, and the Gitea
  contents API to enumerate the directory.
- Install step now gives an absolute-URL fetch-then-run for the only-SKILL.md case,
  keeping `bash scripts/install.sh` for the bundled case.
- State that every scripts/… and references/… path is relative to the skill dir and
  resolvable against the raw base URL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 20:51:15 +01:00
steve 68a29ad5c7 docs: mark CI release verified; note releases must be public for the installer
The Gitea Actions workflow published v0.4.0 successfully, so drop the "untested"
caveat. Document that release assets download anonymously — the repo/releases must
be public or install.sh gets a 404 (private repos 404 unauthenticated downloads).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 20:40:45 +01:00
steve 5e3101647d build: add cross-platform release target, tea publish, and Gitea Actions release
release / release (push) Successful in 3m33s
- Makefile `release`: cross-compiles CGO-free static binaries for linux/amd64,
  linux/arm64, darwin/amd64, darwin/arm64, windows/amd64 into dist/, named
  emcli_<version>_<os>_<arch>[.exe] (matching skills/emcli/scripts/install.sh),
  plus a sha256 checksums.txt. VERSION is injected into internal/version.String.
- Makefile `publish`: creates the Gitea release and uploads all dist/ assets via tea.
- .gitea/workflows/release.yml: on a v* tag, build + publish via the Gitea API.
- RELEASING.md: the local (make) and CI flows.

Verified end-to-end: `make release VERSION=v0.4.0` builds all five assets with the
version baked in; serving them locally, skills/emcli/scripts/install.sh downloads,
passes checksum verification, and the installed binary reports v0.4.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.4.0
2026-06-22 20:32:22 +01:00
steve 7ad4f1adc2 feat(skill): add emcli Agent Skill (agentskills.io standard)
skills/emcli/ — an Agent Skill teaching an agent to read and send mail through
emcli's JSON agent commands:

- SKILL.md: name/description (what + when + trigger keywords), compatibility,
  metadata; body covers the security model (agent-only commands, never touch
  EMCLI_KEY), setup, the list→get→ack workflow, sending, and enforcement
  awareness. Frontmatter validated against the spec (name matches dir; desc
  574/1024; compatibility 239/500); body 146 lines (<500).
- scripts/install.sh: detects OS/arch, downloads the release binary, verifies
  the sha256 checksum when present, fails gracefully. Release tag/assets
  (v0.4.0, emcli_<ver>_<os>_<arch>) are placeholders until the first release.
- references/{commands.md,install.md}: full agent command reference (flags, JSON
  shapes, error codes, enforcement) and install options, loaded on demand.

README links to the skill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 20:25:08 +01:00
steve 55c763d641 docs: add user manual; expand README
USER-MANUAL.md — full user-facing guide: setup (EMCLI_KEY/EMCLI_DB), adding
accounts (incl. Gmail app-password walkthrough), admin commands (account /
whitelist / config / audit / doctor / init), agent commands (list/get/search/
ack/send) with exact flags, the JSON envelope + error codes, enforcement rules,
troubleshooting, and a cheat sheet. README now summarizes emcli and links it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 20:14:06 +01:00
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