maoda-commander-tt
v0.0.59
Published
一个通用命令行工具
Readme
tt-cli (tt)
一个 AI-native 的通用 CLI:默认调用方是 AI agent。JSON 模式的命令在 stdout 只输出
一行 envelope,诊断与进度走 stderr,失败退出码为 1;透传命令在 --help 中声明输出模式。
基于 commander 与 rockbed
(Result / Disposable / Emitter)。
特性
- Agent 输出契约(tt.agent.v1) — stdout 一行 JSON、stderr 结构化进度、退出码语义稳定, Commander 的输入错误也以同样的 envelope 输出
- 命令只返回
Result<TData>—ok(data)/fail(code, msg, { stage, nextCommand, ... }), 基类负责输出与退出码;passthrough模式用于透传 git、开发服务器等面向人的场景 - 清晰分层 —
core(框架)→commands(表现)→modules(领域)→bedrock(基础设施) - 命令帮助 —
tt --help只列公开命令且不提示隐藏入口;显式tt -hh查看完整列表 - Disposable / 事件 — 命令与服务继承
Disposable,提供onBeforeExecute/onAfterExecute - TypeScript strict + node:test — 全量 strict,测试与源文件同目录
Agent 调用契约
$ tt hello greet
{"code":0,"msg":"success","data":{"message":"Hello, World!"}}
$ tt deploy --bogus ; echo "exit=$?"
{"code":1,"msg":"unknown option '--bogus'","error":{"stage":"input"}}
exit=1
$ tt ai video wait --task-id bad ; echo "exit=$?"
{"code":18017,"msg":"...","error":{"stage":"wait","taskId":"bad","recoverable":true,"nextCommand":"tt ai video wait --task-id bad"}}
exit=1- 成功:
{"code":0,"msg":"success","data":...};失败:{"code":非0,"msg":...,"error"?:{...},"data"?:...}。error.stage说明失败阶段,error.nextCommand是可直接执行的后续命令,data是失败时仍成立的部分结果(如“已发布但校验失败”)。 - stderr 上的
{"protocol":"tt.agent.v1","type":"progress",...,"final":false}只是进度, 必须等待进程退出后再读取 stdout。 - 个别命令允许成功退出但业务码非 0(如
tool get-config未配置返回16003), 会在该命令--help的“Agent 调用契约”里写明。
目录结构
tt-cli/
├── src/
│ ├── main.ts # 可执行入口:注册 createRootCommands() 并运行 CliApp
│ ├── index.ts # 库入口:导出 core 与内置命令
│ ├── core/ # 命令框架(唯一依赖 commander 的层)
│ │ ├── cli-app.ts # 根命令、-hh、根级输入错误 envelope、退出码
│ │ ├── abstract-command.ts # Command 懒构建、生命周期、按输出模式输出
│ │ ├── base-command.ts # 叶子命令(默认 json)
│ │ ├── base-command-group.ts # 命令组(默认 passthrough,打印帮助)
│ │ ├── agent-protocol.ts # envelope 类型、fail()/failFrom()
│ │ ├── agent-progress.ts # stderr 进度心跳
│ │ └── command-meta.ts # ICommandMeta: name/description/aliases/hidden/output
│ ├── commands/ # CLI 表现层,每个命令组一个目录
│ │ ├── index.ts # 顶层注册表 createRootCommands()
│ │ ├── ai/ deploy/ git/ hello/ pippit/ shortcut/ tool/
│ ├── modules/ # 领域逻辑:不依赖 commander,返回 Result<T>
│ │ ├── ark/ git/ pippit/ static-deploy/ video-generation/ xyq/ xyq-tasks/
│ ├── bedrock/ # 与业务无关的基础设施
│ │ ├── config/ cross-app-settings/ download/ pkg/ process/ uuid/
│ └── constants/ # 兼容性常量(已弃用的内置回退值)
├── scripts/release.mjs # 发布脚本
├── ops/static-deploy/ # 服务器侧部署说明
├── AGENTS.md # agent 工作 harness(契约、分层、发布)
└── package.json快速开始
运行环境要求 Node.js 22.13.0 或更高版本。
# 安装依赖
pnpm install
# 开发运行(pnpm 会把额外参数直接传给 tsx,不要再写 `--`,否则 commander 会把它当作选项结束符)
pnpm dev --help
pnpm dev -hh # 含隐藏命令的完整列表
pnpm dev hello greet Alice -u
pnpm dev git mm "update docs"
pnpm dev ai video --help
# 调试模式 (支持 Chrome DevTools / VS Code 断点)
pnpm dev:debug hello greet Alice
# 类型检查、测试、构建、产物冒烟
pnpm exec tsc --noEmit -p .
pnpm test
pnpm build && pnpm test:dist后台启动 Codex
tt codex、tt ai run 和本地图片 worker 共用可执行文件查找顺序:
/Applications/ChatGPT.app/Contents/Resources/codex →
/Applications/Codex.app/Contents/Resources/codex → PATH 中的 codex。
App 内路径不存在、不是文件或不可执行时跳到下一项;启动后的执行失败不重试下一项。
tt codex "我的具体 prompt"
tt codex -C /path/to/repo -m gpt-5.6-sol -r ultra "我的具体 prompt"
tt codex --help默认工作目录 /Users/bytedance/work/autogen,默认模型 gpt-6-astra,默认推理强度
high。模型可选 gpt-6-astra、gpt-5.6-sol;推理强度可选
low、medium、high、xhigh、max、ultra。
macOS/Linux 下通过 nohup 启动后台 worker,由 worker 运行 codex app-server,
通过 initialize、project/list、thread/start、turn/start 提交任务。
工作目录支持普通目录,无需 Git 仓库。固定无沙箱 danger-full-access 和审批策略
never,保留本机 Codex 配置、登录态与会话持久化。
服务端确认 turn/start 后 tt 立即返回一行 JSON,data 包含 status: "launched"、
worker pid、threadId、turnId、projectId、cwd、model、reasoning。
这表示任务已提交,不代表执行成功。启动或提交错误会直接返回失败;超时不自动重试,
以免重复创建任务。后台 worker 保持协议连接直到本轮结束后退出,调用终端关闭不影响
任务运行。终端 IO 丢弃,不生成任务日志,不提供任务跟踪命令。
按目录实际路径精确匹配已保存项目,并通过 thread/start.projectId 绑定;无匹配
则返回 projectId: null,不自动创建项目;匹配多个项目则在创建会话前报错。
App 可发现 app-server 会话,但 App 侧边栏是否按项目分组还取决于该版本是否启用
项目归属同步。服务端绑定 projectId 不等同于旧版 App 侧边栏已同步;tt 不修改 App
私有状态文件或数据库来强制分组。
后台启动 Traex
tt traex "我的具体 prompt"
tt traex -C /path/to/repo -m gpt-6-astra -r high "我的具体 prompt"
tt traex --help默认目录 /Users/bytedance/work/autogen,默认模型 gpt-5.6-sol,默认推理强度
xhigh。模型可选 gpt-5.6-sol、gpt-6-astra;推理强度按 Traex
支持范围提供 low、medium、high、xhigh。需要本机已安装并登录 traex。
与 tt codex 一样,通过 nohup 后台 worker 运行 traex app-server,固定
danger-full-access 和 never,收到 turn/start 确认后立即返回一行 JSON。
保留本机 Traex 配置、登录态及会话持久化,终端 IO 丢弃,不生成任务日志,
不提供任务跟踪。后台 worker 保持连接到本轮结束后退出。
返回 status: "launched"、worker pid、threadId、turnId、cwd、model、
reasoning 和 projectId: null。Traex 暂不支持项目接口,因此仅传工作目录,
不调用 project/list 或传入 thread/start.projectId。App 显示及项目分组可能
延迟,launched 只代表服务端已接受任务,不代表执行完成或已在 App 中显示。
发布到 npm
发布采用 GitHub Actions + npm Trusted Publishing(OIDC)。本地 pnpm pub 负责
检查、递增 patch 版本、提交版本和推送 tt-v<版本> 标签;GitHub Actions 在
tt-cli/ 中重新安装依赖、测试、构建,再发布 maoda-commander-tt。
首次配置
将本次改造提交到
main并推送到imaoda/local-multi-repo,确保仓库根目录的.github/workflows/publish-tt.yml已在 GitHub 上,且仓库已启用 Actions。登录 npm 包设置, 在 Trusted publishing 中添加 GitHub Actions,填写:
| 字段 | 值 | |---|---| | Organization or user |
imaoda| | Repository |local-multi-repo| | Workflow filename |publish-tt.yml| | Environment name | 留空 | | Allowed actions | 允许直接执行npm publish|Workflow filename 只填文件名。只允许
npm stage publish时,发布仍需人工批准。 保存信任配置时需完成一次 npm 账号验证,后续工作流发布使用 OIDC,无需配置NPM_TOKEN或逐次登录 npm。按下面的日常流程发布,在 GitHub Actions 中确认第一次发布成功。
网页保存异常时,也可以使用官方 CLI 配置同一个信任关系(终端登录及配置时 按 npm 提示完成账号验证):
npm exec --yes [email protected] -- npm login --registry=https://registry.npmjs.org
npm exec --yes [email protected] -- npm trust github maoda-commander-tt \
--repo imaoda/local-multi-repo --file publish-tt.yml --allow-publish --yes \
--registry=https://registry.npmjs.org工作流使用 GitHub 托管的 runner、Node.js 24、npm 11.16.0 和 pnpm 11.1.2, 并固定第三方 Action 的提交 SHA。当前 GitHub 仓库为私有仓库,因此不会生成 npm provenance;这不影响 OIDC 发布。
日常发布
由 agent 代办时,直接说“发布 tt”即可;agent 按
发布 harness 执行检查、发布、等待 npm 目标版本可用,并在本机
全局安装该精确版本、验证 tt --version。无需用户自行执行下面的命令。
先提交代码,确保整个 Git 仓库没有未提交或未跟踪的文件,并切换到 main。
本地 main 可以有尚未推送的提交,但不能落后于远端或与远端分叉。
cd ~/work/local-multi-repo/tt-cli
pnpm pub命令会运行源码测试、发布脚本测试、构建和 CLI 帮助检查,然后只修改本包的
package.json 版本、创建发布提交和带注释的 tt-v<版本> 标签,原子推送
main 与这个标签。其他包的标签不会随之推送。CI 会校验标签与包版本一致;
普通代码推送不会触发 npm 发布。
推送成功表示已触发 CI,最终结果以
Publish tt 工作流
为准。等待本次发布结束再发布下一版;GitHub 的并发队列可能替换尚未开始的
发布任务。发布后,在需要更新本机全局 tt 的电脑上运行 pnpm upd。
失败后继续
- 本地检查失败:先修复并提交,再运行
pnpm pub;检查失败不会递增版本。 - 推送失败但发布提交和标签已生成:按终端提示修复网络或权限,在原发布提交上
运行
pnpm pub --retry,重推同一版本,避免再次递增。 - 推送成功但 CI 失败:若只是 npm 信任配置错误,修正后在 Actions 中重跑失败的
工作流;若需修改代码,提交修复后通过
pnpm pub发布新版本。
不要在 CI 中运行 pnpm pub:它负责本地版本提交和推送,CI 的最终发布步骤是
npm publish。已发布的版本不能重复发布。
静态文件部署
tt deploy 通过 HTTPS API 将现成的文件或目录部署到 nginx 静态目录,不执行构建。
命令面向 AI 调用:stdout 只输出一个最终 JSON,诊断信息写入 stderr;失败时退出码为 1。
tt --help 可发现该命令,tt deploy --help 查看完整说明。
# 将 dist 的内容直接部署为 demo 项目
tt deploy ./dist --name demo
# 单文件默认保持文件名,也可以用 --name 指定目标文件名
tt deploy ./report.pdf
tt deploy ./report.pdf --name latest-report.pdf
# 查看部署根下的直接子目录及访问链接
tt deploy --list
# 不可恢复地删除根下一级文件或整个项目目录
tt deploy --delete report.pdf
tt deploy --delete demo独立项目直接部署在 tt 根目录下,无需刻意嵌套。 当前服务器目标为
/root/repos/tt:tt deploy ./dist --name demo 将目录内容直接放入
/root/repos/tt/demo/,对应 https://www.imaoda.com/tt/demo/,不会额外套一层 dist/。
目标名称只能是根下一级的名称,不能传 group/demo;__catalog 是保留名称。
省略 --name 时使用源文件或目录的名称。
HTML 的静态资源使用相对于 HTML 的路径,例如 ./assets/app.js、./style.css
和 ./images/cover.png。避免 /assets/app.js 这类从域名根目录开始的路径,否则
浏览器会请求项目目录之外的位置。部署前先产出适合子路径访问的静态文件。
每次部署完整替换同名目标,包括移除旧版本中存在、本次已删除的文件。命令先上传到
临时位置,上传成功后再发布。空目录会在上传前报错;目录里的符号链接和敏感文件
(如 .git、.env、私钥)会导致整次部署拒绝,请只提供准备公开的静态产物。
点开头的路径不会通过 nginx 公开;如果目录里只有这类文件,也会因没有可验证的
公开内容而拒绝部署。
本机配置与接口鉴权
在 ~/.config/maoda-commander-tt/maoda-commander-tt.json 的已有配置中增加
staticDeploy 字段,保留其他字段。将示例 token 替换为已配置到服务器的固定 token:
{
"staticDeploy": {
"apiUrl": "https://www.imaoda.com/api/tt",
"token": "<你的固定部署 token>"
}
}apiUrl 指向部署 API,命令自动为请求添加 Authorization: Bearer <token>。
固定 token 只保存在本机配置中,不打包到 npm,也不放进命令行参数、公开目录或
返回结果。部署不需要本机 SSH 权限;换电脑时配置同一 API 地址及 token 即可。
本机需要 Node.js 22.13.0 或更高版本和 tar。API 只接受 HTTPS(本机测试的
loopback HTTP 除外),客户端不跟随重定向,以免将 token 发送到其他地址。
服务器使用 TT_DEPLOY_TOKEN 验证部署、删除和目录查询请求;TT_DEPLOY_ROOT 和
TT_DEPLOY_BASE_URL 决定落盘根目录和公开 URL,不由调用端任意指定。
当前限制为每次归档 50 MiB、最多 2 个并发操作。收到 token 的人可以部署、覆盖或删除
这个根目录下的项目;需要更换时同时更新服务器和本机配置。
服务器需要预先配置 API 和 nginx 路由,参见 部署运维说明。
当前服务器在静态入口上
保留既有跨域响应头、OPTIONS 预检及 HSTS,并设置 Cache-Control: no-cache:
允许浏览器缓存,但再次使用前向服务器确认内容是否更新。
目录页与 AI 返回值
浏览器目录入口为 https://www.imaoda.com/tt/__catalog/,JSON 清单入口为
https://www.imaoda.com/tt/__catalog/index.json。目录页只列 /root/repos/tt
下的直接子目录及链接,不列其他仓库、根下单文件或子目录里的文件;/tt/ 本身
不开放目录浏览。入口采用固定名称,知道链接的人都能看,不提供身份认证。
每次部署、删除和 --list 都会更新清单,AI 可直接使用 tt deploy --list 获取同一范围。
有首页的目录部署成功后,返回示例:
{
"code": 0,
"msg": "success",
"data": {
"name": "demo",
"type": "directory",
"state": "ready",
"url": "https://www.imaoda.com/tt/demo/",
"baseUrl": "https://www.imaoda.com/tt/demo/",
"remotePath": "/root/repos/tt/demo",
"verification": {
"state": "passed",
"checkedUrl": "https://www.imaoda.com/tt/demo/",
"httpStatus": 200
}
}
}只有服务器通过公开 URL 与文件 SHA-256 验收才报告 ready;这表示已检查文件
可访问且内容一致,不能替代浏览器
渲染和全站资源检查。目录优先使用 index.html 或 index.htm 作为首页;没有首页
但含有文件时,url 为 null,baseUrl 保留目录地址,verification.checkedUrl
指向实际检查的文件,AI 不应把目录地址当作已可访问的页面。
文件已经发布但访问检查失败时,返回 code: 1、error.stage,同时保留
data.state: "published"、URL、远端路径和检查结果,退出码为 1。AI 可以据此继续
排查访问问题;参数、配置或上传等发布前错误不附带虚构的发布成功数据。
tt deploy --list 的 data 包含 catalogUrl 和
directories: [{ "name": "demo", "url": ".../demo/", "hasIndex": true }]。
hasIndex: false 表示该目录没有首页,链接不保证返回页面。
删除文件或项目目录
tt deploy --delete <name> 删除 tt 根目录下的一个直接子项,可以是单文件,也可以
是包含多层内容的整个目录。删除不可恢复,无需交互确认;其他顶层项目保留。
名称必须是一级名称,不支持 demo/style.css 这样的内部路径,不能与本地 source、
--name 或 --list 同时使用,且不能删除保留名称 __catalog。
删除后刷新目录清单。成功时返回 code: 0,data 示例:
{
"name": "demo",
"type": "directory",
"state": "deleted",
"remotePath": "/root/repos/tt/demo",
"catalogUrl": "https://www.imaoda.com/tt/__catalog/"
}目标已不存在时同样成功,返回 state: "absent"、type: null,便于重复调用。
若公开目标已删除,但目录清单刷新或私有暂存清理失败,命令返回错误和非零退出码,
同时保留 data.state: "deleted";AI 应据此排查后续步骤,不能把它理解为目标仍在。
Shell 快捷方式
# 在 ~/work/web-proxy 中先执行 w2 start,成功后执行 pnpm gateway
tt sc w2命令日志直接输出到当前终端,gateway 在前台运行。
AI 图片生成
图片、音频与视频统一采用异步提交:默认返回 taskId,status 单次查询,
wait 等待已有任务,--output 隐含 --wait。生成是否成功以 data.state 为准,
不能只看查询命令的 code=0;超时返回 data.timedOut=true,不会取消生成。
提交返回 taskId、当前 state、outputs 和适用时的 nextCommand。
后续查询或等待直接复用返回的完整 taskId。
# 默认仅提交;后台继续生成
tt ai image '画一片绿色树叶'
# 统一参考图入口;提示词按传入顺序使用图1、图2
tt ai image --images ./composition.png --images ./palette.png \
--aspect-ratio 16:9 --model-id gpt_image_2 \
'使用图1的构图和图2的配色'
# 一次调用等待并保存;仅用 --wait 时返回产物链接或已有本地路径
tt ai image --output ./result.png '画一片绿色树叶'
# 查询、等待同一个任务,不会重新生成
tt ai image status --task-id '<task-id>'
tt ai image wait --task-id '<task-id>' --timeout-seconds 600 --output ./result.png
# 没收到提交输出或丢失 ID 时,查看本机持久记录
tt ai image list等待时间为 1–600 的整数秒,默认 360 秒。outputs[] 中每项含 downloadUrl 或
绝对 localPath,两者互斥;无可用产物时为 []。使用 --output 后返回本地路径,
再次 wait --output 只保存已有产物。参考图使用可重复的 --images <path> 传入,
提示词按顺序以“图1、图2”引用。
支持模型参数的生成方式默认使用 gpt_image_2_5_sunburst;其他生成方式忽略
--model-id 和 --effort。已知模型的 effort 范围如下:
| --model-id | --effort 可选值 | 默认 effort |
| --- | --- | --- |
| gpt_image_2(image2) | medium、high | high |
| gpt_image_2_5_sunburst(image2.5 Sunburst,默认模型) | high、xhigh、max | max |
其他图片模型 ID 允许透传,其 effort 不校验,省略时沿用 medium。
支持的比例为 21:9、16:9、3:2、4:3、1:1、3:4、2:3、9:16 和 auto。
省略时由生成方式决定默认比例;需要固定比例时显式传入,自动选择比例用 auto。
--quiet 关闭提交进度;stdout 始终只有一个最终 JSON envelope。
ai image list 只返回本机保存的图片任务记录,不包含全部远端任务,调用方应保存
提交返回的 ID。任务可在提交命令退出后继续;重启或中断后的 unknown 表示无法确认
结果,应先查询原任务和产物。status / wait 不会重新生成。
AI 音频生成
ai audio 接受一段自然语言提示词,完成远端提交后返回 taskId、
state=submitted 和 nextCommand。参考音频可选;提供时,
提示词需自行用 @音频1 引用它,命令不会改写提示词。
# 提交纯文本生成音频任务
tt ai audio '萝莉音说:“我今天不回来了”'
# 参考一段本地音频的音色
tt ai audio --audio ./reference.mp3 \
'@音频1 参考它的音色,说:“我生气了,哼”'
# 等待生成并下载到本地(--output 隐含 --wait)
tt ai audio --audio ./reference.mp3 --output ./result.mp3 \
'@音频1 参考它的音色,说:“我生气了,哼”'继续查询或等待既有任务:
tt ai audio --wait '用温柔的声音说:“晚安”'
tt ai audio status --task-id 'xyq:<threadId>'
tt ai audio wait --task-id 'xyq:<threadId>' --timeout-seconds 600 --output ./result.mp3命令固定使用 seedaudio_1.0、MP3 和 44.1 kHz;等待默认 60 秒,可通过
--timeout-seconds 设为 1–600 秒。参考音频直接上传,不进入持久素材缓存。
云端产物字段统一为 outputs[].downloadUrl,本地产物为 outputs[].localPath;
旧同步调用需增加 --wait,旧 remoteUrl 读取需迁移到 downloadUrl。
超时保留 taskId 与 nextCommand,下载异常的恢复命令还保留绝对输出路径。
音频和视频尚无本机任务列表,调用方须保存提交返回的 ID;查询仍需对应账号权限。
AI 视频生成
ai video 完成素材上传与任务提交后返回 taskId,生成异步进行(通常约 5 分钟)。
查询和等待复用返回的完整任务 ID。
--model-id 仅接受 seedance2.0、seedance2.0_fast、seedance2.5、wan3.0、
minimax-h3,默认 seedance2.5;未知值在提交前报输入错误。
--duration-seconds 必填,为 4–30 的整数;--output-count 接受正整数,当前实际生成
1 个。--resolution 接受 480p、720p、1080p、2k、4k,默认 720p;
除 minimax-h3 外,2k/4k 会调整为 1080p。minimax-h3 的输出分辨率不由此参数
控制,实际尺寸以产物为准。参数调整在 appliedParameters 和 adjustments 中返回。
# 提交并立即返回 taskId 与 nextCommand
tt ai video --duration-seconds 8 --images ./ref.png '图1 中的角色转身微笑'
# 一次调用等到结束并下载最高画质产物(隐含 --wait)
tt ai video --duration-seconds 8 --output ./result.mp4 '海边日出,慢镜头'
# 用 taskId 查询/等待既有任务
tt ai video status --task-id '<task-id>'
tt ai video wait --task-id '<task-id>' --timeout-seconds 600 --output ./result.mp4等待超时不是失败:wait 返回 timedOut: true 与当前 state,可用同一 taskId
继续等待;ai video --wait 和 --output 同样在 data 中返回当前状态与 timedOut。
查询或下载异常时,error 中保留 taskId、recoverable 和恢复命令。
保存 taskId 后可跨进程查询;仍需有效登录态及任务访问权限,任务和产物链接的
保留期限由服务端决定。
联网搜索
ai websearch 通过火山方舟 Responses API 的 web_search 工具搜索,data.text
是回答正文,data.content 是含引用标注的原始数组。API Key 按
ARK_API_KEY 环境变量 → 跨应用设置 ark.apiKey → 内置回退值 的顺序解析。
如何扩展新命令
- 在
src/commands/<group>/下新建xxx-command.ts,继承BaseCommand<TOptions, TData>:
import type { Command } from "commander";
import { ok, type Result } from "rockbed/error";
import { BaseCommand, fail } from "../../core";
import type { ICommandActionContext, ICommandMeta } from "../../core";
import { doSomething } from "../../modules/my-domain";
interface IMyOptions {
readonly verbose: boolean;
}
interface IMyOutput {
readonly target: string;
readonly result: string;
}
export class MyCommand extends BaseCommand<IMyOptions, IMyOutput> {
protected _meta(): ICommandMeta {
return { name: "my-cmd", description: "这是我的自定义命令", aliases: ["mc"] };
// hidden: true 隐藏;output: "passthrough" 透传子进程输出
}
protected _configureArguments(cmd: Command): void {
cmd.argument("<target>", "操作目标");
}
protected _configureOptions(cmd: Command): void {
cmd.option("--verbose", "输出更多诊断到 stderr", false);
}
protected _agentContract(): readonly string[] {
return ["data.result 为处理结果;target 不存在时 error.stage=input。"];
}
protected async _execute(
ctx: ICommandActionContext<IMyOptions>,
): Promise<Result<IMyOutput>> {
const target = String(ctx.args[0] ?? "").trim();
if (!target) {
return fail(1, "请指定 target", { stage: "input" });
}
if (ctx.options.verbose) {
this._debug(`target = ${target}`); // 写 stderr,不污染 stdout
}
const result = await doSomething(target); // Result<string>
if (!result.ok) {
return fail(result.code, result.msg, { stage: "process", target });
}
return ok({ target, result: result.value });
}
}- 注册:顶层命令加入
src/commands/index.ts的createRootCommands(); 子命令在所属命令组的_registerSubcommands()中this._addSubcommand(new MyCommand())。 - 在同目录添加
my-command.test.ts:mockprocess.stdout.write, 调用new MyCommand().command.parseAsync([...], { from: "user" }),断言 envelope 与process.exitCode。
新命令会自动出现在 --help、help [command] 中,并自动获得“执行案例”和“Agent 调用契约”页脚。
架构说明
分层与依赖方向
core(框架,唯一依赖 commander)← commands(参数定义、把领域 Result 整形为 data)
→ modules(领域逻辑,返回 Result<T>,不写 stdout)→ bedrock(进程、下载、配置等基础设施)。
依赖只能向下,modules/bedrock 不得引用 commands/core。
命令基类
| 成员 | 说明 |
|---|---|
| _meta() | 名称、描述、别名、hidden、output |
| _execute(ctx) | 核心逻辑,返回 Result<TData>;json 模式下由基类输出 envelope |
| _configureOptions(cmd) / _configureArguments(cmd) | 配置 commander 选项/参数 |
| _agentContract() | 该命令特有的契约行,追加到 --help |
| _toSuccessEnvelope(data) | 覆盖成功 envelope(如成功退出但业务码非 0) |
| _debug(text) | 写 stderr 诊断 |
| _output(text) | 仅 passthrough 模式:写 stdout |
| BaseCommandGroup._registerSubcommands() | 命令组挂接子命令 |
commander Command 在首次访问 .command 时才构建,因此上述钩子可以使用构造函数注入的服务。
错误处理
import { err, ok } from "rockbed/error";
import { fail, failFrom } from "../../core";
return ok(data); // {"code":0,"msg":"success","data":...}
return fail(1001, "文件不存在", { stage: "read" }); // {"code":1001,"msg":"...","error":{"stage":"read"}}
return fail(1, "已发布但校验失败", { stage: "verify" }, { state: "published" }); // 附带部分结果 data
return failFrom(domainResult, { stage: "upload", taskId }); // 保留领域错误码/消息,补充 agent 细节
return err(1, "plain"); // {"code":1,"msg":"plain"}长耗时命令使用 createAgentProgressReporter({ enabled, command, phase, message })
向 stderr 输出 tt.agent.v1 进度事件,dispose() 后停止。
生命周期事件
const cmd = new HelloGreetCommand();
cmd.onBeforeExecute((ctx) => console.error("即将执行", ctx.args));
cmd.onAfterExecute((result) => {
if (!result.ok) console.error(`执行失败: [${result.code}] ${result.msg}`);
});Scripts
| 脚本 | 说明 |
|---|---|
| pnpm dev | 使用 tsx 直接运行(开发用) |
| pnpm dev:debug | 启动 Node inspect 调试 |
| pnpm test | 运行全部 *.test.ts(node:test) |
| pnpm build | tsup 构建 dist/main.js(CLI)与 dist/index.js(库) |
| pnpm test:dist | 构建产物冒烟(--help) |
| pnpm start | 运行编译后产物 |
| pnpm clean | 清理 dist 目录 |
| pnpm test:release | 在临时 Git 仓库中验证发布脚本 |
| pnpm pub | 检查、升 patch 版本并推送 tt 标签,触发 CI 发布 |
| pnpm pub --retry | 重推当前发布提交和标签,不递增版本 |
| pnpm upd | 将本机全局 tt 更新到 npm 上的最新版本 |
技术栈
- TypeScript 5.x (strict mode)
- commander 13.x
- rockbed (
Result/Disposable/Emitter) - tsx (开发热运行)、tsup (构建)、node:test (测试)
