- Structure CLI apps with a clear command → handler → output pipeline. Separate argument parsing from business logic.
- Use subcommands for complex CLIs. Each subcommand should have its own help text, flags, and validation.
- Exit with meaningful codes: 0 for success, 1 for general errors, 2 for usage errors. Document exit codes.
- Write to stdout for output data, stderr for logs/progress/errors. This enables piping and redirection.
- Implement a config hierarchy: CLI flags > env vars > config file > defaults. Use XDG directories for config files.
- Add --json or --output=json flag for machine-readable output. Human-readable by default, structured when piped.
- Validate all inputs early and fail fast with clear error messages that include the invalid value and expected format.
- Support --verbose/-v and --quiet/-q flags. Default output should be minimal but informative.
- Add shell completion scripts (bash, zsh, fish). Most CLI frameworks generate these automatically.
- Use progress bars for long operations. Detect TTY and suppress progress in non-interactive mode.