cli-design
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCLI Design: Unix-Composable Command-Line Interfaces
CLI设计:Unix可组合式命令行接口
This skill covers language-agnostic CLI design principles. The rules about stream separation, exit codes, format flags, and composability apply regardless of implementation language.
For API contract stability and Hyrum's Law, see the skill. For config, env vars, and graceful shutdown, see the skill.
api-designtwelve-factorTypeScript implementation patterns are in the directory. Load them on demand when building a CLI in TypeScript:
resources/| Resource | Load when... |
|---|---|
| Implementing Result types, entry point wiring, formatters, logger, JSON envelope schemas |
| Writing Vitest tests for CLI behavior (streams, exit codes, pipes, contract tests) |
| Understanding Node.js buffering, NDJSON, signal handling, crash-only design |
本技能涵盖语言无关的CLI设计原则。流分离、退出码、格式标志和可组合性相关规则适用于所有实现语言。
如需了解API契约稳定性和海勒姆定律,请查看技能。如需配置、环境变量和优雅停机相关内容,请查看技能。
api-designtwelve-factorTypeScript实现模式存放在目录中。使用TypeScript构建CLI时,可按需加载:
resources/| 资源 | 加载时机... |
|---|---|
| 实现Result类型、入口点连接、格式化器、日志器、JSON信封模式时 |
| 为CLI行为编写Vitest测试(流、退出码、管道、契约测试)时 |
| 理解Node.js缓冲、NDJSON、信号处理、崩溃仅设计时 |
When to Use
适用场景
- Building any command-line tool (any language)
- Designing command tree, flags, and I/O contracts
- Implementing the output layer (format detection, stream routing)
- Testing CLI behavior (stdout/stderr separation, exit codes)
- Reviewing a CLI for Unix composability
- 构建任意命令行工具(任意语言)
- 设计命令树、标志和I/O契约
- 实现输出层(格式检测、流路由)
- 测试CLI行为(stdout/stderr分离、退出码)
- 评审CLI的Unix可组合性
Core Principle
核心原则
stdout is for DATA only — the product the user asked for.
stderr is for EVERYTHING ELSE — diagnostics, progress, spinners, warnings, errors.
This separation is what makes work. One spinner character on stdout breaks every downstream pipe.
mycli --json | jq ..."Whatever software you're building, you can be absolutely certain that people will use it in ways you didn't anticipate. Your software will become a part in a larger system — your only choice is over whether it will be a well-behaved part." — clig.dev
stdout仅用于DATA——即用户请求的产物。
stderr用于所有其他内容——诊断信息、进度、加载动画、警告、错误。
这种分离正是能够正常工作的原因。stdout中哪怕一个加载动画字符都会破坏下游所有管道。
mycli --json | jq ..."无论你构建什么软件,都可以确定人们会以你未曾预料的方式使用它。你的软件会成为更大系统的一部分——你唯一能选择的是它是否会成为一个行为良好的组件。" —— clig.dev
The Unix Stream Contract
Unix流契约
| Content | Stream | Why |
|---|---|---|
| Primary output (data, results, JSON) | stdout | Pipeable, buffered for throughput |
| Progress bars, spinners, status | stderr | Not data — must not corrupt pipes |
| Warnings, errors, diagnostics | stderr | Visible to user even when stdout is piped |
| Debug/verbose output | stderr | Diagnostic, never data |
Buffering behavior:
- stdout: line-buffered when connected to a TTY, block-buffered when piped (~2x faster than stderr)
- stderr: unbuffered — every write is a syscall (immediate but expensive)
- Check each stream independently — stdout being piped does not mean stderr is piped
When stdout is piped, the user doesn't want your status messages in their data. All non-data output must go to stderr.
For a deep dive on buffering behavior and performance implications, see .
resources/stream-contracts.md| 内容 | 流 | 原因 |
|---|---|---|
| 主输出(数据、结果、JSON) | stdout | 可管道化、为吞吐量做缓冲 |
| 进度条、加载动画、状态 | stderr | 不属于数据——不得破坏管道 |
| 警告、错误、诊断信息 | stderr | 即使stdout被管道传输,用户仍能看到 |
| 调试/详细输出 | stderr | 诊断用途,绝非数据 |
缓冲行为:
- stdout:连接到TTY时为行缓冲,被管道传输时为块缓冲(速度比stderr快约2倍)
- stderr:无缓冲——每次写入都是系统调用(即时但开销大)
- 独立检查每个流——stdout被管道传输不代表stderr也被管道传输
当stdout被管道传输时,用户不希望数据中混入状态消息。所有非数据输出必须发送到stderr。
如需深入了解缓冲行为及其性能影响,请查看。
resources/stream-contracts.mdKeep Handlers Pure
保持处理器纯净
The practical rule: functions that do the work should return data, not write to stdout. The CLI entry point handles all I/O.
Entry point (CLI main) Your logic (handlers)
───────────────────── ─────────────────────
parse args (input) → structured result
detect format (json/plain/human) no printing to stdout
call handler no writing to stderr
format the result no calling exit
write to correct stream just returns data
set exit codeThis isn't an architecture mandate — it's just clean function design. The benefits are concrete:
- Testable without subprocess spawning — call the handler, assert on the returned value
- Format flexibility for free — same data renders as JSON, plain text, or coloured tables by swapping one function
- Reusable — the same handler works from a CLI, MCP server, HTTP API, or programmatic import
For simple CLIs where the "handler" is just calling a library, this separation already exists naturally — your library returns data, your CLI formats it. No extra layers needed.
If your project uses hexagonal architecture, the mapping is direct: the CLI entry point is a driving adapter, and the handler is a use case that returns a result through a port. See the skill — the patterns reinforce each other, but hex arch is not required to benefit from keeping handlers pure.
hexagonal-architectureFor TypeScript implementation patterns (Result types, entry point wiring, formatters, logger interfaces), see .
resources/output-architecture.md实用规则:执行业务逻辑的函数应返回数据,而非写入stdout。CLI入口点处理所有I/O操作。
入口点(CLI主函数) 业务逻辑(处理器)
───────────────────── ─────────────────────
解析参数 (输入) → 结构化结果
检测格式(json/plain/human) 不向stdout打印内容
调用处理器 不向stderr写入内容
格式化结果 不调用exit
写入正确流 仅返回数据
设置退出码这不是架构强制要求——只是清晰的函数设计。带来的好处很具体:
- 无需生成子进程即可测试——调用处理器,断言返回值
- 免费获得格式灵活性——通过替换一个函数,相同数据可渲染为JSON、纯文本或彩色表格
- 可复用——同一处理器可用于CLI、MCP服务器、HTTP API或程序化导入场景
对于简单CLI,其“处理器”只是调用库,这种分离天然存在——库返回数据,CLI负责格式化。无需额外层级。
如果项目采用六边形架构,映射关系直接:CLI入口点是驱动适配器,处理器是用例,通过端口返回结果。请查看技能——这些模式相辅相成,但无需六边形架构也能从保持处理器纯净中获益。
hexagonal-architecture如需TypeScript实现模式(Result类型、入口点连接、格式化器、日志器接口),请查看。
resources/output-architecture.mdFormat Flag Contract
格式标志契约
Three-tier output hierarchy:
三层输出层级:
Default: Human-Readable
默认:人类可读
- Colors, tables, formatted text
- Progress bars and spinners on stderr
- Output tailored for terminal width
- May change between versions — this is not a contract
- 颜色、表格、格式化文本
- 进度条和加载动画输出到stderr
- 输出适配终端宽度
- 版本间可能变化——这不属于契约内容
--plain
: Grep/Awk-Friendly
--plain--plain
:适合Grep/Awk
--plain- One record per line, no formatting, no colors
- Stable between minor versions — this is a contract
- Flat table rows, no borders, no grouped sections
- Enables:
mycli list --plain | grep error | wc -l
"Encourage your users to useor--plainin scripts to keep output stable." — clig.dev--json
- 每行一条记录,无格式、无颜色
- 小版本间保持稳定——这属于契约内容
- 扁平化表格行,无边框、无分组区域
- 支持:
mycli list --plain | grep error | wc -l
"鼓励用户在脚本中使用或--plain以保持输出稳定。" —— clig.dev--json
--json
: Structured Data
--json--json
:结构化数据
--json- stdout contains ONLY valid JSON — no spinners, no color, no progress
- stderr continues normally — human diagnostics still visible
- Errors are structured JSON too — not just success responses
- Schema is versioned — breaking changes to JSON output are breaking changes to the CLI
- implies non-interactive regardless of TTY
--json
Consistent envelope:
json
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "CONFIG_MISSING", "message": "...", "fix": "..." } }- stdout仅包含有效JSON——无加载动画、无颜色、无进度信息
- stderr正常输出——人类可读的诊断信息仍可见
- 错误也以结构化JSON形式返回——不仅是成功响应
- 模式带版本——JSON输出的破坏性变更属于CLI的破坏性变更
- 意味着非交互模式,无论是否为TTY
--json
统一信封格式:
json
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "CONFIG_MISSING", "message": "...", "fix": "..." } }NDJSON for Streaming
NDJSON用于流式传输
For large datasets, use NDJSON (one JSON object per ):
\n- Each line is independently parseable
- Include a field per record for multiplexing events
type - Final line can be a summary record
- Enables:
mycli run --format ndjson | while read -r line; do ...; done
For NDJSON specification details, see .
resources/stream-contracts.md对于大型数据集,使用NDJSON(每行一个JSON对象):
- 每行可独立解析
- 每条记录包含字段用于事件多路复用
type - 最后一行可为汇总记录
- 支持:
mycli run --format ndjson | while read -r line; do ...; done
如需NDJSON规范细节,请查看。
resources/stream-contracts.mdExit Codes
退出码
| Code | Meaning | When |
|---|---|---|
| 0 | Success | Operation completed as expected |
| 1 | Domain failure | Tool-specific failure (e.g. quality threshold not met) |
| 2 | Invalid usage | Bad flags, missing required args, validation error |
| 78 | Configuration error | Invalid config file, missing required config |
| 75 | Temporary failure | Network timeout, service unavailable — retry may help |
| 130 | SIGINT | User pressed Ctrl-C (128 + 2) |
| 143 | SIGTERM | Process terminated (128 + 15) |
Rules:
- Non-zero exit code MUST have a stderr explanation
- Document exit codes in
--help - Never use codes above 125 for application errors (reserved for signals: 128 + signal number)
- Exit code 75 (transient) is critical — it tells retry logic the failure may be temporary
- Map non-zero codes to the most important failure modes for your tool
| 代码 | 含义 | 适用场景 |
|---|---|---|
| 0 | 成功 | 操作按预期完成 |
| 1 | 业务域失败 | 工具特定失败(如质量阈值未达标) |
| 2 | 无效用法 | 错误标志、缺少必填参数、验证错误 |
| 78 | 配置错误 | 无效配置文件、缺少必填配置 |
| 75 | 临时失败 | 网络超时、服务不可用——重试可能解决 |
| 130 | SIGINT | 用户按下Ctrl-C(128 + 2) |
| 143 | SIGTERM | 进程被终止(128 + 15) |
规则:
- 非零退出码必须在stderr中给出解释
- 在中记录退出码
--help - 应用错误绝不要使用125以上的代码(为信号保留:128 + 信号编号)
- 退出码75(临时)至关重要——它告诉重试逻辑失败可能是暂时的
- 将非零代码映射到工具最重要的失败模式
TTY Detection
TTY检测
Check priority order (first match wins):
| Priority | Condition | Effect |
|---|---|---|
| 1 | | Non-interactive, no color, no animation |
| 2 | | Disable color (output may still be interactive) |
| 3 | | Disable color |
| 4 | | Enable color regardless |
| 5 | | Disable color and animations |
| 6 | | No interactive prompts |
| 7 | stdout is not a TTY ( | Plain output, no animations on stdout |
| 8 | Default | Full interactive with colors |
Check stdout and stderr independently. When stdout is piped but stderr is a TTY, you can still show spinners on stderr while keeping stdout clean for the pipe consumer.
Optionally support for app-specific color override.
MYCLI_NO_COLOR检查优先级(匹配到第一个即生效):
| 优先级 | 条件 | 效果 |
|---|---|---|
| 1 | | 非交互模式,无颜色、无动画 |
| 2 | | 禁用颜色(输出仍可为交互模式) |
| 3 | | 禁用颜色 |
| 4 | | 强制启用颜色 |
| 5 | | 禁用颜色和动画 |
| 6 | | 无交互式提示 |
| 7 | stdout不是TTY ( | 纯文本输出,stdout无动画 |
| 8 | 默认 | 全交互模式,带颜色 |
独立检查stdout和stderr。当stdout被管道传输但stderr是TTY时,仍可在stderr显示加载动画,同时保持stdout干净供管道消费者使用。
可选支持作为应用特定的颜色覆盖项。
MYCLI_NO_COLORInput Design
输入设计
Flags Over Arguments
标志优先于参数
- 1 positional arg: acceptable (the "main thing")
- 2 positional args: suspicious — consider flags instead
- 3+ positional args: never acceptable
Flags are self-documenting, order-independent, and future-proof.
bash
undefined- 1个位置参数:可接受(“主要对象”)
- 2个位置参数:需谨慎——考虑改用标志
- 3个及以上位置参数:绝不允许
标志自文档化、与顺序无关且面向未来。
bash
undefinedBad — which is source, which is destination?
不佳——哪个是源,哪个是目标?
mycli copy myapp backup
mycli copy myapp backup
Good — explicit
良好——明确清晰
mycli copy --from myapp --to backup
undefinedmycli copy --from myapp --to backup
undefinedStandard Flags
标准标志
Always provide long forms. Short flags only for the most common operations.
| Flag | Meaning |
|---|---|
| Show help (this should only mean help) |
| Print version to stdout |
| Suppress non-essential output |
| More detail in human output |
| Diagnostic output to stderr |
| Skip confirmation prompts |
| Show what would happen without doing it |
| Structured JSON output |
| Stable, grep-friendly plain text |
| Disable color output |
| Disable all prompts/interactivity |
| Output file |
始终提供长格式。仅为最常用操作提供短格式标志。
| 标志 | 含义 |
|---|---|
| 显示帮助(仅用于此用途) |
| 向stdout打印版本信息 |
| 抑制非必要输出 |
| 人类可读输出显示更多细节 |
| 向stderr输出诊断信息 |
| 跳过确认提示 |
| 显示预期操作但不执行 |
| 结构化JSON输出 |
| 稳定、适合Grep的纯文本 |
| 禁用颜色输出 |
| 禁用所有提示/交互 |
| 输出文件 |
Prompts and Interactivity
提示与交互
- All prompts MUST be bypassable via flags for scriptability
- Confirmation → or
--yes--force - Selection →
--type=value - Text input →
--name=value - Passwords → or stdin pipe
--password-file=path - If stdin is not a TTY, never prompt — fail with a clear error or use defaults
- Secrets via files/stdin/env only — never via flag values (they leak to output and shell history)
ps
- 所有提示必须可通过标志绕过,以支持脚本化
- 确认提示 → 或
--yes--force - 选择提示 →
--type=value - 文本输入 →
--name=value - 密码 → 或stdin管道
--password-file=path - 如果stdin不是TTY,绝不提示——返回清晰错误或使用默认值
- 仅通过文件/stdin/环境变量传递密钥——绝不通过标志值(会泄露到输出和shell历史)
ps
Conventions
约定
- Support to stop flag parsing:
--mycli run -- --flag-for-child-process - Support for stdin/stdout file arguments:
-curl ... | mycli process - - Accept both and
--flag=value--flag value - If stdin is expected but is an interactive terminal, display help immediately (don't hang like )
cat
- 支持停止标志解析:
--mycli run -- --flag-for-child-process - 支持作为stdin/stdout文件参数:
-curl ... | mycli process - - 同时支持和
--flag=value格式--flag value - 如果预期stdin但当前是交互式终端,立即显示帮助(不要像那样挂起)
cat
Config Precedence
配置优先级
Highest to lowest priority:
- Flags — per-invocation overrides
- Environment variables — prefix, per-session
MYCLI_* - Project config — ,
.myclirc, or inmycli.config.tspackage.json - User config — (follow XDG spec)
~/.config/mycli/ - Defaults — sensible built-in values
Rules:
- Follow the XDG Base Directory Specification for config file locations
- Env var naming: prefix, uppercase letters + digits + underscores
MYCLI_* - Never accept secrets via flags — use env vars, files, or stdin
- Read where appropriate, but don't use it as a substitute for proper config
.env - If you modify configuration that belongs to another program, ask consent first
从高到低:
- 标志——每次调用的覆盖项
- 环境变量——前缀,会话级
MYCLI_* - 项目配置——、
.myclirc或mycli.config.ts中的配置package.json - 用户配置——(遵循XDG规范)
~/.config/mycli/ - 默认值——合理的内置值
规则:
- 配置文件位置遵循XDG基础目录规范
- 环境变量命名:前缀,大写字母+数字+下划线
MYCLI_* - 绝不通过标志接受密钥——使用环境变量、文件或stdin
- 适当情况下读取,但不要将其作为正规配置的替代品
.env - 如果要修改属于其他程序的配置,先征得同意
Error Design
错误设计
Every error needs:
- Machine-readable code — (e.g.
UPPER_SNAKE_CASE,CONFIG_MISSING)AUTH_EXPIRED - What went wrong — context: which resource, operation, input
- How to fix it — exact command or action the user should take
- Reference — docs URL or (optional)
mycli help <topic>
每个错误需包含:
- 机器可读代码——格式(如
UPPER_SNAKE_CASE、CONFIG_MISSING)AUTH_EXPIRED - 错误内容——上下文:哪个资源、操作、输入出了问题
- 修复方法——用户应执行的确切命令或操作
- 参考——文档URL或(可选)
mycli help <topic>
Human Mode
人类模式
Error: CONFIG_MISSING — Configuration file not found
No configuration file found at ./mycli.config.ts or ~/.config/mycli/config.ts
Fix: Run `mycli init` to create a default configuration file
Docs: https://mycli.dev/docs/configuration- Put the most important information last (the eye is drawn to the end)
- Use red sparingly and intentionally
- Suggest corrections for typos ("Did you mean 'deploy'?")
- Group similar errors under one header — don't repeat 50 similar-looking lines
- Write debug logs to a file, not the terminal (unless )
--debug
Error: CONFIG_MISSING — 未找到配置文件
未在./mycli.config.ts或~/.config/mycli/config.ts找到配置文件
修复方法:运行`mycli init`创建默认配置文件
文档:https://mycli.dev/docs/configuration- 将最重要的信息放在最后(视线会被末尾内容吸引)
- 谨慎且有目的地使用红色
- 为拼写错误提供修正建议(“你是不是想输入'deploy'?”)
- 将相似错误归组到一个标题下——不要重复50条相似内容
- 将调试日志写入文件,而非终端(除非使用)
--debug
JSON Mode
JSON模式
Errors are structured too — not just success responses:
json
{
"ok": false,
"error": {
"code": "CONFIG_MISSING",
"message": "No configuration file found at ./mycli.config.ts",
"fix": "Run `mycli init` to create a default configuration file",
"transient": false
}
}The boolean tells retry logic whether the failure may be temporary.
transient错误也需结构化——不仅是成功响应:
json
{
"ok": false,
"error": {
"code": "CONFIG_MISSING",
"message": "未在./mycli.config.ts找到配置文件",
"fix": "运行`mycli init`创建默认配置文件",
"transient": false
}
}transientComposability Patterns
可组合性模式
Design for real-world pipes:
bash
undefined为实际管道场景设计:
bash
undefinedFilter structured output
过滤结构化输出
mycli list --json | jq '.data[] | select(.status == "failed")'
mycli list --json | jq '.data[] | select(.status == "failed")'
Stream results for large datasets
流式传输大型数据集结果
mycli run --format ndjson | while read -r line; do echo "$line" | jq '.file'; done
mycli run --format ndjson | while read -r line; do echo "$line" | jq '.file'; done
Feed stdin
传入stdin
cat previous-results.json | mycli report --format markdown
cat previous-results.json | mycli report --format markdown
Combine with other tools
与其他工具组合
mycli run --json | mycli diff --baseline previous.json
mycli run --json | mycli diff --baseline previous.json
Silent mode for CI — only exit code matters
CI静默模式——仅关注退出码
mycli check --quiet || echo "Check failed!"
mycli check --quiet || echo "检查失败!"
Chain: create outputs an identifier, next command uses it
链式调用:创建命令输出标识符,下一个命令使用它
mycli create --json | jq -r '.data.id' | xargs mycli deploy --id
mycli create --json | jq -r '.data.id' | xargs mycli deploy --id
Column selection for efficiency
列选择提升效率
mycli list --json --fields name,status,id | jq '.data[]'
mycli list --json --fields name,status,id | jq '.data[]'
Parallel processing
并行处理
mycli list --json --fields id | jq -r '.data[].id' | xargs -P4 mycli process --id
**Key patterns:**
- Create commands output identifiers so subsequent commands can chain
- List commands support `--fields` for column selection (reduces output size, critical for agent efficiency)
- `--quiet` for CI scripts that only care about the exit code
- NDJSON for streaming large datasets without buffering everything in memory
- `--dry-run` with `--json` outputs planned changes as structured data
---mycli list --json --fields id | jq -r '.data[].id' | xargs -P4 mycli process --id
**关键模式:**
- 创建命令输出标识符,以便后续命令链式调用
- 列表命令支持`--fields`进行列选择(减少输出大小,对代理效率至关重要)
- `--quiet`适用于仅关注退出码的CI脚本
- NDJSON用于流式传输大型数据集,无需将所有内容缓冲到内存
- `--dry-run`搭配`--json`以结构化数据形式输出计划变更
---Subcommand Design
子命令设计
- noun verb pattern is most common: ,
mycli config setmycli report generate - Be consistent across all subcommands — same flag names for same things
- No ambiguous pairs (vs
updateis confusing)upgrade - No catch-all subcommands (you can never add subcommands with conflicting names)
- No arbitrary abbreviations — aliases must be explicit and stable
- With no args: list subcommands (multi-command CLI) or show help (single-command CLI)
- 名词+动词模式最常见:、
mycli config setmycli report generate - 所有子命令保持一致——相同功能使用相同标志名称
- 避免模糊配对(和
update易混淆)upgrade - 不要使用万能子命令(无法添加名称冲突的子命令)
- 不要使用任意缩写——别名必须明确且稳定
- 无参数时:列出子命令(多命令CLI)或显示帮助(单命令CLI)
Help
帮助
- — top-level help
mycli --help - — subcommand help
mycli help <subcommand> - — same as above
mycli <subcommand> --help - If run with missing required args, show concise help + 1-2 examples + "use --help for more"
- Examples are the most-read section — lead with them
- Include flag types, defaults, and allowed values for finite sets
- ——顶层帮助
mycli --help - ——子命令帮助
mycli help <subcommand> - ——与上述效果相同
mycli <subcommand> --help - 如果运行时缺少必填参数,显示简洁帮助+1-2个示例+“使用--help查看更多”
- 示例是阅读量最高的部分——优先展示
- 包含标志类型、默认值和有限集合的允许值
Output Stability Contract
输出稳定性契约
Stdout is a public API. Breaking changes to stdout format are breaking changes to the CLI.
| Change | Impact |
|---|---|
| Adding new optional JSON fields | Safe (additive) |
| Adding new subcommands | Safe |
| Adding new flags with preserving defaults | Safe |
| Removing or renaming flags | Breaking |
| Removing or renaming JSON fields | Breaking |
| Changing exit codes | Breaking |
| Changing default behavior | Breaking |
| Changing human-readable output | Usually OK (not a contract) |
When in doubt, add alongside — don't modify. Deprecate with stderr warnings before removing.
Stdout是公共API。stdout格式的破坏性变更属于CLI的破坏性变更。
| 变更 | 影响 |
|---|---|
| 添加新的可选JSON字段 | 安全(增量变更) |
| 添加新子命令 | 安全 |
| 添加新标志且保留默认值 | 安全 |
| 删除或重命名标志 | 破坏性 |
| 删除或重命名JSON字段 | 破坏性 |
| 修改退出码 | 破坏性 |
| 修改默认行为 | 破坏性 |
| 修改人类可读输出 | 通常可接受(不属于契约) |
不确定时,新增而非修改。删除前先通过stderr发出弃用警告。
Anti-Patterns
反模式
| # | Anti-Pattern | Why It's Wrong |
|---|---|---|
| 1 | Mixing data and diagnostics on stdout | Breaks every pipe: |
| 2 | Colors/ANSI in piped output | ANSI sequences corrupt downstream parsing. Check |
| 3 | Interactive prompts with no flag bypass | Agents can't type 'y'. Every prompt needs |
| 4 | Printing nothing on success | Silence is ambiguous — show brief confirmation. Offer |
| 5 | Designing for humans OR machines, not both | Detect context (TTY vs pipe), adapt automatically |
| 6 | Output that doesn't guide the next action | Every output is a signpost: success = next command, failure = fix command |
| 7 | Breaking existing CLI contracts | Flag names, exit codes, output shape are contracts. Add alongside, never modify |
| 8 | | Handlers must return data; only the presentation layer writes to streams |
| 9 | Handlers that exit the process directly | Let the entry point decide. Handlers return errors as data |
| 10 | Non-zero exit without stderr explanation | Scripts need both the code and the reason |
| 11 | Verbose default output | A single test run can generate 419KB. Support |
| # | 反模式 | 错误原因 |
|---|---|---|
| 1 | stdout混合数据和诊断信息 | 破坏所有管道:如果警告在stdout, |
| 2 | 管道输出中包含颜色/ANSI序列 | ANSI序列会破坏下游解析。检查 |
| 3 | 交互式提示无标志绕过方式 | 代理无法输入'y'。每个提示都需要 |
| 4 | 成功时无任何输出 | 静默具有歧义——显示简短确认信息。为需要静默的脚本提供 |
| 5 | 仅为人类或仅为机器设计 | 检测上下文(TTY vs管道),自动适配 |
| 6 | 输出不引导后续操作 | 每个输出都应是路标:成功→下一个命令,失败→修复命令 |
| 7 | 破坏现有CLI契约 | 标志名称、退出码、输出格式都是契约。新增而非修改 |
| 8 | 除CLI适配器外的任何地方使用 | 处理器必须返回数据;仅展示层可写入流 |
| 9 | 处理器直接退出进程 | 让入口点决定退出逻辑。处理器以数据形式返回错误 |
| 10 | 非零退出但stderr无解释 | 脚本需要错误码和原因 |
| 11 | 默认输出过于冗长 | 单次测试运行可能生成419KB数据。支持 |
Verification Checklist
验证清单
After designing or reviewing a CLI:
- stdout has ONLY data; stderr has everything else
- Every command supports with consistent envelope
--json - Exit codes are semantic and documented in
--help - Every prompt has a /
--yes/--forcebypass--flag - Errors include: code, message, fix suggestion
- available for mutating commands
--dry-run - Progress/spinners go to stderr, never stdout
- ,
NO_COLOR, andTERM=dumbrespected--no-color - Piped output contains zero ANSI escape codes
- Success output includes next-action guidance
- Existing flags, exit codes, output fields never removed or renamed
- JSON schema is versioned (additions safe, removals breaking)
- Config follows flags > env > project > user > defaults
- Secrets accepted only via files/stdin/env, never via flags
- Startup < 500ms, print something in < 100ms
- Ctrl-C exits fast with bounded cleanup
- includes 2-3 realistic examples
--help - Human output is grep-parseable (flat rows, no table borders)
设计或评审CLI后:
- stdout仅包含数据;stderr包含所有其他内容
- 每个命令都支持且信封格式一致
--json - 退出码语义明确,并在中记录
--help - 每个提示都有/
--yes/--force绕过方式--flag - 错误包含:代码、消息、修复建议
- 变更命令支持
--dry-run - 进度/加载动画输出到stderr,绝不输出到stdout
- 遵守、
NO_COLOR和TERM=dumb设置--no-color - 管道输出不包含任何ANSI转义码
- 成功输出包含后续操作指引
- 绝不删除或重命名现有标志、退出码、输出字段
- JSON模式带版本(新增安全,删除破坏性)
- 配置遵循标志>环境变量>项目配置>用户配置>默认值优先级
- 仅通过文件/stdin/环境变量接受密钥,绝不通过标志
- 启动时间<500ms,100ms内输出内容
- Ctrl-C可快速退出,清理操作可控
- 包含2-3个真实示例
--help - 人类可读输出可被Grep解析(扁平化行,无表格边框)
Quick Reference
快速参考
Stream Routing
流路由
stdout ← data, results, JSON, NDJSON
stderr ← progress, spinners, warnings, errors, debug, promptsstdout ← 数据、结果、JSON、NDJSON
stderr ← 进度、加载动画、警告、错误、调试信息、提示Format Hierarchy
格式层级
Default (TTY) → colors, tables, formatted text
--plain → one record per line, stable, grep-friendly
--json → structured JSON, versioned schema
--format ndjson → streaming, one JSON object per line默认(TTY) → 颜色、表格、格式化文本
--plain → 每行一条记录,稳定,适合Grep
--json → 结构化JSON,带版本模式
--format ndjson → 流式传输,每行一个JSON对象Exit Codes
退出码
0 success
1 domain failure
2 invalid usage
75 temporary failure (retry)
78 config error
130 SIGINT (Ctrl-C)
143 SIGTERM0 成功
1 业务域失败
2 无效用法
75 临时失败(可重试)
78 配置错误
130 SIGINT(Ctrl-C)
143 SIGTERMConfig Precedence
配置优先级
flags > env vars > project config > user config > defaults标志 > 环境变量 > 项目配置 > 用户配置 > 默认值Flags Cheat Sheet
标志速查
-h --help Show help
--version Print version
-q --quiet Less output
-v --verbose More output
-d --debug Diagnostic output
-f --force Skip prompts
-n --dry-run Preview changes
--json Structured JSON
--plain Grep-friendly text
--no-color Disable color
--no-input No prompts
-o --output Output file
--fields Select columns-h --help 显示帮助
--version 打印版本
-q --quiet 减少输出
-v --verbose 增加输出
-d --debug 输出诊断信息
-f --force 跳过提示
-n --dry-run 预览变更
--json 结构化JSON输出
--plain 适合Grep的纯文本
--no-color 禁用颜色
--no-input 无提示
-o --output 输出文件
--fields 选择列