
Cli
- 3 installs
- 19 repo stars
- Updated August 1, 2026
- xobotyi/cc-foundry
Helps with ai & agent building tasks.
About
cli is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- cli
- AI & Agent Building
- AI-coding skill
Cli by the numbers
- 3 all-time installs (skills.sh)
- Ranked #13,677 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/xobotyi/cc-foundry --skill cliAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 19 |
| Last updated | August 1, 2026 |
| Repository | xobotyi/cc-foundry ↗ |
What it does
Helps with ai & agent building tasks.
Files
CLI Design
Programs are composable by default. stdout carries data, stderr carries diagnostics, exit codes carry status, and signals carry intent. Get the boundaries right and everything else follows.
References
| Topic | Reference | Contents |
|---|---|---|
| Arguments | [${CLAUDE_SKILL_DIR}/references/arguments.md] | Full POSIX guidelines, GNU long option table, subcommand patterns, flag design |
| Output | [${CLAUDE_SKILL_DIR}/references/output.md] | Stream separation details, color codes, ANSI escapes, NO_COLOR spec, pager setup |
| Exit codes | [${CLAUDE_SKILL_DIR}/references/exit-codes.md] | Standard/extended code tables, signal exit codes, partial success patterns |
| Interaction | [${CLAUDE_SKILL_DIR}/references/interaction.md] | TTY detection, prompting patterns, confirmation levels, progress display, error format |
| Configuration | [${CLAUDE_SKILL_DIR}/references/configuration.md] | Full hierarchy, XDG spec, env var catalog, config file formats, secret handling |
| Signals | [${CLAUDE_SKILL_DIR}/references/signals.md] | Full signal table, SIGPIPE handling, crash-only design, child process signals |
Output Streams
This is the single most important convention. Mixing data and diagnostics in stdout is the #1 way to break composability.
- stdout is data. Primary output goes to stdout -- query results,
computed values, formatted data. This is what gets piped to the next command or redirected to a file.
- stderr is diagnostics. Progress indicators, status messages, warnings,
errors, and debug output go to stderr. Users see stderr in the terminal even when stdout is redirected.
- Check each stream independently. stdout may be piped while stderr is
still a TTY. Adapt output format per-stream: human-readable when TTY, machine-parseable when piped.
- No animations when not a TTY. Disable spinners, progress bars, and
color when the target stream is not an interactive terminal. This prevents CI logs from becoming escape-code garbage.
- Print something within 100ms. Before any network request or long
operation, print a status line to stderr. Silence looks like a hang.
- Tell the user what changed. When a command modifies state, describe
what happened and suggest what to do next.
Arguments
- POSIX short flags, GNU long flags. Single-letter options use
-x,
multi-letter use --long-name. Every short flag must have a long equivalent. Reserve one-letter flags for frequently-used options.
- `--` terminates options. Everything after
--is an operand, even if
it starts with -. This is POSIX Guideline 10 and is non-negotiable.
- Prefer flags over positional arguments. Flags are self-documenting.
Exception: primary action on a single target (rm file.txt, cat file.txt). Two or more positional args for different things is a design smell.
- `-` means stdin/stdout. When a command accepts file arguments,
-
means read from stdin (or write to stdout when context is clear).
- Use standard flag names.
-h/--help,--version,-v/--verbose,
-q/--quiet, -f/--force, -n/--dry-run, -o/--output, --json, --no-color, --no-input. Don't reinvent these.
- Make flags order-independent. Users add flags by pressing up-arrow and
appending. mycmd --flag subcmd and mycmd subcmd --flag should both work where possible.
Subcommands
- `noun verb` or `verb noun`, pick one and be consistent. Don't mix
ordering styles. docker container create and docker image pull follow noun verb.
- Support help at every level.
mycmd --help,mycmd subcmd --help,
and mycmd help subcmd should all work.
- No catch-all subcommand. Don't interpret unknown first arguments as
an implicit default subcommand. This prevents you from ever adding new subcommands.
- No arbitrary abbreviations. If
mycmd ialiasesmycmd install,
you can never add mycmd init. Aliases must be explicit and stable.
Exit Codes
- 0 = success, 1 = error, 2 = usage error. This covers most programs.
Only define additional codes when callers need to distinguish specific failure modes.
- Signal exit codes use 128+N. SIGINT (Ctrl-C) exits 130, SIGTERM exits
143. Preserve this convention so calling scripts can distinguish "user cancelled" from "error."
- Never return 0 on failure. Scripts depend on exit codes via
$?and
set -e. A false success silently breaks pipelines.
Configuration
- **Flags > env vars > project config > user config > system config >
defaults.** This is the universal precedence hierarchy. A flag always wins over an env var, which always wins over a config file.
- Follow XDG for user-level files. Use
$XDG_CONFIG_HOME/myapp/(default
~/.config/myapp/), not ~/.myapp. Respect XDG_DATA_HOME, XDG_STATE_HOME, XDG_CACHE_HOME.
- Respect well-known environment variables.
NO_COLOR,FORCE_COLOR,
DEBUG, EDITOR, PAGER, HTTP_PROXY/HTTPS_PROXY, TMPDIR, HOME, TERM, LINES, COLUMNS. Check these before inventing app-specific alternatives.
- Never read secrets from environment variables. They leak through
ps,
Docker inspect, systemd, and process listings. Accept secrets via --password-file, stdin, or a credential helper.
- Never read secrets from flags.
--password=secretleaks intops
output and shell history. Use --password-file or stdin.
Color and Terminal Output
- Disable color when: stdout/stderr is not a TTY (check each
independently), NO_COLOR is set and non-empty, TERM=dumb, or --no-color is passed.
- Support `FORCE_COLOR` or `--color` to override detection and force
color output when the user explicitly wants it.
- Use color intentionally. Red for errors, green for success, yellow
for warnings. Don't paint everything -- if everything is colored, nothing stands out.
- Use a color library. Don't hand-code ANSI escape sequences. Libraries
handle NO_COLOR, TERM detection, and cross-platform differences.
Interactive Behavior
- Only prompt when stdin is a TTY. If stdin is not interactive, fail
with an error telling the user which flag to pass. Never hang waiting for input that will never come.
- Provide `--no-input`. An explicit flag to disable all prompts. Required
for CI/CD, cron, and automation.
- Confirm before destructive actions. Prompt for
y/Ninteractively,
require --force non-interactively. For severe operations (deleting infrastructure), require typing the resource name.
- Provide `--dry-run`. For any state-modifying command, let users
preview what would happen without executing it.
- Don't echo passwords. Disable terminal echo when accepting secret
input. Provide --password-file as a non-interactive alternative.
Signal Handling
- Respond to SIGINT immediately. Print "Shutting down..." before
starting cleanup. Add a timeout so cleanup can't hang forever.
- Second Ctrl-C skips cleanup. Tell the user: "press Ctrl+C again to
force." Then exit immediately.
- Handle SIGTERM like SIGINT. Process managers (systemd, Docker, K8s)
send SIGTERM before SIGKILL. Complete cleanup within the grace period.
- Handle SIGPIPE silently. When the user pipes your output to
head,
don't print an error when the pipe closes. Exit quietly with code 141 or 0.
- Design for crash recovery. Use atomic file operations (write to temp,
rename). Check for incomplete state on startup. Don't require cleanup to complete for correctness.
Help Text
- Support `-h` and `--help`. Both must show help. Don't overload
-h
for anything else.
- Show concise help with no arguments. When a command requires arguments
and gets none, show a brief description, one or two examples, and a pointer to --help for the full listing.
- Lead with examples. Users read examples first. Show common invocations
before the flag catalog.
- Suggest corrections. When the user types a close misspelling, suggest
the correct command. Don't auto-execute the suggestion.
- Support `--version`. Print the version and exit. Format: program name
and version, optionally with build metadata.
Error Messages
- Structure errors consistently. Use a format like:
error: <what went wrong> followed by hint: <how to fix it>.
- Be actionable. Don't say "permission denied" -- say "permission denied;
run with sudo or use --config-dir for a writable location."
- Put important information last. The eye lands at the bottom of output.
Put the error summary and fix there, not the stack trace.
- No stack traces by default. Put them behind
--verboseor write to a
debug log file. Signal-to-noise ratio matters.
- Make bug reporting easy. For unexpected errors, provide a URL or
command to file a bug, pre-populated with diagnostic information.
Structured Output
- Support `--json` for machine-readable output. Output well-formed JSON
to stdout. Use consistent field names across commands.
- Support `--plain` for scriptable tabular output. One record per line,
no color, no decorations. Useful for grep and awk pipelines.
- Use a pager for large output. Pipe through
less -FIRXwhen stdout
is a TTY. Respect the PAGER environment variable. Don't page when output fits one screen.
Robustness
- Validate input early. Check arguments and flags before starting work.
Report all validation errors at once, not one at a time.
- Make operations idempotent where possible. Running the same command
twice should produce the same result, not an error or doubled state.
- Make operations recoverable. If the program fails partway through, the
user should be able to hit up-arrow and enter to retry from where it left off.
- Set network timeouts. Allow timeout configuration and have a
reasonable default. Never hang indefinitely on a network call.
Naming
- Lowercase, short, memorable.
curlnotDownloadURL. Use only
lowercase letters and hyphens. Keep it easy to type.
- Avoid ambiguous subcommand names. Don't have both "update" and
"upgrade". Use distinct verbs or disambiguate with extra words.
Future-Proofing
- Keep changes additive. Add new flags rather than changing existing
behavior. If you must break compatibility, warn in advance.
- Warn before deprecation. When a user invokes a deprecated flag, tell
them what to use instead and when the old form will be removed.
- Changing human-readable output is OK. Encourage
--jsonor--plain
for scripts. Human-readable output is not a stable interface.
Application
When writing CLI code:
- Apply all conventions silently. Don't narrate each rule.
- Use an argument parsing library (clap, cobra, argparse, commander).
- Wire up stdout/stderr correctly from the start. Retrofitting is painful.
- Handle SIGINT/SIGTERM from day one. Don't defer signal handling.
When reviewing CLI code:
- Check stdout vs stderr usage first. This is the most common mistake.
- Verify exit codes match actual success/failure.
- Confirm
--help,--version, and--no-colorare supported. - Look for secret leaks in flags and env vars.
Integration
This skill covers CLI platform concerns -- the interface between a program and the terminal/shell/pipeline. Language-specific implementation details (argument parsing libraries, terminal I/O APIs) come from language skills (golang, javascript, etc.). The coding discipline skill governs general workflow.
Programs are composable when they respect boundaries.
{
"sources": {
"CLI Guidelines (clig.dev)": "https://raw.githubusercontent.com/cli-guidelines/cli-guidelines/main/content/_index.md",
"POSIX Utility Conventions": "https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html",
"GNU Coding Standards - CLI": "https://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html",
"GNU Argument Syntax": "https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html",
"GNU Exit Status": "https://www.gnu.org/software/libc/manual/html_node/Exit-Status.html",
"GNU Termination Signals": "https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html",
"NO_COLOR Specification": "https://raw.githubusercontent.com/jcs/no_color/master/index.md",
"ANSI Escape Codes Reference": "https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797/raw"
},
"lastFetched": "2026-02-20T21:29:03.563Z"
}
Argument Conventions
Detailed conventions for command-line arguments, flags, and subcommands. Extends the behavioral rules in SKILL.md with full specification details, edge cases, and extended examples.
POSIX Utility Syntax Guidelines
The POSIX guidelines (IEEE Std 1003.1-2017, Chapter 12) define the baseline for portable argument syntax:
1. Utility names should be 2-9 characters, inclusive. 2. Names should include only lowercase letters and digits. 3. Each option name is a single alphanumeric character. -W is reserved for vendor options. 4. All options must be preceded by -. 5. Options without arguments, followed by at most one option with an argument, can be grouped behind one -: -abc = -a -b -c. 6. Option and option-argument should be separate arguments, except when the option-argument is optional (then it must be adjacent). 7. Option-arguments should not be optional. 8. Multiple option-arguments for a single option use comma or blank separation within one argument. 9. All options should precede operands. 10. -- terminates options. Everything after is an operand. 11. Option order should not matter, unless mutually exclusive. 12. Operand order may matter (utility-specific). 13. - as an operand means stdin (or stdout from context). 14. If an argument looks like an option per guidelines 3-10, treat it as one.
GNU Long Options
GNU extends POSIX with --long-name options:
- Every single-letter option should have a long equivalent.
- Long options use
--name=valueor--name valuesyntax. - All programs should support
--versionand--help. - Output files should be specified with
-oor--output, not as bare
positional arguments.
Standard Long Option Names
The GNU Coding Standards define a [table of common long options][gnu-options] for consistency across programs. Key entries:
| Short | Long | Purpose |
|---|---|---|
-a | --all | All items |
-d | --debug | Debug output |
-f | --force | Force operation |
-h | --help | Display help |
-n | --dry-run | Simulate without executing |
-o | --output | Output file |
-p | --port | Port number |
-q | --quiet | Suppress non-essential output |
-u | --user | User |
-v | --verbose | Verbose output |
--version | Version information | |
--json | JSON output | |
--no-color | Disable color | |
--no-input | Non-interactive mode | |
--plain | Machine-readable tabular output |
[gnu-options]: https://www.gnu.org/prep/standards/html_node/Option-Table.html
Subcommand Patterns
For complex tools with multiple operations:
Two-Level Subcommands
When a tool has many objects and operations, use noun verb or verb noun:
docker container create # noun verb
docker container list
docker image pullBe consistent with verb naming across object types. Don't have both "update" and "upgrade" as subcommands -- disambiguate.
Subcommand Consistency
- Use the same flag names for the same concepts across subcommands.
- Maintain consistent output formatting across subcommands.
- Support
--helpon every subcommand. - Support
help <subcommand>as an alias for<subcommand> --help.
Avoid Ambiguous Shortcuts
Don't allow arbitrary abbreviations of subcommands. If mycmd i works as mycmd install, you can never add mycmd init. Aliases are fine but they must be explicit and documented.
No Catch-All Subcommand
Don't make a default subcommand that runs when no subcommand matches. This prevents you from ever adding new subcommands without breaking existing usage.
Flag Design Patterns
Flags vs Arguments
Prefer flags over positional arguments. Flags are self-documenting:
# Unclear: which is source, which is destination?
mycmd /path/a /path/b
# Clear:
mycmd --source /path/a --dest /path/bException: primary action on a single target (e.g., rm file.txt).
Boolean Flags
--flagenables,--no-flagdisables.- Default should be the safe/common choice.
- Never require
--flag=trueor--flag=false.
Secret Handling
Never accept secrets directly via flags (--password=secret) because:
- Values leak into
psoutput and shell history. - Encourages insecure env var patterns.
Instead:
- Accept
--password-file /path/to/file - Read from stdin
- Use a credential helper or keyring integration
Stdin Convention
If input or output is a file, support - to read from stdin or write to stdout:
curl https://example.com/data.tar.gz | tar xvf -Order Independence
Make flags and subcommands order-independent where possible. Users commonly add flags at the end by pressing up-arrow and appending.
Configuration
Detailed conventions for configuration hierarchy, environment variables, config files, and XDG directories. Extends the behavioral rules in SKILL.md with full patterns and edge cases.
Configuration Hierarchy
Configuration parameters are resolved in order of precedence (highest first):
1. Command-line flags -- most specific, per-invocation 2. Environment variables -- per-session or per-shell 3. Project-level config -- .env, project config files (version-controlled) 4. User-level config -- ~/.config/myapp/ (XDG) 5. System-wide config -- /etc/myapp/ 6. Built-in defaults -- hardcoded in the program
Higher levels override lower levels. A flag always wins over an env var, which always wins over a config file.
Choosing the Right Mechanism
| Configuration Type | Mechanism | Example |
|---|---|---|
| Varies per invocation | Flags | --verbose, --dry-run, --format json |
| Stable per session, varies per machine | Env vars + flags | HTTP_PROXY, NO_COLOR, EDITOR |
| Stable per project, shared in VCS | Config file | myapp.toml, Makefile, package.json |
| Stable per user, across projects | User config (XDG) | ~/.config/myapp/config.toml |
| Stable system-wide | System config | /etc/myapp/config.toml |
XDG Base Directory Specification
Follow the [XDG Base Directory Spec][xdg] for user-level files:
| Variable | Default | Purpose |
|---|---|---|
XDG_CONFIG_HOME | ~/.config | User configuration |
XDG_DATA_HOME | ~/.local/share | User data |
XDG_STATE_HOME | ~/.local/state | User state (logs, history) |
XDG_CACHE_HOME | ~/.cache | Non-essential cached data |
XDG_RUNTIME_DIR | (system-set) | Runtime files (sockets, PIDs) |
[xdg]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
Place files in $XDG_CONFIG_HOME/myapp/, not ~/.myapp. This reduces home directory clutter and follows the convention used by yarn, fish, neovim, tmux, and many modern tools.
Environment Variables
Naming
- Use
UPPERCASE_WITH_UNDERSCORES. - Prefix with your app name:
MYAPP_DEBUG,MYAPP_CONFIG. - Don't collide with [POSIX standard variables][posix-env].
[posix-env]: https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html
Well-Known Variables to Respect
| Variable | Purpose |
|---|---|
NO_COLOR | Disable color output |
FORCE_COLOR | Force color output |
DEBUG | Enable verbose/debug output |
EDITOR | User's preferred text editor |
PAGER | User's preferred pager (less, more) |
HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY | Network proxies |
SHELL | User's preferred interactive shell |
TERM, TERMINFO, TERMCAP | Terminal capabilities |
TMPDIR | Temporary file directory |
HOME | User's home directory |
LINES, COLUMNS | Terminal dimensions |
.env Files
Read .env files for project-level environment overrides:
- Use existing libraries (dotenv for Node, godotenv for Go, etc.)
- Don't treat
.envas a substitute for proper config files -- it has
one data type (string), no structure, and often contains secrets that shouldn't be in version control.
Secrets in Environment Variables
Do NOT read secrets from environment variables. They leak through:
psoutput and/proc/*/environ- Docker inspect
- systemd
systemctl show - Shell history via command substitution
- Log aggregation
Accept secrets via:
- Credential files (
--password-file) - stdin pipes
- Secret management services (Vault, AWS Secrets Manager)
- OS keyring integration
Config File Formats
Common Formats
| Format | Strengths | Use When |
|---|---|---|
| TOML | Human-readable, typed, hierarchical | General app config |
| YAML | Familiar to DevOps, supports comments | K8s/cloud ecosystem |
| JSON | Universal, no ambiguity | API config, when comments unnecessary |
| INI | Simple, flat | Legacy, very simple config |
Config File Design
1. Document every config option with comments in the default config file. 2. Support generating a default config: myapp config init. 3. Validate config on load, report all errors at once (not one at a time). 4. Show the resolved config: myapp config show (with merged values from all sources). 5. When modifying system config files that aren't yours, use dated comments to mark your additions.
Config Precedence Transparency
When debugging config issues, provide a way to see which source provided each value:
$ myapp config show --resolved
port = 8080 (from: --port flag)
debug = true (from: MYAPP_DEBUG env)
theme = dark (from: ~/.config/myapp/config.toml)
timeout = 30 (from: default)Exit Codes
Detailed conventions for process exit codes and signal-related exit behavior. Extends the behavioral rules in SKILL.md with full tables and edge cases.
Standard Exit Codes
| Code | Meaning | Usage |
|---|---|---|
0 | Success | Operation completed successfully |
1 | General error | Catch-all for unspecified failures |
2 | Usage error | Invalid arguments, missing required flags |
These three codes cover the vast majority of CLI tools. Only define additional codes when the caller (script, CI) needs to distinguish specific failure modes.
Extended Exit Codes
Some tools define richer exit code ranges. The most common convention (used by grep, diff, curl, and many others):
| Range | Meaning |
|---|---|
0 | Success |
1 | General/operational error |
2 | Command-line usage error |
3-125 | Application-specific errors |
126 | Command found but not executable |
127 | Command not found |
128+N | Terminated by signal N |
Signal Exit Codes
When a process is killed by a signal, the conventional exit code is 128 + signal_number:
| Signal | Number | Exit Code | Meaning |
|---|---|---|---|
| SIGHUP | 1 | 129 | Hangup |
| SIGINT | 2 | 130 | Interrupt (Ctrl-C) |
| SIGQUIT | 3 | 131 | Quit (Ctrl-\) |
| SIGABRT | 6 | 134 | Abort |
| SIGKILL | 9 | 137 | Kill (unblockable) |
| SIGTERM | 15 | 143 | Termination |
The shell sets $? to 128+N when a child is killed by signal N. If your program catches a signal and exits cleanly, use the conventional exit code to preserve the signal information for calling scripts.
Design Guidelines
Map exit codes to actionable failure modes:
0 = success
1 = runtime/operational error (network failure, file not found)
2 = usage error (bad arguments, missing required input)Document non-standard codes:
If your program uses codes beyond 0/1/2, document them in --help and man pages.
Exit code in scripts:
Scripts use $? or set -e to check exit codes. A program that returns 0 on partial failure will silently break pipelines.
Partial success:
If a program operates on multiple items and some succeed while others fail, choose a convention and document it:
- Return 0 if any succeeded (optimistic)
- Return 1 if any failed (pessimistic, usually safer)
- Return a specific code for partial failure
Boolean commands:
Commands that answer a yes/no question (test, grep):
0= true / match found1= false / no match2= error (couldn't perform the check)
Interactive Behavior
Detailed conventions for interactive and non-interactive modes, prompts, confirmation dialogs, progress display, and user input. Extends the behavioral rules in SKILL.md with patterns and edge cases.
TTY Detection
The fundamental mechanism for deciding between interactive and non-interactive behavior is checking whether stdin/stdout/stderr are connected to a terminal (TTY).
if stdin is TTY:
interactive input is possible (prompts, confirmations)
else:
running in a pipe or script -- no prompts
if stdout is TTY:
human-readable output (colors, tables, progress)
else:
piped -- machine-readable output, no decorations
if stderr is TTY:
show progress, spinners, interactive diagnostics
else:
plain diagnostics onlyCheck each stream independently. A common pattern is stdout piped to another program while stderr still shows progress in the terminal.
Prompting
When to Prompt
- Prompt when a required value is missing and stdin is a TTY.
- Prompt for confirmation before destructive or irreversible actions.
- Never prompt when stdin is not a TTY -- fail with an error telling
the user which flag to pass.
--no-input Flag
Always provide --no-input (or --non-interactive) to explicitly disable all interactive prompts. When set:
- Skip all prompts
- If required input is missing, fail with an error
- Use defaults where available
This is essential for:
- CI/CD pipelines
- Scripted automation
- Cron jobs
Password Input
When prompting for passwords or secrets:
- Disable echo (don't print characters as they're typed)
- Use platform-specific secure input APIs
- Provide a
--password-filealternative for scripted use
Confirmation Patterns
Danger Levels
Different levels of destructive action warrant different confirmation patterns:
Mild (delete a single file):
- Prompt with
y/N(default no), or skip if clearly intentional
(e.g., rm implies deletion)
Moderate (delete a directory, remote resource, bulk change):
- Prompt with
y/Nin interactive mode - Require
--forceor-fin non-interactive mode - Consider offering
--dry-runto preview the operation
Severe (delete entire application, irreversible bulk operation):
- Require typing the name of the resource to confirm
- Support
--confirm="resource-name"for scripted use - Consider a mandatory
--forceflag even in interactive mode
Dry Run
Provide --dry-run or -n for any operation that modifies state:
- Show exactly what would change without making changes
- Use the same code paths as the real operation where possible
- Clearly label output as a dry run
Progress Display
Principles
- Print something within 100ms of starting any non-trivial operation.
- Before a network request, print what you're about to do.
- Use spinners for indeterminate operations.
- Use progress bars when total size/count is known.
- Show estimated time remaining when possible.
- Animate to indicate the program is still working.
Progress on stderr
All progress indicators go to stderr:
- Spinners, progress bars, status messages
- This keeps stdout clean for data output
- When stderr is not a TTY, suppress animations entirely
Parallel Progress
When running multiple operations in parallel:
- Use a library that supports multiple progress bars (tqdm, indicatif)
- Ensure output doesn't interleave confusingly
- On error, show the full log for the failed operation
Ctrl-C Behavior
When the user hits Ctrl-C during a long operation: 1. Print a message immediately ("Shutting down...") 2. Begin cleanup with a timeout 3. On second Ctrl-C, skip cleanup and exit immediately 4. Tell the user what the second Ctrl-C will do
Example:
^C Gracefully stopping... (press Ctrl+C again to force)Error Display
Error Format
Structure error messages consistently:
error: <what went wrong>
hint: <how to fix it>Or with context:
error: cannot write to /etc/config
cause: permission denied
hint: run with sudo or use --config-dir to specify a writable locationError Guidelines
1. Put the most important information last (where the eye lands). 2. Use red sparingly -- only for the error label or critical detail. 3. Provide actionable hints: what to do next, which flag to pass. 4. Group similar errors under a single header rather than printing many similar lines. 5. For unexpected errors, include debug info and instructions for filing a bug report. 6. Don't show stack traces by default -- put them behind --verbose or write to a log file.
Letting Users Escape
- Make Ctrl-C work at all times, even during network I/O.
- If your program wraps another process where Ctrl-C doesn't propagate,
document the escape sequence.
- For multi-step wizards, support going back to the previous step.
Output Conventions
Detailed conventions for stdout, stderr, structured output, color, and terminal detection. Extends the behavioral rules in SKILL.md with full patterns and edge cases.
Stream Separation
stdout: Data Only
stdout carries the program's primary data output -- the content that gets piped to the next command or redirected to a file.
What goes to stdout:
- Query results, computed output, formatted data
- JSON/CSV/plain-text data when
--jsonor--plainis passed - Content that would be piped:
mycmd | grep pattern
What does NOT go to stdout:
- Progress indicators
- Status messages ("Processing file...")
- Warnings
- Errors
- Debug/log output
stderr: Diagnostics
stderr carries everything else: progress, status, diagnostics, errors.
This separation is critical because:
- Pipes connect stdout of one program to stdin of the next
- Progress bars in stdout corrupt downstream data
- Users see stderr in the terminal even when stdout is redirected
Detection and Adaptation
Check whether stdout and stderr are TTYs independently:
if stdout is TTY:
human-readable output to stdout
else:
machine-parseable output to stdout
if stderr is TTY:
colored diagnostics, progress bars to stderr
else:
plain diagnostics to stderr, no progress animationsStructured Output
--json Flag
Provide --json for machine-readable output:
- Output well-formed JSON to stdout
- One JSON object per logical result (or a JSON array)
- Use consistent field names across commands
- Include metadata fields (version, timestamp) when useful
--plain Flag
When human-readable output breaks machine parseability (e.g., wrapped table cells), provide --plain for simple tabular output:
- One record per line
- Tab or space delimited
- No color codes
- No progress indicators
Human-Readable Output
Success Output
- Display output on success, but keep it brief.
- If a command changes state, tell the user what happened.
- Suggest what to do next when commands form a workflow.
Progress Indication
- Print something within 100ms. If making a network request, say so before
starting.
- Use a spinner or progress bar for long operations.
- Show estimated time remaining when possible.
- Animate something to indicate the program is still working.
- Direct progress indicators to stderr so they don't interfere with piped
stdout.
- Disable animations when stderr is not a TTY (CI environments).
Pager Support
For large output, pipe through a pager (e.g., less -FIRX):
- Only when stdout is a TTY
- Respect the
PAGERenvironment variable less -FIRX: no paging if content fits one screen, case-insensitive
search, color passthrough, leaves content on screen after quit
Color Output
When to Use Color
- Use color intentionally: highlight important information, distinguish
errors, aid scannability.
- Don't overuse -- if everything is colored, nothing stands out.
- Put critical information at the end of output where the eye lands first.
- Use red sparingly and intentionally (errors, destructive actions).
Color Disable Conditions
Disable color output when ANY of these conditions is true:
1. stdout/stderr is not a TTY. Check each stream independently -- if piping stdout but stderr is still a terminal, keep stderr colors. 2. `NO_COLOR` is set and non-empty (regardless of value). This is the no-color.org standard. 3. `TERM=dumb` -- indicates a terminal without color support. 4. `--no-color` flag is passed. 5. `MYAPP_NO_COLOR` environment variable is set (app-specific override).
Color Enable Override
Support FORCE_COLOR or --color to force color output even when the above conditions would disable it. User-level config and per-instance flags override NO_COLOR.
NO_COLOR Specification
From no-color.org:
Command-line software which adds ANSI color to its output by default
should check for a NO_COLOR environment variable that, when presentand not an empty string (regardless of its value), prevents the addition
of ANSI color.
Key points:
- Check
NO_COLOR != "", notNO_COLOR == "1". NO_COLORonly affects color, not other styling (bold, underline, italic).- User-level config files and CLI flags may override
NO_COLOR.
ANSI Escape Code Basics
ANSI escape sequences start with ESC[ (hex \x1B[), known as CSI (Control Sequence Introducer).
Common SGR (Select Graphic Rendition) codes:
| Code | Effect |
|---|---|
0 | Reset all |
1 | Bold |
2 | Dim |
3 | Italic |
4 | Underline |
31 | Red foreground |
32 | Green foreground |
33 | Yellow foreground |
34 | Blue foreground |
Example: \x1B[1;31mERROR\x1B[0m prints "ERROR" in bold red, then resets.
Extended colors:
- 256-color:
\x1B[38;5;{ID}m(foreground),\x1B[48;5;{ID}m(background) - RGB:
\x1B[38;2;{r};{g};{b}m(foreground)
Use a color library rather than hand-coding escape sequences. Libraries handle detection, NO_COLOR, and cross-platform differences.
Signal Handling
Detailed conventions for Unix signal handling, graceful shutdown, and cleanup behavior. Extends the behavioral rules in SKILL.md with full signal tables and implementation patterns.
Common Signals
| Signal | Number | Default Action | Purpose |
|---|---|---|---|
SIGHUP | 1 | Terminate | Terminal hangup / config reload |
SIGINT | 2 | Terminate | Interrupt from keyboard (Ctrl-C) |
SIGQUIT | 3 | Core dump | Quit from keyboard (Ctrl-\) |
SIGABRT | 6 | Core dump | Abort signal (from abort()) |
SIGKILL | 9 | Terminate | Unblockable kill |
SIGPIPE | 13 | Terminate | Broken pipe (write to closed pipe) |
SIGTERM | 15 | Terminate | Polite termination request |
SIGTSTP | 20 | Stop | Terminal stop (Ctrl-Z) |
SIGCONT | 18 | Continue | Resume after stop |
SIGUSR1 | 10 | Terminate | User-defined |
SIGUSR2 | 12 | Terminate | User-defined |
SIGWINCH | 28 | Ignore | Terminal window size change |
SIGINT (Ctrl-C)
The most important signal to handle correctly:
1. Respond immediately. Print a message ("Shutting down...") before starting cleanup. 2. Cleanup with a timeout. Don't let cleanup hang forever. Set a deadline (e.g., 5 seconds). 3. Second Ctrl-C skips cleanup. Tell the user what the second press will do:
^C Gracefully stopping... (press Ctrl+C again to force)4. Exit with code 130 (128 + 2) to preserve signal information for calling scripts.
Implementation Pattern
on SIGINT:
if first_interrupt:
print "Shutting down... (Ctrl+C again to force)"
start cleanup with timeout
set first_interrupt = false
else:
exit immediately (code 130)SIGTERM
Polite termination request, typically from kill or process managers:
1. Handle identically to SIGINT (graceful shutdown + cleanup). 2. Exit with code 143 (128 + 15). 3. Process managers (systemd, Docker, Kubernetes) send SIGTERM first, then SIGKILL after a timeout. Complete cleanup within that window.
SIGPIPE
Sent when writing to a pipe whose read end is closed (e.g., when the user pipes your output to head):
1. Don't print an error. The user intentionally closed the pipe. 2. Exit cleanly. Most languages handle this automatically, but some (Python, Go) need explicit handling. 3. Exit with code 141 (128 + 13) or 0.
Language-Specific Notes
- Python:
BrokenPipeError-- catch and exit quietly. - Go: Ignore
SIGPIPEfor stdout; write errors returnEPIPE. - Rust: Default handler already exits quietly.
- Node.js:
process.stdout.on('error', ...)forEPIPE.
SIGHUP
Historically means the terminal hung up. Modern usage:
- Daemons: Reload configuration (conventional for long-running services).
- CLI tools: Treat as termination (same as SIGTERM).
SIGWINCH
Terminal window resized:
- If your output depends on terminal width (tables, progress bars),
re-read COLUMNS and LINES on SIGWINCH.
- Most TUI frameworks handle this automatically.
Crash-Only Design
Design your program so it can be killed at any point and recover on next run:
1. Use atomic file operations (write to temp, rename). 2. Use write-ahead logs or journals for multi-step operations. 3. Check for incomplete state on startup and recover. 4. Defer non-critical cleanup to the next run. 5. Avoid cleanup that must complete for correctness.
This makes your program both more robust and more responsive -- it can exit immediately on any signal.
Child Process Signals
When your program spawns child processes:
1. Forward SIGINT and SIGTERM to child process groups. 2. Wait for children to exit before exiting yourself. 3. Set a timeout: if children don't exit, send SIGKILL. 4. Report which children were killed if relevant.
Exit Code After Signal
When exiting due to a signal, use exit code 128 + signal_number:
SIGINT (2) -> exit 130
SIGTERM (15) -> exit 143
SIGPIPE (13) -> exit 141This lets calling scripts distinguish "user cancelled" from "error."