cli-design

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

CLI 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
api-design
skill. For config, env vars, and graceful shutdown, see the
twelve-factor
skill.
TypeScript implementation patterns are in the
resources/
directory. Load them on demand when building a CLI in TypeScript:
ResourceLoad when...
output-architecture.md
Implementing Result types, entry point wiring, formatters, logger, JSON envelope schemas
testing-cli.md
Writing Vitest tests for CLI behavior (streams, exit codes, pipes, contract tests)
stream-contracts.md
Understanding Node.js buffering, NDJSON, signal handling, crash-only design

本技能涵盖语言无关的CLI设计原则。流分离、退出码、格式标志和可组合性相关规则适用于所有实现语言。
如需了解API契约稳定性和海勒姆定律,请查看
api-design
技能。如需配置、环境变量和优雅停机相关内容,请查看
twelve-factor
技能。
TypeScript实现模式存放在
resources/
目录中。使用TypeScript构建CLI时,可按需加载:
资源加载时机...
output-architecture.md
实现Result类型、入口点连接、格式化器、日志器、JSON信封模式时
testing-cli.md
为CLI行为编写Vitest测试(流、退出码、管道、契约测试)时
stream-contracts.md
理解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
mycli --json | jq ...
work. One spinner character on stdout breaks every downstream pipe.
"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用于所有其他内容——诊断信息、进度、加载动画、警告、错误。
这种分离正是
mycli --json | jq ...
能够正常工作的原因。stdout中哪怕一个加载动画字符都会破坏下游所有管道。
"无论你构建什么软件,都可以确定人们会以你未曾预料的方式使用它。你的软件会成为更大系统的一部分——你唯一能选择的是它是否会成为一个行为良好的组件。" —— clig.dev

The Unix Stream Contract

Unix流契约

ContentStreamWhy
Primary output (data, results, JSON)stdoutPipeable, buffered for throughput
Progress bars, spinners, statusstderrNot data — must not corrupt pipes
Warnings, errors, diagnosticsstderrVisible to user even when stdout is piped
Debug/verbose outputstderrDiagnostic, 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.md

Keep 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 code
This 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
hexagonal-architecture
skill — the patterns reinforce each other, but hex arch is not required to benefit from keeping handlers pure.
For 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.md

Format 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
:适合Grep/Awk

  • 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 use
--plain
or
--json
in scripts to keep output stable." — clig.dev
  • 每行一条记录,无格式、无颜色
  • 小版本间保持稳定——这属于契约内容
  • 扁平化表格行,无边框、无分组区域
  • 支持:
    mycli list --plain | grep error | wc -l
"鼓励用户在脚本中使用
--plain
--json
以保持输出稳定。" —— clig.dev

--json
: Structured Data

--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
  • --json
    implies non-interactive regardless of TTY
Consistent envelope:
json
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "CONFIG_MISSING", "message": "...", "fix": "..." } }
  • stdout仅包含有效JSON——无加载动画、无颜色、无进度信息
  • stderr正常输出——人类可读的诊断信息仍可见
  • 错误也以结构化JSON形式返回——不仅是成功响应
  • 模式带版本——JSON输出的破坏性变更属于CLI的破坏性变更
  • --json
    意味着非交互模式,无论是否为TTY
统一信封格式:
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
    type
    field per record for multiplexing events
  • 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.md

Exit Codes

退出码

CodeMeaningWhen
0SuccessOperation completed as expected
1Domain failureTool-specific failure (e.g. quality threshold not met)
2Invalid usageBad flags, missing required args, validation error
78Configuration errorInvalid config file, missing required config
75Temporary failureNetwork timeout, service unavailable — retry may help
130SIGINTUser pressed Ctrl-C (128 + 2)
143SIGTERMProcess 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临时失败网络超时、服务不可用——重试可能解决
130SIGINT用户按下Ctrl-C(128 + 2)
143SIGTERM进程被终止(128 + 15)
规则:
  • 非零退出码必须在stderr中给出解释
  • --help
    中记录退出码
  • 应用错误绝不要使用125以上的代码(为信号保留:128 + 信号编号)
  • 退出码75(临时)至关重要——它告诉重试逻辑失败可能是暂时的
  • 将非零代码映射到工具最重要的失败模式

TTY Detection

TTY检测

Check priority order (first match wins):
PriorityConditionEffect
1
--format json
or
--json
flag
Non-interactive, no color, no animation
2
--no-color
flag
Disable color (output may still be interactive)
3
NO_COLOR
env (non-empty)
Disable color
4
FORCE_COLOR
env
Enable color regardless
5
TERM=dumb
Disable color and animations
6
CI=true
No interactive prompts
7stdout is not a TTY (
!isatty(stdout)
)
Plain output, no animations on stdout
8DefaultFull 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
MYCLI_NO_COLOR
for app-specific color override.

