Skip to content

Group aimx --help subcommands by audience#202

Merged
uzyn merged 2 commits into
mainfrom
worktree-happy-wandering-tide
May 5, 2026
Merged

Group aimx --help subcommands by audience#202
uzyn merged 2 commits into
mainfrom
worktree-happy-wandering-tide

Conversation

@uzyn

@uzyn uzyn commented May 5, 2026

Copy link
Copy Markdown
Owner

Summary

Splits the flat 14-command list in aimx --help into two groups so newcomers can orient themselves at a glance:

  • Operations (as current user): send, mailboxes, hooks, agents, mcp — the per-user verbs that the daemon resolves via SO_PEERCRED.
  • Server administration: setup, serve, doctor, logs, dkim-keygen, portcheck, uninstall, upgrade — the operator-class verbs (most root-gated, serve effectively root because it binds :25).

Also retitles the tagline to "The internet's oldest protocol, rebuilt for AI agents.", hides the internal-only ingest subcommand from top-level --help, and tightens every command's one-line description.

Before / after

Before:

Usage: aimx [OPTIONS] <COMMAND>

Commands:
  ingest       Ingest an email from stdin (called by aimx serve or via stdin)
  send         Compose and send an email
  mailboxes    Manage mailboxes
  hooks        Manage hooks
  mcp          Start MCP server in stdio mode
  setup        Run interactive setup wizard
  uninstall    Uninstall the aimx daemon service (config and data are retained)
  doctor       Show server health, mailbox counts, configuration, DNS verification, and recent logs
  logs         Tail or follow the aimx service log
  serve        Start the embedded SMTP listener daemon
  portcheck    Check port 25 connectivity (outbound, inbound)
  agents       Manage AI agent MCP wiring (setup / remove / list)
  dkim-keygen  Generate DKIM keypair for email signing
  help         Print this message or the help of the given subcommand(s)

After:

The internet's oldest protocol, rebuilt for AI agents.

Usage: aimx [OPTIONS] <COMMAND>

Operations (as current user):
  send         Send an email
  mailboxes    Manage mailboxes
  hooks        Manage hooks
  agents       Manage AI agent MCP wiring
  mcp          Start the stdio MCP server (for AI agents)

Server administration:
  setup        Run the interactive setup wizard
  serve        Start the SMTP daemon
  doctor       Show server health, DNS, and recent logs
  logs         Tail the aimx service log
  dkim-keygen  Generate a DKIM keypair
  portcheck    Check port 25 connectivity (inbound, outbound)
  uninstall    Uninstall the aimx service (config and data retained)
  upgrade      Fetch the latest release and swap the installed binary

  help         Print help for a subcommand

Options:
      --data-dir <DATA_DIR>  Data directory override (default: /var/lib/aimx) [env: AIMX_DATA_DIR=]
  -V, --version              Print version
  -h, --help                 Print help

Implementation notes

  • Clap 4.6 doesn't accept help_heading on Subcommand variants — only on Arg. The parent's help_template therefore omits {subcommands} and injects a hand-rolled grouped list via {after-help}. The list lives in a SUBCOMMAND_HELP &str constant with a maintainer comment to keep it in sync with the Command enum (the only real cost of this approach).
  • Dropped long_about so -h and --help render identically; the multi-paragraph description it carried lives in book/ and the README.
  • Added a Cli::version: bool field purely so clap renders -V, --version in the Options block. The existing handle_version_flag interceptor in main.rs still fires before Cli::parse() runs, so the flag is intercepted exactly as before; the field is #[allow(dead_code)] and never read.
  • ingest is now #[command(hide = true)]. The subcommand still parses and runs (it's invoked by aimx serve over stdin in production), it just no longer clutters top-level help.

Test plan

  • cargo build — clean
  • cargo test — 1175 passing, 0 failing across all suites (8/8 help-related integration tests pass)
  • cargo clippy -- -D warnings — clean
  • cargo fmt -- --check — clean
  • Manual: aimx --help and aimx -h both render the new grouped layout
  • Manual: aimx ingest <rcpt> still parses and runs (verified hidden, not removed)
  • Manual: aimx --version still prints the custom banner via handle_version_flag
  • aimx <subcommand> --help unchanged (each subcommand keeps its own auto-rendered help)

🤖 Generated with Claude Code

Splits the flat 14-command list into "Operations (as current user)" and
"Server administration" sections so newcomers can orient themselves
quickly. Per-user verbs (send, mailboxes, hooks, agents, mcp) are
clearly separated from operator/root verbs (setup, serve, doctor, logs,
dkim-keygen, portcheck, uninstall, upgrade).

Implementation notes:
- Clap 4.6 does not support `help_heading` on `Subcommand` variants, so
  the parent's `help_template` omits `{subcommands}` and slots a hand-
  rolled grouped list in via `{after-help}`. The list lives in a
  `SUBCOMMAND_HELP` constant with a comment reminding maintainers to
  keep it in sync with the `Command` enum.
