跳转到内容

面向 Agent 的 CLI 接口规范

学习目标

  • 理解双模式 CLI(无状态子命令 + 有状态 REPL)的设计原理
  • 掌握 --json 输出协议在 Agent 消费中的关键作用
  • 应用 Auto-Save + --dry-run 模式解决 session 一致性问题
  • 设计 Agent 友好的内省命令(info / list / status)

前置知识

本章涉及面向 Agent 的 CLI 接口通用规范,建议先阅读:

下文假设你已理解上述概念,直接聚焦 CLI-Anything 的具体实现。


项目实践

Click + 双模式架构

CLI-Anything 生成的每个 CLI 都基于 Click 框架,同时支持两种交互模式:

无状态子命令模式

Terminal window
cli-anything-gimp project new -o project.json
cli-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_context
def 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 list
Layers:
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.pycli-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 必须提供 infoliststatus 等内省命令。以 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.md Phase 2-3 + repl_skin.py + commands/cli-anything.md
  • 源码验证:gimp/agent-harness/cli_anything/gimp/gimp_cli.py(818 行)