tirtc-device-builder 0.8.0 → 0.9.0

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tirtc-device-builder",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Codex workflows for building and validating TiRTC device firmware across supported chip platforms.",
5
5
  "author": {
6
6
  "name": "TangeAI",
package/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  This project follows Semantic Versioning.
4
4
 
5
+ ## 0.9.0
6
+
7
+ - Add native Skill-directory installation for Codex, Claude Code, OpenCode,
8
+ Gemini CLI, GitHub Copilot, Qwen Code, Windsurf Cascade, Cline, and Kiro.
9
+ - Add `--client` support to both `install` and the resumable `setup` workflow,
10
+ plus a `clients` command that reports resolved user directories.
11
+ - Keep one portable `SKILL.md` and supporting resource tree across clients,
12
+ while preserving explicit replacement and hardware-operation approvals.
13
+
14
+ ## 0.8.1
15
+
16
+ - Separate rapid `idf.py flash` iteration from portable evidence-bundle export,
17
+ so active development keeps the normal `build/` tree and does not require a
18
+ new multi-BIN archive for every device test.
19
+ - Add deterministic ESP-IDF application-descriptor inspection for explicit
20
+ firmware versions, application-BIN SHA-256, and full ELF SHA-256 matching.
21
+ - Add product-control rules that distinguish MCU buttons, boot straps, reset
22
+ lines and PMIC/power-latch keys across baseboard and battery-carrier variants,
23
+ and route button gestures through the runtime's intent queue.
24
+ - Add trace-based diagnosis for repeated AI/H5 downlink audio across SDK
25
+ callbacks, bounded queues, playback, resampling, and codec/I2S writes.
26
+
5
27
  ## 0.8.0
6
28
 
7
29
  - Add an evidence-backed board identity and curated registry workflow with exact,
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # TiRTC Device Builder
2
2
 
3
- TiRTC Device Builder 用于把 ESP32-S3/ESP32-P4 开发板接入 TiRTC。输入可以只有开发板型号,也可以包含原理图、BSP、引脚表和外设示例。安装后的 Codex Skill 会先检查环境、整理有依据的硬件事实,再生成或移植独立的 ESP-IDF 工程并完成板级适配和编译。烧录和实机验证只有在开发者明确给出目标串口并授权后才会执行。
3
+ TiRTC Device Builder 用于把 ESP32-S3/ESP32-P4 开发板接入 TiRTC。输入可以只有开发板型号,也可以包含原理图、BSP、引脚表和外设示例。安装后的 Agent Skill 会先检查环境、整理有依据的硬件事实,再生成或移植独立的 ESP-IDF 工程并完成板级适配和编译。烧录和实机验证只有在开发者明确给出目标串口并授权后才会执行。
4
4
 
5
5
  当前仓库提供一个 Skill:
6
6
 
@@ -43,9 +43,15 @@ npm --version
43
43
  npx --yes tirtc-device-builder@latest setup esp32 --install
44
44
  ```
45
45
 
46
+ 默认安装到 Codex。使用其他客户端时增加 `--client`,例如:
47
+
48
+ ```bash
49
+ npx --yes tirtc-device-builder@latest setup esp32 --install --client qwen-code
50
+ ```
51
+
46
52
  这条命令会:
47
53
 
48
- - 把 `tirtc-esp32-builder` 安装到 Codex Skill 目录;
54
+ - 把 `tirtc-esp32-builder` 安装到所选 Agent 客户端的 Skill 目录;
49
55
  - 下载 ESP32 Device Kit 并校验 SHA-256;
50
56
  - 复用当前可用的 ESP-IDF 5.5.x,找不到时安装 ESP-IDF 5.5.4;
51
57
  - 安装 Espressif 管理的 ESP32-S3 工具链;
@@ -59,21 +65,21 @@ npx --yes tirtc-device-builder@latest setup esp32 --install
59
65
  ```text
60
66
  OVERALL: PASS
61
67
  SETUP: READY
62
- Start a new Codex session and invoke $tirtc-esp32-builder.
68
+ Start a new Codex session, then ask it to use tirtc-esp32-builder.
63
69
  ```
64
70
 
65
71
  如果看到 `OVERALL: NEEDS_SETUP`、`MISS` 或 `FAIL`,先看[常见问题](#常见问题)。
66
72
 
67
- ### 3. 重新打开 Codex
73
+ ### 3. 重新打开 Agent 客户端
68
74
 
69
- Skill Codex 会话启动时被发现。安装完成后,关闭当前 Codex 会话,再打开一个新会话。
75
+ Skill 通常在 Agent 会话启动时被发现。安装完成后,关闭当前会话,再打开一个新会话。
70
76
 
71
- ### 4. 把板卡和目标告诉 Codex
77
+ ### 4. 把板卡和目标告诉 Agent
72
78
 
73
79
  把你已经掌握的信息填进下面的提示词即可,不用先查齐所有硬件参数。先指定工作区根目录,本地路径尽量相对工作区填写;不确定的内容写“未知”。可直接复制的版本见[开发板接入提示词](skills/tirtc-esp32-builder/assets/developer-intake-prompt.md)。
74
80
 
75
81
  ```text
76
- 请使用 $tirtc-esp32-builder 完成这块开发板的 TiRTC 移植。
82
+ 请使用 tirtc-esp32-builder Skill 完成这块开发板的 TiRTC 移植。
77
83
 
78
84
  开发板:
79
85
  - 厂商、完整型号、PCB/硬件版本:<填写>
@@ -339,25 +345,52 @@ tirtc-device-builder setup esp32 --install
339
345
 
340
346
  安装命令是 `npm install`,不要写成 `npm --install`。
341
347
 
342
- 只执行 `npm install --global tirtc-device-builder` 会安装 CLI,不会准备完整开发环境。随后还要运行 `setup esp32 --install`,由它安装 Codex Skill、下载 Device Kit,并在需要时安装 ESP-IDF。npm 包没有 `preinstall`、`install` 或 `postinstall` 生命周期脚本,因此不会在安装 CLI 时改动这些目录。
348
+ 只执行 `npm install --global tirtc-device-builder` 会安装 CLI,不会准备完整开发环境。随后还要运行 `setup esp32 --install`,由它安装 Agent Skill、下载 Device Kit,并在需要时安装 ESP-IDF。npm 包没有 `preinstall`、`install` 或 `postinstall` 生命周期脚本,因此不会在安装 CLI 时改动这些目录。
343
349
 
344
350
  安装公开包不需要执行 `npm login`;这个命令只和维护者发布新版本有关。
345
351
 
346
352
  ### 只安装 Skill
347
353
 
348
- 如果 ESP-IDF 和 Device Kit 已经由团队统一准备,可以只复制 Codex Skill:
354
+ 如果 ESP-IDF 和 Device Kit 已经由团队统一准备,可以只复制 Agent Skill:
349
355
 
350
356
  ```bash
351
357
  npx --yes tirtc-device-builder@latest install esp32
358
+ npx --yes tirtc-device-builder@latest install esp32 --client gemini
352
359
  ```
353
360
 
354
361
  这条命令不安装 ESP-IDF,也不下载 Device Kit。完整的新用户环境仍建议使用 `setup esp32 --install`。
355
362
 
363
+ ### 支持的 Agent 客户端
364
+
365
+ 同一个 `tirtc-esp32-builder` Skill 会完整复制到所选客户端的原生目录,不维护客户端专用的内容副本。先用下面的命令查看当前机器解析出的目录:
366
+
367
+ ```bash
368
+ npx --yes tirtc-device-builder@latest clients
369
+ ```
370
+
371
+ | `--client` | 客户端 | 默认 Skill 根目录 |
372
+ |---|---|---|
373
+ | `codex` | Codex(默认) | `${CODEX_HOME:-~/.codex}/skills` |
374
+ | `claude-code` | Claude Code | `~/.claude/skills` |
375
+ | `opencode` | OpenCode | `${XDG_CONFIG_HOME:-~/.config}/opencode/skills` |
376
+ | `gemini` | Gemini CLI | `~/.gemini/skills` |
377
+ | `copilot` | GitHub Copilot | `~/.copilot/skills` |
378
+ | `qwen-code` | Qwen Code | `~/.qwen/skills` |
379
+ | `windsurf` | Windsurf Cascade | `~/.codeium/windsurf/skills` |
380
+ | `cline` | Cline | `~/.cline/skills` |
381
+ | `kiro` | Kiro | `~/.kiro/skills` |
382
+
383
+ 也可以使用 `claude`、`gemini-cli`、`github-copilot`、`qwen`、`cascade`、`kiro-cli` 等别名。`--skills-dir` 的优先级高于默认目录,适合项目级安装或客户端使用非默认配置目录的情况。
384
+
385
+ `--client` 只选择 Skill 的安装目录和提示文案,不绑定模型服务。DeepSeek、Qwen、GLM、Kimi、OpenAI 或 Claude 等模型仍在对应客户端中配置;只要客户端能发现 Skill 并允许文件、终端等所需工具,就继续使用同一份工作流。
386
+
387
+ Cline 当前还需要在 `Settings → Features → Enable Skills` 中启用实验性 Skills 功能;安装器不会修改客户端自身的权限或功能开关。
388
+
356
389
  ### 默认目录
357
390
 
358
391
  | 内容 | 默认位置 |
359
392
  |---|---|
360
- | Codex Skill | `${CODEX_HOME:-~/.codex}/skills/tirtc-esp32-builder` |
393
+ | Agent Skill | 上表中所选目录下的 `tirtc-esp32-builder` |
361
394
  | 托管根目录 | `~/.tirtc-device-builder` |
362
395
  | Device Kit | `~/.tirtc-device-builder/kits/esp32s3/1.1.1` |
363
396
  | ESP-IDF | `~/.tirtc-device-builder/esp-idf-v5.5.4` |
@@ -436,6 +469,7 @@ npx --yes tirtc-device-builder@latest setup esp32
436
469
  ```bash
437
470
  npx --yes tirtc-device-builder@latest --help
438
471
  npx --yes tirtc-device-builder@latest list
472
+ npx --yes tirtc-device-builder@latest clients
439
473
  npx --yes tirtc-device-builder@latest setup esp32 --help
440
474
  ```
441
475
 
@@ -549,7 +583,19 @@ python3 ~/.codex/skills/tirtc-esp32-builder/scripts/install_video_gate.py .
549
583
  idf.py build
550
584
  ```
551
585
 
552
- 只安装实际请求能力的门禁。编译后把 BIN/ELF 的路径、大小和 SHA-256 写入 Hardware IR 的 `build_evidence.artifacts[]`,再运行 build 阶段评估。编译成功但语义门禁缺失或失败时只能记录 `COMPILE_PASS / CAPABILITY_BLOCKED`。
586
+ 只安装实际请求能力的门禁。编译后先核对固件内嵌版本、应用 BIN
587
+ SHA-256 和完整 ELF SHA-256:
588
+
589
+ ```bash
590
+ python3 ~/.codex/skills/tirtc-esp32-builder/scripts/firmware_identity.py \
591
+ build/<app>.bin --elf build/<app>.elf --expect-version <expected-version>
592
+ ```
593
+
594
+ 随后按
595
+ [`firmware-delivery.md`](skills/tirtc-esp32-builder/references/firmware-delivery.md)
596
+ 选择快速真机迭代或可移植证据包;该文档也规定从 `build/` 记录迁移到
597
+ `artifacts/` 时如何保持 Hardware IR 和报告一致。编译成功但语义门禁缺失
598
+ 或失败时只能记录 `COMPILE_PASS / CAPABILITY_BLOCKED`。
553
599
 
554
600
  生成器会把 TiRTC SDK 复制到工程的 `third_party/tirtc/`。此后工程不再依赖 `tirtc-server-example`,但换机编译仍需准备兼容的 ESP-IDF 5.5.x 工具链。
555
601
 
@@ -599,7 +645,10 @@ cd /absolute/path/my-esp32-device
599
645
  idf.py -p /dev/ttyACM0 flash monitor
600
646
  ```
601
647
 
602
- 使用 `Ctrl+]` 退出 ESP-IDF Monitor。多个串口同时存在时,需要通过 USB 拔插、设备标识或芯片探测确认目标,不能默认烧录第一个端口。
648
+ 普通开发烧录优先使用这一条命令,让 ESP-IDF 从当前构建目录解析
649
+ bootloader、分区表和应用镜像;无需手工维护多条 `esptool.py` 地址参数。
650
+ 使用 `Ctrl+]` 退出 ESP-IDF Monitor。多个串口同时存在时,需要通过 USB
651
+ 拔插、设备标识或芯片探测确认目标,不能默认烧录第一个端口。
603
652
 
604
653
  ### Wi-Fi 凭证和设备绑定
605
654
 
@@ -744,15 +793,16 @@ npx --yes tirtc-device-builder@latest setup esp32 --install \
744
793
 
745
794
  如果指定目录已存在但内容不完整或版本错误,安装器会拒绝覆盖。换一个空的新路径,或先人工备份原目录。
746
795
 
747
- ### 安装完成后 Codex 找不到 Skill
796
+ ### 安装完成后 Agent 客户端找不到 Skill
748
797
 
749
- 关闭当前 Codex 会话并重新打开。再检查默认文件是否存在:
798
+ 关闭当前 Agent 会话并重新打开。先查看客户端目录,再检查对应文件是否存在:
750
799
 
751
800
  ```bash
752
- ls -l ~/.codex/skills/tirtc-esp32-builder/SKILL.md
801
+ npx --yes tirtc-device-builder@latest clients
802
+ ls -l <skills-dir>/tirtc-esp32-builder/SKILL.md
753
803
  ```
754
804
 
755
- 设置过 `CODEX_HOME` 或 `--skills-dir` 时,要检查对应目录。不要同时把 Skill 安装到多个位置。
805
+ 确认安装和启动的是同一个客户端,例如 Qwen Code 要使用 `--client qwen-code`。设置过 `CODEX_HOME`、`XDG_CONFIG_HOME` 或 `--skills-dir` 时,要检查对应目录。项目级目录由客户端自身规则决定,可以用 `--skills-dir` 显式指定。
756
806
 
757
807
  ### 已有 Skill,安装器拒绝覆盖
758
808
 
@@ -911,8 +961,8 @@ metadata 中的版本、标签、上游 commit 和期望 SHA-256 必须与本地
911
961
 
912
962
  ```bash
913
963
  npm test
914
- git tag -a v0.8.0 -m "v0.8.0"
915
- git push origin v0.8.0
964
+ git tag -a v0.9.0 -m "v0.9.0"
965
+ git push origin v0.9.0
916
966
  ```
917
967
 
918
968
  不要重复发布已经存在的 npm 版本。版本变化同步更新 `package.json`、`.codex-plugin/plugin.json` 和发布说明。
@@ -0,0 +1,141 @@
1
+ import { homedir } from "node:os";
2
+ import { join, resolve } from "node:path";
3
+
4
+ export const DEFAULT_AGENT_CLIENT = "codex";
5
+
6
+ const CLIENTS = [
7
+ {
8
+ id: "codex",
9
+ displayName: "Codex",
10
+ aliases: ["codex"],
11
+ skillsDir(environment, home) {
12
+ const codexHome = environment.CODEX_HOME
13
+ ? resolve(environment.CODEX_HOME)
14
+ : join(home, ".codex");
15
+ return join(codexHome, "skills");
16
+ },
17
+ },
18
+ {
19
+ id: "claude-code",
20
+ displayName: "Claude Code",
21
+ aliases: ["claude-code", "claude"],
22
+ skillsDir(_environment, home) {
23
+ return join(home, ".claude", "skills");
24
+ },
25
+ },
26
+ {
27
+ id: "opencode",
28
+ displayName: "OpenCode",
29
+ aliases: ["opencode", "open-code"],
30
+ skillsDir(environment, home) {
31
+ const configHome = environment.XDG_CONFIG_HOME
32
+ ? resolve(environment.XDG_CONFIG_HOME)
33
+ : join(home, ".config");
34
+ return join(configHome, "opencode", "skills");
35
+ },
36
+ },
37
+ {
38
+ id: "gemini",
39
+ displayName: "Gemini CLI",
40
+ aliases: ["gemini", "gemini-cli"],
41
+ skillsDir(_environment, home) {
42
+ return join(home, ".gemini", "skills");
43
+ },
44
+ },
45
+ {
46
+ id: "copilot",
47
+ displayName: "GitHub Copilot",
48
+ aliases: ["copilot", "github-copilot"],
49
+ skillsDir(_environment, home) {
50
+ return join(home, ".copilot", "skills");
51
+ },
52
+ },
53
+ {
54
+ id: "qwen-code",
55
+ displayName: "Qwen Code",
56
+ aliases: ["qwen-code", "qwen"],
57
+ skillsDir(_environment, home) {
58
+ return join(home, ".qwen", "skills");
59
+ },
60
+ },
61
+ {
62
+ id: "windsurf",
63
+ displayName: "Windsurf Cascade",
64
+ aliases: ["windsurf", "cascade"],
65
+ skillsDir(_environment, home) {
66
+ return join(home, ".codeium", "windsurf", "skills");
67
+ },
68
+ },
69
+ {
70
+ id: "cline",
71
+ displayName: "Cline",
72
+ aliases: ["cline"],
73
+ activationNote:
74
+ "Cline may require Settings > Features > Enable Skills before discovery.",
75
+ skillsDir(_environment, home) {
76
+ return join(home, ".cline", "skills");
77
+ },
78
+ },
79
+ {
80
+ id: "kiro",
81
+ displayName: "Kiro",
82
+ aliases: ["kiro", "kiro-cli"],
83
+ skillsDir(_environment, home) {
84
+ return join(home, ".kiro", "skills");
85
+ },
86
+ },
87
+ ];
88
+
89
+ const CLIENT_BY_ALIAS = new Map(
90
+ CLIENTS.flatMap((client) =>
91
+ client.aliases.map((alias) => [alias, client]),
92
+ ),
93
+ );
94
+
95
+ function userHome(environment) {
96
+ if (process.platform === "win32") {
97
+ if (environment.USERPROFILE) {
98
+ return resolve(environment.USERPROFILE);
99
+ }
100
+ if (environment.HOMEDRIVE && environment.HOMEPATH) {
101
+ return resolve(`${environment.HOMEDRIVE}${environment.HOMEPATH}`);
102
+ }
103
+ } else if (environment.HOME) {
104
+ return resolve(environment.HOME);
105
+ }
106
+ return homedir();
107
+ }
108
+
109
+ export function listAgentClients() {
110
+ return CLIENTS;
111
+ }
112
+
113
+ export function resolveAgentClient(identifier) {
114
+ return CLIENT_BY_ALIAS.get(String(identifier).trim().toLowerCase()) ?? null;
115
+ }
116
+
117
+ export function requireAgentClient(identifier) {
118
+ const client = resolveAgentClient(identifier);
119
+ if (!client) {
120
+ throw new Error(
121
+ `unsupported client: ${identifier}; supported clients: ${CLIENTS.map((item) => item.id).join(", ")}`,
122
+ );
123
+ }
124
+ return client;
125
+ }
126
+
127
+ export function defaultSkillsDir(client, environment = process.env) {
128
+ const resolvedClient =
129
+ typeof client === "string" ? requireAgentClient(client) : client;
130
+ return resolvedClient.skillsDir(environment, userHome(environment));
131
+ }
132
+
133
+ export function clientSessionHint(client, skillName) {
134
+ const lines = [
135
+ `Start a new ${client.displayName} session, then ask it to use ${skillName}.`,
136
+ ];
137
+ if (client.activationNote) {
138
+ lines.push(client.activationNote);
139
+ }
140
+ return lines.join("\n");
141
+ }
@@ -10,6 +10,13 @@ import {
10
10
  } from "node:fs";
11
11
  import { homedir } from "node:os";
12
12
  import { delimiter, dirname, join, resolve } from "node:path";
13
+ import {
14
+ clientSessionHint,
15
+ DEFAULT_AGENT_CLIENT,
16
+ defaultSkillsDir,
17
+ listAgentClients,
18
+ requireAgentClient,
19
+ } from "./agent-clients.js";
13
20
  import { ESP32_KIT } from "./esp32-kit-metadata.js";
14
21
 
15
22
  const EXPECTED_IDF_LINE = "5.5";
@@ -43,10 +50,10 @@ function setupRootFrom(environment) {
43
50
  );
44
51
  }
