面向 Agent 的 CLI 接口规范
学习目标
- 理解双模式 CLI(无状态子命令 + 有状态 REPL)的设计原理
- 掌握
--json输出协议在 Agent 消费中的关键作用 - 应用 Auto-Save +
--dry-run模式解决 session 一致性问题 - 设计 Agent 友好的内省命令(info / list / status)
前置知识
本章涉及面向 Agent 的 CLI 接口通用规范,建议先阅读:
下文假设你已理解上述概念,直接聚焦 CLI-Anything 的具体实现。
项目实践
Click + 双模式架构
CLI-Anything 生成的每个 CLI 都基于 Click 框架,同时支持两种交互模式:
无状态子命令模式:
cli-anything-gimp project new -o project.jsoncli-anything-gimp --project project.json layer add -n "Background"cli-anything-gimp --project project.json export render output.png有状态 REPL 模式:
$ cli-anything-gimp╔══════════════════════════════════════╗║ cli-anything-gimp v1.0.0 ║║ GIMP CLI for AI Agents ║╚══════════════════════════════════════╝SKILL: /path/to/skills/cli-anything-gimp/SKILL.md
gimp> project new --name MyPoster✓ Created: MyPoster
gimp[MyPoster]> layer add -n "Background" --color "#1a1a2e"✓ Added layer: Background默认进入 REPL 的实现方式(源码验证 HARNESS.md Phase 3):
@click.group(invoke_without_command=True)@click.pass_contextdef cli(ctx): # ... setup ... if ctx.invoked_subcommand is None: ctx.invoke(repl, project_path=None)invoke_without_command=True 确保无子命令时不报错,而是显式调用 REPL。
--json 全命令覆盖
HARNESS.md 的硬性规则:每个命令必须支持 --json 标志。
在 GIMP CLI 中(源码验证 gimp/agent-harness/cli_anything/gimp/gimp_cli.py),所有命令组都接收 --json 参数:
# 人类可读$ cli-anything-gimp --project project.json layer listLayers: 1. Background (solid, #1a1a2e) 2. Text (typeface: Arial)
# Agent 消费$ cli-anything-gimp --json --project project.json layer list{"layers": [{"name": "Background", "type": "solid", "color": "#1a1a2e"}, ...]}Agent 消费的关键价值:
- 结构化输出消除了人类格式解析的歧义
- 类型正确(布尔值、数字、字符串)便于下游程序处理
- 路径绝对化,Agent 无需处理相对路径歧义
统一 REPL Skin
所有生成的 CLI 共享 repl_skin.py(cli-anything-plugin/repl_skin.py,567 行)提供的统一终端 UI:
| 组件 | 功能 |
|---|---|
print_banner() | 品牌启动框,显示名称、版本、SKILL.md 路径 |
create_prompt_session() | prompt_toolkit 会话(历史、样式、补全) |
get_input() | 带项目名和修改标记的提示符 gimp[MyProject]*> |
success() / error() / warning() / info() | 标准消息颜色 |
table() / progress() | 表格和进度条 |
每个 CLI 拷贝(非 import)repl_skin.py 到自身的 utils/ 目录。这确保了:
- 独立安装的 CLI 不相互依赖
- 各 CLI 可在未来独立演进 skin
- 代价是更新 skin 需重新发布所有 CLI
Auto-Save + --dry-run 模式
HARNESS.md 明确要求(commands/cli-anything.md Auto-Save 章节):
Session-based CLIs must auto-save after one-shot mutations. Without this, one-shot commands silently lose changes because
save_session()is never called before the process exits.
实现模式:
def one_shot_command(session, args): session.apply_mutation(args) if not args.dry_run: session.save() # 自动保存--dry-run 用于:
- 预览操作结果而不实际修改
- 测试流程不产生副作用
内省命令设计
每个 CLI 必须提供 info、list、status 等内省命令。以 GIMP 为例:
| 命令 | 用途 |
|---|---|
project info | 显示项目名、类型、修改状态 |
layer list | 列出所有图层(名称、类型、可见性) |
filter list | 列出可用滤镜 |
--json + 上述 | 机器可读的结构化状态 |
这些命令是 Agent 的”感知器官”——在执行修改前先了解当前状态,避免盲目操作。
后端集成模式
CLI 通过 utils/<software>_backend.py 模块与真实软件通信:
# utils/gimp_backend.py — 伪代码def find_gimp(): path = shutil.which("gimp") if not path: raise RuntimeError("GIMP not found. Install: apt install gimp") return path
def export_xcf_to_png(xcf_path, png_path): gimp = find_gimp() subprocess.run([gimp, "-i", "-b", f"(gimp-file-save ... {xcf_path} {png_path})", "-b", "(gimp-quit 0)"])关键设计:
find_<software>()在未安装时抛出清晰的错误(含安装指令)- 使用
subprocess.run()同步调用,超时处理 - 错误输出解析为结构化 JSON 返回
问题与规避
| 问题 | 规避策略 |
|---|---|
| REPL 模式下一致性丢失 | Auto-save 保证每次可变操作后持久化到 session JSON |
| Agent 无法理解人类格式输出 | 每个命令强制 --json 支持 |
| 多个 CLI 的 UX 不一致 | 统一 REPL Skin,每个 CLI 拷贝 |
| REPL 启动不明显 | 无参数默认进入 REPL(invoke_without_command) |
| 后端未安装时错误模糊 | find_<software>() 抛出带安装指令的 RuntimeError |
设计取舍
拷贝 vs Import REPL Skin
选择:每个 CLI 拷贝 repl_skin.py 而非 Python import。
原因:每个 CLI 是独立的 pip 包,cli_anything.gimp 不应 import cli_anything.blender 中的文件。拷贝确保零交叉依赖。
代价:Skin 更新需重新发布所有 CLI。但皮肤变更频率低(数月一次),成本可接受。
Click vs argparse
选择:使用 Click 而非 Python 标准库 argparse。
原因:
- Click 的
@click.group/@click.command装饰器天然支持命令树 - 自动生成
--help文本,格式一致 invoke_without_command等高级功能简化 REPL 实现skill_generator.py可从 Click 装饰器自动提取元数据
参考来源
- 源码验证:
HARNESS.mdPhase 2-3 +repl_skin.py+commands/cli-anything.md - 源码验证:
gimp/agent-harness/cli_anything/gimp/gimp_cli.py(818 行)