maoda-commander-tt 0.0.48 → 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 CHANGED
@@ -1,37 +1,71 @@
1
- # template-commander
1
+ # template-commander (`tt`)
2
2
 
3
- 一个基于 [commander](https://github.com/tj/commander.js) 的通用 CLI 模板项目,采用面向对象架构设计,支持快速扩展子命令。
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
- - **面向对象架构** — 所有命令继承 `BaseCommand<TOptions>`,统一生命周期管理
8
- - **Disposable 体系** — 继承自 rockbed `Disposable` 基类,自动管理资源释放
9
- - **事件系统** — 基于 rockbed `Emitter` 提供 `onBeforeExecute` / `onAfterExecute` 等生命周期钩子
10
- - **结构化错误处理** — 使用 `Result<T>` 代替 throw/reject,错误码可追溯
11
- - **TypeScript 严格模式** — 全量 strict,类型安全
12
- - **开发热调试** — 基于 tsx 直接运行 TS,支持 `--inspect` 调试
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
- │ │ ├── cli-app.ts # CliApp 主应用类
22
- │ │ ├── base-command.ts # BaseCommand 抽象基类
23
- │ │ └── command-types.ts # 命令元信息类型
24
- │ ├── commands/
25
- │ │ ├── hello/ # 示例命令组
26
- │ │ ├── pippit/ # pippit 命令组
27
- │ │ ├── git/ # git 命令组
28
- │ │ └── deploy/ # 静态文件部署与目录查询
29
- │ └── bedrock/ # 本地补充基础设施
30
- │ ├── config/ # 本地配置读写
31
- │ ├── pkg/ # package.json 查找
32
- │ └── uuid/ # UUID 工具
33
- ├── package.json
34
- └── tsconfig.json
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
35
69
  ```
36
70
 
37
71
  ## 快速开始
@@ -42,21 +76,20 @@ template-commander/
42
76
  # 安装依赖
43
77
  pnpm install
44
78
 
45
- # 开发运行
46
- pnpm dev -- --help
47
- pnpm dev -- hello Alice
48
- pnpm dev -- hello Alice -u
49
- pnpm dev -- git mm "update docs"
50
- pnpm dev -- git pp -n
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
51
85
 
52
86
  # 调试模式 (支持 Chrome DevTools / VS Code 断点)
53
- pnpm dev:debug -- hello Alice
54
-
55
- # 构建
56
- pnpm build
87
+ pnpm dev:debug hello greet Alice
57
88
 
58
- # 运行编译产物
59
- pnpm start -- --help
89
+ # 类型检查、测试、构建、产物冒烟
90
+ pnpm exec tsc --noEmit -p .
91
+ pnpm test
92
+ pnpm build && pnpm test:dist
60
93
  ```
61
94
 
62
95
  ## 发布到 npm
@@ -280,124 +313,221 @@ tt sc w2
280
313
 
281
314
  命令日志直接输出到当前终端,gateway 在前台运行。
282
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
+
283
359
  ## AI 音频生成
284
360
 
285
- `ai audio` 接受一段自然语言提示词并等待 XYQ 返回 MP3。参考音频可选;提供时,
361
+ `ai audio` 接受一段自然语言提示词,完成远端提交后返回 `taskId`、
362
+ `state=submitted` 和 `nextCommand`。参考音频可选;提供时,
286
363
  提示词需自行用 `@音频1` 引用它,命令不会改写提示词。
287
364
 
288
365
  ```bash
289
- # 纯文本生成音频
366
+ # 提交纯文本生成音频任务
290
367
  tt ai audio '萝莉音说:“我今天不回来了”'
291
368
 
292
369
  # 参考一段本地音频的音色
293
370
  tt ai audio --audio ./reference.mp3 \
294
371
  '@音频1 参考它的音色,说:“我生气了,哼”'
295
372
 
296
- # 等待生成并下载到本地;省略 --output 时返回远端地址
373
+ # 等待生成并下载到本地(--output 隐含 --wait)
297
374
  tt ai audio --audio ./reference.mp3 --output ./result.mp3 \
298
375
  '@音频1 参考它的音色,说:“我生气了,哼”'
299
376
  ```
300
377
 
301
- 命令固定使用 `seedaudio_1.0`、MP3 和 44.1 kHz,最多等待 60 秒。参考音频直接
302
- 上传,不进入持久素材缓存。
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` → 内置回退值 的顺序解析。
303
419
 
304
420
  ## 如何扩展新命令
305
421
 
306
- 1. 在 `src/commands/` 下新建文件,创建继承 `BaseCommand<TOptions>` 的类:
422
+ 1. 在 `src/commands/<group>/` 下新建 `xxx-command.ts`,继承 `BaseCommand<TOptions, TData>`:
307
423
 
308
424
  ```typescript
309
- import type { Command } from 'commander';
310
- import { BaseCommand } from '../core/base-command.js';
311
- import type { ICommandActionContext } from '../core/base-command.js';
312
- import type { ICommandMeta } from '../core/command-types.js';
313
- import type { Result } from 'rockbed/error';
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";
314
430
 
315
431
  interface IMyOptions {
316
- verbose: boolean;
432
+ readonly verbose: boolean;
433
+ }
434
+
435
+ interface IMyOutput {
436
+ readonly target: string;
437
+ readonly result: string;
317
438
  }
318
439
 
319
- export class MyCommand extends BaseCommand<IMyOptions> {
440
+ export class MyCommand extends BaseCommand<IMyOptions, IMyOutput> {
320
441
  protected _meta(): ICommandMeta {
321
- return {
322
- name: 'my-cmd',
323
- description: '这是我的自定义命令',
324
- aliases: ['mc'],
325
- };
442
+ return { name: "my-cmd", description: "这是我的自定义命令", aliases: ["mc"] };
443
+ // hidden: true 隐藏;output: "passthrough" 透传子进程输出
326
444
  }
327
445
 
328
446
  protected _configureArguments(cmd: Command): void {
329
- cmd.argument('<target>', '操作目标');
447
+ cmd.argument("<target>", "操作目标");
330
448
  }
331
449
 
332
450
  protected _configureOptions(cmd: Command): void {
333
- cmd.option('--verbose', '详细输出', false);
451
+ cmd.option("--verbose", "输出更多诊断到 stderr", false);
334
452
  }
335
453
 
336
- protected async _execute(ctx: ICommandActionContext<IMyOptions>): Promise<Result<void>> {
337
- const target = ctx.args[0] as string;
454
+ protected _agentContract(): readonly string[] {
455
+ return ["data.result 为处理结果;target 不存在时 error.stage=input。"];
456
+ }
338
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
+ }
339
465
  if (ctx.options.verbose) {
340
- console.log(`[verbose] target = ${target}`);
466
+ this._debug(`target = ${target}`); // 写 stderr,不污染 stdout
341
467
  }
342
-
343
- // 业务逻辑...
344
-
345
- return this._makeOk();
346
- // 或错误: return this._makeError(1001, '发生了某种错误');
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 });
347
473
  }
