@ cofold/ 命令
宣布一个命令一次。 到处跑.
指令框架 复数 家族: 此软件包是声明、 注册和输入每个表面共享; 表面列在下面 软件包,除生成的参考文献外,所有参考文献都是兄弟般的软件包.
这是什么
@cofold/commands 是一个 TypeScript 命令框架,其中命令是可重复使用的定义,而不是只使用 CLI 处理器.
同样的声明可以通过CLI,HTTP端点,一个MCP工具来被曝光,生成文档, shell补全,代理发报技能,或者其他适配器而不重新定义其输入和行为.
const registry = createRegistry()
.provide("config", {
resolve: () => loadConfig(),
})
.provide("store", {
deps: ["config"],
resolve: ({ config }) => open(config.path),
dispose: (store) => store.flush(),
});
registry.action({
id: "note.list",
summary: "List notes, newest first",
needs: ["store"],
input: {
status: {
type: "string",
enum: ["open", "done"],
description: "Only this status",
cli: { short: "-s", value: "STATUS" },
},
limit: {
type: "integer",
minimum: 1,
default: 20,
description: "How many",
cli: { short: "-n", value: "N" },
},
},
surfaces: {
cli: { pattern: ["note", "list"] },
http: { method: "GET", path: "/notes" },
mcp: true,
},
run: ({ input, store }) =>
output(store.all(input.status).slice(0, input.limit)),
});
registry.action({
id: "note.add",
summary: "Add a note",
needs: ["store"],
input: {
title: {
type: "string",
minLength: 1,
maxLength: 120,
description: "What the note says",
},
status: {
type: "string",
enum: ["open", "done"],
default: "open",
description: "Where it starts",
cli: { short: "-s", value: "STATUS" },
},
},
required: ["title"],
surfaces: {
cli: { pattern: ["note", "add", ":title"] },
http: { method: "POST", path: "/notes" },
mcp: true,
},
run: ({ input, store }) => output(store.add(input)),
});
每个表面没有单独的指令定义.
flowchart TD
action(["action"])
action --> cli["CLI"]
action --> http["HTTP"]
action --> mcp["MCP"]
cli --> help["help"]
cli --> docs["docs"]
cli --> completion["completion"]
行动仍是一个平庸的目标。 不同部分 @cofold/commands 决定如何曝光.
一个计划
input 是对输入的语法描述,也是规则的唯一说明.
title: { type: "string", minLength: 1, maxLength: 120 }
status: { type: "string", enum: ["open", "done"], default: "open" }
limit: { type: "integer", minimum: 1, default: 20 }
CLI用它来解析和胁迫论点. HTTP使用它来验证请求. MCP作为工具的输入计划出版. 文档得到相同的默认,语句,描述和制约.
因此,一条规则以同样的方式执行,无论走哪条路:
notes note add "" # title must be 1 to 120 characters
curl -X POST /notes -d '{"title":""}' # 400 title must be 1 to 120 characters
note_add({ status: "later" }) # isError: status must be one of open, done
TypeScript 也从中获取处理器输入 :
input.title // string
input.status // "open" | "done"
input.limit // number
规则只有一份要更正.
一个动作,几个表面
从上面的声明, @cofold/commands 可以提供:
| 表面 | 结果 |
|---|---|
notes note list -s open -n 5 | 解析和强制CLI输入 |
notes note add "Ship it" | 插槽、默认和长度 |
GET /notes?status=open | 对 HTTP 的相同动作 |
POST /notes | 相同的验证,答复如下: 400 |
notes note list --json | 稳定的机器可读输出 |
notes note list --quiet | 脚本纯输出 |
notes note add --help | 生成的使用、 选项、 默认和enums |
notes note <TAB> | 外壳补全 |
notes docs | 生成了 Markdown 引用 |
notes skill | 代理向导命令引用 |
note_list, (中文(简体) ), note_add | MCP 工具由相同的输入方案支持 |
增加表面并不意味着增加另一个执行.
surfaces: {
cli: { pattern: ["note", "add", ":title"] },
http: { method: "POST", path: "/notes" },
mcp: true,
}
如果一个表面没有被宣布,则该动作不会在那里被暴露出.
一项行动也可以说它做什么,对什么: effect 实值 read, (中文(简体) ), add, (中文(简体) ), change 或者说 remove,以及 resource 名称 。 每个表面读取相同的两个字段:参考打印出它们,MCP取出它的提示,终端先问一个 remove,而清单的客户端会绘制一个列表,一个创建和项目动作.
能力
行动可以宣布它们需要什么,而不是自己建立依赖关系.
通常的案例是一个经过认证的客户端,其中一半的程序需要,没有人想再建设两次.
const registry = createRegistry()
.provide("config", {
resolve: () => loadConfig(),
})
.provide("api", {
deps: ["config"],
resolve: ({ config }) => createClient({
baseUrl: config.url,
token: config.token,
}),
dispose: (api) => api.close(),
});
然后说:
registry.action({
id: "case.show",
needs: ["api"],
input: {
id: { type: "string", minLength: 1 },
},
required: ["id"],
surfaces: {
cli: { pattern: ["case", "show", ":id"] },
mcp: true,
},
run: ({ input, api }) => output(api.get(`/cases/${input.id}`)),
});
flowchart LR
config["config"] -->|deps| api["api"]
api -->|needs| run["run"]
run -.->|dispose| close["api.close"]
能力在操作员运行前按依赖顺序解决,并可以依赖其他能力. 任何打开的,无论处理者返回还是扔出,都会被处理掉.
没有为不要求它的行动构建任何结构,所以一个在糟糕的参数上失败的命令永远不会打开连接.
这样可以保持动作声明性,而不会把登记册变成一个全球范围的一包物体.
软件包
每个地表一个包,每个包有一个入站点(@cofold/mcp 3),但生成的引用除外,这是此软件包的 ./docs 进入点.
| 软件包 | 目的 |
|---|---|
@cofold/commands | 行动、计划、能力、登记 没有终极知识. |
@cofold/terminal | 解析、帮助、完成、产出合同和退出代码 |
@cofold/mcp | 在没有 MCP SDK 依赖性的情况下将动作曝光为 MCP 工具 |
@cofold/mcp/stdio | 把这些工具放在Stdio上 还是没有SDK,还是没有依附关系 |
@cofold/mcp/server | 可选 SDK 支持的可流式 HTTP,用于在已有服务器中安装 |
@cofold/commands/docs | 为人员和代理人生成Markdown引用 |
@cofold/remote | 从远程列表或 OpenAPI 实现命令 |
@cofold/yaml | 将 YAML 子集分析为纯数据, 并有源代码错误 |
@cofold/config | 查找配置文件, 并显示哪个值来自 |
还有 零运行时间依赖关系 每个软件包中,包括一个工作中的 MCP 服务器: npm install @cofold/commands @cofold/terminal 安装两个软件包而不安装其他软件包. @cofold/mcp/server 是单一的例外,并且是选入的,因为可流式HTTP值SDK,而stdio则不值. 它宣布官方的MCP SDK是一个可选的对等方,所以除非您提出要求,否则没有安装它.
验证为可选用途 标准Schema,因此Zod, Valibot 和 ArkType 等库可以不用 @cofold/commands 取决于其中任何一个.
试试看
pnpm install
pnpm examples
一个声明,三个表面
node examples/commands/dist/petshop/cli.js pet add Rex -a 3 -b corgi
# see the MCP tools an agent receives
node examples/commands/dist/petshop/cli.js mcp tools
# expose the same actions over HTTP
node examples/commands/dist/petshop/cli.js serve --port 8799 &
curl -X POST localhost:8799/pets \
-d '{"name":"Ada","age":99}'
当地CLI
node examples/commands/dist/kitchen-sink/cli.js note add "Ship cofold" -t work
node examples/commands/dist/kitchen-sink/cli.js note list
# generate the agent-facing reference
node examples/commands/dist/kitchen-sink/cli.js skill
服务器提供的命令
node examples/commands/dist/clerver/cli.js serve --port 8787 &
node examples/commands/dist/clerver/cli.js pet list
node examples/commands/dist/clerver/cli.js pet add Rex --species dog --age 3
将 OpenAPI 文档转换为 CLI
API不需要知道这一点 @cofold/commands 已存在.
# any server that answers the document. clerver happens to be one
node examples/commands/dist/clerver/cli.js serve --port 8793 &
node examples/commands/dist/open-cli/cli.js \
--spec examples/samples/petstore.json \
pets --limit 2
实例
资料库包括四个工作实例: examples/commands数字 :
| 示例 | 显示 |
|---|---|
petshop | 通过CLI、HTTP和MCP暴露出一组动作 |
kitchen-sink | 本地 CLI 特性集 |
clerver | 从远程服务器上发现的命令 |
open-cli | 从现有的 OpenAPI 文档建立 CLI |
文档中的命令
命令已经从这个库没有写的两个文档运抵这里:远程清单和OpenAPI规格. 一个YAML或JSON文件是第三次的同一种接合,它就是将"命令是数据"从一个债权变成一个被证明的财产.
document -> ActionDefinition -> registry -> CLI / HTTP / MCP
除一件事外,文档定义的命令需求已经存在. run 是函数,一个文件不能持有一个,所以执行被声明为数据:已命名的执行者和命令步骤列表.
commands:
release.deploy:
summary: Deploy the application
input:
env: { type: string, enum: [staging, production], default: production, cli: { short: -e } }
surfaces:
cli: { pattern: [release, deploy] }
run:
- exec: { command: ./scripts/deploy.sh, args: ["{env}"] }
input jSON Schema的同一张地图 registry.action 带和 surfaces 是一个相同的声明,所以终端,一个HTTP请求和一个MCP工具调用,完全按照代码中的规则进行.
读这些的前端是 s2cmd,一个包自己的。 文件作者得到验证,帮助,完成, --json,一个生成的引用,HTTP路由和MCP工具,不执行任何这些表面,也不写一个处理器. 它的 README 记录执行者,插值规则,以及为什么一个运行一个进程的文档默认不是一个代理工具.
文档
全部的手动生活在这里 docs/commands/。 。 。 .
以 :
还没建成的,还有问题还没解决 ROADMAP.md。 。 。 .
为什么
不是因为分析很困难 解析是解决的部分.
选择任何维持了一年的程序,并计算行数 关于 其命令对应的线条 :
- 装入配置文件, 以及其中的配置文件或目标
- 证书,绝不打印为错误
--json保持稳定,--quiet打印一个标识符- 一个脚本可以打开的退出代码
- 当有东西被吸入时读取 stdin
NO_COLOR, TTY 检测,终端宽度- 完成时知道这个安装的id, 不只是旗名
- 仍然真实的参考文档
- 而现在的MCP工具, 对于将驱动它的代理
解析器给你第一行的第一分之三. 之后的一切,每个程序都再次写作.
大多数 CLI 库也将命令声明作为设置进程的一部分:
program
.command("note list")
.option("-s, --status <status>")
.action(handler);
描述是交给建设者的字符串,行为是关闭. 接完电话就回电了 询问:任何知道命令存在的物体,它需要什么,它意味着什么,或者它需要什么. 因此帮助漂移,因为用词行就是评论和评论的烂掉. 交叉行为无处可去 所以 --json 每个动作都会被再次写入,其中一人会打印出一个流浪者 console.log. 没有任何内容可以检查,因为没有登记册可以检查.
当终端是唯一的消费者时,这效果很好.
一旦终端不是唯一的呼叫者,它就停止工作。 一个打出的命令,一个HTTP请求和一个MCP工具调用是同一个调用:相同的参数,相同的默认,相同的验证. 只有他们到达的方式是不同的.
每平面申报,协议写出多次并维持多次. 一份副本漂移的一天,一个代理被显示一个规则,请求从未被持有,一个在终端被执行的绑定从API中缺失.
召者已出已来为人. 特工必须被告知 命令接受什么 作为计划, 它可以调用任何东西。 资料 .action(handler) 丢弃正是代理时代需要保存的信息.
@cofold/commands 保持信息活着.
const action = {
id: "note.list",
input: { /* ... */ },
surfaces: { /* ... */ },
run: handler,
};
国家行动倡议是这一行动的一种观点.
HTTP也一样.
MCP也一样.
成本是对处理器的一种限制:处理器收到一个犬形输入对象并返回一个值,而不是读取 process.argv 和印刷。 这就是整个纪律,其他一切从它开始, 因为一个从不触及终端的操作员 可以用一个不是终端的东西来操作.
@cofold/mcp 这是测试。 一个完整的MCP表面,JSON Schema世代包含,没有依赖性,因为它没有什么可以发明的:动作已经知道. @cofold/mcp/stdio 是读取的回路, 并且它是手写的,同样的原因 YAML 子集是 - 新线限制的 JSON- RPC 在两个管子上不值得一个网络框架.
命令是数据。 界面为适配器.
许可证
版权:QQ 2026 Softov.
凭 MIT 许可证。 。 。 .