@cannbot-ai/install-helper 0.0.2-beta.4 → 0.0.2-beta.40

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,286 @@
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"
15
+ import { readdirSync, readFileSync as readFileSync2, existsSync } from "fs";
16
+ import { join as join2, dirname as dirname2 } from "path";
17
+ import { fileURLToPath as fileURLToPath2 } from "url";
18
+ import { parse as parseYaml } from "yaml";
19
+
20
+ // src/utils/logger.ts
21
+ import chalk2 from "chalk";
22
+ import ora from "ora";
23
+
24
+ // src/utils/i18n.ts
25
+ import { readFileSync } from "fs";
26
+ import { join, dirname } from "path";
27
+ import { fileURLToPath } from "url";
28
+ var __filename = fileURLToPath(import.meta.url);
29
+ var __dirname = dirname(__filename);
30
+ function loadLocale(locale) {
31
+ const localePath = join(__dirname, "locales", `${locale}.json`);
32
+ try {
33
+ const content = readFileSync(localePath, "utf-8");
34
+ return JSON.parse(content);
35
+ } catch {
36
+ const fallbackPath = join(__dirname, "locales", "zh_CN.json");
37
+ try {
38
+ const content = readFileSync(fallbackPath, "utf-8");
39
+ return JSON.parse(content);
40
+ } catch {
41
+ return {};
42
+ }
43
+ }
44
+ }
45
+ var currentLang = "zh_CN";
46
+ var messages = loadLocale("zh_CN");
47
+ function setLanguage(lang) {
48
+ currentLang = lang;
49
+ messages = loadLocale(lang);
50
+ }
51
+ function t(key) {
52
+ return messages[key] !== void 0 ? messages[key] : key;
53
+ }
54
+
55
+ // src/ui/theme.ts
56
+ import chalk from "chalk";
57
+ var selectTheme = {
58
+ helpMode: "never"
59
+ };
60
+ function isCJK(code) {
61
+ 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;
62
+ }
63
+ function isEmoji(code) {
64
+ return code >= 127744 && code <= 129791 || code >= 9728 && code <= 10175;
65
+ }
66
+ function getDisplayWidth(str) {
67
+ let width = 0;
68
+ for (const ch of str) {
69
+ const code = ch.codePointAt(0);
70
+ if (isCJK(code) || isEmoji(code)) {
71
+ width += 2;
72
+ } else {
73
+ width += 1;
74
+ }
75
+ }
76
+ return width;
77
+ }
78
+ function wrapDescription(text) {
79
+ const prefix = " \u{1F4A1} ";
80
+ const indent = " ";
81
+ const cols = process.stdout.columns || 120;
82
+ const maxLen = Math.max(20, cols - getDisplayWidth(prefix) - 2);
83
+ const tokens = [];
84
+ let i = 0;
85
+ while (i < text.length) {
86
+ const code = text[i].codePointAt(0);
87
+ if (isCJK(code)) {
88
+ tokens.push({ text: text[i], width: 2 });
89
+ i++;
90
+ } else {
91
+ let j = i;
92
+ while (j < text.length) {
93
+ const c = text[j].codePointAt(0);
94
+ if (isCJK(c)) break;
95
+ j++;
96
+ }
97
+ const word = text.slice(i, j);
98
+ tokens.push({ text: word, width: getDisplayWidth(word) });
99
+ i = j;
100
+ }
101
+ }
102
+ const lines = [];
103
+ let line = "";
104
+ let lineWidth = 0;
105
+ for (const token of tokens) {
106
+ if (lineWidth + token.width > maxLen && line) {
107
+ lines.push(line.trimEnd());
108
+ line = token.text;
109
+ lineWidth = token.width;
110
+ } else {
111
+ line += token.text;
112
+ lineWidth += token.width;
113
+ }
114
+ }
115
+ if (line) lines.push(line.trimEnd());
116
+ return lines.map((l, i2) => chalk.cyan(i2 === 0 ? prefix + l : indent + l)).join("\n");
117
+ }
118
+ var checkboxTheme = {
119
+ icon: {
120
+ checked: "\x1B[32m\u2611\x1B[22m",
121
+ unchecked: "\u2610",
122
+ cursor: "\u276F"
105
123
  },
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"
124
+ helpMode: "never",
125
+ style: {
126
+ description: (text) => wrapDescription(text)
115
127
  }
116
- ];
128
+ };
129
+
130
+ // src/utils/logger.ts
131
+ var logger = {
132
+ success: (msg) => console.log(chalk2.green("\u2713") + " " + msg),
133
+ error: (msg) => console.log(chalk2.red("\u2717") + " " + msg),
134
+ warn: (msg) => console.log(chalk2.yellow("\u26A0") + " " + msg),
135
+ info: (msg) => console.log(chalk2.cyan("\u2192") + " " + msg),
136
+ step: (msg) => console.log(chalk2.dim(msg)),
137
+ blank: () => console.log()
138
+ };
139
+ function createSpinner(text) {
140
+ return ora({ text, color: "cyan" });
141
+ }
142
+ function printBanner(subtitle) {
143
+ console.log(chalk2.cyan(`
144
+ ____ _ _ _ _ _ ____ _
145
+ / ___| / \\ | \\ | | \\ | | __ ) ___ | |_
146
+ | | / _ \\ | \\| | \\| | _ \\ / _ \\| __|
147
+ | |___ / ___ \\| |\\ | |\\ | |_) | (_) | |_
148
+ \\____/_/ \\_\\_| \\_|_| \\_|____/ \\___/ \\__|
149
+ `));
150
+ if (subtitle) {
151
+ console.log(chalk2.bold(` ${subtitle}`));
152
+ console.log();
153
+ }
154
+ }
155
+ function printBoxTitle(title, width = 65) {
156
+ const border = "\u2550".repeat(width);
157
+ const displayWidth = getDisplayWidth(title);
158
+ const totalPadding = width - displayWidth;
159
+ const leftPadding = Math.floor(totalPadding / 2);
160
+ const rightPadding = totalPadding - leftPadding;
161
+ const paddedTitle = " ".repeat(Math.max(0, leftPadding)) + title + " ".repeat(Math.max(0, rightPadding));
162
+ console.log();
163
+ console.log(chalk2.cyan.bold(` \u2554${border}\u2557`));
164
+ console.log(chalk2.cyan.bold(` \u2551`) + chalk2.cyan.bold(paddedTitle) + chalk2.cyan.bold(`\u2551`));
165
+ console.log(chalk2.cyan.bold(` \u255A${border}\u255D`));
166
+ console.log();
167
+ }
168
+ function showOperationHints(isCheckbox = false) {
169
+ if (isCheckbox) {
170
+ 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
171
+ `);
172
+ } else {
173
+ console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}
174
+ `);
175
+ }
176
+ }
177
+
178
+ // src/utils/fs-helpers.ts
179
+ import { lstatSync, statSync } from "fs";
180
+ function isSymlink(path) {
181
+ try {
182
+ return lstatSync(path).isSymbolicLink();
183
+ } catch {
184
+ return false;
185
+ }
186
+ }
187
+ function isDirectory(path) {
188
+ try {
189
+ return statSync(path).isDirectory();
190
+ } catch {
191
+ return false;
192
+ }
193
+ }
194
+
195
+ // src/core/registry.ts
196
+ var __filename2 = fileURLToPath2(import.meta.url);
197
+ var __dirname2 = dirname2(__filename2);
198
+ function findPluginsDir() {
199
+ const candidates = [
200
+ join2(__dirname2, "plugins.d"),
201
+ join2(__dirname2, "..", "plugins.d"),
202
+ join2(__dirname2, "..", "..", "plugins.d")
203
+ ];
204
+ for (const dir of candidates) {
205
+ if (existsSync(dir)) return dir;
206
+ }
207
+ return candidates[0];
208
+ }
209
+ function loadPluginsFromYaml() {
210
+ const pluginsDir = findPluginsDir();
211
+ const defaultsPath = join2(pluginsDir, "_defaults.yml");
212
+ let defaults = {};
213
+ try {
214
+ defaults = parseYaml(readFileSync2(defaultsPath, "utf-8")) || {};
215
+ } catch {
216
+ }
217
+ const plugins = [];
218
+ try {
219
+ for (const file of readdirSync(pluginsDir)) {
220
+ if (file.startsWith("_") || !file.endsWith(".yml")) continue;
221
+ try {
222
+ const content = parseYaml(readFileSync2(join2(pluginsDir, file), "utf-8"));
223
+ if (!content || !content.id) continue;
224
+ plugins.push({
225
+ id: content.id,
226
+ dir: content.dir,
227
+ displayName: content.displayName || content.id,
228
+ script: content.script || defaults.script || "init.sh",
229
+ aliases: content.aliases || [],
230
+ skills: content.skills ?? defaults.skills ?? 0,
231
+ agents: content.agents ?? defaults.agents ?? 0,
232
+ description: content.description || ""
233
+ });
234
+ } catch {
235
+ logger.warn(t("plugin_yaml_error").replace("{file}", file));
236
+ }
237
+ }
238
+ } catch {
239
+ }
240
+ plugins.sort(
241
+ (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
242
+ );
243
+ return plugins;
244
+ }
245
+ var PLUGIN_REGISTRY = loadPluginsFromYaml();
246
+ var PLUGIN_DIRS = ["plugins-official", "plugins-community"];
247
+ function mergeDynamicPlugins(repoPath) {
248
+ if (!existsSync(repoPath)) return;
249
+ const existingIds = new Set(PLUGIN_REGISTRY.map((p) => p.id));
250
+ const dynamic = [];
251
+ for (const pluginDir of PLUGIN_DIRS) {
252
+ const parent = join2(repoPath, pluginDir);
253
+ if (!existsSync(parent)) continue;
254
+ let entries = [];
255
+ try {
256
+ entries = readdirSync(parent);
257
+ } catch {
258
+ continue;
259
+ }
260
+ for (const entry of entries) {
261
+ const pluginPath = join2(parent, entry);
262
+ if (!isDirectory(pluginPath)) continue;
263
+ if (entry === "install-helper") continue;
264
+ const initSh = join2(pluginPath, "init.sh");
265
+ if (!existsSync(initSh)) continue;
266
+ const pluginId = entry;
267
+ if (existingIds.has(pluginId)) continue;
268
+ dynamic.push({
269
+ id: pluginId,
270
+ dir: `${pluginDir}/${entry}`,
271
+ displayName: entry,
272
+ script: "init.sh",
273
+ aliases: [],
274
+ skills: 0,
275
+ agents: 0,
276
+ description: ""
277
+ });
278
+ existingIds.add(pluginId);
279
+ }
280
+ }
281
+ if (dynamic.length > 0) {
282
+ PLUGIN_REGISTRY = [...PLUGIN_REGISTRY, ...dynamic].sort(
283
+ (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
284
+ );
285
+ }
286
+ }
117
287
  function findPlugin(query) {
118
288
  const normalized = query.toLowerCase().trim();
119
289
  const exactMatch = PLUGIN_REGISTRY.find(
@@ -137,19 +307,37 @@ function getPluginById(id) {
137
307
 
138
308
  // src/core/detector.ts
139
309
  import { execa } from "execa";
140
- import { existsSync as existsSync2 } from "fs";
310
+ import { existsSync as existsSync3 } from "fs";
141
311
  import { homedir as homedir2 } from "os";
142
- import { join as join2 } from "path";
312
+ import { join as join4 } from "path";
143
313
 
144
314
  // src/utils/paths.ts
145
315
  import { homedir } from "os";
146
- import { join } from "path";
147
- import { existsSync } from "fs";
316
+ import { join as join3 } from "path";
317
+ import { existsSync as existsSync2 } from "fs";
318
+ var VALID_TOOLS = ["opencode", "claude", "trae", "cursor", "copilot"];
319
+ var VALID_LEVELS = ["project", "global"];
320
+ function validateTool(tool) {
321
+ if (!VALID_TOOLS.includes(tool)) {
322
+ throw new Error(
323
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
324
+ );
325
+ }
326
+ return tool;
327
+ }
328
+ function validateLevel(level) {
329
+ if (!VALID_LEVELS.includes(level)) {
330
+ throw new Error(
331
+ t("error_invalid_level").replace("{level}", level).replace("{levels}", VALID_LEVELS.join(", "))
332
+ );
333
+ }
334
+ return level;
335
+ }
148
336
  function detectTraeVariant() {
149
337
  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";
338
+ if (existsSync2(join3(home, ".trae-cn"))) return "ide";
339
+ if (existsSync2(join3(home, ".marscode"))) return "plugin";
340
+ if (existsSync2(join3(home, ".traecli"))) return "cli";
153
341
  return "unknown";
154
342
  }
155
343
  function getConfigRoot(tool, level, base) {
@@ -157,66 +345,77 @@ function getConfigRoot(tool, level, base) {
157
345
  if (level === "global") {
158
346
  switch (tool) {
159
347
  case "opencode":
160
- return join(home, ".config", "opencode");
348
+ return join3(home, ".config", "opencode");
161
349
  case "claude":
162
- return join(home, ".claude");
350
+ return join3(home, ".claude");
163
351
  case "trae": {
164
352
  const variant = detectTraeVariant();
165
353
  switch (variant) {
166
354
  case "plugin":
167
- return join(home, ".marscode");
355
+ return join3(home, ".marscode");
168
356
  case "cli":
169
- return join(home, ".traecli");
357
+ return join3(home, ".traecli");
170
358
  default:
171
- return join(home, ".trae-cn");
359
+ return join3(home, ".trae-cn");
172
360
  }
173
361
  }
174
362
  case "cursor":
175
- return join(home, ".cursor");
363
+ return join3(home, ".cursor");
176
364
  case "copilot":
177
- return join(home, ".copilot");
365
+ return join3(home, ".copilot");
366
+ default:
367
+ throw new Error(
368
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
369
+ );
178
370
  }
179
371
  }
180
372
  const baseDir = base || process.cwd();
181
373
  switch (tool) {
182
374
  case "opencode":
183
- return join(baseDir, ".opencode");
375
+ return join3(baseDir, ".opencode");
184
376
  case "claude":
185
- return join(baseDir, ".claude");
377
+ return join3(baseDir, ".claude");
186
378
  case "trae": {
187
379
  const variant = detectTraeVariant();
188
380
  switch (variant) {
189
381
  case "plugin":
190
- return join(baseDir, ".marscode");
382
+ return join3(baseDir, ".marscode");
191
383
  case "cli":
192
- return join(baseDir, ".traecli");
384
+ return join3(baseDir, ".traecli");
193
385
  default:
194
- return join(baseDir, ".trae");
386
+ return join3(baseDir, ".trae");
195
387
  }
196
388
  }
197
389
  case "cursor":
198
- return join(baseDir, ".cursor");
390
+ return join3(baseDir, ".cursor");
199
391
  case "copilot":
200
- return join(baseDir, ".github");
392
+ return join3(baseDir, ".github");
393
+ default:
394
+ throw new Error(
395
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
396
+ );
201
397
  }
202
398
  }
203
399
  function getConfigFileName(tool) {
204
400
  return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
205
401
  }
402
+ function getAgentsFileName(tool) {
403
+ return getConfigFileName(tool);
404
+ }
206
405
  function getSkillsDir(configRoot) {
207
- return join(configRoot, "skills");
406
+ return join3(configRoot, "skills");
208
407
  }
209
408
  function getAgentsDir(configRoot) {
210
- return join(configRoot, "agents");
409
+ return join3(configRoot, "agents");
211
410
  }
212
411
  function getManifestPath(configRoot) {
213
- return join(configRoot, "cannbot-manifest.json");
412
+ return join3(configRoot, "cannbot-manifest.json");
214
413
  }
215
414
  function getCannbotConfigDir() {
216
- return join(homedir(), ".cannbot");
415
+ return join3(homedir(), ".cannbot");
217
416
  }
218
417
  function getCannbotRepoPath() {
219
- return join(getCannbotConfigDir(), "repo");
418
+ return join3(getCannbotConfigDir(), "repo");
220
419
  }
221
420
 
222
421
  // src/core/detector.ts
@@ -232,8 +431,9 @@ async function getCommandVersion(cmd, args = ["--version"]) {
232
431
  }
233
432
  async function getCommandPath(cmd) {
234
433
  try {
235
- const result = await execa("sh", ["-c", `command -v ${cmd}`], { timeout: 5e3 });
236
- return result.stdout.trim();
434
+ const whichCmd = process.platform === "win32" ? "where" : "which";
435
+ const result = await execa(whichCmd, [cmd], { timeout: 5e3 });
436
+ return result.stdout.trim().split("\n")[0];
237
437
  } catch {
238
438
  return void 0;
239
439
  }
@@ -254,12 +454,12 @@ async function detectTrae() {
254
454
  const home = homedir2();
255
455
  const variant = detectTraeVariant();
256
456
  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");
457
+ if (variant === "ide" && existsSync3(join4(home, ".trae-cn"))) {
458
+ path = join4(home, ".trae-cn");
459
+ } else if (variant === "plugin" && existsSync3(join4(home, ".marscode"))) {
460
+ path = join4(home, ".marscode");
461
+ } else if (variant === "cli" && existsSync3(join4(home, ".traecli"))) {
462
+ path = join4(home, ".traecli");
263
463
  } else if (variant === "unknown") {
264
464
  const cmdPath = await getCommandPath("trae");
265
465
  if (cmdPath) path = cmdPath;
@@ -275,7 +475,7 @@ async function detectCursor() {
275
475
  }
276
476
  if (process.platform === "darwin") {
277
477
  const appPath = "/Applications/Cursor.app";
278
- if (existsSync2(appPath)) {
478
+ if (existsSync3(appPath)) {
279
479
  return { name: "cursor", path: appPath };
280
480
  }
281
481
  }
@@ -319,17 +519,17 @@ function getToolDisplayName(tool) {
319
519
  return names[tool];
320
520
  }
321
521
  function getAllTools() {
322
- return ["opencode", "claude", "trae", "cursor", "copilot"];
522
+ return VALID_TOOLS;
323
523
  }
324
524
 
325
525
  // src/core/manifest.ts
326
- import { existsSync as existsSync3, readFileSync } from "fs";
327
- import { join as join3 } from "path";
526
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
527
+ import { join as join5 } from "path";
328
528
  function readManifest(configRoot) {
329
529
  const manifestPath = getManifestPath(configRoot);
330
- if (existsSync3(manifestPath)) {
530
+ if (existsSync4(manifestPath)) {
331
531
  try {
332
- const content = readFileSync(manifestPath, "utf-8");
532
+ const content = readFileSync3(manifestPath, "utf-8");
333
533
  return JSON.parse(content);
334
534
  } catch {
335
535
  }
@@ -340,18 +540,18 @@ function readAllManifests(configRoot) {
340
540
  const manifests = [];
341
541
  const plugins = getAllPlugins();
342
542
  const standardPath = getManifestPath(configRoot);
343
- if (existsSync3(standardPath)) {
543
+ if (existsSync4(standardPath)) {
344
544
  try {
345
- const content = readFileSync(standardPath, "utf-8");
545
+ const content = readFileSync3(standardPath, "utf-8");
346
546
  manifests.push(JSON.parse(content));
347
547
  } catch {
348
548
  }
349
549
  }
350
550
  for (const plugin of plugins) {
351
- const pluginManifestPath = join3(configRoot, `${plugin.id}-manifest.json`);
352
- if (existsSync3(pluginManifestPath)) {
551
+ const pluginManifestPath = join5(configRoot, `${plugin.id}-manifest.json`);
552
+ if (existsSync4(pluginManifestPath)) {
353
553
  try {
354
- const content = readFileSync(pluginManifestPath, "utf-8");
554
+ const content = readFileSync3(pluginManifestPath, "utf-8");
355
555
  manifests.push(JSON.parse(content));
356
556
  } catch {
357
557
  }
@@ -362,7 +562,7 @@ function readAllManifests(configRoot) {
362
562
  function scanInstalled() {
363
563
  const plugins = getAllPlugins();
364
564
  const installed = [];
365
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
565
+ const tools = VALID_TOOLS;
366
566
  const levels = ["project", "global"];
367
567
  for (const tool of tools) {
368
568
  for (const level of levels) {
@@ -371,6 +571,9 @@ function scanInstalled() {
371
571
  for (const manifest of manifests) {
372
572
  const plugin = plugins.find((p) => p.id === manifest.team);
373
573
  if (!plugin) continue;
574
+ if (!verifyPluginFilesExist(configRoot, manifest, tool, level)) {
575
+ continue;
576
+ }
374
577
  const alreadyAdded = installed.some(
375
578
  (p) => p.id === plugin.id && p.tool === tool && p.level === level
376
579
  );
@@ -390,275 +593,74 @@ function scanInstalled() {
390
593
  }
391
594
  return installed;
392
595
  }
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];
577
- }
578
-
579
- // src/utils/logger.ts
580
- import chalk from "chalk";
581
- import ora from "ora";
582
- var logger = {
583
- success: (msg) => console.log(chalk.green("\u2713") + " " + msg),
584
- error: (msg) => console.log(chalk.red("\u2717") + " " + msg),
585
- warn: (msg) => console.log(chalk.yellow("\u26A0") + " " + msg),
586
- info: (msg) => console.log(chalk.cyan("\u2192") + " " + msg),
587
- step: (msg) => console.log(chalk.dim(msg)),
588
- blank: () => console.log()
589
- };
590
- function createSpinner(text) {
591
- return ora({ text, color: "cyan" });
592
- }
593
- function printBanner(subtitle) {
594
- console.log(chalk.cyan(`
595
- ____ _ _ _ _ _ ____ _
596
- / ___| / \\ | \\ | | \\ | | __ ) ___ | |_
597
- | | / _ \\ | \\| | \\| | _ \\ / _ \\| __|
598
- | |___ / ___ \\| |\\ | |\\ | |_) | (_) | |_
599
- \\____/_/ \\_\\_| \\_|_| \\_|____/ \\___/ \\__|
600
- `));
601
- if (subtitle) {
602
- console.log(chalk.bold(` ${subtitle}`));
603
- console.log();
596
+ function verifyPluginFilesExist(configRoot, manifest, tool, level) {
597
+ const skillsDir = join5(configRoot, "skills");
598
+ const agentsDir = join5(configRoot, "agents");
599
+ if (manifest.installed_skills && manifest.installed_skills.length > 0) {
600
+ const hasAnySkill = manifest.installed_skills.some((skillId) => {
601
+ const skillPath = join5(skillsDir, skillId);
602
+ return existsSync4(skillPath);
603
+ });
604
+ if (!hasAnySkill) return false;
604
605
  }
605
- }
606
- function getDisplayWidth(str) {
607
- let width = 0;
608
- for (const ch of str) {
609
- const code = ch.codePointAt(0);
610
- if (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) {
611
- width += 2;
612
- } else {
613
- width += 1;
614
- }
606
+ if (manifest.installed_agents && manifest.installed_agents.length > 0) {
607
+ const hasAnyAgent = manifest.installed_agents.some((agentId) => {
608
+ const agentPath = join5(agentsDir, agentId);
609
+ return existsSync4(agentPath);
610
+ });
611
+ if (!hasAnyAgent) return false;
615
612
  }
616
- return width;
617
- }
618
- function printBoxTitle(title, width = 65) {
619
- const border = "\u2550".repeat(width);
620
- const displayWidth = getDisplayWidth(title);
621
- const totalPadding = width - displayWidth;
622
- const leftPadding = Math.floor(totalPadding / 2);
623
- const rightPadding = totalPadding - leftPadding;
624
- const paddedTitle = " ".repeat(Math.max(0, leftPadding)) + title + " ".repeat(Math.max(0, rightPadding));
625
- console.log();
626
- console.log(chalk.cyan.bold(` \u2554${border}\u2557`));
627
- console.log(chalk.cyan.bold(` \u2551`) + chalk.cyan.bold(paddedTitle) + chalk.cyan.bold(`\u2551`));
628
- console.log(chalk.cyan.bold(` \u255A${border}\u255D`));
629
- console.log();
630
- }
631
- function showOperationHints(isCheckbox = false) {
632
- 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
634
- `);
613
+ let agentsMdPath;
614
+ if (tool === "opencode" && level === "project") {
615
+ agentsMdPath = join5(configRoot, "..", "AGENTS.md");
635
616
  } else {
636
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4
637
- `);
617
+ agentsMdPath = join5(configRoot, "AGENTS.md");
638
618
  }
619
+ if (!existsSync4(agentsMdPath)) return false;
620
+ return true;
639
621
  }
640
622
 
641
623
  // src/utils/config.ts
642
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
643
- import { join as join4 } from "path";
644
- import { homedir as homedir3 } from "os";
624
+ import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync4 } from "fs";
625
+ import { join as join6 } from "path";
645
626
  import { parse, stringify } from "yaml";
627
+
628
+ // src/utils/fs.ts
629
+ import { writeFileSync, renameSync, existsSync as existsSync5, unlinkSync } from "fs";
630
+ import { dirname as dirname3 } from "path";
631
+ function atomicWriteFileSync(filePath, data, encoding = "utf-8") {
632
+ const dir = dirname3(filePath);
633
+ if (!existsSync5(dir)) {
634
+ throw new Error(`Directory does not exist: ${dir}`);
635
+ }
636
+ const tmpPath = `${filePath}.tmp`;
637
+ writeFileSync(tmpPath, data, encoding);
638
+ if (existsSync5(filePath)) {
639
+ try {
640
+ unlinkSync(filePath);
641
+ } catch {
642
+ }
643
+ }
644
+ renameSync(tmpPath, filePath);
645
+ }
646
+
647
+ // src/utils/config.ts
646
648
  function getConfigDir() {
647
- return join4(homedir3(), ".cannbot");
649
+ return getCannbotConfigDir();
648
650
  }
649
651
  function getConfigPath() {
650
- return join4(getConfigDir(), "config.yaml");
652
+ return join6(getConfigDir(), "config.yaml");
651
653
  }
652
654
  function readConfig() {
653
655
  const configPath = getConfigPath();
654
- if (!existsSync4(configPath)) {
656
+ if (!existsSync6(configPath)) {
655
657
  return {
656
658
  language: "zh_CN",
657
659
  installedPlugins: []
658
660
  };
659
661
  }
660
662
  try {
661
- const content = readFileSync2(configPath, "utf-8");
663
+ const content = readFileSync4(configPath, "utf-8");
662
664
  const parsed = parse(content);
663
665
  return {
664
666
  language: parsed.language || "zh_CN",
@@ -668,6 +670,7 @@ function readConfig() {
668
670
  installedPlugins: parsed.installedPlugins || []
669
671
  };
670
672
  } catch {
673
+ logger.warn(t("config_corrupted"));
671
674
  return {
672
675
  language: "zh_CN",
673
676
  installedPlugins: []
@@ -676,12 +679,12 @@ function readConfig() {
676
679
  }
677
680
  function writeConfig(config) {
678
681
  const configDir = getConfigDir();
679
- if (!existsSync4(configDir)) {
682
+ if (!existsSync6(configDir)) {
680
683
  mkdirSync(configDir, { recursive: true });
681
684
  }
682
685
  const configPath = getConfigPath();
683
686
  const content = stringify(config);
684
- writeFileSync(configPath, content, "utf-8");
687
+ atomicWriteFileSync(configPath, content);
685
688
  }
686
689
  function updateConfig(updates) {
687
690
  const config = readConfig();
@@ -704,22 +707,11 @@ function removeInstalledPlugin(pluginId) {
704
707
  writeConfig(config);
705
708
  }
706
709
 
707
- // src/ui/theme.ts
708
- var selectTheme = {
709
- helpMode: "never"
710
- };
711
- var checkboxTheme = {
712
- icon: {
713
- checked: "\x1B[32m\u2611\x1B[22m",
714
- unchecked: "\u2610",
715
- cursor: "\u276F"
716
- },
717
- helpMode: "never"
718
- };
719
-
720
- // src/ui/wizard.ts
710
+ // src/utils/constants.ts
721
711
  var BACK = "__back__";
722
712
  var CANCEL = "__cancel__";
713
+
714
+ // src/ui/wizard.ts
723
715
  async function selectToolWithDetection() {
724
716
  const config = readConfig();
725
717
  logger.blank();
@@ -789,7 +781,7 @@ async function runWizard() {
789
781
  step = 2;
790
782
  break;
791
783
  }
792
- if (result === CANCEL || result === false) {
784
+ if (result === CANCEL) {
793
785
  return { language: config.language || "zh_CN", tool: selectedTool, level, plugins: [], confirmed: false };
794
786
  }
795
787
  updateConfig({ lastTool: selectedTool, lastLevel: level });
@@ -799,11 +791,11 @@ async function runWizard() {
799
791
  }
800
792
  }
801
793
  async function stepTool(detectedTools, lastTool) {
802
- printBoxTitle("[1/4] \u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177");
794
+ printBoxTitle(t("wizard_step_1_title"));
803
795
  if (detectedTools.length === 0) {
804
796
  logger.error(t("error_tool_not_found"));
805
797
  const choices2 = [
806
- ...["opencode", "claude", "trae", "cursor", "copilot"].map((tool) => ({
798
+ ...VALID_TOOLS.map((tool) => ({
807
799
  name: `> ${getToolDisplayName(tool)}`,
808
800
  value: tool
809
801
  })),
@@ -874,10 +866,10 @@ async function stepToolManual(lastTool) {
874
866
  return result;
875
867
  }
876
868
  async function stepLevel(lastLevel) {
877
- printBoxTitle("[2/4] \u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E");
869
+ printBoxTitle(t("wizard_step_2_title"));
878
870
  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" },
871
+ { name: `> project \u2014 ${t("install_level_project")}`, value: "project" },
872
+ { name: `> global \u2014 ${t("install_level_global")}`, value: "global" },
881
873
  new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
882
874
  { name: "<- " + t("wizard_back"), value: BACK },
883
875
  { name: "x " + t("wizard_cancel"), value: CANCEL }
@@ -895,7 +887,7 @@ async function stepLevel(lastLevel) {
895
887
  return result;
896
888
  }
897
889
  async function stepPlugins(tool, level) {
898
- printBoxTitle("[3/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6");
890
+ printBoxTitle(t("wizard_step_3_title"));
899
891
  const plugins = getAllPlugins();
900
892
  const configRoot = getConfigRoot(tool, level);
901
893
  const manifests = readAllManifests(configRoot);
@@ -904,14 +896,12 @@ async function stepPlugins(tool, level) {
904
896
  const isInstalled = installedSet.has(p.id);
905
897
  const suffix = isInstalled ? ` [${t("wizard_already_installed")}]` : "";
906
898
  return {
907
- name: `${p.displayName}${suffix} \u2014 ${p.description}`,
899
+ name: `${chalk3.cyan(p.displayName)}${suffix}`,
908
900
  value: p.id,
909
- checked: false
901
+ checked: false,
902
+ description: p.description
910
903
  };
911
904
  });
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
905
  showOperationHints(true);
916
906
  const selected = await checkbox({
917
907
  message: t("wizard_select_plugins"),
@@ -921,9 +911,7 @@ async function stepPlugins(tool, level) {
921
911
  theme: checkboxTheme,
922
912
  pageSize: 15
923
913
  });
924
- if (selected.includes(BACK)) return BACK;
925
- if (selected.includes(CANCEL)) return CANCEL;
926
- const pluginIds = selected.filter((v) => v !== BACK && v !== CANCEL);
914
+ const pluginIds = selected;
927
915
  if (pluginIds.length === 0) {
928
916
  const action = await select({
929
917
  message: t("wizard_no_selection"),
@@ -941,17 +929,17 @@ async function stepPlugins(tool, level) {
941
929
  return pluginIds;
942
930
  }
943
931
  async function stepConfirm(tool, level, plugins) {
944
- printBoxTitle("[4/4] \u786E\u8BA4\u5B89\u88C5");
932
+ printBoxTitle(t("wizard_step_4_title"));
945
933
  const allPlugins = getAllPlugins();
946
934
  const selectedPlugins = plugins.map((id) => allPlugins.find((p) => p.id === id));
947
935
  const displayPath = getConfigRoot(tool, level);
948
936
  const toolName = getToolDisplayName(tool);
949
937
  const levelText = level === "project" ? "project" : "global";
950
- 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`);
951
- logger.info(`\u5B89\u88C5\u8DEF\u5F84: ${chalk2.cyan(displayPath)}`);
938
+ logger.info(t("wizard_confirm_install_format").replace("{count}", chalk3.bold(String(plugins.length))).replace("{tool}", chalk3.green(toolName)).replace("{level}", chalk3.cyan(levelText)));
939
+ logger.info(`${t("wizard_install_path")}: ${chalk3.cyan(displayPath)}`);
952
940
  for (const plugin of selectedPlugins) {
953
941
  if (plugin) {
954
- logger.step(` \u2022 ${chalk2.bold(plugin.displayName)}`);
942
+ logger.step(` \u2022 ${chalk3.bold(plugin.displayName)}`);
955
943
  }
956
944
  }
957
945
  logger.blank();
@@ -969,14 +957,19 @@ async function stepConfirm(tool, level, plugins) {
969
957
  }
970
958
 
971
959
  // src/core/repository.ts
972
- import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
973
- import { join as join6 } from "path";
960
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
961
+ import { join as join8, dirname as dirname5 } from "path";
962
+ import { fileURLToPath as fileURLToPath4 } from "url";
974
963
  import { execa as execa2 } from "execa";
964
+ import { parse as parseYaml3 } from "yaml";
975
965
 
976
966
  // src/core/scanner.ts
977
- import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
978
- import { join as join5, dirname } from "path";
979
- import { parse as parseYaml } from "yaml";
967
+ import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync5, mkdirSync as mkdirSync2 } from "fs";
968
+ import { join as join7, dirname as dirname4 } from "path";
969
+ import { fileURLToPath as fileURLToPath3 } from "url";
970
+ import { parse as parseYaml2 } from "yaml";
971
+ var __filename3 = fileURLToPath3(import.meta.url);
972
+ var __dirname3 = dirname4(__filename3);
980
973
  var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
981
974
  "node_modules",
982
975
  ".git",
@@ -997,46 +990,70 @@ var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
997
990
  "docs",
998
991
  "scripts"
999
992
  ]);
1000
- var SKILL_SCAN_DIRS = [
1001
- "ops",
1002
- "model",
1003
- "graph",
1004
- "infra",
1005
- "ops-lab"
1006
- ];
1007
- var PLUGIN_SCAN_DIRS = [
1008
- "plugins-official",
1009
- "plugins-community"
1010
- ];
993
+ function loadScanConfig() {
994
+ const configPath = join7(__dirname3, "config", "repository.yaml");
995
+ try {
996
+ const config = parseYaml2(readFileSync5(configPath, "utf-8"));
997
+ const ttlHours = config.scanCacheTtlHours || 24;
998
+ return {
999
+ skillDirs: config.scanDirs || ["ops", "model", "graph", "infra", "ops-lab", "runtime"],
1000
+ pluginDirs: config.pluginDirs || ["plugins-official", "plugins-community"],
1001
+ cacheTtlMs: ttlHours * 60 * 60 * 1e3
1002
+ };
1003
+ } catch {
1004
+ return {
1005
+ skillDirs: ["ops", "model", "graph", "infra", "ops-lab", "runtime"],
1006
+ pluginDirs: ["plugins-official", "plugins-community"],
1007
+ cacheTtlMs: 24 * 60 * 60 * 1e3
1008
+ };
1009
+ }
1010
+ }
1011
+ var scanConfig = loadScanConfig();
1012
+ var SKILL_SCAN_DIRS = scanConfig.skillDirs;
1013
+ var PLUGIN_SCAN_DIRS = scanConfig.pluginDirs;
1011
1014
  function scanSkills(repoPath) {
1012
1015
  const skills = [];
1013
1016
  const seen = /* @__PURE__ */ new Set();
1014
1017
  for (const dir of SKILL_SCAN_DIRS) {
1015
- const fullPath = join5(repoPath, dir);
1016
- if (!existsSync5(fullPath)) continue;
1018
+ const fullPath = join7(repoPath, dir);
1019
+ if (!existsSync7(fullPath)) continue;
1017
1020
  scanDirectory(fullPath, repoPath, dir, skills, seen);
1018
1021
  }
1019
1022
  for (const pluginDir of PLUGIN_SCAN_DIRS) {
1020
- const pluginParent = join5(repoPath, pluginDir);
1021
- if (!existsSync5(pluginParent)) continue;
1023
+ const pluginParent = join7(repoPath, pluginDir);
1024
+ if (!existsSync7(pluginParent)) continue;
1022
1025
  for (const plugin of safeReaddir(pluginParent)) {
1023
- const pluginPath = join5(pluginParent, plugin);
1026
+ const pluginPath = join7(pluginParent, plugin);
1024
1027
  if (!isDirectory(pluginPath)) continue;
1025
- const skillsDir = join5(pluginPath, "skills");
1026
- if (existsSync5(skillsDir)) {
1028
+ const skillsDir = join7(pluginPath, "skills");
1029
+ if (existsSync7(skillsDir)) {
1027
1030
  scanDirectory(skillsDir, repoPath, `${pluginDir}/${plugin}/skills`, skills, seen);
1028
1031
  }
1029
- const skillDir = join5(pluginPath, "skill");
1030
- if (existsSync5(skillDir)) {
1032
+ const skillDir = join7(pluginPath, "skill");
1033
+ if (existsSync7(skillDir)) {
1031
1034
  scanDirectory(skillDir, repoPath, `${pluginDir}/${plugin}/skill`, skills, seen);
1032
1035
  }
1036
+ const rootSkillMd = join7(pluginPath, "SKILL.md");
1037
+ const initSh = join7(pluginPath, "init.sh");
1038
+ if (existsSync7(rootSkillMd) && !existsSync7(initSh)) {
1039
+ const frontmatter = parseFrontmatter(rootSkillMd);
1040
+ if (frontmatter && !seen.has(frontmatter.name)) {
1041
+ seen.add(frontmatter.name);
1042
+ skills.push({
1043
+ id: frontmatter.name,
1044
+ description: frontmatter.description,
1045
+ source: `${pluginDir}/${plugin}`,
1046
+ filePath: rootSkillMd
1047
+ });
1048
+ }
1049
+ }
1033
1050
  }
1034
1051
  }
1035
1052
  return skills;
1036
1053
  }
1037
1054
  function parseFrontmatter(filePath) {
1038
1055
  try {
1039
- const content = readFileSync3(filePath, "utf-8");
1056
+ const content = readFileSync5(filePath, "utf-8");
1040
1057
  const lines = content.split("\n");
1041
1058
  if (lines.length < 3) return null;
1042
1059
  if (lines[0].trim() !== "---") return null;
@@ -1049,11 +1066,15 @@ function parseFrontmatter(filePath) {
1049
1066
  }
1050
1067
  if (endIndex === -1) return null;
1051
1068
  const yamlBlock = lines.slice(1, endIndex).map((l) => l.replace(/\r$/, "")).join("\n");
1052
- const parsed = parseYaml(yamlBlock);
1069
+ const parsed = parseYaml2(yamlBlock);
1053
1070
  if (!parsed || typeof parsed !== "object") return null;
1054
1071
  if (!parsed.name || typeof parsed.name !== "string") return null;
1072
+ const trimmedName = parsed.name.trim();
1073
+ if (!trimmedName || /[\/\\]|\.\./.test(trimmedName) || !/^[a-zA-Z0-9._-]+$/.test(trimmedName)) {
1074
+ return null;
1075
+ }
1055
1076
  return {
1056
- name: parsed.name.trim(),
1077
+ name: trimmedName,
1057
1078
  description: (parsed.description || "").trim()
1058
1079
  };
1059
1080
  } catch {
@@ -1062,17 +1083,17 @@ function parseFrontmatter(filePath) {
1062
1083
  }
1063
1084
  function getCurrentCommit(repoPath) {
1064
1085
  try {
1065
- const headFile = join5(repoPath, ".git", "HEAD");
1066
- if (!existsSync5(headFile)) return "unknown";
1067
- const headContent = readFileSync3(headFile, "utf-8").trim();
1086
+ const headFile = join7(repoPath, ".git", "HEAD");
1087
+ if (!existsSync7(headFile)) return "unknown";
1088
+ const headContent = readFileSync5(headFile, "utf-8").trim();
1068
1089
  if (headContent.startsWith("ref: ")) {
1069
- const refPath = join5(repoPath, ".git", headContent.slice(5));
1070
- if (existsSync5(refPath)) {
1071
- return readFileSync3(refPath, "utf-8").trim();
1090
+ const refPath = join7(repoPath, ".git", headContent.slice(5));
1091
+ if (existsSync7(refPath)) {
1092
+ return readFileSync5(refPath, "utf-8").trim();
1072
1093
  }
1073
- const packedRefsPath = join5(repoPath, ".git", "packed-refs");
1074
- if (existsSync5(packedRefsPath)) {
1075
- const packedRefs = readFileSync3(packedRefsPath, "utf-8");
1094
+ const packedRefsPath = join7(repoPath, ".git", "packed-refs");
1095
+ if (existsSync7(packedRefsPath)) {
1096
+ const packedRefs = readFileSync5(packedRefsPath, "utf-8");
1076
1097
  const refName = headContent.slice(5);
1077
1098
  const lines = packedRefs.split("\n");
1078
1099
  for (const line of lines) {
@@ -1090,12 +1111,12 @@ function getCurrentCommit(repoPath) {
1090
1111
  function readScanCache() {
1091
1112
  try {
1092
1113
  const cachePath = getCachePath();
1093
- if (!existsSync5(cachePath)) return null;
1094
- const content = readFileSync3(cachePath, "utf-8");
1114
+ if (!existsSync7(cachePath)) return null;
1115
+ const content = readFileSync5(cachePath, "utf-8");
1095
1116
  const cache = JSON.parse(content);
1096
1117
  if (!cache.skills || !cache.timestamp) return null;
1097
1118
  const age = Date.now() - cache.timestamp;
1098
- if (age > 24 * 60 * 60 * 1e3) return null;
1119
+ if (age > scanConfig.cacheTtlMs) return null;
1099
1120
  return cache;
1100
1121
  } catch {
1101
1122
  return null;
@@ -1104,39 +1125,24 @@ function readScanCache() {
1104
1125
  function writeScanCache(cache) {
1105
1126
  try {
1106
1127
  const cachePath = getCachePath();
1107
- const cacheDir = dirname(cachePath);
1108
- if (!existsSync5(cacheDir)) {
1128
+ const cacheDir = dirname4(cachePath);
1129
+ if (!existsSync7(cacheDir)) {
1109
1130
  mkdirSync2(cacheDir, { recursive: true });
1110
1131
  }
1111
- writeFileSync2(cachePath, JSON.stringify(cache, null, 2));
1132
+ atomicWriteFileSync(cachePath, JSON.stringify(cache, null, 2));
1112
1133
  } catch {
1113
1134
  }
1114
1135
  }
1115
1136
  function getCachePath() {
1116
- const homeDir = process.env.HOME || process.env.USERPROFILE || "/tmp";
1117
- return join5(homeDir, ".cannbot", "scan-cache.json");
1137
+ return join7(getCannbotConfigDir(), "scan-cache.json");
1118
1138
  }
1119
1139
  function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1120
- const selfSkillMd = join5(dirPath, "SKILL.md");
1121
- if (existsSync5(selfSkillMd)) {
1122
- const frontmatter = parseFrontmatter(selfSkillMd);
1123
- if (frontmatter && !seen.has(frontmatter.name)) {
1124
- seen.add(frontmatter.name);
1125
- skills.push({
1126
- id: frontmatter.name,
1127
- description: frontmatter.description,
1128
- source: sourcePrefix,
1129
- filePath: selfSkillMd
1130
- });
1131
- }
1132
- return;
1133
- }
1134
1140
  for (const entry of safeReaddir(dirPath)) {
1135
- const fullPath = join5(dirPath, entry);
1141
+ const fullPath = join7(dirPath, entry);
1136
1142
  if (EXCLUDE_DIRS.has(entry)) continue;
1137
1143
  if (!isDirectory(fullPath)) continue;
1138
- const skillMd = join5(fullPath, "SKILL.md");
1139
- if (existsSync5(skillMd)) {
1144
+ const skillMd = join7(fullPath, "SKILL.md");
1145
+ if (existsSync7(skillMd)) {
1140
1146
  const frontmatter = parseFrontmatter(skillMd);
1141
1147
  if (frontmatter && !seen.has(frontmatter.name)) {
1142
1148
  seen.add(frontmatter.name);
@@ -1149,38 +1155,32 @@ function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1149
1155
  }
1150
1156
  continue;
1151
1157
  }
1152
- scanDirectory(fullPath, repoPath, sourcePrefix, skills, seen);
1158
+ scanDirectory(fullPath, repoPath, sourcePrefix + "/" + entry, skills, seen);
1153
1159
  }
1154
1160
  }
1155
1161
  function safeReaddir(dir) {
1156
1162
  try {
1157
- return readdirSync(dir);
1163
+ return readdirSync2(dir);
1158
1164
  } catch {
1159
1165
  return [];
1160
1166
  }
1161
1167
  }
1162
- function isDirectory(path) {
1163
- try {
1164
- return statSync(path).isDirectory();
1165
- } catch {
1166
- return false;
1167
- }
1168
- }
1169
1168
 
1170
1169
  // src/core/skill-registry.ts
1171
1170
  var CATEGORY_DEFS = [
1172
- { id: "knowledge", name: "\u77E5\u8BC6\u4E0E\u53C2\u8003" },
1173
- { id: "env-tools", name: "\u73AF\u5883\u4E0E\u5DE5\u5177" },
1174
- { id: "debug", name: "\u8C03\u8BD5\u4E0E\u8BCA\u65AD" },
1175
- { id: "testing", name: "\u6D4B\u8BD5\u4E0E\u8D28\u91CF" },
1176
- { id: "ascendc", name: "AscendC \u5F00\u53D1" },
1177
- { id: "pypto", name: "PyPTO \u5F00\u53D1" },
1178
- { id: "tilelang", name: "TileLang \u5F00\u53D1" },
1179
- { id: "triton", name: "Triton \u5F00\u53D1" },
1180
- { id: "model", name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316" },
1181
- { id: "graph", name: "\u56FE\u6A21\u5F0F" },
1182
- { id: "platform", name: "\u5E73\u53F0\u5DE5\u5177" },
1183
- { id: "other", name: "\u5176\u4ED6 Skills" }
1171
+ { id: "knowledge", name: "\u77E5\u8BC6\u4E0E\u53C2\u8003", nameEn: "Knowledge & Reference" },
1172
+ { id: "env-tools", name: "\u73AF\u5883\u4E0E\u5DE5\u5177", nameEn: "Environment & Tools" },
1173
+ { id: "debug", name: "\u8C03\u8BD5\u4E0E\u8BCA\u65AD", nameEn: "Debug & Diagnostics" },
1174
+ { id: "testing", name: "\u6D4B\u8BD5\u4E0E\u8D28\u91CF", nameEn: "Testing & Quality" },
1175
+ { id: "ascendc", name: "AscendC \u5F00\u53D1", nameEn: "AscendC Development" },
1176
+ { id: "pypto", name: "PyPTO \u5F00\u53D1", nameEn: "PyPTO Development" },
1177
+ { id: "tilelang", name: "TileLang \u5F00\u53D1", nameEn: "TileLang Development" },
1178
+ { id: "triton", name: "Triton \u5F00\u53D1", nameEn: "Triton Development" },
1179
+ { id: "model", name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316", nameEn: "Model Inference Optimization" },
1180
+ { id: "runtime", name: "Runtime \u8FC1\u79FB", nameEn: "Runtime Migration" },
1181
+ { id: "graph", name: "\u56FE\u6A21\u5F0F", nameEn: "Graph Mode" },
1182
+ { id: "platform", name: "\u5E73\u53F0\u5DE5\u5177", nameEn: "Platform Tools" },
1183
+ { id: "other", name: "\u5176\u4ED6 Skills", nameEn: "Other Skills" }
1184
1184
  ];
1185
1185
  var dynamicSkills = [];
1186
1186
  var initialized = false;
@@ -1188,7 +1188,8 @@ function initFromScan(scanned) {
1188
1188
  dynamicSkills = scanned.map((s) => ({
1189
1189
  id: s.id,
1190
1190
  description: s.description,
1191
- source: s.source
1191
+ source: s.source,
1192
+ filePath: s.filePath
1192
1193
  }));
1193
1194
  initialized = true;
1194
1195
  }
@@ -1264,9 +1265,7 @@ var STATIC_SKILL_CATEGORIES = [
1264
1265
  { id: "catlass-op-develop", description: "Catlass \u7B97\u5B50\u5F00\u53D1", source: "ops" },
1265
1266
  { id: "catlass-op-perf-tune", description: "Catlass \u6027\u80FD\u8C03\u4F18", source: "ops" },
1266
1267
  { id: "cuda2ascend-simt", description: "CUDA \u8FC1\u79FB\u5230 Ascend C SIMT", source: "ops-lab" },
1267
- { id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" },
1268
- { id: "ops-registry-invoke-workflow", description: "\u6CE8\u518C\u8C03\u7528\u5DE5\u4F5C\u6D41", source: "plugins-official/ops-registry-invoke" },
1269
- { id: "ops-easyasc-dsl", description: "EasyASC DSL \u7B97\u5B50\u5F00\u53D1", source: "plugins-community/ops-easyasc-dsl/skill" }
1268
+ { id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" }
1270
1269
  ]
1271
1270
  },
1272
1271
  {
@@ -1323,7 +1322,9 @@ var STATIC_SKILL_CATEGORIES = [
1323
1322
  { id: "model-infer-prefetch", description: "\u6743\u91CD\u9884\u53D6\u9002\u914D", source: "model" },
1324
1323
  { id: "model-infer-superkernel", description: "SuperKernel \u9002\u914D", source: "model" },
1325
1324
  { id: "model-infer-precision-debug", description: "NPU \u63A8\u7406\u7CBE\u5EA6\u8BCA\u65AD", source: "model" },
1326
- { id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" }
1325
+ { id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" },
1326
+ { id: "model-infer-harmony", description: "\u7AEF\u4FA7\u9E3F\u8499 ASR \u91CF\u5316\u8F6C\u6362\u4E0E\u6253\u5305", source: "model" },
1327
+ { 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" }
1327
1328
  ]
1328
1329
  },
1329
1330
  {
@@ -1339,6 +1340,13 @@ var STATIC_SKILL_CATEGORIES = [
1339
1340
  { id: "torch-custom-ops-guide", description: "\u81EA\u5B9A\u4E49\u7B97\u5B50\u5165\u56FE\u6307\u5357", source: "graph" }
1340
1341
  ]
1341
1342
  },
1343
+ {
1344
+ id: "runtime",
1345
+ name: "Runtime \u8FC1\u79FB",
1346
+ skills: [
1347
+ { id: "runtime_migration", description: "CUDA \u5E94\u7528\u8FC1\u79FB\u5230 CANN \u5E73\u53F0\u6307\u5357", source: "runtime" }
1348
+ ]
1349
+ },
1342
1350
  {
1343
1351
  id: "platform",
1344
1352
  name: "\u5E73\u53F0\u5DE5\u5177",
@@ -1382,9 +1390,10 @@ function getAllSkills() {
1382
1390
  }
1383
1391
  function getAllCategories() {
1384
1392
  const skills = getActiveSkills();
1393
+ const isEn = t("list_name") === "Name";
1385
1394
  return CATEGORY_DEFS.map((def) => ({
1386
1395
  id: def.id,
1387
- name: def.name,
1396
+ name: isEn ? def.nameEn : def.name,
1388
1397
  skills: skills.filter((s) => {
1389
1398
  const catId = CATEGORY_MAP[s.id];
1390
1399
  if (catId) return catId === def.id;
@@ -1394,7 +1403,21 @@ function getAllCategories() {
1394
1403
  }
1395
1404
 
1396
1405
  // src/core/repository.ts
1397
- var REPO_URL = "https://gitcode.com/cann/cannbot-skills.git";
1406
+ var __filename4 = fileURLToPath4(import.meta.url);
1407
+ var __dirname4 = dirname5(__filename4);
1408
+ function loadRepoUrl() {
1409
+ if (process.env.CANNBOT_REPO_URL) {
1410
+ return process.env.CANNBOT_REPO_URL;
1411
+ }
1412
+ const configPath = join8(__dirname4, "config", "repository.yaml");
1413
+ try {
1414
+ const config = parseYaml3(readFileSync6(configPath, "utf-8"));
1415
+ return config.repository?.url || "https://gitcode.com/cann/cannbot-skills.git";
1416
+ } catch {
1417
+ return "https://gitcode.com/cann/cannbot-skills.git";
1418
+ }
1419
+ }
1420
+ var REPO_URL = loadRepoUrl();
1398
1421
  var RepositoryManager = class {
1399
1422
  repoPath;
1400
1423
  constructor(customPath) {
@@ -1419,10 +1442,13 @@ var RepositoryManager = class {
1419
1442
  this.repoPath = cachedPath;
1420
1443
  return cachedPath;
1421
1444
  }
1422
- return await this.cloneRepo();
1445
+ const clonedPath = await this.cloneRepo();
1446
+ return clonedPath;
1423
1447
  }
1424
1448
  async ensureRepoAndScan() {
1425
1449
  const repoPath = await this.ensureRepo();
1450
+ await this.updateRepo();
1451
+ mergeDynamicPlugins(repoPath);
1426
1452
  const cache = readScanCache();
1427
1453
  if (cache && cache.repoCommit === getCurrentCommit(repoPath)) {
1428
1454
  initFromScan(cache.skills);
@@ -1442,7 +1468,9 @@ var RepositoryManager = class {
1442
1468
  try {
1443
1469
  await execa2("git", ["pull", "--quiet"], { cwd: repoPath, timeout: 3e4 });
1444
1470
  } catch {
1471
+ logger.warn(t("repo_pull_failed"));
1445
1472
  }
1473
+ mergeDynamicPlugins(repoPath);
1446
1474
  }
1447
1475
  getRepoPath() {
1448
1476
  if (!this.repoPath) {
@@ -1451,18 +1479,18 @@ var RepositoryManager = class {
1451
1479
  return this.repoPath;
1452
1480
  }
1453
1481
  isValidRepo(path) {
1454
- if (!existsSync6(path)) return false;
1455
- const gitDir = join6(path, ".git");
1456
- const pluginsDir = join6(path, "plugins-official");
1457
- return existsSync6(gitDir) && existsSync6(pluginsDir);
1482
+ if (!existsSync8(path)) return false;
1483
+ const gitDir = join8(path, ".git");
1484
+ const pluginsDir = join8(path, "plugins-official");
1485
+ return existsSync8(gitDir) && existsSync8(pluginsDir);
1458
1486
  }
1459
1487
  async cloneRepo() {
1460
1488
  const configDir = getCannbotConfigDir();
1461
- if (!existsSync6(configDir)) {
1489
+ if (!existsSync8(configDir)) {
1462
1490
  mkdirSync3(configDir, { recursive: true });
1463
1491
  }
1464
1492
  const targetPath = getCannbotRepoPath();
1465
- if (existsSync6(targetPath)) {
1493
+ if (existsSync8(targetPath)) {
1466
1494
  if (this.isValidRepo(targetPath)) {
1467
1495
  this.repoPath = targetPath;
1468
1496
  return targetPath;
@@ -1488,102 +1516,102 @@ function createRepositoryManager(customPath) {
1488
1516
  }
1489
1517
 
1490
1518
  // src/core/installer.ts
1491
- import { existsSync as existsSync8 } from "fs";
1492
- import { join as join8 } from "path";
1493
- import { execa as execa3 } from "execa";
1519
+ import { existsSync as existsSync12 } from "fs";
1520
+ import { join as join12 } from "path";
1521
+ import { execa as execa4 } from "execa";
1494
1522
 
1495
1523
  // src/core/record.ts
1496
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, lstatSync, unlinkSync } from "fs";
1497
- import { join as join7 } from "path";
1524
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
1525
+ import { join as join9 } from "path";
1498
1526
  function getInstallsDir() {
1499
- return join7(getCannbotConfigDir(), "installs");
1527
+ return join9(getCannbotConfigDir(), "installs");
1500
1528
  }
1501
1529
  function getRecordPath(pluginId) {
1502
- return join7(getInstallsDir(), `${pluginId}.json`);
1530
+ return join9(getInstallsDir(), `${pluginId}.json`);
1503
1531
  }
1504
1532
  function readRecord(pluginId) {
1505
1533
  const recordPath = getRecordPath(pluginId);
1506
- if (!existsSync7(recordPath)) {
1534
+ if (!existsSync9(recordPath)) {
1507
1535
  return null;
1508
1536
  }
1509
1537
  try {
1510
- const content = readFileSync4(recordPath, "utf-8");
1538
+ const content = readFileSync7(recordPath, "utf-8");
1511
1539
  return JSON.parse(content);
1512
1540
  } catch {
1541
+ logger.warn(t("record_corrupted").replace("{file}", recordPath));
1513
1542
  return null;
1514
1543
  }
1515
1544
  }
1516
1545
  function writeRecord(record) {
1517
1546
  const installsDir = getInstallsDir();
1518
- if (!existsSync7(installsDir)) {
1547
+ if (!existsSync9(installsDir)) {
1519
1548
  mkdirSync4(installsDir, { recursive: true });
1520
1549
  }
1521
1550
  const recordPath = getRecordPath(record.pluginId);
1522
- writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
1551
+ atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
1523
1552
  }
1524
1553
  function deleteRecord(pluginId) {
1525
1554
  const recordPath = getRecordPath(pluginId);
1526
- if (existsSync7(recordPath)) {
1527
- unlinkSync(recordPath);
1555
+ if (existsSync9(recordPath)) {
1556
+ unlinkSync2(recordPath);
1528
1557
  }
1529
1558
  }
1530
1559
  function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest) {
1531
1560
  const files = [];
1532
1561
  const directories = [];
1533
1562
  if (manifest) {
1534
- const skillsDir = join7(configRoot, "skills");
1535
- if (existsSync7(skillsDir)) {
1563
+ const skillsDir = join9(configRoot, "skills");
1564
+ if (existsSync9(skillsDir)) {
1536
1565
  directories.push(skillsDir);
1537
1566
  for (const skillName of manifest.installed_skills || []) {
1538
- const skillPath = join7(skillsDir, skillName);
1539
- if (existsSync7(skillPath) || isSymlink(skillPath)) {
1567
+ const skillPath = join9(skillsDir, skillName);
1568
+ if (existsSync9(skillPath) || isSymlink(skillPath)) {
1540
1569
  files.push(skillPath);
1541
1570
  }
1542
1571
  }
1543
1572
  }
1544
- const agentsDir = join7(configRoot, "agents");
1545
- if (existsSync7(agentsDir)) {
1573
+ const agentsDir = join9(configRoot, "agents");
1574
+ if (existsSync9(agentsDir)) {
1546
1575
  directories.push(agentsDir);
1547
1576
  for (const agentName of manifest.installed_agents || []) {
1548
- const agentPath = join7(agentsDir, agentName);
1549
- const agentPathMd = join7(agentsDir, agentName + ".md");
1550
- if (existsSync7(agentPath) || isSymlink(agentPath)) {
1577
+ const agentPath = join9(agentsDir, agentName);
1578
+ const agentPathMd = join9(agentsDir, agentName + ".md");
1579
+ if (existsSync9(agentPath) || isSymlink(agentPath)) {
1551
1580
  files.push(agentPath);
1552
- } else if (existsSync7(agentPathMd) || isSymlink(agentPathMd)) {
1581
+ } else if (existsSync9(agentPathMd) || isSymlink(agentPathMd)) {
1553
1582
  files.push(agentPathMd);
1554
1583
  }
1555
1584
  }
1556
1585
  }
1557
1586
  }
1558
- const workflowsLink = join7(configRoot, "workflows");
1587
+ const workflowsLink = join9(configRoot, "workflows");
1559
1588
  if (isSymlink(workflowsLink)) {
1560
1589
  files.push(workflowsLink);
1561
1590
  }
1562
- const manifestPath = join7(configRoot, "cannbot-manifest.json");
1563
- if (existsSync7(manifestPath)) {
1591
+ const manifestPath = join9(configRoot, "cannbot-manifest.json");
1592
+ if (existsSync9(manifestPath)) {
1564
1593
  files.push(manifestPath);
1565
1594
  }
1566
- const pluginManifestPath = join7(configRoot, `${pluginId}-manifest.json`);
1567
- if (existsSync7(pluginManifestPath)) {
1595
+ const pluginManifestPath = join9(configRoot, `${pluginId}-manifest.json`);
1596
+ if (existsSync9(pluginManifestPath)) {
1568
1597
  files.push(pluginManifestPath);
1569
1598
  }
1570
1599
  const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1571
- const configFilePath = level === "project" ? join7(installPath, configFileName) : join7(configRoot, configFileName);
1572
- if (existsSync7(configFilePath)) {
1600
+ const configFilePath = level === "project" ? join9(installPath, configFileName) : join9(configRoot, configFileName);
1601
+ if (existsSync9(configFilePath)) {
1573
1602
  files.push(configFilePath);
1574
1603
  }
1575
1604
  const repoLinks = ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
1576
1605
  for (const repoName of repoLinks) {
1577
- const repoLinkPath = join7(installPath, repoName);
1606
+ const repoLinkPath = join9(installPath, repoName);
1578
1607
  if (isSymlink(repoLinkPath)) {
1579
1608
  files.push(repoLinkPath);
1580
1609
  }
1581
- const repoLinkInConfig = join7(configRoot, repoName);
1610
+ const repoLinkInConfig = join9(configRoot, repoName);
1582
1611
  if (isSymlink(repoLinkInConfig)) {
1583
1612
  files.push(repoLinkInConfig);
1584
1613
  }
1585
1614
  }
1586
- directories.push(configRoot);
1587
1615
  return {
1588
1616
  pluginId,
1589
1617
  displayName,
@@ -1596,71 +1624,418 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
1596
1624
  directories
1597
1625
  };
1598
1626
  }
1599
- function isSymlink(path) {
1627
+ function getSkillRecordPath() {
1628
+ return join9(getInstallsDir(), "skills.json");
1629
+ }
1630
+ function readSkillRecord() {
1631
+ const recordPath = getSkillRecordPath();
1632
+ if (!existsSync9(recordPath)) {
1633
+ return {};
1634
+ }
1600
1635
  try {
1601
- return lstatSync(path).isSymbolicLink();
1636
+ const content = readFileSync7(recordPath, "utf-8");
1637
+ return JSON.parse(content);
1638
+ } catch {
1639
+ logger.warn(t("skill_record_corrupted"));
1640
+ return {};
1641
+ }
1642
+ }
1643
+ function writeSkillRecord(record) {
1644
+ const installsDir = getInstallsDir();
1645
+ if (!existsSync9(installsDir)) {
1646
+ mkdirSync4(installsDir, { recursive: true });
1647
+ }
1648
+ const recordPath = getSkillRecordPath();
1649
+ atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
1650
+ }
1651
+ function addSkillsToRecord(skillIds, tool, level, installPath) {
1652
+ const record = readSkillRecord();
1653
+ if (!record[tool]) record[tool] = {};
1654
+ if (!record[tool][level]) record[tool][level] = {};
1655
+ if (!record[tool][level][installPath]) {
1656
+ record[tool][level][installPath] = { skills: [], installTime: "" };
1657
+ }
1658
+ const entry = record[tool][level][installPath];
1659
+ for (const id of skillIds) {
1660
+ if (!entry.skills.includes(id)) {
1661
+ entry.skills.push(id);
1662
+ }
1663
+ }
1664
+ entry.installTime = (/* @__PURE__ */ new Date()).toISOString();
1665
+ writeSkillRecord(record);
1666
+ }
1667
+ function removeSkillsFromRecord(skillIds, tool, level, installPath) {
1668
+ const record = readSkillRecord();
1669
+ if (!record[tool]?.[level]?.[installPath]) return;
1670
+ const entry = record[tool][level][installPath];
1671
+ entry.skills = entry.skills.filter((id) => !skillIds.includes(id));
1672
+ if (entry.skills.length === 0) {
1673
+ delete record[tool][level][installPath];
1674
+ if (Object.keys(record[tool][level]).length === 0) {
1675
+ delete record[tool][level];
1676
+ if (Object.keys(record[tool]).length === 0) {
1677
+ delete record[tool];
1678
+ }
1679
+ }
1680
+ }
1681
+ writeSkillRecord(record);
1682
+ }
1683
+ function getInstalledSkills(tool, level, installPath) {
1684
+ const record = readSkillRecord();
1685
+ const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
1686
+ const configRoot = getConfigRoot(tool, level);
1687
+ const skillsDir = join9(configRoot, "skills");
1688
+ return recordedSkills.filter((skillId) => {
1689
+ const skillPath = join9(skillsDir, skillId);
1690
+ return existsSync9(skillPath) || isSymlink(skillPath);
1691
+ });
1692
+ }
1693
+
1694
+ // src/core/backup.ts
1695
+ import { existsSync as existsSync10, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync3 } from "fs";
1696
+ import { join as join10 } from "path";
1697
+ function detectCurrentPlugin(configRoot, tool) {
1698
+ const manifest = readManifest(configRoot);
1699
+ if (manifest?.team) {
1700
+ const plugin = findPlugin(manifest.team);
1701
+ if (plugin) {
1702
+ return {
1703
+ pluginId: plugin.id,
1704
+ pluginName: plugin.displayName
1705
+ };
1706
+ }
1707
+ }
1708
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
1709
+ if (!existsSync10(agentsFile)) {
1710
+ return null;
1711
+ }
1712
+ const allRecords = readAllRecords();
1713
+ for (const record of allRecords) {
1714
+ if (record.configRoot === configRoot && record.tool === tool) {
1715
+ const plugin = findPlugin(record.pluginId);
1716
+ if (plugin) {
1717
+ return {
1718
+ pluginId: plugin.id,
1719
+ pluginName: plugin.displayName
1720
+ };
1721
+ }
1722
+ }
1723
+ }
1724
+ return null;
1725
+ }
1726
+ function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
1727
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
1728
+ if (!existsSync10(agentsFile)) {
1729
+ return null;
1730
+ }
1731
+ const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
1732
+ const backupFileName = `${getAgentsFileName(tool)}.cannbot-backup.${fromPluginId}.${timestamp}`;
1733
+ const backupPath = join10(configRoot, backupFileName);
1734
+ try {
1735
+ copyFileSync(agentsFile, backupPath);
1736
+ return {
1737
+ filePath: backupPath,
1738
+ pluginId: fromPluginId,
1739
+ pluginName: fromPluginName,
1740
+ backupTime: timestamp
1741
+ };
1742
+ } catch {
1743
+ return null;
1744
+ }
1745
+ }
1746
+ function findBackups(configRoot) {
1747
+ if (!existsSync10(configRoot)) {
1748
+ return [];
1749
+ }
1750
+ const files = readdirSync3(configRoot);
1751
+ const backups = [];
1752
+ for (const file of files) {
1753
+ if (file.includes(".cannbot-backup.")) {
1754
+ const parts = file.split(".cannbot-backup.");
1755
+ if (parts.length === 2) {
1756
+ const pluginIdAndTime = parts[1];
1757
+ const lastDotIndex = pluginIdAndTime.lastIndexOf(".");
1758
+ if (lastDotIndex > 0) {
1759
+ const pluginId = pluginIdAndTime.substring(0, lastDotIndex);
1760
+ const timestamp = pluginIdAndTime.substring(lastDotIndex + 1);
1761
+ const plugin = findPlugin(pluginId);
1762
+ backups.push({
1763
+ filePath: join10(configRoot, file),
1764
+ pluginId,
1765
+ pluginName: plugin?.displayName || pluginId,
1766
+ backupTime: timestamp
1767
+ });
1768
+ }
1769
+ }
1770
+ }
1771
+ }
1772
+ return backups.sort((a, b) => b.backupTime.localeCompare(a.backupTime));
1773
+ }
1774
+ function restoreBackup(backupPath, configRoot, tool) {
1775
+ try {
1776
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
1777
+ copyFileSync(backupPath, agentsFile);
1778
+ return true;
1779
+ } catch {
1780
+ return false;
1781
+ }
1782
+ }
1783
+ function deleteBackup(backupPath) {
1784
+ try {
1785
+ if (existsSync10(backupPath)) {
1786
+ unlinkSync3(backupPath);
1787
+ return true;
1788
+ }
1789
+ return false;
1602
1790
  } catch {
1603
1791
  return false;
1604
1792
  }
1605
1793
  }
1606
- function getSkillRecordPath() {
1607
- return join7(getInstallsDir(), "skills.json");
1794
+ function formatTimestamp(date) {
1795
+ const year = date.getFullYear();
1796
+ const month = String(date.getMonth() + 1).padStart(2, "0");
1797
+ const day = String(date.getDate()).padStart(2, "0");
1798
+ const hours = String(date.getHours()).padStart(2, "0");
1799
+ const minutes = String(date.getMinutes()).padStart(2, "0");
1800
+ const seconds = String(date.getSeconds()).padStart(2, "0");
1801
+ return `${year}${month}${day}-${hours}${minutes}${seconds}`;
1608
1802
  }
1609
- function readSkillRecord() {
1610
- const recordPath = getSkillRecordPath();
1611
- if (!existsSync7(recordPath)) {
1612
- return {};
1803
+ function readAllRecords() {
1804
+ const records = [];
1805
+ const plugins = getAllPlugins();
1806
+ for (const plugin of plugins) {
1807
+ const record = readRecord(plugin.id);
1808
+ if (record) {
1809
+ records.push(record);
1810
+ }
1613
1811
  }
1812
+ return records;
1813
+ }
1814
+
1815
+ // src/ui/backup-prompts.ts
1816
+ import { select as select2 } from "@inquirer/prompts";
1817
+ import chalk4 from "chalk";
1818
+ async function showOverwriteWarning(currentPluginName, newPluginName) {
1819
+ console.log();
1820
+ console.log(chalk4.yellow(` \u26A0\uFE0F ${t("backup_detected")}`));
1821
+ console.log();
1822
+ console.log(` ${t("backup_current")}: ${chalk4.cyan(currentPluginName)}`);
1823
+ console.log(` ${t("backup_installing")}: ${chalk4.cyan(newPluginName)}`);
1824
+ console.log();
1825
+ console.log(` ${t("backup_overwrite_warning")}`);
1826
+ console.log(` ${t("backup_auto_create")}`);
1827
+ console.log();
1828
+ const choice = await select2({
1829
+ message: t("backup_select_action"),
1830
+ choices: [
1831
+ { name: t("backup_overwrite"), value: "overwrite" },
1832
+ { name: t("backup_cancel"), value: "cancel" }
1833
+ ]
1834
+ });
1835
+ return choice;
1836
+ }
1837
+ async function showRestorePrompt(backups) {
1838
+ console.log();
1839
+ console.log(chalk4.cyan(` ${t("backup_restore_prompt")}`));
1840
+ console.log();
1841
+ const choices = backups.map((b) => ({
1842
+ name: `${t("backup_restore")} ${b.pluginName} (${b.backupTime})`,
1843
+ value: b.filePath
1844
+ }));
1845
+ choices.push({ name: t("backup_only_uninstall"), value: "none" });
1846
+ const choice = await select2({
1847
+ message: t("backup_select_action"),
1848
+ choices
1849
+ });
1850
+ return choice;
1851
+ }
1852
+
1853
+ // src/core/plugin-installer.ts
1854
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync4, realpathSync, copyFileSync as copyFileSync2, cpSync, writeFileSync as writeFileSync3, readFileSync as readFileSync8 } from "fs";
1855
+ import { join as join11 } from "path";
1856
+ import { execa as execa3 } from "execa";
1857
+ function readPluginManifest(pluginDir) {
1858
+ const manifestPath = join11(pluginDir, "plugin-manifest.json");
1859
+ if (!existsSync11(manifestPath)) return null;
1614
1860
  try {
1615
- const content = readFileSync4(recordPath, "utf-8");
1616
- return JSON.parse(content);
1861
+ const content = JSON.parse(readFileSync8(manifestPath, "utf-8"));
1862
+ if (!content || !content.id) return null;
1863
+ return content;
1617
1864
  } catch {
1618
- return {};
1865
+ return null;
1619
1866
  }
1620
1867
  }
1621
- function writeSkillRecord(record) {
1622
- const installsDir = getInstallsDir();
1623
- if (!existsSync7(installsDir)) {
1624
- mkdirSync4(installsDir, { recursive: true });
1868
+ function installLink(source, target) {
1869
+ try {
1870
+ const resolved = realpathSync(source);
1871
+ if (existsSync11(target) || isSymlink(target)) {
1872
+ unlinkSync4(target);
1873
+ }
1874
+ try {
1875
+ symlinkSync(resolved, target);
1876
+ return "symlink";
1877
+ } catch (e) {
1878
+ if (e.code === "EPERM") {
1879
+ cpSync(resolved, target, { recursive: true });
1880
+ return "copy";
1881
+ }
1882
+ throw e;
1883
+ }
1884
+ } catch {
1885
+ return "failed";
1625
1886
  }
1626
- const recordPath = getSkillRecordPath();
1627
- writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
1628
1887
  }
1629
- function addSkillsToRecord(skillIds, tool, level, installPath) {
1630
- const record = readSkillRecord();
1631
- if (!record[tool]) record[tool] = {};
1632
- if (!record[tool][level]) record[tool][level] = {};
1633
- if (!record[tool][level][installPath]) {
1634
- record[tool][level][installPath] = { skills: [], installTime: "" };
1888
+ function installConfigFile(pluginDir, configFile, tool, level, configRoot, installPath) {
1889
+ const sourcePath = join11(pluginDir, configFile);
1890
+ if (!existsSync11(sourcePath)) return;
1891
+ const agentsFileName = getAgentsFileName(tool);
1892
+ const isProjectLevel = level === "project";
1893
+ let primaryTarget;
1894
+ if (isProjectLevel) {
1895
+ primaryTarget = join11(installPath, agentsFileName);
1896
+ } else {
1897
+ primaryTarget = join11(configRoot, agentsFileName);
1635
1898
  }
1636
- const entry = record[tool][level][installPath];
1637
- for (const id of skillIds) {
1638
- if (!entry.skills.includes(id)) {
1639
- entry.skills.push(id);
1899
+ if (existsSync11(primaryTarget) || isSymlink(primaryTarget)) {
1900
+ unlinkSync4(primaryTarget);
1901
+ }
1902
+ try {
1903
+ symlinkSync(realpathSync(sourcePath), primaryTarget);
1904
+ } catch (e) {
1905
+ if (e.code === "EPERM") {
1906
+ copyFileSync2(sourcePath, primaryTarget);
1907
+ } else {
1908
+ throw e;
1640
1909
  }
1641
1910
  }
1642
- entry.installTime = (/* @__PURE__ */ new Date()).toISOString();
1643
- writeSkillRecord(record);
1644
- }
1645
- function removeSkillsFromRecord(skillIds, tool, level, installPath) {
1646
- const record = readSkillRecord();
1647
- if (!record[tool]?.[level]?.[installPath]) return;
1648
- const entry = record[tool][level][installPath];
1649
- entry.skills = entry.skills.filter((id) => !skillIds.includes(id));
1650
- if (entry.skills.length === 0) {
1651
- delete record[tool][level][installPath];
1652
- if (Object.keys(record[tool][level]).length === 0) {
1653
- delete record[tool][level];
1654
- if (Object.keys(record[tool]).length === 0) {
1655
- delete record[tool];
1911
+ if (isProjectLevel && primaryTarget !== join11(configRoot, agentsFileName)) {
1912
+ const configRootTarget = join11(configRoot, agentsFileName);
1913
+ if (existsSync11(configRootTarget) || isSymlink(configRootTarget)) {
1914
+ unlinkSync4(configRootTarget);
1915
+ }
1916
+ try {
1917
+ symlinkSync(realpathSync(sourcePath), configRootTarget);
1918
+ } catch (e) {
1919
+ if (e.code === "EPERM") {
1920
+ copyFileSync2(sourcePath, configRootTarget);
1656
1921
  }
1657
1922
  }
1658
1923
  }
1659
- writeSkillRecord(record);
1660
1924
  }
1661
- function getInstalledSkills(tool, level, installPath) {
1662
- const record = readSkillRecord();
1663
- return record[tool]?.[level]?.[installPath]?.skills || [];
1925
+ async function installExternalRepos(repos, repoPath) {
1926
+ if (!repos || repos.length === 0) return;
1927
+ for (const repo of repos) {
1928
+ const targetDir = join11(repoPath, repo.dir);
1929
+ if (existsSync11(targetDir)) {
1930
+ logger.info(` ${repo.dir} already exists, skipping clone`);
1931
+ continue;
1932
+ }
1933
+ const args = ["clone"];
1934
+ if (repo.depth) {
1935
+ args.push("--depth", String(repo.depth));
1936
+ }
1937
+ if (repo.recursive) {
1938
+ args.push("--recursive");
1939
+ }
1940
+ args.push(repo.url, targetDir);
1941
+ try {
1942
+ await execa3("git", args, { timeout: 12e4 });
1943
+ } catch {
1944
+ logger.warn(` git clone failed: ${repo.url}, clone manually: git clone ${repo.url} ${repo.dir}`);
1945
+ }
1946
+ }
1947
+ }
1948
+ function writeCannbotManifest(configRoot, pluginId, version, level, tool, skills, agents) {
1949
+ const manifest = {
1950
+ brand: "CANNBot",
1951
+ version,
1952
+ team: pluginId,
1953
+ level,
1954
+ tool,
1955
+ installed_skills: skills,
1956
+ installed_agents: agents,
1957
+ brand_dir: configRoot,
1958
+ install_time: (/* @__PURE__ */ new Date()).toISOString()
1959
+ };
1960
+ const manifestPath = join11(configRoot, "cannbot-manifest.json");
1961
+ writeFileSync3(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
1962
+ return manifest;
1963
+ }
1964
+ async function installViaManifest(manifest, pluginDir, repoPath, tool, level, installPath) {
1965
+ const cwd = installPath || process.cwd();
1966
+ const configRoot = getConfigRoot(tool, level, installPath);
1967
+ try {
1968
+ const skillsDir = join11(configRoot, "skills");
1969
+ const agentsDir = join11(configRoot, "agents");
1970
+ mkdirSync5(skillsDir, { recursive: true });
1971
+ mkdirSync5(agentsDir, { recursive: true });
1972
+ const installedSkills = [];
1973
+ const installedAgents = [];
1974
+ for (const source of manifest.skills) {
1975
+ const sourceDir = join11(repoPath, source.dir);
1976
+ if (!existsSync11(sourceDir)) {
1977
+ logger.warn(` Skill source not found: ${source.dir}`);
1978
+ continue;
1979
+ }
1980
+ for (const skillEntry of source.skills) {
1981
+ const skillName = typeof skillEntry === "string" ? skillEntry : skillEntry.name;
1982
+ const skillAlias = typeof skillEntry === "string" ? skillName : skillEntry.as || skillName;
1983
+ const skillPath = join11(sourceDir, skillName);
1984
+ if (!existsSync11(skillPath)) {
1985
+ logger.warn(` Skill not found: ${skillName} in ${source.dir}`);
1986
+ continue;
1987
+ }
1988
+ const targetPath = join11(skillsDir, skillAlias);
1989
+ const result = installLink(skillPath, targetPath);
1990
+ if (result !== "failed") {
1991
+ installedSkills.push(skillAlias);
1992
+ } else {
1993
+ logger.warn(` Failed to install skill: ${skillAlias}`);
1994
+ }
1995
+ }
1996
+ }
1997
+ for (const agentName of manifest.agents) {
1998
+ const agentFile = agentName.endsWith(".md") ? agentName : `${agentName}.md`;
1999
+ const agentPath = join11(pluginDir, "agents", agentFile);
2000
+ if (!existsSync11(agentPath)) {
2001
+ logger.warn(` Agent not found: ${agentFile}`);
2002
+ continue;
2003
+ }
2004
+ const targetPath = join11(agentsDir, agentFile);
2005
+ const result = installLink(agentPath, targetPath);
2006
+ if (result !== "failed") {
2007
+ installedAgents.push(agentFile);
2008
+ } else {
2009
+ logger.warn(` Failed to install agent: ${agentFile}`);
2010
+ }
2011
+ }
2012
+ installConfigFile(pluginDir, manifest.configFile, tool, level, configRoot, cwd);
2013
+ await installExternalRepos(manifest.externalRepos, repoPath);
2014
+ const cannbotManifest = writeCannbotManifest(
2015
+ configRoot,
2016
+ manifest.id,
2017
+ manifest.version || "1.0.0",
2018
+ level,
2019
+ tool,
2020
+ installedSkills,
2021
+ installedAgents
2022
+ );
2023
+ return {
2024
+ success: true,
2025
+ skillsCount: installedSkills.length,
2026
+ agentsCount: installedAgents.length,
2027
+ errors: [],
2028
+ manifest: cannbotManifest
2029
+ };
2030
+ } catch (error) {
2031
+ return {
2032
+ success: false,
2033
+ skillsCount: 0,
2034
+ agentsCount: 0,
2035
+ errors: [error instanceof Error ? error.message : t("error_unknown")],
2036
+ manifest: null
2037
+ };
2038
+ }
1664
2039
  }
1665
2040
 
1666
2041
  // src/core/installer.ts
@@ -1672,18 +2047,116 @@ async function installPlugin(opts) {
1672
2047
  pluginId: opts.pluginId,
1673
2048
  skillsCount: 0,
1674
2049
  agentsCount: 0,
1675
- errors: [`Plugin not found: ${opts.pluginId}`],
2050
+ errors: [t("error_plugin_not_found") + ": " + opts.pluginId],
1676
2051
  warnings: []
1677
2052
  };
1678
2053
  }
1679
- const scriptPath = join8(opts.repoPath, plugin.dir, plugin.script);
1680
- if (!existsSync8(scriptPath)) {
2054
+ const pluginDir = join12(opts.repoPath, plugin.dir);
2055
+ const cwd = opts.installPath || process.cwd();
2056
+ const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
2057
+ const agentsFile = join12(configRoot, getAgentsFileName(opts.tool));
2058
+ let backupInfo = null;
2059
+ if (existsSync12(agentsFile)) {
2060
+ const currentPlugin = detectCurrentPlugin(configRoot, opts.tool);
2061
+ if (currentPlugin && currentPlugin.pluginId !== opts.pluginId) {
2062
+ let choice = "overwrite";
2063
+ if (!opts.yes) {
2064
+ choice = await showOverwriteWarning(
2065
+ currentPlugin.pluginName,
2066
+ plugin.displayName
2067
+ );
2068
+ }
2069
+ if (choice === "cancel") {
2070
+ logger.info(t("backup_cancel"));
2071
+ return {
2072
+ success: false,
2073
+ pluginId: opts.pluginId,
2074
+ skillsCount: 0,
2075
+ agentsCount: 0,
2076
+ errors: [t("backup_cancel")],
2077
+ warnings: []
2078
+ };
2079
+ }
2080
+ backupInfo = createBackup(
2081
+ configRoot,
2082
+ opts.tool,
2083
+ currentPlugin.pluginId,
2084
+ currentPlugin.pluginName
2085
+ );
2086
+ if (!backupInfo) {
2087
+ logger.error(t("backup_failed"));
2088
+ return {
2089
+ success: false,
2090
+ pluginId: opts.pluginId,
2091
+ skillsCount: 0,
2092
+ agentsCount: 0,
2093
+ errors: [t("backup_failed")],
2094
+ warnings: []
2095
+ };
2096
+ }
2097
+ logger.success(`${t("backup_created")}: ${backupInfo.filePath}`);
2098
+ }
2099
+ }
2100
+ const pluginManifest = readPluginManifest(pluginDir);
2101
+ if (pluginManifest) {
2102
+ const result = await installViaManifest(
2103
+ pluginManifest,
2104
+ pluginDir,
2105
+ opts.repoPath,
2106
+ opts.tool,
2107
+ opts.level,
2108
+ opts.installPath
2109
+ );
2110
+ if (result.success) {
2111
+ try {
2112
+ const record = scanInstalledFiles(
2113
+ opts.pluginId,
2114
+ plugin.displayName,
2115
+ opts.tool,
2116
+ opts.level,
2117
+ cwd,
2118
+ configRoot,
2119
+ result.manifest
2120
+ );
2121
+ if (backupInfo) {
2122
+ record.backup = {
2123
+ filePath: backupInfo.filePath,
2124
+ fromPluginId: backupInfo.pluginId,
2125
+ fromPluginName: backupInfo.pluginName,
2126
+ backupTime: backupInfo.backupTime
2127
+ };
2128
+ }
2129
+ writeRecord(record);
2130
+ } catch {
2131
+ logger.warn(t("record_write_failed"));
2132
+ }
2133
+ return {
2134
+ success: true,
2135
+ pluginId: opts.pluginId,
2136
+ skillsCount: result.skillsCount,
2137
+ agentsCount: result.agentsCount,
2138
+ errors: result.errors,
2139
+ warnings: []
2140
+ };
2141
+ } else {
2142
+ return {
2143
+ success: false,
2144
+ pluginId: opts.pluginId,
2145
+ skillsCount: 0,
2146
+ agentsCount: 0,
2147
+ errors: result.errors,
2148
+ warnings: []
2149
+ };
2150
+ }
2151
+ }
2152
+ const scriptPath = join12(pluginDir, plugin.script);
2153
+ if (!existsSync12(scriptPath)) {
1681
2154
  return {
1682
2155
  success: false,
1683
2156
  pluginId: opts.pluginId,
1684
2157
  skillsCount: 0,
1685
2158
  agentsCount: 0,
1686
- errors: [`Script not found: ${scriptPath}`],
2159
+ errors: [t("error_script_not_found").replace("{path}", scriptPath)],
1687
2160
  warnings: []
1688
2161
  };
1689
2162
  }
@@ -1691,14 +2164,15 @@ async function installPlugin(opts) {
1691
2164
  if (opts.installPath) {
1692
2165
  args.push(opts.installPath);
1693
2166
  }
1694
- const cwd = opts.installPath || process.cwd();
1695
2167
  try {
1696
- await execa3("bash", [scriptPath, ...args], {
2168
+ const shell = process.platform === "win32" ? "sh" : "bash";
2169
+ const execOptions = {
1697
2170
  cwd,
1698
2171
  timeout: 3e5,
1699
- stdio: "pipe"
1700
- });
1701
- const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
2172
+ stdio: "pipe",
2173
+ input: opts.yes ? "y\n" : void 0
2174
+ };
2175
+ await execa4(shell, [scriptPath, ...args], execOptions);
1702
2176
  const manifest = readManifest(configRoot);
1703
2177
  let skillsCount = 0;
1704
2178
  let agentsCount = 0;
@@ -1719,8 +2193,17 @@ async function installPlugin(opts) {
1719
2193
  configRoot,
1720
2194
  manifest
1721
2195
  );
2196
+ if (backupInfo) {
2197
+ record.backup = {
2198
+ filePath: backupInfo.filePath,
2199
+ fromPluginId: backupInfo.pluginId,
2200
+ fromPluginName: backupInfo.pluginName,
2201
+ backupTime: backupInfo.backupTime
2202
+ };
2203
+ }
1722
2204
  writeRecord(record);
1723
2205
  } catch {
2206
+ logger.warn(t("record_write_failed"));
1724
2207
  }
1725
2208
  return {
1726
2209
  success: true,
@@ -1731,7 +2214,7 @@ async function installPlugin(opts) {
1731
2214
  warnings: []
1732
2215
  };
1733
2216
  } catch (error) {
1734
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
2217
+ const errorMessage = error instanceof Error ? error.message : t("error_unknown");
1735
2218
  return {
1736
2219
  success: false,
1737
2220
  pluginId: opts.pluginId,
@@ -1744,41 +2227,46 @@ async function installPlugin(opts) {
1744
2227
  }
1745
2228
 
1746
2229
  // src/core/skill-installer.ts
1747
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync2, readdirSync as readdirSync3, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
1748
- import { join as join9 } from "path";
1749
- import { select as select2, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
1750
- import chalk3 from "chalk";
2230
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, symlinkSync as symlinkSync2, unlinkSync as unlinkSync5, readdirSync as readdirSync4, rmdirSync, realpathSync as realpathSync2 } from "fs";
2231
+ import { join as join13, dirname as dirname7 } from "path";
2232
+ import { select as select3, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
2233
+ import chalk5 from "chalk";
1751
2234
  import Table from "cli-table3";
1752
2235
  async function installSkills(skillIds, tool, level, repoPath) {
1753
2236
  const configRoot = getConfigRoot(tool, level);
1754
- const skillsDir = join9(configRoot, "skills");
2237
+ const skillsDir = join13(configRoot, "skills");
1755
2238
  const installPath = level === "project" ? process.cwd() : configRoot;
1756
- if (!existsSync9(skillsDir)) {
1757
- mkdirSync5(skillsDir, { recursive: true });
2239
+ if (!existsSync13(skillsDir)) {
2240
+ mkdirSync6(skillsDir, { recursive: true });
1758
2241
  }
1759
2242
  const results = [];
1760
2243
  const installedIds = [];
1761
2244
  for (const skillId of skillIds) {
1762
2245
  const skill = findSkill(skillId);
1763
2246
  if (!skill) {
1764
- results.push({ skillId, success: false, error: "Skill not found in registry" });
2247
+ results.push({ skillId, success: false, error: t("error_skill_not_found") });
1765
2248
  continue;
1766
2249
  }
1767
- const sourcePath = join9(repoPath, skill.source, skill.id);
1768
- const targetPath = join9(skillsDir, skillId);
1769
- if (!existsSync9(sourcePath)) {
1770
- results.push({ skillId, success: false, error: `Source not found: ${sourcePath}` });
2250
+ const sourcePath = resolveSkillSourcePath(skill, repoPath);
2251
+ const targetPath = join13(skillsDir, skillId);
2252
+ if (!existsSync13(sourcePath)) {
2253
+ results.push({ skillId, success: false, error: t("error_source_not_found").replace("{path}", sourcePath) });
1771
2254
  continue;
1772
2255
  }
1773
2256
  try {
1774
- if (existsSync9(targetPath) || isSymlink2(targetPath)) {
1775
- unlinkSync2(targetPath);
2257
+ const resolvedSource = realpathSync2(sourcePath);
2258
+ if (existsSync13(targetPath) || isSymlink(targetPath)) {
2259
+ unlinkSync5(targetPath);
1776
2260
  }
1777
- symlinkSync(realpathSync(sourcePath), targetPath);
2261
+ symlinkSync2(resolvedSource, targetPath);
1778
2262
  results.push({ skillId, success: true });
1779
2263
  installedIds.push(skillId);
1780
2264
  } catch (error) {
1781
- results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
2265
+ let msg = error instanceof Error ? error.message : t("error_unknown");
2266
+ if (process.platform === "win32" && msg.includes("EPERM")) {
2267
+ msg = t("error_symlink_windows");
2268
+ }
2269
+ results.push({ skillId, success: false, error: msg });
1782
2270
  }
1783
2271
  }
1784
2272
  if (installedIds.length > 0) {
@@ -1788,109 +2276,118 @@ async function installSkills(skillIds, tool, level, repoPath) {
1788
2276
  }
1789
2277
  async function uninstallSkills(skillIds, tool, level) {
1790
2278
  const configRoot = getConfigRoot(tool, level);
1791
- const skillsDir = join9(configRoot, "skills");
2279
+ const skillsDir = join13(configRoot, "skills");
1792
2280
  const installPath = level === "project" ? process.cwd() : configRoot;
1793
2281
  const results = [];
1794
2282
  const removedIds = [];
1795
2283
  for (const skillId of skillIds) {
1796
- const targetPath = join9(skillsDir, skillId);
2284
+ const targetPath = join13(skillsDir, skillId);
1797
2285
  try {
1798
- if (existsSync9(targetPath) || isSymlink2(targetPath)) {
1799
- unlinkSync2(targetPath);
2286
+ if (existsSync13(targetPath) || isSymlink(targetPath)) {
2287
+ unlinkSync5(targetPath);
1800
2288
  results.push({ skillId, success: true });
1801
2289
  removedIds.push(skillId);
1802
- logger.step(` \u79FB\u9664: ${skillId}`);
2290
+ logger.step(` ${t("uninstall_remove_label")}: ${skillId}`);
1803
2291
  } else {
1804
- results.push({ skillId, success: false, error: "Not installed" });
1805
- logger.warn(`${skillId} \u672A\u5B89\u88C5`);
2292
+ results.push({ skillId, success: false, error: t("error_not_installed") });
2293
+ logger.warn(`${skillId} ${t("uninstall_skill_not_installed")}`);
1806
2294
  }
1807
2295
  } catch (error) {
1808
- results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
2296
+ results.push({ skillId, success: false, error: error instanceof Error ? error.message : t("error_unknown") });
1809
2297
  }
1810
2298
  }
1811
2299
  if (removedIds.length > 0) {
1812
2300
  removeSkillsFromRecord(removedIds, tool, level, installPath);
1813
2301
  }
1814
- if (existsSync9(skillsDir)) {
2302
+ if (existsSync13(skillsDir)) {
1815
2303
  try {
1816
- const entries = readdirSync3(skillsDir);
2304
+ const entries = readdirSync4(skillsDir);
1817
2305
  if (entries.length === 0) {
1818
2306
  rmdirSync(skillsDir);
1819
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: skills/`);
1820
- }
1821
- } catch {
1822
- }
1823
- }
1824
- if (existsSync9(configRoot)) {
1825
- try {
1826
- const entries = readdirSync3(configRoot);
1827
- if (entries.length === 0) {
1828
- rmdirSync(configRoot);
1829
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${configRoot.split("/").pop()}/`);
2307
+ logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
1830
2308
  }
1831
2309
  } catch {
1832
2310
  }
1833
2311
  }
1834
2312
  return results;
1835
2313
  }
1836
- async function interactiveSkillSelect() {
1837
- const BACK3 = "__back__";
1838
- const CANCEL3 = "__cancel__";
2314
+ var SELECT_ALL = "__select_all__";
2315
+ var SELECT_ALL_CATEGORY = "__select_all_category__";
2316
+ var CONTINUE_SELECT = "__continue_select__";
2317
+ async function interactiveSkillSelect(tool, level) {
1839
2318
  const categories = getAllCategories();
2319
+ const allSkillIds = getAllSkills().map((s) => s.id);
2320
+ let accumulated = [];
1840
2321
  let step = 0;
1841
2322
  let selectedCategoryId = "";
1842
2323
  while (true) {
1843
2324
  switch (step) {
1844
2325
  case 0: {
1845
- printBoxTitle("[3/4] \u9009\u62E9 Skill \u7C7B\u522B");
2326
+ 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");
2327
+ printBoxTitle(titleSuffix);
1846
2328
  const categoryChoices = [
2329
+ {
2330
+ name: `>> ${t("skill_select_all").replace("{count}", String(allSkillIds.length))}`,
2331
+ value: SELECT_ALL
2332
+ },
2333
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1847
2334
  ...categories.map((cat) => ({
1848
2335
  name: `> ${cat.name} (${cat.skills.length} skills)`,
1849
2336
  value: cat.id
1850
2337
  })),
1851
2338
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1852
- { name: "<- " + t("wizard_back"), value: BACK3 },
1853
- { name: "x " + t("wizard_cancel"), value: CANCEL3 }
2339
+ ...accumulated.length > 0 ? [{ name: `>> ${t("skill_confirm_install_count").replace("{count}", String(accumulated.length))}`, value: "install" }] : [],
2340
+ { name: "<- " + t("wizard_back"), value: BACK },
2341
+ { name: "x " + t("wizard_cancel"), value: CANCEL }
1854
2342
  ];
1855
2343
  showOperationHints();
1856
- selectedCategoryId = await select2({
2344
+ selectedCategoryId = await select3({
1857
2345
  message: t("skill_select_category"),
1858
2346
  choices: categoryChoices,
1859
2347
  loop: false,
1860
2348
  theme: selectTheme,
1861
2349
  pageSize: 15
1862
2350
  });
1863
- if (selectedCategoryId === BACK3) return "back";
1864
- if (selectedCategoryId === CANCEL3) return "cancel";
2351
+ if (selectedCategoryId === BACK) return "back";
2352
+ if (selectedCategoryId === CANCEL) return "cancel";
2353
+ if (selectedCategoryId === SELECT_ALL) {
2354
+ return allSkillIds;
2355
+ }
2356
+ if (selectedCategoryId === "install") {
2357
+ return accumulated;
2358
+ }
1865
2359
  step = 1;
1866
2360
  break;
1867
2361
  }
1868
2362
  case 1: {
1869
2363
  const category = categories.find((c) => c.id === selectedCategoryId);
1870
2364
  if (!category) return [];
1871
- printBoxTitle(`[4/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills \u2014 ${category.name}`);
1872
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
1873
- const levels = ["project", "global"];
1874
- const installedSkills = [];
1875
- for (const tk of tools) {
1876
- for (const l of levels) {
1877
- const path = l === "project" ? process.cwd() : getConfigRoot(tk, l);
1878
- installedSkills.push(...getInstalledSkills(tk, l, path));
1879
- }
1880
- }
2365
+ printBoxTitle(t("wizard_step_4_skill_title").replace("{category}", category.name));
2366
+ const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
2367
+ const installedSkills = getInstalledSkills(tool, level, installPath);
1881
2368
  const installedSet = new Set(installedSkills);
1882
- const skillChoices = category.skills.map((skill) => {
1883
- const isInstalled = installedSet.has(skill.id);
1884
- const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : "";
1885
- return {
1886
- name: `${skill.id}${suffix} \u2014 ${skill.description}`,
1887
- value: skill.id,
1888
- checked: false
1889
- };
1890
- });
1891
- skillChoices.push(new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1892
- skillChoices.push({ name: "<- " + t("wizard_back"), value: BACK3, checked: false });
1893
- skillChoices.push({ name: "x " + t("wizard_cancel"), value: CANCEL3, checked: false });
2369
+ const categorySkillIds = new Set(category.skills.map((s) => s.id));
2370
+ const accumulatedInCategory = new Set(accumulated.filter((id) => categorySkillIds.has(id)));
2371
+ const skillChoices = [
2372
+ {
2373
+ name: `>> ${t("skill_select_all_category")}`,
2374
+ value: SELECT_ALL_CATEGORY,
2375
+ checked: accumulatedInCategory.size === category.skills.length,
2376
+ description: t("skill_select_all_category")
2377
+ },
2378
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2379
+ ...category.skills.map((skill) => {
2380
+ const isInstalled = installedSet.has(skill.id);
2381
+ const isAccumulated = accumulatedInCategory.has(skill.id);
2382
+ const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : isAccumulated ? " [+]" : "";
2383
+ return {
2384
+ name: `${chalk5.cyan(skill.id)}${suffix}`,
2385
+ value: skill.id,
2386
+ checked: isAccumulated,
2387
+ description: skill.description
2388
+ };
2389
+ })
2390
+ ];
1894
2391
  showOperationHints(true);
1895
2392
  const selectedSkills = await checkbox2({
1896
2393
  message: t("skill_select_items"),
@@ -1900,16 +2397,21 @@ async function interactiveSkillSelect() {
1900
2397
  theme: checkboxTheme,
1901
2398
  pageSize: 15
1902
2399
  });
1903
- if (selectedSkills.includes(BACK3)) {
1904
- step = 0;
1905
- break;
2400
+ let skillIds;
2401
+ if (selectedSkills.includes(SELECT_ALL_CATEGORY)) {
2402
+ skillIds = category.skills.map((s) => s.id);
2403
+ } else {
2404
+ skillIds = selectedSkills.filter((id) => id !== SELECT_ALL_CATEGORY);
1906
2405
  }
1907
- if (selectedSkills.includes(CANCEL3)) return "cancel";
1908
- const skillIds = selectedSkills.filter((v) => v !== BACK3 && v !== CANCEL3);
1909
2406
  if (skillIds.length === 0) {
1910
- const action = await select2({
1911
- message: t("wizard_no_selection"),
1912
- choices: [
2407
+ accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
2408
+ const action2 = await select3({
2409
+ message: accumulated.length > 0 ? t("skill_continue_or_install").replace("{count}", String(accumulated.length)) : t("wizard_no_selection"),
2410
+ choices: accumulated.length > 0 ? [
2411
+ { name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
2412
+ { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
2413
+ { name: "x " + t("wizard_cancel"), value: "cancel" }
2414
+ ] : [
1913
2415
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1914
2416
  { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
1915
2417
  { name: "x " + t("wizard_cancel"), value: "cancel" }
@@ -1917,13 +2419,38 @@ async function interactiveSkillSelect() {
1917
2419
  loop: false,
1918
2420
  theme: selectTheme
1919
2421
  });
1920
- if (action === "back") {
2422
+ if (action2 === "install") return accumulated;
2423
+ if (action2 === "back") {
1921
2424
  step = 0;
1922
2425
  break;
1923
2426
  }
1924
2427
  return "cancel";
1925
2428
  }
1926
- return skillIds;
2429
+ accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
2430
+ for (const id of skillIds) {
2431
+ if (!accumulated.includes(id)) {
2432
+ accumulated.push(id);
2433
+ }
2434
+ }
2435
+ const action = await select3({
2436
+ message: t("skill_continue_or_install").replace("{count}", String(accumulated.length)),
2437
+ choices: [
2438
+ { name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
2439
+ { name: "> " + t("skill_continue_select"), value: CONTINUE_SELECT },
2440
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2441
+ { name: "x " + t("wizard_cancel"), value: "cancel" }
2442
+ ],
2443
+ loop: false,
2444
+ theme: selectTheme
2445
+ });
2446
+ if (action === "install") {
2447
+ return accumulated;
2448
+ }
2449
+ if (action === CONTINUE_SELECT) {
2450
+ step = 0;
2451
+ break;
2452
+ }
2453
+ return "cancel";
1927
2454
  }
1928
2455
  }
1929
2456
  }
@@ -1931,11 +2458,11 @@ async function interactiveSkillSelect() {
1931
2458
  function listAllSkills() {
1932
2459
  const categories = getAllCategories();
1933
2460
  console.log();
1934
- console.log(chalk3.bold(` ${t("skill_list_title")}`));
1935
- console.log(chalk3.dim(" " + "\u2500".repeat(60)));
2461
+ console.log(chalk5.bold(` ${t("skill_list_title")}`));
2462
+ console.log(chalk5.dim(" " + "\u2500".repeat(60)));
1936
2463
  for (const category of categories) {
1937
2464
  console.log();
1938
- console.log(chalk3.bold(` ${category.name}`) + chalk3.dim(` (${category.skills.length} skills)`));
2465
+ console.log(chalk5.bold(` ${category.name}`) + chalk5.dim(` (${category.skills.length} skills)`));
1939
2466
  const table = new Table({
1940
2467
  style: { head: [], border: [] },
1941
2468
  colWidths: [35, 45],
@@ -1943,51 +2470,54 @@ function listAllSkills() {
1943
2470
  });
1944
2471
  for (const skill of category.skills) {
1945
2472
  table.push([
1946
- chalk3.cyan(skill.id),
2473
+ chalk5.cyan(skill.id),
1947
2474
  skill.description
1948
2475
  ]);
1949
2476
  }
1950
2477
  console.log(table.toString());
1951
2478
  }
1952
2479
  console.log();
1953
- console.log(chalk3.dim(` \u5171 ${getAllSkills().length} \u4E2A Skills`));
2480
+ console.log(chalk5.dim(` ${t("skill_total_count").replace("{count}", String(getAllSkills().length))}`));
1954
2481
  console.log();
1955
2482
  }
1956
- function isSymlink2(path) {
1957
- try {
1958
- return lstatSync2(path).isSymbolicLink();
1959
- } catch {
1960
- return false;
2483
+ function resolveSkillSourcePath(skill, repoPath) {
2484
+ if (skill.filePath) {
2485
+ const absPath = skill.filePath.startsWith("/") ? skill.filePath : join13(repoPath, skill.filePath);
2486
+ if (existsSync13(absPath)) {
2487
+ return dirname7(absPath);
2488
+ }
1961
2489
  }
2490
+ return join13(repoPath, skill.source, skill.id);
1962
2491
  }
1963
2492
 
1964
2493
  // src/ui/display.ts
1965
- import chalk4 from "chalk";
2494
+ import chalk6 from "chalk";
1966
2495
  import Table2 from "cli-table3";
1967
2496
  function printPluginList(plugins, installed) {
1968
2497
  const table = new Table2({
1969
2498
  head: [
1970
- chalk4.cyan(t("list_id")),
1971
- chalk4.cyan(t("list_name")),
1972
- chalk4.cyan(t("list_status")),
1973
- chalk4.cyan(t("list_skills")),
1974
- chalk4.cyan(t("list_agents"))
2499
+ chalk6.cyan(t("list_id")),
2500
+ chalk6.cyan(t("list_name")),
2501
+ chalk6.cyan(t("list_status")),
2502
+ chalk6.cyan(t("list_description"))
1975
2503
  ],
1976
- style: { head: [], border: [] }
2504
+ style: { head: [], border: [] },
2505
+ colWidths: [6, 32, 8, 50],
2506
+ wordWrap: true
1977
2507
  });
1978
2508
  plugins.forEach((plugin, index) => {
1979
2509
  const isInstalled = installed.has(plugin.id);
1980
- const status = isInstalled ? chalk4.green(`\u2713 ${t("status_installed")}`) : chalk4.dim(`\u2014`);
2510
+ const status = isInstalled ? chalk6.green(`\u2713 ${t("status_installed")}`) : chalk6.dim(`\u2014`);
1981
2511
  table.push([
1982
2512
  String(index + 1),
1983
2513
  plugin.displayName,
1984
2514
  status,
1985
- String(plugin.skills),
1986
- String(plugin.agents)
2515
+ plugin.description || chalk6.dim("\u2014")
1987
2516
  ]);
1988
2517
  });
1989
2518
  console.log();
1990
- console.log(chalk4.bold(` ${t("list_title")} (${plugins.length} \u4E2A)`));
2519
+ const countSuffix = t("count_suffix");
2520
+ console.log(chalk6.bold(` ${t("list_title")} (${plugins.length}${countSuffix ? " " + countSuffix : ""})`));
1991
2521
  console.log(table.toString());
1992
2522
  console.log();
1993
2523
  }
@@ -1996,7 +2526,7 @@ function printInstallSummary(results) {
1996
2526
  const totalCount = results.length;
1997
2527
  console.log();
1998
2528
  console.log(
1999
- chalk4.bold(
2529
+ chalk6.bold(
2000
2530
  ` ${t("install_done")}! ${successCount}/${totalCount} ${t("install_success")}`
2001
2531
  )
2002
2532
  );
@@ -2004,10 +2534,10 @@ function printInstallSummary(results) {
2004
2534
  for (const result of results) {
2005
2535
  if (result.success) {
2006
2536
  console.log(
2007
- chalk4.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
2537
+ chalk6.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
2008
2538
  );
2009
2539
  } else {
2010
- console.log(chalk4.red(" \u2717") + ` ${result.displayName}`);
2540
+ console.log(chalk6.red(" \u2717") + ` ${result.displayName}`);
2011
2541
  }
2012
2542
  }
2013
2543
  console.log();
@@ -2020,22 +2550,25 @@ function printEnhancedSummary(results, tool, configRoot) {
2020
2550
  return;
2021
2551
  }
2022
2552
  const toolName = getToolDisplayName(tool);
2553
+ const countSuffix = t("count_suffix");
2023
2554
  const lines = [
2024
- chalk4.bold(` ${t("install_done")}!`),
2555
+ chalk6.bold(` ${t("install_done")}!`),
2025
2556
  "",
2026
- ` ${chalk4.dim(t("install_installed_to") + ":")} ${configRoot}`,
2027
- ` ${chalk4.dim(t("install_skills_count") + ":")} ${totalSkills} \u4E2A ${chalk4.dim("|")} ${chalk4.dim(t("install_agents_count") + ":")} ${totalAgents} \u4E2A`,
2557
+ ` ${chalk6.dim(t("install_installed_to") + ":")} ${configRoot}`,
2558
+ ` ${chalk6.dim(t("install_skills_count") + ":")} ${totalSkills}${countSuffix ? " " + countSuffix : ""} ${chalk6.dim("|")} ${chalk6.dim(t("install_agents_count") + ":")} ${totalAgents}${countSuffix ? " " + countSuffix : ""}`,
2028
2559
  "",
2029
- chalk4.bold(` ${t("install_enhanced_next_steps")}:`),
2030
- ` ${chalk4.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk4.green(toolName.toLowerCase())}`,
2031
- ` ${chalk4.cyan("2.")} ${t("install_enhanced_try")}: ${chalk4.green("\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50")}`,
2032
- ` ${chalk4.cyan("3.")} ${t("install_enhanced_more")}: ${chalk4.green("install-helper list")}`,
2033
- ` ${chalk4.cyan("4.")} ${t("install_enhanced_check")}: ${chalk4.green("install-helper doctor")}`,
2560
+ chalk6.bold(` ${t("install_enhanced_next_steps")}:`),
2561
+ ` ${chalk6.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk6.green(toolName.toLowerCase())}`,
2562
+ ` ${chalk6.cyan("2.")} ${t("install_enhanced_try")}: ${chalk6.green(t("try_prompt"))}`,
2563
+ ` ${chalk6.cyan("3.")} ${t("install_enhanced_more")}: ${chalk6.green("install-helper list")}`,
2564
+ ` ${chalk6.cyan("4.")} ${t("install_enhanced_check")}: ${chalk6.green("install-helper doctor")}`,
2034
2565
  ""
2035
2566
  ];
2036
2567
  if (successResults.length === 1) {
2568
+ const plugin = findPlugin(successResults[0].pluginId);
2569
+ const docsPath = plugin ? `${plugin.dir}/quickstart.md` : `${successResults[0].pluginId}/quickstart.md`;
2037
2570
  lines.push(
2038
- ` ${chalk4.dim(t("install_enhanced_docs"))}: ${chalk4.dim(`plugins-official/${successResults[0].pluginId}/quickstart.md`)}`
2571
+ ` ${chalk6.dim(t("install_enhanced_docs"))}: ${chalk6.dim(docsPath)}`
2039
2572
  );
2040
2573
  }
2041
2574
  console.log();
@@ -2046,34 +2579,32 @@ function printEnhancedSummary(results, tool, configRoot) {
2046
2579
  }
2047
2580
 
2048
2581
  // src/commands/init.ts
2049
- import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
2050
- import { join as join10 } from "path";
2051
- var BACK2 = "__back__";
2052
- var CANCEL2 = "__cancel__";
2582
+ import { existsSync as existsSync14, readFileSync as readFileSync9 } from "fs";
2583
+ import { join as join14 } from "path";
2053
2584
  async function initCommand() {
2054
2585
  const cwd = process.cwd();
2055
- if (existsSync10(join10(cwd, "package.json")) && (() => {
2586
+ if (existsSync14(join14(cwd, "package.json")) && (() => {
2056
2587
  try {
2057
- const pkg = JSON.parse(readFileSync5(join10(cwd, "package.json"), "utf-8"));
2588
+ const pkg = JSON.parse(readFileSync9(join14(cwd, "package.json"), "utf-8"));
2058
2589
  return pkg.name === "@cannbot-ai/install-helper";
2059
2590
  } catch {
2060
2591
  return false;
2061
2592
  }
2062
2593
  })()) {
2063
- logger.warn("\u5F53\u524D\u76EE\u5F55\u770B\u8D77\u6765\u662F install-helper \u5305\u76EE\u5F55");
2064
- 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");
2065
- logger.info("\u793A\u4F8B\uFF1Acd ~/my-project && install-helper");
2594
+ logger.warn(t("init_pkg_dir_warn"));
2595
+ logger.info(t("init_pkg_dir_hint"));
2596
+ logger.info(t("init_pkg_dir_example"));
2066
2597
  return;
2067
2598
  }
2068
2599
  printBanner(t("wizard_title"));
2069
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
2600
+ const spinner = createSpinner(t("loading_skills_list"));
2070
2601
  spinner.start();
2071
2602
  try {
2072
2603
  const repoManager = createRepositoryManager();
2073
2604
  await repoManager.ensureRepoAndScan();
2074
- spinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
2605
+ spinner.succeed(t("loading_skills_list_complete"));
2075
2606
  } catch {
2076
- spinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
2607
+ spinner.warn(t("loading_skills_list_failed"));
2077
2608
  }
2078
2609
  let step = 0;
2079
2610
  let mode = "plugin";
@@ -2107,8 +2638,7 @@ async function initCommand() {
2107
2638
  }
2108
2639
  }
2109
2640
  async function selectMode() {
2110
- printBoxTitle("\u9009\u62E9\u5B89\u88C5\u7C7B\u578B");
2111
- const CANCEL3 = "__cancel__";
2641
+ printBoxTitle(t("wizard_select_mode_title"));
2112
2642
  const choices = [
2113
2643
  {
2114
2644
  name: `> ${t("wizard_mode_plugin")} \u2014 ${t("wizard_mode_plugin_desc")}`,
@@ -2119,30 +2649,30 @@ async function selectMode() {
2119
2649
  value: "skill"
2120
2650
  },
2121
2651
  new Separator3("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2122
- { name: "x " + t("wizard_cancel"), value: CANCEL3 }
2652
+ { name: "x " + t("wizard_cancel"), value: CANCEL }
2123
2653
  ];
2124
2654
  showOperationHints();
2125
- const result = await select3({
2655
+ const result = await select4({
2126
2656
  message: t("wizard_select_mode"),
2127
2657
  choices,
2128
2658
  loop: false,
2129
2659
  theme: selectTheme
2130
2660
  });
2131
- if (result === CANCEL3) return null;
2661
+ if (result === CANCEL) return null;
2132
2662
  return result;
2133
2663
  }
2134
2664
  async function pluginInstallFlow() {
2135
2665
  const answers = await runWizard();
2136
2666
  if (!answers.confirmed) {
2137
2667
  if (answers.back) return "back";
2138
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
2668
+ logger.info(t("init_cancelled"));
2139
2669
  return "done";
2140
2670
  }
2141
2671
  const repoManager = createRepositoryManager();
2142
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
2672
+ const spinner = createSpinner(t("loading_plugin_data"));
2143
2673
  spinner.start();
2144
2674
  const repoPath = await repoManager.ensureRepo();
2145
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2675
+ spinner.succeed(t("loading_plugin_data_complete"));
2146
2676
  const allPlugins = getAllPlugins();
2147
2677
  const selectedPlugins = answers.plugins.map(
2148
2678
  (id) => allPlugins.find((p) => p.id === id)
@@ -2196,7 +2726,7 @@ async function skillInstallFlow() {
2196
2726
  const result = await selectToolWithDetection();
2197
2727
  if (result === "back") return "back";
2198
2728
  if (result === "cancel") {
2199
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
2729
+ logger.info(t("init_cancelled"));
2200
2730
  return "done";
2201
2731
  }
2202
2732
  tool = result;
@@ -2205,12 +2735,12 @@ async function skillInstallFlow() {
2205
2735
  }
2206
2736
  case 1: {
2207
2737
  const result = await stepLevel();
2208
- if (result === BACK2) {
2738
+ if (result === BACK) {
2209
2739
  step = 0;
2210
2740
  break;
2211
2741
  }
2212
- if (result === CANCEL2) {
2213
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
2742
+ if (result === CANCEL) {
2743
+ logger.info(t("init_cancelled"));
2214
2744
  return "done";
2215
2745
  }
2216
2746
  level = result;
@@ -2218,25 +2748,25 @@ async function skillInstallFlow() {
2218
2748
  break;
2219
2749
  }
2220
2750
  case 2: {
2221
- const selectedSkills = await interactiveSkillSelect();
2751
+ const selectedSkills = await interactiveSkillSelect(tool, level);
2222
2752
  if (selectedSkills === "back") {
2223
2753
  step = 1;
2224
2754
  break;
2225
2755
  }
2226
2756
  if (selectedSkills === "cancel") {
2227
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
2757
+ logger.info(t("init_cancelled"));
2228
2758
  return "done";
2229
2759
  }
2230
2760
  if (selectedSkills.length === 0) {
2231
- logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Skill");
2761
+ logger.info(t("install_no_skill_selected"));
2232
2762
  return "done";
2233
2763
  }
2234
2764
  const repoManager = createRepositoryManager();
2235
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
2765
+ const spinner = createSpinner(t("loading_plugin_data"));
2236
2766
  spinner.start();
2237
2767
  const repoPath = await repoManager.ensureRepo();
2238
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2239
- logger.info(`${t("skill_install_progress")} ${chalk5.bold(selectedSkills.length)} \u4E2A Skills...`);
2768
+ spinner.succeed(t("loading_plugin_data_complete"));
2769
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk7.bold(String(selectedSkills.length)))}...`);
2240
2770
  const results = await installSkills(selectedSkills, tool, level, repoPath);
2241
2771
  let successCount = 0;
2242
2772
  let failCount = 0;
@@ -2254,10 +2784,10 @@ async function skillInstallFlow() {
2254
2784
  }
2255
2785
  const configRoot = getConfigRoot(tool, level);
2256
2786
  logger.blank();
2257
- logger.success(`${t("skill_install_done")}: ${chalk5.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk5.red(failCount + " \u5931\u8D25") : chalk5.dim(failCount + " \u5931\u8D25")}`);
2787
+ 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)))}`);
2258
2788
  logger.blank();
2259
- logger.info(`\u5B89\u88C5\u5230: ${chalk5.cyan(configRoot + "/skills/")}`);
2260
- logger.info(`\u542F\u52A8 ${chalk5.green(tool)} \u5373\u53EF\u4F7F\u7528`);
2789
+ logger.info(`${t("install_to")}: ${chalk7.cyan(configRoot + "/skills/")}`);
2790
+ logger.info(`${t("start_to_use").replace("{tool}", chalk7.green(tool))}`);
2261
2791
  logger.blank();
2262
2792
  return "done";
2263
2793
  }
@@ -2279,32 +2809,31 @@ async function listCommand() {
2279
2809
  }
2280
2810
 
2281
2811
  // src/commands/doctor.ts
2282
- import chalk6 from "chalk";
2283
- import { existsSync as existsSync11, readdirSync as readdirSync4, lstatSync as lstatSync3, unlinkSync as unlinkSync3, mkdirSync as mkdirSync6 } from "fs";
2284
- import { join as join11 } from "path";
2812
+ import chalk8 from "chalk";
2813
+ import { existsSync as existsSync15, readdirSync as readdirSync5, lstatSync as lstatSync2, unlinkSync as unlinkSync6, mkdirSync as mkdirSync7 } from "fs";
2814
+ import { join as join15 } from "path";
2285
2815
  async function doctorCommand(options = {}) {
2286
2816
  console.log();
2287
- console.log(chalk6.bold(` ${t("doctor_title")}`));
2288
- console.log(chalk6.dim(" " + "\u2500".repeat(46)));
2289
- let errors = 0;
2817
+ console.log(chalk8.bold(` ${t("doctor_title")}`));
2818
+ console.log(chalk8.dim(" " + "\u2500".repeat(46)));
2290
2819
  let warnings = 0;
2291
2820
  let fixes = 0;
2292
2821
  console.log();
2293
- console.log(chalk6.bold(` ${t("doctor_tools")}`));
2822
+ console.log(chalk8.bold(` ${t("doctor_tools")}`));
2294
2823
  const detectedTools = await detectTools();
2295
2824
  const allTools = getAllTools();
2296
2825
  for (const tool of allTools) {
2297
2826
  const detected = detectedTools.find((d) => d.name === tool);
2298
2827
  if (detected) {
2299
2828
  console.log(
2300
- chalk6.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
2829
+ chalk8.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
2301
2830
  );
2302
2831
  } else {
2303
- console.log(chalk6.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 \u672A\u5B89\u88C5`);
2832
+ console.log(chalk8.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 ${t("doctor_not_installed")}`);
2304
2833
  }
2305
2834
  }
2306
2835
  console.log();
2307
- console.log(chalk6.bold(` ${t("doctor_plugins")}`));
2836
+ console.log(chalk8.bold(` ${t("doctor_plugins")}`));
2308
2837
  const plugins = getAllPlugins();
2309
2838
  const installed = scanInstalled();
2310
2839
  const installedMap = new Map(installed.map((p) => [p.id, p]));
@@ -2312,80 +2841,80 @@ async function doctorCommand(options = {}) {
2312
2841
  const inst = installedMap.get(plugin.id);
2313
2842
  if (inst) {
2314
2843
  console.log(
2315
- chalk6.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
2844
+ chalk8.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
2316
2845
  );
2317
2846
  } else {
2318
- console.log(chalk6.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 \u672A\u5B89\u88C5`);
2847
+ console.log(chalk8.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 ${t("doctor_not_installed")}`);
2319
2848
  }
2320
2849
  }
2321
2850
  console.log();
2322
- console.log(chalk6.bold(` ${t("doctor_links")}`));
2851
+ console.log(chalk8.bold(` ${t("doctor_links")}`));
2323
2852
  const primaryTool = detectedTools[0]?.name || "opencode";
2324
2853
  const configRoot = getConfigRoot(primaryTool, "project");
2325
2854
  const skillsDir = getSkillsDir(configRoot);
2326
2855
  const agentsDir = getAgentsDir(configRoot);
2327
- if (existsSync11(skillsDir)) {
2856
+ if (existsSync15(skillsDir)) {
2328
2857
  const brokenLinks = checkBrokenLinks(skillsDir);
2329
2858
  if (brokenLinks === 0) {
2330
- const count = readdirSync4(skillsDir).length;
2331
- console.log(chalk6.green(" \u2713") + ` ${skillsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
2859
+ const count = readdirSync5(skillsDir).length;
2860
+ console.log(chalk8.green(" \u2713") + ` ${skillsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2332
2861
  } else {
2333
- console.log(chalk6.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
2862
+ console.log(chalk8.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2334
2863
  warnings++;
2335
2864
  if (options.fix) {
2336
2865
  const fixed = fixBrokenLinks(skillsDir);
2337
2866
  fixes += fixed;
2338
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
2867
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2339
2868
  }
2340
2869
  }
2341
2870
  } else {
2342
- console.log(chalk6.dim(" \u2014") + ` ${skillsDir} \u2014 \u4E0D\u5B58\u5728`);
2871
+ console.log(chalk8.dim(" \u2014") + ` ${skillsDir} \u2014 ${t("doctor_not_exist")}`);
2343
2872
  if (options.fix) {
2344
- mkdirSync6(skillsDir, { recursive: true });
2873
+ mkdirSync7(skillsDir, { recursive: true });
2345
2874
  fixes++;
2346
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${skillsDir}`);
2875
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
2347
2876
  }
2348
2877
  }
2349
- if (existsSync11(agentsDir)) {
2878
+ if (existsSync15(agentsDir)) {
2350
2879
  const brokenLinks = checkBrokenLinks(agentsDir);
2351
2880
  if (brokenLinks === 0) {
2352
- const count = readdirSync4(agentsDir).length;
2353
- console.log(chalk6.green(" \u2713") + ` ${agentsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
2881
+ const count = readdirSync5(agentsDir).length;
2882
+ console.log(chalk8.green(" \u2713") + ` ${agentsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2354
2883
  } else {
2355
- console.log(chalk6.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
2884
+ console.log(chalk8.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2356
2885
  warnings++;
2357
2886
  if (options.fix) {
2358
2887
  const fixed = fixBrokenLinks(agentsDir);
2359
2888
  fixes += fixed;
2360
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
2889
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2361
2890
  }
2362
2891
  }
2363
2892
  } else {
2364
- console.log(chalk6.dim(" \u2014") + ` ${agentsDir} \u2014 \u4E0D\u5B58\u5728`);
2893
+ console.log(chalk8.dim(" \u2014") + ` ${agentsDir} \u2014 ${t("doctor_not_exist")}`);
2365
2894
  if (options.fix) {
2366
- mkdirSync6(agentsDir, { recursive: true });
2895
+ mkdirSync7(agentsDir, { recursive: true });
2367
2896
  fixes++;
2368
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${agentsDir}`);
2897
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", agentsDir)}`);
2369
2898
  }
2370
2899
  }
2371
2900
  console.log();
2372
- console.log(chalk6.bold(` ${t("doctor_config")}`));
2901
+ console.log(chalk8.bold(` ${t("doctor_config")}`));
2373
2902
  const configFile = getConfigFileName(primaryTool);
2374
- const configPath = join11(process.cwd(), configFile);
2375
- if (existsSync11(configPath)) {
2376
- console.log(chalk6.green(" \u2713") + ` ${configFile} \u5B58\u5728`);
2903
+ const configPath = join15(process.cwd(), configFile);
2904
+ if (existsSync15(configPath)) {
2905
+ console.log(chalk8.green(" \u2713") + ` ${configFile} ${t("doctor_config_exists")}`);
2377
2906
  } else {
2378
- console.log(chalk6.dim(" \u2014") + ` ${configFile} \u4E0D\u5B58\u5728`);
2907
+ console.log(chalk8.dim(" \u2014") + ` ${configFile} ${t("doctor_config_not_exist")}`);
2379
2908
  }
2380
2909
  console.log();
2381
- console.log(chalk6.dim(" " + "\u2500".repeat(46)));
2910
+ console.log(chalk8.dim(" " + "\u2500".repeat(46)));
2382
2911
  if (options.fix && fixes > 0) {
2383
2912
  console.log(
2384
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF, ${fixes} \u9879\u5DF2\u4FEE\u590D`
2913
+ ` ${t("doctor_result")}: ${t("doctor_result_with_fix").replace("{warnings}", String(warnings)).replace("{fixes}", String(fixes))}`
2385
2914
  );
2386
2915
  } else {
2387
2916
  console.log(
2388
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF`
2917
+ ` ${t("doctor_result")}: ${t("doctor_result_without_fix").replace("{warnings}", String(warnings))}`
2389
2918
  );
2390
2919
  }
2391
2920
  console.log();
@@ -2393,13 +2922,13 @@ async function doctorCommand(options = {}) {
2393
2922
  function checkBrokenLinks(dir) {
2394
2923
  let broken = 0;
2395
2924
  try {
2396
- const entries = readdirSync4(dir);
2925
+ const entries = readdirSync5(dir);
2397
2926
  for (const entry of entries) {
2398
- const fullPath = join11(dir, entry);
2927
+ const fullPath = join15(dir, entry);
2399
2928
  try {
2400
- const stats = lstatSync3(fullPath);
2929
+ const stats = lstatSync2(fullPath);
2401
2930
  if (stats.isSymbolicLink()) {
2402
- if (!existsSync11(fullPath)) {
2931
+ if (!existsSync15(fullPath)) {
2403
2932
  broken++;
2404
2933
  }
2405
2934
  }
@@ -2414,14 +2943,14 @@ function checkBrokenLinks(dir) {
2414
2943
  function fixBrokenLinks(dir) {
2415
2944
  let fixed = 0;
2416
2945
  try {
2417
- const entries = readdirSync4(dir);
2946
+ const entries = readdirSync5(dir);
2418
2947
  for (const entry of entries) {
2419
- const fullPath = join11(dir, entry);
2948
+ const fullPath = join15(dir, entry);
2420
2949
  try {
2421
- const stats = lstatSync3(fullPath);
2950
+ const stats = lstatSync2(fullPath);
2422
2951
  if (stats.isSymbolicLink()) {
2423
- if (!existsSync11(fullPath)) {
2424
- unlinkSync3(fullPath);
2952
+ if (!existsSync15(fullPath)) {
2953
+ unlinkSync6(fullPath);
2425
2954
  fixed++;
2426
2955
  }
2427
2956
  }
@@ -2434,36 +2963,35 @@ function fixBrokenLinks(dir) {
2434
2963
  }
2435
2964
 
2436
2965
  // src/commands/status.ts
2437
- import chalk7 from "chalk";
2966
+ import chalk9 from "chalk";
2438
2967
  async function statusCommand() {
2439
- const plugins = getAllPlugins();
2440
2968
  const installed = scanInstalled();
2441
2969
  const installedMap = new Map(installed.map((p) => [p.id, p]));
2442
2970
  console.log();
2443
- console.log(chalk7.bold(` ${t("status_title")}`));
2971
+ console.log(chalk9.bold(` ${t("status_title")}`));
2444
2972
  console.log();
2445
2973
  if (installed.length === 0) {
2446
- console.log(chalk7.dim(` ${t("status_none")}`));
2447
- console.log(chalk7.dim(` ${t("status_hint")}`));
2974
+ console.log(chalk9.dim(` ${t("status_none")}`));
2975
+ console.log(chalk9.dim(` ${t("status_hint")}`));
2448
2976
  console.log();
2449
2977
  return;
2450
2978
  }
2451
2979
  for (const inst of installed) {
2452
2980
  console.log(
2453
- chalk7.green(" \u2713") + ` ${inst.displayName}` + chalk7.dim(` (${inst.tool}, ${inst.level})`)
2981
+ chalk9.green(" \u2713") + ` ${inst.displayName}` + chalk9.dim(` (${inst.tool}, ${inst.level})`)
2454
2982
  );
2455
2983
  console.log(
2456
- chalk7.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
2984
+ chalk9.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
2457
2985
  );
2458
- console.log(chalk7.dim(` ${inst.configRoot}`));
2459
- console.log(chalk7.dim(` ${t("status_install_time")}: ${inst.installTime}`));
2986
+ console.log(chalk9.dim(` ${inst.configRoot}`));
2987
+ console.log(chalk9.dim(` ${t("status_install_time")}: ${inst.installTime}`));
2460
2988
  console.log();
2461
2989
  }
2462
2990
  }
2463
2991
 
2464
2992
  // src/commands/install.ts
2465
2993
  import { confirm } from "@inquirer/prompts";
2466
- import chalk8 from "chalk";
2994
+ import chalk10 from "chalk";
2467
2995
  async function installCommand(names, options) {
2468
2996
  if (options.list) {
2469
2997
  try {
@@ -2474,79 +3002,150 @@ async function installCommand(names, options) {
2474
3002
  listAllSkills();
2475
3003
  return;
2476
3004
  }
3005
+ if (options.all) {
3006
+ const scanSpinner2 = createSpinner(t("loading_skills_list"));
3007
+ scanSpinner2.start();
3008
+ try {
3009
+ const scanRepoManager = createRepositoryManager();
3010
+ await scanRepoManager.ensureRepoAndScan();
3011
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
3012
+ } catch {
3013
+ scanSpinner2.warn(t("loading_skills_list_failed"));
3014
+ }
3015
+ const allSkills = getAllSkills();
3016
+ const skillIds = allSkills.map((s) => s.id);
3017
+ if (skillIds.length === 0) {
3018
+ logger.error(t("install_no_skill_selected"));
3019
+ return;
3020
+ }
3021
+ let tool2;
3022
+ if (options.tool) {
3023
+ tool2 = validateTool(options.tool);
3024
+ } else {
3025
+ const detected = await selectToolWithDetection();
3026
+ if (detected === "back" || detected === "cancel") {
3027
+ logger.info(t("init_cancelled"));
3028
+ return;
3029
+ }
3030
+ tool2 = detected;
3031
+ }
3032
+ const level2 = options.level ? validateLevel(options.level) : "project";
3033
+ const repoManager = createRepositoryManager();
3034
+ const spinner = createSpinner(t("loading_plugin_data"));
3035
+ spinner.start();
3036
+ const repoPath = await repoManager.ensureRepo();
3037
+ spinner.succeed(t("loading_plugin_data_complete"));
3038
+ logger.info(`${t("skill_all_install_progress").replace("{count}", chalk10.bold(String(skillIds.length)))}`);
3039
+ const results = await installSkills(skillIds, tool2, level2, repoPath);
3040
+ let successCount = 0;
3041
+ let failCount = 0;
3042
+ const total = results.length;
3043
+ for (let i = 0; i < results.length; i++) {
3044
+ const result = results[i];
3045
+ const progress = `[${i + 1}/${total}]`;
3046
+ if (result.success) {
3047
+ logger.success(`${progress} ${result.skillId}`);
3048
+ successCount++;
3049
+ } else {
3050
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
3051
+ failCount++;
3052
+ }
3053
+ }
3054
+ const configRoot = getConfigRoot(tool2, level2);
3055
+ logger.blank();
3056
+ 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)))}`);
3057
+ logger.blank();
3058
+ logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
3059
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
3060
+ logger.blank();
3061
+ return;
3062
+ }
2477
3063
  if (names.length === 0) {
2478
- const scanSpinner2 = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
3064
+ const scanSpinner2 = createSpinner(t("loading_skills_list"));
2479
3065
  scanSpinner2.start();
2480
3066
  try {
2481
3067
  const scanRepoManager = createRepositoryManager();
2482
3068
  await scanRepoManager.ensureRepoAndScan();
2483
- scanSpinner2.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
3069
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
2484
3070
  } catch {
2485
- scanSpinner2.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
3071
+ scanSpinner2.warn(t("loading_skills_list_failed"));
2486
3072
  }
2487
3073
  let tool2;
2488
3074
  if (options.tool) {
2489
- tool2 = options.tool;
3075
+ tool2 = validateTool(options.tool);
2490
3076
  } else {
2491
3077
  const detected = await selectToolWithDetection();
2492
- if (detected === "back") return;
3078
+ if (detected === "back" || detected === "cancel") {
3079
+ logger.info(t("init_cancelled"));
3080
+ return;
3081
+ }
2493
3082
  tool2 = detected;
2494
3083
  }
3084
+ const level2 = options.level ? validateLevel(options.level) : "project";
2495
3085
  let step = 1;
2496
3086
  while (true) {
2497
3087
  switch (step) {
2498
3088
  case 1: {
2499
- const selectedSkills = await interactiveSkillSelect();
3089
+ const selectedSkills = await interactiveSkillSelect(tool2, level2);
2500
3090
  if (selectedSkills === "back") {
2501
3091
  const redetected = await selectToolWithDetection();
2502
- if (redetected === "back") return;
3092
+ if (redetected === "back" || redetected === "cancel") {
3093
+ logger.info(t("init_cancelled"));
3094
+ return;
3095
+ }
2503
3096
  tool2 = redetected;
2504
3097
  break;
2505
3098
  }
3099
+ if (selectedSkills === "cancel") {
3100
+ logger.info(t("init_cancelled"));
3101
+ return;
3102
+ }
2506
3103
  if (selectedSkills.length === 0) {
2507
- logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Skill");
3104
+ logger.info(t("install_no_skill_selected"));
2508
3105
  return;
2509
3106
  }
2510
3107
  names = selectedSkills;
2511
3108
  const repoManager = createRepositoryManager();
2512
- const spinner = createSpinner(t("install_cloning"));
3109
+ const spinner = createSpinner(t("loading_plugin_data"));
2513
3110
  spinner.start();
2514
3111
  const repoPath = await repoManager.ensureRepo();
2515
- spinner.succeed(t("install_repo_ready"));
2516
- logger.info(`${t("skill_install_progress")} ${chalk8.bold(names.length)} \u4E2A Skills...`);
2517
- const level2 = options.level || "project";
3112
+ spinner.succeed(t("loading_plugin_data_complete"));
3113
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(names.length)))}...`);
2518
3114
  const results = await installSkills(names, tool2, level2, repoPath);
2519
3115
  let successCount = 0;
2520
3116
  let failCount = 0;
2521
- for (const result of results) {
3117
+ const total = results.length;
3118
+ for (let i = 0; i < results.length; i++) {
3119
+ const result = results[i];
3120
+ const progress = `[${i + 1}/${total}]`;
2522
3121
  if (result.success) {
2523
- logger.success(`${result.skillId}`);
3122
+ logger.success(`${progress} ${result.skillId}`);
2524
3123
  successCount++;
2525
3124
  } else {
2526
- logger.error(`${result.skillId}: ${result.error}`);
3125
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
2527
3126
  failCount++;
2528
3127
  }
2529
3128
  }
2530
3129
  const configRoot = getConfigRoot(tool2, level2);
2531
3130
  logger.blank();
2532
- logger.success(`${t("skill_install_done")}: ${chalk8.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk8.red(failCount + " \u5931\u8D25") : chalk8.dim(failCount + " \u5931\u8D25")}`);
3131
+ 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)))}`);
2533
3132
  logger.blank();
2534
- logger.info(`\u5B89\u88C5\u5230: ${chalk8.cyan(configRoot + "/skills/")}`);
2535
- logger.info(`\u542F\u52A8 ${chalk8.green(tool2)} \u5373\u53EF\u4F7F\u7528`);
3133
+ logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
3134
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
2536
3135
  logger.blank();
2537
3136
  return;
2538
3137
  }
2539
3138
  }
2540
3139
  }
2541
3140
  }
2542
- const scanSpinner = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
3141
+ const scanSpinner = createSpinner(t("loading_skills_list"));
2543
3142
  scanSpinner.start();
2544
3143
  try {
2545
3144
  const scanRepoManager = createRepositoryManager();
2546
3145
  await scanRepoManager.ensureRepoAndScan();
2547
- scanSpinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
3146
+ scanSpinner.succeed(t("loading_skills_list_complete"));
2548
3147
  } catch {
2549
- scanSpinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
3148
+ scanSpinner.warn(t("loading_skills_list_failed"));
2550
3149
  }
2551
3150
  const plugins = [];
2552
3151
  const skills = [];
@@ -2563,22 +3162,25 @@ async function installCommand(names, options) {
2563
3162
  }
2564
3163
  logger.error(`${t("error_plugin_not_found")}: ${name}`);
2565
3164
  const allPlugins = getAllPlugins();
2566
- logger.info("\u53EF\u7528\u63D2\u4EF6:");
3165
+ logger.info(t("available_plugins") + ":");
2567
3166
  for (const p of allPlugins) {
2568
3167
  logger.step(` ${p.id} (${p.aliases.join(", ")})`);
2569
3168
  }
2570
- logger.info(`\u53EF\u7528 Skills: ${chalk8.cyan("install-helper install --list")}`);
3169
+ logger.info(`${t("available_skills")}: ${chalk10.cyan("install-helper install --list")}`);
2571
3170
  return;
2572
3171
  }
2573
3172
  let tool;
2574
3173
  if (options.tool) {
2575
- tool = options.tool;
3174
+ tool = validateTool(options.tool);
2576
3175
  } else {
2577
3176
  const detected = await selectToolWithDetection();
2578
- if (detected === "back") return;
3177
+ if (detected === "back" || detected === "cancel") {
3178
+ logger.info(t("init_cancelled"));
3179
+ return;
3180
+ }
2579
3181
  tool = detected;
2580
3182
  }
2581
- const level = options.level || "project";
3183
+ const level = options.level ? validateLevel(options.level) : "project";
2582
3184
  if (plugins.length > 0) {
2583
3185
  const configRoot = getConfigRoot(tool, level);
2584
3186
  const manifests = readAllManifests(configRoot);
@@ -2606,16 +3208,18 @@ async function installCommand(names, options) {
2606
3208
  }
2607
3209
  if (pluginsToInstall.length > 0) {
2608
3210
  const repoManager = createRepositoryManager();
2609
- const spinner = createSpinner(t("install_cloning"));
3211
+ const spinner = createSpinner(t("loading_plugin_data"));
2610
3212
  spinner.start();
2611
3213
  const repoPath = await repoManager.ensureRepo();
2612
- spinner.succeed(t("install_repo_ready"));
3214
+ spinner.succeed(t("loading_plugin_data_complete"));
2613
3215
  const allPlugins = getAllPlugins();
2614
3216
  const results = await installPlugins(
2615
3217
  pluginsToInstall,
2616
3218
  tool,
2617
3219
  level,
2618
- repoPath
3220
+ repoPath,
3221
+ void 0,
3222
+ options.yes
2619
3223
  );
2620
3224
  const summary = results.map((result) => {
2621
3225
  const plugin = allPlugins.find((p) => p.id === result.pluginId);
@@ -2633,32 +3237,35 @@ async function installCommand(names, options) {
2633
3237
  }
2634
3238
  if (skills.length > 0) {
2635
3239
  const repoManager = createRepositoryManager();
2636
- const spinner = createSpinner(t("install_cloning"));
3240
+ const spinner = createSpinner(t("loading_plugin_data"));
2637
3241
  spinner.start();
2638
3242
  const repoPath = await repoManager.ensureRepo();
2639
- spinner.succeed(t("install_repo_ready"));
2640
- logger.info(`${t("skill_install_progress")} ${chalk8.bold(skills.length)} \u4E2A Skills...`);
3243
+ spinner.succeed(t("loading_plugin_data_complete"));
3244
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(skills.length)))}...`);
2641
3245
  const results = await installSkills(skills, tool, level, repoPath);
2642
3246
  let successCount = 0;
2643
3247
  let failCount = 0;
2644
- for (const result of results) {
3248
+ const total = results.length;
3249
+ for (let i = 0; i < results.length; i++) {
3250
+ const result = results[i];
3251
+ const progress = `[${i + 1}/${total}]`;
2645
3252
  if (result.success) {
2646
- logger.success(`${result.skillId}`);
3253
+ logger.success(`${progress} ${result.skillId}`);
2647
3254
  successCount++;
2648
3255
  } else {
2649
- logger.error(`${result.skillId}: ${result.error}`);
3256
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
2650
3257
  failCount++;
2651
3258
  }
2652
3259
  }
2653
3260
  logger.blank();
2654
- logger.success(`${t("skill_install_done")}: ${chalk8.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk8.red(failCount + " \u5931\u8D25") : chalk8.dim(failCount + " \u5931\u8D25")}`);
3261
+ 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)))}`);
2655
3262
  logger.blank();
2656
- logger.info(`\u5B89\u88C5\u5230: ${chalk8.cyan(getConfigRoot(tool, level) + "/skills/")}`);
2657
- logger.info(`\u542F\u52A8 ${chalk8.green(tool)} \u5373\u53EF\u4F7F\u7528`);
3263
+ logger.info(`${t("install_to")}: ${chalk10.cyan(getConfigRoot(tool, level) + "/skills/")}`);
3264
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool))}`);
2658
3265
  logger.blank();
2659
3266
  }
2660
3267
  }
2661
- async function installPlugins(pluginIds, tool, level, repoPath) {
3268
+ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes) {
2662
3269
  const results = [];
2663
3270
  const total = pluginIds.length;
2664
3271
  for (let i = 0; i < pluginIds.length; i++) {
@@ -2670,7 +3277,9 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
2670
3277
  pluginId,
2671
3278
  tool,
2672
3279
  level,
2673
- repoPath
3280
+ repoPath,
3281
+ installPath,
3282
+ yes
2674
3283
  });
2675
3284
  if (result.success) {
2676
3285
  spinner.succeed(
@@ -2686,11 +3295,12 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
2686
3295
  }
2687
3296
 
2688
3297
  // src/commands/uninstall.ts
2689
- import { existsSync as existsSync12, unlinkSync as unlinkSync4, rmdirSync as rmdirSync2, readdirSync as readdirSync5, lstatSync as lstatSync4 } from "fs";
2690
- import chalk9 from "chalk";
3298
+ import { existsSync as existsSync16, unlinkSync as unlinkSync7, rmdirSync as rmdirSync2, readdirSync as readdirSync6 } from "fs";
3299
+ import { resolve, basename as basename2 } from "path";
3300
+ import chalk11 from "chalk";
2691
3301
  async function uninstallCommand(names, options) {
2692
- const tool = options.tool || "opencode";
2693
- const level = options.level || "project";
3302
+ const tool = options.tool ? validateTool(options.tool) : "opencode";
3303
+ const level = options.level ? validateLevel(options.level) : "project";
2694
3304
  const plugins = [];
2695
3305
  const skills = [];
2696
3306
  for (const name of names) {
@@ -2704,40 +3314,50 @@ async function uninstallCommand(names, options) {
2704
3314
  skills.push(skill.id);
2705
3315
  continue;
2706
3316
  }
2707
- logger.error(`\u672A\u627E\u5230: ${name}`);
3317
+ logger.error(`${t("uninstall_not_found")}: ${name}`);
2708
3318
  }
2709
3319
  for (const pluginId of plugins) {
2710
3320
  const plugin = findPlugin(pluginId);
2711
3321
  if (!plugin) continue;
2712
3322
  const record = readRecord(plugin.id);
2713
3323
  if (!record) {
2714
- 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`);
2715
- 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`);
3324
+ logger.warn(`${plugin.displayName} ${t("uninstall_no_record")}`);
3325
+ logger.info(`${t("uninstall_no_record_hint")}`);
2716
3326
  continue;
2717
3327
  }
2718
- logger.info(`\u6B63\u5728\u5378\u8F7D ${plugin.displayName}...`);
3328
+ logger.info(`${t("uninstall_in_progress")} ${plugin.displayName}...`);
3329
+ const configRoot = getConfigRoot(record.tool, record.level);
3330
+ const allowedBases = [resolve(configRoot), resolve(record.installPath)];
3331
+ const isSafePath = (p) => {
3332
+ const resolved = resolve(p);
3333
+ return allowedBases.some((base) => resolved === base || resolved.startsWith(base + "/"));
3334
+ };
2719
3335
  let removedFiles = 0;
2720
3336
  let removedDirs = 0;
2721
3337
  for (const filePath of record.files) {
3338
+ if (!isSafePath(filePath)) continue;
2722
3339
  try {
2723
- if (existsSync12(filePath) || isSymlink3(filePath)) {
2724
- unlinkSync4(filePath);
2725
- const name = filePath.split("/").pop() || filePath;
2726
- logger.step(` \u79FB\u9664: ${name}`);
3340
+ if (existsSync16(filePath) || isSymlink(filePath)) {
3341
+ unlinkSync7(filePath);
3342
+ const name = basename2(filePath);
3343
+ logger.step(` ${t("uninstall_remove_label")}: ${name}`);
2727
3344
  removedFiles++;
2728
3345
  }
2729
3346
  } catch {
2730
3347
  }
2731
3348
  }
2732
- const sortedDirs = [...record.directories].sort((a, b) => b.length - a.length);
3349
+ const sortedDirs = [...record.directories].filter(isSafePath).sort((a, b) => b.length - a.length);
2733
3350
  for (const dirPath of sortedDirs) {
2734
3351
  try {
2735
- if (existsSync12(dirPath)) {
2736
- const entries = readdirSync5(dirPath);
3352
+ if (resolve(dirPath) === resolve(configRoot)) {
3353
+ continue;
3354
+ }
3355
+ if (existsSync16(dirPath)) {
3356
+ const entries = readdirSync6(dirPath);
2737
3357
  if (entries.length === 0) {
2738
3358
  rmdirSync2(dirPath);
2739
- const name = dirPath.split("/").pop() || dirPath;
2740
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${name}/`);
3359
+ const name = basename2(dirPath);
3360
+ logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
2741
3361
  removedDirs++;
2742
3362
  }
2743
3363
  }
@@ -2746,15 +3366,32 @@ async function uninstallCommand(names, options) {
2746
3366
  }
2747
3367
  deleteRecord(plugin.id);
2748
3368
  removeInstalledPlugin(plugin.id);
3369
+ const backups = findBackups(configRoot);
3370
+ const otherBackups = backups.filter((b) => b.pluginId !== pluginId);
3371
+ if (otherBackups.length > 0) {
3372
+ const choice = await showRestorePrompt(otherBackups);
3373
+ if (choice !== "none") {
3374
+ const restored = restoreBackup(choice, configRoot, record.tool);
3375
+ if (restored) {
3376
+ const backupInfo = otherBackups.find((b) => b.filePath === choice);
3377
+ logger.success(`${t("backup_restore_success")}: ${backupInfo?.pluginName || ""}`);
3378
+ deleteBackup(choice);
3379
+ }
3380
+ }
3381
+ }
3382
+ const currentBackups = backups.filter((b) => b.pluginId === pluginId);
3383
+ for (const backup of currentBackups) {
3384
+ deleteBackup(backup.filePath);
3385
+ }
2749
3386
  const totalRemoved = removedFiles + removedDirs;
2750
3387
  if (totalRemoved > 0) {
2751
- logger.success(`\u5DF2\u5378\u8F7D ${plugin.displayName}\uFF08\u79FB\u9664 ${removedFiles} \u4E2A\u6587\u4EF6\uFF0C${removedDirs} \u4E2A\u76EE\u5F55\uFF09`);
3388
+ logger.success(t("uninstall_summary_format").replace("{name}", plugin.displayName).replace("{files}", String(removedFiles)).replace("{dirs}", String(removedDirs)));
2752
3389
  } else {
2753
- logger.warn(`${plugin.displayName} \u7684\u6587\u4EF6\u5DF2\u4E0D\u5B58\u5728\uFF0C\u5DF2\u6E05\u7406\u5B89\u88C5\u8BB0\u5F55`);
3390
+ logger.warn(`${plugin.displayName}${t("uninstall_files_gone")}`);
2754
3391
  }
2755
3392
  }
2756
3393
  if (skills.length > 0) {
2757
- logger.info(`\u6B63\u5728\u5378\u8F7D ${skills.length} \u4E2A Skills...`);
3394
+ logger.info(`${t("uninstall_skills_in_progress")} ${t("skill_count_format").replace("{count}", String(skills.length))}...`);
2758
3395
  const results = await uninstallSkills(skills, tool, level);
2759
3396
  let successCount = 0;
2760
3397
  let failCount = 0;
@@ -2766,30 +3403,26 @@ async function uninstallCommand(names, options) {
2766
3403
  }
2767
3404
  }
2768
3405
  logger.blank();
2769
- logger.success(`${t("skill_uninstall_done")}: ${chalk9.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk9.red(failCount + " \u5931\u8D25") : chalk9.dim(failCount + " \u5931\u8D25")}`);
3406
+ 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)))}`);
2770
3407
  logger.blank();
2771
3408
  }
2772
3409
  }
2773
- function isSymlink3(path) {
2774
- try {
2775
- return lstatSync4(path).isSymbolicLink();
2776
- } catch {
2777
- return false;
2778
- }
2779
- }
2780
3410
 
2781
3411
  // src/commands/update.ts
2782
- import chalk10 from "chalk";
3412
+ import chalk12 from "chalk";
2783
3413
  async function updateCommand(pluginNames, options) {
2784
3414
  let tool;
2785
3415
  if (options.tool) {
2786
- tool = options.tool;
3416
+ tool = validateTool(options.tool);
2787
3417
  } else {
2788
3418
  const detected = await selectToolWithDetection();
2789
- if (detected === "back") return;
3419
+ if (detected === "back" || detected === "cancel") {
3420
+ logger.info(t("init_cancelled"));
3421
+ return;
3422
+ }
2790
3423
  tool = detected;
2791
3424
  }
2792
- const level = options.level || "project";
3425
+ const level = options.level ? validateLevel(options.level) : "project";
2793
3426
  let pluginsToUpdate = [];
2794
3427
  if (pluginNames.length === 0) {
2795
3428
  const configRoot = getConfigRoot(tool, level);
@@ -2797,7 +3430,7 @@ async function updateCommand(pluginNames, options) {
2797
3430
  pluginsToUpdate = manifests.map((m) => m.team);
2798
3431
  if (pluginsToUpdate.length === 0) {
2799
3432
  logger.info(t("update_no_plugins"));
2800
- logger.info(`\u4F7F\u7528 ${chalk10.cyan("install-helper install <plugin>")} \u5B89\u88C5\u63D2\u4EF6`);
3433
+ logger.info(t("update_install_hint").replace("{cmd}", chalk12.cyan("install-helper install <plugin>")));
2801
3434
  return;
2802
3435
  }
2803
3436
  } else {
@@ -2813,9 +3446,7 @@ async function updateCommand(pluginNames, options) {
2813
3446
  const repoManager = createRepositoryManager();
2814
3447
  const updateSpinner = createSpinner(t("update_updating") + "...");
2815
3448
  updateSpinner.start();
2816
- await repoManager.updateRepo();
2817
- const repoPath = repoManager.getRepoPath();
2818
- await repoManager.ensureRepoAndScan();
3449
+ const repoPath = await repoManager.ensureRepoAndScan();
2819
3450
  updateSpinner.succeed(t("install_repo_ready"));
2820
3451
  const allPlugins = getAllPlugins();
2821
3452
  const results = [];
@@ -2859,42 +3490,43 @@ async function updateCommand(pluginNames, options) {
2859
3490
  }
2860
3491
 
2861
3492
  // src/commands/info.ts
2862
- import chalk11 from "chalk";
2863
- import { existsSync as existsSync13, readdirSync as readdirSync6 } from "fs";
2864
- import { join as join12 } from "path";
3493
+ import chalk13 from "chalk";
3494
+ import { existsSync as existsSync17, readdirSync as readdirSync7 } from "fs";
3495
+ import { join as join16 } from "path";
2865
3496
  async function infoCommand(pluginName) {
2866
3497
  const plugin = findPlugin(pluginName);
2867
3498
  if (!plugin) {
2868
- console.log(chalk11.red(` ${t("info_not_found")}: ${pluginName}`));
3499
+ console.log(chalk13.red(` ${t("info_not_found")}: ${pluginName}`));
2869
3500
  return;
2870
3501
  }
2871
3502
  const installed = scanInstalled();
2872
- const isInstalled = installed.some((p) => p.id === plugin.id);
2873
- const statusText = isInstalled ? chalk11.green(`\u2713 ${t("status_installed")}`) : chalk11.dim(`\u2014 ${t("status_not_installed")}`);
3503
+ const inst = installed.find((p) => p.id === plugin.id);
3504
+ const isInstalled = !!inst;
3505
+ const statusText = isInstalled ? chalk13.green(`\u2713 ${t("status_installed")}`) : chalk13.dim(`\u2014 ${t("status_not_installed")}`);
2874
3506
  console.log();
2875
- console.log(chalk11.bold(` ${plugin.displayName}`));
2876
- console.log(chalk11.dim(" " + "\u2500".repeat(40)));
3507
+ console.log(chalk13.bold(` ${plugin.displayName}`));
3508
+ console.log(chalk13.dim(" " + "\u2500".repeat(40)));
2877
3509
  console.log();
2878
- console.log(` ${chalk11.dim(t("info_description") + ":")} ${plugin.description}`);
2879
- console.log(` ${chalk11.dim("ID:")} ${plugin.id}`);
2880
- console.log(` ${chalk11.dim(t("info_status") + ":")} ${statusText}`);
2881
- console.log(` ${chalk11.dim(t("info_skills") + ":")} ${plugin.skills}`);
2882
- console.log(` ${chalk11.dim(t("info_agents") + ":")} ${plugin.agents}`);
2883
- console.log(` ${chalk11.dim("\u522B\u540D:")} ${plugin.aliases.join(", ")}`);
3510
+ console.log(` ${chalk13.dim(t("info_description") + ":")} ${plugin.description}`);
3511
+ console.log(` ${chalk13.dim("ID:")} ${plugin.id}`);
3512
+ console.log(` ${chalk13.dim(t("info_status") + ":")} ${statusText}`);
3513
+ console.log(` ${chalk13.dim(t("info_skills") + ":")} ${inst ? inst.skillsCount : plugin.skills}`);
3514
+ console.log(` ${chalk13.dim(t("info_agents") + ":")} ${inst ? inst.agentsCount : plugin.agents}`);
3515
+ console.log(` ${chalk13.dim(t("info_aliases") + ":")} ${plugin.aliases.join(", ")}`);
2884
3516
  console.log();
2885
3517
  try {
2886
3518
  const repoManager = createRepositoryManager();
2887
3519
  const repoPath = await repoManager.ensureRepo();
2888
- const quickstartPath = join12(repoPath, plugin.dir, "quickstart.md");
2889
- if (existsSync13(quickstartPath)) {
2890
- console.log(` ${chalk11.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
3520
+ const quickstartPath = join16(repoPath, plugin.dir, "quickstart.md");
3521
+ if (existsSync17(quickstartPath)) {
3522
+ console.log(` ${chalk13.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
2891
3523
  }
2892
- const agentsDir = join12(repoPath, plugin.dir, "agents");
2893
- if (existsSync13(agentsDir)) {
2894
- const agentFiles = readdirSync6(agentsDir).filter((f) => f.endsWith(".md"));
3524
+ const agentsDir = join16(repoPath, plugin.dir, "agents");
3525
+ if (existsSync17(agentsDir)) {
3526
+ const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md"));
2895
3527
  if (agentFiles.length > 0) {
2896
3528
  console.log();
2897
- console.log(` ${chalk11.bold("Agents:")}`);
3529
+ console.log(` ${chalk13.bold("Agents:")}`);
2898
3530
  for (const agent of agentFiles) {
2899
3531
  console.log(` \u2022 ${agent.replace(".md", "")}`);
2900
3532
  }
@@ -2906,49 +3538,63 @@ async function infoCommand(pluginName) {
2906
3538
  }
2907
3539
 
2908
3540
  // src/commands/lang.ts
2909
- import chalk12 from "chalk";
3541
+ import chalk14 from "chalk";
2910
3542
  async function langCommand(action, value) {
2911
3543
  const config = readConfig();
2912
3544
  if (!action || action === "show") {
2913
3545
  console.log();
2914
- console.log(chalk12.bold(` ${t("lang_title")}`));
2915
- console.log(chalk12.dim(" " + "\u2500".repeat(40)));
3546
+ console.log(chalk14.bold(` ${t("lang_title")}`));
3547
+ console.log(chalk14.dim(" " + "\u2500".repeat(40)));
2916
3548
  console.log();
2917
- console.log(` ${chalk12.dim(t("lang_current") + ":")} ${config.language}`);
3549
+ console.log(` ${chalk14.dim(t("lang_current") + ":")} ${config.language}`);
2918
3550
  console.log();
2919
- console.log(chalk12.dim(" \u652F\u6301\u7684\u8BED\u8A00: zh_CN, en_US"));
2920
- console.log(chalk12.dim(" \u7528\u6CD5: install-helper lang set <language>"));
3551
+ console.log(chalk14.dim(` ${t("lang_supported")}`));
3552
+ console.log(chalk14.dim(` ${t("lang_usage")}`));
2921
3553
  console.log();
2922
3554
  return;
2923
3555
  }
2924
3556
  if (action === "set") {
2925
3557
  if (!value) {
2926
- console.log(chalk12.red(" \u8BF7\u6307\u5B9A\u8BED\u8A00: install-helper lang set zh_CN"));
3558
+ console.log(chalk14.red(` ${t("lang_specify")}`));
2927
3559
  return;
2928
3560
  }
2929
3561
  if (value !== "zh_CN" && value !== "en_US") {
2930
- console.log(chalk12.red(` ${t("lang_invalid")}`));
3562
+ console.log(chalk14.red(` ${t("lang_invalid")}`));
2931
3563
  return;
2932
3564
  }
2933
3565
  updateConfig({ language: value });
2934
3566
  setLanguage(value);
2935
3567
  console.log();
2936
- console.log(chalk12.green(` \u2713 ${t("lang_set")} ${value}`));
3568
+ console.log(chalk14.green(` \u2713 ${t("lang_set")} ${value}`));
2937
3569
  console.log();
2938
3570
  return;
2939
3571
  }
2940
- console.log(chalk12.red(` \u672A\u77E5\u64CD\u4F5C: ${action}`));
2941
- console.log(chalk12.dim(" \u652F\u6301\u7684\u64CD\u4F5C: show, set"));
3572
+ console.log(chalk14.red(` ${t("lang_unknown_action")}: ${action}`));
3573
+ console.log(chalk14.dim(` ${t("lang_supported_actions")}`));
2942
3574
  }
2943
3575
 
2944
3576
  // src/cli.ts
3577
+ import { readFileSync as readFileSync10 } from "fs";
3578
+ import { fileURLToPath as fileURLToPath5 } from "url";
3579
+ import { dirname as dirname8, join as join17 } from "path";
3580
+ function readVersion() {
3581
+ try {
3582
+ const __filename5 = fileURLToPath5(import.meta.url);
3583
+ const __dirname5 = dirname8(__filename5);
3584
+ const pkgPath = join17(__dirname5, "..", "package.json");
3585
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
3586
+ return pkg.version || "0.0.0";
3587
+ } catch {
3588
+ return "0.0.0";
3589
+ }
3590
+ }
2945
3591
  function createCLI() {
2946
3592
  const config = readConfig();
2947
3593
  if (config.language) {
2948
3594
  setLanguage(config.language);
2949
3595
  }
2950
3596
  const program2 = new Command();
2951
- program2.name("install-helper").description("Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.4");
3597
+ program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version(readVersion());
2952
3598
  program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
2953
3599
  await initCommand();
2954
3600
  });
@@ -2961,7 +3607,7 @@ function createCLI() {
2961
3607
  program2.command("status").description("Show installed plugins").action(async () => {
2962
3608
  await statusCommand();
2963
3609
  });
2964
- 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) => {
3610
+ 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) => {
2965
3611
  await installCommand(names, options);
2966
3612
  });
2967
3613
  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) => {
@@ -2985,7 +3631,7 @@ function createCLI() {
2985
3631
  // src/index.ts
2986
3632
  process.on("uncaughtException", (error) => {
2987
3633
  if (error.name === "ExitPromptError") {
2988
- console.log("\n\u5DF2\u53D6\u6D88\u5B89\u88C5");
3634
+ console.log("\n" + t("init_cancelled"));
2989
3635
  process.exit(0);
2990
3636
  }
2991
3637
  throw error;