@cannbot-ai/install-helper 0.0.2-beta.12 → 0.0.2-beta.15

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
@@ -18,6 +18,22 @@ npx @cannbot-ai/install-helper
18
18
 
19
19
  交互式向导会自动检测已安装的 AI 编程工具(OpenCode / Claude Code / Trae / Cursor),通过箭头键选择场景即可完成安装。
20
20
 
21
+ ## 主要特性
22
+
23
+ ### 核心功能
24
+ - **动态 Skill 发现**:自动扫描仓库中的 SKILL.md 文件,实时发现可用 Skills
25
+ - **智能缓存机制**:扫描结果缓存 24 小时,大幅提升后续命令执行速度
26
+ - **备份与恢复**:安装新插件时自动备份 AGENTS.md,支持一键恢复
27
+ - **参数校验**:`--tool` 和 `--level` 参数支持校验,提供友好的错误提示(支持中英文)
28
+ - **智能记忆**:记住上次选择的工具和安装级别,下次运行自动填充
29
+
30
+ ### 安装管理
31
+ - **交互式向导**:箭头键选择,空格切换,回车确认
32
+ - **插件安装**:支持 10 个官方插件,自动处理依赖
33
+ - **Skill 安装**:支持 89 个 Skills,按 11 个类别组织
34
+ - **智能卸载**:根据安装记录精确删除,自动清理空目录
35
+ - **健康检查**:检测安装状态,自动修复常见问题
36
+
21
37
  ## 命令一览
22
38
 
23
39
  | 命令 | 说明 |
@@ -168,6 +184,10 @@ install-helper uninstall ops-direct-invoke --tool opencode
168
184
 
169
185
  ## 可用场景
170
186
 
187
+ install-helper 支持 **10 个官方插件** 和 **89 个 Skills**(动态发现,数量可能随仓库更新而变化)。
188
+
189
+ #### 官方插件
190
+
171
191
  | # | 场景 | ID | 别名 |
172
192
  |---|------|----|------|
173
193
  | 1 | AscendC Kernel 直调 | ops-direct-invoke | ops-direct, direct, kernel |
@@ -181,6 +201,23 @@ install-helper uninstall ops-direct-invoke --tool opencode
181
201
  | 9 | 代码检视 | ops-code-reviewer | code-review, reviewer |
182
202
  | 10 | torch.compile 图模式 | torch-compile | torch, compile |
183
203
 
204
+ #### Skills 类别
205
+
206
+ Skills 按 11 个类别组织(动态发现,数量可能随仓库更新而变化):
207
+ - 知识与参考
208
+ - 环境与工具
209
+ - 调试与诊断
210
+ - 测试与质量
211
+ - AscendC 开发
212
+ - PyPTO 开发
213
+ - TileLang 开发
214
+ - Triton 开发
215
+ - 模型推理优化
216
+ - 图模式
217
+ - 平台工具
218
+
219
+ 使用 `install-helper install --list` 查看完整的 Skills 列表。
220
+
184
221
  ## 前置条件
185
222
 
186
223
  - Node.js >= 18
@@ -192,17 +229,21 @@ install-helper uninstall ops-direct-invoke --tool opencode
192
229
  install-helper 是一个编排工具:
193
230
 
194
231
  1. **自动检测**已安装的 AI 编程工具
195
- 2. **克隆或更新** cannbot-skills 仓库(缓存到 `~/.cannbot/repo/`)
196
- 3. **调用**各插件的 `init.sh` / `install.sh` 脚本执行实际安装
197
- 4. **追踪**安装状态(`~/.cannbot/config.yaml` + manifest)
232
+ 2. **动态扫描**仓库中的 SKILL.md 文件,发现可用 Skills
233
+ 3. **缓存扫描结果**到 `~/.cannbot/scan-cache.json`(24 小时有效)
234
+ 4. **克隆或更新** cannbot-skills 仓库(缓存到 `~/.cannbot/repo/`)
235
+ 5. **备份现有配置**(如果检测到已安装的 AGENTS.md)
236
+ 6. **调用**各插件的 `init.sh` / `install.sh` 脚本执行实际安装
237
+ 7. **追踪**安装状态(`~/.cannbot/config.yaml` + manifest)
198
238
 
199
239
  安装逻辑仍由各插件的 init.sh 脚本负责,install-helper 提供统一的交互式入口和状态管理。
200
240
 
201
241
  ## 配置文件
202
242
 
203
- 配置保存在 `~/.cannbot/config.yaml`:
243
+ 配置保存在 `~/.cannbot/` 目录:
204
244
 
205
245
  ```yaml
246
+ # config.yaml - 用户配置
206
247
  language: zh_CN # 界面语言
207
248
  lastTool: opencode # 上次使用的 AI 工具
208
249
  lastLevel: project # 上次安装级别
@@ -211,6 +252,26 @@ installedPlugins: # 已安装的插件列表
211
252
  - pypto-op-orchestrator
212
253
  ```