检查优先级(匹配到第一个即生效):
优先级条件效果
1
--format json
--json
标志
非交互模式,无颜色、无动画
2
--no-color
标志
禁用颜色(输出仍可为交互模式)
3
NO_COLOR
环境变量(非空)
禁用颜色
4
FORCE_COLOR
环境变量
强制启用颜色
5
TERM=dumb
禁用颜色和动画
6
CI=true
无交互式提示
7stdout不是TTY (
!isatty(stdout)
)
纯文本输出,stdout无动画
8默认全交互模式,带颜色
独立检查stdout和stderr。当stdout被管道传输但stderr是TTY时,仍可在stderr显示加载动画,同时保持stdout干净供管道消费者使用。
可选支持
MYCLI_NO_COLOR
作为应用特定的颜色覆盖项。

Input 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
undefined

Bad — which is source, which is destination?

不佳——哪个是源,哪个是目标?

mycli copy myapp backup
mycli copy myapp backup

Good — explicit

良好——明确清晰

mycli copy --from myapp --to backup
undefined
mycli copy --from myapp --to backup
undefined

Standard Flags

标准标志

Always provide long forms. Short flags only for the most common operations.
FlagMeaning
-h
,
--help
Show help (this should only mean help)
--version
Print version to stdout
-q
,
--quiet
Suppress non-essential output
-v
,
--verbose
More detail in human output
-d
,
--debug
Diagnostic output to stderr
-f
,
--force
Skip confirmation prompts
-n
,
--dry-run
Show what would happen without doing it
--json
Structured JSON output
--plain
Stable, grep-friendly plain text
--no-color
Disable color output
--no-input
Disable all prompts/interactivity
-o
,
--output
Output file
始终提供长格式。仅为最常用操作提供短格式标志。
标志含义
-h
,
--help
显示帮助(仅用于此用途)
--version
向stdout打印版本信息
-q
,
--quiet
抑制非必要输出
-v
,
--verbose
人类可读输出显示更多细节
-d
,
--debug
向stderr输出诊断信息
-f
,
--force
跳过确认提示
-n
,
--dry-run
显示预期操作但不执行
--json
结构化JSON输出
--plain
稳定、适合Grep的纯文本
--no-color
禁用颜色输出
--no-input
禁用所有提示/交互
-o
,
--output
输出文件

Prompts and Interactivity

提示与交互

  • All prompts MUST be bypassable via flags for scriptability
  • Confirmation →
    --yes
    or
    --force
  • Selection →
    --type=value
  • Text input →
    --name=value
  • Passwords →
    --password-file=path
    or stdin pipe
  • 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
    ps
    output and shell history)
  • 所有提示必须可通过标志绕过,以支持脚本化
  • 确认提示 →
    --yes
    --force
  • 选择提示 →
    --type=value
  • 文本输入 →
    --name=value
  • 密码 →
    --password-file=path
    或stdin管道
  • 如果stdin不是TTY,绝不提示——返回清晰错误或使用默认值
  • 仅通过文件/stdin/环境变量传递密钥——绝不通过标志值(会泄露到
    ps
    输出和shell历史)

Conventions

约定

  • Support
    --
    to stop flag parsing:
    mycli run -- --flag-for-child-process
  • Support
    -
    for stdin/stdout file arguments:
    curl ... | mycli process -
  • Accept both
    --flag=value
    and
    --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:
  1. Flags — per-invocation overrides
  2. Environment variables
    MYCLI_*
    prefix, per-session
  3. Project config
    .myclirc
    ,
    mycli.config.ts
    , or in
    package.json
  4. User config
    ~/.config/mycli/
    (follow XDG spec)
  5. Defaults — sensible built-in values
Rules:
  • Follow the XDG Base Directory Specification for config file locations
  • Env var naming:
    MYCLI_*
    prefix, uppercase letters + digits + underscores
  • Never accept secrets via flags — use env vars, files, or stdin
  • Read
    .env
    where appropriate, but don't use it as a substitute for proper config
  • If you modify configuration that belongs to another program, ask consent first

从高到低:
  1. 标志——每次调用的覆盖项
  2. 环境变量——
    MYCLI_*
    前缀,会话级
  3. 项目配置——
    .myclirc
    mycli.config.ts
    package.json
    中的配置
  4. 用户配置——
    ~/.config/mycli/
    (遵循XDG规范)
  5. 默认值——合理的内置值
规则:
  • 配置文件位置遵循XDG基础目录规范
  • 环境变量命名:
    MYCLI_*
    前缀,大写字母+数字+下划线
  • 绝不通过标志接受密钥——使用环境变量、文件或stdin
  • 适当情况下读取
    .env
    ,但不要将其作为正规配置的替代品
  • 如果要修改属于其他程序的配置,先征得同意

Error Design

错误设计

Every error needs:
  1. Machine-readable code
    UPPER_SNAKE_CASE
    (e.g.
    CONFIG_MISSING
    ,
    AUTH_EXPIRED
    )
  2. What went wrong — context: which resource, operation, input
  3. How to fix it — exact command or action the user should take
  4. Reference — docs URL or
    mycli help <topic>
    (optional)
