maoda-commander-tt 0.0.47 → 0.0.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +369 -101
- package/dist/chunk-GAOUOOBO.js +7973 -0
- package/dist/chunk-GAOUOOBO.js.map +1 -0
- package/dist/chunk-S5SWRA53.js +354 -0
- package/dist/chunk-S5SWRA53.js.map +1 -0
- package/dist/image-worker-main.d.ts +2 -0
- package/dist/image-worker-main.js +89 -0
- package/dist/image-worker-main.js.map +1 -0
- package/dist/index.d.ts +346 -63
- package/dist/index.js +56 -5
- package/dist/main.js +15 -6905
- package/dist/main.js.map +1 -1
- package/package.json +2 -3
- package/dist/chunk-S2SVYY3T.js +0 -367
- package/dist/chunk-S2SVYY3T.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,36 +1,71 @@
|
|
|
1
|
-
# template-commander
|
|
1
|
+
# template-commander (`tt`)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一个 AI-native 的通用 CLI:默认调用方是 AI agent。每个命令在 stdout 只输出一行
|
|
4
|
+
JSON envelope,诊断与进度走 stderr,失败退出码为 1,`--help` 自带面向 agent 的调用契约。
|
|
5
|
+
基于 [commander](https://github.com/tj/commander.js) 与 [rockbed](https://www.npmjs.com/package/rockbed)
|
|
6
|
+
(`Result` / `Disposable` / `Emitter`)。
|
|
4
7
|
|
|
5
8
|
## 特性
|
|
6
9
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
10
|
+
- **Agent 输出契约(tt.agent.v1)** — stdout 一行 JSON、stderr 结构化进度、退出码语义稳定,
|
|
11
|
+
Commander 的输入错误也以同样的 envelope 输出
|
|
12
|
+
- **命令只返回 `Result<TData>`** — `ok(data)` / `fail(code, msg, { stage, nextCommand, ... })`,
|
|
13
|
+
基类负责输出与退出码;`passthrough` 模式用于透传 git、开发服务器等面向人的场景
|
|
14
|
+
- **清晰分层** — `core`(框架)→ `commands`(表现)→ `modules`(领域)→ `bedrock`(基础设施)
|
|
15
|
+
- **可发现性** — `tt --help` 列公开命令,`tt -hh` 含隐藏命令;每个命令 `--help` 含示例与契约
|
|
16
|
+
- **Disposable / 事件** — 命令与服务继承 `Disposable`,提供 `onBeforeExecute` / `onAfterExecute`
|
|
17
|
+
- **TypeScript strict + node:test** — 全量 strict,测试与源文件同目录
|
|
18
|
+
|
|
19
|
+
## Agent 调用契约
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
$ tt hello greet
|
|
23
|
+
{"code":0,"msg":"success","data":{"message":"Hello, World!"}}
|
|
24
|
+
|
|
25
|
+
$ tt deploy --bogus ; echo "exit=$?"
|
|
26
|
+
{"code":1,"msg":"unknown option '--bogus'","error":{"stage":"input"}}
|
|
27
|
+
exit=1
|
|
28
|
+
|
|
29
|
+
$ tt ai video wait --task-id bad ; echo "exit=$?"
|
|
30
|
+
{"code":18017,"msg":"...","error":{"stage":"wait","taskId":"bad","recoverable":true,"nextCommand":"tt ai video wait --task-id bad"}}
|
|
31
|
+
exit=1
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- 成功:`{"code":0,"msg":"success","data":...}`;失败:`{"code":非0,"msg":...,"error"?:{...},"data"?:...}`。
|
|
35
|
+
`error.stage` 说明失败阶段,`error.nextCommand` 是可直接执行的后续命令,
|
|
36
|
+
`data` 是失败时仍成立的部分结果(如“已发布但校验失败”)。
|
|
37
|
+
- stderr 上的 `{"protocol":"tt.agent.v1","type":"progress",...,"final":false}` 只是进度,
|
|
38
|
+
必须等待进程退出后再读取 stdout。
|
|
39
|
+
- 个别命令允许成功退出但业务码非 0(如 `tool get-config` 未配置返回 `16003`),
|
|
40
|
+
会在该命令 `--help` 的“Agent 调用契约”里写明。
|
|
13
41
|
|
|
14
42
|
## 目录结构
|
|
15
43
|
|
|
16
44
|
```
|
|
17
45
|
template-commander/
|
|
18
46
|
├── src/
|
|
19
|
-
│ ├── main.ts
|
|
20
|
-
│ ├── core
|
|
21
|
-
│
|
|
22
|
-
│ │ ├──
|
|
23
|
-
│ │
|
|
24
|
-
│ ├──
|
|
25
|
-
│ │ ├──
|
|
26
|
-
│ │ ├──
|
|
27
|
-
│ │
|
|
28
|
-
│ └──
|
|
29
|
-
│
|
|
30
|
-
│
|
|
31
|
-
│
|
|
32
|
-
├──
|
|
33
|
-
|
|
47
|
+
│ ├── main.ts # 可执行入口:注册 createRootCommands() 并运行 CliApp
|
|
48
|
+
│ ├── index.ts # 库入口:导出 core 与内置命令
|
|
49
|
+
│ ├── core/ # 命令框架(唯一依赖 commander 的层)
|
|
50
|
+
│ │ ├── cli-app.ts # 根命令、-hh、根级输入错误 envelope、退出码
|
|
51
|
+
│ │ ├── abstract-command.ts # Command 懒构建、生命周期、按输出模式输出
|
|
52
|
+
│ │ ├── base-command.ts # 叶子命令(默认 json)
|
|
53
|
+
│ │ ├── base-command-group.ts # 命令组(默认 passthrough,打印帮助)
|
|
54
|
+
│ │ ├── agent-protocol.ts # envelope 类型、fail()/failFrom()
|
|
55
|
+
│ │ ├── agent-progress.ts # stderr 进度心跳
|
|
56
|
+
│ │ └── command-meta.ts # ICommandMeta: name/description/aliases/hidden/output
|
|
57
|
+
│ ├── commands/ # CLI 表现层,每个命令组一个目录
|
|
58
|
+
│ │ ├── index.ts # 顶层注册表 createRootCommands()
|
|
59
|
+
│ │ ├── ai/ deploy/ git/ hello/ pippit/ shortcut/ tool/
|
|
60
|
+
│ ├── modules/ # 领域逻辑:不依赖 commander,返回 Result<T>
|
|
61
|
+
│ │ ├── ark/ git/ pippit/ static-deploy/ video-generation/ xyq/ xyq-tasks/
|
|
62
|
+
│ ├── bedrock/ # 与业务无关的基础设施
|
|
63
|
+
│ │ ├── config/ cross-app-settings/ download/ pkg/ process/ uuid/
|
|
64
|
+
│ └── constants/ # 兼容性常量(已弃用的内置回退值)
|
|
65
|
+
├── scripts/release.mjs # 发布脚本
|
|
66
|
+
├── ops/static-deploy/ # 服务器侧部署说明
|
|
67
|
+
├── AGENTS.md # agent 工作 harness(契约、分层、发布)
|
|
68
|
+
└── package.json
|
|
34
69
|
```
|
|
35
70
|
|
|
36
71
|
## 快速开始
|
|
@@ -41,21 +76,20 @@ template-commander/
|
|
|
41
76
|
# 安装依赖
|
|
42
77
|
pnpm install
|
|
43
78
|
|
|
44
|
-
#
|
|
45
|
-
pnpm dev --
|
|
46
|
-
pnpm dev
|
|
47
|
-
pnpm dev
|
|
48
|
-
pnpm dev
|
|
49
|
-
pnpm dev
|
|
79
|
+
# 开发运行(pnpm 会把额外参数直接传给 tsx,不要再写 `--`,否则 commander 会把它当作选项结束符)
|
|
80
|
+
pnpm dev --help
|
|
81
|
+
pnpm dev -hh # 含隐藏命令的完整列表
|
|
82
|
+
pnpm dev hello greet Alice -u
|
|
83
|
+
pnpm dev git mm "update docs"
|
|
84
|
+
pnpm dev ai video --help
|
|
50
85
|
|
|
51
86
|
# 调试模式 (支持 Chrome DevTools / VS Code 断点)
|
|
52
|
-
pnpm dev:debug
|
|
53
|
-
|
|
54
|
-
# 构建
|
|
55
|
-
pnpm build
|
|
87
|
+
pnpm dev:debug hello greet Alice
|
|
56
88
|
|
|
57
|
-
#
|
|
58
|
-
pnpm
|
|
89
|
+
# 类型检查、测试、构建、产物冒烟
|
|
90
|
+
pnpm exec tsc --noEmit -p .
|
|
91
|
+
pnpm test
|
|
92
|
+
pnpm build && pnpm test:dist
|
|
59
93
|
```
|
|
60
94
|
|
|
61
95
|
## 发布到 npm
|
|
@@ -135,6 +169,141 @@ pnpm pub
|
|
|
135
169
|
|
|
136
170
|
参见 [npm Trusted Publishing 文档](https://docs.npmjs.com/trusted-publishers/)。
|
|
137
171
|
|
|
172
|
+
## 静态文件部署
|
|
173
|
+
|
|
174
|
+
`tt deploy` 通过 HTTPS API 将现成的文件或目录部署到 nginx 静态目录,不执行构建。
|
|
175
|
+
命令面向 AI 调用:stdout 只输出一个最终 JSON,诊断信息写入 stderr;失败时退出码为 1。
|
|
176
|
+
`tt --help` 可发现该命令,`tt deploy --help` 查看完整说明。
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# 将 dist 的内容直接部署为 demo 项目
|
|
180
|
+
tt deploy ./dist --name demo
|
|
181
|
+
|
|
182
|
+
# 单文件默认保持文件名,也可以用 --name 指定目标文件名
|
|
183
|
+
tt deploy ./report.pdf
|
|
184
|
+
tt deploy ./report.pdf --name latest-report.pdf
|
|
185
|
+
|
|
186
|
+
# 查看部署根下的直接子目录及访问链接
|
|
187
|
+
tt deploy --list
|
|
188
|
+
|
|
189
|
+
# 不可恢复地删除根下一级文件或整个项目目录
|
|
190
|
+
tt deploy --delete report.pdf
|
|
191
|
+
tt deploy --delete demo
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**独立项目直接部署在 tt 根目录下,无需刻意嵌套。** 当前服务器目标为
|
|
195
|
+
`/root/repos/tt`:`tt deploy ./dist --name demo` 将目录内容直接放入
|
|
196
|
+
`/root/repos/tt/demo/`,对应 `https://www.imaoda.com/tt/demo/`,不会额外套一层 `dist/`。
|
|
197
|
+
目标名称只能是根下一级的名称,不能传 `group/demo`;`__catalog` 是保留名称。
|
|
198
|
+
省略 `--name` 时使用源文件或目录的名称。
|
|
199
|
+
|
|
200
|
+
**HTML 的静态资源使用相对于 HTML 的路径**,例如 `./assets/app.js`、`./style.css`
|
|
201
|
+
和 `./images/cover.png`。避免 `/assets/app.js` 这类从域名根目录开始的路径,否则
|
|
202
|
+
浏览器会请求项目目录之外的位置。部署前先产出适合子路径访问的静态文件。
|
|
203
|
+
|
|
204
|
+
每次部署完整替换同名目标,包括移除旧版本中存在、本次已删除的文件。命令先上传到
|
|
205
|
+
临时位置,上传成功后再发布。空目录会在上传前报错;目录里的符号链接和敏感文件
|
|
206
|
+
(如 `.git`、`.env`、私钥)会导致整次部署拒绝,请只提供准备公开的静态产物。
|
|
207
|
+
点开头的路径不会通过 nginx 公开;如果目录里只有这类文件,也会因没有可验证的
|
|
208
|
+
公开内容而拒绝部署。
|
|
209
|
+
|
|
210
|
+
### 本机配置与接口鉴权
|
|
211
|
+
|
|
212
|
+
在 `~/.config/maoda-commander-tt/maoda-commander-tt.json` 的已有配置中增加
|
|
213
|
+
`staticDeploy` 字段,保留其他字段。将示例 token 替换为已配置到服务器的固定 token:
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"staticDeploy": {
|
|
218
|
+
"apiUrl": "https://www.imaoda.com/api/tt",
|
|
219
|
+
"token": "<你的固定部署 token>"
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`apiUrl` 指向部署 API,命令自动为请求添加 `Authorization: Bearer <token>`。
|
|
225
|
+
固定 token 只保存在本机配置中,不打包到 npm,也不放进命令行参数、公开目录或
|
|
226
|
+
返回结果。部署不需要本机 SSH 权限;换电脑时配置同一 API 地址及 token 即可。
|
|
227
|
+
本机需要 Node.js 22.13.0 或更高版本和 `tar`。API 只接受 HTTPS(本机测试的
|
|
228
|
+
loopback HTTP 除外),客户端不跟随重定向,以免将 token 发送到其他地址。
|
|
229
|
+
|
|
230
|
+
服务器使用 `TT_DEPLOY_TOKEN` 验证部署、删除和目录查询请求;`TT_DEPLOY_ROOT` 和
|
|
231
|
+
`TT_DEPLOY_BASE_URL` 决定落盘根目录和公开 URL,不由调用端任意指定。
|
|
232
|
+
当前限制为每次归档 50 MiB、最多 2 个并发操作。收到 token 的人可以部署、覆盖或删除
|
|
233
|
+
这个根目录下的项目;需要更换时同时更新服务器和本机配置。
|
|
234
|
+
|
|
235
|
+
服务器需要预先配置 API 和 nginx 路由,参见 [部署运维说明](ops/static-deploy/README.md)。
|
|
236
|
+
当前服务器在静态入口上
|
|
237
|
+
保留既有跨域响应头、OPTIONS 预检及 HSTS,并设置 `Cache-Control: no-cache`:
|
|
238
|
+
允许浏览器缓存,但再次使用前向服务器确认内容是否更新。
|
|
239
|
+
|
|
240
|
+
### 目录页与 AI 返回值
|
|
241
|
+
|
|
242
|
+
浏览器目录入口为 `https://www.imaoda.com/tt/__catalog/`,JSON 清单入口为
|
|
243
|
+
`https://www.imaoda.com/tt/__catalog/index.json`。目录页只列 `/root/repos/tt`
|
|
244
|
+
下的直接子目录及链接,不列其他仓库、根下单文件或子目录里的文件;`/tt/` 本身
|
|
245
|
+
不开放目录浏览。入口采用固定名称,知道链接的人都能看,不提供身份认证。
|
|
246
|
+
每次部署、删除和 `--list` 都会更新清单,AI 可直接使用 `tt deploy --list` 获取同一范围。
|
|
247
|
+
|
|
248
|
+
有首页的目录部署成功后,返回示例:
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
{
|
|
252
|
+
"code": 0,
|
|
253
|
+
"msg": "success",
|
|
254
|
+
"data": {
|
|
255
|
+
"name": "demo",
|
|
256
|
+
"type": "directory",
|
|
257
|
+
"state": "ready",
|
|
258
|
+
"url": "https://www.imaoda.com/tt/demo/",
|
|
259
|
+
"baseUrl": "https://www.imaoda.com/tt/demo/",
|
|
260
|
+
"remotePath": "/root/repos/tt/demo",
|
|
261
|
+
"verification": {
|
|
262
|
+
"state": "passed",
|
|
263
|
+
"checkedUrl": "https://www.imaoda.com/tt/demo/",
|
|
264
|
+
"httpStatus": 200
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
只有服务器通过公开 URL 与文件 SHA-256 验收才报告 `ready`;这表示已检查文件
|
|
271
|
+
可访问且内容一致,不能替代浏览器
|
|
272
|
+
渲染和全站资源检查。目录优先使用 `index.html` 或 `index.htm` 作为首页;没有首页
|
|
273
|
+
但含有文件时,`url` 为 `null`,`baseUrl` 保留目录地址,`verification.checkedUrl`
|
|
274
|
+
指向实际检查的文件,AI 不应把目录地址当作已可访问的页面。
|
|
275
|
+
|
|
276
|
+
文件已经发布但访问检查失败时,返回 `code: 1`、`error.stage`,同时保留
|
|
277
|
+
`data.state: "published"`、URL、远端路径和检查结果,退出码为 1。AI 可以据此继续
|
|
278
|
+
排查访问问题;参数、配置或上传等发布前错误不附带虚构的发布成功数据。
|
|
279
|
+
|
|
280
|
+
`tt deploy --list` 的 `data` 包含 `catalogUrl` 和
|
|
281
|
+
`directories: [{ "name": "demo", "url": ".../demo/", "hasIndex": true }]`。
|
|
282
|
+
`hasIndex: false` 表示该目录没有首页,链接不保证返回页面。
|
|
283
|
+
|
|
284
|
+
### 删除文件或项目目录
|
|
285
|
+
|
|
286
|
+
`tt deploy --delete <name>` 删除 tt 根目录下的一个直接子项,可以是单文件,也可以
|
|
287
|
+
是包含多层内容的整个目录。删除不可恢复,无需交互确认;其他顶层项目保留。
|
|
288
|
+
名称必须是一级名称,不支持 `demo/style.css` 这样的内部路径,不能与本地 source、
|
|
289
|
+
`--name` 或 `--list` 同时使用,且不能删除保留名称 `__catalog`。
|
|
290
|
+
|
|
291
|
+
删除后刷新目录清单。成功时返回 `code: 0`,`data` 示例:
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
{
|
|
295
|
+
"name": "demo",
|
|
296
|
+
"type": "directory",
|
|
297
|
+
"state": "deleted",
|
|
298
|
+
"remotePath": "/root/repos/tt/demo",
|
|
299
|
+
"catalogUrl": "https://www.imaoda.com/tt/__catalog/"
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
目标已不存在时同样成功,返回 `state: "absent"`、`type: null`,便于重复调用。
|
|
304
|
+
若公开目标已删除,但目录清单刷新或私有暂存清理失败,命令返回错误和非零退出码,
|
|
305
|
+
同时保留 `data.state: "deleted"`;AI 应据此排查后续步骤,不能把它理解为目标仍在。
|
|
306
|
+
|
|
138
307
|
## Shell 快捷方式
|
|
139
308
|
|
|
140
309
|
```bash
|
|
@@ -144,124 +313,221 @@ tt sc w2
|
|
|
144
313
|
|
|
145
314
|
命令日志直接输出到当前终端,gateway 在前台运行。
|
|
146
315
|
|
|
316
|
+
## AI 图片生成
|
|
317
|
+
|
|
318
|
+
图片、音频与视频统一采用异步提交:默认返回 `taskId`,`status` 单次查询,
|
|
319
|
+
`wait` 等待已有任务,`--output` 隐含 `--wait`。生成是否成功以 `data.state` 为准,
|
|
320
|
+
不能只看查询命令的 `code=0`;超时返回 `data.timedOut=true`,不会取消生成。
|
|
321
|
+
|
|
322
|
+
`ai image` 保存本机任务并启动独立后台进程,确认接管后返回
|
|
323
|
+
`taskId`、当前 `state` 和 `nextCommand`。图片 ID 格式为 `local-image:<uuid>`;
|
|
324
|
+
`submitted` 表示本地任务已受理,`working` 表示后台进程正在执行。
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
# 默认仅提交;后台继续生成
|
|
328
|
+
tt ai image '画一片绿色树叶'
|
|
329
|
+
|
|
330
|
+
# 一次调用等待并保存;也可仅用 --wait 返回归档图片路径
|
|
331
|
+
tt ai image --output ./result.png '画一片绿色树叶'
|
|
332
|
+
|
|
333
|
+
# 查询、等待同一个任务,不会重新生成
|
|
334
|
+
tt ai image status --task-id 'local-image:<uuid>'
|
|
335
|
+
tt ai image wait --task-id 'local-image:<uuid>' --timeout-seconds 600 --output ./result.png
|
|
336
|
+
|
|
337
|
+
# 没收到提交输出或丢失 ID 时,查看本机持久记录
|
|
338
|
+
tt ai image list
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
等待默认 360 秒,上限 600 秒。完成时 `outputs[].localPath` 为绝对路径;再次
|
|
342
|
+
`wait --output` 只复制已有产物。参考图继续通过提示词中的绝对路径提供。
|
|
343
|
+
`--quiet` 关闭提交进度;stdout 始终只有一个最终 JSON envelope。
|
|
344
|
+
|
|
345
|
+
任务记录默认位于 `~/.tt/ai/image-tasks`,可用 `TT_AI_IMAGE_TASKS_DIR` 指定持久目录。
|
|
346
|
+
每个任务独立保存请求、内部 Codex 会话 ID 和产物;`list` 返回任务状态,
|
|
347
|
+
不会暴露提示词和会话内容。图片经完整 PNG 校验后原子归档,查询仅认已归档产物。
|
|
348
|
+
这些记录不会随 `ai video cache clear` 清理。
|
|
349
|
+
|
|
350
|
+
后台任务可以在提交命令退出后继续执行;机器重启或 worker 失联后,若无完整归档
|
|
351
|
+
图片,会报告 `state=unknown`,不会隐式 `codex exec resume` 或重新生成。
|
|
352
|
+
仅存在临时文件不代表完成。新命令不是跨机器任务服务,也不保证生图工具中断后
|
|
353
|
+
能接回同一次远端生成。Codex 会话会保留,便于后续诊断。
|
|
354
|
+
|
|
355
|
+
迁移旧调用:`tt ai image '...'` 若需要直接拿文件,改为
|
|
356
|
+
`tt ai image --wait '...'`,读取 `data.outputs[0].localPath`,不再读取旧 `imagePath`。
|
|
357
|
+
外部 `generate` 技能中依赖同步行为的命令也需在升级 tt 时加 `--wait`。
|
|
358
|
+
|
|
147
359
|
## AI 音频生成
|
|
148
360
|
|
|
149
|
-
`ai audio`
|
|
361
|
+
`ai audio` 接受一段自然语言提示词,完成远端提交后返回 `taskId`、
|
|
362
|
+
`state=submitted` 和 `nextCommand`。参考音频可选;提供时,
|
|
150
363
|
提示词需自行用 `@音频1` 引用它,命令不会改写提示词。
|
|
151
364
|
|
|
152
365
|
```bash
|
|
153
|
-
#
|
|
366
|
+
# 提交纯文本生成音频任务
|
|
154
367
|
tt ai audio '萝莉音说:“我今天不回来了”'
|
|
155
368
|
|
|
156
369
|
# 参考一段本地音频的音色
|
|
157
370
|
tt ai audio --audio ./reference.mp3 \
|
|
158
371
|
'@音频1 参考它的音色,说:“我生气了,哼”'
|
|
159
372
|
|
|
160
|
-
#
|
|
373
|
+
# 等待生成并下载到本地(--output 隐含 --wait)
|
|
161
374
|
tt ai audio --audio ./reference.mp3 --output ./result.mp3 \
|
|
162
375
|
'@音频1 参考它的音色,说:“我生气了,哼”'
|
|
163
376
|
```
|
|
164
377
|
|
|
165
|
-
|
|
166
|
-
|
|
378
|
+
继续查询或等待既有任务:
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
tt ai audio --wait '用温柔的声音说:“晚安”'
|
|
382
|
+
tt ai audio status --task-id 'xyq:<threadId>'
|
|
383
|
+
tt ai audio wait --task-id 'xyq:<threadId>' --timeout-seconds 600 --output ./result.mp3
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
命令固定使用 `seedaudio_1.0`、MP3 和 44.1 kHz;等待默认 60 秒,可通过
|
|
387
|
+
`--timeout-seconds` 设为 1–600 秒。参考音频直接上传,不进入持久素材缓存。
|
|
388
|
+
云端产物字段统一为 `outputs[].downloadUrl`,本地产物为 `outputs[].localPath`;
|
|
389
|
+
旧同步调用需增加 `--wait`,旧 `remoteUrl` 读取需迁移到 `downloadUrl`。
|
|
390
|
+
超时保留 `taskId` 与 `nextCommand`,下载异常的恢复命令还保留绝对输出路径。
|
|
391
|
+
音频和视频尚无本机任务列表,调用方须保存提交返回的 ID;查询仍需对应账号权限。
|
|
392
|
+
|
|
393
|
+
## AI 视频生成
|
|
394
|
+
|
|
395
|
+
`ai video` 同步完成会话初始化、素材上传与远端任务创建后返回 `taskId`,生成本身
|
|
396
|
+
异步进行(通常约 5 分钟)。提交期间 stderr 每 10 秒输出一次 `progress` 事件。
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
# 提交并立即返回 taskId 与 nextCommand
|
|
400
|
+
tt ai video --duration-seconds 8 --images ./ref.png '图1 中的角色转身微笑'
|
|
401
|
+
|
|
402
|
+
# 一次调用等到结束并下载最高画质产物(隐含 --wait)
|
|
403
|
+
tt ai video --duration-seconds 8 --output ./result.mp4 '海边日出,慢镜头'
|
|
404
|
+
|
|
405
|
+
# 用 taskId 查询/等待既有任务
|
|
406
|
+
tt ai video status --task-id xyq:<threadId>
|
|
407
|
+
tt ai video wait --task-id xyq:<threadId> --timeout-seconds 600 --output ./result.mp4
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
等待超时不是失败:`wait` 返回 `timedOut: true` 与当前 `state`,可用同一 `taskId`
|
|
411
|
+
继续等待;`ai video --wait` 和 `--output` 同样在 `data` 中返回当前状态与 `timedOut`。
|
|
412
|
+
查询或下载异常时,`error` 中保留 `taskId`、`recoverable` 和恢复命令。
|
|
413
|
+
|
|
414
|
+
## 联网搜索
|
|
415
|
+
|
|
416
|
+
`ai websearch` 通过火山方舟 Responses API 的 `web_search` 工具搜索,`data.text`
|
|
417
|
+
是回答正文,`data.content` 是含引用标注的原始数组。API Key 按
|
|
418
|
+
`ARK_API_KEY` 环境变量 → 跨应用设置 `ark.apiKey` → 内置回退值 的顺序解析。
|
|
167
419
|
|
|
168
420
|
## 如何扩展新命令
|
|
169
421
|
|
|
170
|
-
1. 在 `src/commands
|
|
422
|
+
1. 在 `src/commands/<group>/` 下新建 `xxx-command.ts`,继承 `BaseCommand<TOptions, TData>`:
|
|
171
423
|
|
|
172
424
|
```typescript
|
|
173
|
-
import type { Command } from
|
|
174
|
-
import {
|
|
175
|
-
import
|
|
176
|
-
import type { ICommandMeta } from
|
|
177
|
-
import
|
|
425
|
+
import type { Command } from "commander";
|
|
426
|
+
import { ok, type Result } from "rockbed/error";
|
|
427
|
+
import { BaseCommand, fail } from "../../core";
|
|
428
|
+
import type { ICommandActionContext, ICommandMeta } from "../../core";
|
|
429
|
+
import { doSomething } from "../../modules/my-domain";
|
|
178
430
|
|
|
179
431
|
interface IMyOptions {
|
|
180
|
-
verbose: boolean;
|
|
432
|
+
readonly verbose: boolean;
|
|
181
433
|
}
|
|
182
434
|
|
|
183
|
-
|
|
435
|
+
interface IMyOutput {
|
|
436
|
+
readonly target: string;
|
|
437
|
+
readonly result: string;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export class MyCommand extends BaseCommand<IMyOptions, IMyOutput> {
|
|
184
441
|
protected _meta(): ICommandMeta {
|
|
185
|
-
return {
|
|
186
|
-
|
|
187
|
-
description: '这是我的自定义命令',
|
|
188
|
-
aliases: ['mc'],
|
|
189
|
-
};
|
|
442
|
+
return { name: "my-cmd", description: "这是我的自定义命令", aliases: ["mc"] };
|
|
443
|
+
// hidden: true 隐藏;output: "passthrough" 透传子进程输出
|
|
190
444
|
}
|
|
191
445
|
|
|
192
446
|
protected _configureArguments(cmd: Command): void {
|
|
193
|
-
cmd.argument(
|
|
447
|
+
cmd.argument("<target>", "操作目标");
|
|
194
448
|
}
|
|
195
449
|
|
|
196
450
|
protected _configureOptions(cmd: Command): void {
|
|
197
|
-
cmd.option(
|
|
451
|
+
cmd.option("--verbose", "输出更多诊断到 stderr", false);
|
|
198
452
|
}
|
|
199
453
|
|
|
200
|
-
protected
|
|
201
|
-
|
|
454
|
+
protected _agentContract(): readonly string[] {
|
|
455
|
+
return ["data.result 为处理结果;target 不存在时 error.stage=input。"];
|
|
456
|
+
}
|
|
202
457
|
|
|
458
|
+
protected async _execute(
|
|
459
|
+
ctx: ICommandActionContext<IMyOptions>,
|
|
460
|
+
): Promise<Result<IMyOutput>> {
|
|
461
|
+
const target = String(ctx.args[0] ?? "").trim();
|
|
462
|
+
if (!target) {
|
|
463
|
+
return fail(1, "请指定 target", { stage: "input" });
|
|
464
|
+
}
|
|
203
465
|
if (ctx.options.verbose) {
|
|
204
|
-
|
|
466
|
+
this._debug(`target = ${target}`); // 写 stderr,不污染 stdout
|
|
205
467
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
468
|
+
const result = await doSomething(target); // Result<string>
|
|
469
|
+
if (!result.ok) {
|
|
470
|
+
return fail(result.code, result.msg, { stage: "process", target });
|
|
471
|
+
}
|
|
472
|
+
return ok({ target, result: result.value });
|
|
211
473
|
}
|
|
212
474
|
}
|
|
213
475
|
```
|
|
214
476
|
|
|
215
|
-
2.
|
|
477
|
+
2. 注册:顶层命令加入 `src/commands/index.ts` 的 `createRootCommands()`;
|
|
478
|
+
子命令在所属命令组的 `_registerSubcommands()` 中 `this._addSubcommand(new MyCommand())`。
|
|
479
|
+
3. 在同目录添加 `my-command.test.ts`:mock `process.stdout.write`,
|
|
480
|
+
调用 `new MyCommand().command.parseAsync([...], { from: "user" })`,断言 envelope 与 `process.exitCode`。
|
|
216
481
|
|
|
217
|
-
|
|
218
|
-
import { MyCommand } from './commands/my-command.js';
|
|
219
|
-
|
|
220
|
-
app.registerCommand(new MyCommand());
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
完成!新命令会自动出现在 `--help` 输出和 `help [command]` 中。
|
|
482
|
+
新命令会自动出现在 `--help`、`help [command]` 中,并自动获得“执行案例”和“Agent 调用契约”页脚。
|
|
224
483
|
|
|
225
484
|
## 架构说明
|
|
226
485
|
|
|
227
|
-
###
|
|
486
|
+
### 分层与依赖方向
|
|
487
|
+
|
|
488
|
+
`core`(框架,唯一依赖 commander)← `commands`(参数定义、把领域 `Result` 整形为 `data`)
|
|
489
|
+
→ `modules`(领域逻辑,返回 `Result<T>`,不写 stdout)→ `bedrock`(进程、下载、配置等基础设施)。
|
|
490
|
+
依赖只能向下,`modules`/`bedrock` 不得引用 `commands`/`core`。
|
|
228
491
|
|
|
229
|
-
|
|
492
|
+
### 命令基类
|
|
230
493
|
|
|
231
|
-
|
|
|
494
|
+
| 成员 | 说明 |
|
|
232
495
|
|---|---|
|
|
233
|
-
| `_meta()` |
|
|
234
|
-
| `_execute(ctx)` |
|
|
235
|
-
| `_configureOptions(cmd)`
|
|
236
|
-
| `
|
|
237
|
-
| `
|
|
496
|
+
| `_meta()` | 名称、描述、别名、`hidden`、`output` |
|
|
497
|
+
| `_execute(ctx)` | 核心逻辑,返回 `Result<TData>`;json 模式下由基类输出 envelope |
|
|
498
|
+
| `_configureOptions(cmd)` / `_configureArguments(cmd)` | 配置 commander 选项/参数 |
|
|
499
|
+
| `_agentContract()` | 该命令特有的契约行,追加到 `--help` |
|
|
500
|
+
| `_toSuccessEnvelope(data)` | 覆盖成功 envelope(如成功退出但业务码非 0) |
|
|
501
|
+
| `_debug(text)` | 写 stderr 诊断 |
|
|
502
|
+
| `_output(text)` | 仅 passthrough 模式:写 stdout |
|
|
503
|
+
| `BaseCommandGroup._registerSubcommands()` | 命令组挂接子命令 |
|
|
238
504
|
|
|
239
|
-
|
|
505
|
+
commander `Command` 在首次访问 `.command` 时才构建,因此上述钩子可以使用构造函数注入的服务。
|
|
506
|
+
|
|
507
|
+
### 错误处理
|
|
240
508
|
|
|
241
509
|
```typescript
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
});
|
|
510
|
+
import { err, ok } from "rockbed/error";
|
|
511
|
+
import { fail, failFrom } from "../../core";
|
|
512
|
+
|
|
513
|
+
return ok(data); // {"code":0,"msg":"success","data":...}
|
|
514
|
+
return fail(1001, "文件不存在", { stage: "read" }); // {"code":1001,"msg":"...","error":{"stage":"read"}}
|
|
515
|
+
return fail(1, "已发布但校验失败", { stage: "verify" }, { state: "published" }); // 附带部分结果 data
|
|
516
|
+
return failFrom(domainResult, { stage: "upload", taskId }); // 保留领域错误码/消息,补充 agent 细节
|
|
517
|
+
return err(1, "plain"); // {"code":1,"msg":"plain"}
|
|
248
518
|
```
|
|
249
519
|
|
|
250
|
-
|
|
520
|
+
长耗时命令使用 `createAgentProgressReporter({ enabled, command, phase, message })`
|
|
521
|
+
向 stderr 输出 `tt.agent.v1` 进度事件,`dispose()` 后停止。
|
|
251
522
|
|
|
252
|
-
|
|
523
|
+
### 生命周期事件
|
|
253
524
|
|
|
254
525
|
```typescript
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
// 也可定义可复用错误
|
|
262
|
-
import { defineError } from 'rockbed/error';
|
|
263
|
-
const missingNameError = defineError(1002, '参数 name 不能为空');
|
|
264
|
-
return missingNameError();
|
|
526
|
+
const cmd = new HelloGreetCommand();
|
|
527
|
+
cmd.onBeforeExecute((ctx) => console.error("即将执行", ctx.args));
|
|
528
|
+
cmd.onAfterExecute((result) => {
|
|
529
|
+
if (!result.ok) console.error(`执行失败: [${result.code}] ${result.msg}`);
|
|
530
|
+
});
|
|
265
531
|
```
|
|
266
532
|
|
|
267
533
|
## Scripts
|
|
@@ -270,7 +536,9 @@ return missingNameError();
|
|
|
270
536
|
|---|---|
|
|
271
537
|
| `pnpm dev` | 使用 tsx 直接运行(开发用) |
|
|
272
538
|
| `pnpm dev:debug` | 启动 Node inspect 调试 |
|
|
273
|
-
| `pnpm
|
|
539
|
+
| `pnpm test` | 运行全部 `*.test.ts`(node:test) |
|
|
540
|
+
| `pnpm build` | tsup 构建 `dist/main.js`(CLI)与 `dist/index.js`(库) |
|
|
541
|
+
| `pnpm test:dist` | 构建产物冒烟(`--help`) |
|
|
274
542
|
| `pnpm start` | 运行编译后产物 |
|
|
275
543
|
| `pnpm clean` | 清理 dist 目录 |
|
|
276
544
|
| `pnpm test:release` | 在临时 Git 仓库中验证发布脚本 |
|
|
@@ -282,5 +550,5 @@ return missingNameError();
|
|
|
282
550
|
|
|
283
551
|
- TypeScript 5.x (strict mode)
|
|
284
552
|
- commander 13.x
|
|
285
|
-
-
|
|
286
|
-
- tsx (开发热运行)
|
|
553
|
+
- rockbed (`Result` / `Disposable` / `Emitter`)
|
|
554
|
+
- tsx (开发热运行)、tsup (构建)、node:test (测试)
|