npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

maoda-commander-tt

v0.0.59

Published

一个通用命令行工具

Readme

tt-cli (tt)

一个 AI-native 的通用 CLI:默认调用方是 AI agent。JSON 模式的命令在 stdout 只输出 一行 envelope,诊断与进度走 stderr,失败退出码为 1;透传命令在 --help 中声明输出模式。 基于 commanderrockbedResult / 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 codextt 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-astragpt-5.6-sol;推理强度可选 lowmediumhighxhighmaxultra

macOS/Linux 下通过 nohup 启动后台 worker,由 worker 运行 codex app-server, 通过 initializeproject/listthread/startturn/start 提交任务。 工作目录支持普通目录,无需 Git 仓库。固定无沙箱 danger-full-access 和审批策略 never,保留本机 Codex 配置、登录态与会话持久化。

服务端确认 turn/start 后 tt 立即返回一行 JSON,data 包含 status: "launched"、 worker pidthreadIdturnIdprojectIdcwdmodelreasoning。 这表示任务已提交,不代表执行成功。启动或提交错误会直接返回失败;超时不自动重试, 以免重复创建任务。后台 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-solgpt-6-astra;推理强度按 Traex 支持范围提供 lowmediumhighxhigh。需要本机已安装并登录 traex

tt codex 一样,通过 nohup 后台 worker 运行 traex app-server,固定 danger-full-accessnever,收到 turn/start 确认后立即返回一行 JSON。 保留本机 Traex 配置、登录态及会话持久化,终端 IO 丢弃,不生成任务日志, 不提供任务跟踪。后台 worker 保持连接到本轮结束后退出。

返回 status: "launched"、worker pidthreadIdturnIdcwdmodelreasoningprojectId: 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

首次配置

  1. 将本次改造提交到 main 并推送到 imaoda/local-multi-repo,确保仓库根目录的 .github/workflows/publish-tt.yml 已在 GitHub 上,且仓库已启用 Actions。

  2. 登录 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。

  3. 按下面的日常流程发布,在 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。已发布的版本不能重复发布。

参见 npm Trusted Publishing 文档

静态文件部署

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/tttt 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_ROOTTT_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.htmlindex.htm 作为首页;没有首页 但含有文件时,urlnullbaseUrl 保留目录地址,verification.checkedUrl 指向实际检查的文件,AI 不应把目录地址当作已可访问的页面。

文件已经发布但访问检查失败时,返回 code: 1error.stage,同时保留 data.state: "published"、URL、远端路径和检查结果,退出码为 1。AI 可以据此继续 排查访问问题;参数、配置或上传等发布前错误不附带虚构的发布成功数据。

tt deploy --listdata 包含 catalogUrldirectories: [{ "name": "demo", "url": ".../demo/", "hasIndex": true }]hasIndex: false 表示该目录没有首页,链接不保证返回页面。

删除文件或项目目录

tt deploy --delete <name> 删除 tt 根目录下的一个直接子项,可以是单文件,也可以 是包含多层内容的整个目录。删除不可恢复,无需交互确认;其他顶层项目保留。 名称必须是一级名称,不支持 demo/style.css 这样的内部路径,不能与本地 source、 --name--list 同时使用,且不能删除保留名称 __catalog

删除后刷新目录清单。成功时返回 code: 0data 示例:

{
  "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 图片生成

图片、音频与视频统一采用异步提交:默认返回 taskIdstatus 单次查询, wait 等待已有任务,--output 隐含 --wait。生成是否成功以 data.state 为准, 不能只看查询命令的 code=0;超时返回 data.timedOut=true,不会取消生成。

提交返回 taskId、当前 stateoutputs 和适用时的 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) | mediumhigh | high | | gpt_image_2_5_sunburst(image2.5 Sunburst,默认模型) | highxhighmax | max |

其他图片模型 ID 允许透传,其 effort 不校验,省略时沿用 medium。 支持的比例为 21:916:93:24:31:13:42:39:16auto。 省略时由生成方式决定默认比例;需要固定比例时显式传入,自动选择比例用 auto--quiet 关闭提交进度;stdout 始终只有一个最终 JSON envelope。

ai image list 只返回本机保存的图片任务记录,不包含全部远端任务,调用方应保存 提交返回的 ID。任务可在提交命令退出后继续;重启或中断后的 unknown 表示无法确认 结果,应先查询原任务和产物。status / wait 不会重新生成。

AI 音频生成

ai audio 接受一段自然语言提示词,完成远端提交后返回 taskIdstate=submittednextCommand。参考音频可选;提供时, 提示词需自行用 @音频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。 超时保留 taskIdnextCommand,下载异常的恢复命令还保留绝对输出路径。 音频和视频尚无本机任务列表,调用方须保存提交返回的 ID;查询仍需对应账号权限。

AI 视频生成

ai video 完成素材上传与任务提交后返回 taskId,生成异步进行(通常约 5 分钟)。 查询和等待复用返回的完整任务 ID。

--model-id 仅接受 seedance2.0seedance2.0_fastseedance2.5wan3.0minimax-h3,默认 seedance2.5;未知值在提交前报输入错误。 --duration-seconds 必填,为 4–30 的整数;--output-count 接受正整数,当前实际生成 1 个。--resolution 接受 480p720p1080p2k4k,默认 720p; 除 minimax-h3 外,2k/4k 会调整为 1080pminimax-h3 的输出分辨率不由此参数 控制,实际尺寸以产物为准。参数调整在 appliedParametersadjustments 中返回。

# 提交并立即返回 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 中保留 taskIdrecoverable 和恢复命令。

保存 taskId 后可跨进程查询;仍需有效登录态及任务访问权限,任务和产物链接的 保留期限由服务端决定。

联网搜索

ai websearch 通过火山方舟 Responses API 的 web_search 工具搜索,data.text 是回答正文,data.content 是含引用标注的原始数组。API Key 按 ARK_API_KEY 环境变量 → 跨应用设置 ark.apiKey → 内置回退值 的顺序解析。

如何扩展新命令

  1. 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 });
  }
}
  1. 注册:顶层命令加入 src/commands/index.tscreateRootCommands(); 子命令在所属命令组的 _registerSubcommands()this._addSubcommand(new MyCommand())
  2. 在同目录添加 my-command.test.ts:mock process.stdout.write, 调用 new MyCommand().command.parseAsync([...], { from: "user" }),断言 envelope 与 process.exitCode

新命令会自动出现在 --helphelp [command] 中,并自动获得“执行案例”和“Agent 调用契约”页脚。

架构说明

分层与依赖方向

core(框架,唯一依赖 commander)← commands(参数定义、把领域 Result 整形为 data) → modules(领域逻辑,返回 Result<T>,不写 stdout)→ bedrock(进程、下载、配置等基础设施)。 依赖只能向下,modules/bedrock 不得引用 commands/core

命令基类

| 成员 | 说明 | |---|---| | _meta() | 名称、描述、别名、hiddenoutput | | _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 (测试)