每个错误需包含:
  1. 机器可读代码——
    UPPER_SNAKE_CASE
    格式(如
    CONFIG_MISSING
    AUTH_EXPIRED
  2. 错误内容——上下文:哪个资源、操作、输入出了问题
  3. 修复方法——用户应执行的确切命令或操作
  4. 参考——文档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
transient
boolean tells retry logic whether the failure may be temporary.

错误也需结构化——不仅是成功响应:
json
{
  "ok": false,
  "error": {
    "code": "CONFIG_MISSING",
    "message": "未在./mycli.config.ts找到配置文件",
    "fix": "运行`mycli init`创建默认配置文件",
    "transient": false
  }
}
transient
布尔值告诉重试逻辑失败是否可能是暂时的。

Composability Patterns

可组合性模式

Design for real-world pipes:
bash
undefined
为实际管道场景设计:
bash
undefined

Filter 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 set
    ,
    mycli report generate
  • Be consistent across all subcommands — same flag names for same things
  • No ambiguous pairs (
    update
    vs
    upgrade
    is confusing)
  • 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 set
    mycli report generate
  • 所有子命令保持一致——相同功能使用相同标志名称
  • 避免模糊配对(
    update
    upgrade
    易混淆)
  • 不要使用万能子命令(无法添加名称冲突的子命令)
  • 不要使用任意缩写——别名必须明确且稳定
  • 无参数时:列出子命令(多命令CLI)或显示帮助(单命令CLI)

Help

帮助

  • mycli --help
    — top-level help
  • mycli help <subcommand>
    — subcommand help
  • mycli <subcommand> --help
    — same as above
  • 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.
ChangeImpact
Adding new optional JSON fieldsSafe (additive)
Adding new subcommandsSafe
Adding new flags with preserving defaultsSafe
Removing or renaming flagsBreaking
Removing or renaming JSON fieldsBreaking
Changing exit codesBreaking
Changing default behaviorBreaking
Changing human-readable outputUsually 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-PatternWhy It's Wrong
1Mixing data and diagnostics on stdoutBreaks every pipe:
mycli list | jq .
fails if warnings are on stdout
2Colors/ANSI in piped outputANSI sequences corrupt downstream parsing. Check
isatty(stdout)
+
NO_COLOR
3Interactive prompts with no flag bypassAgents can't type 'y'. Every prompt needs
--yes
/
--force
. Non-TTY without bypass = hang
4Printing nothing on successSilence is ambiguous — show brief confirmation. Offer
-q
for scripts that want silence
5Designing for humans OR machines, not bothDetect context (TTY vs pipe), adapt automatically
6Output that doesn't guide the next actionEvery output is a signpost: success = next command, failure = fix command
7Breaking existing CLI contractsFlag names, exit codes, output shape are contracts. Add alongside, never modify
8
console.log
anywhere except the CLI adapter
Handlers must return data; only the presentation layer writes to streams
9Handlers that exit the process directlyLet the entry point decide. Handlers return errors as data
10Non-zero exit without stderr explanationScripts need both the code and the reason
11Verbose default outputA single test run can generate 419KB. Support
--fields
,
--quiet
,
--json

#反模式错误原因
1stdout混合数据和诊断信息破坏所有管道:如果警告在stdout,
mycli list | jq .
会失败
2管道输出中包含颜色/ANSI序列ANSI序列会破坏下游解析。检查
isatty(stdout)
NO_COLOR
3交互式提示无标志绕过方式代理无法输入'y'。每个提示都需要
--yes
/
--force
。非TTY环境无绕过方式会导致挂起
4成功时无任何输出静默具有歧义——显示简短确认信息。为需要静默的脚本提供
-q
选项
5仅为人类或仅为机器设计检测上下文(TTY vs管道),自动适配
6输出不引导后续操作每个输出都应是路标:成功→下一个命令,失败→修复命令
7破坏现有CLI契约标志名称、退出码、输出格式都是契约。新增而非修改
8除CLI适配器外的任何地方使用
console.log
处理器必须返回数据;仅展示层可写入流
9处理器直接退出进程让入口点决定退出逻辑。处理器以数据形式返回错误
10非零退出但stderr无解释脚本需要错误码和原因
11默认输出过于冗长单次测试运行可能生成419KB数据。支持
--fields
--quiet
--json

Verification Checklist

验证清单

After designing or reviewing a CLI:
  • stdout has ONLY data; stderr has everything else
  • Every command supports
    --json
    with consistent envelope
  • Exit codes are semantic and documented in
    --help
  • Every prompt has a
    --yes
    /
    --force
    /
    --flag
    bypass
  • Errors include: code, message, fix suggestion
  • --dry-run
    available for mutating commands
  • Progress/spinners go to stderr, never stdout
  • NO_COLOR
    ,
    TERM=dumb
    , and
    --no-color
    respected
  • 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
  • --help
    includes 2-3 realistic examples
  • 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可快速退出,清理操作可控
  • --help
    包含2-3个真实示例
  • 人类可读输出可被Grep解析(扁平化行,无表格边框)

Quick Reference

快速参考

Stream Routing

流路由

stdout ← data, results, JSON, NDJSON
stderr ← progress, spinners, warnings, errors, debug, prompts
stdout ← 数据、结果、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 SIGTERM
0   成功
1   业务域失败
2   无效用法
75  临时失败(可重试)
78  配置错误
130 SIGINT(Ctrl-C)
143 SIGTERM

Config 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      选择列