From dh
Reviews CLI apps for exit codes, help/version flags, stdin/stdout/stderr separation, non-interactive operation, signal handling, and argument validation. Activates on argparse, click, typer, commander.js.
npx claudepluginhub jamie-bitflight/claude_skills --plugin development-harnessThis skill uses the workspace's default tool permissions.
Stack-specific rules loaded by `dh:code-reviewer` when CLI entrypoints are detected (argparse, click, typer, commander.js, or similar argument parsing libraries).
Guides designing, implementing, and reviewing CLI tools with contracts, help text, exit codes, stdout/stderr rules, and Python audit script.
Provides design and implementation patterns for CLI tools with modern UX: commands, flags, config precedence, output streams, error handling, signals, security, and distribution. Use for designing CLIs or reviewing UX.
Designs CLI surfaces including args/flags/subcommands/help/output/errors/config for new tools. Audits existing CLIs for consistency, composability, and agent ergonomics.
Share bugs, ideas, or general feedback.
Stack-specific rules loaded by dh:code-reviewer when CLI entrypoints are detected (argparse, click, typer, commander.js, or similar argument parsing libraries).
0 must be returned only on success — any error condition must produce a non-zero exit code0 after printing an error message is a blocking finding — tools in pipelines cannot detect the failure1 for general errors, 2 for usage/argument errors, 3+ for application-specific codes documented in --helpsys.exit(1) or process.exit(1) must be called on fatal errors, not just printing to stderr# WRONG: exits 0 even on error
def main():
try:
run()
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
# implicit exit 0
# RIGHT: non-zero on error
def main():
try:
run()
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
--help must be present and print usage information, option descriptions, and examples — the auto-generated help from argparse/click/typer is acceptable as a minimum--version must be present and print the version string matching pyproject.toml or package.json--help must exit 0; --version must exit 0stderrstdoutstdout breaks pipe usage — this is a blocking finding for tools that produce structured outputstderr with a non-zero exit codeinput(), readline, inquirer) must have a corresponding flag alternative (--flag value) for use in CI and scriptssys.stdin.isatty() or equivalent and skip interactive prompts in non-TTY mode130 by defaultatexit callbacksNO_COLOR environment variable is set (any non-empty value)sys.stdout.isatty() or equivalent before colorizing output--dry-run flag that shows what would happen without doing it--dry-run output must clearly distinguish what would be changed and what would remain untouched# WRONG: no --dry-run for destructive command
@app.command()
def delete_records(pattern: str):
records = find_records(pattern)
for r in records:
r.delete()
print(f"Deleted {len(records)} records")
# RIGHT: dry-run support
@app.command()
def delete_records(pattern: str, dry_run: bool = typer.Option(False, "--dry-run")):
records = find_records(pattern)
if dry_run:
for r in records:
print(f"Would delete: {r.id}")
print(f"Would delete {len(records)} records (dry run)")
return
for r in records:
r.delete()
print(f"Deleted {len(records)} records")
# WRONG: ANSI always on
print(f"\033[32mSuccess\033[0m")
# RIGHT: conditional color
import sys
USE_COLOR = sys.stdout.isatty() and not os.environ.get("NO_COLOR")
success = "\033[32mSuccess\033[0m" if USE_COLOR else "Success"
print(success)