关于 cli-ops
面向开发可供 AI 助手和高级用户组合调用的命令行工具的开发者,提供 CLI 设计模式。重点包括可预测的 JSON 输出、标准化错误处理和命令组合。
主要功能
- 01命令结构与命名约定
- 02JSON 输出和流分离规范
- 03语义化退出码与错误格式
- 04帮助信息和使用示例规范
- 05认证、筛选与分页模式
适用场景
- 01设计便于脚本解析的命令输出
- 02统一多个子命令的参数形式
- 03让自动化脚本按错误类型分支
- 04规划认证命令和凭据存储
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ 0xdarkmatter/ claude-mods --skill cli-ops安装后在 Claude Code 中输入/cli-ops确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/.gitkeepreferences/implementation.mdjson-schemas.mdscripts/.gitkeep详细介绍
面向 Agent 工作流的命令行模式
用于构建命令行工具的模式,方便 AI 助手和高级用户进行命令串联、解析并可靠地使用。
理念
为Agent 工作流构建命令行工具,面向会串联命令、以编程方式解析输出并期望行为可预测的 AI 助手和高级用户。
核心原则
| 原则 | 含义 | 重要性 |
|---|---|---|
| 自带文档 | --help 内容全面且始终保持最新 | 大模型无需外部文档即可了解工具能力 |
| 可预测 | 所有命令都遵循相同模式 | 学会一次,处处适用 |
| 可组合 | Unix 理念——做好一件事 | 工具可以自然地串联使用 |
| 可解析 | 始终提供 --json,且输出始终有效 | 无需使用解析技巧即可供机器读取 |
| 默认安静 | 仅输出数据,除非用户要求,否则不添加装饰 | 脚本不会因意外输出而出错 |
| 快速失败 | 输入无效时立即报错 | 不会静默失败或只返回部分结果 |
设计公理
- stdout 神圣不可侵犯——仅输出数据。绝不输出进度、日志或装饰内容。
- stderr 面向人类——进度条、颜色、表格和警告都输出到这里。
- 退出码有明确含义——脚本可以根据失败模式进行分支处理。
- 帮助信息包含示例——这是最快的理解途径。
- JSON 结构可预测——所有命令都使用相同的结构。
命令架构
结构模式
<tool> [global-options] <resource> <action> [options] [arguments]
每个命令行工具都遵循以下层级:
<tool>
├── --version, --help # Global flags
├── auth # Authentication (if required)
│ ├── login
│ ├── status
│ └── logout
└── <resource> # Domain resources (plural nouns)
├── list # Get many
├── get <id> # Get one by ID
├── create # Make new (if supported)
├── update <id> # Modify existing (if supported)
├── delete <id> # Remove (if supported)
└── <custom-action> # Domain-specific verbs
命名约定
| 元素 | 约定 | 有效示例 | 无效示例 |
|---|---|---|---|
| 工具名称 | 小写,2-12 个字符 | mytool、datactl | MyTool、my-tool-cli |
| 资源 | 复数名词,小写 | invoices、users | Invoice、user |
| 操作 | 动词,小写 | list、get、sync | listing、getter |
| 长选项 | kebab-case | --dry-run、--output-format | --dryRun、--output_format |
| 短选项 | 单个字母 | -n、-q、-v | -num、-quiet |
标准资源操作
| 操作 | HTTP 等效项 | 返回内容 | 幂等 |
|---|---|---|---|
list | GET /resources | 数组 | 是 |
get <id> | GET /resources/:id | 对象 | 是 |
create | POST /resources | 已创建的对象 | 否 |
update <id> | PATCH /resources/:id | 已更新的对象 | 是 |
delete <id> | DELETE /resources/:id | 确认信息 | 是 |
search | GET /resources?q= | 数组 | 是 |
标志和选项
必需标志
每个命令 MUST 支持:
| 标志 | 短选项 | 行为 | 输出 |
|---|---|---|---|
--help | -h | 显示包含示例的帮助信息 | 帮助文本输出到 stdout,退出码为 0 |
--json | 输出机器可读内容 | JSON 输出到 stdout |
根命令 MUST 另外支持:
| 标志 | 短选项 | 行为 | 输出 |
|---|---|---|---|
--version | -V | 显示版本 | <tool> <version> 输出到 stdout,退出码为 0 |
推荐标志
| 标志 | 短选项 | 类型 | 用途 | 默认值 |
|---|---|---|---|---|
--quiet | -q | bool | 抑制非必要的 stderr 输出 | false |
--verbose | -v | bool | 提高详细程度 | false |
--dry-run | bool | 预览但不执行 | false | |
--limit | -n | int | 返回结果的最大数量 | 20 |
--output | -o | path | 将输出写入文件 | stdout |
--format | -f | enum | 输出格式 | 视情况而定 |
标志行为规则
- 布尔标志不接受值:使用
--json,而非--json=true - 短标志可以组合:
-vq等同于-v -q - 未知标志会报错:绝不静默忽略
- 重复标志:使用最后一个值(不适用时则报错)
输出规范
流分离
这是最关键的规则:
| 流 | 内容 | 何时 |
|---|---|---|
| stdout | 仅数据 | 始终 |
| stderr | 其他所有内容 | 交互模式 |
stdout 接收:
- 设置
--json时输出 JSON - 交互模式下输出精简文本
- 绝不输出其他内容。
stderr 接收:
- 进度指示器(旋转图标、进度条)
- 状态消息(“正在获取……”“完成”)
- 警告
- 富文本格式的表格
- 颜色和装饰
- 调试信息(
--verbose)
交互模式检测
import sys
def is_interactive() -> bool:
"""True if connected to a terminal, not piped."""
return sys.stdout.isatty() and sys.stderr.isatty()
| 上下文 | stdout.isatty() | 行为 |
|---|---|---|
| 终端 | True | 富文本输出到 stderr,摘要输出到 stdout |
管道(| jq) | False | 精简输出/JSON 输出到 stdout |
重定向(> file) | False | 精简输出到 stdout |
--json 标志 | 任意 | 输出 JSON 到 stdout,抑制 stderr 噪声 |
JSON 输出架构
完整的 JSON 响应模式请参见 references/json-schemas.md。
关键约定:
- 列表响应:
{"data": [...], "meta": {...}} - 单项:
{"data": {...}} - 错误:
{"error": {"code": "...", "message": "..."}} - ISO 8601 日期、十进制金额、字符串 ID
退出代码
脚本可以依赖的语义化退出代码:
| 代码 | 名称 | 含义 | 何时 |
|---|---|---|---|
| 0 | SUCCESS | 操作已完成 | 一切正常 |
| 1 | ERROR | 一般/未知错误 | 意外故障 |
| 2 | AUTH_REQUIRED | 未通过身份验证 | 没有令牌、令牌已过期 |
| 3 | NOT_FOUND | 未找到资源 | ID 不存在 |
| 4 | VALIDATION | 输入无效 | 参数错误、验证失败 |
| 5 | FORBIDDEN | 权限被拒绝 | 已通过身份验证,但未获授权 |
| 6 | RATE_LIMITED | 请求过多 | API 限流 |
| 7 | CONFLICT | 状态冲突 | 并发修改、重复项 |
用法
# Script can branch on exit code
mytool items get item-001 --json
case $? in
0) echo "Success" ;;
2) echo "Need to authenticate" && mytool auth login ;;
3) echo "Item not found" ;;
*) echo "Error occurred" ;;
esac
实现
# Constants
EXIT_SUCCESS = 0
EXIT_ERROR = 1
EXIT_AUTH_REQUIRED = 2
EXIT_NOT_FOUND = 3
EXIT_VALIDATION = 4
EXIT_FORBIDDEN = 5
EXIT_RATE_LIMITED = 6
EXIT_CONFLICT = 7
# Usage
raise typer.Exit(EXIT_NOT_FOUND)
错误处理
错误输出格式
使用 --json 时,错误会以结构化 JSON 格式输出到 stdout,同时向 stderr 输出消息:
stderr:
Error: Item not found
stdout:
{
"error": {
"code": "NOT_FOUND",
"message": "Item not found",
"details": {
"item_id": "bad-id"
}
}
}
错误代码
| 代码 | 退出代码 | 含义 |
|---|---|---|
AUTH_REQUIRED | 2 | 必须先进行身份验证 |
TOKEN_EXPIRED | 2 | 令牌需要刷新 |
FORBIDDEN | 5 | 权限不足 |
NOT_FOUND | 3 | 资源不存在 |
VALIDATION_ERROR | 4 | 输入无效 |
INVALID_ARGUMENT | 4 | 参数值错误 |
MISSING_ARGUMENT | 4 | 缺少必需参数 |
RATE_LIMITED | 6 | 请求过多 |
CONFLICT | 7 | 状态冲突 |
ALREADY_EXISTS | 7 | 资源重复 |
INTERNAL_ERROR | 1 | 意外错误 |
API_ERROR | 1 | 上游 API 失败 |
NETWORK_ERROR | 1 | 连接失败 |
实现模式
def _error(
message: str,
code: str = "ERROR",
exit_code: int = EXIT_ERROR,
details: dict = None,
as_json: bool = False,
):
"""Output error and exit."""
error_obj = {"error": {"code": code, "message": message}}
if details:
error_obj["error"]["details"] = details
if as_json:
print(json.dumps(error_obj, indent=2))
# Always print human message to stderr
console.print(f"[red]Error:[/red] {message}")
raise typer.Exit(exit_code)
帮助系统
帮助要求
每个 --help 输出都必须包含:
- 简要说明(一行)
- 用法语法
- 带说明的选项
- 示例(对发现功能至关重要)
帮助格式模板
<one-line description>
Usage: <tool> <resource> <action> [OPTIONS] [ARGS]
Arguments:
<arg> Description of positional argument
Options:
-s, --status TEXT Filter by status
-n, --limit INTEGER Max results [default: 20]
--json Output as JSON
-h, --help Show this help
Examples:
<tool> <resource> <action>
<tool> <resource> <action> --status active
<tool> <resource> <action> --json | jq '.[0]'
示例至关重要
示例应展示:
- 基本用法——最简单的调用方式
- 常用筛选条件——最常用的选项
- JSON 管道传递——如何与
jq串联 - 实际场景——真实用例
身份验证
身份验证命令
需要身份验证的工具必须实现:
<tool> auth login # Interactive authentication
<tool> auth status # Check current state
<tool> auth logout # Clear credentials
凭据存储优先级
推荐: 使用操作系统密钥环,并配置回退方案,以实现最高安全性
-
环境变量(CI/CD、测试)
MYTOOL_API_TOKEN或类似变量- 优先级最高,覆盖所有其他来源
-
操作系统密钥环(主要存储方式,安全)
- Windows:Credential Manager
- macOS:Keychain
- Linux:Secret Service(GNOME Keyring、KWallet)
- 静态加密,并按用户隔离
-
.env 文件(开发环境回退方案)
- 以纯文本形式存储在当前目录
- 便于本地开发
- 必须添加到
.gitignore
依赖项:
dependencies = [
"keyring>=24.0.0", # OS keyring access
"python-dotenv>=1.0.0", # .env file support
]
简单替代方案: 仅使用 ~/.config/<tool>/ 下的配置文件
- 适用于不含敏感凭据的工具
- 或操作系统密钥环会增加过多复杂性的情况
完整的凭据存储实现请参阅 references/implementation.md。
未通过身份验证时的行为
需要身份验证但未提供凭据时:
$ mytool items list
Error: Not authenticated. Run: mytool auth login
# exit code: 2
$ mytool items list --json
# stderr: Error: Not authenticated. Run: mytool auth login
{"error": {"code": "AUTH_REQUIRED", "message": "Not authenticated. Run: mytool auth login"}}
# exit code: 2
数据约定
日期处理
输入(灵活): 接受多种格式,方便用户使用
| 格式 | 示例 | 说明 |
|---|---|---|
| ISO 日期 | 2025-01-15 | 精确日期 |
| ISO 日期时间 | 2025-01-15T10:30:00Z | 精确日期时间 |
| 相对日期 | today、yesterday、tomorrow | 当天、前一天、后一天 |
| 相对时间 | last、this(结合上下文) | 上一时段、当前时段 |
输出(严格): 始终输出 ISO 8601 格式
{
"created_at": "2025-01-15T10:30:00Z",
"due_date": "2025-02-15",
"month": "2025-01"
}
金额
- 存储为十进制数,而非分
- 金额不明确时,注明货币
- 不要进行格式化(JSON 中不要包含 "$" 或 ",")
{
"total": 1250.50,
"currency": "USD"
}
ID
- 始终使用字符串(即使 ID 是数字)
- 保留来源中的确切格式
{
"id": "abc_123",
"legacy_id": "12345"
}
枚举
- 在 JSON 中使用 UPPER_SNAKE_CASE
- 输入不区分大小写
# All equivalent
--status DRAFT
--status draft
--status Draft
{"status": "IN_PROGRESS"}
筛选与分页
常见筛选模式
# By status
--status DRAFT
--status active,pending # Multiple values
# By date range
--from 2025-01-01 --to 2025-01-31
--month 2025-01
--month last
# By related entity
--user "Alice"
--project "Project X"
# Text search
--search "keyword"
-q "keyword"
# Boolean filters
--archived
--no-archived
--include-deleted
分页
# Limit results
--limit 50
-n 50
# Offset-based
--page 2
--offset 20
# Cursor-based
--cursor "eyJpZCI6MTIzfQ=="
--after "item_123"
实现
完整的 Python 实现模板请参阅 references/implementation.md,其中包括:
- 使用 Typer 的 CLI 骨架
- 使用 httpx 的客户端模式
- 错误处理
- 身份验证流程
- 测试模式
反模式
❌ 输出污染
# BAD: Progress to stdout
$ bad-tool items list --json
Fetching items...
[{"id": "1"}]
Done!
# GOOD: Only JSON to stdout
$ good-tool items list --json
[{"id": "1"}]
❌ 交互式提示
# BAD: Prompts in non-interactive context
$ bad-tool items create
Enter name: _
# GOOD: Fail fast with required flags
$ good-tool items create
Error: --name is required
❌ 标志不一致
# BAD: Different flags for same concept
$ tool1 list -j
$ tool2 list --format=json
# GOOD: Same flags everywhere
$ tool1 list --json
$ tool2 list --json
❌ 静默失败
# BAD: Success exit code on failure
$ bad-tool items delete bad-id
Item not found
$ echo $?
0
# GOOD: Semantic exit code
$ good-tool items delete bad-id
Error: Item not found: bad-id
$ echo $?
3
快速参考
必备检查清单
-
<tool> --version -
<tool> --help并附带示例 -
<tool> <resource> list [--json] -
<tool> <resource> get <id> [--json] - 语义化退出代码(0、1、2、3、4、5、6、7)
- 错误输出到 stderr,数据输出到 stdout
-
--json输出有效 JSON - 流分离(stdout = 数据,stderr = UI)
建议添加项
- 身份验证命令(
auth login、auth status、auth logout) - 创建/更新/删除操作
-
--quiet和--verbose模式 - 对变更操作提供
--dry-run - 分页(
--limit、--page) - 筛选(状态、日期范围、搜索)
- 自动化测试
框架选择
Typer(新工具首选):
- 类型提示可提供自动验证
- 内置帮助生成
- 集成 Rich,实现美观的输出
- 样板代码比 Click 更少
Click(现有工具可继续使用):
- Typer 基于 Click 构建(100% 兼容)
- 结构良好的 Click 代码无需迁移
- 两者都必须遵循相同的输出约定
# Typer (preferred)
import typer
from rich.console import Console
app = typer.Typer()
console = Console(stderr=True) # UI to stderr
# Click (acceptable)
import click
from rich.console import Console
console = Console(stderr=True) # Same pattern