@cannbot-ai/install-helper 0.0.2-beta.3 → 0.0.2-beta.31

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
@@ -4,116 +4,104 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
- import { select as select3, Separator as Separator3 } from "@inquirer/prompts";
8
- import chalk5 from "chalk";
7
+ import { select as select4, Separator as Separator3 } from "@inquirer/prompts";
8
+ import chalk7 from "chalk";
9
9
 
10
10
  // src/ui/wizard.ts
11
11
  import { select, checkbox, Separator } from "@inquirer/prompts";
12
- import chalk2 from "chalk";
12
+ import chalk3 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, existsSync, statSync } 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
+ plugins.sort(
52
+ (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
53
+ );
54
+ return plugins;
55
+ }
56
+ var PLUGIN_REGISTRY = loadPluginsFromYaml();
57
+ var PLUGIN_DIRS = ["plugins-official", "plugins-community"];
58
+ function mergeDynamicPlugins(repoPath) {
59
+ if (!existsSync(repoPath)) return;
60
+ const existingIds = new Set(PLUGIN_REGISTRY.map((p) => p.id));
61
+ const dynamic = [];
62
+ for (const pluginDir of PLUGIN_DIRS) {
63
+ const parent = join(repoPath, pluginDir);
64
+ if (!existsSync(parent)) continue;
65
+ let entries = [];
66
+ try {
67
+ entries = readdirSync(parent);
68
+ } catch {
69
+ continue;
70
+ }
71
+ for (const entry of entries) {
72
+ const pluginPath = join(parent, entry);
73
+ if (!isDirectory(pluginPath)) continue;
74
+ if (entry === "install-helper") continue;
75
+ const initSh = join(pluginPath, "init.sh");
76
+ if (!existsSync(initSh)) continue;
77
+ const pluginId = entry;
78
+ if (existingIds.has(pluginId)) continue;
79
+ dynamic.push({
80
+ id: pluginId,
81
+ dir: `${pluginDir}/${entry}`,
82
+ displayName: entry,
83
+ script: "init.sh",
84
+ aliases: [],
85
+ skills: 0,
86
+ agents: 0,
87
+ description: ""
88
+ });
89
+ existingIds.add(pluginId);
90
+ }
91
+ }
92
+ if (dynamic.length > 0) {
93
+ PLUGIN_REGISTRY = [...PLUGIN_REGISTRY, ...dynamic].sort(
94
+ (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
95
+ );
96
+ }
97
+ }
98
+ function isDirectory(path) {
99
+ try {
100
+ return statSync(path).isDirectory();
101
+ } catch {
102
+ return false;
103
+ }
104
+ }
117
105
  function findPlugin(query) {
118
106
  const normalized = query.toLowerCase().trim();
119
107
  const exactMatch = PLUGIN_REGISTRY.find(
@@ -137,19 +125,70 @@ function getPluginById(id) {
137
125
 
138
126
  // src/core/detector.ts
139
127
  import { execa } from "execa";
140
- import { existsSync as existsSync2 } from "fs";
128
+ import { existsSync as existsSync3 } from "fs";
141
129
  import { homedir as homedir2 } from "os";
142
- import { join as join2 } from "path";
130
+ import { join as join4 } from "path";
143
131
 
144
132
  // src/utils/paths.ts
145
133
  import { homedir } from "os";
146
- import { join } from "path";
147
- import { existsSync } from "fs";
134
+ import { join as join3 } from "path";
135
+ import { existsSync as existsSync2 } from "fs";
136
+
137
+ // src/utils/i18n.ts
138
+ import { readFileSync as readFileSync2 } from "fs";
139
+ import { join as join2, dirname as dirname2 } from "path";
140
+ import { fileURLToPath as fileURLToPath2 } from "url";
141
+ var __filename2 = fileURLToPath2(import.meta.url);
142
+ var __dirname2 = dirname2(__filename2);
143
+ function loadLocale(locale) {
144
+ const localePath = join2(__dirname2, "locales", `${locale}.json`);
145
+ try {
146
+ const content = readFileSync2(localePath, "utf-8");
147
+ return JSON.parse(content);
148
+ } catch {
149
+ const fallbackPath = join2(__dirname2, "locales", "zh_CN.json");
150
+ try {
151
+ const content = readFileSync2(fallbackPath, "utf-8");
152
+ return JSON.parse(content);
153
+ } catch {
154
+ return {};
155
+ }
156
+ }
157
+ }
158
+ var currentLang = "zh_CN";
159
+ var messages = loadLocale("zh_CN");
160
+ function setLanguage(lang) {
161
+ currentLang = lang;
162
+ messages = loadLocale(lang);
163
+ }
164
+ function t(key) {
165
+ return messages[key] !== void 0 ? messages[key] : key;
166
+ }
167
+
168
+ // src/utils/paths.ts
169
+ var VALID_TOOLS = ["opencode", "claude", "trae", "cursor", "copilot"];
170
+ var VALID_LEVELS = ["project", "global"];
171
+ function validateTool(tool) {
172
+ if (!VALID_TOOLS.includes(tool)) {
173
+ throw new Error(
174
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
175
+ );
176
+ }
177
+ return tool;
178
+ }
179
+ function validateLevel(level) {
180
+ if (!VALID_LEVELS.includes(level)) {
181
+ throw new Error(
182
+ t("error_invalid_level").replace("{level}", level).replace("{levels}", VALID_LEVELS.join(", "))
183
+ );
184
+ }
185
+ return level;
186
+ }
148
187
  function detectTraeVariant() {
149
188
  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";
189
+ if (existsSync2(join3(home, ".trae-cn"))) return "ide";
190
+ if (existsSync2(join3(home, ".marscode"))) return "plugin";
191
+ if (existsSync2(join3(home, ".traecli"))) return "cli";
153
192
  return "unknown";
154
193
  }
155
194
  function getConfigRoot(tool, level, base) {
@@ -157,66 +196,74 @@ function getConfigRoot(tool, level, base) {
157
196
  if (level === "global") {
158
197
  switch (tool) {
159
198
  case "opencode":
160
- return join(home, ".config", "opencode");
199
+ return join3(home, ".config", "opencode");
161
200
  case "claude":
162
- return join(home, ".claude");
201
+ return join3(home, ".claude");
163
202
  case "trae": {
164
203
  const variant = detectTraeVariant();
165
204
  switch (variant) {
166
205
  case "plugin":
167
- return join(home, ".marscode");
206
+ return join3(home, ".marscode");
168
207
  case "cli":
169
- return join(home, ".traecli");
208
+ return join3(home, ".traecli");
170
209
  default:
171
- return join(home, ".trae-cn");
210
+ return join3(home, ".trae-cn");
172
211
  }
173
212
  }
174
213
  case "cursor":
175
- return join(home, ".cursor");
214
+ return join3(home, ".cursor");
176
215
  case "copilot":
177
- return join(home, ".copilot");
216
+ return join3(home, ".copilot");
217
+ default:
218
+ throw new Error(
219
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
220
+ );
178
221
  }
179
222
  }
180
223
  const baseDir = base || process.cwd();
181
224
  switch (tool) {
182
225
  case "opencode":
183
- return join(baseDir, ".opencode");
226
+ return join3(baseDir, ".opencode");
184
227
  case "claude":
185
- return join(baseDir, ".claude");
228
+ return join3(baseDir, ".claude");
186
229
  case "trae": {
187
230
  const variant = detectTraeVariant();
188
231
  switch (variant) {
189
232
  case "plugin":
190
- return join(baseDir, ".marscode");
233
+ return join3(baseDir, ".marscode");
191
234
  case "cli":
192
- return join(baseDir, ".traecli");
235
+ return join3(baseDir, ".traecli");
193
236
  default:
194
- return join(baseDir, ".trae");
237
+ return join3(baseDir, ".trae");
195
238
  }
196
239
  }
197
240
  case "cursor":
198
- return join(baseDir, ".cursor");
241
+ return join3(baseDir, ".cursor");
199
242
  case "copilot":
200
- return join(baseDir, ".github");
243
+ return join3(baseDir, ".github");
244
+ default:
245
+ throw new Error(
246
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
247
+ );
201
248
  }
202
249
  }
203
250
  function getConfigFileName(tool) {
204
251
  return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
205
252
  }
206
253
  function getSkillsDir(configRoot) {
207
- return join(configRoot, "skills");
254
+ return join3(configRoot, "skills");
208
255
  }
209
256
  function getAgentsDir(configRoot) {
210
- return join(configRoot, "agents");
257
+ return join3(configRoot, "agents");
211
258
  }
212
259
  function getManifestPath(configRoot) {
213
- return join(configRoot, "cannbot-manifest.json");
260
+ return join3(configRoot, "cannbot-manifest.json");
214
261
  }
215
262
  function getCannbotConfigDir() {
216
- return join(homedir(), ".cannbot");
263
+ return join3(homedir(), ".cannbot");
217
264
  }
218
265
  function getCannbotRepoPath() {
219
- return join(getCannbotConfigDir(), "repo");
266
+ return join3(getCannbotConfigDir(), "repo");
220
267
  }
221
268
 
222
269
  // src/core/detector.ts
@@ -232,7 +279,7 @@ async function getCommandVersion(cmd, args = ["--version"]) {
232
279
  }
233
280
  async function getCommandPath(cmd) {
234
281
  try {
235
- const result = await execa("sh", ["-c", `command -v ${cmd}`], { timeout: 5e3 });
282
+ const result = await execa("which", [cmd], { timeout: 5e3 });
236
283
  return result.stdout.trim();
237
284
  } catch {
238
285
  return void 0;
@@ -254,12 +301,12 @@ async function detectTrae() {
254
301
  const home = homedir2();
255
302
  const variant = detectTraeVariant();
256
303
  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");
304
+ if (variant === "ide" && existsSync3(join4(home, ".trae-cn"))) {
305
+ path = join4(home, ".trae-cn");
306
+ } else if (variant === "plugin" && existsSync3(join4(home, ".marscode"))) {
307
+ path = join4(home, ".marscode");
308
+ } else if (variant === "cli" && existsSync3(join4(home, ".traecli"))) {
309
+ path = join4(home, ".traecli");
263
310
  } else if (variant === "unknown") {
264
311
  const cmdPath = await getCommandPath("trae");
265
312
  if (cmdPath) path = cmdPath;
@@ -275,7 +322,7 @@ async function detectCursor() {
275
322
  }
276
323
  if (process.platform === "darwin") {
277
324
  const appPath = "/Applications/Cursor.app";
278
- if (existsSync2(appPath)) {
325
+ if (existsSync3(appPath)) {
279
326
  return { name: "cursor", path: appPath };
280
327
  }
281
328
  }
@@ -323,13 +370,13 @@ function getAllTools() {
323
370
  }
324
371
 
325
372
  // src/core/manifest.ts
326
- import { existsSync as existsSync3, readFileSync } from "fs";
327
- import { join as join3 } from "path";
373
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
374
+ import { join as join5 } from "path";
328
375
  function readManifest(configRoot) {
329
376
  const manifestPath = getManifestPath(configRoot);
330
- if (existsSync3(manifestPath)) {
377
+ if (existsSync4(manifestPath)) {
331
378
  try {
332
- const content = readFileSync(manifestPath, "utf-8");
379
+ const content = readFileSync3(manifestPath, "utf-8");
333
380
  return JSON.parse(content);
334
381
  } catch {
335
382
  }
@@ -340,18 +387,18 @@ function readAllManifests(configRoot) {
340
387
  const manifests = [];
341
388
  const plugins = getAllPlugins();
342
389
  const standardPath = getManifestPath(configRoot);
343
- if (existsSync3(standardPath)) {
390
+ if (existsSync4(standardPath)) {
344
391
  try {
345
- const content = readFileSync(standardPath, "utf-8");
392
+ const content = readFileSync3(standardPath, "utf-8");
346
393
  manifests.push(JSON.parse(content));
347
394
  } catch {
348
395
  }
349
396
  }
350
397
  for (const plugin of plugins) {
351
- const pluginManifestPath = join3(configRoot, `${plugin.id}-manifest.json`);
352
- if (existsSync3(pluginManifestPath)) {
398
+ const pluginManifestPath = join5(configRoot, `${plugin.id}-manifest.json`);
399
+ if (existsSync4(pluginManifestPath)) {
353
400
  try {
354
- const content = readFileSync(pluginManifestPath, "utf-8");
401
+ const content = readFileSync3(pluginManifestPath, "utf-8");
355
402
  manifests.push(JSON.parse(content));
356
403
  } catch {
357
404
  }
@@ -371,6 +418,9 @@ function scanInstalled() {
371
418
  for (const manifest of manifests) {
372
419
  const plugin = plugins.find((p) => p.id === manifest.team);
373
420
  if (!plugin) continue;
421
+ if (!verifyPluginFilesExist(configRoot, manifest, tool, level)) {
422
+ continue;
423
+ }
374
424
  const alreadyAdded = installed.some(
375
425
  (p) => p.id === plugin.id && p.tool === tool && p.level === level
376
426
  );
@@ -390,190 +440,31 @@ function scanInstalled() {
390
440
  }
391
441
  return installed;
392
442
  }
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
- };
483
- var en_US = {
484
- wizard_title: "CANNBot Skills Setup Wizard",
485
- wizard_detect: "Detecting installed AI coding tools...",
486
- wizard_select_tool: "Select AI coding tool",
487
- wizard_select_level: "Select install level",
488
- wizard_select_plugins: "Select scenarios to install",
489
- wizard_confirm: "Confirm installation",
490
- wizard_detected_tool: "Detected",
491
- wizard_confirm_tool: "Confirm using",
492
- wizard_last_used: "last used",
493
- wizard_already_installed: "installed",
494
- install_cloning: "Cloning cannbot-skills repository...",
495
- install_repo_ready: "Repository ready",
496
- install_progress: "Installing",
497
- install_done: "Installation complete",
498
- install_success: "success",
499
- install_failed: "failed",
500
- install_already_installed: "already installed",
501
- install_reinstall_confirm: "Reinstall (overwrite)",
502
- install_skip: "skipped",
503
- install_enhanced_next_steps: "Next steps",
504
- install_enhanced_launch: "Launch",
505
- install_enhanced_try: "Try",
506
- install_enhanced_more: "More",
507
- install_enhanced_check: "Check",
508
- install_enhanced_docs: "Docs",
509
- update_title: "Update installed Skills",
510
- update_updating: "Updating",
511
- update_done: "Update complete",
512
- update_no_plugins: "No installed plugins",
513
- doctor_title: "CANNBot Doctor",
514
- doctor_tools: "AI Tools",
515
- doctor_plugins: "Installed Plugins",
516
- doctor_links: "Link Integrity",
517
- doctor_config: "Configuration",
518
- doctor_result: "Result",
519
- doctor_fix_title: "Auto-fix",
520
- doctor_fix_cleaning: "Cleaning broken links",
521
- doctor_fix_rebuilding: "Rebuilding directories",
522
- doctor_fix_done: "Fix complete",
523
- info_title: "Plugin Details",
524
- info_description: "Description",
525
- info_skills: "Skills",
526
- info_agents: "Agents",
527
- info_status: "Status",
528
- info_quickstart: "Quick Start",
529
- info_not_found: "Plugin not found",
530
- lang_title: "Language Settings",
531
- lang_current: "Current language",
532
- lang_set: "Language set to",
533
- lang_invalid: "Invalid language, supported: zh_CN, en_US",
534
- list_title: "Available Scenarios",
535
- list_id: "#",
536
- list_name: "Name",
537
- list_status: "Status",
538
- list_skills: "Skills",
539
- list_agents: "Agents",
540
- status_installed: "installed",
541
- status_not_installed: "not installed",
542
- error_tool_not_found: "No AI coding tool detected",
543
- error_plugin_not_found: "Plugin not found",
544
- error_repo_not_found: "Repository not found",
545
- quick_start: "Launch command",
546
- quick_start_hint: "Try: help me develop an Abs operator",
547
- skill_select_category: "Select Skill category",
548
- skill_select_items: "Select Skills to install (space to toggle, enter to confirm)",
549
- skill_already_installed: "installed",
550
- skill_install_progress: "Installing",
551
- skill_install_done: "Skill installation complete",
552
- skill_uninstall_done: "Skill uninstallation complete",
553
- skill_list_title: "Available Skills",
554
- wizard_select_mode: "Select installation mode",
555
- wizard_mode_plugin: "Install Plugin",
556
- wizard_mode_plugin_desc: "Full development workflow (Skills + Agents + config)",
557
- wizard_mode_skill: "Install Skill",
558
- wizard_mode_skill_desc: "Domain knowledge only, give AI specific capabilities",
559
- wizard_back: "Go back",
560
- wizard_cancel: "Cancel installation",
561
- wizard_no_selection: "No items selected, please choose an action",
562
- wizard_back_to_reselect: "Go back",
563
- status_title: "Installed Plugins",
564
- status_none: "No installed plugins",
565
- status_hint: "Run install-helper init to start",
566
- status_install_time: "Installed at",
567
- install_installed_to: "Installed to",
568
- install_skills_count: "Skills",
569
- install_agents_count: "Agents"
570
- };
571
- var currentLang = "zh_CN";
572
- function setLanguage(lang) {
573
- currentLang = lang;
574
- }
575
- function t(key) {
576
- return currentLang === "zh_CN" ? zh_CN[key] : en_US[key];
443
+ function verifyPluginFilesExist(configRoot, manifest, tool, level) {
444
+ const skillsDir = join5(configRoot, "skills");
445
+ const agentsDir = join5(configRoot, "agents");
446
+ if (manifest.installed_skills && manifest.installed_skills.length > 0) {
447
+ const hasAnySkill = manifest.installed_skills.some((skillId) => {
448
+ const skillPath = join5(skillsDir, skillId);
449
+ return existsSync4(skillPath);
450
+ });
451
+ if (!hasAnySkill) return false;
452
+ }
453
+ if (manifest.installed_agents && manifest.installed_agents.length > 0) {
454
+ const hasAnyAgent = manifest.installed_agents.some((agentId) => {
455
+ const agentPath = join5(agentsDir, agentId);
456
+ return existsSync4(agentPath);
457
+ });
458
+ if (!hasAnyAgent) return false;
459
+ }
460
+ let agentsMdPath;
461
+ if (tool === "opencode" && level === "project") {
462
+ agentsMdPath = join5(configRoot, "..", "AGENTS.md");
463
+ } else {
464
+ agentsMdPath = join5(configRoot, "AGENTS.md");
465
+ }
466
+ if (!existsSync4(agentsMdPath)) return false;
467
+ return true;
577
468
  }
578
469
 
579
470
  // src/utils/logger.ts
@@ -630,35 +521,35 @@ function printBoxTitle(title, width = 65) {
630
521
  }
631
522
  function showOperationHints(isCheckbox = false) {
632
523
  if (isCheckbox) {
633
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u7A7A\u683C\x1B[0m \u9009\u62E9 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4
524
+ 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
634
525
  `);
635
526
  } else {
636
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4
527
+ console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}
637
528
  `);
638
529
  }
639
530
  }
640
531
 
641
532
  // src/utils/config.ts
642
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
643
- import { join as join4 } from "path";
533
+ import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
534
+ import { join as join6 } from "path";
644
535
  import { homedir as homedir3 } from "os";
645
536
  import { parse, stringify } from "yaml";
646
537
  function getConfigDir() {
647
- return join4(homedir3(), ".cannbot");
538
+ return join6(homedir3(), ".cannbot");
648
539
  }
649
540
  function getConfigPath() {
650
- return join4(getConfigDir(), "config.yaml");
541
+ return join6(getConfigDir(), "config.yaml");
651
542
  }
652
543
  function readConfig() {
653
544
  const configPath = getConfigPath();
654
- if (!existsSync4(configPath)) {
545
+ if (!existsSync5(configPath)) {
655
546
  return {
656
547
  language: "zh_CN",
657
548
  installedPlugins: []
658
549
  };
659
550
  }
660
551
  try {
661
- const content = readFileSync2(configPath, "utf-8");
552
+ const content = readFileSync4(configPath, "utf-8");
662
553
  const parsed = parse(content);
663
554
  return {
664
555
  language: parsed.language || "zh_CN",
@@ -676,7 +567,7 @@ function readConfig() {
676
567
  }
677
568
  function writeConfig(config) {
678
569
  const configDir = getConfigDir();
679
- if (!existsSync4(configDir)) {
570
+ if (!existsSync5(configDir)) {
680
571
  mkdirSync(configDir, { recursive: true });
681
572
  }
682
573
  const configPath = getConfigPath();
@@ -705,16 +596,78 @@ function removeInstalledPlugin(pluginId) {
705
596
  }
706
597
 
707
598
  // src/ui/theme.ts
599
+ import chalk2 from "chalk";
708
600
  var selectTheme = {
709
601
  helpMode: "never"
710
602
  };
603
+ function isCJK(code) {
604
+ return code >= 11904 && code <= 40959 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65103 || code >= 65280 && code <= 65519 || code >= 13312 && code <= 19903 || code >= 131072 && code <= 173791 || code >= 173824 && code <= 177983 || code >= 177984 && code <= 178207 || code >= 178208 && code <= 183983;
605
+ }
606
+ function isEmoji(code) {
607
+ return code >= 127744 && code <= 129791 || code >= 9728 && code <= 10175;
608
+ }
609
+ function getDisplayWidth2(str) {
610
+ let width = 0;
611
+ for (const ch of str) {
612
+ const code = ch.codePointAt(0);
613
+ if (isCJK(code) || isEmoji(code)) {
614
+ width += 2;
615
+ } else {
616
+ width += 1;
617
+ }
618
+ }
619
+ return width;
620
+ }
621
+ function wrapDescription(text) {
622
+ const prefix = " \u{1F4A1} ";
623
+ const indent = " ";
624
+ const cols = process.stdout.columns || 120;
625
+ const maxLen = Math.max(20, cols - getDisplayWidth2(prefix) - 2);
626
+ const tokens = [];
627
+ let i = 0;
628
+ while (i < text.length) {
629
+ const code = text[i].codePointAt(0);
630
+ if (isCJK(code)) {
631
+ tokens.push({ text: text[i], width: 2 });
632
+ i++;
633
+ } else {
634
+ let j = i;
635
+ while (j < text.length) {
636
+ const c = text[j].codePointAt(0);
637
+ if (isCJK(c)) break;
638
+ j++;
639
+ }
640
+ const word = text.slice(i, j);
641
+ tokens.push({ text: word, width: getDisplayWidth2(word) });
642
+ i = j;
643
+ }
644
+ }
645
+ const lines = [];
646
+ let line = "";
647
+ let lineWidth = 0;
648
+ for (const token of tokens) {
649
+ if (lineWidth + token.width > maxLen && line) {
650
+ lines.push(line.trimEnd());
651
+ line = token.text;
652
+ lineWidth = token.width;
653
+ } else {
654
+ line += token.text;
655
+ lineWidth += token.width;
656
+ }
657
+ }
658
+ if (line) lines.push(line.trimEnd());
659
+ return lines.map((l, i2) => chalk2.cyan(i2 === 0 ? prefix + l : indent + l)).join("\n");
660
+ }
711
661
  var checkboxTheme = {
712
662
  icon: {
713
663
  checked: "\x1B[32m\u2611\x1B[22m",
714
664
  unchecked: "\u2610",
715
665
  cursor: "\u276F"
716
666
  },
717
- helpMode: "never"
667
+ helpMode: "never",
668
+ style: {
669
+ description: (text) => wrapDescription(text)
670
+ }
718
671
  };
719
672
 
720
673
  // src/ui/wizard.ts
@@ -789,7 +742,7 @@ async function runWizard() {
789
742
  step = 2;
790
743
  break;
791
744
  }
792
- if (result === CANCEL || result === false) {
745
+ if (result === CANCEL) {
793
746
  return { language: config.language || "zh_CN", tool: selectedTool, level, plugins: [], confirmed: false };
794
747
  }
795
748
  updateConfig({ lastTool: selectedTool, lastLevel: level });
@@ -799,7 +752,7 @@ async function runWizard() {
799
752
  }
800
753
  }
801
754
  async function stepTool(detectedTools, lastTool) {
802
- printBoxTitle("[1/4] \u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177");
755
+ printBoxTitle(t("wizard_step_1_title"));
803
756
  if (detectedTools.length === 0) {
804
757
  logger.error(t("error_tool_not_found"));
805
758
  const choices2 = [
@@ -874,10 +827,10 @@ async function stepToolManual(lastTool) {
874
827
  return result;
875
828
  }
876
829
  async function stepLevel(lastLevel) {
877
- printBoxTitle("[2/4] \u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E");
830
+ printBoxTitle(t("wizard_step_2_title"));
878
831
  const choices = [
879
- { name: "> project \u2014 \u5B89\u88C5\u5230\u5F53\u524D\u76EE\u5F55\uFF08\u63A8\u8350\uFF0C\u4EC5\u5F53\u524D\u9879\u76EE\u53EF\u7528\uFF09", value: "project" },
880
- { name: "> global \u2014 \u5B89\u88C5\u5230\u5168\u5C40\uFF08\u6240\u6709\u9879\u76EE\u5171\u4EAB\uFF09", value: "global" },
832
+ { name: `> project \u2014 ${t("install_level_project")}`, value: "project" },
833
+ { name: `> global \u2014 ${t("install_level_global")}`, value: "global" },
881
834
  new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
882
835
  { name: "<- " + t("wizard_back"), value: BACK },
883
836
  { name: "x " + t("wizard_cancel"), value: CANCEL }
@@ -895,7 +848,7 @@ async function stepLevel(lastLevel) {
895
848
  return result;
896
849
  }
897
850
  async function stepPlugins(tool, level) {
898
- printBoxTitle("[3/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6");
851
+ printBoxTitle(t("wizard_step_3_title"));
899
852
  const plugins = getAllPlugins();
900
853
  const configRoot = getConfigRoot(tool, level);
901
854
  const manifests = readAllManifests(configRoot);
@@ -904,25 +857,22 @@ async function stepPlugins(tool, level) {
904
857
  const isInstalled = installedSet.has(p.id);
905
858
  const suffix = isInstalled ? ` [${t("wizard_already_installed")}]` : "";
906
859
  return {
907
- name: `${p.displayName}${suffix} \u2014 ${p.description}`,
860
+ name: `${chalk3.cyan(p.displayName)}${suffix}`,
908
861
  value: p.id,
909
- checked: false
862
+ checked: false,
863
+ description: p.description
910
864
  };
911
865
  });
912
- choices.push(new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
913
- choices.push({ name: "<- " + t("wizard_back"), value: BACK, checked: false });
914
- choices.push({ name: "x " + t("wizard_cancel"), value: CANCEL, checked: false });
915
866
  showOperationHints(true);
916
867
  const selected = await checkbox({
917
868
  message: t("wizard_select_plugins"),
918
869
  choices,
919
870
  loop: false,
920
871
  instructions: false,
921
- theme: checkboxTheme
872
+ theme: checkboxTheme,
873
+ pageSize: 15
922
874
  });
923
- if (selected.includes(BACK)) return BACK;
924
- if (selected.includes(CANCEL)) return CANCEL;
925
- const pluginIds = selected.filter((v) => v !== BACK && v !== CANCEL);
875
+ const pluginIds = selected;
926
876
  if (pluginIds.length === 0) {
927
877
  const action = await select({
928
878
  message: t("wizard_no_selection"),
@@ -940,17 +890,17 @@ async function stepPlugins(tool, level) {
940
890
  return pluginIds;
941
891
  }
942
892
  async function stepConfirm(tool, level, plugins) {
943
- printBoxTitle("[4/4] \u786E\u8BA4\u5B89\u88C5");
893
+ printBoxTitle(t("wizard_step_4_title"));
944
894
  const allPlugins = getAllPlugins();
945
895
  const selectedPlugins = plugins.map((id) => allPlugins.find((p) => p.id === id));
946
896
  const displayPath = getConfigRoot(tool, level);
947
897
  const toolName = getToolDisplayName(tool);
948
898
  const levelText = level === "project" ? "project" : "global";
949
- 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`);
950
- logger.info(`\u5B89\u88C5\u8DEF\u5F84: ${chalk2.cyan(displayPath)}`);
899
+ logger.info(t("wizard_confirm_install_format").replace("{count}", chalk3.bold(String(plugins.length))).replace("{tool}", chalk3.green(toolName)).replace("{level}", chalk3.cyan(levelText)));
900
+ logger.info(`${t("wizard_install_path")}: ${chalk3.cyan(displayPath)}`);
951
901
  for (const plugin of selectedPlugins) {
952
902
  if (plugin) {
953
- logger.step(` \u2022 ${chalk2.bold(plugin.displayName)}`);
903
+ logger.step(` \u2022 ${chalk3.bold(plugin.displayName)}`);
954
904
  }
955
905
  }
956
906
  logger.blank();
@@ -968,14 +918,19 @@ async function stepConfirm(tool, level, plugins) {
968
918
  }
969
919
 
970
920
  // src/core/repository.ts
971
- import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
972
- import { join as join6 } from "path";
921
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
922
+ import { join as join8, dirname as dirname4 } from "path";
923
+ import { fileURLToPath as fileURLToPath4 } from "url";
973
924
  import { execa as execa2 } from "execa";
925
+ import { parse as parseYaml3 } from "yaml";
974
926
 
975
927
  // src/core/scanner.ts
976
- import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
977
- import { join as join5, dirname } from "path";
978
- import { parse as parseYaml } from "yaml";
928
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
929
+ import { join as join7, dirname as dirname3 } from "path";
930
+ import { fileURLToPath as fileURLToPath3 } from "url";
931
+ import { parse as parseYaml2 } from "yaml";
932
+ var __filename3 = fileURLToPath3(import.meta.url);
933
+ var __dirname3 = dirname3(__filename3);
979
934
  var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
980
935
  "node_modules",
981
936
  ".git",
@@ -996,46 +951,67 @@ var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
996
951
  "docs",
997
952
  "scripts"
998
953
  ]);
999
- var SKILL_SCAN_DIRS = [
1000
- "ops",
1001
- "model",
1002
- "graph",
1003
- "infra",
1004
- "ops-lab"
1005
- ];
1006
- var PLUGIN_SCAN_DIRS = [
1007
- "plugins-official",
1008
- "plugins-community"
1009
- ];
954
+ function loadScanConfig() {
955
+ const configPath = join7(__dirname3, "config", "repository.yaml");
956
+ try {
957
+ const config = parseYaml2(readFileSync5(configPath, "utf-8"));
958
+ return {
959
+ skillDirs: config.scanDirs || ["ops", "model", "graph", "infra", "ops-lab", "runtime"],
960
+ pluginDirs: config.pluginDirs || ["plugins-official", "plugins-community"]
961
+ };
962
+ } catch {
963
+ return {
964
+ skillDirs: ["ops", "model", "graph", "infra", "ops-lab", "runtime"],
965
+ pluginDirs: ["plugins-official", "plugins-community"]
966
+ };
967
+ }
968
+ }
969
+ var scanConfig = loadScanConfig();
970
+ var SKILL_SCAN_DIRS = scanConfig.skillDirs;
971
+ var PLUGIN_SCAN_DIRS = scanConfig.pluginDirs;
1010
972
  function scanSkills(repoPath) {
1011
973
  const skills = [];
1012
974
  const seen = /* @__PURE__ */ new Set();
1013
975
  for (const dir of SKILL_SCAN_DIRS) {
1014
- const fullPath = join5(repoPath, dir);
1015
- if (!existsSync5(fullPath)) continue;
976
+ const fullPath = join7(repoPath, dir);
977
+ if (!existsSync6(fullPath)) continue;
1016
978
  scanDirectory(fullPath, repoPath, dir, skills, seen);
1017
979
  }
1018
980
  for (const pluginDir of PLUGIN_SCAN_DIRS) {
1019
- const pluginParent = join5(repoPath, pluginDir);
1020
- if (!existsSync5(pluginParent)) continue;
981
+ const pluginParent = join7(repoPath, pluginDir);
982
+ if (!existsSync6(pluginParent)) continue;
1021
983
  for (const plugin of safeReaddir(pluginParent)) {
1022
- const pluginPath = join5(pluginParent, plugin);
1023
- if (!isDirectory(pluginPath)) continue;
1024
- const skillsDir = join5(pluginPath, "skills");
1025
- if (existsSync5(skillsDir)) {
984
+ const pluginPath = join7(pluginParent, plugin);
985
+ if (!isDirectory2(pluginPath)) continue;
986
+ const skillsDir = join7(pluginPath, "skills");
987
+ if (existsSync6(skillsDir)) {
1026
988
  scanDirectory(skillsDir, repoPath, `${pluginDir}/${plugin}/skills`, skills, seen);
1027
989
  }
1028
- const skillDir = join5(pluginPath, "skill");
1029
- if (existsSync5(skillDir)) {
990
+ const skillDir = join7(pluginPath, "skill");
991
+ if (existsSync6(skillDir)) {
1030
992
  scanDirectory(skillDir, repoPath, `${pluginDir}/${plugin}/skill`, skills, seen);
1031
993
  }
994
+ const rootSkillMd = join7(pluginPath, "SKILL.md");
995
+ const initSh = join7(pluginPath, "init.sh");
996
+ if (existsSync6(rootSkillMd) && !existsSync6(initSh)) {
997
+ const frontmatter = parseFrontmatter(rootSkillMd);
998
+ if (frontmatter && !seen.has(frontmatter.name)) {
999
+ seen.add(frontmatter.name);
1000
+ skills.push({
1001
+ id: frontmatter.name,
1002
+ description: frontmatter.description,
1003
+ source: `${pluginDir}/${plugin}`,
1004
+ filePath: rootSkillMd
1005
+ });
1006
+ }
1007
+ }
1032
1008
  }
1033
1009
  }
1034
1010
  return skills;
1035
1011
  }
1036
1012
  function parseFrontmatter(filePath) {
1037
1013
  try {
1038
- const content = readFileSync3(filePath, "utf-8");
1014
+ const content = readFileSync5(filePath, "utf-8");
1039
1015
  const lines = content.split("\n");
1040
1016
  if (lines.length < 3) return null;
1041
1017
  if (lines[0].trim() !== "---") return null;
@@ -1048,7 +1024,7 @@ function parseFrontmatter(filePath) {
1048
1024
  }
1049
1025
  if (endIndex === -1) return null;
1050
1026
  const yamlBlock = lines.slice(1, endIndex).map((l) => l.replace(/\r$/, "")).join("\n");
1051
- const parsed = parseYaml(yamlBlock);
1027
+ const parsed = parseYaml2(yamlBlock);
1052
1028
  if (!parsed || typeof parsed !== "object") return null;
1053
1029
  if (!parsed.name || typeof parsed.name !== "string") return null;
1054
1030
  return {
@@ -1061,17 +1037,17 @@ function parseFrontmatter(filePath) {
1061
1037
  }
1062
1038
  function getCurrentCommit(repoPath) {
1063
1039
  try {
1064
- const headFile = join5(repoPath, ".git", "HEAD");
1065
- if (!existsSync5(headFile)) return "unknown";
1066
- const headContent = readFileSync3(headFile, "utf-8").trim();
1040
+ const headFile = join7(repoPath, ".git", "HEAD");
1041
+ if (!existsSync6(headFile)) return "unknown";
1042
+ const headContent = readFileSync5(headFile, "utf-8").trim();
1067
1043
  if (headContent.startsWith("ref: ")) {
1068
- const refPath = join5(repoPath, ".git", headContent.slice(5));
1069
- if (existsSync5(refPath)) {
1070
- return readFileSync3(refPath, "utf-8").trim();
1044
+ const refPath = join7(repoPath, ".git", headContent.slice(5));
1045
+ if (existsSync6(refPath)) {
1046
+ return readFileSync5(refPath, "utf-8").trim();
1071
1047
  }
1072
- const packedRefsPath = join5(repoPath, ".git", "packed-refs");
1073
- if (existsSync5(packedRefsPath)) {
1074
- const packedRefs = readFileSync3(packedRefsPath, "utf-8");
1048
+ const packedRefsPath = join7(repoPath, ".git", "packed-refs");
1049
+ if (existsSync6(packedRefsPath)) {
1050
+ const packedRefs = readFileSync5(packedRefsPath, "utf-8");
1075
1051
  const refName = headContent.slice(5);
1076
1052
  const lines = packedRefs.split("\n");
1077
1053
  for (const line of lines) {
@@ -1089,8 +1065,8 @@ function getCurrentCommit(repoPath) {
1089
1065
  function readScanCache() {
1090
1066
  try {
1091
1067
  const cachePath = getCachePath();
1092
- if (!existsSync5(cachePath)) return null;
1093
- const content = readFileSync3(cachePath, "utf-8");
1068
+ if (!existsSync6(cachePath)) return null;
1069
+ const content = readFileSync5(cachePath, "utf-8");
1094
1070
  const cache = JSON.parse(content);
1095
1071
  if (!cache.skills || !cache.timestamp) return null;
1096
1072
  const age = Date.now() - cache.timestamp;
@@ -1103,8 +1079,8 @@ function readScanCache() {
1103
1079
  function writeScanCache(cache) {
1104
1080
  try {
1105
1081
  const cachePath = getCachePath();
1106
- const cacheDir = dirname(cachePath);
1107
- if (!existsSync5(cacheDir)) {
1082
+ const cacheDir = dirname3(cachePath);
1083
+ if (!existsSync6(cacheDir)) {
1108
1084
  mkdirSync2(cacheDir, { recursive: true });
1109
1085
  }
1110
1086
  writeFileSync2(cachePath, JSON.stringify(cache, null, 2));
@@ -1112,30 +1088,15 @@ function writeScanCache(cache) {
1112
1088
  }
1113
1089
  }
1114
1090
  function getCachePath() {
1115
- const homeDir = process.env.HOME || process.env.USERPROFILE || "/tmp";
1116
- return join5(homeDir, ".cannbot", "scan-cache.json");
1091
+ return join7(getCannbotConfigDir(), "scan-cache.json");
1117
1092
  }
1118
1093
  function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1119
- const selfSkillMd = join5(dirPath, "SKILL.md");
1120
- if (existsSync5(selfSkillMd)) {
1121
- const frontmatter = parseFrontmatter(selfSkillMd);
1122
- if (frontmatter && !seen.has(frontmatter.name)) {
1123
- seen.add(frontmatter.name);
1124
- skills.push({
1125
- id: frontmatter.name,
1126
- description: frontmatter.description,
1127
- source: sourcePrefix,
1128
- filePath: selfSkillMd
1129
- });
1130
- }
1131
- return;
1132
- }
1133
1094
  for (const entry of safeReaddir(dirPath)) {
1134
- const fullPath = join5(dirPath, entry);
1095
+ const fullPath = join7(dirPath, entry);
1135
1096
  if (EXCLUDE_DIRS.has(entry)) continue;
1136
- if (!isDirectory(fullPath)) continue;
1137
- const skillMd = join5(fullPath, "SKILL.md");
1138
- if (existsSync5(skillMd)) {
1097
+ if (!isDirectory2(fullPath)) continue;
1098
+ const skillMd = join7(fullPath, "SKILL.md");
1099
+ if (existsSync6(skillMd)) {
1139
1100
  const frontmatter = parseFrontmatter(skillMd);
1140
1101
  if (frontmatter && !seen.has(frontmatter.name)) {
1141
1102
  seen.add(frontmatter.name);
@@ -1148,19 +1109,19 @@ function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1148
1109
  }
1149
1110
  continue;
1150
1111
  }
1151
- scanDirectory(fullPath, repoPath, sourcePrefix, skills, seen);
1112
+ scanDirectory(fullPath, repoPath, sourcePrefix + "/" + entry, skills, seen);
1152
1113
  }
1153
1114
  }
1154
1115
  function safeReaddir(dir) {
1155
1116
  try {
1156
- return readdirSync(dir);
1117
+ return readdirSync2(dir);
1157
1118
  } catch {
1158
1119
  return [];
1159
1120
  }
1160
1121
  }
1161
- function isDirectory(path) {
1122
+ function isDirectory2(path) {
1162
1123
  try {
1163
- return statSync(path).isDirectory();
1124
+ return statSync2(path).isDirectory();
1164
1125
  } catch {
1165
1126
  return false;
1166
1127
  }
@@ -1177,6 +1138,7 @@ var CATEGORY_DEFS = [
1177
1138
  { id: "tilelang", name: "TileLang \u5F00\u53D1" },
1178
1139
  { id: "triton", name: "Triton \u5F00\u53D1" },
1179
1140
  { id: "model", name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316" },
1141
+ { id: "runtime", name: "Runtime \u8FC1\u79FB" },
1180
1142
  { id: "graph", name: "\u56FE\u6A21\u5F0F" },
1181
1143
  { id: "platform", name: "\u5E73\u53F0\u5DE5\u5177" },
1182
1144
  { id: "other", name: "\u5176\u4ED6 Skills" }
@@ -1187,7 +1149,8 @@ function initFromScan(scanned) {
1187
1149
  dynamicSkills = scanned.map((s) => ({
1188
1150
  id: s.id,
1189
1151
  description: s.description,
1190
- source: s.source
1152
+ source: s.source,
1153
+ filePath: s.filePath
1191
1154
  }));
1192
1155
  initialized = true;
1193
1156
  }
@@ -1263,9 +1226,7 @@ var STATIC_SKILL_CATEGORIES = [
1263
1226
  { id: "catlass-op-develop", description: "Catlass \u7B97\u5B50\u5F00\u53D1", source: "ops" },
1264
1227
  { id: "catlass-op-perf-tune", description: "Catlass \u6027\u80FD\u8C03\u4F18", source: "ops" },
1265
1228
  { id: "cuda2ascend-simt", description: "CUDA \u8FC1\u79FB\u5230 Ascend C SIMT", source: "ops-lab" },
1266
- { id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" },
1267
- { id: "ops-registry-invoke-workflow", description: "\u6CE8\u518C\u8C03\u7528\u5DE5\u4F5C\u6D41", source: "plugins-official/ops-registry-invoke" },
1268
- { id: "ops-easyasc-dsl", description: "EasyASC DSL \u7B97\u5B50\u5F00\u53D1", source: "plugins-community/ops-easyasc-dsl/skill" }
1229
+ { id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" }
1269
1230
  ]
1270
1231
  },
1271
1232
  {
@@ -1322,7 +1283,9 @@ var STATIC_SKILL_CATEGORIES = [
1322
1283
  { id: "model-infer-prefetch", description: "\u6743\u91CD\u9884\u53D6\u9002\u914D", source: "model" },
1323
1284
  { id: "model-infer-superkernel", description: "SuperKernel \u9002\u914D", source: "model" },
1324
1285
  { id: "model-infer-precision-debug", description: "NPU \u63A8\u7406\u7CBE\u5EA6\u8BCA\u65AD", source: "model" },
1325
- { id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" }
1286
+ { id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" },
1287
+ { id: "model-infer-harmony", description: "\u7AEF\u4FA7\u9E3F\u8499 ASR \u91CF\u5316\u8F6C\u6362\u4E0E\u6253\u5305", source: "model" },
1288
+ { id: "science-model-npu-migration", description: "NPU \u4EE3\u7801\u7EA7\u8FC1\u79FB\uFF08\u73AF\u5883\u95E8\u7981/\u811A\u672C\u9002\u914D/\u7CBE\u5EA6\u6027\u80FD\u5BF9\u6BD4\uFF09", source: "plugins-community/science-model-npu-migration", filePath: "plugins-community/science-model-npu-migration/SKILL.md" }
1326
1289
  ]
1327
1290
  },
1328
1291
  {
@@ -1338,6 +1301,13 @@ var STATIC_SKILL_CATEGORIES = [
1338
1301
  { id: "torch-custom-ops-guide", description: "\u81EA\u5B9A\u4E49\u7B97\u5B50\u5165\u56FE\u6307\u5357", source: "graph" }
1339
1302
  ]
1340
1303
  },
1304
+ {
1305
+ id: "runtime",
1306
+ name: "Runtime \u8FC1\u79FB",
1307
+ skills: [
1308
+ { id: "runtime_migration", description: "CUDA \u5E94\u7528\u8FC1\u79FB\u5230 CANN \u5E73\u53F0\u6307\u5357", source: "runtime" }
1309
+ ]
1310
+ },
1341
1311
  {
1342
1312
  id: "platform",
1343
1313
  name: "\u5E73\u53F0\u5DE5\u5177",
@@ -1393,7 +1363,21 @@ function getAllCategories() {
1393
1363
  }
1394
1364
 
1395
1365
  // src/core/repository.ts
1396
- var REPO_URL = "https://gitcode.com/cann/cannbot-skills.git";
1366
+ var __filename4 = fileURLToPath4(import.meta.url);
1367
+ var __dirname4 = dirname4(__filename4);
1368
+ function loadRepoUrl() {
1369
+ if (process.env.CANNBOT_REPO_URL) {
1370
+ return process.env.CANNBOT_REPO_URL;
1371
+ }
1372
+ const configPath = join8(__dirname4, "config", "repository.yaml");
1373
+ try {
1374
+ const config = parseYaml3(readFileSync6(configPath, "utf-8"));
1375
+ return config.repository?.url || "https://gitcode.com/cann/cannbot-skills.git";
1376
+ } catch {
1377
+ return "https://gitcode.com/cann/cannbot-skills.git";
1378
+ }
1379
+ }
1380
+ var REPO_URL = loadRepoUrl();
1397
1381
  var RepositoryManager = class {
1398
1382
  repoPath;
1399
1383
  constructor(customPath) {
@@ -1418,10 +1402,13 @@ var RepositoryManager = class {
1418
1402
  this.repoPath = cachedPath;
1419
1403
  return cachedPath;
1420
1404
  }
1421
- return await this.cloneRepo();
1405
+ const clonedPath = await this.cloneRepo();
1406
+ return clonedPath;
1422
1407
  }
1423
1408
  async ensureRepoAndScan() {
1424
1409
  const repoPath = await this.ensureRepo();
1410
+ await this.updateRepo();
1411
+ mergeDynamicPlugins(repoPath);
1425
1412
  const cache = readScanCache();
1426
1413
  if (cache && cache.repoCommit === getCurrentCommit(repoPath)) {
1427
1414
  initFromScan(cache.skills);
@@ -1442,6 +1429,7 @@ var RepositoryManager = class {
1442
1429
  await execa2("git", ["pull", "--quiet"], { cwd: repoPath, timeout: 3e4 });
1443
1430
  } catch {
1444
1431
  }
1432
+ mergeDynamicPlugins(repoPath);
1445
1433
  }
1446
1434
  getRepoPath() {
1447
1435
  if (!this.repoPath) {
@@ -1450,18 +1438,18 @@ var RepositoryManager = class {
1450
1438
  return this.repoPath;
1451
1439
  }
1452
1440
  isValidRepo(path) {
1453
- if (!existsSync6(path)) return false;
1454
- const gitDir = join6(path, ".git");
1455
- const pluginsDir = join6(path, "plugins-official");
1456
- return existsSync6(gitDir) && existsSync6(pluginsDir);
1441
+ if (!existsSync7(path)) return false;
1442
+ const gitDir = join8(path, ".git");
1443
+ const pluginsDir = join8(path, "plugins-official");
1444
+ return existsSync7(gitDir) && existsSync7(pluginsDir);
1457
1445
  }
1458
1446
  async cloneRepo() {
1459
1447
  const configDir = getCannbotConfigDir();
1460
- if (!existsSync6(configDir)) {
1448
+ if (!existsSync7(configDir)) {
1461
1449
  mkdirSync3(configDir, { recursive: true });
1462
1450
  }
1463
1451
  const targetPath = getCannbotRepoPath();
1464
- if (existsSync6(targetPath)) {
1452
+ if (existsSync7(targetPath)) {
1465
1453
  if (this.isValidRepo(targetPath)) {
1466
1454
  this.repoPath = targetPath;
1467
1455
  return targetPath;
@@ -1487,26 +1475,26 @@ function createRepositoryManager(customPath) {
1487
1475
  }
1488
1476
 
1489
1477
  // src/core/installer.ts
1490
- import { existsSync as existsSync8 } from "fs";
1491
- import { join as join8 } from "path";
1478
+ import { existsSync as existsSync10 } from "fs";
1479
+ import { join as join11 } from "path";
1492
1480
  import { execa as execa3 } from "execa";
1493
1481
 
1494
1482
  // src/core/record.ts
1495
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, lstatSync, unlinkSync } from "fs";
1496
- import { join as join7 } from "path";
1483
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4, lstatSync, unlinkSync } from "fs";
1484
+ import { join as join9 } from "path";
1497
1485
  function getInstallsDir() {
1498
- return join7(getCannbotConfigDir(), "installs");
1486
+ return join9(getCannbotConfigDir(), "installs");
1499
1487
  }
1500
1488
  function getRecordPath(pluginId) {
1501
- return join7(getInstallsDir(), `${pluginId}.json`);
1489
+ return join9(getInstallsDir(), `${pluginId}.json`);
1502
1490
  }
1503
1491
  function readRecord(pluginId) {
1504
1492
  const recordPath = getRecordPath(pluginId);
1505
- if (!existsSync7(recordPath)) {
1493
+ if (!existsSync8(recordPath)) {
1506
1494
  return null;
1507
1495
  }
1508
1496
  try {
1509
- const content = readFileSync4(recordPath, "utf-8");
1497
+ const content = readFileSync7(recordPath, "utf-8");
1510
1498
  return JSON.parse(content);
1511
1499
  } catch {
1512
1500
  return null;
@@ -1514,7 +1502,7 @@ function readRecord(pluginId) {
1514
1502
  }
1515
1503
  function writeRecord(record) {
1516
1504
  const installsDir = getInstallsDir();
1517
- if (!existsSync7(installsDir)) {
1505
+ if (!existsSync8(installsDir)) {
1518
1506
  mkdirSync4(installsDir, { recursive: true });
1519
1507
  }
1520
1508
  const recordPath = getRecordPath(record.pluginId);
@@ -1522,7 +1510,7 @@ function writeRecord(record) {
1522
1510
  }
1523
1511
  function deleteRecord(pluginId) {
1524
1512
  const recordPath = getRecordPath(pluginId);
1525
- if (existsSync7(recordPath)) {
1513
+ if (existsSync8(recordPath)) {
1526
1514
  unlinkSync(recordPath);
1527
1515
  }
1528
1516
  }
@@ -1530,59 +1518,58 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
1530
1518
  const files = [];
1531
1519
  const directories = [];
1532
1520
  if (manifest) {
1533
- const skillsDir = join7(configRoot, "skills");
1534
- if (existsSync7(skillsDir)) {
1521
+ const skillsDir = join9(configRoot, "skills");
1522
+ if (existsSync8(skillsDir)) {
1535
1523
  directories.push(skillsDir);
1536
1524
  for (const skillName of manifest.installed_skills || []) {
1537
- const skillPath = join7(skillsDir, skillName);
1538
- if (existsSync7(skillPath) || isSymlink(skillPath)) {
1525
+ const skillPath = join9(skillsDir, skillName);
1526
+ if (existsSync8(skillPath) || isSymlink(skillPath)) {
1539
1527
  files.push(skillPath);
1540
1528
  }
1541
1529
  }
1542
1530
  }
1543
- const agentsDir = join7(configRoot, "agents");
1544
- if (existsSync7(agentsDir)) {
1531
+ const agentsDir = join9(configRoot, "agents");
1532
+ if (existsSync8(agentsDir)) {
1545
1533
  directories.push(agentsDir);
1546
1534
  for (const agentName of manifest.installed_agents || []) {
1547
- const agentPath = join7(agentsDir, agentName);
1548
- const agentPathMd = join7(agentsDir, agentName + ".md");
1549
- if (existsSync7(agentPath) || isSymlink(agentPath)) {
1535
+ const agentPath = join9(agentsDir, agentName);
1536
+ const agentPathMd = join9(agentsDir, agentName + ".md");
1537
+ if (existsSync8(agentPath) || isSymlink(agentPath)) {
1550
1538
  files.push(agentPath);
1551
- } else if (existsSync7(agentPathMd) || isSymlink(agentPathMd)) {
1539
+ } else if (existsSync8(agentPathMd) || isSymlink(agentPathMd)) {
1552
1540
  files.push(agentPathMd);
1553
1541
  }
1554
1542
  }
1555
1543
  }
1556
1544
  }
1557
- const workflowsLink = join7(configRoot, "workflows");
1545
+ const workflowsLink = join9(configRoot, "workflows");
1558
1546
  if (isSymlink(workflowsLink)) {
1559
1547
  files.push(workflowsLink);
1560
1548
  }
1561
- const manifestPath = join7(configRoot, "cannbot-manifest.json");
1562
- if (existsSync7(manifestPath)) {
1549
+ const manifestPath = join9(configRoot, "cannbot-manifest.json");
1550
+ if (existsSync8(manifestPath)) {
1563
1551
  files.push(manifestPath);
1564
1552
  }
1565
- const pluginManifestPath = join7(configRoot, `${pluginId}-manifest.json`);
1566
- if (existsSync7(pluginManifestPath)) {
1553
+ const pluginManifestPath = join9(configRoot, `${pluginId}-manifest.json`);
1554
+ if (existsSync8(pluginManifestPath)) {
1567
1555
  files.push(pluginManifestPath);
1568
1556
  }
1569
1557
  const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1570
- const configFilePath = level === "project" ? join7(installPath, configFileName) : join7(configRoot, configFileName);
1571
- if (existsSync7(configFilePath)) {
1558
+ const configFilePath = level === "project" ? join9(installPath, configFileName) : join9(configRoot, configFileName);
1559
+ if (existsSync8(configFilePath)) {
1572
1560
  files.push(configFilePath);
1573
1561
  }
1574
1562
  const repoLinks = ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
1575
1563
  for (const repoName of repoLinks) {
1576
- const repoLinkPath = join7(installPath, repoName);
1564
+ const repoLinkPath = join9(installPath, repoName);
1577
1565
  if (isSymlink(repoLinkPath)) {
1578
1566
  files.push(repoLinkPath);
1579
1567
  }
1580
- const repoLinkInConfig = join7(configRoot, repoName);
1568
+ const repoLinkInConfig = join9(configRoot, repoName);
1581
1569
  if (isSymlink(repoLinkInConfig)) {
1582
1570
  files.push(repoLinkInConfig);
1583
1571
  }
1584
1572
  }
1585
- directories.push(configRoot);
1586
1573
  return {
1587
1574
  pluginId,
1588
1575
  displayName,
@@ -1603,15 +1590,15 @@ function isSymlink(path) {
1603
1590
  }
1604
1591
  }
1605
1592
  function getSkillRecordPath() {
1606
- return join7(getInstallsDir(), "skills.json");
1593
+ return join9(getInstallsDir(), "skills.json");
1607
1594
  }
1608
1595
  function readSkillRecord() {
1609
1596
  const recordPath = getSkillRecordPath();
1610
- if (!existsSync7(recordPath)) {
1597
+ if (!existsSync8(recordPath)) {
1611
1598
  return {};
1612
1599
  }
1613
1600
  try {
1614
- const content = readFileSync4(recordPath, "utf-8");
1601
+ const content = readFileSync7(recordPath, "utf-8");
1615
1602
  return JSON.parse(content);
1616
1603
  } catch {
1617
1604
  return {};
@@ -1619,7 +1606,7 @@ function readSkillRecord() {
1619
1606
  }
1620
1607
  function writeSkillRecord(record) {
1621
1608
  const installsDir = getInstallsDir();
1622
- if (!existsSync7(installsDir)) {
1609
+ if (!existsSync8(installsDir)) {
1623
1610
  mkdirSync4(installsDir, { recursive: true });
1624
1611
  }
1625
1612
  const recordPath = getSkillRecordPath();
@@ -1659,7 +1646,186 @@ function removeSkillsFromRecord(skillIds, tool, level, installPath) {
1659
1646
  }
1660
1647
  function getInstalledSkills(tool, level, installPath) {
1661
1648
  const record = readSkillRecord();
1662
- return record[tool]?.[level]?.[installPath]?.skills || [];
1649
+ const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
1650
+ const configRoot = getConfigRoot(tool, level);
1651
+ const skillsDir = join9(configRoot, "skills");
1652
+ return recordedSkills.filter((skillId) => {
1653
+ const skillPath = join9(skillsDir, skillId);
1654
+ return existsSync8(skillPath) || isSymlink(skillPath);
1655
+ });
1656
+ }
1657
+
1658
+ // src/core/backup.ts
1659
+ import { existsSync as existsSync9, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync2 } from "fs";
1660
+ import { join as join10 } from "path";
1661
+ function getAgentsFileName(tool) {
1662
+ return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1663
+ }
1664
+ function detectCurrentPlugin(configRoot, tool) {
1665
+ const manifest = readManifest(configRoot);
1666
+ if (manifest?.team) {
1667
+ const plugin = findPlugin(manifest.team);
1668
+ if (plugin) {
1669
+ return {
1670
+ pluginId: plugin.id,
1671
+ pluginName: plugin.displayName
1672
+ };
1673
+ }
1674
+ }
1675
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
1676
+ if (!existsSync9(agentsFile)) {
1677
+ return null;
1678
+ }
1679
+ const allRecords = readAllRecords();
1680
+ for (const record of allRecords) {
1681
+ if (record.configRoot === configRoot && record.tool === tool) {
1682
+ const plugin = findPlugin(record.pluginId);
1683
+ if (plugin) {
1684
+ return {
1685
+ pluginId: plugin.id,
1686
+ pluginName: plugin.displayName
1687
+ };
1688
+ }
1689
+ }
1690
+ }
1691
+ return null;
1692
+ }
1693
+ function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
1694
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
1695
+ if (!existsSync9(agentsFile)) {
1696
+ return null;
1697
+ }
1698
+ const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
1699
+ const backupFileName = `${getAgentsFileName(tool)}.cannbot-backup.${fromPluginId}.${timestamp}`;
1700
+ const backupPath = join10(configRoot, backupFileName);
1701
+ try {
1702
+ copyFileSync(agentsFile, backupPath);
1703
+ return {
1704
+ filePath: backupPath,
1705
+ pluginId: fromPluginId,
1706
+ pluginName: fromPluginName,
1707
+ backupTime: timestamp
1708
+ };
1709
+ } catch {
1710
+ return null;
1711
+ }
1712
+ }
1713
+ function findBackups(configRoot) {
1714
+ if (!existsSync9(configRoot)) {
1715
+ return [];
1716
+ }
1717
+ const files = readdirSync3(configRoot);
1718
+ const backups = [];
1719
+ for (const file of files) {
1720
+ if (file.includes(".cannbot-backup.")) {
1721
+ const parts = file.split(".cannbot-backup.");
1722
+ if (parts.length === 2) {
1723
+ const pluginIdAndTime = parts[1];
1724
+ const lastDotIndex = pluginIdAndTime.lastIndexOf(".");
1725
+ if (lastDotIndex > 0) {
1726
+ const pluginId = pluginIdAndTime.substring(0, lastDotIndex);
1727
+ const timestamp = pluginIdAndTime.substring(lastDotIndex + 1);
1728
+ const plugin = findPlugin(pluginId);
1729
+ backups.push({
1730
+ filePath: join10(configRoot, file),
1731
+ pluginId,
1732
+ pluginName: plugin?.displayName || pluginId,
1733
+ backupTime: timestamp
1734
+ });
1735
+ }
1736
+ }
1737
+ }
1738
+ }
1739
+ return backups.sort((a, b) => b.backupTime.localeCompare(a.backupTime));
1740
+ }
1741
+ function restoreBackup(backupPath, configRoot, tool) {
1742
+ try {
1743
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
1744
+ copyFileSync(backupPath, agentsFile);
1745
+ return true;
1746
+ } catch {
1747
+ return false;
1748
+ }
1749
+ }
1750
+ function deleteBackup(backupPath) {
1751
+ try {
1752
+ if (existsSync9(backupPath)) {
1753
+ unlinkSync2(backupPath);
1754
+ return true;
1755
+ }
1756
+ return false;
1757
+ } catch {
1758
+ return false;
1759
+ }
1760
+ }
1761
+ function formatTimestamp(date) {
1762
+ const year = date.getFullYear();
1763
+ const month = String(date.getMonth() + 1).padStart(2, "0");
1764
+ const day = String(date.getDate()).padStart(2, "0");
1765
+ const hours = String(date.getHours()).padStart(2, "0");
1766
+ const minutes = String(date.getMinutes()).padStart(2, "0");
1767
+ const seconds = String(date.getSeconds()).padStart(2, "0");
1768
+ return `${year}${month}${day}-${hours}${minutes}${seconds}`;
1769
+ }
1770
+ function readAllRecords() {
1771
+ const records = [];
1772
+ const plugins = [
1773
+ "ops-direct-invoke",
1774
+ "ops-direct-invoke-flash",
1775
+ "ops-registry-invoke",
1776
+ "pypto-op-orchestrator",
1777
+ "triton-op-generator",
1778
+ "tilelang-op-orchestrator",
1779
+ "model-infer-optimize",
1780
+ "catlass-op-generator",
1781
+ "ops-code-reviewer",
1782
+ "torch-compile"
1783
+ ];
1784
+ for (const pluginId of plugins) {
1785
+ const record = readRecord(pluginId);
1786
+ if (record) {
1787
+ records.push(record);
1788
+ }
1789
+ }
1790
+ return records;
1791
+ }
1792
+
1793
+ // src/ui/backup-prompts.ts
1794
+ import { select as select2 } from "@inquirer/prompts";
1795
+ import chalk4 from "chalk";
1796
+ async function showOverwriteWarning(currentPluginName, newPluginName) {
1797
+ console.log();
1798
+ console.log(chalk4.yellow(` \u26A0\uFE0F ${t("backup_detected")}`));
1799
+ console.log();
1800
+ console.log(` ${t("backup_current")}: ${chalk4.cyan(currentPluginName)}`);
1801
+ console.log(` ${t("backup_installing")}: ${chalk4.cyan(newPluginName)}`);
1802
+ console.log();
1803
+ console.log(` ${t("backup_overwrite_warning")}`);
1804
+ console.log(` ${t("backup_auto_create")}`);
1805
+ console.log();
1806
+ const choice = await select2({
1807
+ message: t("backup_select_action"),
1808
+ choices: [
1809
+ { name: t("backup_overwrite"), value: "overwrite" },
1810
+ { name: t("backup_cancel"), value: "cancel" }
1811
+ ]
1812
+ });
1813
+ return choice;
1814
+ }
1815
+ async function showRestorePrompt(backups) {
1816
+ console.log();
1817
+ console.log(chalk4.cyan(` ${t("backup_restore_prompt")}`));
1818
+ console.log();
1819
+ const choices = backups.map((b) => ({
1820
+ name: `${t("backup_restore")} ${b.pluginName} (${b.backupTime})`,
1821
+ value: b.filePath
1822
+ }));
1823
+ choices.push({ name: t("backup_only_uninstall"), value: "none" });
1824
+ const choice = await select2({
1825
+ message: t("backup_select_action"),
1826
+ choices
1827
+ });
1828
+ return choice;
1663
1829
  }
1664
1830
 
1665
1831
  // src/core/installer.ts
@@ -1675,8 +1841,8 @@ async function installPlugin(opts) {
1675
1841
  warnings: []
1676
1842
  };
1677
1843
  }
1678
- const scriptPath = join8(opts.repoPath, plugin.dir, plugin.script);
1679
- if (!existsSync8(scriptPath)) {
1844
+ const scriptPath = join11(opts.repoPath, plugin.dir, plugin.script);
1845
+ if (!existsSync10(scriptPath)) {
1680
1846
  return {
1681
1847
  success: false,
1682
1848
  pluginId: opts.pluginId,
@@ -1691,13 +1857,49 @@ async function installPlugin(opts) {
1691
1857
  args.push(opts.installPath);
1692
1858
  }
1693
1859
  const cwd = opts.installPath || process.cwd();
1860
+ const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
1861
+ const agentsFile = join11(configRoot, getAgentsFileName(opts.tool));
1862
+ let backupInfo = null;
1863
+ if (existsSync10(agentsFile)) {
1864
+ const currentPlugin = detectCurrentPlugin(configRoot, opts.tool);
1865
+ if (currentPlugin && currentPlugin.pluginId !== opts.pluginId) {
1866
+ let choice = "overwrite";
1867
+ if (!opts.yes) {
1868
+ choice = await showOverwriteWarning(
1869
+ currentPlugin.pluginName,
1870
+ plugin.displayName
1871
+ );
1872
+ }
1873
+ if (choice === "cancel") {
1874
+ logger.info(t("backup_cancel"));
1875
+ return {
1876
+ success: false,
1877
+ pluginId: opts.pluginId,
1878
+ skillsCount: 0,
1879
+ agentsCount: 0,
1880
+ errors: [t("backup_cancel")],
1881
+ warnings: []
1882
+ };
1883
+ }
1884
+ backupInfo = createBackup(
1885
+ configRoot,
1886
+ opts.tool,
1887
+ currentPlugin.pluginId,
1888
+ currentPlugin.pluginName
1889
+ );
1890
+ if (backupInfo) {
1891
+ logger.success(`${t("backup_created")}: ${backupInfo.filePath}`);
1892
+ }
1893
+ }
1894
+ }
1694
1895
  try {
1695
- await execa3("bash", [scriptPath, ...args], {
1896
+ const execOptions = {
1696
1897
  cwd,
1697
1898
  timeout: 3e5,
1698
- stdio: "pipe"
1699
- });
1700
- const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
1899
+ stdio: "pipe",
1900
+ input: opts.yes ? "y\n" : void 0
1901
+ };
1902
+ await execa3("bash", [scriptPath, ...args], execOptions);
1701
1903
  const manifest = readManifest(configRoot);
1702
1904
  let skillsCount = 0;
1703
1905
  let agentsCount = 0;
@@ -1718,6 +1920,14 @@ async function installPlugin(opts) {
1718
1920
  configRoot,
1719
1921
  manifest
1720
1922
  );
1923
+ if (backupInfo) {
1924
+ record.backup = {
1925
+ filePath: backupInfo.filePath,
1926
+ fromPluginId: backupInfo.pluginId,
1927
+ fromPluginName: backupInfo.pluginName,
1928
+ backupTime: backupInfo.backupTime
1929
+ };
1930
+ }
1721
1931
  writeRecord(record);
1722
1932
  } catch {
1723
1933
  }
@@ -1743,16 +1953,16 @@ async function installPlugin(opts) {
1743
1953
  }
1744
1954
 
1745
1955
  // src/core/skill-installer.ts
1746
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync2, readdirSync as readdirSync3, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
1747
- import { join as join9 } from "path";
1748
- import { select as select2, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
1749
- import chalk3 from "chalk";
1956
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync3, readdirSync as readdirSync4, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
1957
+ import { join as join12, dirname as dirname5 } from "path";
1958
+ import { select as select3, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
1959
+ import chalk5 from "chalk";
1750
1960
  import Table from "cli-table3";
1751
1961
  async function installSkills(skillIds, tool, level, repoPath) {
1752
1962
  const configRoot = getConfigRoot(tool, level);
1753
- const skillsDir = join9(configRoot, "skills");
1963
+ const skillsDir = join12(configRoot, "skills");
1754
1964
  const installPath = level === "project" ? process.cwd() : configRoot;
1755
- if (!existsSync9(skillsDir)) {
1965
+ if (!existsSync11(skillsDir)) {
1756
1966
  mkdirSync5(skillsDir, { recursive: true });
1757
1967
  }
1758
1968
  const results = [];
@@ -1763,15 +1973,15 @@ async function installSkills(skillIds, tool, level, repoPath) {
1763
1973
  results.push({ skillId, success: false, error: "Skill not found in registry" });
1764
1974
  continue;
1765
1975
  }
1766
- const sourcePath = join9(repoPath, skill.source, skill.id);
1767
- const targetPath = join9(skillsDir, skillId);
1768
- if (!existsSync9(sourcePath)) {
1976
+ const sourcePath = resolveSkillSourcePath(skill, repoPath);
1977
+ const targetPath = join12(skillsDir, skillId);
1978
+ if (!existsSync11(sourcePath)) {
1769
1979
  results.push({ skillId, success: false, error: `Source not found: ${sourcePath}` });
1770
1980
  continue;
1771
1981
  }
1772
1982
  try {
1773
- if (existsSync9(targetPath) || isSymlink2(targetPath)) {
1774
- unlinkSync2(targetPath);
1983
+ if (existsSync11(targetPath) || isSymlink2(targetPath)) {
1984
+ unlinkSync3(targetPath);
1775
1985
  }
1776
1986
  symlinkSync(realpathSync(sourcePath), targetPath);
1777
1987
  results.push({ skillId, success: true });
@@ -1787,21 +1997,21 @@ async function installSkills(skillIds, tool, level, repoPath) {
1787
1997
  }
1788
1998
  async function uninstallSkills(skillIds, tool, level) {
1789
1999
  const configRoot = getConfigRoot(tool, level);
1790
- const skillsDir = join9(configRoot, "skills");
2000
+ const skillsDir = join12(configRoot, "skills");
1791
2001
  const installPath = level === "project" ? process.cwd() : configRoot;
1792
2002
  const results = [];
1793
2003
  const removedIds = [];
1794
2004
  for (const skillId of skillIds) {
1795
- const targetPath = join9(skillsDir, skillId);
2005
+ const targetPath = join12(skillsDir, skillId);
1796
2006
  try {
1797
- if (existsSync9(targetPath) || isSymlink2(targetPath)) {
1798
- unlinkSync2(targetPath);
2007
+ if (existsSync11(targetPath) || isSymlink2(targetPath)) {
2008
+ unlinkSync3(targetPath);
1799
2009
  results.push({ skillId, success: true });
1800
2010
  removedIds.push(skillId);
1801
- logger.step(` \u79FB\u9664: ${skillId}`);
2011
+ logger.step(` ${t("uninstall_remove_label")}: ${skillId}`);
1802
2012
  } else {
1803
2013
  results.push({ skillId, success: false, error: "Not installed" });
1804
- logger.warn(`${skillId} \u672A\u5B89\u88C5`);
2014
+ logger.warn(`${skillId} ${t("uninstall_skill_not_installed")}`);
1805
2015
  }
1806
2016
  } catch (error) {
1807
2017
  results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
@@ -1810,103 +2020,121 @@ async function uninstallSkills(skillIds, tool, level) {
1810
2020
  if (removedIds.length > 0) {
1811
2021
  removeSkillsFromRecord(removedIds, tool, level, installPath);
1812
2022
  }
1813
- if (existsSync9(skillsDir)) {
2023
+ if (existsSync11(skillsDir)) {
1814
2024
  try {
1815
- const entries = readdirSync3(skillsDir);
2025
+ const entries = readdirSync4(skillsDir);
1816
2026
  if (entries.length === 0) {
1817
2027
  rmdirSync(skillsDir);
1818
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: skills/`);
1819
- }
1820
- } catch {
1821
- }
1822
- }
1823
- if (existsSync9(configRoot)) {
1824
- try {
1825
- const entries = readdirSync3(configRoot);
1826
- if (entries.length === 0) {
1827
- rmdirSync(configRoot);
1828
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${configRoot.split("/").pop()}/`);
2028
+ logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
1829
2029
  }
1830
2030
  } catch {
1831
2031
  }
1832
2032
  }
1833
2033
  return results;
1834
2034
  }
1835
- async function interactiveSkillSelect() {
2035
+ var SELECT_ALL = "__select_all__";
2036
+ var SELECT_ALL_CATEGORY = "__select_all_category__";
2037
+ var CONTINUE_SELECT = "__continue_select__";
2038
+ async function interactiveSkillSelect(tool, level) {
1836
2039
  const BACK3 = "__back__";
1837
2040
  const CANCEL3 = "__cancel__";
1838
2041
  const categories = getAllCategories();
2042
+ const allSkillIds = getAllSkills().map((s) => s.id);
2043
+ let accumulated = [];
1839
2044
  let step = 0;
1840
2045
  let selectedCategoryId = "";
1841
2046
  while (true) {
1842
2047
  switch (step) {
1843
2048
  case 0: {
1844
- printBoxTitle("[3/4] \u9009\u62E9 Skill \u7C7B\u522B");
2049
+ const titleSuffix = accumulated.length > 0 ? `${t("wizard_step_3_skill_title")}\uFF08${t("skill_count_format").replace("{count}", String(accumulated.length))}\uFF09` : t("wizard_step_3_skill_title");
2050
+ printBoxTitle(titleSuffix);
1845
2051
  const categoryChoices = [
2052
+ {
2053
+ name: `>> ${t("skill_select_all").replace("{count}", String(allSkillIds.length))}`,
2054
+ value: SELECT_ALL
2055
+ },
2056
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1846
2057
  ...categories.map((cat) => ({
1847
2058
  name: `> ${cat.name} (${cat.skills.length} skills)`,
1848
2059
  value: cat.id
1849
2060
  })),
1850
2061
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2062
+ ...accumulated.length > 0 ? [{ name: `>> ${t("skill_confirm_install_count").replace("{count}", String(accumulated.length))}`, value: "install" }] : [],
1851
2063
  { name: "<- " + t("wizard_back"), value: BACK3 },
1852
2064
  { name: "x " + t("wizard_cancel"), value: CANCEL3 }
1853
2065
  ];
1854
2066
  showOperationHints();
1855
- selectedCategoryId = await select2({
2067
+ selectedCategoryId = await select3({
1856
2068
  message: t("skill_select_category"),
1857
2069
  choices: categoryChoices,
1858
2070
  loop: false,
1859
- theme: selectTheme
2071
+ theme: selectTheme,
2072
+ pageSize: 15
1860
2073
  });
1861
2074
  if (selectedCategoryId === BACK3) return "back";
1862
2075
  if (selectedCategoryId === CANCEL3) return "cancel";
2076
+ if (selectedCategoryId === SELECT_ALL) {
2077
+ return allSkillIds;
2078
+ }
2079
+ if (selectedCategoryId === "install") {
2080
+ return accumulated;
2081
+ }
1863
2082
  step = 1;
1864
2083
  break;
1865
2084
  }
1866
2085
  case 1: {
1867
2086
  const category = categories.find((c) => c.id === selectedCategoryId);
1868
2087
  if (!category) return [];
1869
- printBoxTitle(`[4/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills \u2014 ${category.name}`);
1870
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
1871
- const levels = ["project", "global"];
1872
- const installedSkills = [];
1873
- for (const tk of tools) {
1874
- for (const l of levels) {
1875
- const path = l === "project" ? process.cwd() : getConfigRoot(tk, l);
1876
- installedSkills.push(...getInstalledSkills(tk, l, path));
1877
- }
1878
- }
2088
+ printBoxTitle(t("wizard_step_4_skill_title").replace("{category}", category.name));
2089
+ const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
2090
+ const installedSkills = getInstalledSkills(tool, level, installPath);
1879
2091
  const installedSet = new Set(installedSkills);
1880
- const skillChoices = category.skills.map((skill) => {
1881
- const isInstalled = installedSet.has(skill.id);
1882
- const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : "";
1883
- return {
1884
- name: `${skill.id}${suffix} \u2014 ${skill.description}`,
1885
- value: skill.id,
1886
- checked: false
1887
- };
1888
- });
1889
- skillChoices.push(new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1890
- skillChoices.push({ name: "<- " + t("wizard_back"), value: BACK3, checked: false });
1891
- skillChoices.push({ name: "x " + t("wizard_cancel"), value: CANCEL3, checked: false });
2092
+ const categorySkillIds = new Set(category.skills.map((s) => s.id));
2093
+ const accumulatedInCategory = new Set(accumulated.filter((id) => categorySkillIds.has(id)));
2094
+ const skillChoices = [
2095
+ {
2096
+ name: `>> ${t("skill_select_all_category")}`,
2097
+ value: SELECT_ALL_CATEGORY,
2098
+ checked: accumulatedInCategory.size === category.skills.length,
2099
+ description: t("skill_select_all_category")
2100
+ },
2101
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2102
+ ...category.skills.map((skill) => {
2103
+ const isInstalled = installedSet.has(skill.id);
2104
+ const isAccumulated = accumulatedInCategory.has(skill.id);
2105
+ const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : isAccumulated ? " [+]" : "";
2106
+ return {
2107
+ name: `${chalk5.cyan(skill.id)}${suffix}`,
2108
+ value: skill.id,
2109
+ checked: isAccumulated,
2110
+ description: skill.description
2111
+ };
2112
+ })
2113
+ ];
1892
2114
  showOperationHints(true);
1893
2115
  const selectedSkills = await checkbox2({
1894
2116
  message: t("skill_select_items"),
1895
2117
  choices: skillChoices,
1896
2118
  loop: false,
1897
2119
  instructions: false,
1898
- theme: checkboxTheme
2120
+ theme: checkboxTheme,
2121
+ pageSize: 15
1899
2122
  });
1900
- if (selectedSkills.includes(BACK3)) {
1901
- step = 0;
1902
- break;
2123
+ let skillIds;
2124
+ if (selectedSkills.includes(SELECT_ALL_CATEGORY)) {
2125
+ skillIds = category.skills.map((s) => s.id);
2126
+ } else {
2127
+ skillIds = selectedSkills.filter((id) => id !== SELECT_ALL_CATEGORY);
1903
2128
  }
1904
- if (selectedSkills.includes(CANCEL3)) return "cancel";
1905
- const skillIds = selectedSkills.filter((v) => v !== BACK3 && v !== CANCEL3);
1906
2129
  if (skillIds.length === 0) {
1907
- const action = await select2({
1908
- message: t("wizard_no_selection"),
1909
- choices: [
2130
+ accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
2131
+ const action2 = await select3({
2132
+ message: accumulated.length > 0 ? t("skill_continue_or_install").replace("{count}", String(accumulated.length)) : t("wizard_no_selection"),
2133
+ choices: accumulated.length > 0 ? [
2134
+ { name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
2135
+ { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
2136
+ { name: "x " + t("wizard_cancel"), value: "cancel" }
2137
+ ] : [
1910
2138
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1911
2139
  { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
1912
2140
  { name: "x " + t("wizard_cancel"), value: "cancel" }
@@ -1914,13 +2142,38 @@ async function interactiveSkillSelect() {
1914
2142
  loop: false,
1915
2143
  theme: selectTheme
1916
2144
  });
1917
- if (action === "back") {
2145
+ if (action2 === "install") return accumulated;
2146
+ if (action2 === "back") {
1918
2147
  step = 0;
1919
2148
  break;
1920
2149
  }
1921
2150
  return "cancel";
1922
2151
  }
1923
- return skillIds;
2152
+ accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
2153
+ for (const id of skillIds) {
2154
+ if (!accumulated.includes(id)) {
2155
+ accumulated.push(id);
2156
+ }
2157
+ }
2158
+ const action = await select3({
2159
+ message: t("skill_continue_or_install").replace("{count}", String(accumulated.length)),
2160
+ choices: [
2161
+ { name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
2162
+ { name: "> " + t("skill_continue_select"), value: CONTINUE_SELECT },
2163
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2164
+ { name: "x " + t("wizard_cancel"), value: "cancel" }
2165
+ ],
2166
+ loop: false,
2167
+ theme: selectTheme
2168
+ });
2169
+ if (action === "install") {
2170
+ return accumulated;
2171
+ }
2172
+ if (action === CONTINUE_SELECT) {
2173
+ step = 0;
2174
+ break;
2175
+ }
2176
+ return "cancel";
1924
2177
  }
1925
2178
  }
1926
2179
  }
@@ -1928,11 +2181,11 @@ async function interactiveSkillSelect() {
1928
2181
  function listAllSkills() {
1929
2182
  const categories = getAllCategories();
1930
2183
  console.log();
1931
- console.log(chalk3.bold(` ${t("skill_list_title")}`));
1932
- console.log(chalk3.dim(" " + "\u2500".repeat(60)));
2184
+ console.log(chalk5.bold(` ${t("skill_list_title")}`));
2185
+ console.log(chalk5.dim(" " + "\u2500".repeat(60)));
1933
2186
  for (const category of categories) {
1934
2187
  console.log();
1935
- console.log(chalk3.bold(` ${category.name}`) + chalk3.dim(` (${category.skills.length} skills)`));
2188
+ console.log(chalk5.bold(` ${category.name}`) + chalk5.dim(` (${category.skills.length} skills)`));
1936
2189
  const table = new Table({
1937
2190
  style: { head: [], border: [] },
1938
2191
  colWidths: [35, 45],
@@ -1940,16 +2193,25 @@ function listAllSkills() {
1940
2193
  });
1941
2194
  for (const skill of category.skills) {
1942
2195
  table.push([
1943
- chalk3.cyan(skill.id),
2196
+ chalk5.cyan(skill.id),
1944
2197
  skill.description
1945
2198
  ]);
1946
2199
  }
1947
2200
  console.log(table.toString());
1948
2201
  }
1949
2202
  console.log();
1950
- console.log(chalk3.dim(` \u5171 ${getAllSkills().length} \u4E2A Skills`));
2203
+ console.log(chalk5.dim(` ${t("skill_total_count").replace("{count}", String(getAllSkills().length))}`));
1951
2204
  console.log();
1952
2205
  }
2206
+ function resolveSkillSourcePath(skill, repoPath) {
2207
+ if (skill.filePath) {
2208
+ const absPath = skill.filePath.startsWith("/") ? skill.filePath : join12(repoPath, skill.filePath);
2209
+ if (existsSync11(absPath)) {
2210
+ return dirname5(absPath);
2211
+ }
2212
+ }
2213
+ return join12(repoPath, skill.source, skill.id);
2214
+ }
1953
2215
  function isSymlink2(path) {
1954
2216
  try {
1955
2217
  return lstatSync2(path).isSymbolicLink();
@@ -1959,22 +2221,22 @@ function isSymlink2(path) {
1959
2221
  }
1960
2222
 
1961
2223
  // src/ui/display.ts
1962
- import chalk4 from "chalk";
2224
+ import chalk6 from "chalk";
1963
2225
  import Table2 from "cli-table3";
1964
2226
  function printPluginList(plugins, installed) {
1965
2227
  const table = new Table2({
1966
2228
  head: [
1967
- chalk4.cyan(t("list_id")),
1968
- chalk4.cyan(t("list_name")),
1969
- chalk4.cyan(t("list_status")),
1970
- chalk4.cyan(t("list_skills")),
1971
- chalk4.cyan(t("list_agents"))
2229
+ chalk6.cyan(t("list_id")),
2230
+ chalk6.cyan(t("list_name")),
2231
+ chalk6.cyan(t("list_status")),
2232
+ chalk6.cyan(t("list_skills")),
2233
+ chalk6.cyan(t("list_agents"))
1972
2234
  ],
1973
2235
  style: { head: [], border: [] }
1974
2236
  });
1975
2237
  plugins.forEach((plugin, index) => {
1976
2238
  const isInstalled = installed.has(plugin.id);
1977
- const status = isInstalled ? chalk4.green(`\u2713 ${t("status_installed")}`) : chalk4.dim(`\u2014`);
2239
+ const status = isInstalled ? chalk6.green(`\u2713 ${t("status_installed")}`) : chalk6.dim(`\u2014`);
1978
2240
  table.push([
1979
2241
  String(index + 1),
1980
2242
  plugin.displayName,
@@ -1984,7 +2246,8 @@ function printPluginList(plugins, installed) {
1984
2246
  ]);
1985
2247
  });
1986
2248
  console.log();
1987
- console.log(chalk4.bold(` ${t("list_title")} (${plugins.length} \u4E2A)`));
2249
+ const countSuffix = t("count_suffix");
2250
+ console.log(chalk6.bold(` ${t("list_title")} (${plugins.length}${countSuffix ? " " + countSuffix : ""})`));
1988
2251
  console.log(table.toString());
1989
2252
  console.log();
1990
2253
  }
@@ -1993,7 +2256,7 @@ function printInstallSummary(results) {
1993
2256
  const totalCount = results.length;
1994
2257
  console.log();
1995
2258
  console.log(
1996
- chalk4.bold(
2259
+ chalk6.bold(
1997
2260
  ` ${t("install_done")}! ${successCount}/${totalCount} ${t("install_success")}`
1998
2261
  )
1999
2262
  );
@@ -2001,10 +2264,10 @@ function printInstallSummary(results) {
2001
2264
  for (const result of results) {
2002
2265
  if (result.success) {
2003
2266
  console.log(
2004
- chalk4.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
2267
+ chalk6.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
2005
2268
  );
2006
2269
  } else {
2007
- console.log(chalk4.red(" \u2717") + ` ${result.displayName}`);
2270
+ console.log(chalk6.red(" \u2717") + ` ${result.displayName}`);
2008
2271
  }
2009
2272
  }
2010
2273
  console.log();
@@ -2017,22 +2280,25 @@ function printEnhancedSummary(results, tool, configRoot) {
2017
2280
  return;
2018
2281
  }
2019
2282
  const toolName = getToolDisplayName(tool);
2283
+ const countSuffix = t("count_suffix");
2020
2284
  const lines = [
2021
- chalk4.bold(` ${t("install_done")}!`),
2285
+ chalk6.bold(` ${t("install_done")}!`),
2022
2286
  "",
2023
- ` ${chalk4.dim(t("install_installed_to") + ":")} ${configRoot}`,
2024
- ` ${chalk4.dim(t("install_skills_count") + ":")} ${totalSkills} \u4E2A ${chalk4.dim("|")} ${chalk4.dim(t("install_agents_count") + ":")} ${totalAgents} \u4E2A`,
2287
+ ` ${chalk6.dim(t("install_installed_to") + ":")} ${configRoot}`,
2288
+ ` ${chalk6.dim(t("install_skills_count") + ":")} ${totalSkills}${countSuffix ? " " + countSuffix : ""} ${chalk6.dim("|")} ${chalk6.dim(t("install_agents_count") + ":")} ${totalAgents}${countSuffix ? " " + countSuffix : ""}`,
2025
2289
  "",
2026
- chalk4.bold(` ${t("install_enhanced_next_steps")}:`),
2027
- ` ${chalk4.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk4.green(toolName.toLowerCase())}`,
2028
- ` ${chalk4.cyan("2.")} ${t("install_enhanced_try")}: ${chalk4.green("\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50")}`,
2029
- ` ${chalk4.cyan("3.")} ${t("install_enhanced_more")}: ${chalk4.green("install-helper list")}`,
2030
- ` ${chalk4.cyan("4.")} ${t("install_enhanced_check")}: ${chalk4.green("install-helper doctor")}`,
2290
+ chalk6.bold(` ${t("install_enhanced_next_steps")}:`),
2291
+ ` ${chalk6.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk6.green(toolName.toLowerCase())}`,
2292
+ ` ${chalk6.cyan("2.")} ${t("install_enhanced_try")}: ${chalk6.green(t("try_prompt"))}`,
2293
+ ` ${chalk6.cyan("3.")} ${t("install_enhanced_more")}: ${chalk6.green("install-helper list")}`,
2294
+ ` ${chalk6.cyan("4.")} ${t("install_enhanced_check")}: ${chalk6.green("install-helper doctor")}`,
2031
2295
  ""
2032
2296
  ];
2033
2297
  if (successResults.length === 1) {
2298
+ const plugin = findPlugin(successResults[0].pluginId);
2299
+ const docsPath = plugin ? `${plugin.dir}/quickstart.md` : `${successResults[0].pluginId}/quickstart.md`;
2034
2300
  lines.push(
2035
- ` ${chalk4.dim(t("install_enhanced_docs"))}: ${chalk4.dim(`plugins-official/${successResults[0].pluginId}/quickstart.md`)}`
2301
+ ` ${chalk6.dim(t("install_enhanced_docs"))}: ${chalk6.dim(docsPath)}`
2036
2302
  );
2037
2303
  }
2038
2304
  console.log();
@@ -2043,34 +2309,34 @@ function printEnhancedSummary(results, tool, configRoot) {
2043
2309
  }
2044
2310
 
2045
2311
  // src/commands/init.ts
2046
- import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
2047
- import { join as join10 } from "path";
2312
+ import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
2313
+ import { join as join13 } from "path";
2048
2314
  var BACK2 = "__back__";
2049
2315
  var CANCEL2 = "__cancel__";
2050
2316
  async function initCommand() {
2051
2317
  const cwd = process.cwd();
2052
- if (existsSync10(join10(cwd, "package.json")) && (() => {
2318
+ if (existsSync12(join13(cwd, "package.json")) && (() => {
2053
2319
  try {
2054
- const pkg = JSON.parse(readFileSync5(join10(cwd, "package.json"), "utf-8"));
2320
+ const pkg = JSON.parse(readFileSync8(join13(cwd, "package.json"), "utf-8"));
2055
2321
  return pkg.name === "@cannbot-ai/install-helper";
2056
2322
  } catch {
2057
2323
  return false;
2058
2324
  }
2059
2325
  })()) {
2060
- logger.warn("\u5F53\u524D\u76EE\u5F55\u770B\u8D77\u6765\u662F install-helper \u5305\u76EE\u5F55");
2061
- logger.info("\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");
2062
- logger.info("\u793A\u4F8B\uFF1Acd ~/my-project && install-helper");
2326
+ logger.warn(t("init_pkg_dir_warn"));
2327
+ logger.info(t("init_pkg_dir_hint"));
2328
+ logger.info(t("init_pkg_dir_example"));
2063
2329
  return;
2064
2330
  }
2065
2331
  printBanner(t("wizard_title"));
2066
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
2332
+ const spinner = createSpinner(t("loading_skills_list"));
2067
2333
  spinner.start();
2068
2334
  try {
2069
2335
  const repoManager = createRepositoryManager();
2070
2336
  await repoManager.ensureRepoAndScan();
2071
- spinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
2337
+ spinner.succeed(t("loading_skills_list_complete"));
2072
2338
  } catch {
2073
- spinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
2339
+ spinner.warn(t("loading_skills_list_failed"));
2074
2340
  }
2075
2341
  let step = 0;
2076
2342
  let mode = "plugin";
@@ -2104,7 +2370,7 @@ async function initCommand() {
2104
2370
  }
2105
2371
  }
2106
2372
  async function selectMode() {
2107
- printBoxTitle("\u9009\u62E9\u5B89\u88C5\u7C7B\u578B");
2373
+ printBoxTitle(t("wizard_select_mode_title"));
2108
2374
  const CANCEL3 = "__cancel__";
2109
2375
  const choices = [
2110
2376
  {
@@ -2119,7 +2385,7 @@ async function selectMode() {
2119
2385
  { name: "x " + t("wizard_cancel"), value: CANCEL3 }
2120
2386
  ];
2121
2387
  showOperationHints();
2122
- const result = await select3({
2388
+ const result = await select4({
2123
2389
  message: t("wizard_select_mode"),
2124
2390
  choices,
2125
2391
  loop: false,
@@ -2132,14 +2398,14 @@ async function pluginInstallFlow() {
2132
2398
  const answers = await runWizard();
2133
2399
  if (!answers.confirmed) {
2134
2400
  if (answers.back) return "back";
2135
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
2401
+ logger.info(t("init_cancelled"));
2136
2402
  return "done";
2137
2403
  }
2138
2404
  const repoManager = createRepositoryManager();
2139
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
2405
+ const spinner = createSpinner(t("loading_plugin_data"));
2140
2406
  spinner.start();
2141
2407
  const repoPath = await repoManager.ensureRepo();
2142
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2408
+ spinner.succeed(t("loading_plugin_data_complete"));
2143
2409
  const allPlugins = getAllPlugins();
2144
2410
  const selectedPlugins = answers.plugins.map(
2145
2411
  (id) => allPlugins.find((p) => p.id === id)
@@ -2193,7 +2459,7 @@ async function skillInstallFlow() {
2193
2459
  const result = await selectToolWithDetection();
2194
2460
  if (result === "back") return "back";
2195
2461
  if (result === "cancel") {
2196
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
2462
+ logger.info(t("init_cancelled"));
2197
2463
  return "done";
2198
2464
  }
2199
2465
  tool = result;
@@ -2207,7 +2473,7 @@ async function skillInstallFlow() {
2207
2473
  break;
2208
2474
  }
2209
2475
  if (result === CANCEL2) {
2210
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
2476
+ logger.info(t("init_cancelled"));
2211
2477
  return "done";
2212
2478
  }
2213
2479
  level = result;
@@ -2215,25 +2481,25 @@ async function skillInstallFlow() {
2215
2481
  break;
2216
2482
  }
2217
2483
  case 2: {
2218
- const selectedSkills = await interactiveSkillSelect();
2484
+ const selectedSkills = await interactiveSkillSelect(tool, level);
2219
2485
  if (selectedSkills === "back") {
2220
2486
  step = 1;
2221
2487
  break;
2222
2488
  }
2223
2489
  if (selectedSkills === "cancel") {
2224
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
2490
+ logger.info(t("init_cancelled"));
2225
2491
  return "done";
2226
2492
  }
2227
2493
  if (selectedSkills.length === 0) {
2228
- logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Skill");
2494
+ logger.info(t("install_no_skill_selected"));
2229
2495
  return "done";
2230
2496
  }
2231
2497
  const repoManager = createRepositoryManager();
2232
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
2498
+ const spinner = createSpinner(t("loading_plugin_data"));
2233
2499
  spinner.start();
2234
2500
  const repoPath = await repoManager.ensureRepo();
2235
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2236
- logger.info(`${t("skill_install_progress")} ${chalk5.bold(selectedSkills.length)} \u4E2A Skills...`);
2501
+ spinner.succeed(t("loading_plugin_data_complete"));
2502
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk7.bold(String(selectedSkills.length)))}...`);
2237
2503
  const results = await installSkills(selectedSkills, tool, level, repoPath);
2238
2504
  let successCount = 0;
2239
2505
  let failCount = 0;
@@ -2251,10 +2517,10 @@ async function skillInstallFlow() {
2251
2517
  }
2252
2518
  const configRoot = getConfigRoot(tool, level);
2253
2519
  logger.blank();
2254
- logger.success(`${t("skill_install_done")}: ${chalk5.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk5.red(failCount + " \u5931\u8D25") : chalk5.dim(failCount + " \u5931\u8D25")}`);
2520
+ logger.success(`${t("skill_install_done")}: ${chalk7.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk7.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk7.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2255
2521
  logger.blank();
2256
- logger.info(`\u5B89\u88C5\u5230: ${chalk5.cyan(configRoot + "/skills/")}`);
2257
- logger.info(`\u542F\u52A8 ${chalk5.green(tool)} \u5373\u53EF\u4F7F\u7528`);
2522
+ logger.info(`${t("install_to")}: ${chalk7.cyan(configRoot + "/skills/")}`);
2523
+ logger.info(`${t("start_to_use").replace("{tool}", chalk7.green(tool))}`);
2258
2524
  logger.blank();
2259
2525
  return "done";
2260
2526
  }
@@ -2276,32 +2542,32 @@ async function listCommand() {
2276
2542
  }
2277
2543
 
2278
2544
  // src/commands/doctor.ts
2279
- import chalk6 from "chalk";
2280
- import { existsSync as existsSync11, readdirSync as readdirSync4, lstatSync as lstatSync3, unlinkSync as unlinkSync3, mkdirSync as mkdirSync6 } from "fs";
2281
- import { join as join11 } from "path";
2545
+ import chalk8 from "chalk";
2546
+ import { existsSync as existsSync13, readdirSync as readdirSync5, lstatSync as lstatSync3, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6 } from "fs";
2547
+ import { join as join14 } from "path";
2282
2548
  async function doctorCommand(options = {}) {
2283
2549
  console.log();
2284
- console.log(chalk6.bold(` ${t("doctor_title")}`));
2285
- console.log(chalk6.dim(" " + "\u2500".repeat(46)));
2550
+ console.log(chalk8.bold(` ${t("doctor_title")}`));
2551
+ console.log(chalk8.dim(" " + "\u2500".repeat(46)));
2286
2552
  let errors = 0;
2287
2553
  let warnings = 0;
2288
2554
  let fixes = 0;
2289
2555
  console.log();
2290
- console.log(chalk6.bold(` ${t("doctor_tools")}`));
2556
+ console.log(chalk8.bold(` ${t("doctor_tools")}`));
2291
2557
  const detectedTools = await detectTools();
2292
2558
  const allTools = getAllTools();
2293
2559
  for (const tool of allTools) {
2294
2560
  const detected = detectedTools.find((d) => d.name === tool);
2295
2561
  if (detected) {
2296
2562
  console.log(
2297
- chalk6.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
2563
+ chalk8.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
2298
2564
  );
2299
2565
  } else {
2300
- console.log(chalk6.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 \u672A\u5B89\u88C5`);
2566
+ console.log(chalk8.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 ${t("doctor_not_installed")}`);
2301
2567
  }
2302
2568
  }
2303
2569
  console.log();
2304
- console.log(chalk6.bold(` ${t("doctor_plugins")}`));
2570
+ console.log(chalk8.bold(` ${t("doctor_plugins")}`));
2305
2571
  const plugins = getAllPlugins();
2306
2572
  const installed = scanInstalled();
2307
2573
  const installedMap = new Map(installed.map((p) => [p.id, p]));
@@ -2309,80 +2575,80 @@ async function doctorCommand(options = {}) {
2309
2575
  const inst = installedMap.get(plugin.id);
2310
2576
  if (inst) {
2311
2577
  console.log(
2312
- chalk6.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
2578
+ chalk8.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
2313
2579
  );
2314
2580
  } else {
2315
- console.log(chalk6.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 \u672A\u5B89\u88C5`);
2581
+ console.log(chalk8.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 ${t("doctor_not_installed")}`);
2316
2582
  }
2317
2583
  }
2318
2584
  console.log();
2319
- console.log(chalk6.bold(` ${t("doctor_links")}`));
2585
+ console.log(chalk8.bold(` ${t("doctor_links")}`));
2320
2586
  const primaryTool = detectedTools[0]?.name || "opencode";
2321
2587
  const configRoot = getConfigRoot(primaryTool, "project");
2322
2588
  const skillsDir = getSkillsDir(configRoot);
2323
2589
  const agentsDir = getAgentsDir(configRoot);
2324
- if (existsSync11(skillsDir)) {
2590
+ if (existsSync13(skillsDir)) {
2325
2591
  const brokenLinks = checkBrokenLinks(skillsDir);
2326
2592
  if (brokenLinks === 0) {
2327
- const count = readdirSync4(skillsDir).length;
2328
- console.log(chalk6.green(" \u2713") + ` ${skillsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
2593
+ const count = readdirSync5(skillsDir).length;
2594
+ console.log(chalk8.green(" \u2713") + ` ${skillsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2329
2595
  } else {
2330
- console.log(chalk6.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
2596
+ console.log(chalk8.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2331
2597
  warnings++;
2332
2598
  if (options.fix) {
2333
2599
  const fixed = fixBrokenLinks(skillsDir);
2334
2600
  fixes += fixed;
2335
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
2601
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2336
2602
  }
2337
2603
  }
2338
2604
  } else {
2339
- console.log(chalk6.dim(" \u2014") + ` ${skillsDir} \u2014 \u4E0D\u5B58\u5728`);
2605
+ console.log(chalk8.dim(" \u2014") + ` ${skillsDir} \u2014 ${t("doctor_not_exist")}`);
2340
2606
  if (options.fix) {
2341
2607
  mkdirSync6(skillsDir, { recursive: true });
2342
2608
  fixes++;
2343
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${skillsDir}`);
2609
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
2344
2610
  }
2345
2611
  }
2346
- if (existsSync11(agentsDir)) {
2612
+ if (existsSync13(agentsDir)) {
2347
2613
  const brokenLinks = checkBrokenLinks(agentsDir);
2348
2614
  if (brokenLinks === 0) {
2349
- const count = readdirSync4(agentsDir).length;
2350
- console.log(chalk6.green(" \u2713") + ` ${agentsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
2615
+ const count = readdirSync5(agentsDir).length;
2616
+ console.log(chalk8.green(" \u2713") + ` ${agentsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2351
2617
  } else {
2352
- console.log(chalk6.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
2618
+ console.log(chalk8.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2353
2619
  warnings++;
2354
2620
  if (options.fix) {
2355
2621
  const fixed = fixBrokenLinks(agentsDir);
2356
2622
  fixes += fixed;
2357
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
2623
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2358
2624
  }
2359
2625
  }
2360
2626
  } else {
2361
- console.log(chalk6.dim(" \u2014") + ` ${agentsDir} \u2014 \u4E0D\u5B58\u5728`);
2627
+ console.log(chalk8.dim(" \u2014") + ` ${agentsDir} \u2014 ${t("doctor_not_exist")}`);
2362
2628
  if (options.fix) {
2363
2629
  mkdirSync6(agentsDir, { recursive: true });
2364
2630
  fixes++;
2365
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${agentsDir}`);
2631
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", agentsDir)}`);
2366
2632
  }
2367
2633
  }
2368
2634
  console.log();
2369
- console.log(chalk6.bold(` ${t("doctor_config")}`));
2635
+ console.log(chalk8.bold(` ${t("doctor_config")}`));
2370
2636
  const configFile = getConfigFileName(primaryTool);
2371
- const configPath = join11(process.cwd(), configFile);
2372
- if (existsSync11(configPath)) {
2373
- console.log(chalk6.green(" \u2713") + ` ${configFile} \u5B58\u5728`);
2637
+ const configPath = join14(process.cwd(), configFile);
2638
+ if (existsSync13(configPath)) {
2639
+ console.log(chalk8.green(" \u2713") + ` ${configFile} ${t("doctor_config_exists")}`);
2374
2640
  } else {
2375
- console.log(chalk6.dim(" \u2014") + ` ${configFile} \u4E0D\u5B58\u5728`);
2641
+ console.log(chalk8.dim(" \u2014") + ` ${configFile} ${t("doctor_config_not_exist")}`);
2376
2642
  }
2377
2643
  console.log();
2378
- console.log(chalk6.dim(" " + "\u2500".repeat(46)));
2644
+ console.log(chalk8.dim(" " + "\u2500".repeat(46)));
2379
2645
  if (options.fix && fixes > 0) {
2380
2646
  console.log(
2381
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF, ${fixes} \u9879\u5DF2\u4FEE\u590D`
2647
+ ` ${t("doctor_result")}: ${t("doctor_result_with_fix").replace("{warnings}", String(warnings)).replace("{errors}", String(errors)).replace("{fixes}", String(fixes))}`
2382
2648
  );
2383
2649
  } else {
2384
2650
  console.log(
2385
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF`
2651
+ ` ${t("doctor_result")}: ${t("doctor_result_without_fix").replace("{warnings}", String(warnings)).replace("{errors}", String(errors))}`
2386
2652
  );
2387
2653
  }
2388
2654
  console.log();
@@ -2390,13 +2656,13 @@ async function doctorCommand(options = {}) {
2390
2656
  function checkBrokenLinks(dir) {
2391
2657
  let broken = 0;
2392
2658
  try {
2393
- const entries = readdirSync4(dir);
2659
+ const entries = readdirSync5(dir);
2394
2660
  for (const entry of entries) {
2395
- const fullPath = join11(dir, entry);
2661
+ const fullPath = join14(dir, entry);
2396
2662
  try {
2397
2663
  const stats = lstatSync3(fullPath);
2398
2664
  if (stats.isSymbolicLink()) {
2399
- if (!existsSync11(fullPath)) {
2665
+ if (!existsSync13(fullPath)) {
2400
2666
  broken++;
2401
2667
  }
2402
2668
  }
@@ -2411,14 +2677,14 @@ function checkBrokenLinks(dir) {
2411
2677
  function fixBrokenLinks(dir) {
2412
2678
  let fixed = 0;
2413
2679
  try {
2414
- const entries = readdirSync4(dir);
2680
+ const entries = readdirSync5(dir);
2415
2681
  for (const entry of entries) {
2416
- const fullPath = join11(dir, entry);
2682
+ const fullPath = join14(dir, entry);
2417
2683
  try {
2418
2684
  const stats = lstatSync3(fullPath);
2419
2685
  if (stats.isSymbolicLink()) {
2420
- if (!existsSync11(fullPath)) {
2421
- unlinkSync3(fullPath);
2686
+ if (!existsSync13(fullPath)) {
2687
+ unlinkSync4(fullPath);
2422
2688
  fixed++;
2423
2689
  }
2424
2690
  }
@@ -2431,36 +2697,36 @@ function fixBrokenLinks(dir) {
2431
2697
  }
2432
2698
 
2433
2699
  // src/commands/status.ts
2434
- import chalk7 from "chalk";
2700
+ import chalk9 from "chalk";
2435
2701
  async function statusCommand() {
2436
2702
  const plugins = getAllPlugins();
2437
2703
  const installed = scanInstalled();
2438
2704
  const installedMap = new Map(installed.map((p) => [p.id, p]));
2439
2705
  console.log();
2440
- console.log(chalk7.bold(` ${t("status_title")}`));
2706
+ console.log(chalk9.bold(` ${t("status_title")}`));
2441
2707
  console.log();
2442
2708
  if (installed.length === 0) {
2443
- console.log(chalk7.dim(` ${t("status_none")}`));
2444
- console.log(chalk7.dim(` ${t("status_hint")}`));
2709
+ console.log(chalk9.dim(` ${t("status_none")}`));
2710
+ console.log(chalk9.dim(` ${t("status_hint")}`));
2445
2711
  console.log();
2446
2712
  return;
2447
2713
  }
2448
2714
  for (const inst of installed) {
2449
2715
  console.log(
2450
- chalk7.green(" \u2713") + ` ${inst.displayName}` + chalk7.dim(` (${inst.tool}, ${inst.level})`)
2716
+ chalk9.green(" \u2713") + ` ${inst.displayName}` + chalk9.dim(` (${inst.tool}, ${inst.level})`)
2451
2717
  );
2452
2718
  console.log(
2453
- chalk7.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
2719
+ chalk9.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
2454
2720
  );
2455
- console.log(chalk7.dim(` ${inst.configRoot}`));
2456
- console.log(chalk7.dim(` ${t("status_install_time")}: ${inst.installTime}`));
2721
+ console.log(chalk9.dim(` ${inst.configRoot}`));
2722
+ console.log(chalk9.dim(` ${t("status_install_time")}: ${inst.installTime}`));
2457
2723
  console.log();
2458
2724
  }
2459
2725
  }
2460
2726
 
2461
2727
  // src/commands/install.ts
2462
2728
  import { confirm } from "@inquirer/prompts";
2463
- import chalk8 from "chalk";
2729
+ import chalk10 from "chalk";
2464
2730
  async function installCommand(names, options) {
2465
2731
  if (options.list) {
2466
2732
  try {
@@ -2471,79 +2737,138 @@ async function installCommand(names, options) {
2471
2737
  listAllSkills();
2472
2738
  return;
2473
2739
  }
2740
+ if (options.all) {
2741
+ const scanSpinner2 = createSpinner(t("loading_skills_list"));
2742
+ scanSpinner2.start();
2743
+ try {
2744
+ const scanRepoManager = createRepositoryManager();
2745
+ await scanRepoManager.ensureRepoAndScan();
2746
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
2747
+ } catch {
2748
+ scanSpinner2.warn(t("loading_skills_list_failed"));
2749
+ }
2750
+ const allSkills = getAllSkills();
2751
+ const skillIds = allSkills.map((s) => s.id);
2752
+ if (skillIds.length === 0) {
2753
+ logger.error(t("install_no_skill_selected"));
2754
+ return;
2755
+ }
2756
+ let tool2;
2757
+ if (options.tool) {
2758
+ tool2 = validateTool(options.tool);
2759
+ } else {
2760
+ const detected = await selectToolWithDetection();
2761
+ if (detected === "back" || detected === "cancel") return;
2762
+ tool2 = detected;
2763
+ }
2764
+ const level2 = options.level ? validateLevel(options.level) : "project";
2765
+ const repoManager = createRepositoryManager();
2766
+ const spinner = createSpinner(t("loading_plugin_data"));
2767
+ spinner.start();
2768
+ const repoPath = await repoManager.ensureRepo();
2769
+ spinner.succeed(t("loading_plugin_data_complete"));
2770
+ logger.info(`${t("skill_all_install_progress").replace("{count}", chalk10.bold(String(skillIds.length)))}`);
2771
+ const results = await installSkills(skillIds, tool2, level2, repoPath);
2772
+ let successCount = 0;
2773
+ let failCount = 0;
2774
+ const total = results.length;
2775
+ for (let i = 0; i < results.length; i++) {
2776
+ const result = results[i];
2777
+ const progress = `[${i + 1}/${total}]`;
2778
+ if (result.success) {
2779
+ logger.success(`${progress} ${result.skillId}`);
2780
+ successCount++;
2781
+ } else {
2782
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
2783
+ failCount++;
2784
+ }
2785
+ }
2786
+ const configRoot = getConfigRoot(tool2, level2);
2787
+ logger.blank();
2788
+ logger.success(`${t("skill_install_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)))}`);
2789
+ logger.blank();
2790
+ logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
2791
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
2792
+ logger.blank();
2793
+ return;
2794
+ }
2474
2795
  if (names.length === 0) {
2475
- const scanSpinner2 = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
2796
+ const scanSpinner2 = createSpinner(t("loading_skills_list"));
2476
2797
  scanSpinner2.start();
2477
2798
  try {
2478
2799
  const scanRepoManager = createRepositoryManager();
2479
2800
  await scanRepoManager.ensureRepoAndScan();
2480
- scanSpinner2.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
2801
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
2481
2802
  } catch {
2482
- scanSpinner2.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
2803
+ scanSpinner2.warn(t("loading_skills_list_failed"));
2483
2804
  }
2484
2805
  let tool2;
2485
2806
  if (options.tool) {
2486
- tool2 = options.tool;
2807
+ tool2 = validateTool(options.tool);
2487
2808
  } else {
2488
2809
  const detected = await selectToolWithDetection();
2489
- if (detected === "back") return;
2810
+ if (detected === "back" || detected === "cancel") return;
2490
2811
  tool2 = detected;
2491
2812
  }
2813
+ const level2 = options.level ? validateLevel(options.level) : "project";
2492
2814
  let step = 1;
2493
2815
  while (true) {
2494
2816
  switch (step) {
2495
2817
  case 1: {
2496
- const selectedSkills = await interactiveSkillSelect();
2818
+ const selectedSkills = await interactiveSkillSelect(tool2, level2);
2497
2819
  if (selectedSkills === "back") {
2498
2820
  const redetected = await selectToolWithDetection();
2499
- if (redetected === "back") return;
2821
+ if (redetected === "back" || redetected === "cancel") return;
2500
2822
  tool2 = redetected;
2501
2823
  break;
2502
2824
  }
2825
+ if (selectedSkills === "cancel") return;
2503
2826
  if (selectedSkills.length === 0) {
2504
- logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Skill");
2827
+ logger.info(t("install_no_skill_selected"));
2505
2828
  return;
2506
2829
  }
2507
2830
  names = selectedSkills;
2508
2831
  const repoManager = createRepositoryManager();
2509
- const spinner = createSpinner(t("install_cloning"));
2832
+ const spinner = createSpinner(t("loading_plugin_data"));
2510
2833
  spinner.start();
2511
2834
  const repoPath = await repoManager.ensureRepo();
2512
- spinner.succeed(t("install_repo_ready"));
2513
- logger.info(`${t("skill_install_progress")} ${chalk8.bold(names.length)} \u4E2A Skills...`);
2514
- const level2 = options.level || "project";
2835
+ spinner.succeed(t("loading_plugin_data_complete"));
2836
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(names.length)))}...`);
2515
2837
  const results = await installSkills(names, tool2, level2, repoPath);
2516
2838
  let successCount = 0;
2517
2839
  let failCount = 0;
2518
- for (const result of results) {
2840
+ const total = results.length;
2841
+ for (let i = 0; i < results.length; i++) {
2842
+ const result = results[i];
2843
+ const progress = `[${i + 1}/${total}]`;
2519
2844
  if (result.success) {
2520
- logger.success(`${result.skillId}`);
2845
+ logger.success(`${progress} ${result.skillId}`);
2521
2846
  successCount++;
2522
2847
  } else {
2523
- logger.error(`${result.skillId}: ${result.error}`);
2848
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
2524
2849
  failCount++;
2525
2850
  }
2526
2851
  }
2527
2852
  const configRoot = getConfigRoot(tool2, level2);
2528
2853
  logger.blank();
2529
- logger.success(`${t("skill_install_done")}: ${chalk8.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk8.red(failCount + " \u5931\u8D25") : chalk8.dim(failCount + " \u5931\u8D25")}`);
2854
+ logger.success(`${t("skill_install_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)))}`);
2530
2855
  logger.blank();
2531
- logger.info(`\u5B89\u88C5\u5230: ${chalk8.cyan(configRoot + "/skills/")}`);
2532
- logger.info(`\u542F\u52A8 ${chalk8.green(tool2)} \u5373\u53EF\u4F7F\u7528`);
2856
+ logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
2857
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
2533
2858
  logger.blank();
2534
2859
  return;
2535
2860
  }
2536
2861
  }
2537
2862
  }
2538
2863
  }
2539
- const scanSpinner = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
2864
+ const scanSpinner = createSpinner(t("loading_skills_list"));
2540
2865
  scanSpinner.start();
2541
2866
  try {
2542
2867
  const scanRepoManager = createRepositoryManager();
2543
2868
  await scanRepoManager.ensureRepoAndScan();
2544
- scanSpinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
2869
+ scanSpinner.succeed(t("loading_skills_list_complete"));
2545
2870
  } catch {
2546
- scanSpinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
2871
+ scanSpinner.warn(t("loading_skills_list_failed"));
2547
2872
  }
2548
2873
  const plugins = [];
2549
2874
  const skills = [];
@@ -2560,22 +2885,22 @@ async function installCommand(names, options) {
2560
2885
  }
2561
2886
  logger.error(`${t("error_plugin_not_found")}: ${name}`);
2562
2887
  const allPlugins = getAllPlugins();
2563
- logger.info("\u53EF\u7528\u63D2\u4EF6:");
2888
+ logger.info(t("available_plugins") + ":");
2564
2889
  for (const p of allPlugins) {
2565
2890
  logger.step(` ${p.id} (${p.aliases.join(", ")})`);
2566
2891
  }
2567
- logger.info(`\u53EF\u7528 Skills: ${chalk8.cyan("install-helper install --list")}`);
2892
+ logger.info(`${t("available_skills")}: ${chalk10.cyan("install-helper install --list")}`);
2568
2893
  return;
2569
2894
  }
2570
2895
  let tool;
2571
2896
  if (options.tool) {
2572
- tool = options.tool;
2897
+ tool = validateTool(options.tool);
2573
2898
  } else {
2574
2899
  const detected = await selectToolWithDetection();
2575
- if (detected === "back") return;
2900
+ if (detected === "back" || detected === "cancel") return;
2576
2901
  tool = detected;
2577
2902
  }
2578
- const level = options.level || "project";
2903
+ const level = options.level ? validateLevel(options.level) : "project";
2579
2904
  if (plugins.length > 0) {
2580
2905
  const configRoot = getConfigRoot(tool, level);
2581
2906
  const manifests = readAllManifests(configRoot);
@@ -2603,16 +2928,18 @@ async function installCommand(names, options) {
2603
2928
  }
2604
2929
  if (pluginsToInstall.length > 0) {
2605
2930
  const repoManager = createRepositoryManager();
2606
- const spinner = createSpinner(t("install_cloning"));
2931
+ const spinner = createSpinner(t("loading_plugin_data"));
2607
2932
  spinner.start();
2608
2933
  const repoPath = await repoManager.ensureRepo();
2609
- spinner.succeed(t("install_repo_ready"));
2934
+ spinner.succeed(t("loading_plugin_data_complete"));
2610
2935
  const allPlugins = getAllPlugins();
2611
2936
  const results = await installPlugins(
2612
2937
  pluginsToInstall,
2613
2938
  tool,
2614
2939
  level,
2615
- repoPath
2940
+ repoPath,
2941
+ void 0,
2942
+ options.yes
2616
2943
  );
2617
2944
  const summary = results.map((result) => {
2618
2945
  const plugin = allPlugins.find((p) => p.id === result.pluginId);
@@ -2630,32 +2957,35 @@ async function installCommand(names, options) {
2630
2957
  }
2631
2958
  if (skills.length > 0) {
2632
2959
  const repoManager = createRepositoryManager();
2633
- const spinner = createSpinner(t("install_cloning"));
2960
+ const spinner = createSpinner(t("loading_plugin_data"));
2634
2961
  spinner.start();
2635
2962
  const repoPath = await repoManager.ensureRepo();
2636
- spinner.succeed(t("install_repo_ready"));
2637
- logger.info(`${t("skill_install_progress")} ${chalk8.bold(skills.length)} \u4E2A Skills...`);
2963
+ spinner.succeed(t("loading_plugin_data_complete"));
2964
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(skills.length)))}...`);
2638
2965
  const results = await installSkills(skills, tool, level, repoPath);
2639
2966
  let successCount = 0;
2640
2967
  let failCount = 0;
2641
- for (const result of results) {
2968
+ const total = results.length;
2969
+ for (let i = 0; i < results.length; i++) {
2970
+ const result = results[i];
2971
+ const progress = `[${i + 1}/${total}]`;
2642
2972
  if (result.success) {
2643
- logger.success(`${result.skillId}`);
2973
+ logger.success(`${progress} ${result.skillId}`);
2644
2974
  successCount++;
2645
2975
  } else {
2646
- logger.error(`${result.skillId}: ${result.error}`);
2976
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
2647
2977
  failCount++;
2648
2978
  }
2649
2979
  }
2650
2980
  logger.blank();
2651
- logger.success(`${t("skill_install_done")}: ${chalk8.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk8.red(failCount + " \u5931\u8D25") : chalk8.dim(failCount + " \u5931\u8D25")}`);
2981
+ logger.success(`${t("skill_install_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)))}`);
2652
2982
  logger.blank();
2653
- logger.info(`\u5B89\u88C5\u5230: ${chalk8.cyan(getConfigRoot(tool, level) + "/skills/")}`);
2654
- logger.info(`\u542F\u52A8 ${chalk8.green(tool)} \u5373\u53EF\u4F7F\u7528`);
2983
+ logger.info(`${t("install_to")}: ${chalk10.cyan(getConfigRoot(tool, level) + "/skills/")}`);
2984
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool))}`);
2655
2985
  logger.blank();
2656
2986
  }
2657
2987
  }
2658
- async function installPlugins(pluginIds, tool, level, repoPath) {
2988
+ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes) {
2659
2989
  const results = [];
2660
2990
  const total = pluginIds.length;
2661
2991
  for (let i = 0; i < pluginIds.length; i++) {
@@ -2667,7 +2997,9 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
2667
2997
  pluginId,
2668
2998
  tool,
2669
2999
  level,
2670
- repoPath
3000
+ repoPath,
3001
+ installPath,
3002
+ yes
2671
3003
  });
2672
3004
  if (result.success) {
2673
3005
  spinner.succeed(
@@ -2683,11 +3015,11 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
2683
3015
  }
2684
3016
 
2685
3017
  // src/commands/uninstall.ts
2686
- import { existsSync as existsSync12, unlinkSync as unlinkSync4, rmdirSync as rmdirSync2, readdirSync as readdirSync5, lstatSync as lstatSync4 } from "fs";
2687
- import chalk9 from "chalk";
3018
+ import { existsSync as existsSync14, unlinkSync as unlinkSync5, rmdirSync as rmdirSync2, readdirSync as readdirSync6, lstatSync as lstatSync4 } from "fs";
3019
+ import chalk11 from "chalk";
2688
3020
  async function uninstallCommand(names, options) {
2689
- const tool = options.tool || "opencode";
2690
- const level = options.level || "project";
3021
+ const tool = options.tool ? validateTool(options.tool) : "opencode";
3022
+ const level = options.level ? validateLevel(options.level) : "project";
2691
3023
  const plugins = [];
2692
3024
  const skills = [];
2693
3025
  for (const name of names) {
@@ -2701,40 +3033,44 @@ async function uninstallCommand(names, options) {
2701
3033
  skills.push(skill.id);
2702
3034
  continue;
2703
3035
  }
2704
- logger.error(`\u672A\u627E\u5230: ${name}`);
3036
+ logger.error(`${t("uninstall_not_found")}: ${name}`);
2705
3037
  }
2706
3038
  for (const pluginId of plugins) {
2707
3039
  const plugin = findPlugin(pluginId);
2708
3040
  if (!plugin) continue;
2709
3041
  const record = readRecord(plugin.id);
2710
3042
  if (!record) {
2711
- logger.warn(`${plugin.displayName} \u672A\u627E\u5230\u5B89\u88C5\u8BB0\u5F55\uFF0C\u53EF\u80FD\u672A\u5B89\u88C5\u6216\u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F`);
2712
- logger.info(`\u5982\u9700\u5F3A\u5236\u6E05\u7406\uFF0C\u8BF7\u624B\u52A8\u5220\u9664 .opencode/ \u6216 .claude/ \u76EE\u5F55\u4E2D\u7684\u76F8\u5173\u6587\u4EF6`);
3043
+ logger.warn(`${plugin.displayName} ${t("uninstall_no_record")}`);
3044
+ logger.info(`${t("uninstall_no_record_hint")}`);
2713
3045
  continue;
2714
3046
  }
2715
- logger.info(`\u6B63\u5728\u5378\u8F7D ${plugin.displayName}...`);
3047
+ logger.info(`${t("uninstall_in_progress")} ${plugin.displayName}...`);
2716
3048
  let removedFiles = 0;
2717
3049
  let removedDirs = 0;
2718
3050
  for (const filePath of record.files) {
2719
3051
  try {
2720
- if (existsSync12(filePath) || isSymlink3(filePath)) {
2721
- unlinkSync4(filePath);
3052
+ if (existsSync14(filePath) || isSymlink3(filePath)) {
3053
+ unlinkSync5(filePath);
2722
3054
  const name = filePath.split("/").pop() || filePath;
2723
- logger.step(` \u79FB\u9664: ${name}`);
3055
+ logger.step(` ${t("uninstall_remove_label")}: ${name}`);
2724
3056
  removedFiles++;
2725
3057
  }
2726
3058
  } catch {
2727
3059
  }
2728
3060
  }
3061
+ const configRoot = getConfigRoot(record.tool, record.level);
2729
3062
  const sortedDirs = [...record.directories].sort((a, b) => b.length - a.length);
2730
3063
  for (const dirPath of sortedDirs) {
2731
3064
  try {
2732
- if (existsSync12(dirPath)) {
2733
- const entries = readdirSync5(dirPath);
3065
+ if (dirPath === configRoot) {
3066
+ continue;
3067
+ }
3068
+ if (existsSync14(dirPath)) {
3069
+ const entries = readdirSync6(dirPath);
2734
3070
  if (entries.length === 0) {
2735
3071
  rmdirSync2(dirPath);
2736
3072
  const name = dirPath.split("/").pop() || dirPath;
2737
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${name}/`);
3073
+ logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
2738
3074
  removedDirs++;
2739
3075
  }
2740
3076
  }
@@ -2743,15 +3079,32 @@ async function uninstallCommand(names, options) {
2743
3079
  }
2744
3080
  deleteRecord(plugin.id);
2745
3081
  removeInstalledPlugin(plugin.id);
3082
+ const backups = findBackups(configRoot);
3083
+ const otherBackups = backups.filter((b) => b.pluginId !== pluginId);
3084
+ if (otherBackups.length > 0) {
3085
+ const choice = await showRestorePrompt(otherBackups);
3086
+ if (choice !== "none") {
3087
+ const restored = restoreBackup(choice, configRoot, record.tool);
3088
+ if (restored) {
3089
+ const backupInfo = otherBackups.find((b) => b.filePath === choice);
3090
+ logger.success(`${t("backup_restore_success")}: ${backupInfo?.pluginName || ""}`);
3091
+ deleteBackup(choice);
3092
+ }
3093
+ }
3094
+ }
3095
+ const currentBackups = backups.filter((b) => b.pluginId === pluginId);
3096
+ for (const backup of currentBackups) {
3097
+ deleteBackup(backup.filePath);
3098
+ }
2746
3099
  const totalRemoved = removedFiles + removedDirs;
2747
3100
  if (totalRemoved > 0) {
2748
- logger.success(`\u5DF2\u5378\u8F7D ${plugin.displayName}\uFF08\u79FB\u9664 ${removedFiles} \u4E2A\u6587\u4EF6\uFF0C${removedDirs} \u4E2A\u76EE\u5F55\uFF09`);
3101
+ 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`);
2749
3102
  } else {
2750
- logger.warn(`${plugin.displayName} \u7684\u6587\u4EF6\u5DF2\u4E0D\u5B58\u5728\uFF0C\u5DF2\u6E05\u7406\u5B89\u88C5\u8BB0\u5F55`);
3103
+ logger.warn(`${plugin.displayName}${t("uninstall_files_gone")}`);
2751
3104
  }
2752
3105
  }
2753
3106
  if (skills.length > 0) {
2754
- logger.info(`\u6B63\u5728\u5378\u8F7D ${skills.length} \u4E2A Skills...`);
3107
+ logger.info(`${t("uninstall_skills_in_progress")} ${t("skill_count_format").replace("{count}", String(skills.length))}...`);
2755
3108
  const results = await uninstallSkills(skills, tool, level);
2756
3109
  let successCount = 0;
2757
3110
  let failCount = 0;
@@ -2763,7 +3116,7 @@ async function uninstallCommand(names, options) {
2763
3116
  }
2764
3117
  }
2765
3118
  logger.blank();
2766
- logger.success(`${t("skill_uninstall_done")}: ${chalk9.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk9.red(failCount + " \u5931\u8D25") : chalk9.dim(failCount + " \u5931\u8D25")}`);
3119
+ logger.success(`${t("skill_uninstall_done")}: ${chalk11.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk11.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk11.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2767
3120
  logger.blank();
2768
3121
  }
2769
3122
  }
@@ -2776,17 +3129,17 @@ function isSymlink3(path) {
2776
3129
  }
2777
3130
 
2778
3131
  // src/commands/update.ts
2779
- import chalk10 from "chalk";
3132
+ import chalk12 from "chalk";
2780
3133
  async function updateCommand(pluginNames, options) {
2781
3134
  let tool;
2782
3135
  if (options.tool) {
2783
- tool = options.tool;
3136
+ tool = validateTool(options.tool);
2784
3137
  } else {
2785
3138
  const detected = await selectToolWithDetection();
2786
- if (detected === "back") return;
3139
+ if (detected === "back" || detected === "cancel") return;
2787
3140
  tool = detected;
2788
3141
  }
2789
- const level = options.level || "project";
3142
+ const level = options.level ? validateLevel(options.level) : "project";
2790
3143
  let pluginsToUpdate = [];
2791
3144
  if (pluginNames.length === 0) {
2792
3145
  const configRoot = getConfigRoot(tool, level);
@@ -2794,7 +3147,7 @@ async function updateCommand(pluginNames, options) {
2794
3147
  pluginsToUpdate = manifests.map((m) => m.team);
2795
3148
  if (pluginsToUpdate.length === 0) {
2796
3149
  logger.info(t("update_no_plugins"));
2797
- logger.info(`\u4F7F\u7528 ${chalk10.cyan("install-helper install <plugin>")} \u5B89\u88C5\u63D2\u4EF6`);
3150
+ logger.info(t("update_install_hint").replace("{cmd}", chalk12.cyan("install-helper install <plugin>")));
2798
3151
  return;
2799
3152
  }
2800
3153
  } else {
@@ -2856,42 +3209,42 @@ async function updateCommand(pluginNames, options) {
2856
3209
  }
2857
3210
 
2858
3211
  // src/commands/info.ts
2859
- import chalk11 from "chalk";
2860
- import { existsSync as existsSync13, readdirSync as readdirSync6 } from "fs";
2861
- import { join as join12 } from "path";
3212
+ import chalk13 from "chalk";
3213
+ import { existsSync as existsSync15, readdirSync as readdirSync7 } from "fs";
3214
+ import { join as join15 } from "path";
2862
3215
  async function infoCommand(pluginName) {
2863
3216
  const plugin = findPlugin(pluginName);
2864
3217
  if (!plugin) {
2865
- console.log(chalk11.red(` ${t("info_not_found")}: ${pluginName}`));
3218
+ console.log(chalk13.red(` ${t("info_not_found")}: ${pluginName}`));
2866
3219
  return;
2867
3220
  }
2868
3221
  const installed = scanInstalled();
2869
3222
  const isInstalled = installed.some((p) => p.id === plugin.id);
2870
- const statusText = isInstalled ? chalk11.green(`\u2713 ${t("status_installed")}`) : chalk11.dim(`\u2014 ${t("status_not_installed")}`);
3223
+ const statusText = isInstalled ? chalk13.green(`\u2713 ${t("status_installed")}`) : chalk13.dim(`\u2014 ${t("status_not_installed")}`);
2871
3224
  console.log();
2872
- console.log(chalk11.bold(` ${plugin.displayName}`));
2873
- console.log(chalk11.dim(" " + "\u2500".repeat(40)));
3225
+ console.log(chalk13.bold(` ${plugin.displayName}`));
3226
+ console.log(chalk13.dim(" " + "\u2500".repeat(40)));
2874
3227
  console.log();
2875
- console.log(` ${chalk11.dim(t("info_description") + ":")} ${plugin.description}`);
2876
- console.log(` ${chalk11.dim("ID:")} ${plugin.id}`);
2877
- console.log(` ${chalk11.dim(t("info_status") + ":")} ${statusText}`);
2878
- console.log(` ${chalk11.dim(t("info_skills") + ":")} ${plugin.skills}`);
2879
- console.log(` ${chalk11.dim(t("info_agents") + ":")} ${plugin.agents}`);
2880
- console.log(` ${chalk11.dim("\u522B\u540D:")} ${plugin.aliases.join(", ")}`);
3228
+ console.log(` ${chalk13.dim(t("info_description") + ":")} ${plugin.description}`);
3229
+ console.log(` ${chalk13.dim("ID:")} ${plugin.id}`);
3230
+ console.log(` ${chalk13.dim(t("info_status") + ":")} ${statusText}`);
3231
+ console.log(` ${chalk13.dim(t("info_skills") + ":")} ${plugin.skills}`);
3232
+ console.log(` ${chalk13.dim(t("info_agents") + ":")} ${plugin.agents}`);
3233
+ console.log(` ${chalk13.dim(t("info_aliases") + ":")} ${plugin.aliases.join(", ")}`);
2881
3234
  console.log();
2882
3235
  try {
2883
3236
  const repoManager = createRepositoryManager();
2884
3237
  const repoPath = await repoManager.ensureRepo();
2885
- const quickstartPath = join12(repoPath, plugin.dir, "quickstart.md");
2886
- if (existsSync13(quickstartPath)) {
2887
- console.log(` ${chalk11.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
3238
+ const quickstartPath = join15(repoPath, plugin.dir, "quickstart.md");
3239
+ if (existsSync15(quickstartPath)) {
3240
+ console.log(` ${chalk13.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
2888
3241
  }
2889
- const agentsDir = join12(repoPath, plugin.dir, "agents");
2890
- if (existsSync13(agentsDir)) {
2891
- const agentFiles = readdirSync6(agentsDir).filter((f) => f.endsWith(".md"));
3242
+ const agentsDir = join15(repoPath, plugin.dir, "agents");
3243
+ if (existsSync15(agentsDir)) {
3244
+ const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md"));
2892
3245
  if (agentFiles.length > 0) {
2893
3246
  console.log();
2894
- console.log(` ${chalk11.bold("Agents:")}`);
3247
+ console.log(` ${chalk13.bold("Agents:")}`);
2895
3248
  for (const agent of agentFiles) {
2896
3249
  console.log(` \u2022 ${agent.replace(".md", "")}`);
2897
3250
  }
@@ -2903,39 +3256,39 @@ async function infoCommand(pluginName) {
2903
3256
  }
2904
3257
 
2905
3258
  // src/commands/lang.ts
2906
- import chalk12 from "chalk";
3259
+ import chalk14 from "chalk";
2907
3260
  async function langCommand(action, value) {
2908
3261
  const config = readConfig();
2909
3262
  if (!action || action === "show") {
2910
3263
  console.log();
2911
- console.log(chalk12.bold(` ${t("lang_title")}`));
2912
- console.log(chalk12.dim(" " + "\u2500".repeat(40)));
3264
+ console.log(chalk14.bold(` ${t("lang_title")}`));
3265
+ console.log(chalk14.dim(" " + "\u2500".repeat(40)));
2913
3266
  console.log();
2914
- console.log(` ${chalk12.dim(t("lang_current") + ":")} ${config.language}`);
3267
+ console.log(` ${chalk14.dim(t("lang_current") + ":")} ${config.language}`);
2915
3268
  console.log();
2916
- console.log(chalk12.dim(" \u652F\u6301\u7684\u8BED\u8A00: zh_CN, en_US"));
2917
- console.log(chalk12.dim(" \u7528\u6CD5: install-helper lang set <language>"));
3269
+ console.log(chalk14.dim(` ${t("lang_supported")}`));
3270
+ console.log(chalk14.dim(` ${t("lang_usage")}`));
2918
3271
  console.log();
2919
3272
  return;
2920
3273
  }
2921
3274
  if (action === "set") {
2922
3275
  if (!value) {
2923
- console.log(chalk12.red(" \u8BF7\u6307\u5B9A\u8BED\u8A00: install-helper lang set zh_CN"));
3276
+ console.log(chalk14.red(` ${t("lang_specify")}`));
2924
3277
  return;
2925
3278
  }
2926
3279
  if (value !== "zh_CN" && value !== "en_US") {
2927
- console.log(chalk12.red(` ${t("lang_invalid")}`));
3280
+ console.log(chalk14.red(` ${t("lang_invalid")}`));
2928
3281
  return;
2929
3282
  }
2930
3283
  updateConfig({ language: value });
2931
3284
  setLanguage(value);
2932
3285
  console.log();
2933
- console.log(chalk12.green(` \u2713 ${t("lang_set")} ${value}`));
3286
+ console.log(chalk14.green(` \u2713 ${t("lang_set")} ${value}`));
2934
3287
  console.log();
2935
3288
  return;
2936
3289
  }
2937
- console.log(chalk12.red(` \u672A\u77E5\u64CD\u4F5C: ${action}`));
2938
- console.log(chalk12.dim(" \u652F\u6301\u7684\u64CD\u4F5C: show, set"));
3290
+ console.log(chalk14.red(` ${t("lang_unknown_action")}: ${action}`));
3291
+ console.log(chalk14.dim(` ${t("lang_supported_actions")}`));
2939
3292
  }
2940
3293
 
2941
3294
  // src/cli.ts
@@ -2945,7 +3298,7 @@ function createCLI() {
2945
3298
  setLanguage(config.language);
2946
3299
  }
2947
3300
  const program2 = new Command();
2948
- program2.name("install-helper").description("Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.3");
3301
+ program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.31");
2949
3302
  program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
2950
3303
  await initCommand();
2951
3304
  });
@@ -2958,7 +3311,7 @@ function createCLI() {
2958
3311
  program2.command("status").description("Show installed plugins").action(async () => {
2959
3312
  await statusCommand();
2960
3313
  });
2961
- program2.command("install [names...]").description("Install plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, copilot)").option("-l, --level <level>", "Install level (project, global)", "project").option("-y, --yes", "Skip confirmation prompts").option("--list", "List all available skills by category").action(async (names, options) => {
3314
+ program2.command("install [names...]").description("Install plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, copilot)").option("-l, --level <level>", "Install level (project, global)", "project").option("-y, --yes", "Skip confirmation prompts").option("-a, --all", "Install ALL available skills").option("--list", "List all available skills by category").action(async (names, options) => {
2962
3315
  await installCommand(names, options);
2963
3316
  });
2964
3317
  program2.command("uninstall <names...>").description("Uninstall plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, copilot)").option("-l, --level <level>", "Install level (project, global)", "project").action(async (names, options) => {
@@ -2982,7 +3335,7 @@ function createCLI() {
2982
3335
  // src/index.ts
2983
3336
  process.on("uncaughtException", (error) => {
2984
3337
  if (error.name === "ExitPromptError") {
2985
- console.log("\n\u5DF2\u53D6\u6D88\u5B89\u88C5");
3338
+ console.log("\n" + t("init_cancelled"));
2986
3339
  process.exit(0);
2987
3340
  }
2988
3341
  throw error;