OpenCLI深度调研
OpenCLI 深度调研
调研时间:2026-05-19
规范版本:OpenCLI Specification v0.1(草案)
官方站点:https://opencli.org
仓库:https://github.com/spectreconsole/open-cli
目录
一、什么是 OpenCLI
OpenCLI Specification(OCS) 是一种平台无关、语言无关的 CLI(命令行)应用接口描述规范。它用一份 JSON 或 YAML 文档来声明一个 CLI 工具的命令树、参数、选项、退出码等元信息——使得人类和机器都可以在不阅读源码、不查 man 文档的情况下理解这个 CLI 工具应该如何被调用。
它的定位类比起来非常清楚:
OpenAPI 之于 HTTP API,就是 OpenCLI 之于 CLI。
OpenCLI 由 Spectre.Console 团队(以 Patrik Svensson 为主要维护者)发起,规范文档明确说"heavily influenced by the OpenAPI specification",整个对象模型、字段命名风格、版本控制策略都在向 OpenAPI 看齐。
目前的状态是 v0.1 提案(draft),仍处于积极征集社区反馈的阶段,未发布稳定版。
二、为什么需要 OpenCLI
CLI 工具的接口长期以来是"野生的"——每个工具的 --help 各搞一套格式,自动补全脚本要为 bash/zsh/fish/powershell 重复写多份,文档要靠手维护,AI Agent 想自动调用一个 CLI 还得反复尝试。OpenCLI 想解决的痛点:
| 痛点 | 现状 | OpenCLI 带来的可能 |
|---|---|---|
| 文档与实际不一致 | --help 输出和官方网站文档分两份维护 |
单一描述文件作为 single source of truth,文档自动生成 |
| 自动补全脚本重复造 | bash/zsh/fish/pwsh 各写一份补全 | 从一份描述生成多种 shell 的补全脚本 |
| AI / MCP 调用 CLI 困难 | LLM 必须解析自然语言 help 信息 | 直接读结构化描述,转成 MCP 工具 schema |
| API 变更难追踪 | 选项被改名/删除没人通知 | 两个版本的描述做 diff,自动检测破坏性变更 |
| 跨语言客户端 | 想用 Python 包装一个 Go CLI 要手写 wrapper | 从描述文件生成强类型客户端 |
官方 README 给出的五大用途,浓缩为一句话就是:用一份机器可读的描述,撬动 CLI 工具周边的所有自动化。
三、规范结构总览
一份 OpenCLI 描述文档以 Document Object 为根,主要包含四大块:
OpenCLI Document
├── opencli : string # 规范版本号(如 "0.1")
├── info : CliInfo # 工具元信息(标题、版本、license、联系方式)
├── command : Command # 根命令(递归嵌套子命令)
└── conventions : Conventions # 工具的语法约定(短选项分组、分隔符等)
Command 是递归结构,下面挂着 options、arguments、commands(子命令),从而描述任意深度的命令树。
字段命名遵循 camelCase,全部 大小写敏感。数组中元素的顺序具有规范意义(normative),生成器在渲染帮助/补全时应按声明顺序展示。
四、核心对象详解
4.1 Document Object(根对象)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
opencli |
string | ✓ | 规范版本号 |
info |
CliInfo | ✓ | CLI 元信息 |
command |
Command | ✓ | 根命令 |
conventions |
Conventions | ✗ | 语法约定 |
4.2 CliInfo Object
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
title |
string | ✓ | 应用标题 |
version |
string | ✓ | 应用版本 |
summary |
string | ✗ | 一句话简介 |
description |
string | ✗ | 详细描述 |
contact |
Contact | ✗ | 联系人/组织信息 |
license |
License | ✗ | 许可证(支持 SPDX 标识) |
4.3 Command Object(递归核心)
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
name |
string | - | 必填,命令名 |
aliases |
string[] | - | 别名 |
description |
string | - | 命令描述 |
options |
Option[] | - | 该命令的选项 |
arguments |
Argument[] | - | 该命令的位置参数 |
commands |
Command[] | - | 子命令(递归) |
exitCodes |
ExitCode[] | - | 退出码列表 |
examples |
string[] | - | 使用示例(字符串形式) |
interactive |
bool | false |
是否需要交互输入 |
hidden |
bool | false |
是否隐藏(不显示在帮助中) |
metadata |
Metadata[] | - | 扩展元数据 |
4.4 Option Object
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
name |
string | - | 必填,选项主名(如 --verbose) |
aliases |
string[] | - | 短别名(如 -v) |
required |
bool | false |
是否必填 |
arguments |
Argument[] | - | 选项后跟的值 |
group |
string | - | 选项分组(用于帮助分类显示) |
recursive |
bool | false |
是否递归向子命令继承(即 global flag) |
description |
string | - | 描述 |
hidden |
bool | false |
是否隐藏 |
recursive: true是个非常实用的字段——比如--verbose在git整棵命令树都生效,就只需在根命令声明一次。
4.5 Argument Object
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
name |
string | - | 必填,参数名 |
required |
bool | false |
是否必填 |
arity |
Arity | {min:1, max:1} |
接受的值数量范围 |
acceptedValues |
string[] | - | 枚举可接受的值 |
group |
string | - | 参数分组 |
description |
string | - | 描述 |
4.6 Arity Object
{ "minimum": 1, "maximum": 1 }
maximum 不指定(nil)表示不限数量,常用于变长参数(vararg)。
4.7 ExitCode Object
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
code |
int | ✓ | 退出码 |
description |
string | ✗ | 含义 |
4.8 Conventions Object
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
groupOptions |
bool | true |
是否允许短选项合并(如 -rf) |
optionArgumentSeparator |
string | " "(空格) |
选项与值的分隔符(也可以是 =) |
4.9 Metadata Object
{ name, value } 键值对,用于规范未覆盖到的扩展信息——典型用法是写入"该命令对应的 MCP 工具名称"、"权限分级"、"内部工单号"等。
五、实战示例
5.1 最小可用示例:一个 greet 工具
假设我们有一个简单 CLI:greet --name Alice --shout,对应的 OpenCLI 描述:
{
"$schema": "https://opencli.org/draft.json",
"opencli": "0.1",
"info": {
"title": "greet",
"version": "1.0.0",
"summary": "A friendly greeter",
"license": { "identifier": "MIT" }
},
"command": {
"name": "greet",
"description": "Print a greeting",
"options": [
{
"name": "--name",
"aliases": ["-n"],
"required": true,
"description": "Whom to greet",
"arguments": [
{ "name": "NAME", "required": true }
]
},
{
"name": "--shout",
"description": "Shout the greeting in upper case"
}
],
"exitCodes": [
{ "code": 0, "description": "Success" },
{ "code": 1, "description": "Missing name" }
],
"examples": [
"greet --name Alice",
"greet -n Bob --shout"
]
}
}
5.2 带子命令的示例(仿 .NET CLI)
这是 OpenCLI 官方仓库里 examples/dotnet.json 的精简版,演示 根命令 + 子命令 + 选项+值 的完整结构:
{
"$schema": "https://opencli.org/draft.json",
"opencli": "0.1",
"info": {
"title": "dotnet",
"version": "9.0.1",
"description": "The .NET CLI",
"license": {
"name": "MIT License",
"identifier": "MIT",
"url": "https://opensource.org/license/mit"
}
},
"command": {
"name": "dotnet",
"options": [
{ "name": "--help", "aliases": ["-h"], "description": "Display help." },
{ "name": "--info", "description": "Display .NET information." },
{ "name": "--list-sdks", "description": "Display the installed SDKs." },
{ "name": "--list-runtimes", "description": "Display the installed runtimes." }
],
"commands": [
{
"name": "build",
"description": "Builds a project and all of its dependencies.",
"arguments": [
{
"name": "PROJECT | SOLUTION",
"description": "The project or solution file to operate on. If a file is not specified, the command will search the current directory for one."
}
],
"options": [
{
"name": "--configuration",
"aliases": ["-c"],
"description": "The configuration to use for building the project. The default for most projects is 'Debug'.",
"arguments": [
{
"name": "CONFIGURATION",
"required": true,
"arity": { "minimum": 1, "maximum": 1 },
"acceptedValues": ["Debug", "Release"]
}
]
}
]
}
]
}
}
5.3 复杂示例:仿 git commit 的多分组选项
git commit 选项分了好几组(提交内容、消息、作者)。OpenCLI 通过 group 字段表达这种结构:
{
"name": "commit",
"description": "Record changes to the repository",
"options": [
{
"name": "--all",
"aliases": ["-a"],
"group": "Content",
"description": "Stage all modified and deleted files"
},
{
"name": "--message",
"aliases": ["-m"],
"required": true,
"group": "Message",
"description": "Commit message",
"arguments": [
{ "name": "MSG", "required": true, "arity": { "minimum": 1, "maximum": 1 } }
]
},
{
"name": "--author",
"group": "Author",
"description": "Override author for commit",
"arguments": [
{ "name": "AUTHOR", "required": true }
]
},
{
"name": "--amend",
"group": "Content",
"description": "Amend previous commit"
}
],
"exitCodes": [
{ "code": 0, "description": "Commit created" },
{ "code": 1, "description": "Nothing to commit / commit aborted" },
{ "code": 128, "description": "Repository error" }
]
}
5.4 全局选项(recursive)示例
模拟 kubectl --namespace foo get pods 这种"在任何子命令下都生效"的全局选项:
{
"command": {
"name": "kubectl",
"options": [
{
"name": "--namespace",
"aliases": ["-n"],
"recursive": true,
"description": "Kubernetes namespace (applies to all subcommands)",
"arguments": [{ "name": "NS", "required": true }]
},
{
"name": "--kubeconfig",
"recursive": true,
"description": "Path to kubeconfig file",
"arguments": [{ "name": "PATH", "required": true }]
}
],
"commands": [
{
"name": "get",
"commands": [
{ "name": "pods", "description": "List pods" },
{ "name": "services", "description": "List services" }
]
},
{
"name": "apply",
"options": [
{
"name": "--filename",
"aliases": ["-f"],
"required": true,
"arguments": [
{ "name": "FILE", "arity": { "minimum": 1, "maximum": null } }
]
}
]
}
]
}
}
注意 --filename 的 arity.maximum 设为 null,表示可以重复多次(-f a.yaml -f b.yaml -f c.yaml)。
5.5 交互式命令示例
声明 interactive: true 让消费者(如 MCP Server、CI 系统)知道这个子命令需要 TTY、不能直接非交互调用:
{
"name": "login",
"description": "Sign in to the platform",
"interactive": true,
"options": [
{
"name": "--token",
"description": "Skip interactive prompt by providing a token directly",
"arguments": [{ "name": "TOKEN", "required": true }]
}
]
}
六、应用场景与落地
OpenCLI 的真正价值在于"一份描述,多种产物"。以下是五个目前已经在路线图里、或者社区已经开始尝试的方向。
6.1 自动生成多种 Shell 的补全脚本
一份 mytool.opencli.json,可以喂给生成器分别产出 bash/zsh/fish/powershell 补全。伪代码示意(Node 实现):
import { readFileSync } from "fs";
const doc = JSON.parse(readFileSync("mytool.opencli.json", "utf8"));
function genBashCompletion(cmd, prefix = "") {
const subs = (cmd.commands || []).map(c => c.name).join(" ");
const opts = (cmd.options || []).flatMap(o => [o.name, ...(o.aliases || [])]).join(" ");
let script = `_${cmd.name}_${prefix || "root"}() {\n`;
script += ` local cur="\${COMP_WORDS[COMP_CWORD]}"\n`;
script += ` COMPREPLY=( $(compgen -W "${subs} ${opts}" -- "$cur") )\n`;
script += `}\n`;
for (const sub of cmd.commands || []) {
script += genBashCompletion(sub, cmd.name);
}
return script;
}
console.log(genBashCompletion(doc.command));
console.log(`complete -F _${doc.command.name}_root ${doc.command.name}`);
zsh、fish、PowerShell 同理替换模板,一份描述就够。
6.2 自动生成 Markdown / HTML 文档
基于 OpenCLI 描述渲染美观的文档,避免 README 与 --help 漂移。最小渲染逻辑(Python):
import json
def render(cmd, depth=2):
md = f"{'#' * depth} `{cmd['name']}`\n\n"
if cmd.get("description"):
md += cmd["description"] + "\n\n"
if cmd.get("arguments"):
md += "**Arguments:**\n\n"
for a in cmd["arguments"]:
req = " *(required)*" if a.get("required") else ""
md += f"- `{a['name']}`{req} — {a.get('description','')}\n"
md += "\n"
if cmd.get("options"):
md += "**Options:**\n\n| Option | Aliases | Description |\n|---|---|---|\n"
for o in cmd["options"]:
md += f"| `{o['name']}` | {', '.join(o.get('aliases', []))} | {o.get('description','')} |\n"
md += "\n"
if cmd.get("examples"):
md += "**Examples:**\n\n```bash\n" + "\n".join(cmd["examples"]) + "\n```\n\n"
for sub in cmd.get("commands", []):
md += render(sub, depth + 1)
return md
doc = json.load(open("mytool.opencli.json"))
print(f"# {doc['info']['title']} v{doc['info']['version']}\n")
print(doc['info'].get('description', ''))
print(render(doc["command"]))
6.3 转成 MCP(Model Context Protocol)工具,让 AI 直接调
这是 OpenCLI 最被官方强调的一个目标场景。MCP 把外部能力暴露给 LLM,传统做法是为每个 CLI 工具手写一个 MCP server。有了 OpenCLI,可以做一层通用桥:
// 把 OpenCLI 描述里的每个叶子命令,注册成一个 MCP tool
import { spawn } from "node:child_process";
function commandToMcpTool(rootBin: string, path: string[], cmd: any) {
const properties: Record<string, any> = {};
const required: string[] = [];
for (const opt of cmd.options || []) {
const key = opt.name.replace(/^-+/, "");
properties[key] = { type: opt.arguments?.length ? "string" : "boolean", description: opt.description };
if (opt.required) required.push(key);
}
for (const arg of cmd.arguments || []) {
properties[arg.name] = { type: "string", description: arg.description };
if (arg.required) required.push(arg.name);
}
return {
name: [rootBin, ...path, cmd.name].join("_"),
description: cmd.description,
inputSchema: { type: "object", properties, required },
handler: async (input: Record<string, any>) => {
const args = [...path, cmd.name];
for (const opt of cmd.options || []) {
const key = opt.name.replace(/^-+/, "");
if (key in input) {
args.push(opt.name);
if (opt.arguments?.length) args.push(String(input[key]));
}
}
for (const arg of cmd.arguments || []) {
if (arg.name in input) args.push(String(input[arg.name]));
}
return await runProcess(rootBin, args);
},
};
}
效果:一旦给 kubectl、gh、docker 都备好了 OpenCLI 描述,AI Agent 就能"零定制成本"调用它们。
6.4 CLI API 变更检测
把两个版本的 OpenCLI 描述做语义 diff,可以自动告警破坏性变更:
# 伪代码工具
opencli-diff dotnet@8.0.json dotnet@9.0.json
# 输出:
# [BREAKING] Command `dotnet build`: option `--no-restore` was renamed to `--skip-restore`
# [ADDED] Command `dotnet workload`
# [CHANGED] Option `--configuration` of `dotnet build`: acceptedValues changed
# from [Debug, Release] to [Debug, Release, ReleaseAOT]
这对发布 SDK、维护 CI 模板、写自动化脚本的团队非常有价值——以前这些信息要靠 release notes 手工梳理。
6.5 跨语言客户端代码生成
类似 OpenAPI Generator,可以从 OpenCLI 描述生成 Python、Go、Java 等语言的强类型 wrapper:
# 自动生成的 dotnet_client.py 片段
class DotnetClient:
def __init__(self, executable: str = "dotnet"):
self._exe = executable
def build(
self,
project: str | None = None,
configuration: Literal["Debug", "Release"] | None = None,
) -> subprocess.CompletedProcess:
"""Builds a project and all of its dependencies."""
args = [self._exe, "build"]
if project: args.append(project)
if configuration: args += ["--configuration", configuration]
return subprocess.run(args, capture_output=True, text=True, check=False)
调用方就有了 IDE 补全、类型检查、文档悬浮提示——而不是直接拼字符串。
七、与 OpenAPI 的对照
OpenCLI 的设计师明确把 OpenAPI 当模板,二者的概念基本一一对应:
| 维度 | OpenAPI | OpenCLI |
|---|---|---|
| 描述对象 | HTTP API | CLI 应用 |
| 核心概念 | Path / Operation | Command(递归) |
| 输入 | Parameters / RequestBody | Options / Arguments |
| 输出 | Responses(状态码) | ExitCodes(退出码) |
| 错误模型 | HTTP status + schema | Exit code + description |
| 元信息 | info 块(title/version/license/contact) |
info 块(结构几乎一致) |
| 文档格式 | JSON / YAML | JSON / YAML |
| 版本号 | openapi: 3.1.0 |
opencli: 0.1 |
| 扩展机制 | x-* 字段 |
metadata: [{name, value}] |
| 工具生态 | Swagger UI、Redoc、Generator | (建设中)补全生成、MCP 转换、文档生成 |
差别主要在于 CLI 特有的概念:
- 递归命令树(OpenAPI 是扁平的 path 列表,CLI 是树)
- arity(一个选项可以接受 N 个值)
- interactive(标识是否需要 TTY)
- conventions(描述短选项是否能合并、用什么分隔符)
- recursive(全局 flag)
八、现状与未来展望
当前状态(截至 2026-05):
- 规范版本:v0.1,明确标注是 proposal,欢迎社区反馈
- 维护方:Spectre.Console 组织(Patrik Svensson 等)
- 仓库:spectreconsole/open-cli,包含规范文档(
draft.md)、JSON Schema(schema.json)、TypeSpec 定义(typespec/main.tsp)和 dotnet 示例(examples/dotnet.json) - 工具链:基于 TypeSpec 维护规范源,自动生成 JSON Schema;构建用 Cake / .NET 9 SDK
- 站点:https://opencli.org(Docusaurus 搭建)
最近的规范变更(节选自 changelog):
| 日期 | 变更 |
|---|---|
| 2026-04-19 | 根命令也成为 Command Object 的实例(统一了模型) |
| 2026-03-24 | arity 默认值改为 {minimum: 1, maximum: 1} |
| 2025-10-04 | schema 增加默认值 |
| 2025-08-06 | Option 新增 recursive 字段;Argument 移除 ordinal 字段 |
| 2025-07-15 | 新增 interactive 字段 |
| 2025-07-16 | 新增 Metadata Object;map 改成数组(保证顺序稳定) |
值得期待的方向:
- 官方 .NET SDK:示例页面预留了 ".NET SDK" 入口,将来很可能与 System.CommandLine 和 Spectre.Console.Cli 深度集成,让你直接
[OpenCli]标注就能导出描述 - 跨语言生成器:对标 openapi-generator,社区会涌现
opencli-gen-bash、opencli-gen-mcp、opencli-gen-python等工具 - MCP 桥接器:把任意带 OpenCLI 描述的 CLI 一键暴露成 MCP server,是 LLM 应用领域明显的需求
- 大型工具的官方描述:
gh、kubectl、docker、aws等如果发布官方 OpenCLI 文件,整个生态会迅速起飞 - 稳定 v1.0:目前是 0.1,规范字段还在演进,正式落地生产还需要等版本稳定
目前的局限:
- 只是草案,字段还会变(生产环境引入需评估锁版本)
- 工具链稀薄,主要还是 .NET 生态
- 还没有大型 CLI 工具发布官方描述文件
- 缺少校验(validation)规则的标准化(比如"互斥选项"、"依赖选项"还没有原生支持,目前只能塞进
metadata)
九、参考资料
- 规范主页:https://opencli.org
- 规范源码(draft.md / schema.json / TypeSpec):https://github.com/spectreconsole/open-cli
- dotnet 示例:https://github.com/spectreconsole/open-cli/blob/main/examples/dotnet.json
- 灵感来源 OpenAPI:https://spec.openapis.org/oas/latest.html
- 关联生态 Spectre.Console:https://spectreconsole.net
- MCP(Model Context Protocol):https://modelcontextprotocol.io
一句话总结:OpenCLI 想做"CLI 世界的 OpenAPI"——用一份机器可读的 JSON/YAML 把 CLI 工具描述清楚,让文档、补全脚本、AI 调用、跨语言 wrapper、变更检测全部自动化。它现在还只是 v0.1 提案,但思路对、问题真,是个值得持续关注的规范。

浙公网安备 33010602011771号