45
52
 
46
- function takeValue(args, index, option) {
53
+ function takeValue(args, index, option, kind = "a path") {
47
54
  const value = args[index + 1];
48
55
  if (!value || value.startsWith("--")) {
49
- throw new Error(`${option} requires a path`);
56
+ throw new Error(`${option} requires ${kind}`);
50
57
  }
51
58
  return value;
52
59
  }
@@ -54,13 +61,15 @@ function takeValue(args, index, option) {
54
61
  export function parseSetupOptions(args, defaults = {}) {
55
62
  const environment = defaults.environment ?? process.env;
56
63
  const options = {
64
+ client:
65
+ defaults.client ?? requireAgentClient(DEFAULT_AGENT_CLIENT),
57
66
  forceSkill: false,
58
67
  help: false,
59
68
  idfDir: null,
60
69
  install: false,
61
70
  kitArchive: null,
62
71
  rootDir: setupRootFrom(environment),
63
- skillsDir: defaults.skillsDir,
72
+ skillsDir: null,
64
73
  thingConnectRoot: null,
65
74
  };
66
75
 
@@ -78,6 +87,13 @@ export function parseSetupOptions(args, defaults = {}) {
78
87
  options.forceSkill = true;
79
88
  continue;
80
89
  }
90
+ if (argument === "--client") {
91
+ options.client = requireAgentClient(
92
+ takeValue(args, index, argument, "a name"),
93
+ );
94
+ index += 1;
95
+ continue;
96
+ }
81
97
  if (
82
98
  argument === "--root" ||
83
99
  argument === "--skills-dir" ||
@@ -103,9 +119,7 @@ export function parseSetupOptions(args, defaults = {}) {
103
119
  throw new Error(`unknown setup option: ${argument}`);
104
120
  }
105
121
 
106
- if (!options.skillsDir) {
107
- throw new Error("setup requires a default skills directory");
108
- }
122
+ options.skillsDir ??= defaultSkillsDir(options.client, environment);
109
123
  if (options.forceSkill && !options.install) {
110
124
  throw new Error("--force-skill requires --install");
111
125
  }
@@ -123,8 +137,9 @@ Usage:
123
137
 
124
138
  Options:
125
139
  --install Install missing user-space components
140
+ --client <name> Agent client (default: codex)
126
141
  --root <path> Managed files (default: ~/.tirtc-device-builder)
127
- --skills-dir <path> Codex skills directory
142
+ --skills-dir <path> Override the selected client's Skill directory
128
143
  --thing-connect-root <path> Reuse an existing Device Kit or legacy workspace
129
144
  --kit-archive <path> Install from a local verified Kit archive
130
145
  --idf-dir <path> Reuse or install ESP-IDF at this path
@@ -133,12 +148,13 @@ Options:
133
148
 
134
149
  Examples:
135
150
  npx tirtc-device-builder setup esp32
136
- npx tirtc-device-builder setup esp32 --install
151
+ npx tirtc-device-builder setup esp32 --install --client gemini
137
152
  npx tirtc-device-builder setup esp32 --install --root /opt/tirtc-dev
138
153
 
139
154
  Check mode is read-only. --install downloads the pinned ESP32 Device Kit,
140
155
  may clone ESP-IDF v5.5.4, runs Espressif's user-space tool installer, and
141
- installs the Codex Skill. It does not run sudo or edit shell profiles.`);
156
+ installs the Agent Skill. It does not run sudo or edit shell profiles.
157
+ Supported clients: ${listAgentClients().map((client) => client.id).join(", ")}.`);
142
158
  }
143
159
 
144
160
  function printCheck(status, name, detail) {
@@ -420,6 +436,7 @@ function writeManagedEnvironment(context, packageVersion) {
420
436
  device_kit_version: context.deviceKit.version,
421
437
  device_kit_root: context.thingConnectRoot,
422
438
  thing_connect_root: context.thingConnectRoot,
439
+ client: context.client.id,
423
440
  skills_dir: context.skillsDir,
424
441
  };
425
442
  writeAtomic(
@@ -542,6 +559,7 @@ function setupContext(options, runtime) {
542
559
  const skillVersion = readSkillVersion(skillTarget);
543
560
  return {
544
561
  activeIdf: active,
562
+ client: options.client,
545
563
  configPath,
546
564
  idf,
547
565
  idfDir,
@@ -569,8 +587,8 @@ function printState(context, runtime) {
569
587
  printCheck("INFO", "setup root", context.rootDir);
570
588
  printCheck(
571
589
  context.skillReady ? "PASS" : "MISS",
572
- "Codex Skill",
573
- `${context.skillTarget} (version ${
590
+ "Agent Skill",
591
+ `${context.client.displayName}: ${context.skillTarget} (version ${
574
592
  context.skillVersion || "missing"
575
593
  }; expected ${runtime.packageVersion})`,
576
594
  );
@@ -639,13 +657,13 @@ function installSkill(options, context, runtime) {
639
657
  const present = existsSync(join(context.skillTarget, "SKILL.md"));
640
658
  if (present && context.skillReady && !options.forceSkill) {
641
659
  console.log(
642
- `SKIP Codex Skill ${runtime.packageVersion} already exists: ${context.skillTarget}`,
660
+ `SKIP ${context.client.displayName} Skill ${runtime.packageVersion} already exists: ${context.skillTarget}`,
643
661
  );
644
662
  return;
645
663
  }
646
664
  if (present && !options.forceSkill) {
647
665
  throw new Error(
648
- `Codex Skill at ${context.skillTarget} has version ${
666
+ `${context.client.displayName} Skill at ${context.skillTarget} has version ${
649
667
  context.skillVersion || "missing"
650
668
  }, expected ${runtime.packageVersion}; rerun with --force-skill to replace it`,
651
669
  );
@@ -654,6 +672,8 @@ function installSkill(options, context, runtime) {
654
672
  runtime.cliPath,
655
673
  "install",
656
674
  runtime.platform.name,
675
+ "--client",
676
+ context.client.id,
657
677
  "--skills-dir",
658
678
  options.skillsDir,
659
679
  ];
@@ -830,8 +850,8 @@ export function runEsp32Setup(args, input) {
830
850
  let options;
831
851
  try {
832
852
  options = parseSetupOptions(args, {
853
+ client: input.defaultClient,
833
854
  environment: runtime.environment,
834
- skillsDir: input.defaultSkillsDir,
835
855
  });
836
856
  } catch (error) {
837
857
  console.error(
@@ -849,8 +869,12 @@ export function runEsp32Setup(args, input) {
849
869
  if (!options.install) {
850
870
  if (!isReady(context)) {
851
871
  console.log("OVERALL: NEEDS_SETUP");
872
+ const clientOption =
873
+ context.client.id === "codex"
874
+ ? ""
875
+ : ` --client ${context.client.id}`;
852
876
  console.log(
853
- `NEXT: npx tirtc-device-builder@${runtime.packageVersion} setup esp32 --install`,
877
+ `NEXT: npx tirtc-device-builder@${runtime.packageVersion} setup esp32 --install${clientOption}`,
854
878
  );
855
879
  return 1;
856
880
  }
@@ -867,7 +891,9 @@ export function runEsp32Setup(args, input) {
867
891
  return 1;
868
892
  }
869
893
  console.log("INSTALL PLAN:");
870
- console.log(` Skill: ${context.skillTarget}`);
894
+ console.log(
895
+ ` Skill: ${context.skillTarget} (${context.client.displayName})`,
896
+ );
871
897
  console.log(` Device Kit: ${context.thingConnectRoot} (${ESP32_KIT.version})`);
872
898
  console.log(` ESP-IDF: ${context.idfDir} (${PINNED_IDF_VERSION})`);
873
899
  console.log(` IDF tools: ${context.idfToolsPath || "existing default"}`);
@@ -917,6 +943,6 @@ export function runEsp32Setup(args, input) {
917
943
  }
918
944
  console.log("SETUP: READY");
919
945
  console.log(`Environment helper: ${join(context.rootDir, "env.sh")}`);
920
- console.log("Start a new Codex session and invoke $tirtc-esp32-builder.");
946
+ console.log(clientSessionHint(context.client, runtime.platform.skill));
921
947
  return 0;
922
948
  }
@@ -9,9 +9,15 @@ import {
9
9
  renameSync,
10
10
  rmSync,
11
11
  } from "node:fs";
12
- import { homedir } from "node:os";
13
12
  import { dirname, join, resolve } from "node:path";
14
13
  import { fileURLToPath } from "node:url";
14
+ import {
15
+ clientSessionHint,
16
+ DEFAULT_AGENT_CLIENT,
17
+ defaultSkillsDir,
18
+ listAgentClients,
19
+ requireAgentClient,
20
+ } from "./agent-clients.js";
15
21
  import { runEsp32Setup } from "./setup-esp32.js";
16
22
 
17
23
  const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -35,7 +41,8 @@ function printHelp() {
35
41
 
36
42
  Usage:
37
43
  tirtc-device-builder list
38
- tirtc-device-builder install <platform> [--skills-dir <path>] [--force]
44
+ tirtc-device-builder clients
45
+ tirtc-device-builder install <platform> [--client <name>] [--skills-dir <path>] [--force]
39
46
  tirtc-device-builder setup <platform> [setup options]
40
47
  tirtc-device-builder doctor <platform> [doctor options]
41
48
  tirtc-device-builder boards <platform> <list|validate|match|init-identity|candidate> [options]
@@ -46,15 +53,18 @@ Platforms:
46
53
 
47
54
  Examples:
48
55
  npx tirtc-device-builder install esp32
56
+ npx tirtc-device-builder install esp32 --client qwen-code
49
57
  npx tirtc-device-builder setup esp32
50
- npx tirtc-device-builder setup esp32 --install
58
+ npx tirtc-device-builder setup esp32 --install --client gemini
51
59
  npx tirtc-device-builder install esp32 --skills-dir /absolute/path/skills
52
60
  npx tirtc-device-builder doctor esp32 --project /absolute/path/project
53
61
  npx tirtc-device-builder boards esp32 match --identity ./board-identity.json
54
62
 
55
- Install defaults to ${"$"}{CODEX_HOME:-~/.codex}/skills. Existing skills are
56
- preserved unless --force is explicitly supplied. Setup checks are read-only;
57
- setup --install installs missing user-space components without running sudo.`);
63
+ Install defaults to the selected client's user Skill directory; Codex is the
64
+ default client. Run "clients" to list supported names and resolved directories.
65
+ Existing skills are preserved unless --force is explicitly supplied. Setup
66
+ checks are read-only; setup --install installs missing user-space components
67
+ without running sudo.`);
58
68
  }
59
69
 
60
70
  function fail(message) {
@@ -71,17 +81,11 @@ function resolvePlatform(identifier) {
71
81
  return null;
72
82
  }
73
83
 
74
- function defaultSkillsDir() {
75
- const codexHome = process.env.CODEX_HOME
76
- ? resolve(process.env.CODEX_HOME)
77
- : join(homedir(), ".codex");
78
- return join(codexHome, "skills");
79
- }
80
-
81
84
  function parseInstallOptions(args) {
82
85
  const options = {
86
+ client: requireAgentClient(DEFAULT_AGENT_CLIENT),
83
87
  force: false,
84
- skillsDir: defaultSkillsDir(),
88
+ skillsDir: null,
85
89
  };
86
90
 
87
91
  for (let index = 0; index < args.length; index += 1) {
@@ -90,6 +94,15 @@ function parseInstallOptions(args) {
90
94
  options.force = true;
91
95
  continue;
92
96
  }
97
+ if (argument === "--client") {
98
+ const value = args[index + 1];
99
+ if (!value || value.startsWith("--")) {
100
+ throw new Error("--client requires a name");
101
+ }
102
+ options.client = requireAgentClient(value);
103
+ index += 1;
104
+ continue;
105
+ }
93
106
  if (argument === "--skills-dir") {
94
107
  const value = args[index + 1];
95
108
  if (!value || value.startsWith("--")) {
@@ -102,6 +115,7 @@ function parseInstallOptions(args) {
102
115
  throw new Error(`unknown install option: ${argument}`);
103
116
  }
104
117
 
118
+ options.skillsDir ??= defaultSkillsDir(options.client);
105
119
  return options;
106
120
  }
107
121
 
@@ -151,8 +165,10 @@ function installSkill(platform, options) {
151
165
  if (movedExisting && existsSync(backup)) {
152
166
  rmSync(backup, { force: true, recursive: true });
153
167
  }
154
- console.log(`Installed ${platform.skill} ${PACKAGE.version} to ${target}`);
155
- console.log(`Start a new Codex session, then invoke $${platform.skill}.`);
168
+ console.log(
169
+ `Installed ${platform.skill} ${PACKAGE.version} for ${options.client.displayName} to ${target}`,
170
+ );
171
+ console.log(clientSessionHint(options.client, platform.skill));
156
172
  }
157
173
 
158
174
  function runDoctor(platform, args) {
@@ -214,6 +230,15 @@ function main(args) {
214
230
  }
215
231
  return 0;
216
232
  }
233
+ if (args[0] === "clients") {
234
+ for (const client of listAgentClients()) {
235
+ const marker = client.id === DEFAULT_AGENT_CLIENT ? " (default)" : "";
236
+ console.log(
237
+ `${client.id}\t${client.displayName}${marker}\t${defaultSkillsDir(client)}`,
238
+ );
239
+ }
240
+ return 0;
241
+ }
217
242
 
218
243
  const [command, identifier, ...rest] = args;
219
244
  if (
@@ -244,7 +269,7 @@ function main(args) {
244
269
  }
245
270
  return runEsp32Setup(rest, {
246
271
  cliPath: fileURLToPath(import.meta.url),
247
- defaultSkillsDir: defaultSkillsDir(),
272
+ defaultClient: requireAgentClient(DEFAULT_AGENT_CLIENT),
248
273
  packageRoot: PACKAGE_ROOT,
249
274
  packageVersion: PACKAGE.version,
250
275
  platform,