348
474
  }
349
475
  ```
350
476
 
351
- 2. 在 `src/main.ts` 中注册:
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`。
352
481
 
353
- ```typescript
354
- import { MyCommand } from './commands/my-command.js';
355
-
356
- app.registerCommand(new MyCommand());
357
- ```
358
-
359
- 完成!新命令会自动出现在 `--help` 输出和 `help [command]` 中。
482
+ 新命令会自动出现在 `--help`、`help [command]` 中,并自动获得“执行案例”和“Agent 调用契约”页脚。
360
483
 
361
484
  ## 架构说明
362
485
 
363
- ### BaseCommand
486
+ ### 分层与依赖方向
364
487
 
365
- 每个子命令需要实现:
488
+ `core`(框架,唯一依赖 commander)← `commands`(参数定义、把领域 `Result` 整形为 `data`)
489
+ → `modules`(领域逻辑,返回 `Result<T>`,不写 stdout)→ `bedrock`(进程、下载、配置等基础设施)。
490
+ 依赖只能向下,`modules`/`bedrock` 不得引用 `commands`/`core`。
366
491
 
367
- | 方法 | 说明 |
492
+ ### 命令基类
493
+
494
+ | 成员 | 说明 |
368
495
  |---|---|
369
- | `_meta()` | 返回命令名、描述、别名 |
370
- | `_execute(ctx)` | 命令核心逻辑,返回 `Result<void>` |
371
- | `_configureOptions(cmd)` | (可选) 配置 commander 选项 |
372
- | `_configureArguments(cmd)` | (可选) 配置 commander 参数 |
373
- | `getExample()` | 根据已注册的参数和选项自动生成命令执行示例 |
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()` | 命令组挂接子命令 |
374
504
 
375
- ### 生命周期事件
505
+ commander `Command` 在首次访问 `.command` 时才构建,因此上述钩子可以使用构造函数注入的服务。
506
+
507
+ ### 错误处理
376
508
 
377
509
  ```typescript
378
- const cmd = new HelloCommand();
379
- cmd.onBeforeExecute(() => console.log('即将执行'));
380
- cmd.onAfterExecute((result) => {
381
- if (result.ok) console.log('执行成功');
382
- else console.log(`执行失败: [${result.code}] ${result.msg}`);
383
- });
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"}
384
518
  ```
385
519
 
386
- ### 错误处理
520
+ 长耗时命令使用 `createAgentProgressReporter({ enabled, command, phase, message })`
521
+ 向 stderr 输出 `tt.agent.v1` 进度事件,`dispose()` 后停止。
387
522
 
388
- 使用 `Result<T>` 而非 throw:
523
+ ### 生命周期事件
389
524
 
390
525
  ```typescript
391
- // 成功
392
- return this._makeOk();
393
-
394
- // 失败 (错误码 + 消息)
395
- return this._makeError(1001, '文件不存在');
396
-
397
- // 也可定义可复用错误
398
- import { defineError } from 'rockbed/error';
399
- const missingNameError = defineError(1002, '参数 name 不能为空');
400
- 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
+ });
401
531
  ```
402
532
 
403
533
  ## Scripts
@@ -406,7 +536,9 @@ return missingNameError();
406
536
  |---|---|
407
537
  | `pnpm dev` | 使用 tsx 直接运行(开发用) |
408
538
  | `pnpm dev:debug` | 启动 Node inspect 调试 |
409
- | `pnpm build` | TypeScript 编译 |
539
+ | `pnpm test` | 运行全部 `*.test.ts`(node:test) |
540
+ | `pnpm build` | tsup 构建 `dist/main.js`(CLI)与 `dist/index.js`(库) |
541
+ | `pnpm test:dist` | 构建产物冒烟(`--help`) |
410
542
  | `pnpm start` | 运行编译后产物 |
411
543
  | `pnpm clean` | 清理 dist 目录 |
412
544
  | `pnpm test:release` | 在临时 Git 仓库中验证发布脚本 |
@@ -418,5 +550,5 @@ return missingNameError();
418
550
 
419
551
  - TypeScript 5.x (strict mode)
420
552
  - commander 13.x
421
- - chalk 5.x
422
- - tsx (开发热运行)
553
+ - rockbed (`Result` / `Disposable` / `Emitter`)
554
+ - tsx (开发热运行)、tsup (构建)、node:test (测试)