213
254
 
255
+ ```json
256
+ // scan-cache.json - 扫描缓存(自动生成,24 小时有效)
257
+ {
258
+ "skills": [...],
259
+ "timestamp": 1234567890,
260
+ "repoPath": "/path/to/repo"
261
+ }
262
+ ```
263
+
264
+ ```json
265
+ // installs/<plugin>.json - 安装记录(每个插件一个文件)
266
+ {
267
+ "pluginId": "ops-direct-invoke",
268
+ "installTime": "2026-06-23T10:00:00Z",
269
+ "configRoot": "/path/to/config",
270
+ "files": [...],
271
+ "directories": [...]
272
+ }
273
+ ```
274
+
214
275
  ## 开发
215
276
 
216
277
  ```bash
package/dist/index.js CHANGED
@@ -282,7 +282,38 @@ var zh_CN = {
282
282
  wizard_step_4_skill_title: "[4/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills \u2014 {category}",
283
283
  install_level_project: "\u5B89\u88C5\u5230\u5F53\u524D\u76EE\u5F55\uFF08\u63A8\u8350\uFF0C\u4EC5\u5F53\u524D\u9879\u76EE\u53EF\u7528\uFF09",
284
284
  install_level_global: "\u5B89\u88C5\u5230\u5168\u5C40\uFF08\u6240\u6709\u9879\u76EE\u5171\u4EAB\uFF09",
285
- wizard_select_mode_title: "\u9009\u62E9\u5B89\u88C5\u7C7B\u578B"
285
+ wizard_select_mode_title: "\u9009\u62E9\u5B89\u88C5\u7C7B\u578B",
286
+ skill_count_format: "{count} \u4E2A Skills",
287
+ result_success_format: "{count} \u6210\u529F",
288
+ result_failed_format: "{count} \u5931\u8D25",
289
+ uninstall_remove_label: "\u79FB\u9664",
290
+ uninstall_remove_file_format: "\u79FB\u9664 {count} \u4E2A\u6587\u4EF6",
291
+ uninstall_remove_dir_format: "{count} \u4E2A\u76EE\u5F55",
292
+ uninstall_clean_empty_dir: "\u6E05\u7406\u7A7A\u76EE\u5F55",
293
+ uninstall_skill_not_installed: "\u672A\u5B89\u88C5",
294
+ available_plugins: "\u53EF\u7528\u63D2\u4EF6",
295
+ available_skills: "\u53EF\u7528 Skills",
296
+ skill_total_count: "\u5171 {count} \u4E2A Skills",
297
+ wizard_confirm_install_format: "\u5373\u5C06\u5B89\u88C5 {count} \u4E2A\u63D2\u4EF6\u5230 {tool}\uFF08{level} \u7EA7\u522B\uFF09\uFF1A",
298
+ wizard_install_path: "\u5B89\u88C5\u8DEF\u5F84",
299
+ update_install_hint: "\u4F7F\u7528 {cmd} \u5B89\u88C5\u63D2\u4EF6",
300
+ hint_select_move: "\u79FB\u52A8",
301
+ hint_select_space: "\u7A7A\u683C",
302
+ hint_select_check: "\u52FE\u9009",
303
+ hint_select_confirm: "\u786E\u8BA4",
304
+ hint_checkbox_suffix: "\u4E0D\u9009\u6309\u56DE\u8F66\u53EF\u8FD4\u56DE",
305
+ doctor_not_installed: "\u672A\u5B89\u88C5",
306
+ doctor_links_valid: "{count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548",
307
+ doctor_broken_links: "{count} \u4E2A\u5931\u6548\u94FE\u63A5",
308
+ doctor_fixed: "\u5DF2\u6E05\u7406 {count} \u4E2A\u5931\u6548\u94FE\u63A5",
309
+ doctor_not_exist: "\u4E0D\u5B58\u5728",
310
+ doctor_created: "\u5DF2\u521B\u5EFA {path}",
311
+ doctor_config_exists: "\u5B58\u5728",
312
+ doctor_config_not_exist: "\u4E0D\u5B58\u5728",
313
+ doctor_result_with_fix: "{warnings} \u4E2A\u8B66\u544A, {errors} \u4E2A\u9519\u8BEF, {fixes} \u9879\u5DF2\u4FEE\u590D",
314
+ doctor_result_without_fix: "{warnings} \u4E2A\u8B66\u544A, {errors} \u4E2A\u9519\u8BEF",
315
+ count_suffix: "\u4E2A",
316
+ try_prompt: "\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50"
286
317
  };
287
318
  var en_US = {
288
319
  wizard_title: "CANNBot Install Helper",
@@ -419,7 +450,38 @@ var en_US = {
419
450
  wizard_step_4_skill_title: "[4/4] Select Skills to install \u2014 {category}",
420
451
  install_level_project: "Install to current directory (recommended, current project only)",
421
452
  install_level_global: "Install globally (all projects)",
422
- wizard_select_mode_title: "Select installation type"
453
+ wizard_select_mode_title: "Select installation type",
454
+ skill_count_format: "{count} Skills",
455
+ result_success_format: "{count} succeeded",
456
+ result_failed_format: "{count} failed",
457
+ uninstall_remove_label: "Removed",
458
+ uninstall_remove_file_format: "Removed {count} files",
459
+ uninstall_remove_dir_format: "{count} directories",
460
+ uninstall_clean_empty_dir: "Cleaned empty directory",
461
+ uninstall_skill_not_installed: "Not installed",
462
+ available_plugins: "Available plugins",
463
+ available_skills: "Available Skills",
464
+ skill_total_count: "Total {count} Skills",
465
+ wizard_confirm_install_format: "About to install {count} plugins to {tool} ({level} level):",
466
+ wizard_install_path: "Install path",
467
+ update_install_hint: "Use {cmd} to install plugins",
468
+ hint_select_move: "Navigate",
469
+ hint_select_space: "Space",
470
+ hint_select_check: "Toggle",
471
+ hint_select_confirm: "Confirm",
472
+ hint_checkbox_suffix: "Press enter without selection to go back",
473
+ doctor_not_installed: "Not installed",
474
+ doctor_links_valid: "{count} symlinks all valid",
475
+ doctor_broken_links: "{count} broken links",
476
+ doctor_fixed: "Cleaned {count} broken links",
477
+ doctor_not_exist: "Does not exist",
478
+ doctor_created: "Created {path}",
479
+ doctor_config_exists: "Exists",
480
+ doctor_config_not_exist: "Does not exist",
481
+ doctor_result_with_fix: "{warnings} warnings, {errors} errors, {fixes} fixed",
482
+ doctor_result_without_fix: "{warnings} warnings, {errors} errors",
483
+ count_suffix: "",
484
+ try_prompt: "help me develop an Abs operator"
423
485
  };
424
486
  var currentLang = "zh_CN";
425
487
  function setLanguage(lang) {
@@ -682,6 +744,9 @@ function scanInstalled() {
682
744
  for (const manifest of manifests) {
683
745
  const plugin = plugins.find((p) => p.id === manifest.team);
684
746
  if (!plugin) continue;
747
+ if (!verifyPluginFilesExist(configRoot, manifest, tool, level)) {
748
+ continue;
749
+ }
685
750
  const alreadyAdded = installed.some(
686
751
  (p) => p.id === plugin.id && p.tool === tool && p.level === level
687
752
  );
@@ -701,6 +766,32 @@ function scanInstalled() {
701
766
  }
702
767
  return installed;
703
768
  }
769
+ function verifyPluginFilesExist(configRoot, manifest, tool, level) {
770
+ const skillsDir = join3(configRoot, "skills");
771
+ const agentsDir = join3(configRoot, "agents");
772
+ if (manifest.installed_skills && manifest.installed_skills.length > 0) {
773
+ const hasAnySkill = manifest.installed_skills.some((skillId) => {
774
+ const skillPath = join3(skillsDir, skillId);
775
+ return existsSync3(skillPath);
776
+ });
777
+ if (!hasAnySkill) return false;
778
+ }
779
+ if (manifest.installed_agents && manifest.installed_agents.length > 0) {
780
+ const hasAnyAgent = manifest.installed_agents.some((agentId) => {
781
+ const agentPath = join3(agentsDir, agentId);
782
+ return existsSync3(agentPath);
783
+ });
784
+ if (!hasAnyAgent) return false;
785
+ }
786
+ let agentsMdPath;
787
+ if (tool === "opencode" && level === "project") {
788
+ agentsMdPath = join3(configRoot, "..", "AGENTS.md");
789
+ } else {
790
+ agentsMdPath = join3(configRoot, "AGENTS.md");
791
+ }
792
+ if (!existsSync3(agentsMdPath)) return false;
793
+ return true;
794
+ }
704
795
 
705
796
  // src/utils/logger.ts
706
797
  import chalk from "chalk";
@@ -756,10 +847,10 @@ function printBoxTitle(title, width = 65) {
756
847
  }
757
848
  function showOperationHints(isCheckbox = false) {
758
849
  if (isCheckbox) {
759
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u7A7A\u683C\x1B[0m \u52FE\u9009 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4\uFF08\u4E0D\u9009\u6309\u56DE\u8F66\u53EF\u8FD4\u56DE\uFF09
850
+ console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m${t("hint_select_space")}\x1B[0m ${t("hint_select_check")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}\uFF08${t("hint_checkbox_suffix")}\uFF09
760
851
  `);