- Replaced `about` with the new tagline ("The internet's oldest
  protocol, rebuilt for AI agents.") and dropped `long_about` so `-h`
  and `--help` render identically.
- Hid `ingest` from --help (it is only ever invoked by `aimx serve`
  over stdin, never by humans). The subcommand still parses and runs.
- Added a `Cli::version` field purely so `-V, --version` renders in the
  Options block; the existing `handle_version_flag` interceptor in
  `main.rs` still handles the actual flag before `Cli::parse()` runs.
- Shortened every subcommand description to one tight line.

@uzyn uzyn left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes Overview

Splits aimx --help into two audience-grouped sections — Operations (as current user) for per-user verbs and Server administration for operator/root verbs — by replacing clap's auto-rendered subcommand list with a hand-rolled SUBCOMMAND_HELP constant injected via {after-help}. Also retitles the tagline, drops long_about so -h and --help render identically, hides the internal ingest subcommand from top-level help, adds an explicit -V, --version flag to the parser purely so it shows in the Options block, and tightens every subcommand's one-line description.

Scope Alignment

Implementation matches the PR description exactly. Every claim verified locally:

  • aimx --help and aimx -h render the new grouped layout, -V, --version appears in Options.
  • New tagline renders.
  • aimx --version and aimx -V still print the custom version_string() banner via handle_version_flag (intercepted in main.rs before Cli::parse() runs).
  • aimx ingest --help still works (subcommand wired, just hidden from the top-level list).
  • aimx mailboxes --help and other subcommand help pages are unchanged.
  • aimx serve --version is correctly rejected by clap (the new version: bool field is parent-only, not global).
  • cargo build, cargo test --test integration help_shows_subcommands, cargo clippy -- -D warnings, cargo fmt -- --check all clean.

No scope creep: the diff touches exactly two files (src/cli.rs, tests/integration.rs) and is limited to help-surface concerns.

Potential Bugs

None found in the changed code.

One pre-existing edge case worth noting (not introduced by this PR, since handle_version_flag is unchanged): aimx --data-dir /tmp --version does not print the banner. handle_version_flag stops scanning at /tmp because it can't tell /tmp is the value for --data-dir. Before this PR clap rejected this with "unexpected argument --version"; after this PR the new parent-level version field is consumed but no subcommand follows, so clap errors with "command required" instead. Both are error states, both pre-date this PR, and the message change is a minor UX shift in an already-unfriendly path. Mentioning here only because the PR brings the --version flag into the parser surface. Non-blocker.

Test Coverage

The integration test help_shows_subcommands was updated correctly:

  • Adds positive assertions for the two new headings.
  • Adds a negative assertion that ingest no longer appears in --help.
  • Keeps assertions for each visible subcommand name.

Two minor gaps, both non-blocking:

  • The test does not verify the order of sections (Operations before Server administration) or that subcommands appear under their intended heading. A maintainer who accidentally swapped the two halves of SUBCOMMAND_HELP would still pass the test. A simple find("Operations") < find("Server administration") < find("send") ordering check would close this.
  • No assertion that -V, --version appears in the Options block, even though the PR explicitly adds a Cli::version field to make that line render.

Code Quality

  • SUBCOMMAND_HELP is a hand-rolled string that must stay in sync with the Command enum. The PR comment in cli.rs:6-10 acknowledges this and asks maintainers to keep it in sync. For a 14-command surface that changes rarely this is acceptable; an automated drift guard (a unit test that walks Command::clap() and asserts each non-hidden variant is mentioned in SUBCOMMAND_HELP) would make the constant self-maintaining, but it's a nice-to-have, not required.
  • Cli::version is #[allow(dead_code)] with a clear comment explaining why. Reasonable trade-off given that clap forces a struct field if you want the flag rendered.
  • help_template correctly omits {subcommands} since {after-help} now carries that role; rendered output has no double-blank-lines or trailing whitespace.

Security Issues

None — this PR does not touch any code path that handles input, auth, networking, or filesystem access.

Summary and Recommended Actions

Overall verdict: Ready to merge.

  • Blockers: none
  • Non-blockers: none
  • Nice-to-haves:
    • Add an ordering assertion to help_shows_subcommands so an accidental swap of the two headings is caught.
    • Add an assertion that -V, --version appears in the --help Options block.
    • Consider a unit test that compares Command variants against SUBCOMMAND_HELP to detect drift when new subcommands are added.

Recommended merge commit message

Group `aimx --help` subcommands by audience

Splits the flat 14-command list in `aimx --help` into "Operations (as
current user)" (send, mailboxes, hooks, agents, mcp) and "Server
administration" (setup, serve, doctor, logs, dkim-keygen, portcheck,
uninstall, upgrade) so newcomers can orient themselves at a glance.

Implementation:
- Clap 4 doesn't allow `help_heading` on subcommand variants, so the
  parent's `help_template` omits `{subcommands}` and slots a hand-rolled
  grouped list in via `{after-help}`. The list lives in a
  `SUBCOMMAND_HELP` constant with a maintainer comment.
- New tagline ("The internet's oldest protocol, rebuilt for AI agents.")
  replaces the old `about`/`long_about` pair; the multi-paragraph
  description it carried lives in the README.
- Hides `ingest` from top-level `--help` (still wired and invoked by
  `aimx serve` over stdin).
- Declares `Cli::version` purely so `-V, --version` renders in the
  Options block; `handle_version_flag` in `main.rs` still intercepts
  the flag before `Cli::parse()` runs.
- Tightens every subcommand's one-line description.

Integration test updated to assert the new headings and that `ingest`
is hidden.

The hand-rolled `after_help` listing in `src/cli.rs` replaces clap's
default `Commands:` heading with audience-grouped sections
("Operations (as current user):", "Server administration:"), so the
verb extractor stopped finding any subcommands and the script bailed
with the "could not extract" guard.

Switch the awk to match any `^  <verb>  +<Description>` line so it
works against both the legacy and grouped layouts. Also allowlist
`ingest`, which is now `#[command(hide = true)]` but is still a real
CLI entry point documented in book/cli.md and book/hook-recipes.md.
@uzyn uzyn merged commit 1620e2e into main May 5, 2026
8 checks passed
@uzyn uzyn deleted the worktree-happy-wandering-tide branch May 5, 2026 13:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant