themadorg/madmail/add-cli-command
Claude Code and agent skill by themadorg/madmail
- community
Checking that the SKILL.md file is still in the repository.
Add a new madmail CLI command end-to-end: clap definition, ctl handler with --json output, DB settings keys, dispatch wiring, unit tests, E2E tests, tab-completion verification, and operator documentation. Use when the user asks to add, implement, or create a new madmail/chatmail subcommand, wire a CLI tool, or runs /add-cli-command.
Install
npx vskill@latest install themadorg/madmail/add-cli-commandSKILL.md
Add CLI Command
Implement a new `madmail` operator command following existing patterns in this repo. Production binary: `madmail`; dev crate binary: same name (cargo build -p chatmail → target/debug/madmail).
Do not ship a command without all four layers: clap + handler + tests + docs.
0. Orient — read before coding
Study commands closest to what you are adding:
| Pattern | Read these | |
|---|---|---|
| DB toggle (enable/disable/status) | service_toggle.rs, registration.rs, cli.rs ServiceToggleCommand | |
| DB setting (status/set/reset) | message_size.rs, language.rs, proxy.rs | |
| Nested subcommands | port.rs, proxy.rs, federation.rs | |
| Destructive + confirmation | delete_cmd.rs, accounts.rs (-y / --yes) | |
| Read-only status | status_cmd.rs, push.rs | |
| Config-file only | html.rs, version.rs |
Also read:
| File | Purpose | |
|---|---|---|
crates/chatmail-config/src/cli.rs | Command tree, global Args, parse tests | |
crates/chatmail/src/ctl/dispatch.rs | Routing, not_implemented list | |
crates/chatmail/src/ctl/output.rs | --json envelope (CtlOut) | |
crates/chatmail/src/ctl/context.rs | CtlContext, open_pool() | |
crates/chatmail/src/ctl/test_harness.rs | Unit-test helpers | |
crates/chatmail/src/ctl/ops_tests.rs | Dispatch test examples | |
tests/ctl_cli_e2e.rs | Subprocess + JSON E2E | |
docs/TDD/14-cli-tools.md | Parity matrix |
Check Madmail v1 parity if present: context/madmail/docs/chatmail/commands.md.
1. Pick the implementation pattern
| Type | When to use | Reuse | |
|---|---|---|---|
| Service toggle | Single __FOO_ENABLED__ bool | service_toggle::run() — only if label fits webimap/websmtp pattern; otherwise copy proxy.rs enable/disable | |
| Setting group | status / set / reset on one DB key | proxy.rs setting_set / setting_reset / setting_status | |
| Custom handler | Anything else | New ctl/<name>.rs module |
Naming conventions:
| Item | Rule | Example | |
|---|---|---|---|
| CLI name | kebab-case, Madmail parity | message-size, admin-token | |
| Rust module | snake_case | message_size.rs | |
| Clap variant | PascalCase | MessageSize | |
| Settings key | __SCREAMING_SNAKE__ | __SS_ENABLED__ in settings_keys.rs | |
| Doc page | kebab-case path | proxy-cipher-set.md | |
CtlOut command string | space-separated CLI path | "proxy cipher set" |
2. Clap definition (chatmail-config)
File: crates/chatmail-config/src/cli.rs
- Add variant to
Commandenum with doc comment. - Add subcommand enum(s) if nested.
- Use existing attributes consistently:
#[command(name = "kebab-name")]when Rust variant differs#[command(subcommand_required = false)]+Option<SubCmd>for defaultstatusvisible_aliases = ["alias"]for short names#[arg(short, long)] yes: boolfor destructive ops
- Export new types from
crates/chatmail-config/src/lib.rspub use cli::{...}when handlers need them.
Add parse tests in cli.rs #[cfg(test)] or ops_tests.rs:
Test aliases (pr → proxy) when added.
3. Settings key (if DB-backed)
File: crates/chatmail-db/src/settings_keys.rs
Only add keys stored in the settings table at runtime. Use set_setting, get_setting, get_bool_setting, delete_setting from chatmail_db.
Wire the key into the server reload path if the running daemon must pick it up (see how existing keys are read in the relevant crate).
4. Handler module (chatmail::ctl)
Files:
crates/chatmail/src/ctl/<name>.rs— new modulecrates/chatmail/src/ctl/mod.rs—mod <name>;crates/chatmail/src/ctl/dispatch.rs— import +matcharm +command_name()arm
Handler skeleton:
Rules:
- Never
println!directly — alwaysCtlOut. - Always branch on
out.is_json()for status commands with formatted human output. - Return
Err(ChatmailError::config(...))for operator errors;mainprints JSON error to stderr when--json. - Include reload hints in human text and
reload_required: truein mutation JSON whenmadmail reloadis needed. - Never echo passwords/secrets in JSON (see
proxy password status).
5. --json output (mandatory)
Every implemented subcommand must support global --json. Use CtlOut:
| Method | Use for | |
|---|---|---|
out.emit(data) | Status / read-only (JSON only prints; human uses line/blank) | |
out.done(human, data) | Simple mutation without message field | |
out.done_msg(human, data, message) | Mutations — adds "message" to envelope | |
out.aborted() | User declined -y confirmation |
Success envelope (stdout):
Error envelope (stderr):
`command` string is part of the contract — match it exactly in tests and json-output.md. Examples from real handlers:
"push status","proxy cipher set","webimap"(toggle mutations use service name)
Status command pattern:
Mutation pattern:
Destructive ops:
6. Dispatch wiring
File: crates/chatmail/src/ctl/dispatch.rs
use super::<module>;- Match arm:
Some(Command::MyCmd { cmd }) => my_cmd::my_cmd(&cli.args, cmd.as_ref()).await command_name()arm for the top-level name- Remove command from
not_implemented()message list once implemented - Update
docs/TDD/14-cli-tools.mdstatus → done
For simple toggles, wire directly:
7. Tests (mandatory)
A. Clap parse tests
Assert Cli::try_parse_from / parse_cli produces correct enum shape. Include alias coverage.
Run: cargo test -p chatmail-config and cargo test -p chatmail ctl
B. Dispatch unit tests (in-process)
Files: crates/chatmail/src/ctl/ops_tests.rs (settings/port/proxy), dispatch_tests.rs (accounts/destructive)
Use test_harness.rs:
Assert:
- DB state (
get_setting,get_bool_setting, SQL) - Error messages:
dispatch(&cli).await.unwrap_err().to_string().contains("expected") - Preconditions (e.g.
proxy enablefails when not configured) - Validation rejects bad input
For config-dependent commands, use write_ss_test_config() pattern or write a similar helper.
Do not rely on stdout in unit tests — assert DB/filesystem side effects.
C. --json unit or E2E tests
At minimum one test per command family verifying JSON envelope shape:
E2E pattern in tests/ctl_cli_e2e.rs:
Add E2E when the command has subprocess-visible behavior worth guarding (JSON contract, stdout markers).
Run: cargo test -p chatmail-integration --test ctl_cli_e2e --test ctl_ops_e2e
D. Tab completion
Completions are auto-generated from clap. After adding commands/aliases:
Add assertions in crates/chatmail/src/ctl/docs.rs when the command is top-level or has a non-obvious alias (see bash_completion_includes_proxy_subcommand, bash_completion_prefix_pr_matches_proxy).
8. Documentation (mandatory)
Follow the `update-cli-docs` skill (.grok/skills/update-cli-docs/SKILL.md) for the full doc workflow.
Minimum checklist for a new implemented command:
- [ ]
docs/guide/cli/<command>.mdparent page - [ ] Leaf pages for each subcommand (
<command>-<sub>.md) - [ ]
docs/guide/cli/json-output.mdsections with realdatafields from handler - [ ]
docs/guide/cli/README.mdentry in correct category section - [ ]
docs/TDD/14-cli-tools.md→ done, module name, settings keys - [ ] If top-level command or alias:
docs/man/madmail.1.scd→make man - [ ]
cd landing && bun run docs:tree(search index) - [ ] Cross-link related docs (e.g. feature TDD) when central to the topic
Verify help matches docs:
Never invent flags not present in clap.
9. Final verification
Run before considering the command done:
Manually spot-check --json on at least one status and one mutation subcommand.
10. Common pitfalls
| Pitfall | Fix | |
|---|---|---|
Parses but not implemented | Forgot dispatch.rs match arm | |
| Compile error | Forgot mod.rs registration | |
--json prints human text | Used println! instead of CtlOut | |
--json prints nothing on status | Missing out.is_json() branch | |
| JSON contract broken | Wrong CtlOut::from_args command string | |
| E2E hangs | Missing -y on destructive command | |
| Secrets in JSON | Omit values; return source / db_override only | |
| Docs out of sync | Re-run --help, update json-output.md anchors | |
not_implemented list stale | Update message in dispatch.rs | |
New toggle via service_toggle | Hardcoded "webimap"/"websmtp" labels — write custom handler if needed |
11. File checklist
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryChecking.grok/skills/add-cli-command/SKILL.md
Popularity
- Repository stars
- 190
- Repository forks
- 20
- 7-day trend score
- 26
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/themadorg/madmail/add-cli-command)