761
852
  } else {
762
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4
853
+ console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}
763
854
  `);
764
855
  }
765
856
  }
@@ -1068,8 +1159,8 @@ async function stepConfirm(tool, level, plugins) {
1068
1159
  const displayPath = getConfigRoot(tool, level);
1069
1160
  const toolName = getToolDisplayName(tool);
1070
1161
  const levelText = level === "project" ? "project" : "global";
1071
- logger.info(`\u5373\u5C06\u5B89\u88C5 ${chalk2.bold(plugins.length)} \u4E2A\u63D2\u4EF6\u5230 ${chalk2.green(toolName)}\uFF08${chalk2.cyan(levelText)} \u7EA7\u522B\uFF09\uFF1A`);
1072
- logger.info(`\u5B89\u88C5\u8DEF\u5F84: ${chalk2.cyan(displayPath)}`);
1162
+ logger.info(t("wizard_confirm_install_format").replace("{count}", chalk2.bold(String(plugins.length))).replace("{tool}", chalk2.green(toolName)).replace("{level}", chalk2.cyan(levelText)));
1163
+ logger.info(`${t("wizard_install_path")}: ${chalk2.cyan(displayPath)}`);
1073
1164
  for (const plugin of selectedPlugins) {
1074
1165
  if (plugin) {
1075
1166
  logger.step(` \u2022 ${chalk2.bold(plugin.displayName)}`);
@@ -1096,7 +1187,7 @@ import { execa as execa2 } from "execa";
1096
1187
 
1097
1188
  // src/core/scanner.ts
1098
1189
  import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1099
- import { join as join5 } from "path";
1190
+ import { join as join5, dirname } from "path";
1100
1191
  import { parse as parseYaml } from "yaml";
1101
1192
  var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
1102
1193
  "node_modules",
@@ -1779,7 +1870,13 @@ function removeSkillsFromRecord(skillIds, tool, level, installPath) {
1779
1870
  }
1780
1871
  function getInstalledSkills(tool, level, installPath) {
1781
1872
  const record = readSkillRecord();
1782
- return record[tool]?.[level]?.[installPath]?.skills || [];
1873
+ const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
1874
+ const configRoot = getConfigRoot(tool, level);
1875
+ const skillsDir = join7(configRoot, "skills");
1876
+ return recordedSkills.filter((skillId) => {
1877
+ const skillPath = join7(skillsDir, skillId);
1878
+ return existsSync7(skillPath) || isSymlink(skillPath);
1879
+ });
1783
1880
  }
1784
1881
 
1785
1882
  // src/core/backup.ts
@@ -2135,10 +2232,10 @@ async function uninstallSkills(skillIds, tool, level) {
2135
2232
  unlinkSync3(targetPath);
2136
2233
  results.push({ skillId, success: true });
2137
2234
  removedIds.push(skillId);
2138
- logger.step(` \u79FB\u9664: ${skillId}`);
2235
+ logger.step(` ${t("uninstall_remove_label")}: ${skillId}`);
2139
2236
  } else {
2140
2237
  results.push({ skillId, success: false, error: "Not installed" });
2141
- logger.warn(`${skillId} \u672A\u5B89\u88C5`);
2238
+ logger.warn(`${skillId} ${t("uninstall_skill_not_installed")}`);
2142
2239
  }
2143
2240
  } catch (error) {
2144
2241
  results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
@@ -2152,14 +2249,14 @@ async function uninstallSkills(skillIds, tool, level) {
2152
2249
  const entries = readdirSync3(skillsDir);
2153
2250
  if (entries.length === 0) {
2154
2251
  rmdirSync(skillsDir);
2155
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: skills/`);
2252
+ logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
2156
2253
  }
