@cannbot-ai/install-helper 0.0.2-beta.9 → 1.0.3

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/dist/index.js CHANGED
@@ -12,108 +12,45 @@ import { select, checkbox, Separator } from "@inquirer/prompts";
12
12
  import chalk2 from "chalk";
13
13
 
14
14
  // src/core/registry.ts
15
- var PLUGIN_REGISTRY = [
16
- {
17
- id: "ops-direct-invoke",
18
- dir: "plugins-official/ops-direct-invoke",
19
- displayName: "AscendC Kernel \u76F4\u8C03",
20
- script: "init.sh",
21
- aliases: ["ops-direct", "ascendc-direct", "direct", "kernel"],
22
- skills: 16,
23
- agents: 3,
24
- description: "Ascend C Kernel <<<>>> \u76F4\u8C03\u7B97\u5B50\u5F00\u53D1\u5168\u6D41\u7A0B"
25
- },
26
- {
27
- id: "ops-direct-invoke-flash",
28
- dir: "plugins-official/ops-direct-invoke-flash",
29
- displayName: "AscendC Kernel \u4ECE\u96F6\u6784\u5EFA",
30
- script: "init.sh",
31
- aliases: ["flash", "kernel-flash"],
32
- skills: 1,
33
- agents: 1,
34
- description: "Ascend C / Ascend950 Reg API \u6838\u51FD\u6570\u4ECE\u96F6\u6784\u5EFA"
35
- },
36
- {
37
- id: "ops-registry-invoke",
38
- dir: "plugins-official/ops-registry-invoke",
39
- displayName: "AscendC \u7B97\u5B50\u6CE8\u518C\u8C03\u7528",
40
- script: "init.sh",
41
- aliases: ["ops-registry", "ascendc-registry", "registry"],
42
- skills: 12,
43
- agents: 4,
44
- description: "Ascend C \u7B97\u5B50\u6CE8\u518C\u8C03\u7528\u5F00\u53D1\u6D41\u7A0B"
45
- },
46
- {
47
- id: "pypto-op-orchestrator",
48
- dir: "plugins-official/pypto-op-orchestrator",
49
- displayName: "PyPTO \u7B97\u5B50",
50
- script: "init.sh",
51
- aliases: ["pypto", "pytorch"],
52
- skills: 8,
53
- agents: 3,
54
- description: "PyPTO \u7B97\u5B50\u7AEF\u5230\u7AEF\u5F00\u53D1\u7F16\u6392"
55
- },
56
- {
57
- id: "triton-op-generator",
58
- dir: "plugins-official/triton-op-generator",
59
- displayName: "Triton \u7B97\u5B50\u751F\u6210",
60
- script: "install.sh",
61
- aliases: ["triton"],
62
- skills: 6,
63
- agents: 0,
64
- description: "Triton-Ascend \u7B97\u5B50\u4EE3\u7801\u751F\u6210\u4E0E\u4F18\u5316"
65
- },
66
- {
67
- id: "tilelang-op-orchestrator",
68
- dir: "plugins-official/tilelang-op-orchestrator",
69
- displayName: "TileLang \u7B97\u5B50",
70
- script: "init.sh",
71
- aliases: ["tilelang"],
72
- skills: 9,
73
- agents: 3,
74
- description: "TileLang \u7B97\u5B50\u5F00\u53D1\u6D41\u7A0B"
75
- },
76
- {
77
- id: "model-infer-optimize",
78
- dir: "plugins-official/model-infer-optimize",
79
- displayName: "NPU \u63A8\u7406\u4F18\u5316",
80
- script: "init.sh",
81
- aliases: ["model-infer", "infer", "inference"],
82
- skills: 11,
83
- agents: 3,
84
- description: "NPU \u6A21\u578B\u63A8\u7406\u7AEF\u5230\u7AEF\u4F18\u5316"
85
- },
86
- {
87
- id: "catlass-op-generator",
88
- dir: "plugins-official/catlass-op-generator",
89
- displayName: "Catlass \u7B97\u5B50\u76F4\u8C03",
90
- script: "init.sh",
91
- aliases: ["catlass"],
92
- skills: 10,
93
- agents: 3,
94
- description: "Catlass \u7B97\u5B50\u76F4\u8C03\u5F00\u53D1"
95
- },
96
- {
97
- id: "ops-code-reviewer",
98
- dir: "plugins-official/ops-code-reviewer",
99
- displayName: "\u4EE3\u7801\u68C0\u89C6",
100
- script: "init.sh",
101
- aliases: ["code-review", "reviewer", "review"],
102
- skills: 5,
103
- agents: 1,
104
- description: "\u4EE3\u7801\u68C0\u89C6\u4E0E\u89C4\u8303\u68C0\u67E5"
105
- },
106
- {
107
- id: "torch-compile",
108
- dir: "plugins-official/torch-compile",
109
- displayName: "torch.compile \u56FE\u6A21\u5F0F",
110
- script: "init.sh",
111
- aliases: ["torch", "compile", "graph"],
112
- skills: 6,
113
- agents: 1,
114
- description: "PyTorch torch.compile \u56FE\u6A21\u5F0F\u9002\u914D"
15
+ import { readdirSync, readFileSync } from "fs";
16
+ import { join, dirname } from "path";
17
+ import { fileURLToPath } from "url";
18
+ import { parse as parseYaml } from "yaml";
19
+ var __filename = fileURLToPath(import.meta.url);
20
+ var __dirname = dirname(__filename);
21
+ function loadPluginsFromYaml() {
22
+ const pluginsDir = join(__dirname, "plugins.d");
23
+ const defaultsPath = join(pluginsDir, "_defaults.yml");
24
+ let defaults = {};
25
+ try {
26
+ defaults = parseYaml(readFileSync(defaultsPath, "utf-8")) || {};
27
+ } catch {
115
28
  }
116
- ];
29
+ const plugins = [];
30
+ try {
31
+ for (const file of readdirSync(pluginsDir)) {
32
+ if (file.startsWith("_") || !file.endsWith(".yml")) continue;
33
+ try {
34
+ const content = parseYaml(readFileSync(join(pluginsDir, file), "utf-8"));
35
+ if (!content || !content.id) continue;
36
+ plugins.push({
37
+ id: content.id,
38
+ dir: content.dir,
39
+ displayName: content.displayName || content.id,
40
+ script: content.script || defaults.script || "init.sh",
41
+ aliases: content.aliases || [],
42
+ skills: content.skills ?? defaults.skills ?? 0,
43
+ agents: content.agents ?? defaults.agents ?? 0,
44
+ description: content.description || ""
45
+ });
46
+ } catch {
47
+ }
48
+ }
49
+ } catch {
50
+ }
51
+ return plugins;
52
+ }
53
+ var PLUGIN_REGISTRY = loadPluginsFromYaml();
117
54
  function findPlugin(query) {
118
55
  const normalized = query.toLowerCase().trim();
119
56
  const exactMatch = PLUGIN_REGISTRY.find(
@@ -139,17 +76,68 @@ function getPluginById(id) {
139
76
  import { execa } from "execa";
140
77
  import { existsSync as existsSync2 } from "fs";
141
78
  import { homedir as homedir2 } from "os";
142
- import { join as join2 } from "path";
79
+ import { join as join4 } from "path";
143
80
 
144
81
  // src/utils/paths.ts
145
82
  import { homedir } from "os";
146
- import { join } from "path";
83
+ import { join as join3 } from "path";
147
84
  import { existsSync } from "fs";
85
+
86
+ // src/utils/i18n.ts
87
+ import { readFileSync as readFileSync2 } from "fs";
88
+ import { join as join2, dirname as dirname2 } from "path";
89
+ import { fileURLToPath as fileURLToPath2 } from "url";
90
+ var __filename2 = fileURLToPath2(import.meta.url);
91
+ var __dirname2 = dirname2(__filename2);
92
+ function loadLocale(locale) {
93
+ const localePath = join2(__dirname2, "locales", `${locale}.json`);
94
+ try {
95
+ const content = readFileSync2(localePath, "utf-8");
96
+ return JSON.parse(content);
97
+ } catch {
98
+ const fallbackPath = join2(__dirname2, "locales", "zh_CN.json");
99
+ try {
100
+ const content = readFileSync2(fallbackPath, "utf-8");
101
+ return JSON.parse(content);
102
+ } catch {
103
+ return {};
104
+ }
105
+ }
106
+ }
107
+ var currentLang = "zh_CN";
108
+ var messages = loadLocale("zh_CN");
109
+ function setLanguage(lang) {
110
+ currentLang = lang;
111
+ messages = loadLocale(lang);
112
+ }
113
+ function t(key) {
114
+ return messages[key] !== void 0 ? messages[key] : key;
115
+ }
116
+
117
+ // src/utils/paths.ts
118
+ var VALID_TOOLS = ["opencode", "claude", "trae", "cursor", "copilot"];
119
+ var VALID_LEVELS = ["project", "global"];
120
+ function validateTool(tool) {
121
+ if (!VALID_TOOLS.includes(tool)) {
122
+ throw new Error(
123
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
124
+ );
125
+ }
126
+ return tool;
127
+ }
128
+ function validateLevel(level) {
129
+ if (!VALID_LEVELS.includes(level)) {
130
+ throw new Error(
131
+ t("error_invalid_level").replace("{level}", level).replace("{levels}", VALID_LEVELS.join(", "))
132
+ );
133
+ }
134
+ return level;
135
+ }
148
136
  function detectTraeVariant() {
149
137
  const home = homedir();
150
- if (existsSync(join(home, ".trae-cn"))) return "ide";
151
- if (existsSync(join(home, ".marscode"))) return "plugin";
152
- if (existsSync(join(home, ".traecli"))) return "cli";
138
+ if (existsSync(join3(home, ".trae-cn"))) return "ide";
139
+ if (existsSync(join3(home, ".marscode"))) return "plugin";
140
+ if (existsSync(join3(home, ".traecli"))) return "cli";
153
141
  return "unknown";
154
142
  }
155
143
  function getConfigRoot(tool, level, base) {
@@ -157,66 +145,74 @@ function getConfigRoot(tool, level, base) {
157
145
  if (level === "global") {
158
146
  switch (tool) {
159
147
  case "opencode":
160
- return join(home, ".config", "opencode");
148
+ return join3(home, ".config", "opencode");
161
149
  case "claude":
162
- return join(home, ".claude");
150
+ return join3(home, ".claude");
163
151
  case "trae": {
164
152
  const variant = detectTraeVariant();
165
153
  switch (variant) {
166
154
  case "plugin":
167
- return join(home, ".marscode");
155
+ return join3(home, ".marscode");
168
156
  case "cli":
169
- return join(home, ".traecli");
157
+ return join3(home, ".traecli");
170
158
  default:
171
- return join(home, ".trae-cn");
159
+ return join3(home, ".trae-cn");
172
160
  }
173
161
  }
174
162
  case "cursor":
175
- return join(home, ".cursor");
163
+ return join3(home, ".cursor");
176
164
  case "copilot":
177
- return join(home, ".copilot");
165
+ return join3(home, ".copilot");
166
+ default:
167
+ throw new Error(
168
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
169
+ );
178
170
  }
179
171
  }
180
172
  const baseDir = base || process.cwd();
181
173
  switch (tool) {
182
174
  case "opencode":
183
- return join(baseDir, ".opencode");
175
+ return join3(baseDir, ".opencode");
184
176
  case "claude":
185
- return join(baseDir, ".claude");
177
+ return join3(baseDir, ".claude");
186
178
  case "trae": {
187
179
  const variant = detectTraeVariant();
188
180
  switch (variant) {
189
181
  case "plugin":
190
- return join(baseDir, ".marscode");
182
+ return join3(baseDir, ".marscode");
191
183
  case "cli":
192
- return join(baseDir, ".traecli");
184
+ return join3(baseDir, ".traecli");
193
185
  default:
194
- return join(baseDir, ".trae");
186
+ return join3(baseDir, ".trae");
195
187
  }
196
188
  }
197
189
  case "cursor":
198
- return join(baseDir, ".cursor");
190
+ return join3(baseDir, ".cursor");
199
191
  case "copilot":
200
- return join(baseDir, ".github");
192
+ return join3(baseDir, ".github");
193
+ default:
194
+ throw new Error(
195
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
196
+ );
201
197
  }
202
198
  }
203
199
  function getConfigFileName(tool) {
204
200
  return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
205
201
  }
206
202
  function getSkillsDir(configRoot) {
207
- return join(configRoot, "skills");
203
+ return join3(configRoot, "skills");
208
204
  }
209
205
  function getAgentsDir(configRoot) {
210
- return join(configRoot, "agents");
206
+ return join3(configRoot, "agents");
211
207
  }
212
208
  function getManifestPath(configRoot) {
213
- return join(configRoot, "cannbot-manifest.json");
209
+ return join3(configRoot, "cannbot-manifest.json");
214
210
  }
215
211
  function getCannbotConfigDir() {
216
- return join(homedir(), ".cannbot");
212
+ return join3(homedir(), ".cannbot");
217
213
  }
218
214
  function getCannbotRepoPath() {
219
- return join(getCannbotConfigDir(), "repo");
215
+ return join3(getCannbotConfigDir(), "repo");
220
216
  }
221
217
 
222
218
  // src/core/detector.ts
@@ -254,12 +250,12 @@ async function detectTrae() {
254
250
  const home = homedir2();
255
251
  const variant = detectTraeVariant();
256
252
  let path;
257
- if (variant === "ide" && existsSync2(join2(home, ".trae-cn"))) {
258
- path = join2(home, ".trae-cn");
259
- } else if (variant === "plugin" && existsSync2(join2(home, ".marscode"))) {
260
- path = join2(home, ".marscode");
261
- } else if (variant === "cli" && existsSync2(join2(home, ".traecli"))) {
262
- path = join2(home, ".traecli");
253
+ if (variant === "ide" && existsSync2(join4(home, ".trae-cn"))) {
254
+ path = join4(home, ".trae-cn");
255
+ } else if (variant === "plugin" && existsSync2(join4(home, ".marscode"))) {
256
+ path = join4(home, ".marscode");
257
+ } else if (variant === "cli" && existsSync2(join4(home, ".traecli"))) {
258
+ path = join4(home, ".traecli");
263
259
  } else if (variant === "unknown") {
264
260
  const cmdPath = await getCommandPath("trae");
265
261
  if (cmdPath) path = cmdPath;
@@ -323,13 +319,13 @@ function getAllTools() {
323
319
  }
324
320
 
325
321
  // src/core/manifest.ts
326
- import { existsSync as existsSync3, readFileSync } from "fs";
327
- import { join as join3 } from "path";
322
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
323
+ import { join as join5 } from "path";
328
324
  function readManifest(configRoot) {
329
325
  const manifestPath = getManifestPath(configRoot);
330
326
  if (existsSync3(manifestPath)) {
331
327
  try {
332
- const content = readFileSync(manifestPath, "utf-8");
328
+ const content = readFileSync3(manifestPath, "utf-8");
333
329
  return JSON.parse(content);
334
330
  } catch {
335
331
  }
@@ -342,16 +338,16 @@ function readAllManifests(configRoot) {
342
338
  const standardPath = getManifestPath(configRoot);
343
339
  if (existsSync3(standardPath)) {
344
340
  try {
345
- const content = readFileSync(standardPath, "utf-8");
341
+ const content = readFileSync3(standardPath, "utf-8");
346
342
  manifests.push(JSON.parse(content));
347
343
  } catch {
348
344
  }
349
345
  }
350
346
  for (const plugin of plugins) {
351
- const pluginManifestPath = join3(configRoot, `${plugin.id}-manifest.json`);
347
+ const pluginManifestPath = join5(configRoot, `${plugin.id}-manifest.json`);
352
348
  if (existsSync3(pluginManifestPath)) {
353
349
  try {
354
- const content = readFileSync(pluginManifestPath, "utf-8");
350
+ const content = readFileSync3(pluginManifestPath, "utf-8");
355
351
  manifests.push(JSON.parse(content));
356
352
  } catch {
357
353
  }
@@ -371,6 +367,9 @@ function scanInstalled() {
371
367
  for (const manifest of manifests) {
372
368
  const plugin = plugins.find((p) => p.id === manifest.team);
373
369
  if (!plugin) continue;
370
+ if (!verifyPluginFilesExist(configRoot, manifest, tool, level)) {
371
+ continue;
372
+ }
374
373
  const alreadyAdded = installed.some(
375
374
  (p) => p.id === plugin.id && p.tool === tool && p.level === level
376
375
  );
@@ -390,284 +389,31 @@ function scanInstalled() {
390
389
  }
391
390
  return installed;
392
391
  }
393
-
394
- // src/utils/i18n.ts
395
- var zh_CN = {
396
- wizard_title: "CANNBot Install Helper",
397
- wizard_detect: "\u6B63\u5728\u68C0\u6D4B\u5DF2\u5B89\u88C5\u7684 AI \u7F16\u7A0B\u5DE5\u5177...",
398
- wizard_select_tool: "\u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177",
399
- wizard_select_level: "\u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E",
400
- wizard_select_plugins: "\u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6",
401
- wizard_confirm: "\u786E\u8BA4\u5B89\u88C5",
402
- wizard_detected_tool: "\u68C0\u6D4B\u5230",
403
- wizard_confirm_tool: "\u4F7F\u7528",
404
- wizard_last_used: "\u4E0A\u6B21\u4F7F\u7528",
405
- wizard_already_installed: "\u5DF2\u5B89\u88C5",
406
- install_cloning: "\u6B63\u5728\u514B\u9686 cannbot-skills \u4ED3\u5E93...",
407
- install_repo_ready: "\u4ED3\u5E93\u5C31\u7EEA",
408
- install_progress: "\u6B63\u5728\u5B89\u88C5",
409
- install_done: "\u5B89\u88C5\u5B8C\u6210",
410
- install_success: "\u6210\u529F",
411
- install_failed: "\u5931\u8D25",
412
- install_already_installed: "\u5DF2\u5B89\u88C5",
413
- install_reinstall_confirm: "\u662F\u5426\u91CD\u65B0\u5B89\u88C5\uFF08\u8986\u76D6\uFF09",
414
- install_skip: "\u8DF3\u8FC7",
415
- install_enhanced_next_steps: "\u4E0B\u4E00\u6B65",
416
- install_enhanced_launch: "\u542F\u52A8",
417
- install_enhanced_try: "\u8BD5\u8BD5",
418
- install_enhanced_more: "\u66F4\u591A",
419
- install_enhanced_check: "\u68C0\u67E5",
420
- install_enhanced_docs: "\u6587\u6863",
421
- update_title: "\u66F4\u65B0\u5DF2\u5B89\u88C5\u7684 Skills",
422
- update_updating: "\u6B63\u5728\u66F4\u65B0",
423
- update_done: "\u66F4\u65B0\u5B8C\u6210",
424
- update_no_plugins: "\u6682\u65E0\u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6",
425
- doctor_title: "CANNBot Doctor",
426
- doctor_tools: "AI \u5DE5\u5177",
427
- doctor_plugins: "\u5DF2\u5B89\u88C5\u63D2\u4EF6",
428
- doctor_links: "\u94FE\u63A5\u5B8C\u6574\u6027",
429
- doctor_config: "\u914D\u7F6E\u6587\u4EF6",
430
- doctor_result: "\u7ED3\u679C",
431
- doctor_fix_title: "\u81EA\u52A8\u4FEE\u590D",
432
- doctor_fix_cleaning: "\u6E05\u7406\u5931\u6548\u94FE\u63A5",
433
- doctor_fix_rebuilding: "\u91CD\u5EFA\u76EE\u5F55",
434
- doctor_fix_done: "\u4FEE\u590D\u5B8C\u6210",
435
- info_title: "\u63D2\u4EF6\u8BE6\u60C5",
436
- info_description: "\u63CF\u8FF0",
437
- info_skills: "Skills",
438
- info_agents: "Agents",
439
- info_status: "\u72B6\u6001",
440
- info_quickstart: "\u5FEB\u901F\u5F00\u59CB",
441
- info_not_found: "\u672A\u627E\u5230\u63D2\u4EF6",
442
- lang_title: "\u8BED\u8A00\u8BBE\u7F6E",
443
- lang_current: "\u5F53\u524D\u8BED\u8A00",
444
- lang_set: "\u8BED\u8A00\u5DF2\u8BBE\u7F6E\u4E3A",
445
- lang_invalid: "\u65E0\u6548\u7684\u8BED\u8A00\uFF0C\u652F\u6301: zh_CN, en_US",
446
- list_title: "\u53EF\u7528\u573A\u666F",
447
- list_id: "\u5E8F\u53F7",
448
- list_name: "\u573A\u666F\u540D",
449
- list_status: "\u72B6\u6001",
450
- list_skills: "Skills",
451
- list_agents: "Agents",
452
- status_installed: "\u5DF2\u88C5",
453
- status_not_installed: "\u672A\u88C5",
454
- error_tool_not_found: "\u672A\u68C0\u6D4B\u5230\u4EFB\u4F55 AI \u7F16\u7A0B\u5DE5\u5177",
455
- error_plugin_not_found: "\u672A\u627E\u5230\u63D2\u4EF6",
456
- error_repo_not_found: "\u4ED3\u5E93\u672A\u627E\u5230",
457
- quick_start: "\u542F\u52A8\u547D\u4EE4",
458
- quick_start_hint: "\u8BD5\u8BD5\u8F93\u5165: \u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50",
459
- skill_select_category: "\u9009\u62E9 Skill \u7C7B\u522B",
460
- skill_select_items: "\u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills",
461
- skill_already_installed: "\u5DF2\u5B89\u88C5",
462
- skill_install_progress: "\u6B63\u5728\u5B89\u88C5",
463
- skill_install_done: "Skill \u5B89\u88C5\u5B8C\u6210",
464
- skill_uninstall_done: "Skill \u5378\u8F7D\u5B8C\u6210",
465
- skill_list_title: "\u53EF\u7528 Skills",
466
- wizard_select_mode: "\u9009\u62E9\u5B89\u88C5\u7C7B\u578B",
467
- wizard_mode_plugin: "\u5B89\u88C5 Plugin",
468
- wizard_mode_plugin_desc: "\u5B8C\u6574\u5F00\u53D1\u5DE5\u4F5C\u6D41\uFF08Skills + Agents + \u914D\u7F6E\u6587\u4EF6\uFF09",
469
- wizard_mode_skill: "\u5B89\u88C5 Skill",
470
- wizard_mode_skill_desc: "\u4EC5\u5B89\u88C5\u9886\u57DF\u77E5\u8BC6\u6280\u80FD\uFF0C\u8BA9 AI \u638C\u63E1\u7279\u5B9A\u80FD\u529B",
471
- wizard_back: "\u8FD4\u56DE",
472
- wizard_cancel: "\u9000\u51FA",
473
- wizard_no_selection: "\u672A\u9009\u62E9\u4EFB\u4F55\u9879\uFF0C\u8BF7\u9009\u62E9\u64CD\u4F5C",
474
- wizard_back_to_reselect: "\u8FD4\u56DE",
475
- status_title: "\u5DF2\u5B89\u88C5\u63D2\u4EF6",
476
- status_none: "\u6682\u65E0\u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6",
477
- status_hint: "\u8FD0\u884C install-helper init \u5F00\u59CB\u5B89\u88C5",
478
- status_install_time: "\u5B89\u88C5\u65F6\u95F4",
479
- install_installed_to: "\u5B89\u88C5\u5230",
480
- install_skills_count: "Skills",
481
- install_agents_count: "Agents",
482
- lang_supported: "\u652F\u6301\u7684\u8BED\u8A00: zh_CN, en_US",
483
- lang_usage: "\u7528\u6CD5: install-helper lang set <language>",
484
- lang_specify: "\u8BF7\u6307\u5B9A\u8BED\u8A00: install-helper lang set zh_CN",
485
- lang_unknown_action: "\u672A\u77E5\u64CD\u4F5C",
486
- lang_supported_actions: "\u652F\u6301\u7684\u64CD\u4F5C: show, set",
487
- info_aliases: "\u522B\u540D",
488
- uninstall_not_found: "\u672A\u627E\u5230",
489
- uninstall_no_record: "\u672A\u627E\u5230\u5B89\u88C5\u8BB0\u5F55\uFF0C\u53EF\u80FD\u672A\u5B89\u88C5\u6216\u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F",
490
- uninstall_no_record_hint: "\u5982\u9700\u5F3A\u5236\u6E05\u7406\uFF0C\u8BF7\u624B\u52A8\u5220\u9664 .opencode/ \u6216 .claude/ \u76EE\u5F55\u4E2D\u7684\u76F8\u5173\u6587\u4EF6",
491
- uninstall_in_progress: "\u6B63\u5728\u5378\u8F7D",
492
- uninstall_removed: "\u5DF2\u5378\u8F7D",
493
- uninstall_files_gone: "\u7684\u6587\u4EF6\u5DF2\u4E0D\u5B58\u5728\uFF0C\u5DF2\u6E05\u7406\u5B89\u88C5\u8BB0\u5F55",
494
- uninstall_skills_in_progress: "\u6B63\u5728\u5378\u8F7D",
495
- init_pkg_dir_warn: "\u5F53\u524D\u76EE\u5F55\u770B\u8D77\u6765\u662F install-helper \u5305\u76EE\u5F55",
496
- init_pkg_dir_hint: "\u8BF7\u5728\u4F60\u7684\u9879\u76EE\u76EE\u5F55\u4E2D\u8FD0\u884C\u6B64\u547D\u4EE4\uFF0C\u800C\u4E0D\u662F\u5728 install-helper \u5B89\u88C5\u76EE\u5F55\u4E2D",
497
- init_pkg_dir_example: "\u793A\u4F8B\uFF1Acd ~/my-project && install-helper",
498
- init_cancelled: "\u5DF2\u53D6\u6D88\u5B89\u88C5",
499
- backup_detected: "\u68C0\u6D4B\u5230\u5F53\u524D\u76EE\u5F55\u5DF2\u5B89\u88C5\u63D2\u4EF6",
500
- backup_current: "\u5F53\u524D\u5B89\u88C5",
501
- backup_installing: "\u5373\u5C06\u5B89\u88C5",
502
- backup_overwrite_warning: "\u7EE7\u7EED\u5B89\u88C5\u5C06\u8986\u76D6\u73B0\u6709\u7684 AGENTS.md \u914D\u7F6E\u6587\u4EF6\u3002",
503
- backup_auto_create: "\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u521B\u5EFA\u5907\u4EFD\uFF0C\u4EE5\u4FBF\u540E\u7EED\u6062\u590D\u3002",
504
- backup_overwrite: "\u8986\u76D6\u5B89\u88C5\uFF08\u81EA\u52A8\u5907\u4EFD\uFF09",
505
- backup_cancel: "\u53D6\u6D88\u5B89\u88C5",
506
- backup_restore_prompt: "\u68C0\u6D4B\u5230\u5907\u4EFD\u914D\u7F6E\uFF0C\u662F\u5426\u6062\u590D\uFF1F",
507
- backup_restore: "\u6062\u590D",
508
- backup_only_uninstall: "\u4EC5\u5378\u8F7D\uFF08\u4E0D\u6062\u590D\uFF09",
509
- backup_select_action: "\u8BF7\u9009\u62E9\u64CD\u4F5C",
510
- backup_restore_success: "\u5DF2\u6062\u590D\u914D\u7F6E",
511
- backup_created: "\u5DF2\u521B\u5EFA\u5907\u4EFD",
512
- loading_skills_list: "\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...",
513
- loading_skills_list_complete: "Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210",
514
- loading_plugin_data: "\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...",
515
- loading_plugin_data_complete: "\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210",
516
- loading_skills_list_failed: "Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E",
517
- install_no_skill_selected: "\u672A\u9009\u62E9\u4EFB\u4F55 Skill",
518
- install_to: "\u5B89\u88C5\u5230",
519
- start_to_use: "\u542F\u52A8 {tool} \u5373\u53EF\u4F7F\u7528",
520
- wizard_step_1_title: "[1/4] \u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177",
521
- wizard_step_2_title: "[2/4] \u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E",
522
- wizard_step_3_title: "[3/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6",
523
- wizard_step_4_title: "[4/4] \u786E\u8BA4\u5B89\u88C5",
524
- wizard_step_3_skill_title: "[3/4] \u9009\u62E9 Skill \u7C7B\u522B",
525
- wizard_step_4_skill_title: "[4/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills \u2014 {category}",
526
- install_level_project: "\u5B89\u88C5\u5230\u5F53\u524D\u76EE\u5F55\uFF08\u63A8\u8350\uFF0C\u4EC5\u5F53\u524D\u9879\u76EE\u53EF\u7528\uFF09",
527
- install_level_global: "\u5B89\u88C5\u5230\u5168\u5C40\uFF08\u6240\u6709\u9879\u76EE\u5171\u4EAB\uFF09",
528
- wizard_select_mode_title: "\u9009\u62E9\u5B89\u88C5\u7C7B\u578B"
529
- };
530
- var en_US = {
531
- wizard_title: "CANNBot Install Helper",
532
- wizard_detect: "Detecting installed AI coding tools...",
533
- wizard_select_tool: "Select AI coding tool",
534
- wizard_select_level: "Select install level",
535
- wizard_select_plugins: "Select scenarios to install",
536
- wizard_confirm: "Confirm installation",
537
- wizard_detected_tool: "Detected",
538
- wizard_confirm_tool: "Confirm using",
539
- wizard_last_used: "last used",
540
- wizard_already_installed: "installed",
541
- install_cloning: "Cloning cannbot-skills repository...",
542
- install_repo_ready: "Repository ready",
543
- install_progress: "Installing",
544
- install_done: "Installation complete",
545
- install_success: "success",
546
- install_failed: "failed",
547
- install_already_installed: "already installed",
548
- install_reinstall_confirm: "Reinstall (overwrite)",
549
- install_skip: "skipped",
550
- install_enhanced_next_steps: "Next steps",
551
- install_enhanced_launch: "Launch",
552
- install_enhanced_try: "Try",
553
- install_enhanced_more: "More",
554
- install_enhanced_check: "Check",
555
- install_enhanced_docs: "Docs",
556
- update_title: "Update installed Skills",
557
- update_updating: "Updating",
558
- update_done: "Update complete",
559
- update_no_plugins: "No installed plugins",
560
- doctor_title: "CANNBot Doctor",
561
- doctor_tools: "AI Tools",
562
- doctor_plugins: "Installed Plugins",
563
- doctor_links: "Link Integrity",
564
- doctor_config: "Configuration",
565
- doctor_result: "Result",
566
- doctor_fix_title: "Auto-fix",
567
- doctor_fix_cleaning: "Cleaning broken links",
568
- doctor_fix_rebuilding: "Rebuilding directories",
569
- doctor_fix_done: "Fix complete",
570
- info_title: "Plugin Details",
571
- info_description: "Description",
572
- info_skills: "Skills",
573
- info_agents: "Agents",
574
- info_status: "Status",
575
- info_quickstart: "Quick Start",
576
- info_not_found: "Plugin not found",
577
- lang_title: "Language Settings",
578
- lang_current: "Current language",
579
- lang_set: "Language set to",
580
- lang_invalid: "Invalid language, supported: zh_CN, en_US",
581
- list_title: "Available Scenarios",
582
- list_id: "#",
583
- list_name: "Name",
584
- list_status: "Status",
585
- list_skills: "Skills",
586
- list_agents: "Agents",
587
- status_installed: "installed",
588
- status_not_installed: "not installed",
589
- error_tool_not_found: "No AI coding tool detected",
590
- error_plugin_not_found: "Plugin not found",
591
- error_repo_not_found: "Repository not found",
592
- quick_start: "Launch command",
593
- quick_start_hint: "Try: help me develop an Abs operator",
594
- skill_select_category: "Select Skill category",
595
- skill_select_items: "Select Skills to install (space to toggle, enter to confirm)",
596
- skill_already_installed: "installed",
597
- skill_install_progress: "Installing",
598
- skill_install_done: "Skill installation complete",
599
- skill_uninstall_done: "Skill uninstallation complete",
600
- skill_list_title: "Available Skills",
601
- wizard_select_mode: "Select installation mode",
602
- wizard_mode_plugin: "Install Plugin",
603
- wizard_mode_plugin_desc: "Full development workflow (Skills + Agents + config)",
604
- wizard_mode_skill: "Install Skill",
605
- wizard_mode_skill_desc: "Domain knowledge only, give AI specific capabilities",
606
- wizard_back: "Go back",
607
- wizard_cancel: "Cancel installation",
608
- wizard_no_selection: "No items selected, please choose an action",
609
- wizard_back_to_reselect: "Go back",
610
- status_title: "Installed Plugins",
611
- status_none: "No installed plugins",
612
- status_hint: "Run install-helper init to start",
613
- status_install_time: "Installed at",
614
- install_installed_to: "Installed to",
615
- install_skills_count: "Skills",
616
- install_agents_count: "Agents",
617
- lang_supported: "Supported languages: zh_CN, en_US",
618
- lang_usage: "Usage: install-helper lang set <language>",
619
- lang_specify: "Please specify language: install-helper lang set zh_CN",
620
- lang_unknown_action: "Unknown action",
621
- lang_supported_actions: "Supported actions: show, set",
622
- info_aliases: "Aliases",
623
- uninstall_not_found: "Not found",
624
- uninstall_no_record: "No install record found, may not be installed or record is corrupted",
625
- uninstall_no_record_hint: "For manual cleanup, delete related files in .opencode/ or .claude/ directory",
626
- uninstall_in_progress: "Uninstalling",
627
- uninstall_removed: "Uninstalled",
628
- uninstall_files_gone: " files no longer exist, install record cleaned",
629
- uninstall_skills_in_progress: "Uninstalling",
630
- init_pkg_dir_warn: "Current directory appears to be the install-helper package directory",
631
- init_pkg_dir_hint: "Please run this command in your project directory, not in the install-helper package directory",
632
- init_pkg_dir_example: "Example: cd ~/my-project && install-helper",
633
- init_cancelled: "Installation cancelled",
634
- backup_detected: "Plugin already installed in current directory",
635
- backup_current: "Currently installed",
636
- backup_installing: "About to install",
637
- backup_overwrite_warning: "Continuing will overwrite existing AGENTS.md configuration.",
638
- backup_auto_create: "System will automatically create backup for future restoration.",
639
- backup_overwrite: "Overwrite (auto backup)",
640
- backup_cancel: "Cancel installation",
641
- backup_restore_prompt: "Backup configuration detected, restore?",
642
- backup_restore: "Restore",
643
- backup_only_uninstall: "Uninstall only (no restore)",
644
- backup_select_action: "Please select an action",
645
- backup_restore_success: "Configuration restored",
646
- backup_created: "Backup created",
647
- loading_skills_list: "Loading Skills list...",
648
- loading_skills_list_complete: "Skills list loaded",
649
- loading_plugin_data: "Loading plugin data...",
650
- loading_plugin_data_complete: "Plugin data loaded",
651
- loading_skills_list_failed: "Failed to load Skills list, using built-in data",
652
- install_no_skill_selected: "No Skill selected",
653
- install_to: "Install to",
654
- start_to_use: "Start {tool} to use",
655
- wizard_step_1_title: "[1/4] Select AI coding tool",
656
- wizard_step_2_title: "[2/4] Select installation location",
657
- wizard_step_3_title: "[3/4] Select plugins to install",
658
- wizard_step_4_title: "[4/4] Confirm installation",
659
- wizard_step_3_skill_title: "[3/4] Select Skill category",
660
- wizard_step_4_skill_title: "[4/4] Select Skills to install \u2014 {category}",
661
- install_level_project: "Install to current directory (recommended, current project only)",
662
- install_level_global: "Install globally (all projects)",
663
- wizard_select_mode_title: "Select installation type"
664
- };
665
- var currentLang = "zh_CN";
666
- function setLanguage(lang) {
667
- currentLang = lang;
668
- }
669
- function t(key) {
670
- return currentLang === "zh_CN" ? zh_CN[key] : en_US[key];
392
+ function verifyPluginFilesExist(configRoot, manifest, tool, level) {
393
+ const skillsDir = join5(configRoot, "skills");
394
+ const agentsDir = join5(configRoot, "agents");
395
+ if (manifest.installed_skills && manifest.installed_skills.length > 0) {
396
+ const hasAnySkill = manifest.installed_skills.some((skillId) => {
397
+ const skillPath = join5(skillsDir, skillId);
398
+ return existsSync3(skillPath);
399
+ });
400
+ if (!hasAnySkill) return false;
401
+ }
402
+ if (manifest.installed_agents && manifest.installed_agents.length > 0) {
403
+ const hasAnyAgent = manifest.installed_agents.some((agentId) => {
404
+ const agentPath = join5(agentsDir, agentId);
405
+ return existsSync3(agentPath);
406
+ });
407
+ if (!hasAnyAgent) return false;
408
+ }
409
+ let agentsMdPath;
410
+ if (tool === "opencode" && level === "project") {
411
+ agentsMdPath = join5(configRoot, "..", "AGENTS.md");
412
+ } else {
413
+ agentsMdPath = join5(configRoot, "AGENTS.md");
414
+ }
415
+ if (!existsSync3(agentsMdPath)) return false;
416
+ return true;
671
417
  }
672
418
 
673
419
  // src/utils/logger.ts
@@ -724,24 +470,24 @@ function printBoxTitle(title, width = 65) {
724
470
  }
725
471
  function showOperationHints(isCheckbox = false) {
726
472
  if (isCheckbox) {
727
- 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
473
+ 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
728
474
  `);
729
475
  } else {
730
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4
476
+ console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}
731
477
  `);
732
478
  }
733
479
  }
734
480
 
735
481
  // src/utils/config.ts
736
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
737
- import { join as join4 } from "path";
482
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
483
+ import { join as join6 } from "path";
738
484
  import { homedir as homedir3 } from "os";
739
485
  import { parse, stringify } from "yaml";
740
486
  function getConfigDir() {
741
- return join4(homedir3(), ".cannbot");
487
+ return join6(homedir3(), ".cannbot");
742
488
  }
743
489
  function getConfigPath() {
744
- return join4(getConfigDir(), "config.yaml");
490
+ return join6(getConfigDir(), "config.yaml");
745
491
  }
746
492
  function readConfig() {
747
493
  const configPath = getConfigPath();
@@ -752,7 +498,7 @@ function readConfig() {
752
498
  };
753
499
  }
754
500
  try {
755
- const content = readFileSync2(configPath, "utf-8");
501
+ const content = readFileSync4(configPath, "utf-8");
756
502
  const parsed = parse(content);
757
503
  return {
758
504
  language: parsed.language || "zh_CN",
@@ -1036,8 +782,8 @@ async function stepConfirm(tool, level, plugins) {
1036
782
  const displayPath = getConfigRoot(tool, level);
1037
783
  const toolName = getToolDisplayName(tool);
1038
784
  const levelText = level === "project" ? "project" : "global";
1039
- 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`);
1040
- logger.info(`\u5B89\u88C5\u8DEF\u5F84: ${chalk2.cyan(displayPath)}`);
785
+ logger.info(t("wizard_confirm_install_format").replace("{count}", chalk2.bold(String(plugins.length))).replace("{tool}", chalk2.green(toolName)).replace("{level}", chalk2.cyan(levelText)));
786
+ logger.info(`${t("wizard_install_path")}: ${chalk2.cyan(displayPath)}`);
1041
787
  for (const plugin of selectedPlugins) {
1042
788
  if (plugin) {
1043
789
  logger.step(` \u2022 ${chalk2.bold(plugin.displayName)}`);
@@ -1058,14 +804,19 @@ async function stepConfirm(tool, level, plugins) {
1058
804
  }
1059
805
 
1060
806
  // src/core/repository.ts
1061
- import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
1062
- import { join as join6 } from "path";
807
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
808
+ import { join as join8, dirname as dirname4 } from "path";
809
+ import { fileURLToPath as fileURLToPath4 } from "url";
1063
810
  import { execa as execa2 } from "execa";
811
+ import { parse as parseYaml3 } from "yaml";
1064
812
 
1065
813
  // src/core/scanner.ts
1066
- import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1067
- import { join as join5 } from "path";
1068
- import { parse as parseYaml } from "yaml";
814
+ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
815
+ import { join as join7, dirname as dirname3 } from "path";
816
+ import { fileURLToPath as fileURLToPath3 } from "url";
817
+ import { parse as parseYaml2 } from "yaml";
818
+ var __filename3 = fileURLToPath3(import.meta.url);
819
+ var __dirname3 = dirname3(__filename3);
1069
820
  var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
1070
821
  "node_modules",
1071
822
  ".git",
@@ -1086,36 +837,43 @@ var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
1086
837
  "docs",
1087
838
  "scripts"
1088
839
  ]);
1089
- var SKILL_SCAN_DIRS = [
1090
- "ops",
1091
- "model",
1092
- "graph",
1093
- "infra",
1094
- "ops-lab"
1095
- ];
1096
- var PLUGIN_SCAN_DIRS = [
1097
- "plugins-official",
1098
- "plugins-community"
1099
- ];
840
+ function loadScanConfig() {
841
+ const configPath = join7(__dirname3, "config", "repository.yaml");
842
+ try {
843
+ const config = parseYaml2(readFileSync5(configPath, "utf-8"));
844
+ return {
845
+ skillDirs: config.scanDirs || ["ops", "model", "graph", "infra", "ops-lab"],
846
+ pluginDirs: config.pluginDirs || ["plugins-official", "plugins-community"]
847
+ };
848
+ } catch {
849
+ return {
850
+ skillDirs: ["ops", "model", "graph", "infra", "ops-lab"],
851
+ pluginDirs: ["plugins-official", "plugins-community"]
852
+ };
853
+ }
854
+ }
855
+ var scanConfig = loadScanConfig();
856
+ var SKILL_SCAN_DIRS = scanConfig.skillDirs;
857
+ var PLUGIN_SCAN_DIRS = scanConfig.pluginDirs;
1100
858
  function scanSkills(repoPath) {
1101
859
  const skills = [];
1102
860
  const seen = /* @__PURE__ */ new Set();
1103
861
  for (const dir of SKILL_SCAN_DIRS) {
1104
- const fullPath = join5(repoPath, dir);
862
+ const fullPath = join7(repoPath, dir);
1105
863
  if (!existsSync5(fullPath)) continue;
1106
864
  scanDirectory(fullPath, repoPath, dir, skills, seen);
1107
865
  }
1108
866
  for (const pluginDir of PLUGIN_SCAN_DIRS) {
1109
- const pluginParent = join5(repoPath, pluginDir);
867
+ const pluginParent = join7(repoPath, pluginDir);
1110
868
  if (!existsSync5(pluginParent)) continue;
1111
869
  for (const plugin of safeReaddir(pluginParent)) {
1112
- const pluginPath = join5(pluginParent, plugin);
870
+ const pluginPath = join7(pluginParent, plugin);
1113
871
  if (!isDirectory(pluginPath)) continue;
1114
- const skillsDir = join5(pluginPath, "skills");
872
+ const skillsDir = join7(pluginPath, "skills");
1115
873
  if (existsSync5(skillsDir)) {
1116
874
  scanDirectory(skillsDir, repoPath, `${pluginDir}/${plugin}/skills`, skills, seen);
1117
875
  }
1118
- const skillDir = join5(pluginPath, "skill");
876
+ const skillDir = join7(pluginPath, "skill");
1119
877
  if (existsSync5(skillDir)) {
1120
878
  scanDirectory(skillDir, repoPath, `${pluginDir}/${plugin}/skill`, skills, seen);
1121
879
  }
@@ -1125,7 +883,7 @@ function scanSkills(repoPath) {
1125
883
  }
1126
884
  function parseFrontmatter(filePath) {
1127
885
  try {
1128
- const content = readFileSync3(filePath, "utf-8");
886
+ const content = readFileSync5(filePath, "utf-8");
1129
887
  const lines = content.split("\n");
1130
888
  if (lines.length < 3) return null;
1131
889
  if (lines[0].trim() !== "---") return null;
@@ -1138,7 +896,7 @@ function parseFrontmatter(filePath) {
1138
896
  }
1139
897
  if (endIndex === -1) return null;
1140
898
  const yamlBlock = lines.slice(1, endIndex).map((l) => l.replace(/\r$/, "")).join("\n");
1141
- const parsed = parseYaml(yamlBlock);
899
+ const parsed = parseYaml2(yamlBlock);
1142
900
  if (!parsed || typeof parsed !== "object") return null;
1143
901
  if (!parsed.name || typeof parsed.name !== "string") return null;
1144
902
  return {
@@ -1151,17 +909,17 @@ function parseFrontmatter(filePath) {
1151
909
  }
1152
910
  function getCurrentCommit(repoPath) {
1153
911
  try {
1154
- const headFile = join5(repoPath, ".git", "HEAD");
912
+ const headFile = join7(repoPath, ".git", "HEAD");
1155
913
  if (!existsSync5(headFile)) return "unknown";
1156
- const headContent = readFileSync3(headFile, "utf-8").trim();
914
+ const headContent = readFileSync5(headFile, "utf-8").trim();
1157
915
  if (headContent.startsWith("ref: ")) {
1158
- const refPath = join5(repoPath, ".git", headContent.slice(5));
916
+ const refPath = join7(repoPath, ".git", headContent.slice(5));
1159
917
  if (existsSync5(refPath)) {
1160
- return readFileSync3(refPath, "utf-8").trim();
918
+ return readFileSync5(refPath, "utf-8").trim();
1161
919
  }
1162
- const packedRefsPath = join5(repoPath, ".git", "packed-refs");
920
+ const packedRefsPath = join7(repoPath, ".git", "packed-refs");
1163
921
  if (existsSync5(packedRefsPath)) {
1164
- const packedRefs = readFileSync3(packedRefsPath, "utf-8");
922
+ const packedRefs = readFileSync5(packedRefsPath, "utf-8");
1165
923
  const refName = headContent.slice(5);
1166
924
  const lines = packedRefs.split("\n");
1167
925
  for (const line of lines) {
@@ -1180,7 +938,7 @@ function readScanCache() {
1180
938
  try {
1181
939
  const cachePath = getCachePath();
1182
940
  if (!existsSync5(cachePath)) return null;
1183
- const content = readFileSync3(cachePath, "utf-8");
941
+ const content = readFileSync5(cachePath, "utf-8");
1184
942
  const cache = JSON.parse(content);
1185
943
  if (!cache.skills || !cache.timestamp) return null;
1186
944
  const age = Date.now() - cache.timestamp;
@@ -1193,7 +951,7 @@ function readScanCache() {
1193
951
  function writeScanCache(cache) {
1194
952
  try {
1195
953
  const cachePath = getCachePath();
1196
- const cacheDir = dirname(cachePath);
954
+ const cacheDir = dirname3(cachePath);
1197
955
  if (!existsSync5(cacheDir)) {
1198
956
  mkdirSync2(cacheDir, { recursive: true });
1199
957
  }
@@ -1202,11 +960,10 @@ function writeScanCache(cache) {
1202
960
  }
1203
961
  }
1204
962
  function getCachePath() {
1205
- const homeDir = process.env.HOME || process.env.USERPROFILE || "/tmp";
1206
- return join5(homeDir, ".cannbot", "scan-cache.json");
963
+ return join7(getCannbotConfigDir(), "scan-cache.json");
1207
964
  }
1208
965
  function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1209
- const selfSkillMd = join5(dirPath, "SKILL.md");
966
+ const selfSkillMd = join7(dirPath, "SKILL.md");
1210
967
  if (existsSync5(selfSkillMd)) {
1211
968
  const frontmatter = parseFrontmatter(selfSkillMd);
1212
969
  if (frontmatter && !seen.has(frontmatter.name)) {
@@ -1221,10 +978,10 @@ function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1221
978
  return;
1222
979
  }
1223
980
  for (const entry of safeReaddir(dirPath)) {
1224
- const fullPath = join5(dirPath, entry);
981
+ const fullPath = join7(dirPath, entry);
1225
982
  if (EXCLUDE_DIRS.has(entry)) continue;
1226
983
  if (!isDirectory(fullPath)) continue;
1227
- const skillMd = join5(fullPath, "SKILL.md");
984
+ const skillMd = join7(fullPath, "SKILL.md");
1228
985
  if (existsSync5(skillMd)) {
1229
986
  const frontmatter = parseFrontmatter(skillMd);
1230
987
  if (frontmatter && !seen.has(frontmatter.name)) {
@@ -1243,7 +1000,7 @@ function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1243
1000
  }
1244
1001
  function safeReaddir(dir) {
1245
1002
  try {
1246
- return readdirSync(dir);
1003
+ return readdirSync2(dir);
1247
1004
  } catch {
1248
1005
  return [];
1249
1006
  }
@@ -1483,7 +1240,21 @@ function getAllCategories() {
1483
1240
  }
1484
1241
 
1485
1242
  // src/core/repository.ts
1486
- var REPO_URL = "https://gitcode.com/cann/cannbot-skills.git";
1243
+ var __filename4 = fileURLToPath4(import.meta.url);
1244
+ var __dirname4 = dirname4(__filename4);
1245
+ function loadRepoUrl() {
1246
+ if (process.env.CANNBOT_REPO_URL) {
1247
+ return process.env.CANNBOT_REPO_URL;
1248
+ }
1249
+ const configPath = join8(__dirname4, "config", "repository.yaml");
1250
+ try {
1251
+ const config = parseYaml3(readFileSync6(configPath, "utf-8"));
1252
+ return config.repository?.url || "https://gitcode.com/cann/cannbot-skills.git";
1253
+ } catch {
1254
+ return "https://gitcode.com/cann/cannbot-skills.git";
1255
+ }
1256
+ }
1257
+ var REPO_URL = loadRepoUrl();
1487
1258
  var RepositoryManager = class {
1488
1259
  repoPath;
1489
1260
  constructor(customPath) {
@@ -1541,8 +1312,8 @@ var RepositoryManager = class {
1541
1312
  }
1542
1313
  isValidRepo(path) {
1543
1314
  if (!existsSync6(path)) return false;
1544
- const gitDir = join6(path, ".git");
1545
- const pluginsDir = join6(path, "plugins-official");
1315
+ const gitDir = join8(path, ".git");
1316
+ const pluginsDir = join8(path, "plugins-official");
1546
1317
  return existsSync6(gitDir) && existsSync6(pluginsDir);
1547
1318
  }
1548
1319
  async cloneRepo() {
@@ -1578,17 +1349,17 @@ function createRepositoryManager(customPath) {
1578
1349
 
1579
1350
  // src/core/installer.ts
1580
1351
  import { existsSync as existsSync9 } from "fs";
1581
- import { join as join9 } from "path";
1352
+ import { join as join11 } from "path";
1582
1353
  import { execa as execa3 } from "execa";
1583
1354
 
1584
1355
  // src/core/record.ts
1585
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, lstatSync, unlinkSync } from "fs";
1586
- import { join as join7 } from "path";
1356
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4, lstatSync, unlinkSync } from "fs";
1357
+ import { join as join9 } from "path";
1587
1358
  function getInstallsDir() {
1588
- return join7(getCannbotConfigDir(), "installs");
1359
+ return join9(getCannbotConfigDir(), "installs");
1589
1360
  }
1590
1361
  function getRecordPath(pluginId) {
1591
- return join7(getInstallsDir(), `${pluginId}.json`);
1362
+ return join9(getInstallsDir(), `${pluginId}.json`);
1592
1363
  }
1593
1364
  function readRecord(pluginId) {
1594
1365
  const recordPath = getRecordPath(pluginId);
@@ -1596,7 +1367,7 @@ function readRecord(pluginId) {
1596
1367
  return null;
1597
1368
  }
1598
1369
  try {
1599
- const content = readFileSync4(recordPath, "utf-8");
1370
+ const content = readFileSync7(recordPath, "utf-8");
1600
1371
  return JSON.parse(content);
1601
1372
  } catch {
1602
1373
  return null;
@@ -1620,22 +1391,22 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
1620
1391
  const files = [];
1621
1392
  const directories = [];
1622
1393
  if (manifest) {
1623
- const skillsDir = join7(configRoot, "skills");
1394
+ const skillsDir = join9(configRoot, "skills");
1624
1395
  if (existsSync7(skillsDir)) {
1625
1396
  directories.push(skillsDir);
1626
1397
  for (const skillName of manifest.installed_skills || []) {
1627
- const skillPath = join7(skillsDir, skillName);
1398
+ const skillPath = join9(skillsDir, skillName);
1628
1399
  if (existsSync7(skillPath) || isSymlink(skillPath)) {
1629
1400
  files.push(skillPath);
1630
1401
  }
1631
1402
  }
1632
1403
  }
1633
- const agentsDir = join7(configRoot, "agents");
1404
+ const agentsDir = join9(configRoot, "agents");
1634
1405
  if (existsSync7(agentsDir)) {
1635
1406
  directories.push(agentsDir);
1636
1407
  for (const agentName of manifest.installed_agents || []) {
1637
- const agentPath = join7(agentsDir, agentName);
1638
- const agentPathMd = join7(agentsDir, agentName + ".md");
1408
+ const agentPath = join9(agentsDir, agentName);
1409
+ const agentPathMd = join9(agentsDir, agentName + ".md");
1639
1410
  if (existsSync7(agentPath) || isSymlink(agentPath)) {
1640
1411
  files.push(agentPath);
1641
1412
  } else if (existsSync7(agentPathMd) || isSymlink(agentPathMd)) {
@@ -1644,35 +1415,34 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
1644
1415
  }
1645
1416
  }
1646
1417
  }
1647
- const workflowsLink = join7(configRoot, "workflows");
1418
+ const workflowsLink = join9(configRoot, "workflows");
1648
1419
  if (isSymlink(workflowsLink)) {
1649
1420
  files.push(workflowsLink);
1650
1421
  }
1651
- const manifestPath = join7(configRoot, "cannbot-manifest.json");
1422
+ const manifestPath = join9(configRoot, "cannbot-manifest.json");
1652
1423
  if (existsSync7(manifestPath)) {
1653
1424
  files.push(manifestPath);
1654
1425
  }
1655
- const pluginManifestPath = join7(configRoot, `${pluginId}-manifest.json`);
1426
+ const pluginManifestPath = join9(configRoot, `${pluginId}-manifest.json`);
1656
1427
  if (existsSync7(pluginManifestPath)) {
1657
1428
  files.push(pluginManifestPath);
1658
1429
  }
1659
1430
  const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1660
- const configFilePath = level === "project" ? join7(installPath, configFileName) : join7(configRoot, configFileName);
1431
+ const configFilePath = level === "project" ? join9(installPath, configFileName) : join9(configRoot, configFileName);
1661
1432
  if (existsSync7(configFilePath)) {
1662
1433
  files.push(configFilePath);
1663
1434
  }
1664
1435
  const repoLinks = ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
1665
1436
  for (const repoName of repoLinks) {
1666
- const repoLinkPath = join7(installPath, repoName);
1437
+ const repoLinkPath = join9(installPath, repoName);
1667
1438
  if (isSymlink(repoLinkPath)) {
1668
1439
  files.push(repoLinkPath);
1669
1440
  }
1670
- const repoLinkInConfig = join7(configRoot, repoName);
1441
+ const repoLinkInConfig = join9(configRoot, repoName);
1671
1442
  if (isSymlink(repoLinkInConfig)) {
1672
1443
  files.push(repoLinkInConfig);
1673
1444
  }
1674
1445
  }
1675
- directories.push(configRoot);
1676
1446
  return {
1677
1447
  pluginId,
1678
1448
  displayName,
@@ -1693,7 +1463,7 @@ function isSymlink(path) {
1693
1463
  }
1694
1464
  }
1695
1465
  function getSkillRecordPath() {
1696
- return join7(getInstallsDir(), "skills.json");
1466
+ return join9(getInstallsDir(), "skills.json");
1697
1467
  }
1698
1468
  function readSkillRecord() {
1699
1469
  const recordPath = getSkillRecordPath();
@@ -1701,7 +1471,7 @@ function readSkillRecord() {
1701
1471
  return {};
1702
1472
  }
1703
1473
  try {
1704
- const content = readFileSync4(recordPath, "utf-8");
1474
+ const content = readFileSync7(recordPath, "utf-8");
1705
1475
  return JSON.parse(content);
1706
1476
  } catch {
1707
1477
  return {};
@@ -1749,12 +1519,18 @@ function removeSkillsFromRecord(skillIds, tool, level, installPath) {
1749
1519
  }
1750
1520
  function getInstalledSkills(tool, level, installPath) {
1751
1521
  const record = readSkillRecord();
1752
- return record[tool]?.[level]?.[installPath]?.skills || [];
1522
+ const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
1523
+ const configRoot = getConfigRoot(tool, level);
1524
+ const skillsDir = join9(configRoot, "skills");
1525
+ return recordedSkills.filter((skillId) => {
1526
+ const skillPath = join9(skillsDir, skillId);
1527
+ return existsSync7(skillPath) || isSymlink(skillPath);
1528
+ });
1753
1529
  }
1754
1530
 
1755
1531
  // src/core/backup.ts
1756
1532
  import { existsSync as existsSync8, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync2 } from "fs";
1757
- import { join as join8 } from "path";
1533
+ import { join as join10 } from "path";
1758
1534
  function getAgentsFileName(tool) {
1759
1535
  return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1760
1536
  }
@@ -1769,7 +1545,7 @@ function detectCurrentPlugin(configRoot, tool) {
1769
1545
  };
1770
1546
  }
1771
1547
  }
1772
- const agentsFile = join8(configRoot, getAgentsFileName(tool));
1548
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
1773
1549
  if (!existsSync8(agentsFile)) {
1774
1550
  return null;
1775
1551
  }
@@ -1788,13 +1564,13 @@ function detectCurrentPlugin(configRoot, tool) {
1788
1564
  return null;
1789
1565
  }
1790
1566
  function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
1791
- const agentsFile = join8(configRoot, getAgentsFileName(tool));
1567
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
1792
1568
  if (!existsSync8(agentsFile)) {
1793
1569
  return null;
1794
1570
  }
1795
1571
  const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
1796
1572
  const backupFileName = `${getAgentsFileName(tool)}.cannbot-backup.${fromPluginId}.${timestamp}`;
1797
- const backupPath = join8(configRoot, backupFileName);
1573
+ const backupPath = join10(configRoot, backupFileName);
1798
1574
  try {
1799
1575
  copyFileSync(agentsFile, backupPath);
1800
1576
  return {
@@ -1824,7 +1600,7 @@ function findBackups(configRoot) {
1824
1600
  const timestamp = pluginIdAndTime.substring(lastDotIndex + 1);
1825
1601
  const plugin = findPlugin(pluginId);
1826
1602
  backups.push({
1827
- filePath: join8(configRoot, file),
1603
+ filePath: join10(configRoot, file),
1828
1604
  pluginId,
1829
1605
  pluginName: plugin?.displayName || pluginId,
1830
1606
  backupTime: timestamp
@@ -1837,7 +1613,7 @@ function findBackups(configRoot) {
1837
1613
  }
1838
1614
  function restoreBackup(backupPath, configRoot, tool) {
1839
1615
  try {
1840
- const agentsFile = join8(configRoot, getAgentsFileName(tool));
1616
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
1841
1617
  copyFileSync(backupPath, agentsFile);
1842
1618
  return true;
1843
1619
  } catch {
@@ -1938,7 +1714,7 @@ async function installPlugin(opts) {
1938
1714
  warnings: []
1939
1715
  };
1940
1716
  }
1941
- const scriptPath = join9(opts.repoPath, plugin.dir, plugin.script);
1717
+ const scriptPath = join11(opts.repoPath, plugin.dir, plugin.script);
1942
1718
  if (!existsSync9(scriptPath)) {
1943
1719
  return {
1944
1720
  success: false,
@@ -1955,7 +1731,7 @@ async function installPlugin(opts) {
1955
1731
  }
1956
1732
  const cwd = opts.installPath || process.cwd();
1957
1733
  const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
1958
- const agentsFile = join9(configRoot, getAgentsFileName(opts.tool));
1734
+ const agentsFile = join11(configRoot, getAgentsFileName(opts.tool));
1959
1735
  let backupInfo = null;
1960
1736
  if (existsSync9(agentsFile)) {
1961
1737
  const currentPlugin = detectCurrentPlugin(configRoot, opts.tool);
@@ -2051,13 +1827,13 @@ async function installPlugin(opts) {
2051
1827
 
2052
1828
  // src/core/skill-installer.ts
2053
1829
  import { existsSync as existsSync10, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync3, readdirSync as readdirSync4, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
2054
- import { join as join10 } from "path";
1830
+ import { join as join12 } from "path";
2055
1831
  import { select as select3, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
2056
1832
  import chalk4 from "chalk";
2057
1833
  import Table from "cli-table3";
2058
1834
  async function installSkills(skillIds, tool, level, repoPath) {
2059
1835
  const configRoot = getConfigRoot(tool, level);
2060
- const skillsDir = join10(configRoot, "skills");
1836
+ const skillsDir = join12(configRoot, "skills");
2061
1837
  const installPath = level === "project" ? process.cwd() : configRoot;
2062
1838
  if (!existsSync10(skillsDir)) {
2063
1839
  mkdirSync5(skillsDir, { recursive: true });
@@ -2070,8 +1846,8 @@ async function installSkills(skillIds, tool, level, repoPath) {
2070
1846
  results.push({ skillId, success: false, error: "Skill not found in registry" });
2071
1847
  continue;
2072
1848
  }
2073
- const sourcePath = join10(repoPath, skill.source, skill.id);
2074
- const targetPath = join10(skillsDir, skillId);
1849
+ const sourcePath = join12(repoPath, skill.source, skill.id);
1850
+ const targetPath = join12(skillsDir, skillId);
2075
1851
  if (!existsSync10(sourcePath)) {
2076
1852
  results.push({ skillId, success: false, error: `Source not found: ${sourcePath}` });
2077
1853
  continue;
@@ -2094,21 +1870,21 @@ async function installSkills(skillIds, tool, level, repoPath) {
2094
1870
  }
2095
1871
  async function uninstallSkills(skillIds, tool, level) {
2096
1872
  const configRoot = getConfigRoot(tool, level);
2097
- const skillsDir = join10(configRoot, "skills");
1873
+ const skillsDir = join12(configRoot, "skills");
2098
1874
  const installPath = level === "project" ? process.cwd() : configRoot;
2099
1875
  const results = [];
2100
1876
  const removedIds = [];
2101
1877
  for (const skillId of skillIds) {
2102
- const targetPath = join10(skillsDir, skillId);
1878
+ const targetPath = join12(skillsDir, skillId);
2103
1879
  try {
2104
1880
  if (existsSync10(targetPath) || isSymlink2(targetPath)) {
2105
1881
  unlinkSync3(targetPath);
2106
1882
  results.push({ skillId, success: true });
2107
1883
  removedIds.push(skillId);
2108
- logger.step(` \u79FB\u9664: ${skillId}`);
1884
+ logger.step(` ${t("uninstall_remove_label")}: ${skillId}`);
2109
1885
  } else {
2110
1886
  results.push({ skillId, success: false, error: "Not installed" });
2111
- logger.warn(`${skillId} \u672A\u5B89\u88C5`);
1887
+ logger.warn(`${skillId} ${t("uninstall_skill_not_installed")}`);
2112
1888
  }
2113
1889
  } catch (error) {
2114
1890
  results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
@@ -2122,24 +1898,14 @@ async function uninstallSkills(skillIds, tool, level) {
2122
1898
  const entries = readdirSync4(skillsDir);
2123
1899
  if (entries.length === 0) {
2124
1900
  rmdirSync(skillsDir);
2125
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: skills/`);
2126
- }
2127
- } catch {
2128
- }
2129
- }
2130
- if (existsSync10(configRoot)) {
2131
- try {
2132
- const entries = readdirSync4(configRoot);
2133
- if (entries.length === 0) {
2134
- rmdirSync(configRoot);
2135
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${configRoot.split("/").pop()}/`);
1901
+ logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
2136
1902
  }
2137
1903
  } catch {
2138
1904
  }
2139
1905
  }
2140
1906
  return results;
2141
1907
  }
2142
- async function interactiveSkillSelect() {
1908
+ async function interactiveSkillSelect(tool, level) {
2143
1909
  const BACK3 = "__back__";
2144
1910
  const CANCEL3 = "__cancel__";
2145
1911
  const categories = getAllCategories();
@@ -2175,15 +1941,8 @@ async function interactiveSkillSelect() {
2175
1941
  const category = categories.find((c) => c.id === selectedCategoryId);
2176
1942
  if (!category) return [];
2177
1943
  printBoxTitle(t("wizard_step_4_skill_title").replace("{category}", category.name));
2178
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
2179
- const levels = ["project", "global"];
2180
- const installedSkills = [];
2181
- for (const tk of tools) {
2182
- for (const l of levels) {
2183
- const path = l === "project" ? process.cwd() : getConfigRoot(tk, l);
2184
- installedSkills.push(...getInstalledSkills(tk, l, path));
2185
- }
2186
- }
1944
+ const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
1945
+ const installedSkills = getInstalledSkills(tool, level, installPath);
2187
1946
  const installedSet = new Set(installedSkills);
2188
1947
  const skillChoices = category.skills.map((skill) => {
2189
1948
  const isInstalled = installedSet.has(skill.id);
@@ -2248,7 +2007,7 @@ function listAllSkills() {
2248
2007
  console.log(table.toString());
2249
2008
  }
2250
2009
  console.log();
2251
- console.log(chalk4.dim(` \u5171 ${getAllSkills().length} \u4E2A Skills`));
2010
+ console.log(chalk4.dim(` ${t("skill_total_count").replace("{count}", String(getAllSkills().length))}`));
2252
2011
  console.log();
2253
2012
  }
2254
2013
  function isSymlink2(path) {
@@ -2285,7 +2044,8 @@ function printPluginList(plugins, installed) {
2285
2044
  ]);
2286
2045
  });
2287
2046
  console.log();
2288
- console.log(chalk5.bold(` ${t("list_title")} (${plugins.length} \u4E2A)`));
2047
+ const countSuffix = t("count_suffix");
2048
+ console.log(chalk5.bold(` ${t("list_title")} (${plugins.length}${countSuffix ? " " + countSuffix : ""})`));
2289
2049
  console.log(table.toString());
2290
2050
  console.log();
2291
2051
  }
@@ -2318,22 +2078,25 @@ function printEnhancedSummary(results, tool, configRoot) {
2318
2078
  return;
2319
2079
  }
2320
2080
  const toolName = getToolDisplayName(tool);
2081
+ const countSuffix = t("count_suffix");
2321
2082
  const lines = [
2322
2083
  chalk5.bold(` ${t("install_done")}!`),
2323
2084
  "",
2324
2085
  ` ${chalk5.dim(t("install_installed_to") + ":")} ${configRoot}`,
2325
- ` ${chalk5.dim(t("install_skills_count") + ":")} ${totalSkills} \u4E2A ${chalk5.dim("|")} ${chalk5.dim(t("install_agents_count") + ":")} ${totalAgents} \u4E2A`,
2086
+ ` ${chalk5.dim(t("install_skills_count") + ":")} ${totalSkills}${countSuffix ? " " + countSuffix : ""} ${chalk5.dim("|")} ${chalk5.dim(t("install_agents_count") + ":")} ${totalAgents}${countSuffix ? " " + countSuffix : ""}`,
2326
2087
  "",
2327
2088
  chalk5.bold(` ${t("install_enhanced_next_steps")}:`),
2328
2089
  ` ${chalk5.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk5.green(toolName.toLowerCase())}`,
2329
- ` ${chalk5.cyan("2.")} ${t("install_enhanced_try")}: ${chalk5.green("\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50")}`,
2090
+ ` ${chalk5.cyan("2.")} ${t("install_enhanced_try")}: ${chalk5.green(t("try_prompt"))}`,
2330
2091
  ` ${chalk5.cyan("3.")} ${t("install_enhanced_more")}: ${chalk5.green("install-helper list")}`,
2331
2092
  ` ${chalk5.cyan("4.")} ${t("install_enhanced_check")}: ${chalk5.green("install-helper doctor")}`,
2332
2093
  ""
2333
2094
  ];
2334
2095
  if (successResults.length === 1) {
2096
+ const plugin = findPlugin(successResults[0].pluginId);
2097
+ const docsPath = plugin ? `${plugin.dir}/quickstart.md` : `${successResults[0].pluginId}/quickstart.md`;
2335
2098
  lines.push(
2336
- ` ${chalk5.dim(t("install_enhanced_docs"))}: ${chalk5.dim(`plugins-official/${successResults[0].pluginId}/quickstart.md`)}`
2099
+ ` ${chalk5.dim(t("install_enhanced_docs"))}: ${chalk5.dim(docsPath)}`
2337
2100
  );
2338
2101
  }
2339
2102
  console.log();
@@ -2344,15 +2107,15 @@ function printEnhancedSummary(results, tool, configRoot) {
2344
2107
  }
2345
2108
 
2346
2109
  // src/commands/init.ts
2347
- import { existsSync as existsSync11, readFileSync as readFileSync5 } from "fs";
2348
- import { join as join11 } from "path";
2110
+ import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
2111
+ import { join as join13 } from "path";
2349
2112
  var BACK2 = "__back__";
2350
2113
  var CANCEL2 = "__cancel__";
2351
2114
  async function initCommand() {
2352
2115
  const cwd = process.cwd();
2353
- if (existsSync11(join11(cwd, "package.json")) && (() => {
2116
+ if (existsSync11(join13(cwd, "package.json")) && (() => {
2354
2117
  try {
2355
- const pkg = JSON.parse(readFileSync5(join11(cwd, "package.json"), "utf-8"));
2118
+ const pkg = JSON.parse(readFileSync8(join13(cwd, "package.json"), "utf-8"));
2356
2119
  return pkg.name === "@cannbot-ai/install-helper";
2357
2120
  } catch {
2358
2121
  return false;
@@ -2516,7 +2279,7 @@ async function skillInstallFlow() {
2516
2279
  break;
2517
2280
  }
2518
2281
  case 2: {
2519
- const selectedSkills = await interactiveSkillSelect();
2282
+ const selectedSkills = await interactiveSkillSelect(tool, level);
2520
2283
  if (selectedSkills === "back") {
2521
2284
  step = 1;
2522
2285
  break;
@@ -2534,7 +2297,7 @@ async function skillInstallFlow() {
2534
2297
  spinner.start();
2535
2298
  const repoPath = await repoManager.ensureRepo();
2536
2299
  spinner.succeed(t("loading_plugin_data_complete"));
2537
- logger.info(`${t("skill_install_progress")} ${chalk6.bold(selectedSkills.length)} \u4E2A Skills...`);
2300
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk6.bold(String(selectedSkills.length)))}...`);
2538
2301
  const results = await installSkills(selectedSkills, tool, level, repoPath);
2539
2302
  let successCount = 0;
2540
2303
  let failCount = 0;
@@ -2552,7 +2315,7 @@ async function skillInstallFlow() {
2552
2315
  }
2553
2316
  const configRoot = getConfigRoot(tool, level);
2554
2317
  logger.blank();
2555
- logger.success(`${t("skill_install_done")}: ${chalk6.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk6.red(failCount + " \u5931\u8D25") : chalk6.dim(failCount + " \u5931\u8D25")}`);
2318
+ 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)))}`);
2556
2319
  logger.blank();
2557
2320
  logger.info(`${t("install_to")}: ${chalk6.cyan(configRoot + "/skills/")}`);
2558
2321
  logger.info(`${t("start_to_use").replace("{tool}", chalk6.green(tool))}`);
@@ -2579,7 +2342,7 @@ async function listCommand() {
2579
2342
  // src/commands/doctor.ts
2580
2343
  import chalk7 from "chalk";
2581
2344
  import { existsSync as existsSync12, readdirSync as readdirSync5, lstatSync as lstatSync3, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6 } from "fs";
2582
- import { join as join12 } from "path";
2345
+ import { join as join14 } from "path";
2583
2346
  async function doctorCommand(options = {}) {
2584
2347
  console.log();
2585
2348
  console.log(chalk7.bold(` ${t("doctor_title")}`));
@@ -2598,7 +2361,7 @@ async function doctorCommand(options = {}) {
2598
2361
  chalk7.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
2599
2362
  );
2600
2363
  } else {
2601
- console.log(chalk7.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 \u672A\u5B89\u88C5`);
2364
+ console.log(chalk7.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 ${t("doctor_not_installed")}`);
2602
2365
  }
2603
2366
  }
2604
2367
  console.log();
@@ -2613,7 +2376,7 @@ async function doctorCommand(options = {}) {
2613
2376
  chalk7.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
2614
2377
  );
2615
2378
  } else {
2616
- console.log(chalk7.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 \u672A\u5B89\u88C5`);
2379
+ console.log(chalk7.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 ${t("doctor_not_installed")}`);
2617
2380
  }
2618
2381
  }
2619
2382
  console.log();
@@ -2626,64 +2389,64 @@ async function doctorCommand(options = {}) {
2626
2389
  const brokenLinks = checkBrokenLinks(skillsDir);
2627
2390
  if (brokenLinks === 0) {
2628
2391
  const count = readdirSync5(skillsDir).length;
2629
- console.log(chalk7.green(" \u2713") + ` ${skillsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
2392
+ console.log(chalk7.green(" \u2713") + ` ${skillsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2630
2393
  } else {
2631
- console.log(chalk7.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
2394
+ console.log(chalk7.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2632
2395
  warnings++;
2633
2396
  if (options.fix) {
2634
2397
  const fixed = fixBrokenLinks(skillsDir);
2635
2398
  fixes += fixed;
2636
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
2399
+ console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2637
2400
  }
2638
2401
  }
2639
2402
  } else {
2640
- console.log(chalk7.dim(" \u2014") + ` ${skillsDir} \u2014 \u4E0D\u5B58\u5728`);
2403
+ console.log(chalk7.dim(" \u2014") + ` ${skillsDir} \u2014 ${t("doctor_not_exist")}`);
2641
2404
  if (options.fix) {
2642
2405
  mkdirSync6(skillsDir, { recursive: true });
2643
2406
  fixes++;
2644
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${skillsDir}`);
2407
+ console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
2645
2408
  }
2646
2409
  }
2647
2410
  if (existsSync12(agentsDir)) {
2648
2411
  const brokenLinks = checkBrokenLinks(agentsDir);
2649
2412
  if (brokenLinks === 0) {
2650
2413
  const count = readdirSync5(agentsDir).length;
2651
- console.log(chalk7.green(" \u2713") + ` ${agentsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
2414
+ console.log(chalk7.green(" \u2713") + ` ${agentsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2652
2415
  } else {
2653
- console.log(chalk7.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
2416
+ console.log(chalk7.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2654
2417
  warnings++;
2655
2418
  if (options.fix) {
2656
2419
  const fixed = fixBrokenLinks(agentsDir);
2657
2420
  fixes += fixed;
2658
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
2421
+ console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2659
2422
  }
2660
2423
  }
2661
2424
  } else {
2662
- console.log(chalk7.dim(" \u2014") + ` ${agentsDir} \u2014 \u4E0D\u5B58\u5728`);
2425
+ console.log(chalk7.dim(" \u2014") + ` ${agentsDir} \u2014 ${t("doctor_not_exist")}`);
2663
2426
  if (options.fix) {
2664
2427
  mkdirSync6(agentsDir, { recursive: true });
2665
2428
  fixes++;
2666
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${agentsDir}`);
2429
+ console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", agentsDir)}`);
2667
2430
  }
2668
2431
  }
2669
2432
  console.log();
2670
2433
  console.log(chalk7.bold(` ${t("doctor_config")}`));
2671
2434
  const configFile = getConfigFileName(primaryTool);
2672
- const configPath = join12(process.cwd(), configFile);
2435
+ const configPath = join14(process.cwd(), configFile);
2673
2436
  if (existsSync12(configPath)) {
2674
- console.log(chalk7.green(" \u2713") + ` ${configFile} \u5B58\u5728`);
2437
+ console.log(chalk7.green(" \u2713") + ` ${configFile} ${t("doctor_config_exists")}`);
2675
2438
  } else {
2676
- console.log(chalk7.dim(" \u2014") + ` ${configFile} \u4E0D\u5B58\u5728`);
2439
+ console.log(chalk7.dim(" \u2014") + ` ${configFile} ${t("doctor_config_not_exist")}`);
2677
2440
  }
2678
2441
  console.log();
2679
2442
  console.log(chalk7.dim(" " + "\u2500".repeat(46)));
2680
2443
  if (options.fix && fixes > 0) {
2681
2444
  console.log(
2682
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF, ${fixes} \u9879\u5DF2\u4FEE\u590D`
2445
+ ` ${t("doctor_result")}: ${t("doctor_result_with_fix").replace("{warnings}", String(warnings)).replace("{errors}", String(errors)).replace("{fixes}", String(fixes))}`
2683
2446
  );
2684
2447
  } else {
2685
2448
  console.log(
2686
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF`
2449
+ ` ${t("doctor_result")}: ${t("doctor_result_without_fix").replace("{warnings}", String(warnings)).replace("{errors}", String(errors))}`
2687
2450
  );
2688
2451
  }
2689
2452
  console.log();
@@ -2693,7 +2456,7 @@ function checkBrokenLinks(dir) {
2693
2456
  try {
2694
2457
  const entries = readdirSync5(dir);
2695
2458
  for (const entry of entries) {
2696
- const fullPath = join12(dir, entry);
2459
+ const fullPath = join14(dir, entry);
2697
2460
  try {
2698
2461
  const stats = lstatSync3(fullPath);
2699
2462
  if (stats.isSymbolicLink()) {
@@ -2714,7 +2477,7 @@ function fixBrokenLinks(dir) {
2714
2477
  try {
2715
2478
  const entries = readdirSync5(dir);
2716
2479
  for (const entry of entries) {
2717
- const fullPath = join12(dir, entry);
2480
+ const fullPath = join14(dir, entry);
2718
2481
  try {
2719
2482
  const stats = lstatSync3(fullPath);
2720
2483
  if (stats.isSymbolicLink()) {
@@ -2778,29 +2541,31 @@ async function installCommand(names, options) {
2778
2541
  try {
2779
2542
  const scanRepoManager = createRepositoryManager();
2780
2543
  await scanRepoManager.ensureRepoAndScan();
2781
- scanSpinner2.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
2544
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
2782
2545
  } catch {
2783
- scanSpinner2.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
2546
+ scanSpinner2.warn(t("loading_skills_list_failed"));
2784
2547
  }
2785
2548
  let tool2;
2786
2549
  if (options.tool) {
2787
- tool2 = options.tool;
2550
+ tool2 = validateTool(options.tool);
2788
2551
  } else {
2789
2552
  const detected = await selectToolWithDetection();
2790
- if (detected === "back") return;
2553
+ if (detected === "back" || detected === "cancel") return;
2791
2554
  tool2 = detected;
2792
2555
  }
2556
+ const level2 = options.level ? validateLevel(options.level) : "project";
2793
2557
  let step = 1;
2794
2558
  while (true) {
2795
2559
  switch (step) {
2796
2560
  case 1: {
2797
- const selectedSkills = await interactiveSkillSelect();
2561
+ const selectedSkills = await interactiveSkillSelect(tool2, level2);
2798
2562
  if (selectedSkills === "back") {
2799
2563
  const redetected = await selectToolWithDetection();
2800
- if (redetected === "back") return;
2564
+ if (redetected === "back" || redetected === "cancel") return;
2801
2565
  tool2 = redetected;
2802
2566
  break;
2803
2567
  }
2568
+ if (selectedSkills === "cancel") return;
2804
2569
  if (selectedSkills.length === 0) {
2805
2570
  logger.info(t("install_no_skill_selected"));
2806
2571
  return;
@@ -2810,9 +2575,8 @@ async function installCommand(names, options) {
2810
2575
  const spinner = createSpinner(t("loading_plugin_data"));
2811
2576
  spinner.start();
2812
2577
  const repoPath = await repoManager.ensureRepo();
2813
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2814
- logger.info(`${t("skill_install_progress")} ${chalk9.bold(names.length)} \u4E2A Skills...`);
2815
- const level2 = options.level || "project";
2578
+ spinner.succeed(t("loading_plugin_data_complete"));
2579
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk9.bold(String(names.length)))}...`);
2816
2580
  const results = await installSkills(names, tool2, level2, repoPath);
2817
2581
  let successCount = 0;
2818
2582
  let failCount = 0;
@@ -2830,7 +2594,7 @@ async function installCommand(names, options) {
2830
2594
  }
2831
2595
  const configRoot = getConfigRoot(tool2, level2);
2832
2596
  logger.blank();
2833
- logger.success(`${t("skill_install_done")}: ${chalk9.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk9.red(failCount + " \u5931\u8D25") : chalk9.dim(failCount + " \u5931\u8D25")}`);
2597
+ 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)))}`);
2834
2598
  logger.blank();
2835
2599
  logger.info(`${t("install_to")}: ${chalk9.cyan(configRoot + "/skills/")}`);
2836
2600
  logger.info(`${t("start_to_use").replace("{tool}", chalk9.green(tool2))}`);
@@ -2845,9 +2609,9 @@ async function installCommand(names, options) {
2845
2609
  try {
2846
2610
  const scanRepoManager = createRepositoryManager();
2847
2611
  await scanRepoManager.ensureRepoAndScan();
2848
- scanSpinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
2612
+ scanSpinner.succeed(t("loading_skills_list_complete"));
2849
2613
  } catch {
2850
- scanSpinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
2614
+ scanSpinner.warn(t("loading_skills_list_failed"));
2851
2615
  }
2852
2616
  const plugins = [];
2853
2617
  const skills = [];
@@ -2864,22 +2628,22 @@ async function installCommand(names, options) {
2864
2628
  }
2865
2629
  logger.error(`${t("error_plugin_not_found")}: ${name}`);
2866
2630
  const allPlugins = getAllPlugins();
2867
- logger.info("\u53EF\u7528\u63D2\u4EF6:");
2631
+ logger.info(t("available_plugins") + ":");
2868
2632
  for (const p of allPlugins) {
2869
2633
  logger.step(` ${p.id} (${p.aliases.join(", ")})`);
2870
2634
  }
2871
- logger.info(`\u53EF\u7528 Skills: ${chalk9.cyan("install-helper install --list")}`);
2635
+ logger.info(`${t("available_skills")}: ${chalk9.cyan("install-helper install --list")}`);
2872
2636
  return;
2873
2637
  }
2874
2638
  let tool;
2875
2639
  if (options.tool) {
2876
- tool = options.tool;
2640
+ tool = validateTool(options.tool);
2877
2641
  } else {
2878
2642
  const detected = await selectToolWithDetection();
2879
- if (detected === "back") return;
2643
+ if (detected === "back" || detected === "cancel") return;
2880
2644
  tool = detected;
2881
2645
  }
2882
- const level = options.level || "project";
2646
+ const level = options.level ? validateLevel(options.level) : "project";
2883
2647
  if (plugins.length > 0) {
2884
2648
  const configRoot = getConfigRoot(tool, level);
2885
2649
  const manifests = readAllManifests(configRoot);
@@ -2940,7 +2704,7 @@ async function installCommand(names, options) {
2940
2704
  spinner.start();
2941
2705
  const repoPath = await repoManager.ensureRepo();
2942
2706
  spinner.succeed(t("loading_plugin_data_complete"));
2943
- logger.info(`${t("skill_install_progress")} ${chalk9.bold(skills.length)} \u4E2A Skills...`);
2707
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk9.bold(String(skills.length)))}...`);
2944
2708
  const results = await installSkills(skills, tool, level, repoPath);
2945
2709
  let successCount = 0;
2946
2710
  let failCount = 0;
@@ -2957,7 +2721,7 @@ async function installCommand(names, options) {
2957
2721
  }
2958
2722
  }
2959
2723
  logger.blank();
2960
- logger.success(`${t("skill_install_done")}: ${chalk9.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk9.red(failCount + " \u5931\u8D25") : chalk9.dim(failCount + " \u5931\u8D25")}`);
2724
+ 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)))}`);
2961
2725
  logger.blank();
2962
2726
  logger.info(`${t("install_to")}: ${chalk9.cyan(getConfigRoot(tool, level) + "/skills/")}`);
2963
2727
  logger.info(`${t("start_to_use").replace("{tool}", chalk9.green(tool))}`);
@@ -2995,8 +2759,8 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
2995
2759
  import { existsSync as existsSync13, unlinkSync as unlinkSync5, rmdirSync as rmdirSync2, readdirSync as readdirSync6, lstatSync as lstatSync4 } from "fs";
2996
2760
  import chalk10 from "chalk";
2997
2761
  async function uninstallCommand(names, options) {
2998
- const tool = options.tool || "opencode";
2999
- const level = options.level || "project";
2762
+ const tool = options.tool ? validateTool(options.tool) : "opencode";
2763
+ const level = options.level ? validateLevel(options.level) : "project";
3000
2764
  const plugins = [];
3001
2765
  const skills = [];
3002
2766
  for (const name of names) {
@@ -3029,21 +2793,25 @@ async function uninstallCommand(names, options) {
3029
2793
  if (existsSync13(filePath) || isSymlink3(filePath)) {
3030
2794
  unlinkSync5(filePath);
3031
2795
  const name = filePath.split("/").pop() || filePath;
3032
- logger.step(` \u79FB\u9664: ${name}`);
2796
+ logger.step(` ${t("uninstall_remove_label")}: ${name}`);
3033
2797
  removedFiles++;
3034
2798
  }
3035
2799
  } catch {
3036
2800
  }
3037
2801
  }
2802
+ const configRoot = getConfigRoot(record.tool, record.level);
3038
2803
  const sortedDirs = [...record.directories].sort((a, b) => b.length - a.length);
3039
2804
  for (const dirPath of sortedDirs) {
3040
2805
  try {
2806
+ if (dirPath === configRoot) {
2807
+ continue;
2808
+ }
3041
2809
  if (existsSync13(dirPath)) {
3042
2810
  const entries = readdirSync6(dirPath);
3043
2811
  if (entries.length === 0) {
3044
2812
  rmdirSync2(dirPath);
3045
2813
  const name = dirPath.split("/").pop() || dirPath;
3046
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${name}/`);
2814
+ logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
3047
2815
  removedDirs++;
3048
2816
  }
3049
2817
  }
@@ -3052,7 +2820,6 @@ async function uninstallCommand(names, options) {
3052
2820
  }
3053
2821
  deleteRecord(plugin.id);
3054
2822
  removeInstalledPlugin(plugin.id);
3055
- const configRoot = getConfigRoot(record.tool, record.level);
3056
2823
  const backups = findBackups(configRoot);
3057
2824
  const otherBackups = backups.filter((b) => b.pluginId !== pluginId);
3058
2825
  if (otherBackups.length > 0) {
@@ -3072,13 +2839,13 @@ async function uninstallCommand(names, options) {
3072
2839
  }
3073
2840
  const totalRemoved = removedFiles + removedDirs;
3074
2841
  if (totalRemoved > 0) {
3075
- logger.success(`${t("uninstall_removed")} ${plugin.displayName}\uFF08\u79FB\u9664 ${removedFiles} \u4E2A\u6587\u4EF6\uFF0C${removedDirs} \u4E2A\u76EE\u5F55\uFF09`);
2842
+ 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`);
3076
2843
  } else {
3077
2844
  logger.warn(`${plugin.displayName}${t("uninstall_files_gone")}`);
3078
2845
  }
3079
2846
  }
3080
2847
  if (skills.length > 0) {
3081
- logger.info(`${t("uninstall_skills_in_progress")} ${skills.length} \u4E2A Skills...`);
2848
+ logger.info(`${t("uninstall_skills_in_progress")} ${t("skill_count_format").replace("{count}", String(skills.length))}...`);
3082
2849
  const results = await uninstallSkills(skills, tool, level);
3083
2850
  let successCount = 0;
3084
2851
  let failCount = 0;
@@ -3090,7 +2857,7 @@ async function uninstallCommand(names, options) {
3090
2857
  }
3091
2858
  }
3092
2859
  logger.blank();
3093
- logger.success(`${t("skill_uninstall_done")}: ${chalk10.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk10.red(failCount + " \u5931\u8D25") : chalk10.dim(failCount + " \u5931\u8D25")}`);
2860
+ 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)))}`);
3094
2861
  logger.blank();
3095
2862
  }
3096
2863
  }
@@ -3107,13 +2874,13 @@ import chalk11 from "chalk";
3107
2874
  async function updateCommand(pluginNames, options) {
3108
2875
  let tool;
3109
2876
  if (options.tool) {
3110
- tool = options.tool;
2877
+ tool = validateTool(options.tool);
3111
2878
  } else {
3112
2879
  const detected = await selectToolWithDetection();
3113
- if (detected === "back") return;
2880
+ if (detected === "back" || detected === "cancel") return;
3114
2881
  tool = detected;
3115
2882
  }
3116
- const level = options.level || "project";
2883
+ const level = options.level ? validateLevel(options.level) : "project";
3117
2884
  let pluginsToUpdate = [];
3118
2885
  if (pluginNames.length === 0) {
3119
2886
  const configRoot = getConfigRoot(tool, level);
@@ -3121,7 +2888,7 @@ async function updateCommand(pluginNames, options) {
3121
2888
  pluginsToUpdate = manifests.map((m) => m.team);
3122
2889
  if (pluginsToUpdate.length === 0) {
3123
2890
  logger.info(t("update_no_plugins"));
3124
- logger.info(`\u4F7F\u7528 ${chalk11.cyan("install-helper install <plugin>")} \u5B89\u88C5\u63D2\u4EF6`);
2891
+ logger.info(t("update_install_hint").replace("{cmd}", chalk11.cyan("install-helper install <plugin>")));
3125
2892
  return;
3126
2893
  }
3127
2894
  } else {
@@ -3185,7 +2952,7 @@ async function updateCommand(pluginNames, options) {
3185
2952
  // src/commands/info.ts
3186
2953
  import chalk12 from "chalk";
3187
2954
  import { existsSync as existsSync14, readdirSync as readdirSync7 } from "fs";
3188
- import { join as join13 } from "path";
2955
+ import { join as join15 } from "path";
3189
2956
  async function infoCommand(pluginName) {
3190
2957
  const plugin = findPlugin(pluginName);
3191
2958
  if (!plugin) {
@@ -3209,11 +2976,11 @@ async function infoCommand(pluginName) {
3209
2976
  try {
3210
2977
  const repoManager = createRepositoryManager();
3211
2978
  const repoPath = await repoManager.ensureRepo();
3212
- const quickstartPath = join13(repoPath, plugin.dir, "quickstart.md");
2979
+ const quickstartPath = join15(repoPath, plugin.dir, "quickstart.md");
3213
2980
  if (existsSync14(quickstartPath)) {
3214
2981
  console.log(` ${chalk12.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
3215
2982
  }
3216
- const agentsDir = join13(repoPath, plugin.dir, "agents");
2983
+ const agentsDir = join15(repoPath, plugin.dir, "agents");
3217
2984
  if (existsSync14(agentsDir)) {
3218
2985
  const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md"));
3219
2986
  if (agentFiles.length > 0) {
@@ -3272,7 +3039,7 @@ function createCLI() {
3272
3039
  setLanguage(config.language);
3273
3040
  }
3274
3041
  const program2 = new Command();
3275
- program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.9");
3042
+ program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("1.0.3");
3276
3043
  program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
3277
3044
  await initCommand();
3278
3045
  });
@@ -3309,7 +3076,7 @@ function createCLI() {
3309
3076
  // src/index.ts
3310
3077
  process.on("uncaughtException", (error) => {
3311
3078
  if (error.name === "ExitPromptError") {
3312
- console.log("\n\u5DF2\u53D6\u6D88\u5B89\u88C5");
3079
+ console.log("\n" + t("init_cancelled"));
3313
3080
  process.exit(0);
3314
3081
  }
3315
3082
  throw error;