- Every command should support a
--jsonflag for machine-parseable output - JSON goes to stdout; progress, warnings, and debug info go to stderr
- Keep JSON structures flat and types consistent across commands (e.g. ISO 8601 dates everywhere)
- Detect TTY and auto-switch between human-friendly tables and machine-friendly output
- Respect
NO_COLORenv var; support--no-color,--plain,--quietflags - Consider built-in filtering (
--jq,--query) to reduce need for external tools
- Use noun-verb hierarchy:
tool resource action --flags(nottool action-resource) - Running a bare noun (
tool resource) should list available subcommands - Prefer long-named flags over positional args (max 1 positional arg per command)
- Use standard flag names:
--help,--version,--verbose,--quiet,--output,--force,--dry-run - Support
--flag/--no-flagfor booleans,--for end-of-flags,-for stdin/stdout
- Use meaningful codes beyond just 0 and 1 (stay in the 0–125 range)
- Suggested scheme: 0=success, 2=bad args, 3=not found, 4=auth required, 5=conflict, 75=transient failure
- Avoid 126–255 (reserved shell meanings)
- Document exit codes in
--helpand optionally viatool help exit-codes - With
--json, pair exit codes with structured error objects on stdout
- Stderr errors should include: error code, title, description, fix suggestion, docs URL
- Structured errors on stdout should follow the same format flag as regular output
- Distinguish transient vs. permanent errors so agents know whether to retry
- Never hang waiting for input in non-TTY mode — fail with a clear message
- Always provide
--yes/--no-confirmto bypass interactive prompts
--helpshould include: description, usage pattern, all flags with types/defaults, and examples- Lead with examples — agents and humans both learn from them faster than flag descriptions
- Mark required vs. optional flags explicitly
- Use progressive disclosure: bare command →
--help→help <topic>→ full docs - Consider a
tool schema <command>subcommand returning JSON Schema of inputs/outputs
- Prefer declarative
apply/ensurecommands overcreatethat fails on duplicates - Provide
--if-not-existswhen full idempotency isn’t possible - Return distinct exit codes for conflicts (e.g. 5 = already exists)
- Support
--dry-runwith structured output showing planned changes - Require
--yesor dry-run first for destructive operations
- Precedence: command flags → env vars → project config → user config → system defaults
- Prefix env vars with your tool name:
MYCTL_ENV,MYCTL_REGISTRY - Document env vars in
--helpoutput
- Store skills in
.claude/skills/<name>/SKILL.md(project) or~/.claude/skills/<name>/(personal) - Frontmatter
descriptionmust be specific and action-oriented — Claude under-triggers, so list explicit scenarios - Use
allowed-tools: Bash(mytool *)to pre-authorize CLI commands - Use
$ARGUMENTSto capture user input from/skill-name <args> - Set
disable-model-invocation: truefor skills with side effects (deploy, delete) - Keep SKILL.md under 500 lines — only add context Claude doesn’t already have
- Always instruct Claude to use
--jsonfor parsing output - Document exit codes and recovery actions in the skill body
- Bundle complex multi-step workflows as bash scripts in
scripts/(token-efficient — only output enters context) - Implement feedback loops: run → validate → fix → repeat
- Use
context: forkfor long-running or isolated tasks