2157
2254
  } catch {
2158
2255
  }
2159
2256
  }
2160
2257
  return results;
2161
2258
  }
2162
- async function interactiveSkillSelect() {
2259
+ async function interactiveSkillSelect(tool, level) {
2163
2260
  const BACK3 = "__back__";
2164
2261
  const CANCEL3 = "__cancel__";
2165
2262
  const categories = getAllCategories();
@@ -2195,15 +2292,8 @@ async function interactiveSkillSelect() {
2195
2292
  const category = categories.find((c) => c.id === selectedCategoryId);
2196
2293
  if (!category) return [];
2197
2294
  printBoxTitle(t("wizard_step_4_skill_title").replace("{category}", category.name));
2198
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
2199
- const levels = ["project", "global"];
2200
- const installedSkills = [];
2201
- for (const tk of tools) {
2202
- for (const l of levels) {
2203
- const path = l === "project" ? process.cwd() : getConfigRoot(tk, l);
2204
- installedSkills.push(...getInstalledSkills(tk, l, path));
2205
- }
2206
- }
2295
+ const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
2296
+ const installedSkills = getInstalledSkills(tool, level, installPath);
2207
2297
  const installedSet = new Set(installedSkills);
2208
2298
  const skillChoices = category.skills.map((skill) => {
2209
2299
  const isInstalled = installedSet.has(skill.id);
@@ -2268,7 +2358,7 @@ function listAllSkills() {
2268
2358
  console.log(table.toString());
2269
2359
  }
2270
2360
  console.log();
2271
- console.log(chalk4.dim(` \u5171 ${getAllSkills().length} \u4E2A Skills`));
2361
+ console.log(chalk4.dim(` ${t("skill_total_count").replace("{count}", String(getAllSkills().length))}`));
2272
2362
  console.log();
2273
2363
  }
2274
2364
  function isSymlink2(path) {
@@ -2305,7 +2395,8 @@ function printPluginList(plugins, installed) {
2305
2395
  ]);
2306
2396
  });
2307
2397
  console.log();
2308
- console.log(chalk5.bold(` ${t("list_title")} (${plugins.length} \u4E2A)`));
2398
+ const countSuffix = t("count_suffix");
2399
+ console.log(chalk5.bold(` ${t("list_title")} (${plugins.length}${countSuffix ? " " + countSuffix : ""})`));
2309
2400
  console.log(table.toString());
2310
2401
  console.log();
2311
2402
  }
@@ -2338,15 +2429,16 @@ function printEnhancedSummary(results, tool, configRoot) {
2338
2429
  return;
2339
2430
  }
2340
2431
  const toolName = getToolDisplayName(tool);
2432
+ const countSuffix = t("count_suffix");
2341
2433
  const lines = [
2342
2434
  chalk5.bold(` ${t("install_done")}!`),
2343
2435
  "",
2344
2436
  ` ${chalk5.dim(t("install_installed_to") + ":")} ${configRoot}`,
2345
- ` ${chalk5.dim(t("install_skills_count") + ":")} ${totalSkills} \u4E2A ${chalk5.dim("|")} ${chalk5.dim(t("install_agents_count") + ":")} ${totalAgents} \u4E2A`,
2437
+ ` ${chalk5.dim(t("install_skills_count") + ":")} ${totalSkills}${countSuffix ? " " + countSuffix : ""} ${chalk5.dim("|")} ${chalk5.dim(t("install_agents_count") + ":")} ${totalAgents}${countSuffix ? " " + countSuffix : ""}`,
2346
2438
  "",
2347
2439
  chalk5.bold(` ${t("install_enhanced_next_steps")}:`),
2348
2440
  ` ${chalk5.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk5.green(toolName.toLowerCase())}`,
2349
- ` ${chalk5.cyan("2.")} ${t("install_enhanced_try")}: ${chalk5.green("\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50")}`,
2441
+ ` ${chalk5.cyan("2.")} ${t("install_enhanced_try")}: ${chalk5.green(t("try_prompt"))}`,
2350
2442
  ` ${chalk5.cyan("3.")} ${t("install_enhanced_more")}: ${chalk5.green("install-helper list")}`,
2351
2443
  ` ${chalk5.cyan("4.")} ${t("install_enhanced_check")}: ${chalk5.green("install-helper doctor")}`,
2352
2444
  ""
@@ -2536,7 +2628,7 @@ async function skillInstallFlow() {
2536
2628
  break;
2537
2629
  }
2538
2630
  case 2: {
2539
- const selectedSkills = await interactiveSkillSelect();
2631
+ const selectedSkills = await interactiveSkillSelect(tool, level);
2540
2632
  if (selectedSkills === "back") {
2541
2633
  step = 1;
2542
2634
  break;
@@ -2554,7 +2646,7 @@ async function skillInstallFlow() {
2554
2646
  spinner.start();
2555
2647
  const repoPath = await repoManager.ensureRepo();
2556
2648
  spinner.succeed(t("loading_plugin_data_complete"));
2557
- logger.info(`${t("skill_install_progress")} ${chalk6.bold(selectedSkills.length)} \u4E2A Skills...`);
2649
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk6.bold(String(selectedSkills.length)))}...`);
2558
2650
  const results = await installSkills(selectedSkills, tool, level, repoPath);
2559
2651
  let successCount = 0;
2560
2652
  let failCount = 0;
@@ -2572,7 +2664,7 @@ async function skillInstallFlow() {
2572
2664
  }
2573
2665
  const configRoot = getConfigRoot(tool, level);
2574
2666
  logger.blank();
2575
- logger.success(`${t("skill_install_done")}: ${chalk6.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk6.red(failCount + " \u5931\u8D25") : chalk6.dim(failCount + " \u5931\u8D25")}`);
2667
+ logger.success(`${t("skill_install_done")}: ${chalk6.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk6.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk6.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2576
2668
  logger.blank();
2577
2669
  logger.info(`${t("install_to")}: ${chalk6.cyan(configRoot + "/skills/")}`);
2578
2670
  logger.info(`${t("start_to_use").replace("{tool}", chalk6.green(tool))}`);
@@ -2618,7 +2710,7 @@ async function doctorCommand(options = {}) {
2618
2710
  chalk7.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
2619
2711
  );
2620
2712
  } else {
2621
- console.log(chalk7.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 \u672A\u5B89\u88C5`);
2713
+ console.log(chalk7.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 ${t("doctor_not_installed")}`);
2622
2714
  }
2623
2715
  }
2624
2716
  console.log();
@@ -2633,7 +2725,7 @@ async function doctorCommand(options = {}) {
2633
2725
  chalk7.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
2634
2726
  );
2635
2727
  } else {
2636
- console.log(chalk7.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 \u672A\u5B89\u88C5`);
2728
+ console.log(chalk7.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 ${t("doctor_not_installed")}`);
2637
2729
  }
2638
2730
  }
2639
2731
  console.log();
@@ -2646,44 +2738,44 @@ async function doctorCommand(options = {}) {
2646
2738
  const brokenLinks = checkBrokenLinks(skillsDir);
2647
2739
  if (brokenLinks === 0) {
2648
2740
  const count = readdirSync4(skillsDir).length;
2649
- console.log(chalk7.green(" \u2713") + ` ${skillsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
2741
+ console.log(chalk7.green(" \u2713") + ` ${skillsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2650
2742
  } else {
2651
- console.log(chalk7.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
2743
+ console.log(chalk7.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2652
2744
  warnings++;
2653
2745
  if (options.fix) {
2654
2746
  const fixed = fixBrokenLinks(skillsDir);
2655
2747
  fixes += fixed;
2656
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
2748
+ console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2657
2749
  }
2658
2750
  }
2659
2751
  } else {
2660
- console.log(chalk7.dim(" \u2014") + ` ${skillsDir} \u2014 \u4E0D\u5B58\u5728`);
2752
+ console.log(chalk7.dim(" \u2014") + ` ${skillsDir} \u2014 ${t("doctor_not_exist")}`);
2661
2753
  if (options.fix) {
2662
2754
  mkdirSync6(skillsDir, { recursive: true });
2663
2755
  fixes++;
2664
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${skillsDir}`);
2756
+ console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
2665
2757
  }
2666
2758
  }
2667
2759
  if (existsSync12(agentsDir)) {
2668
2760
  const brokenLinks = checkBrokenLinks(agentsDir);
2669
2761
  if (brokenLinks === 0) {
2670
2762
  const count = readdirSync4(agentsDir).length;
2671
- console.log(chalk7.green(" \u2713") + ` ${agentsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
2763
+ console.log(chalk7.green(" \u2713") + ` ${agentsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2672
2764
  } else {
2673
- console.log(chalk7.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
2765
+ console.log(chalk7.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2674
2766
  warnings++;
2675
2767
  if (options.fix) {
2676
2768
  const fixed = fixBrokenLinks(agentsDir);
2677
2769
  fixes += fixed;
2678
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
2770
+ console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2679
2771
  }
2680
2772
  }
2681
2773
  } else {
2682
- console.log(chalk7.dim(" \u2014") + ` ${agentsDir} \u2014 \u4E0D\u5B58\u5728`);
2774
+ console.log(chalk7.dim(" \u2014") + ` ${agentsDir} \u2014 ${t("doctor_not_exist")}`);
2683
2775
  if (options.fix) {
2684
2776
  mkdirSync6(agentsDir, { recursive: true });
2685
2777
  fixes++;
2686
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${agentsDir}`);
2778
+ console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", agentsDir)}`);
2687
2779
  }
2688
2780
  }
2689
2781
  console.log();
@@ -2691,19 +2783,19 @@ async function doctorCommand(options = {}) {
2691
2783
  const configFile = getConfigFileName(primaryTool);
2692
2784
  const configPath = join12(process.cwd(), configFile);
2693
2785
  if (existsSync12(configPath)) {
2694
- console.log(chalk7.green(" \u2713") + ` ${configFile} \u5B58\u5728`);
2786
+ console.log(chalk7.green(" \u2713") + ` ${configFile} ${t("doctor_config_exists")}`);
2695
2787
  } else {
2696
- console.log(chalk7.dim(" \u2014") + ` ${configFile} \u4E0D\u5B58\u5728`);
2788
+ console.log(chalk7.dim(" \u2014") + ` ${configFile} ${t("doctor_config_not_exist")}`);
2697
2789
  }
2698
2790
  console.log();
2699
2791
  console.log(chalk7.dim(" " + "\u2500".repeat(46)));
2700
2792
  if (options.fix && fixes > 0) {
2701
2793
  console.log(
2702
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF, ${fixes} \u9879\u5DF2\u4FEE\u590D`
2794
+ ` ${t("doctor_result")}: ${t("doctor_result_with_fix").replace("{warnings}", String(warnings)).replace("{errors}", String(errors)).replace("{fixes}", String(fixes))}`
2703
2795
  );
2704
2796
  } else {
2705
2797
  console.log(
2706
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF`
2798
+ ` ${t("doctor_result")}: ${t("doctor_result_without_fix").replace("{warnings}", String(warnings)).replace("{errors}", String(errors))}`
2707
2799
  );
2708
2800
  }
2709
2801
  console.log();
@@ -2798,9 +2890,9 @@ async function installCommand(names, options) {
2798
2890
  try {
2799
2891
  const scanRepoManager = createRepositoryManager();
2800
2892
  await scanRepoManager.ensureRepoAndScan();
2801
- scanSpinner2.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
2893
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
2802
2894
  } catch {
2803
- scanSpinner2.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
2895
+ scanSpinner2.warn(t("loading_skills_list_failed"));
2804
2896
  }
2805
2897
  let tool2;
2806
2898
  if (options.tool) {
@@ -2810,11 +2902,12 @@ async function installCommand(names, options) {
2810
2902
  if (detected === "back" || detected === "cancel") return;
2811
2903
  tool2 = detected;
2812
2904
  }
2905
+ const level2 = options.level ? validateLevel(options.level) : "project";
2813
2906
  let step = 1;
2814
2907
  while (true) {
2815
2908
  switch (step) {
2816
2909
  case 1: {
2817
- const selectedSkills = await interactiveSkillSelect();
2910
+ const selectedSkills = await interactiveSkillSelect(tool2, level2);
2818
2911
  if (selectedSkills === "back") {
2819
2912
  const redetected = await selectToolWithDetection();
2820
2913
  if (redetected === "back" || redetected === "cancel") return;
@@ -2831,9 +2924,8 @@ async function installCommand(names, options) {
2831
2924
  const spinner = createSpinner(t("loading_plugin_data"));
2832
2925
  spinner.start();
2833
2926
  const repoPath = await repoManager.ensureRepo();
2834
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2835
- logger.info(`${t("skill_install_progress")} ${chalk9.bold(names.length)} \u4E2A Skills...`);
2836
- const level2 = options.level ? validateLevel(options.level) : "project";
2927
+ spinner.succeed(t("loading_plugin_data_complete"));
2928
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk9.bold(String(names.length)))}...`);
2837
2929
  const results = await installSkills(names, tool2, level2, repoPath);
2838
2930
  let successCount = 0;
2839
2931
  let failCount = 0;
@@ -2851,7 +2943,7 @@ async function installCommand(names, options) {
2851
2943
  }
2852
2944
  const configRoot = getConfigRoot(tool2, level2);
2853
2945
  logger.blank();
2854
- logger.success(`${t("skill_install_done")}: ${chalk9.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk9.red(failCount + " \u5931\u8D25") : chalk9.dim(failCount + " \u5931\u8D25")}`);
2946
+ logger.success(`${t("skill_install_done")}: ${chalk9.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk9.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk9.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2855
2947
  logger.blank();
2856
2948
  logger.info(`${t("install_to")}: ${chalk9.cyan(configRoot + "/skills/")}`);
2857
2949
  logger.info(`${t("start_to_use").replace("{tool}", chalk9.green(tool2))}`);
@@ -2866,9 +2958,9 @@ async function installCommand(names, options) {
2866
2958
  try {
2867
2959
  const scanRepoManager = createRepositoryManager();
2868
2960
  await scanRepoManager.ensureRepoAndScan();
2869
- scanSpinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
2961
+ scanSpinner.succeed(t("loading_skills_list_complete"));
2870
2962
  } catch {
2871
- scanSpinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
2963
+ scanSpinner.warn(t("loading_skills_list_failed"));
2872
2964
  }
2873
2965
  const plugins = [];
2874
2966
  const skills = [];
@@ -2885,11 +2977,11 @@ async function installCommand(names, options) {
2885
2977
  }
2886
2978
  logger.error(`${t("error_plugin_not_found")}: ${name}`);
2887
2979
  const allPlugins = getAllPlugins();
2888
- logger.info("\u53EF\u7528\u63D2\u4EF6:");
2980
+ logger.info(t("available_plugins") + ":");
2889
2981
  for (const p of allPlugins) {
2890
2982
  logger.step(` ${p.id} (${p.aliases.join(", ")})`);
2891
2983
  }
2892
- logger.info(`\u53EF\u7528 Skills: ${chalk9.cyan("install-helper install --list")}`);
2984
+ logger.info(`${t("available_skills")}: ${chalk9.cyan("install-helper install --list")}`);
2893
2985
  return;
2894
2986
  }
2895
2987
  let tool;
@@ -2961,7 +3053,7 @@ async function installCommand(names, options) {
2961
3053
  spinner.start();
2962
3054
  const repoPath = await repoManager.ensureRepo();
2963
3055
  spinner.succeed(t("loading_plugin_data_complete"));
2964
- logger.info(`${t("skill_install_progress")} ${chalk9.bold(skills.length)} \u4E2A Skills...`);
3056
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk9.bold(String(skills.length)))}...`);
2965
3057
  const results = await installSkills(skills, tool, level, repoPath);
2966
3058
  let successCount = 0;
2967
3059
  let failCount = 0;
@@ -2978,7 +3070,7 @@ async function installCommand(names, options) {
2978
3070
  }
2979
3071
  }
2980
3072
  logger.blank();
2981
- logger.success(`${t("skill_install_done")}: ${chalk9.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk9.red(failCount + " \u5931\u8D25") : chalk9.dim(failCount + " \u5931\u8D25")}`);
3073
+ logger.success(`${t("skill_install_done")}: ${chalk9.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk9.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk9.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2982
3074
  logger.blank();
2983
3075
  logger.info(`${t("install_to")}: ${chalk9.cyan(getConfigRoot(tool, level) + "/skills/")}`);
2984
3076
  logger.info(`${t("start_to_use").replace("{tool}", chalk9.green(tool))}`);
@@ -3050,7 +3142,7 @@ async function uninstallCommand(names, options) {
3050
3142
  if (existsSync13(filePath) || isSymlink3(filePath)) {
3051
3143
  unlinkSync5(filePath);
3052
3144
  const name = filePath.split("/").pop() || filePath;
3053
- logger.step(` \u79FB\u9664: ${name}`);
3145
+ logger.step(` ${t("uninstall_remove_label")}: ${name}`);
3054
3146
  removedFiles++;
3055
3147
  }
3056
3148
  } catch {
@@ -3068,7 +3160,7 @@ async function uninstallCommand(names, options) {
3068
3160
  if (entries.length === 0) {
3069
3161
  rmdirSync2(dirPath);
3070
3162
  const name = dirPath.split("/").pop() || dirPath;
3071
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${name}/`);
3163
+ logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
3072
3164
  removedDirs++;
3073
3165
  }
3074
3166
  }
@@ -3096,13 +3188,13 @@ async function uninstallCommand(names, options) {
3096
3188
  }
3097
3189
  const totalRemoved = removedFiles + removedDirs;
3098
3190
  if (totalRemoved > 0) {
3099
- logger.success(`${t("uninstall_removed")} ${plugin.displayName}\uFF08\u79FB\u9664 ${removedFiles} \u4E2A\u6587\u4EF6\uFF0C${removedDirs} \u4E2A\u76EE\u5F55\uFF09`);
3191
+ logger.success(`${t("uninstall_removed")} ${plugin.displayName}\uFF08${t("uninstall_remove_file_format").replace("{count}", String(removedFiles))}\uFF0C${t("uninstall_remove_dir_format").replace("{count}", String(removedDirs))}\uFF09`);
3100
3192
  } else {
3101
3193
  logger.warn(`${plugin.displayName}${t("uninstall_files_gone")}`);
3102
3194
  }
3103
3195
  }
3104
3196
  if (skills.length > 0) {
3105
- logger.info(`${t("uninstall_skills_in_progress")} ${skills.length} \u4E2A Skills...`);
3197
+ logger.info(`${t("uninstall_skills_in_progress")} ${t("skill_count_format").replace("{count}", String(skills.length))}...`);
3106
3198
  const results = await uninstallSkills(skills, tool, level);
3107
3199
  let successCount = 0;
3108
3200
  let failCount = 0;
@@ -3114,7 +3206,7 @@ async function uninstallCommand(names, options) {
3114
3206
  }
3115
3207
  }
3116
3208
  logger.blank();
3117
- logger.success(`${t("skill_uninstall_done")}: ${chalk10.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk10.red(failCount + " \u5931\u8D25") : chalk10.dim(failCount + " \u5931\u8D25")}`);
3209
+ logger.success(`${t("skill_uninstall_done")}: ${chalk10.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk10.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk10.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
3118
3210
  logger.blank();
3119
3211
  }
3120
3212
  }
@@ -3145,7 +3237,7 @@ async function updateCommand(pluginNames, options) {
3145
3237
  pluginsToUpdate = manifests.map((m) => m.team);
3146
3238
  if (pluginsToUpdate.length === 0) {
3147
3239
  logger.info(t("update_no_plugins"));
3148
- logger.info(`\u4F7F\u7528 ${chalk11.cyan("install-helper install <plugin>")} \u5B89\u88C5\u63D2\u4EF6`);
3240
+ logger.info(t("update_install_hint").replace("{cmd}", chalk11.cyan("install-helper install <plugin>")));
3149
3241
  return;
3150
3242
  }
3151
3243
  } else {
@@ -3296,7 +3388,7 @@ function createCLI() {
3296
3388
  setLanguage(config.language);
3297
3389
  }
3298
3390
  const program2 = new Command();
3299
- program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.12");
3391
+ program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.15");
3300
3392
  program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
3301
3393
  await initCommand();
3302
3394
  });
@@ -3333,7 +3425,7 @@ function createCLI() {
3333
3425
  // src/index.ts
3334
3426
  process.on("uncaughtException", (error) => {
3335
3427
  if (error.name === "ExitPromptError") {
3336
- console.log("\n\u5DF2\u53D6\u6D88\u5B89\u88C5");
3428
+ console.log("\n" + t("init_cancelled"));
3337
3429
  process.exit(0);
3338
3430
  }
3339
3431
  throw error;