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

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,56 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
- import { select as select3, Separator as Separator3 } from "@inquirer/prompts";
8
- import chalk5 from "chalk";
7
+ import { select as select4, Separator as Separator3 } from "@inquirer/prompts";
8
+ import chalk7 from "chalk";
9
9
 
10
10
  // src/ui/wizard.ts
11
11
  import { select, checkbox, Separator } from "@inquirer/prompts";
12
- import chalk2 from "chalk";
12
+ import chalk3 from "chalk";
13
13
 
14
14
  // src/core/registry.ts
15
- var PLUGIN_REGISTRY = [
16
- {
17
- id: "ops-direct-invoke",
18
- dir: "plugins-official/ops-direct-invoke",
19
- displayName: "AscendC Kernel \u76F4\u8C03",
20
- script: "init.sh",
21
- aliases: ["ops-direct", "ascendc-direct", "direct", "kernel"],
22
- skills: 16,
23
- agents: 3,
24
- description: "Ascend C Kernel <<<>>> \u76F4\u8C03\u7B97\u5B50\u5F00\u53D1\u5168\u6D41\u7A0B"
25
- },
26
- {
27
- id: "ops-direct-invoke-flash",
28
- dir: "plugins-official/ops-direct-invoke-flash",
29
- displayName: "AscendC Kernel \u4ECE\u96F6\u6784\u5EFA",
30
- script: "init.sh",
31
- aliases: ["flash", "kernel-flash"],
32
- skills: 1,
33
- agents: 1,
34
- description: "Ascend C / Ascend950 Reg API \u6838\u51FD\u6570\u4ECE\u96F6\u6784\u5EFA"
35
- },
36
- {
37
- id: "ops-registry-invoke",
38
- dir: "plugins-official/ops-registry-invoke",
39
- displayName: "AscendC \u7B97\u5B50\u6CE8\u518C\u8C03\u7528",
40
- script: "init.sh",
41
- aliases: ["ops-registry", "ascendc-registry", "registry"],
42
- skills: 12,
43
- agents: 4,
44
- description: "Ascend C \u7B97\u5B50\u6CE8\u518C\u8C03\u7528\u5F00\u53D1\u6D41\u7A0B"
45
- },
46
- {
47
- id: "pypto-op-orchestrator",
48
- dir: "plugins-official/pypto-op-orchestrator",
49
- displayName: "PyPTO \u7B97\u5B50",
50
- script: "init.sh",
51
- aliases: ["pypto", "pytorch"],
52
- skills: 8,
53
- agents: 3,
54
- description: "PyPTO \u7B97\u5B50\u7AEF\u5230\u7AEF\u5F00\u53D1\u7F16\u6392"
55
- },
56
- {
57
- id: "triton-op-generator",
58
- dir: "plugins-official/triton-op-generator",
59
- displayName: "Triton \u7B97\u5B50\u751F\u6210",
60
- script: "install.sh",
61
- aliases: ["triton"],
62
- skills: 6,
63
- agents: 0,
64
- description: "Triton-Ascend \u7B97\u5B50\u4EE3\u7801\u751F\u6210\u4E0E\u4F18\u5316"
65
- },
66
- {
67
- id: "tilelang-op-orchestrator",
68
- dir: "plugins-official/tilelang-op-orchestrator",
69
- displayName: "TileLang \u7B97\u5B50",
70
- script: "init.sh",
71
- aliases: ["tilelang"],
72
- skills: 9,
73
- agents: 3,
74
- description: "TileLang \u7B97\u5B50\u5F00\u53D1\u6D41\u7A0B"
75
- },
76
- {
77
- id: "model-infer-optimize",
78
- dir: "plugins-official/model-infer-optimize",
79
- displayName: "NPU \u63A8\u7406\u4F18\u5316",
80
- script: "init.sh",
81
- aliases: ["model-infer", "infer", "inference"],
82
- skills: 11,
83
- agents: 3,
84
- description: "NPU \u6A21\u578B\u63A8\u7406\u7AEF\u5230\u7AEF\u4F18\u5316"
85
- },
86
- {
87
- id: "catlass-op-generator",
88
- dir: "plugins-official/catlass-op-generator",
89
- displayName: "Catlass \u7B97\u5B50\u76F4\u8C03",
90
- script: "init.sh",
91
- aliases: ["catlass"],
92
- skills: 10,
93
- agents: 3,
94
- description: "Catlass \u7B97\u5B50\u76F4\u8C03\u5F00\u53D1"
95
- },
96
- {
97
- id: "ops-code-reviewer",
98
- dir: "plugins-official/ops-code-reviewer",
99
- displayName: "\u4EE3\u7801\u68C0\u89C6",
100
- script: "init.sh",
101
- aliases: ["code-review", "reviewer", "review"],
102
- skills: 5,
103
- agents: 1,
104
- description: "\u4EE3\u7801\u68C0\u89C6\u4E0E\u89C4\u8303\u68C0\u67E5"
105
- },
106
- {
107
- id: "torch-compile",
108
- dir: "plugins-official/torch-compile",
109
- displayName: "torch.compile \u56FE\u6A21\u5F0F",
110
- script: "init.sh",
111
- aliases: ["torch", "compile", "graph"],
112
- skills: 6,
113
- agents: 1,
114
- description: "PyTorch torch.compile \u56FE\u6A21\u5F0F\u9002\u914D"
15
+ import { readdirSync, readFileSync } from "fs";
16
+ import { join, dirname } from "path";
17
+ import { fileURLToPath } from "url";
18
+ import { parse as parseYaml } from "yaml";
19
+ var __filename = fileURLToPath(import.meta.url);
20
+ var __dirname = dirname(__filename);
21
+ function loadPluginsFromYaml() {
22
+ const pluginsDir = join(__dirname, "plugins.d");
23
+ const defaultsPath = join(pluginsDir, "_defaults.yml");
24
+ let defaults = {};
25
+ try {
26
+ defaults = parseYaml(readFileSync(defaultsPath, "utf-8")) || {};
27
+ } catch {
115
28
  }
116
- ];
29
+ const plugins = [];
30
+ try {
31
+ for (const file of readdirSync(pluginsDir)) {
32
+ if (file.startsWith("_") || !file.endsWith(".yml")) continue;
33
+ try {
34
+ const content = parseYaml(readFileSync(join(pluginsDir, file), "utf-8"));
35
+ if (!content || !content.id) continue;
36
+ plugins.push({
37
+ id: content.id,
38
+ dir: content.dir,
39
+ displayName: content.displayName || content.id,
40
+ script: content.script || defaults.script || "init.sh",
41
+ aliases: content.aliases || [],
42
+ skills: content.skills ?? defaults.skills ?? 0,
43
+ agents: content.agents ?? defaults.agents ?? 0,
44
+ description: content.description || ""
45
+ });
46
+ } catch {
47
+ }
48
+ }
49
+ } catch {
50
+ }
51
+ plugins.sort(
52
+ (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
53
+ );
54
+ return plugins;
55
+ }
56
+ var PLUGIN_REGISTRY = loadPluginsFromYaml();
117
57
  function findPlugin(query) {
118
58
  const normalized = query.toLowerCase().trim();
119
59
  const exactMatch = PLUGIN_REGISTRY.find(
@@ -139,17 +79,68 @@ function getPluginById(id) {
139
79
  import { execa } from "execa";
140
80
  import { existsSync as existsSync2 } from "fs";
141
81
  import { homedir as homedir2 } from "os";
142
- import { join as join2 } from "path";
82
+ import { join as join4 } from "path";
143
83
 
144
84
  // src/utils/paths.ts
145
85
  import { homedir } from "os";
146
- import { join } from "path";
86
+ import { join as join3 } from "path";
147
87
  import { existsSync } from "fs";
88
+
89
+ // src/utils/i18n.ts
90
+ import { readFileSync as readFileSync2 } from "fs";
91
+ import { join as join2, dirname as dirname2 } from "path";
92
+ import { fileURLToPath as fileURLToPath2 } from "url";
93
+ var __filename2 = fileURLToPath2(import.meta.url);
94
+ var __dirname2 = dirname2(__filename2);
95
+ function loadLocale(locale) {
96
+ const localePath = join2(__dirname2, "locales", `${locale}.json`);
97
+ try {
98
+ const content = readFileSync2(localePath, "utf-8");
99
+ return JSON.parse(content);
100
+ } catch {
101
+ const fallbackPath = join2(__dirname2, "locales", "zh_CN.json");
102
+ try {
103
+ const content = readFileSync2(fallbackPath, "utf-8");
104
+ return JSON.parse(content);
105
+ } catch {
106
+ return {};
107
+ }
108
+ }
109
+ }
110
+ var currentLang = "zh_CN";
111
+ var messages = loadLocale("zh_CN");
112
+ function setLanguage(lang) {
113
+ currentLang = lang;
114
+ messages = loadLocale(lang);
115
+ }
116
+ function t(key) {
117
+ return messages[key] !== void 0 ? messages[key] : key;
118
+ }
119
+
120
+ // src/utils/paths.ts
121
+ var VALID_TOOLS = ["opencode", "claude", "trae", "cursor", "copilot"];
122
+ var VALID_LEVELS = ["project", "global"];
123
+ function validateTool(tool) {
124
+ if (!VALID_TOOLS.includes(tool)) {
125
+ throw new Error(
126
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
127
+ );
128
+ }
129
+ return tool;
130
+ }
131
+ function validateLevel(level) {
132
+ if (!VALID_LEVELS.includes(level)) {
133
+ throw new Error(
134
+ t("error_invalid_level").replace("{level}", level).replace("{levels}", VALID_LEVELS.join(", "))
135
+ );
136
+ }
137
+ return level;
138
+ }
148
139
  function detectTraeVariant() {
149
140
  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";
141
+ if (existsSync(join3(home, ".trae-cn"))) return "ide";
142
+ if (existsSync(join3(home, ".marscode"))) return "plugin";
143
+ if (existsSync(join3(home, ".traecli"))) return "cli";
153
144
  return "unknown";
154
145
  }
155
146
  function getConfigRoot(tool, level, base) {
@@ -157,66 +148,74 @@ function getConfigRoot(tool, level, base) {
157
148
  if (level === "global") {
158
149
  switch (tool) {
159
150
  case "opencode":
160
- return join(home, ".config", "opencode");
151
+ return join3(home, ".config", "opencode");
161
152
  case "claude":
162
- return join(home, ".claude");
153
+ return join3(home, ".claude");
163
154
  case "trae": {
164
155
  const variant = detectTraeVariant();
165
156
  switch (variant) {
166
157
  case "plugin":
167
- return join(home, ".marscode");
158
+ return join3(home, ".marscode");
168
159
  case "cli":
169
- return join(home, ".traecli");
160
+ return join3(home, ".traecli");
170
161
  default:
171
- return join(home, ".trae-cn");
162
+ return join3(home, ".trae-cn");
172
163
  }
173
164
  }
174
165
  case "cursor":
175
- return join(home, ".cursor");
166
+ return join3(home, ".cursor");
176
167
  case "copilot":
177
- return join(home, ".copilot");
168
+ return join3(home, ".copilot");
169
+ default:
170
+ throw new Error(
171
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
172
+ );
178
173
  }
179
174
  }
180
175
  const baseDir = base || process.cwd();
181
176
  switch (tool) {
182
177
  case "opencode":
183
- return join(baseDir, ".opencode");
178
+ return join3(baseDir, ".opencode");
184
179
  case "claude":
185
- return join(baseDir, ".claude");
180
+ return join3(baseDir, ".claude");
186
181
  case "trae": {
187
182
  const variant = detectTraeVariant();
188
183
  switch (variant) {
189
184
  case "plugin":
190
- return join(baseDir, ".marscode");
185
+ return join3(baseDir, ".marscode");
191
186
  case "cli":
192
- return join(baseDir, ".traecli");
187
+ return join3(baseDir, ".traecli");
193
188
  default:
194
- return join(baseDir, ".trae");
189
+ return join3(baseDir, ".trae");
195
190
  }
196
191
  }
197
192
  case "cursor":
198
- return join(baseDir, ".cursor");
193
+ return join3(baseDir, ".cursor");
199
194
  case "copilot":
200
- return join(baseDir, ".github");
195
+ return join3(baseDir, ".github");
196
+ default:
197
+ throw new Error(
198
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
199
+ );
201
200
  }
202
201
  }
203
202
  function getConfigFileName(tool) {
204
203
  return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
205
204
  }
206
205
  function getSkillsDir(configRoot) {
207
- return join(configRoot, "skills");
206
+ return join3(configRoot, "skills");
208
207
  }
209
208
  function getAgentsDir(configRoot) {
210
- return join(configRoot, "agents");
209
+ return join3(configRoot, "agents");
211
210
  }
212
211
  function getManifestPath(configRoot) {
213
- return join(configRoot, "cannbot-manifest.json");
212
+ return join3(configRoot, "cannbot-manifest.json");
214
213
  }
215
214
  function getCannbotConfigDir() {
216
- return join(homedir(), ".cannbot");
215
+ return join3(homedir(), ".cannbot");
217
216
  }
218
217
  function getCannbotRepoPath() {
219
- return join(getCannbotConfigDir(), "repo");
218
+ return join3(getCannbotConfigDir(), "repo");
220
219
  }
221
220
 
222
221
  // src/core/detector.ts
@@ -232,7 +231,7 @@ async function getCommandVersion(cmd, args = ["--version"]) {
232
231
  }
233
232
  async function getCommandPath(cmd) {
234
233
  try {
235
- const result = await execa("sh", ["-c", `command -v ${cmd}`], { timeout: 5e3 });
234
+ const result = await execa("which", [cmd], { timeout: 5e3 });
236
235
  return result.stdout.trim();
237
236
  } catch {
238
237
  return void 0;
@@ -254,12 +253,12 @@ async function detectTrae() {
254
253
  const home = homedir2();
255
254
  const variant = detectTraeVariant();
256
255
  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");
256
+ if (variant === "ide" && existsSync2(join4(home, ".trae-cn"))) {
257
+ path = join4(home, ".trae-cn");
258
+ } else if (variant === "plugin" && existsSync2(join4(home, ".marscode"))) {
259
+ path = join4(home, ".marscode");
260
+ } else if (variant === "cli" && existsSync2(join4(home, ".traecli"))) {
261
+ path = join4(home, ".traecli");
263
262
  } else if (variant === "unknown") {
264
263
  const cmdPath = await getCommandPath("trae");
265
264
  if (cmdPath) path = cmdPath;
@@ -323,13 +322,13 @@ function getAllTools() {
323
322
  }
324
323
 
325
324
  // src/core/manifest.ts
326
- import { existsSync as existsSync3, readFileSync } from "fs";
327
- import { join as join3 } from "path";
325
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
326
+ import { join as join5 } from "path";
328
327
  function readManifest(configRoot) {
329
328
  const manifestPath = getManifestPath(configRoot);
330
329
  if (existsSync3(manifestPath)) {
331
330
  try {
332
- const content = readFileSync(manifestPath, "utf-8");
331
+ const content = readFileSync3(manifestPath, "utf-8");
333
332
  return JSON.parse(content);
334
333
  } catch {
335
334
  }
@@ -342,16 +341,16 @@ function readAllManifests(configRoot) {
342
341
  const standardPath = getManifestPath(configRoot);
343
342
  if (existsSync3(standardPath)) {
344
343
  try {
345
- const content = readFileSync(standardPath, "utf-8");
344
+ const content = readFileSync3(standardPath, "utf-8");
346
345
  manifests.push(JSON.parse(content));
347
346
  } catch {
348
347
  }
349
348
  }
350
349
  for (const plugin of plugins) {
351
- const pluginManifestPath = join3(configRoot, `${plugin.id}-manifest.json`);
350
+ const pluginManifestPath = join5(configRoot, `${plugin.id}-manifest.json`);
352
351
  if (existsSync3(pluginManifestPath)) {
353
352
  try {
354
- const content = readFileSync(pluginManifestPath, "utf-8");
353
+ const content = readFileSync3(pluginManifestPath, "utf-8");
355
354
  manifests.push(JSON.parse(content));
356
355
  } catch {
357
356
  }
@@ -371,6 +370,9 @@ function scanInstalled() {
371
370
  for (const manifest of manifests) {
372
371
  const plugin = plugins.find((p) => p.id === manifest.team);
373
372
  if (!plugin) continue;
373
+ if (!verifyPluginFilesExist(configRoot, manifest, tool, level)) {
374
+ continue;
375
+ }
374
376
  const alreadyAdded = installed.some(
375
377
  (p) => p.id === plugin.id && p.tool === tool && p.level === level
376
378
  );
@@ -390,190 +392,31 @@ function scanInstalled() {
390
392
  }
391
393
  return installed;
392
394
  }
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];
395
+ function verifyPluginFilesExist(configRoot, manifest, tool, level) {
396
+ const skillsDir = join5(configRoot, "skills");
397
+ const agentsDir = join5(configRoot, "agents");
398
+ if (manifest.installed_skills && manifest.installed_skills.length > 0) {
399
+ const hasAnySkill = manifest.installed_skills.some((skillId) => {
400
+ const skillPath = join5(skillsDir, skillId);
401
+ return existsSync3(skillPath);
402
+ });
403
+ if (!hasAnySkill) return false;
404
+ }
405
+ if (manifest.installed_agents && manifest.installed_agents.length > 0) {
406
+ const hasAnyAgent = manifest.installed_agents.some((agentId) => {
407
+ const agentPath = join5(agentsDir, agentId);
408
+ return existsSync3(agentPath);
409
+ });
410
+ if (!hasAnyAgent) return false;
411
+ }
412
+ let agentsMdPath;
413
+ if (tool === "opencode" && level === "project") {
414
+ agentsMdPath = join5(configRoot, "..", "AGENTS.md");
415
+ } else {
416
+ agentsMdPath = join5(configRoot, "AGENTS.md");
417
+ }
418
+ if (!existsSync3(agentsMdPath)) return false;
419
+ return true;
577
420
  }
578
421
 
579
422
  // src/utils/logger.ts
@@ -630,24 +473,24 @@ function printBoxTitle(title, width = 65) {
630
473
  }
631
474
  function showOperationHints(isCheckbox = false) {
632
475
  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
476
+ console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m${t("hint_select_space")}\x1B[0m ${t("hint_select_check")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}\uFF08${t("hint_checkbox_suffix")}\uFF09
634
477
  `);
635
478
  } else {
636
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4
479
+ console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}
637
480
  `);
638
481
  }
639
482
  }
640
483
 
641
484
  // src/utils/config.ts
642
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
643
- import { join as join4 } from "path";
485
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
486
+ import { join as join6 } from "path";
644
487
  import { homedir as homedir3 } from "os";
645
488
  import { parse, stringify } from "yaml";
646
489
  function getConfigDir() {
647
- return join4(homedir3(), ".cannbot");
490
+ return join6(homedir3(), ".cannbot");
648
491
  }
649
492
  function getConfigPath() {
650
- return join4(getConfigDir(), "config.yaml");
493
+ return join6(getConfigDir(), "config.yaml");
651
494
  }
652
495
  function readConfig() {
653
496
  const configPath = getConfigPath();
@@ -658,7 +501,7 @@ function readConfig() {
658
501
  };
659
502
  }
660
503
  try {
661
- const content = readFileSync2(configPath, "utf-8");
504
+ const content = readFileSync4(configPath, "utf-8");
662
505
  const parsed = parse(content);
663
506
  return {
664
507
  language: parsed.language || "zh_CN",
@@ -705,16 +548,78 @@ function removeInstalledPlugin(pluginId) {
705
548
  }
706
549
 
707
550
  // src/ui/theme.ts
551
+ import chalk2 from "chalk";
708
552
  var selectTheme = {
709
553
  helpMode: "never"
710
554
  };
555
+ function isCJK(code) {
556
+ 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;
557
+ }
558
+ function isEmoji(code) {
559
+ return code >= 127744 && code <= 129791 || code >= 9728 && code <= 10175;
560
+ }
561
+ function getDisplayWidth2(str) {
562
+ let width = 0;
563
+ for (const ch of str) {
564
+ const code = ch.codePointAt(0);
565
+ if (isCJK(code) || isEmoji(code)) {
566
+ width += 2;
567
+ } else {
568
+ width += 1;
569
+ }
570
+ }
571
+ return width;
572
+ }
573
+ function wrapDescription(text) {
574
+ const prefix = " \u{1F4A1} ";
575
+ const indent = " ";
576
+ const cols = process.stdout.columns || 120;
577
+ const maxLen = Math.max(20, cols - getDisplayWidth2(prefix) - 2);
578
+ const tokens = [];
579
+ let i = 0;
580
+ while (i < text.length) {
581
+ const code = text[i].codePointAt(0);
582
+ if (isCJK(code)) {
583
+ tokens.push({ text: text[i], width: 2 });
584
+ i++;
585
+ } else {
586
+ let j = i;
587
+ while (j < text.length) {
588
+ const c = text[j].codePointAt(0);
589
+ if (isCJK(c)) break;
590
+ j++;
591
+ }
592
+ const word = text.slice(i, j);
593
+ tokens.push({ text: word, width: getDisplayWidth2(word) });
594
+ i = j;
595
+ }
596
+ }
597
+ const lines = [];
598
+ let line = "";
599
+ let lineWidth = 0;
600
+ for (const token of tokens) {
601
+ if (lineWidth + token.width > maxLen && line) {
602
+ lines.push(line.trimEnd());
603
+ line = token.text;
604
+ lineWidth = token.width;
605
+ } else {
606
+ line += token.text;
607
+ lineWidth += token.width;
608
+ }
609
+ }
610
+ if (line) lines.push(line.trimEnd());
611
+ return lines.map((l, i2) => chalk2.cyan(i2 === 0 ? prefix + l : indent + l)).join("\n");
612
+ }
711
613
  var checkboxTheme = {
712
614
  icon: {
713
615
  checked: "\x1B[32m\u2611\x1B[22m",
714
616
  unchecked: "\u2610",
715
617
  cursor: "\u276F"
716
618
  },
717
- helpMode: "never"
619
+ helpMode: "never",
620
+ style: {
621
+ description: (text) => wrapDescription(text)
622
+ }
718
623
  };
719
624
 
720
625
  // src/ui/wizard.ts
@@ -789,7 +694,7 @@ async function runWizard() {
789
694
  step = 2;
790
695
  break;
791
696
  }
792
- if (result === CANCEL || result === false) {
697
+ if (result === CANCEL) {
793
698
  return { language: config.language || "zh_CN", tool: selectedTool, level, plugins: [], confirmed: false };
794
699
  }
795
700
  updateConfig({ lastTool: selectedTool, lastLevel: level });
@@ -799,7 +704,7 @@ async function runWizard() {
799
704
  }
800
705
  }
801
706
  async function stepTool(detectedTools, lastTool) {
802
- printBoxTitle("[1/4] \u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177");
707
+ printBoxTitle(t("wizard_step_1_title"));
803
708
  if (detectedTools.length === 0) {
804
709
  logger.error(t("error_tool_not_found"));
805
710
  const choices2 = [
@@ -874,10 +779,10 @@ async function stepToolManual(lastTool) {
874
779
  return result;
875
780
  }
876
781
  async function stepLevel(lastLevel) {
877
- printBoxTitle("[2/4] \u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E");
782
+ printBoxTitle(t("wizard_step_2_title"));
878
783
  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" },
784
+ { name: `> project \u2014 ${t("install_level_project")}`, value: "project" },
785
+ { name: `> global \u2014 ${t("install_level_global")}`, value: "global" },
881
786
  new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
882
787
  { name: "<- " + t("wizard_back"), value: BACK },
883
788
  { name: "x " + t("wizard_cancel"), value: CANCEL }
@@ -895,7 +800,7 @@ async function stepLevel(lastLevel) {
895
800
  return result;
896
801
  }
897
802
  async function stepPlugins(tool, level) {
898
- printBoxTitle("[3/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6");
803
+ printBoxTitle(t("wizard_step_3_title"));
899
804
  const plugins = getAllPlugins();
900
805
  const configRoot = getConfigRoot(tool, level);
901
806
  const manifests = readAllManifests(configRoot);
@@ -904,25 +809,22 @@ async function stepPlugins(tool, level) {
904
809
  const isInstalled = installedSet.has(p.id);
905
810
  const suffix = isInstalled ? ` [${t("wizard_already_installed")}]` : "";
906
811
  return {
907
- name: `${p.displayName}${suffix} \u2014 ${p.description}`,
812
+ name: `${chalk3.cyan(p.displayName)}${suffix}`,
908
813
  value: p.id,
909
- checked: false
814
+ checked: false,
815
+ description: p.description
910
816
  };
911
817
  });
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
818
  showOperationHints(true);
916
819
  const selected = await checkbox({
917
820
  message: t("wizard_select_plugins"),
918
821
  choices,
919
822
  loop: false,
920
823
  instructions: false,
921
- theme: checkboxTheme
824
+ theme: checkboxTheme,
825
+ pageSize: 15
922
826
  });
923
- if (selected.includes(BACK)) return BACK;
924
- if (selected.includes(CANCEL)) return CANCEL;
925
- const pluginIds = selected.filter((v) => v !== BACK && v !== CANCEL);
827
+ const pluginIds = selected;
926
828
  if (pluginIds.length === 0) {
927
829
  const action = await select({
928
830
  message: t("wizard_no_selection"),
@@ -940,17 +842,17 @@ async function stepPlugins(tool, level) {
940
842
  return pluginIds;
941
843
  }
942
844
  async function stepConfirm(tool, level, plugins) {
943
- printBoxTitle("[4/4] \u786E\u8BA4\u5B89\u88C5");
845
+ printBoxTitle(t("wizard_step_4_title"));
944
846
  const allPlugins = getAllPlugins();
945
847
  const selectedPlugins = plugins.map((id) => allPlugins.find((p) => p.id === id));
946
848
  const displayPath = getConfigRoot(tool, level);
947
849
  const toolName = getToolDisplayName(tool);
948
850
  const levelText = level === "project" ? "project" : "global";
949
- logger.info(`\u5373\u5C06\u5B89\u88C5 ${chalk2.bold(plugins.length)} \u4E2A\u63D2\u4EF6\u5230 ${chalk2.green(toolName)}\uFF08${chalk2.cyan(levelText)} \u7EA7\u522B\uFF09\uFF1A`);
950
- logger.info(`\u5B89\u88C5\u8DEF\u5F84: ${chalk2.cyan(displayPath)}`);
851
+ logger.info(t("wizard_confirm_install_format").replace("{count}", chalk3.bold(String(plugins.length))).replace("{tool}", chalk3.green(toolName)).replace("{level}", chalk3.cyan(levelText)));
852
+ logger.info(`${t("wizard_install_path")}: ${chalk3.cyan(displayPath)}`);
951
853
  for (const plugin of selectedPlugins) {
952
854
  if (plugin) {
953
- logger.step(` \u2022 ${chalk2.bold(plugin.displayName)}`);
855
+ logger.step(` \u2022 ${chalk3.bold(plugin.displayName)}`);
954
856
  }
955
857
  }
956
858
  logger.blank();
@@ -968,14 +870,19 @@ async function stepConfirm(tool, level, plugins) {
968
870
  }
969
871
 
970
872
  // src/core/repository.ts
971
- import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
972
- import { join as join6 } from "path";
873
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
874
+ import { join as join8, dirname as dirname4 } from "path";
875
+ import { fileURLToPath as fileURLToPath4 } from "url";
973
876
  import { execa as execa2 } from "execa";
877
+ import { parse as parseYaml3 } from "yaml";
974
878
 
975
879
  // src/core/scanner.ts
976
- import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
977
- import { join as join5, dirname } from "path";
978
- import { parse as parseYaml } from "yaml";
880
+ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
881
+ import { join as join7, dirname as dirname3 } from "path";
882
+ import { fileURLToPath as fileURLToPath3 } from "url";
883
+ import { parse as parseYaml2 } from "yaml";
884
+ var __filename3 = fileURLToPath3(import.meta.url);
885
+ var __dirname3 = dirname3(__filename3);
979
886
  var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
980
887
  "node_modules",
981
888
  ".git",
@@ -996,36 +903,43 @@ var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
996
903
  "docs",
997
904
  "scripts"
998
905
  ]);
999
- var SKILL_SCAN_DIRS = [
1000
- "ops",
1001
- "model",
1002
- "graph",
1003
- "infra",
1004
- "ops-lab"
1005
- ];
1006
- var PLUGIN_SCAN_DIRS = [
1007
- "plugins-official",
1008
- "plugins-community"
1009
- ];
906
+ function loadScanConfig() {
907
+ const configPath = join7(__dirname3, "config", "repository.yaml");
908
+ try {
909
+ const config = parseYaml2(readFileSync5(configPath, "utf-8"));
910
+ return {
911
+ skillDirs: config.scanDirs || ["ops", "model", "graph", "infra", "ops-lab"],
912
+ pluginDirs: config.pluginDirs || ["plugins-official", "plugins-community"]
913
+ };
914
+ } catch {
915
+ return {
916
+ skillDirs: ["ops", "model", "graph", "infra", "ops-lab"],
917
+ pluginDirs: ["plugins-official", "plugins-community"]
918
+ };
919
+ }
920
+ }
921
+ var scanConfig = loadScanConfig();
922
+ var SKILL_SCAN_DIRS = scanConfig.skillDirs;
923
+ var PLUGIN_SCAN_DIRS = scanConfig.pluginDirs;
1010
924
  function scanSkills(repoPath) {
1011
925
  const skills = [];
1012
926
  const seen = /* @__PURE__ */ new Set();
1013
927
  for (const dir of SKILL_SCAN_DIRS) {
1014
- const fullPath = join5(repoPath, dir);
928
+ const fullPath = join7(repoPath, dir);
1015
929
  if (!existsSync5(fullPath)) continue;
1016
930
  scanDirectory(fullPath, repoPath, dir, skills, seen);
1017
931
  }
1018
932
  for (const pluginDir of PLUGIN_SCAN_DIRS) {
1019
- const pluginParent = join5(repoPath, pluginDir);
933
+ const pluginParent = join7(repoPath, pluginDir);
1020
934
  if (!existsSync5(pluginParent)) continue;
1021
935
  for (const plugin of safeReaddir(pluginParent)) {
1022
- const pluginPath = join5(pluginParent, plugin);
936
+ const pluginPath = join7(pluginParent, plugin);
1023
937
  if (!isDirectory(pluginPath)) continue;
1024
- const skillsDir = join5(pluginPath, "skills");
938
+ const skillsDir = join7(pluginPath, "skills");
1025
939
  if (existsSync5(skillsDir)) {
1026
940
  scanDirectory(skillsDir, repoPath, `${pluginDir}/${plugin}/skills`, skills, seen);
1027
941
  }
1028
- const skillDir = join5(pluginPath, "skill");
942
+ const skillDir = join7(pluginPath, "skill");
1029
943
  if (existsSync5(skillDir)) {
1030
944
  scanDirectory(skillDir, repoPath, `${pluginDir}/${plugin}/skill`, skills, seen);
1031
945
  }
@@ -1035,7 +949,7 @@ function scanSkills(repoPath) {
1035
949
  }
1036
950
  function parseFrontmatter(filePath) {
1037
951
  try {
1038
- const content = readFileSync3(filePath, "utf-8");
952
+ const content = readFileSync5(filePath, "utf-8");
1039
953
  const lines = content.split("\n");
1040
954
  if (lines.length < 3) return null;
1041
955
  if (lines[0].trim() !== "---") return null;
@@ -1048,7 +962,7 @@ function parseFrontmatter(filePath) {
1048
962
  }
1049
963
  if (endIndex === -1) return null;
1050
964
  const yamlBlock = lines.slice(1, endIndex).map((l) => l.replace(/\r$/, "")).join("\n");
1051
- const parsed = parseYaml(yamlBlock);
965
+ const parsed = parseYaml2(yamlBlock);
1052
966
  if (!parsed || typeof parsed !== "object") return null;
1053
967
  if (!parsed.name || typeof parsed.name !== "string") return null;
1054
968
  return {
@@ -1061,17 +975,17 @@ function parseFrontmatter(filePath) {
1061
975
  }
1062
976
  function getCurrentCommit(repoPath) {
1063
977
  try {
1064
- const headFile = join5(repoPath, ".git", "HEAD");
978
+ const headFile = join7(repoPath, ".git", "HEAD");
1065
979
  if (!existsSync5(headFile)) return "unknown";
1066
- const headContent = readFileSync3(headFile, "utf-8").trim();
980
+ const headContent = readFileSync5(headFile, "utf-8").trim();
1067
981
  if (headContent.startsWith("ref: ")) {
1068
- const refPath = join5(repoPath, ".git", headContent.slice(5));
982
+ const refPath = join7(repoPath, ".git", headContent.slice(5));
1069
983
  if (existsSync5(refPath)) {
1070
- return readFileSync3(refPath, "utf-8").trim();
984
+ return readFileSync5(refPath, "utf-8").trim();
1071
985
  }
1072
- const packedRefsPath = join5(repoPath, ".git", "packed-refs");
986
+ const packedRefsPath = join7(repoPath, ".git", "packed-refs");
1073
987
  if (existsSync5(packedRefsPath)) {
1074
- const packedRefs = readFileSync3(packedRefsPath, "utf-8");
988
+ const packedRefs = readFileSync5(packedRefsPath, "utf-8");
1075
989
  const refName = headContent.slice(5);
1076
990
  const lines = packedRefs.split("\n");
1077
991
  for (const line of lines) {
@@ -1090,7 +1004,7 @@ function readScanCache() {
1090
1004
  try {
1091
1005
  const cachePath = getCachePath();
1092
1006
  if (!existsSync5(cachePath)) return null;
1093
- const content = readFileSync3(cachePath, "utf-8");
1007
+ const content = readFileSync5(cachePath, "utf-8");
1094
1008
  const cache = JSON.parse(content);
1095
1009
  if (!cache.skills || !cache.timestamp) return null;
1096
1010
  const age = Date.now() - cache.timestamp;
@@ -1103,7 +1017,7 @@ function readScanCache() {
1103
1017
  function writeScanCache(cache) {
1104
1018
  try {
1105
1019
  const cachePath = getCachePath();
1106
- const cacheDir = dirname(cachePath);
1020
+ const cacheDir = dirname3(cachePath);
1107
1021
  if (!existsSync5(cacheDir)) {
1108
1022
  mkdirSync2(cacheDir, { recursive: true });
1109
1023
  }
@@ -1112,29 +1026,14 @@ function writeScanCache(cache) {
1112
1026
  }
1113
1027
  }
1114
1028
  function getCachePath() {
1115
- const homeDir = process.env.HOME || process.env.USERPROFILE || "/tmp";
1116
- return join5(homeDir, ".cannbot", "scan-cache.json");
1029
+ return join7(getCannbotConfigDir(), "scan-cache.json");
1117
1030
  }
1118
1031
  function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1119
- const selfSkillMd = join5(dirPath, "SKILL.md");
1120
- if (existsSync5(selfSkillMd)) {
1121
- const frontmatter = parseFrontmatter(selfSkillMd);
1122
- if (frontmatter && !seen.has(frontmatter.name)) {
1123
- seen.add(frontmatter.name);
1124
- skills.push({
1125
- id: frontmatter.name,
1126
- description: frontmatter.description,
1127
- source: sourcePrefix,
1128
- filePath: selfSkillMd
1129
- });
1130
- }
1131
- return;
1132
- }
1133
1032
  for (const entry of safeReaddir(dirPath)) {
1134
- const fullPath = join5(dirPath, entry);
1033
+ const fullPath = join7(dirPath, entry);
1135
1034
  if (EXCLUDE_DIRS.has(entry)) continue;
1136
1035
  if (!isDirectory(fullPath)) continue;
1137
- const skillMd = join5(fullPath, "SKILL.md");
1036
+ const skillMd = join7(fullPath, "SKILL.md");
1138
1037
  if (existsSync5(skillMd)) {
1139
1038
  const frontmatter = parseFrontmatter(skillMd);
1140
1039
  if (frontmatter && !seen.has(frontmatter.name)) {
@@ -1148,12 +1047,12 @@ function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1148
1047
  }
1149
1048
  continue;
1150
1049
  }
1151
- scanDirectory(fullPath, repoPath, sourcePrefix, skills, seen);
1050
+ scanDirectory(fullPath, repoPath, sourcePrefix + "/" + entry, skills, seen);
1152
1051
  }
1153
1052
  }
1154
1053
  function safeReaddir(dir) {
1155
1054
  try {
1156
- return readdirSync(dir);
1055
+ return readdirSync2(dir);
1157
1056
  } catch {
1158
1057
  return [];
1159
1058
  }
@@ -1393,7 +1292,21 @@ function getAllCategories() {
1393
1292
  }
1394
1293
 
1395
1294
  // src/core/repository.ts
1396
- var REPO_URL = "https://gitcode.com/cann/cannbot-skills.git";
1295
+ var __filename4 = fileURLToPath4(import.meta.url);
1296
+ var __dirname4 = dirname4(__filename4);
1297
+ function loadRepoUrl() {
1298
+ if (process.env.CANNBOT_REPO_URL) {
1299
+ return process.env.CANNBOT_REPO_URL;
1300
+ }
1301
+ const configPath = join8(__dirname4, "config", "repository.yaml");
1302
+ try {
1303
+ const config = parseYaml3(readFileSync6(configPath, "utf-8"));
1304
+ return config.repository?.url || "https://gitcode.com/cann/cannbot-skills.git";
1305
+ } catch {
1306
+ return "https://gitcode.com/cann/cannbot-skills.git";
1307
+ }
1308
+ }
1309
+ var REPO_URL = loadRepoUrl();
1397
1310
  var RepositoryManager = class {
1398
1311
  repoPath;
1399
1312
  constructor(customPath) {
@@ -1451,8 +1364,8 @@ var RepositoryManager = class {
1451
1364
  }
1452
1365
  isValidRepo(path) {
1453
1366
  if (!existsSync6(path)) return false;
1454
- const gitDir = join6(path, ".git");
1455
- const pluginsDir = join6(path, "plugins-official");
1367
+ const gitDir = join8(path, ".git");
1368
+ const pluginsDir = join8(path, "plugins-official");
1456
1369
  return existsSync6(gitDir) && existsSync6(pluginsDir);
1457
1370
  }
1458
1371
  async cloneRepo() {
@@ -1487,18 +1400,18 @@ function createRepositoryManager(customPath) {
1487
1400
  }
1488
1401
 
1489
1402
  // src/core/installer.ts
1490
- import { existsSync as existsSync8 } from "fs";
1491
- import { join as join8 } from "path";
1403
+ import { existsSync as existsSync9 } from "fs";
1404
+ import { join as join11 } from "path";
1492
1405
  import { execa as execa3 } from "execa";
1493
1406
 
1494
1407
  // src/core/record.ts
1495
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, lstatSync, unlinkSync } from "fs";
1496
- import { join as join7 } from "path";
1408
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4, lstatSync, unlinkSync } from "fs";
1409
+ import { join as join9 } from "path";
1497
1410
  function getInstallsDir() {
1498
- return join7(getCannbotConfigDir(), "installs");
1411
+ return join9(getCannbotConfigDir(), "installs");
1499
1412
  }
1500
1413
  function getRecordPath(pluginId) {
1501
- return join7(getInstallsDir(), `${pluginId}.json`);
1414
+ return join9(getInstallsDir(), `${pluginId}.json`);
1502
1415
  }
1503
1416
  function readRecord(pluginId) {
1504
1417
  const recordPath = getRecordPath(pluginId);
@@ -1506,7 +1419,7 @@ function readRecord(pluginId) {
1506
1419
  return null;
1507
1420
  }
1508
1421
  try {
1509
- const content = readFileSync4(recordPath, "utf-8");
1422
+ const content = readFileSync7(recordPath, "utf-8");
1510
1423
  return JSON.parse(content);
1511
1424
  } catch {
1512
1425
  return null;
@@ -1530,22 +1443,22 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
1530
1443
  const files = [];
1531
1444
  const directories = [];
1532
1445
  if (manifest) {
1533
- const skillsDir = join7(configRoot, "skills");
1446
+ const skillsDir = join9(configRoot, "skills");
1534
1447
  if (existsSync7(skillsDir)) {
1535
1448
  directories.push(skillsDir);
1536
1449
  for (const skillName of manifest.installed_skills || []) {
1537
- const skillPath = join7(skillsDir, skillName);
1450
+ const skillPath = join9(skillsDir, skillName);
1538
1451
  if (existsSync7(skillPath) || isSymlink(skillPath)) {
1539
1452
  files.push(skillPath);
1540
1453
  }
1541
1454
  }
1542
1455
  }
1543
- const agentsDir = join7(configRoot, "agents");
1456
+ const agentsDir = join9(configRoot, "agents");
1544
1457
  if (existsSync7(agentsDir)) {
1545
1458
  directories.push(agentsDir);
1546
1459
  for (const agentName of manifest.installed_agents || []) {
1547
- const agentPath = join7(agentsDir, agentName);
1548
- const agentPathMd = join7(agentsDir, agentName + ".md");
1460
+ const agentPath = join9(agentsDir, agentName);
1461
+ const agentPathMd = join9(agentsDir, agentName + ".md");
1549
1462
  if (existsSync7(agentPath) || isSymlink(agentPath)) {
1550
1463
  files.push(agentPath);
1551
1464
  } else if (existsSync7(agentPathMd) || isSymlink(agentPathMd)) {
@@ -1554,35 +1467,34 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
1554
1467
  }
1555
1468
  }
1556
1469
  }
1557
- const workflowsLink = join7(configRoot, "workflows");
1470
+ const workflowsLink = join9(configRoot, "workflows");
1558
1471
  if (isSymlink(workflowsLink)) {
1559
1472
  files.push(workflowsLink);
1560
1473
  }
1561
- const manifestPath = join7(configRoot, "cannbot-manifest.json");
1474
+ const manifestPath = join9(configRoot, "cannbot-manifest.json");
1562
1475
  if (existsSync7(manifestPath)) {
1563
1476
  files.push(manifestPath);
1564
1477
  }
1565
- const pluginManifestPath = join7(configRoot, `${pluginId}-manifest.json`);
1478
+ const pluginManifestPath = join9(configRoot, `${pluginId}-manifest.json`);
1566
1479
  if (existsSync7(pluginManifestPath)) {
1567
1480
  files.push(pluginManifestPath);
1568
1481
  }
1569
1482
  const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1570
- const configFilePath = level === "project" ? join7(installPath, configFileName) : join7(configRoot, configFileName);
1483
+ const configFilePath = level === "project" ? join9(installPath, configFileName) : join9(configRoot, configFileName);
1571
1484
  if (existsSync7(configFilePath)) {
1572
1485
  files.push(configFilePath);
1573
1486
  }
1574
1487
  const repoLinks = ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
1575
1488
  for (const repoName of repoLinks) {
1576
- const repoLinkPath = join7(installPath, repoName);
1489
+ const repoLinkPath = join9(installPath, repoName);
1577
1490
  if (isSymlink(repoLinkPath)) {
1578
1491
  files.push(repoLinkPath);
1579
1492
  }
1580
- const repoLinkInConfig = join7(configRoot, repoName);
1493
+ const repoLinkInConfig = join9(configRoot, repoName);
1581
1494
  if (isSymlink(repoLinkInConfig)) {
1582
1495
  files.push(repoLinkInConfig);
1583
1496
  }
1584
1497
  }
1585
- directories.push(configRoot);
1586
1498
  return {
1587
1499
  pluginId,
1588
1500
  displayName,
@@ -1603,7 +1515,7 @@ function isSymlink(path) {
1603
1515
  }
1604
1516
  }
1605
1517
  function getSkillRecordPath() {
1606
- return join7(getInstallsDir(), "skills.json");
1518
+ return join9(getInstallsDir(), "skills.json");
1607
1519
  }
1608
1520
  function readSkillRecord() {
1609
1521
  const recordPath = getSkillRecordPath();
@@ -1611,7 +1523,7 @@ function readSkillRecord() {
1611
1523
  return {};
1612
1524
  }
1613
1525
  try {
1614
- const content = readFileSync4(recordPath, "utf-8");
1526
+ const content = readFileSync7(recordPath, "utf-8");
1615
1527
  return JSON.parse(content);
1616
1528
  } catch {
1617
1529
  return {};
@@ -1659,7 +1571,186 @@ function removeSkillsFromRecord(skillIds, tool, level, installPath) {
1659
1571
  }
1660
1572
  function getInstalledSkills(tool, level, installPath) {
1661
1573
  const record = readSkillRecord();
1662
- return record[tool]?.[level]?.[installPath]?.skills || [];
1574
+ const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
1575
+ const configRoot = getConfigRoot(tool, level);
1576
+ const skillsDir = join9(configRoot, "skills");
1577
+ return recordedSkills.filter((skillId) => {
1578
+ const skillPath = join9(skillsDir, skillId);
1579
+ return existsSync7(skillPath) || isSymlink(skillPath);
1580
+ });
1581
+ }
1582
+
1583
+ // src/core/backup.ts
1584
+ import { existsSync as existsSync8, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync2 } from "fs";
1585
+ import { join as join10 } from "path";
1586
+ function getAgentsFileName(tool) {
1587
+ return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1588
+ }
1589
+ function detectCurrentPlugin(configRoot, tool) {
1590
+ const manifest = readManifest(configRoot);
1591
+ if (manifest?.team) {
1592
+ const plugin = findPlugin(manifest.team);
1593
+ if (plugin) {
1594
+ return {
1595
+ pluginId: plugin.id,
1596
+ pluginName: plugin.displayName
1597
+ };
1598
+ }
1599
+ }
1600
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
1601
+ if (!existsSync8(agentsFile)) {
1602
+ return null;
1603
+ }
1604
+ const allRecords = readAllRecords();
1605
+ for (const record of allRecords) {
1606
+ if (record.configRoot === configRoot && record.tool === tool) {
1607
+ const plugin = findPlugin(record.pluginId);
1608
+ if (plugin) {
1609
+ return {
1610
+ pluginId: plugin.id,
1611
+ pluginName: plugin.displayName
1612
+ };
1613
+ }
1614
+ }
1615
+ }
1616
+ return null;
1617
+ }
1618
+ function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
1619
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
1620
+ if (!existsSync8(agentsFile)) {
1621
+ return null;
1622
+ }
1623
+ const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
1624
+ const backupFileName = `${getAgentsFileName(tool)}.cannbot-backup.${fromPluginId}.${timestamp}`;
1625
+ const backupPath = join10(configRoot, backupFileName);
1626
+ try {
1627
+ copyFileSync(agentsFile, backupPath);
1628
+ return {
1629
+ filePath: backupPath,
1630
+ pluginId: fromPluginId,
1631
+ pluginName: fromPluginName,
1632
+ backupTime: timestamp
1633
+ };
1634
+ } catch {
1635
+ return null;
1636
+ }
1637
+ }
1638
+ function findBackups(configRoot) {
1639
+ if (!existsSync8(configRoot)) {
1640
+ return [];
1641
+ }
1642
+ const files = readdirSync3(configRoot);
1643
+ const backups = [];
1644
+ for (const file of files) {
1645
+ if (file.includes(".cannbot-backup.")) {
1646
+ const parts = file.split(".cannbot-backup.");
1647
+ if (parts.length === 2) {
1648
+ const pluginIdAndTime = parts[1];
1649
+ const lastDotIndex = pluginIdAndTime.lastIndexOf(".");
1650
+ if (lastDotIndex > 0) {
1651
+ const pluginId = pluginIdAndTime.substring(0, lastDotIndex);
1652
+ const timestamp = pluginIdAndTime.substring(lastDotIndex + 1);
1653
+ const plugin = findPlugin(pluginId);
1654
+ backups.push({
1655
+ filePath: join10(configRoot, file),
1656
+ pluginId,
1657
+ pluginName: plugin?.displayName || pluginId,
1658
+ backupTime: timestamp
1659
+ });
1660
+ }
1661
+ }
1662
+ }
1663
+ }
1664
+ return backups.sort((a, b) => b.backupTime.localeCompare(a.backupTime));
1665
+ }
1666
+ function restoreBackup(backupPath, configRoot, tool) {
1667
+ try {
1668
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
1669
+ copyFileSync(backupPath, agentsFile);
1670
+ return true;
1671
+ } catch {
1672
+ return false;
1673
+ }
1674
+ }
1675
+ function deleteBackup(backupPath) {
1676
+ try {
1677
+ if (existsSync8(backupPath)) {
1678
+ unlinkSync2(backupPath);
1679
+ return true;
1680
+ }
1681
+ return false;
1682
+ } catch {
1683
+ return false;
1684
+ }
1685
+ }
1686
+ function formatTimestamp(date) {
1687
+ const year = date.getFullYear();
1688
+ const month = String(date.getMonth() + 1).padStart(2, "0");
1689
+ const day = String(date.getDate()).padStart(2, "0");
1690
+ const hours = String(date.getHours()).padStart(2, "0");
1691
+ const minutes = String(date.getMinutes()).padStart(2, "0");
1692
+ const seconds = String(date.getSeconds()).padStart(2, "0");
1693
+ return `${year}${month}${day}-${hours}${minutes}${seconds}`;
1694
+ }
1695
+ function readAllRecords() {
1696
+ const records = [];
1697
+ const plugins = [
1698
+ "ops-direct-invoke",
1699
+ "ops-direct-invoke-flash",
1700
+ "ops-registry-invoke",
1701
+ "pypto-op-orchestrator",
1702
+ "triton-op-generator",
1703
+ "tilelang-op-orchestrator",
1704
+ "model-infer-optimize",
1705
+ "catlass-op-generator",
1706
+ "ops-code-reviewer",
1707
+ "torch-compile"
1708
+ ];
1709
+ for (const pluginId of plugins) {
1710
+ const record = readRecord(pluginId);
1711
+ if (record) {
1712
+ records.push(record);
1713
+ }
1714
+ }
1715
+ return records;
1716
+ }
1717
+
1718
+ // src/ui/backup-prompts.ts
1719
+ import { select as select2 } from "@inquirer/prompts";
1720
+ import chalk4 from "chalk";
1721
+ async function showOverwriteWarning(currentPluginName, newPluginName) {
1722
+ console.log();
1723
+ console.log(chalk4.yellow(` \u26A0\uFE0F ${t("backup_detected")}`));
1724
+ console.log();
1725
+ console.log(` ${t("backup_current")}: ${chalk4.cyan(currentPluginName)}`);
1726
+ console.log(` ${t("backup_installing")}: ${chalk4.cyan(newPluginName)}`);
1727
+ console.log();
1728
+ console.log(` ${t("backup_overwrite_warning")}`);
1729
+ console.log(` ${t("backup_auto_create")}`);
1730
+ console.log();
1731
+ const choice = await select2({
1732
+ message: t("backup_select_action"),
1733
+ choices: [
1734
+ { name: t("backup_overwrite"), value: "overwrite" },
1735
+ { name: t("backup_cancel"), value: "cancel" }
1736
+ ]
1737
+ });
1738
+ return choice;
1739
+ }
1740
+ async function showRestorePrompt(backups) {
1741
+ console.log();
1742
+ console.log(chalk4.cyan(` ${t("backup_restore_prompt")}`));
1743
+ console.log();
1744
+ const choices = backups.map((b) => ({
1745
+ name: `${t("backup_restore")} ${b.pluginName} (${b.backupTime})`,
1746
+ value: b.filePath
1747
+ }));
1748
+ choices.push({ name: t("backup_only_uninstall"), value: "none" });
1749
+ const choice = await select2({
1750
+ message: t("backup_select_action"),
1751
+ choices
1752
+ });
1753
+ return choice;
1663
1754
  }
1664
1755
 
1665
1756
  // src/core/installer.ts
@@ -1675,8 +1766,8 @@ async function installPlugin(opts) {
1675
1766
  warnings: []
1676
1767
  };
1677
1768
  }
1678
- const scriptPath = join8(opts.repoPath, plugin.dir, plugin.script);
1679
- if (!existsSync8(scriptPath)) {
1769
+ const scriptPath = join11(opts.repoPath, plugin.dir, plugin.script);
1770
+ if (!existsSync9(scriptPath)) {
1680
1771
  return {
1681
1772
  success: false,
1682
1773
  pluginId: opts.pluginId,
@@ -1691,13 +1782,49 @@ async function installPlugin(opts) {
1691
1782
  args.push(opts.installPath);
1692
1783
  }
1693
1784
  const cwd = opts.installPath || process.cwd();
1785
+ const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
1786
+ const agentsFile = join11(configRoot, getAgentsFileName(opts.tool));
1787
+ let backupInfo = null;
1788
+ if (existsSync9(agentsFile)) {
1789
+ const currentPlugin = detectCurrentPlugin(configRoot, opts.tool);
1790
+ if (currentPlugin && currentPlugin.pluginId !== opts.pluginId) {
1791
+ let choice = "overwrite";
1792
+ if (!opts.yes) {
1793
+ choice = await showOverwriteWarning(
1794
+ currentPlugin.pluginName,
1795
+ plugin.displayName
1796
+ );
1797
+ }
1798
+ if (choice === "cancel") {
1799
+ logger.info(t("backup_cancel"));
1800
+ return {
1801
+ success: false,
1802
+ pluginId: opts.pluginId,
1803
+ skillsCount: 0,
1804
+ agentsCount: 0,
1805
+ errors: [t("backup_cancel")],
1806
+ warnings: []
1807
+ };
1808
+ }
1809
+ backupInfo = createBackup(
1810
+ configRoot,
1811
+ opts.tool,
1812
+ currentPlugin.pluginId,
1813
+ currentPlugin.pluginName
1814
+ );
1815
+ if (backupInfo) {
1816
+ logger.success(`${t("backup_created")}: ${backupInfo.filePath}`);
1817
+ }
1818
+ }
1819
+ }
1694
1820
  try {
1695
- await execa3("bash", [scriptPath, ...args], {
1821
+ const execOptions = {
1696
1822
  cwd,
1697
1823
  timeout: 3e5,
1698
- stdio: "pipe"
1699
- });
1700
- const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
1824
+ stdio: "pipe",
1825
+ input: opts.yes ? "y\n" : void 0
1826
+ };
1827
+ await execa3("bash", [scriptPath, ...args], execOptions);
1701
1828
  const manifest = readManifest(configRoot);
1702
1829
  let skillsCount = 0;
1703
1830
  let agentsCount = 0;
@@ -1718,6 +1845,14 @@ async function installPlugin(opts) {
1718
1845
  configRoot,
1719
1846
  manifest
1720
1847
  );
1848
+ if (backupInfo) {
1849
+ record.backup = {
1850
+ filePath: backupInfo.filePath,
1851
+ fromPluginId: backupInfo.pluginId,
1852
+ fromPluginName: backupInfo.pluginName,
1853
+ backupTime: backupInfo.backupTime
1854
+ };
1855
+ }
1721
1856
  writeRecord(record);
1722
1857
  } catch {
1723
1858
  }
@@ -1743,16 +1878,16 @@ async function installPlugin(opts) {
1743
1878
  }
1744
1879
 
1745
1880
  // src/core/skill-installer.ts
1746
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync2, readdirSync as readdirSync3, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
1747
- import { join as join9 } from "path";
1748
- import { select as select2, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
1749
- import chalk3 from "chalk";
1881
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync3, readdirSync as readdirSync4, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
1882
+ import { join as join12 } from "path";
1883
+ import { select as select3, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
1884
+ import chalk5 from "chalk";
1750
1885
  import Table from "cli-table3";
1751
1886
  async function installSkills(skillIds, tool, level, repoPath) {
1752
1887
  const configRoot = getConfigRoot(tool, level);
1753
- const skillsDir = join9(configRoot, "skills");
1888
+ const skillsDir = join12(configRoot, "skills");
1754
1889
  const installPath = level === "project" ? process.cwd() : configRoot;
1755
- if (!existsSync9(skillsDir)) {
1890
+ if (!existsSync10(skillsDir)) {
1756
1891
  mkdirSync5(skillsDir, { recursive: true });
1757
1892
  }
1758
1893
  const results = [];
@@ -1763,15 +1898,15 @@ async function installSkills(skillIds, tool, level, repoPath) {
1763
1898
  results.push({ skillId, success: false, error: "Skill not found in registry" });
1764
1899
  continue;
1765
1900
  }
1766
- const sourcePath = join9(repoPath, skill.source, skill.id);
1767
- const targetPath = join9(skillsDir, skillId);
1768
- if (!existsSync9(sourcePath)) {
1901
+ const sourcePath = join12(repoPath, skill.source, skill.id);
1902
+ const targetPath = join12(skillsDir, skillId);
1903
+ if (!existsSync10(sourcePath)) {
1769
1904
  results.push({ skillId, success: false, error: `Source not found: ${sourcePath}` });
1770
1905
  continue;
1771
1906
  }
1772
1907
  try {
1773
- if (existsSync9(targetPath) || isSymlink2(targetPath)) {
1774
- unlinkSync2(targetPath);
1908
+ if (existsSync10(targetPath) || isSymlink2(targetPath)) {
1909
+ unlinkSync3(targetPath);
1775
1910
  }
1776
1911
  symlinkSync(realpathSync(sourcePath), targetPath);
1777
1912
  results.push({ skillId, success: true });
@@ -1787,21 +1922,21 @@ async function installSkills(skillIds, tool, level, repoPath) {
1787
1922
  }
1788
1923
  async function uninstallSkills(skillIds, tool, level) {
1789
1924
  const configRoot = getConfigRoot(tool, level);
1790
- const skillsDir = join9(configRoot, "skills");
1925
+ const skillsDir = join12(configRoot, "skills");
1791
1926
  const installPath = level === "project" ? process.cwd() : configRoot;
1792
1927
  const results = [];
1793
1928
  const removedIds = [];
1794
1929
  for (const skillId of skillIds) {
1795
- const targetPath = join9(skillsDir, skillId);
1930
+ const targetPath = join12(skillsDir, skillId);
1796
1931
  try {
1797
- if (existsSync9(targetPath) || isSymlink2(targetPath)) {
1798
- unlinkSync2(targetPath);
1932
+ if (existsSync10(targetPath) || isSymlink2(targetPath)) {
1933
+ unlinkSync3(targetPath);
1799
1934
  results.push({ skillId, success: true });
1800
1935
  removedIds.push(skillId);
1801
- logger.step(` \u79FB\u9664: ${skillId}`);
1936
+ logger.step(` ${t("uninstall_remove_label")}: ${skillId}`);
1802
1937
  } else {
1803
1938
  results.push({ skillId, success: false, error: "Not installed" });
1804
- logger.warn(`${skillId} \u672A\u5B89\u88C5`);
1939
+ logger.warn(`${skillId} ${t("uninstall_skill_not_installed")}`);
1805
1940
  }
1806
1941
  } catch (error) {
1807
1942
  results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
@@ -1810,103 +1945,121 @@ async function uninstallSkills(skillIds, tool, level) {
1810
1945
  if (removedIds.length > 0) {
1811
1946
  removeSkillsFromRecord(removedIds, tool, level, installPath);
1812
1947
  }
1813
- if (existsSync9(skillsDir)) {
1948
+ if (existsSync10(skillsDir)) {
1814
1949
  try {
1815
- const entries = readdirSync3(skillsDir);
1950
+ const entries = readdirSync4(skillsDir);
1816
1951
  if (entries.length === 0) {
1817
1952
  rmdirSync(skillsDir);
1818
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: skills/`);
1819
- }
1820
- } catch {
1821
- }
1822
- }
1823
- if (existsSync9(configRoot)) {
1824
- try {
1825
- const entries = readdirSync3(configRoot);
1826
- if (entries.length === 0) {
1827
- rmdirSync(configRoot);
1828
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${configRoot.split("/").pop()}/`);
1953
+ logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
1829
1954
  }
1830
1955
  } catch {
1831
1956
  }
1832
1957
  }
1833
1958
  return results;
1834
1959
  }
1835
- async function interactiveSkillSelect() {
1960
+ var SELECT_ALL = "__select_all__";
1961
+ var SELECT_ALL_CATEGORY = "__select_all_category__";
1962
+ var CONTINUE_SELECT = "__continue_select__";
1963
+ async function interactiveSkillSelect(tool, level) {
1836
1964
  const BACK3 = "__back__";
1837
1965
  const CANCEL3 = "__cancel__";
1838
1966
  const categories = getAllCategories();
1967
+ const allSkillIds = getAllSkills().map((s) => s.id);
1968
+ let accumulated = [];
1839
1969
  let step = 0;
1840
1970
  let selectedCategoryId = "";
1841
1971
  while (true) {
1842
1972
  switch (step) {
1843
1973
  case 0: {
1844
- printBoxTitle("[3/4] \u9009\u62E9 Skill \u7C7B\u522B");
1974
+ 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");
1975
+ printBoxTitle(titleSuffix);
1845
1976
  const categoryChoices = [
1977
+ {
1978
+ name: `>> ${t("skill_select_all").replace("{count}", String(allSkillIds.length))}`,
1979
+ value: SELECT_ALL
1980
+ },
1981
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1846
1982
  ...categories.map((cat) => ({
1847
1983
  name: `> ${cat.name} (${cat.skills.length} skills)`,
1848
1984
  value: cat.id
1849
1985
  })),
1850
1986
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1987
+ ...accumulated.length > 0 ? [{ name: `>> ${t("skill_confirm_install_count").replace("{count}", String(accumulated.length))}`, value: "install" }] : [],
1851
1988
  { name: "<- " + t("wizard_back"), value: BACK3 },
1852
1989
  { name: "x " + t("wizard_cancel"), value: CANCEL3 }
1853
1990
  ];
1854
1991
  showOperationHints();
1855
- selectedCategoryId = await select2({
1992
+ selectedCategoryId = await select3({
1856
1993
  message: t("skill_select_category"),
1857
1994
  choices: categoryChoices,
1858
1995
  loop: false,
1859
- theme: selectTheme
1996
+ theme: selectTheme,
1997
+ pageSize: 15
1860
1998
  });
1861
1999
  if (selectedCategoryId === BACK3) return "back";
1862
2000
  if (selectedCategoryId === CANCEL3) return "cancel";
2001
+ if (selectedCategoryId === SELECT_ALL) {
2002
+ return allSkillIds;
2003
+ }
2004
+ if (selectedCategoryId === "install") {
2005
+ return accumulated;
2006
+ }
1863
2007
  step = 1;
1864
2008
  break;
1865
2009
  }
1866
2010
  case 1: {
1867
2011
  const category = categories.find((c) => c.id === selectedCategoryId);
1868
2012
  if (!category) return [];
1869
- printBoxTitle(`[4/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills \u2014 ${category.name}`);
1870
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
1871
- const levels = ["project", "global"];
1872
- const installedSkills = [];
1873
- for (const tk of tools) {
1874
- for (const l of levels) {
1875
- const path = l === "project" ? process.cwd() : getConfigRoot(tk, l);
1876
- installedSkills.push(...getInstalledSkills(tk, l, path));
1877
- }
1878
- }
2013
+ printBoxTitle(t("wizard_step_4_skill_title").replace("{category}", category.name));
2014
+ const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
2015
+ const installedSkills = getInstalledSkills(tool, level, installPath);
1879
2016
  const installedSet = new Set(installedSkills);
1880
- const skillChoices = category.skills.map((skill) => {
1881
- const isInstalled = installedSet.has(skill.id);
1882
- const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : "";
1883
- return {
1884
- name: `${skill.id}${suffix} \u2014 ${skill.description}`,
1885
- value: skill.id,
1886
- checked: false
1887
- };
1888
- });
1889
- skillChoices.push(new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1890
- skillChoices.push({ name: "<- " + t("wizard_back"), value: BACK3, checked: false });
1891
- skillChoices.push({ name: "x " + t("wizard_cancel"), value: CANCEL3, checked: false });
2017
+ const categorySkillIds = new Set(category.skills.map((s) => s.id));
2018
+ const accumulatedInCategory = new Set(accumulated.filter((id) => categorySkillIds.has(id)));
2019
+ const skillChoices = [
2020
+ {
2021
+ name: `>> ${t("skill_select_all_category")}`,
2022
+ value: SELECT_ALL_CATEGORY,
2023
+ checked: accumulatedInCategory.size === category.skills.length,
2024
+ description: t("skill_select_all_category")
2025
+ },
2026
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2027
+ ...category.skills.map((skill) => {
2028
+ const isInstalled = installedSet.has(skill.id);
2029
+ const isAccumulated = accumulatedInCategory.has(skill.id);
2030
+ const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : isAccumulated ? " [+]" : "";
2031
+ return {
2032
+ name: `${chalk5.cyan(skill.id)}${suffix}`,
2033
+ value: skill.id,
2034
+ checked: isAccumulated,
2035
+ description: skill.description
2036
+ };
2037
+ })
2038
+ ];
1892
2039
  showOperationHints(true);
1893
2040
  const selectedSkills = await checkbox2({
1894
2041
  message: t("skill_select_items"),
1895
2042
  choices: skillChoices,
1896
2043
  loop: false,
1897
2044
  instructions: false,
1898
- theme: checkboxTheme
2045
+ theme: checkboxTheme,
2046
+ pageSize: 15
1899
2047
  });
1900
- if (selectedSkills.includes(BACK3)) {
1901
- step = 0;
1902
- break;
2048
+ let skillIds;
2049
+ if (selectedSkills.includes(SELECT_ALL_CATEGORY)) {
2050
+ skillIds = category.skills.map((s) => s.id);
2051
+ } else {
2052
+ skillIds = selectedSkills.filter((id) => id !== SELECT_ALL_CATEGORY);
1903
2053
  }
1904
- if (selectedSkills.includes(CANCEL3)) return "cancel";
1905
- const skillIds = selectedSkills.filter((v) => v !== BACK3 && v !== CANCEL3);
1906
2054
  if (skillIds.length === 0) {
1907
- const action = await select2({
1908
- message: t("wizard_no_selection"),
1909
- choices: [
2055
+ accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
2056
+ const action2 = await select3({
2057
+ message: accumulated.length > 0 ? t("skill_continue_or_install").replace("{count}", String(accumulated.length)) : t("wizard_no_selection"),
2058
+ choices: accumulated.length > 0 ? [
2059
+ { name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
2060
+ { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
2061
+ { name: "x " + t("wizard_cancel"), value: "cancel" }
2062
+ ] : [
1910
2063
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1911
2064
  { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
1912
2065
  { name: "x " + t("wizard_cancel"), value: "cancel" }
@@ -1914,13 +2067,38 @@ async function interactiveSkillSelect() {
1914
2067
  loop: false,
1915
2068
  theme: selectTheme
1916
2069
  });
1917
- if (action === "back") {
2070
+ if (action2 === "install") return accumulated;
2071
+ if (action2 === "back") {
1918
2072
  step = 0;
1919
2073
  break;
1920
2074
  }
1921
2075
  return "cancel";
1922
2076
  }
1923
- return skillIds;
2077
+ accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
2078
+ for (const id of skillIds) {
2079
+ if (!accumulated.includes(id)) {
2080
+ accumulated.push(id);
2081
+ }
2082
+ }
2083
+ const action = await select3({
2084
+ message: t("skill_continue_or_install").replace("{count}", String(accumulated.length)),
2085
+ choices: [
2086
+ { name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
2087
+ { name: "> " + t("skill_continue_select"), value: CONTINUE_SELECT },
2088
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2089
+ { name: "x " + t("wizard_cancel"), value: "cancel" }
2090
+ ],
2091
+ loop: false,
2092
+ theme: selectTheme
2093
+ });
2094
+ if (action === "install") {
2095
+ return accumulated;
2096
+ }
2097
+ if (action === CONTINUE_SELECT) {
2098
+ step = 0;
2099
+ break;
2100
+ }
2101
+ return "cancel";
1924
2102
  }
1925
2103
  }
1926
2104
  }
@@ -1928,11 +2106,11 @@ async function interactiveSkillSelect() {
1928
2106
  function listAllSkills() {
1929
2107
  const categories = getAllCategories();
1930
2108
  console.log();
1931
- console.log(chalk3.bold(` ${t("skill_list_title")}`));
1932
- console.log(chalk3.dim(" " + "\u2500".repeat(60)));
2109
+ console.log(chalk5.bold(` ${t("skill_list_title")}`));
2110
+ console.log(chalk5.dim(" " + "\u2500".repeat(60)));
1933
2111
  for (const category of categories) {
1934
2112
  console.log();
1935
- console.log(chalk3.bold(` ${category.name}`) + chalk3.dim(` (${category.skills.length} skills)`));
2113
+ console.log(chalk5.bold(` ${category.name}`) + chalk5.dim(` (${category.skills.length} skills)`));
1936
2114
  const table = new Table({
1937
2115
  style: { head: [], border: [] },
1938
2116
  colWidths: [35, 45],
@@ -1940,14 +2118,14 @@ function listAllSkills() {
1940
2118
  });
1941
2119
  for (const skill of category.skills) {
1942
2120
  table.push([
1943
- chalk3.cyan(skill.id),
2121
+ chalk5.cyan(skill.id),
1944
2122
  skill.description
1945
2123
  ]);
1946
2124
  }
1947
2125
  console.log(table.toString());
1948
2126
  }
1949
2127
  console.log();
1950
- console.log(chalk3.dim(` \u5171 ${getAllSkills().length} \u4E2A Skills`));
2128
+ console.log(chalk5.dim(` ${t("skill_total_count").replace("{count}", String(getAllSkills().length))}`));
1951
2129
  console.log();
1952
2130
  }
1953
2131
  function isSymlink2(path) {
@@ -1959,22 +2137,22 @@ function isSymlink2(path) {
1959
2137
  }
1960
2138
 
1961
2139
  // src/ui/display.ts
1962
- import chalk4 from "chalk";
2140
+ import chalk6 from "chalk";
1963
2141
  import Table2 from "cli-table3";
1964
2142
  function printPluginList(plugins, installed) {
1965
2143
  const table = new Table2({
1966
2144
  head: [
1967
- chalk4.cyan(t("list_id")),
1968
- chalk4.cyan(t("list_name")),
1969
- chalk4.cyan(t("list_status")),
1970
- chalk4.cyan(t("list_skills")),
1971
- chalk4.cyan(t("list_agents"))
2145
+ chalk6.cyan(t("list_id")),
2146
+ chalk6.cyan(t("list_name")),
2147
+ chalk6.cyan(t("list_status")),
2148
+ chalk6.cyan(t("list_skills")),
2149
+ chalk6.cyan(t("list_agents"))
1972
2150
  ],
1973
2151
  style: { head: [], border: [] }
1974
2152
  });
1975
2153
  plugins.forEach((plugin, index) => {
1976
2154
  const isInstalled = installed.has(plugin.id);
1977
- const status = isInstalled ? chalk4.green(`\u2713 ${t("status_installed")}`) : chalk4.dim(`\u2014`);
2155
+ const status = isInstalled ? chalk6.green(`\u2713 ${t("status_installed")}`) : chalk6.dim(`\u2014`);
1978
2156
  table.push([
1979
2157
  String(index + 1),
1980
2158
  plugin.displayName,
@@ -1984,7 +2162,8 @@ function printPluginList(plugins, installed) {
1984
2162
  ]);
1985
2163
  });
1986
2164
  console.log();
1987
- console.log(chalk4.bold(` ${t("list_title")} (${plugins.length} \u4E2A)`));
2165
+ const countSuffix = t("count_suffix");
2166
+ console.log(chalk6.bold(` ${t("list_title")} (${plugins.length}${countSuffix ? " " + countSuffix : ""})`));
1988
2167
  console.log(table.toString());
1989
2168
  console.log();
1990
2169
  }
@@ -1993,7 +2172,7 @@ function printInstallSummary(results) {
1993
2172
  const totalCount = results.length;
1994
2173
  console.log();
1995
2174
  console.log(
1996
- chalk4.bold(
2175
+ chalk6.bold(
1997
2176
  ` ${t("install_done")}! ${successCount}/${totalCount} ${t("install_success")}`
1998
2177
  )
1999
2178
  );
@@ -2001,10 +2180,10 @@ function printInstallSummary(results) {
2001
2180
  for (const result of results) {
2002
2181
  if (result.success) {
2003
2182
  console.log(
2004
- chalk4.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
2183
+ chalk6.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
2005
2184
  );
2006
2185
  } else {
2007
- console.log(chalk4.red(" \u2717") + ` ${result.displayName}`);
2186
+ console.log(chalk6.red(" \u2717") + ` ${result.displayName}`);
2008
2187
  }
2009
2188
  }
2010
2189
  console.log();
@@ -2017,22 +2196,25 @@ function printEnhancedSummary(results, tool, configRoot) {
2017
2196
  return;
2018
2197
  }
2019
2198
  const toolName = getToolDisplayName(tool);
2199
+ const countSuffix = t("count_suffix");
2020
2200
  const lines = [
2021
- chalk4.bold(` ${t("install_done")}!`),
2201
+ chalk6.bold(` ${t("install_done")}!`),
2022
2202
  "",
2023
- ` ${chalk4.dim(t("install_installed_to") + ":")} ${configRoot}`,
2024
- ` ${chalk4.dim(t("install_skills_count") + ":")} ${totalSkills} \u4E2A ${chalk4.dim("|")} ${chalk4.dim(t("install_agents_count") + ":")} ${totalAgents} \u4E2A`,
2203
+ ` ${chalk6.dim(t("install_installed_to") + ":")} ${configRoot}`,
2204
+ ` ${chalk6.dim(t("install_skills_count") + ":")} ${totalSkills}${countSuffix ? " " + countSuffix : ""} ${chalk6.dim("|")} ${chalk6.dim(t("install_agents_count") + ":")} ${totalAgents}${countSuffix ? " " + countSuffix : ""}`,
2025
2205
  "",
2026
- chalk4.bold(` ${t("install_enhanced_next_steps")}:`),
2027
- ` ${chalk4.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk4.green(toolName.toLowerCase())}`,
2028
- ` ${chalk4.cyan("2.")} ${t("install_enhanced_try")}: ${chalk4.green("\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50")}`,
2029
- ` ${chalk4.cyan("3.")} ${t("install_enhanced_more")}: ${chalk4.green("install-helper list")}`,
2030
- ` ${chalk4.cyan("4.")} ${t("install_enhanced_check")}: ${chalk4.green("install-helper doctor")}`,
2206
+ chalk6.bold(` ${t("install_enhanced_next_steps")}:`),
2207
+ ` ${chalk6.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk6.green(toolName.toLowerCase())}`,
2208
+ ` ${chalk6.cyan("2.")} ${t("install_enhanced_try")}: ${chalk6.green(t("try_prompt"))}`,
2209
+ ` ${chalk6.cyan("3.")} ${t("install_enhanced_more")}: ${chalk6.green("install-helper list")}`,
2210
+ ` ${chalk6.cyan("4.")} ${t("install_enhanced_check")}: ${chalk6.green("install-helper doctor")}`,
2031
2211
  ""
2032
2212
  ];
2033
2213
  if (successResults.length === 1) {
2214
+ const plugin = findPlugin(successResults[0].pluginId);
2215
+ const docsPath = plugin ? `${plugin.dir}/quickstart.md` : `${successResults[0].pluginId}/quickstart.md`;
2034
2216
  lines.push(
2035
- ` ${chalk4.dim(t("install_enhanced_docs"))}: ${chalk4.dim(`plugins-official/${successResults[0].pluginId}/quickstart.md`)}`
2217
+ ` ${chalk6.dim(t("install_enhanced_docs"))}: ${chalk6.dim(docsPath)}`
2036
2218
  );
2037
2219
  }
2038
2220
  console.log();
@@ -2043,34 +2225,34 @@ function printEnhancedSummary(results, tool, configRoot) {
2043
2225
  }
2044
2226
 
2045
2227
  // src/commands/init.ts
2046
- import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
2047
- import { join as join10 } from "path";
2228
+ import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
2229
+ import { join as join13 } from "path";
2048
2230
  var BACK2 = "__back__";
2049
2231
  var CANCEL2 = "__cancel__";
2050
2232
  async function initCommand() {
2051
2233
  const cwd = process.cwd();
2052
- if (existsSync10(join10(cwd, "package.json")) && (() => {
2234
+ if (existsSync11(join13(cwd, "package.json")) && (() => {
2053
2235
  try {
2054
- const pkg = JSON.parse(readFileSync5(join10(cwd, "package.json"), "utf-8"));
2236
+ const pkg = JSON.parse(readFileSync8(join13(cwd, "package.json"), "utf-8"));
2055
2237
  return pkg.name === "@cannbot-ai/install-helper";
2056
2238
  } catch {
2057
2239
  return false;
2058
2240
  }
2059
2241
  })()) {
2060
- logger.warn("\u5F53\u524D\u76EE\u5F55\u770B\u8D77\u6765\u662F install-helper \u5305\u76EE\u5F55");
2061
- logger.info("\u8BF7\u5728\u4F60\u7684\u9879\u76EE\u76EE\u5F55\u4E2D\u8FD0\u884C\u6B64\u547D\u4EE4\uFF0C\u800C\u4E0D\u662F\u5728 install-helper \u5B89\u88C5\u76EE\u5F55\u4E2D");
2062
- logger.info("\u793A\u4F8B\uFF1Acd ~/my-project && install-helper");
2242
+ logger.warn(t("init_pkg_dir_warn"));
2243
+ logger.info(t("init_pkg_dir_hint"));
2244
+ logger.info(t("init_pkg_dir_example"));
2063
2245
  return;
2064
2246
  }
2065
2247
  printBanner(t("wizard_title"));
2066
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
2248
+ const spinner = createSpinner(t("loading_skills_list"));
2067
2249
  spinner.start();
2068
2250
  try {
2069
2251
  const repoManager = createRepositoryManager();
2070
2252
  await repoManager.ensureRepoAndScan();
2071
- spinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
2253
+ spinner.succeed(t("loading_skills_list_complete"));
2072
2254
  } catch {
2073
- spinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
2255
+ spinner.warn(t("loading_skills_list_failed"));
2074
2256
  }
2075
2257
  let step = 0;
2076
2258
  let mode = "plugin";
@@ -2104,7 +2286,7 @@ async function initCommand() {
2104
2286
  }
2105
2287
  }
2106
2288
  async function selectMode() {
2107
- printBoxTitle("\u9009\u62E9\u5B89\u88C5\u7C7B\u578B");
2289
+ printBoxTitle(t("wizard_select_mode_title"));
2108
2290
  const CANCEL3 = "__cancel__";
2109
2291
  const choices = [
2110
2292
  {
@@ -2119,7 +2301,7 @@ async function selectMode() {
2119
2301
  { name: "x " + t("wizard_cancel"), value: CANCEL3 }
2120
2302
  ];
2121
2303
  showOperationHints();
2122
- const result = await select3({
2304
+ const result = await select4({
2123
2305
  message: t("wizard_select_mode"),
2124
2306
  choices,
2125
2307
  loop: false,
@@ -2132,14 +2314,14 @@ async function pluginInstallFlow() {
2132
2314
  const answers = await runWizard();
2133
2315
  if (!answers.confirmed) {
2134
2316
  if (answers.back) return "back";
2135
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
2317
+ logger.info(t("init_cancelled"));
2136
2318
  return "done";
2137
2319
  }
2138
2320
  const repoManager = createRepositoryManager();
2139
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
2321
+ const spinner = createSpinner(t("loading_plugin_data"));
2140
2322
  spinner.start();
2141
2323
  const repoPath = await repoManager.ensureRepo();
2142
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2324
+ spinner.succeed(t("loading_plugin_data_complete"));
2143
2325
  const allPlugins = getAllPlugins();
2144
2326
  const selectedPlugins = answers.plugins.map(
2145
2327
  (id) => allPlugins.find((p) => p.id === id)
@@ -2193,7 +2375,7 @@ async function skillInstallFlow() {
2193
2375
  const result = await selectToolWithDetection();
2194
2376
  if (result === "back") return "back";
2195
2377
  if (result === "cancel") {
2196
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
2378
+ logger.info(t("init_cancelled"));
2197
2379
  return "done";
2198
2380
  }
2199
2381
  tool = result;
@@ -2207,7 +2389,7 @@ async function skillInstallFlow() {
2207
2389
  break;
2208
2390
  }
2209
2391
  if (result === CANCEL2) {
2210
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
2392
+ logger.info(t("init_cancelled"));
2211
2393
  return "done";
2212
2394
  }
2213
2395
  level = result;
@@ -2215,25 +2397,25 @@ async function skillInstallFlow() {
2215
2397
  break;
2216
2398
  }
2217
2399
  case 2: {
2218
- const selectedSkills = await interactiveSkillSelect();
2400
+ const selectedSkills = await interactiveSkillSelect(tool, level);
2219
2401
  if (selectedSkills === "back") {
2220
2402
  step = 1;
2221
2403
  break;
2222
2404
  }
2223
2405
  if (selectedSkills === "cancel") {
2224
- logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
2406
+ logger.info(t("init_cancelled"));
2225
2407
  return "done";
2226
2408
  }
2227
2409
  if (selectedSkills.length === 0) {
2228
- logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Skill");
2410
+ logger.info(t("install_no_skill_selected"));
2229
2411
  return "done";
2230
2412
  }
2231
2413
  const repoManager = createRepositoryManager();
2232
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
2414
+ const spinner = createSpinner(t("loading_plugin_data"));
2233
2415
  spinner.start();
2234
2416
  const repoPath = await repoManager.ensureRepo();
2235
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2236
- logger.info(`${t("skill_install_progress")} ${chalk5.bold(selectedSkills.length)} \u4E2A Skills...`);
2417
+ spinner.succeed(t("loading_plugin_data_complete"));
2418
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk7.bold(String(selectedSkills.length)))}...`);
2237
2419
  const results = await installSkills(selectedSkills, tool, level, repoPath);
2238
2420
  let successCount = 0;
2239
2421
  let failCount = 0;
@@ -2251,10 +2433,10 @@ async function skillInstallFlow() {
2251
2433
  }
2252
2434
  const configRoot = getConfigRoot(tool, level);
2253
2435
  logger.blank();
2254
- logger.success(`${t("skill_install_done")}: ${chalk5.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk5.red(failCount + " \u5931\u8D25") : chalk5.dim(failCount + " \u5931\u8D25")}`);
2436
+ logger.success(`${t("skill_install_done")}: ${chalk7.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk7.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk7.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2255
2437
  logger.blank();
2256
- logger.info(`\u5B89\u88C5\u5230: ${chalk5.cyan(configRoot + "/skills/")}`);
2257
- logger.info(`\u542F\u52A8 ${chalk5.green(tool)} \u5373\u53EF\u4F7F\u7528`);
2438
+ logger.info(`${t("install_to")}: ${chalk7.cyan(configRoot + "/skills/")}`);
2439
+ logger.info(`${t("start_to_use").replace("{tool}", chalk7.green(tool))}`);
2258
2440
  logger.blank();
2259
2441
  return "done";
2260
2442
  }
@@ -2276,32 +2458,32 @@ async function listCommand() {
2276
2458
  }
2277
2459
 
2278
2460
  // src/commands/doctor.ts
2279
- import chalk6 from "chalk";
2280
- import { existsSync as existsSync11, readdirSync as readdirSync4, lstatSync as lstatSync3, unlinkSync as unlinkSync3, mkdirSync as mkdirSync6 } from "fs";
2281
- import { join as join11 } from "path";
2461
+ import chalk8 from "chalk";
2462
+ import { existsSync as existsSync12, readdirSync as readdirSync5, lstatSync as lstatSync3, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6 } from "fs";
2463
+ import { join as join14 } from "path";
2282
2464
  async function doctorCommand(options = {}) {
2283
2465
  console.log();
2284
- console.log(chalk6.bold(` ${t("doctor_title")}`));
2285
- console.log(chalk6.dim(" " + "\u2500".repeat(46)));
2466
+ console.log(chalk8.bold(` ${t("doctor_title")}`));
2467
+ console.log(chalk8.dim(" " + "\u2500".repeat(46)));
2286
2468
  let errors = 0;
2287
2469
  let warnings = 0;
2288
2470
  let fixes = 0;
2289
2471
  console.log();
2290
- console.log(chalk6.bold(` ${t("doctor_tools")}`));
2472
+ console.log(chalk8.bold(` ${t("doctor_tools")}`));
2291
2473
  const detectedTools = await detectTools();
2292
2474
  const allTools = getAllTools();
2293
2475
  for (const tool of allTools) {
2294
2476
  const detected = detectedTools.find((d) => d.name === tool);
2295
2477
  if (detected) {
2296
2478
  console.log(
2297
- chalk6.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
2479
+ chalk8.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
2298
2480
  );
2299
2481
  } else {
2300
- console.log(chalk6.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 \u672A\u5B89\u88C5`);
2482
+ console.log(chalk8.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 ${t("doctor_not_installed")}`);
2301
2483
  }
2302
2484
  }
2303
2485
  console.log();
2304
- console.log(chalk6.bold(` ${t("doctor_plugins")}`));
2486
+ console.log(chalk8.bold(` ${t("doctor_plugins")}`));
2305
2487
  const plugins = getAllPlugins();
2306
2488
  const installed = scanInstalled();
2307
2489
  const installedMap = new Map(installed.map((p) => [p.id, p]));
@@ -2309,80 +2491,80 @@ async function doctorCommand(options = {}) {
2309
2491
  const inst = installedMap.get(plugin.id);
2310
2492
  if (inst) {
2311
2493
  console.log(
2312
- chalk6.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
2494
+ chalk8.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
2313
2495
  );
2314
2496
  } else {
2315
- console.log(chalk6.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 \u672A\u5B89\u88C5`);
2497
+ console.log(chalk8.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 ${t("doctor_not_installed")}`);
2316
2498
  }
2317
2499
  }
2318
2500
  console.log();
2319
- console.log(chalk6.bold(` ${t("doctor_links")}`));
2501
+ console.log(chalk8.bold(` ${t("doctor_links")}`));
2320
2502
  const primaryTool = detectedTools[0]?.name || "opencode";
2321
2503
  const configRoot = getConfigRoot(primaryTool, "project");
2322
2504
  const skillsDir = getSkillsDir(configRoot);
2323
2505
  const agentsDir = getAgentsDir(configRoot);
2324
- if (existsSync11(skillsDir)) {
2506
+ if (existsSync12(skillsDir)) {
2325
2507
  const brokenLinks = checkBrokenLinks(skillsDir);
2326
2508
  if (brokenLinks === 0) {
2327
- const count = readdirSync4(skillsDir).length;
2328
- console.log(chalk6.green(" \u2713") + ` ${skillsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
2509
+ const count = readdirSync5(skillsDir).length;
2510
+ console.log(chalk8.green(" \u2713") + ` ${skillsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2329
2511
  } else {
2330
- console.log(chalk6.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
2512
+ console.log(chalk8.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2331
2513
  warnings++;
2332
2514
  if (options.fix) {
2333
2515
  const fixed = fixBrokenLinks(skillsDir);
2334
2516
  fixes += fixed;
2335
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
2517
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2336
2518
  }
2337
2519
  }
2338
2520
  } else {
2339
- console.log(chalk6.dim(" \u2014") + ` ${skillsDir} \u2014 \u4E0D\u5B58\u5728`);
2521
+ console.log(chalk8.dim(" \u2014") + ` ${skillsDir} \u2014 ${t("doctor_not_exist")}`);
2340
2522
  if (options.fix) {
2341
2523
  mkdirSync6(skillsDir, { recursive: true });
2342
2524
  fixes++;
2343
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${skillsDir}`);
2525
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
2344
2526
  }
2345
2527
  }
2346
- if (existsSync11(agentsDir)) {
2528
+ if (existsSync12(agentsDir)) {
2347
2529
  const brokenLinks = checkBrokenLinks(agentsDir);
2348
2530
  if (brokenLinks === 0) {
2349
- const count = readdirSync4(agentsDir).length;
2350
- console.log(chalk6.green(" \u2713") + ` ${agentsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
2531
+ const count = readdirSync5(agentsDir).length;
2532
+ console.log(chalk8.green(" \u2713") + ` ${agentsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2351
2533
  } else {
2352
- console.log(chalk6.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
2534
+ console.log(chalk8.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2353
2535
  warnings++;
2354
2536
  if (options.fix) {
2355
2537
  const fixed = fixBrokenLinks(agentsDir);
2356
2538
  fixes += fixed;
2357
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
2539
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2358
2540
  }
2359
2541
  }
2360
2542
  } else {
2361
- console.log(chalk6.dim(" \u2014") + ` ${agentsDir} \u2014 \u4E0D\u5B58\u5728`);
2543
+ console.log(chalk8.dim(" \u2014") + ` ${agentsDir} \u2014 ${t("doctor_not_exist")}`);
2362
2544
  if (options.fix) {
2363
2545
  mkdirSync6(agentsDir, { recursive: true });
2364
2546
  fixes++;
2365
- console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${agentsDir}`);
2547
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", agentsDir)}`);
2366
2548
  }
2367
2549
  }
2368
2550
  console.log();
2369
- console.log(chalk6.bold(` ${t("doctor_config")}`));
2551
+ console.log(chalk8.bold(` ${t("doctor_config")}`));
2370
2552
  const configFile = getConfigFileName(primaryTool);
2371
- const configPath = join11(process.cwd(), configFile);
2372
- if (existsSync11(configPath)) {
2373
- console.log(chalk6.green(" \u2713") + ` ${configFile} \u5B58\u5728`);
2553
+ const configPath = join14(process.cwd(), configFile);
2554
+ if (existsSync12(configPath)) {
2555
+ console.log(chalk8.green(" \u2713") + ` ${configFile} ${t("doctor_config_exists")}`);
2374
2556
  } else {
2375
- console.log(chalk6.dim(" \u2014") + ` ${configFile} \u4E0D\u5B58\u5728`);
2557
+ console.log(chalk8.dim(" \u2014") + ` ${configFile} ${t("doctor_config_not_exist")}`);
2376
2558
  }
2377
2559
  console.log();
2378
- console.log(chalk6.dim(" " + "\u2500".repeat(46)));
2560
+ console.log(chalk8.dim(" " + "\u2500".repeat(46)));
2379
2561
  if (options.fix && fixes > 0) {
2380
2562
  console.log(
2381
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF, ${fixes} \u9879\u5DF2\u4FEE\u590D`
2563
+ ` ${t("doctor_result")}: ${t("doctor_result_with_fix").replace("{warnings}", String(warnings)).replace("{errors}", String(errors)).replace("{fixes}", String(fixes))}`
2382
2564
  );
2383
2565
  } else {
2384
2566
  console.log(
2385
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF`
2567
+ ` ${t("doctor_result")}: ${t("doctor_result_without_fix").replace("{warnings}", String(warnings)).replace("{errors}", String(errors))}`
2386
2568
  );
2387
2569
  }
2388
2570
  console.log();
@@ -2390,13 +2572,13 @@ async function doctorCommand(options = {}) {
2390
2572
  function checkBrokenLinks(dir) {
2391
2573
  let broken = 0;
2392
2574
  try {
2393
- const entries = readdirSync4(dir);
2575
+ const entries = readdirSync5(dir);
2394
2576
  for (const entry of entries) {
2395
- const fullPath = join11(dir, entry);
2577
+ const fullPath = join14(dir, entry);
2396
2578
  try {
2397
2579
  const stats = lstatSync3(fullPath);
2398
2580
  if (stats.isSymbolicLink()) {
2399
- if (!existsSync11(fullPath)) {
2581
+ if (!existsSync12(fullPath)) {
2400
2582
  broken++;
2401
2583
  }
2402
2584
  }
@@ -2411,14 +2593,14 @@ function checkBrokenLinks(dir) {
2411
2593
  function fixBrokenLinks(dir) {
2412
2594
  let fixed = 0;
2413
2595
  try {
2414
- const entries = readdirSync4(dir);
2596
+ const entries = readdirSync5(dir);
2415
2597
  for (const entry of entries) {
2416
- const fullPath = join11(dir, entry);
2598
+ const fullPath = join14(dir, entry);
2417
2599
  try {
2418
2600
  const stats = lstatSync3(fullPath);
2419
2601
  if (stats.isSymbolicLink()) {
2420
- if (!existsSync11(fullPath)) {
2421
- unlinkSync3(fullPath);
2602
+ if (!existsSync12(fullPath)) {
2603
+ unlinkSync4(fullPath);
2422
2604
  fixed++;
2423
2605
  }
2424
2606
  }
@@ -2431,36 +2613,36 @@ function fixBrokenLinks(dir) {
2431
2613
  }
2432
2614
 
2433
2615
  // src/commands/status.ts
2434
- import chalk7 from "chalk";
2616
+ import chalk9 from "chalk";
2435
2617
  async function statusCommand() {
2436
2618
  const plugins = getAllPlugins();
2437
2619
  const installed = scanInstalled();
2438
2620
  const installedMap = new Map(installed.map((p) => [p.id, p]));
2439
2621
  console.log();
2440
- console.log(chalk7.bold(` ${t("status_title")}`));
2622
+ console.log(chalk9.bold(` ${t("status_title")}`));
2441
2623
  console.log();
2442
2624
  if (installed.length === 0) {
2443
- console.log(chalk7.dim(` ${t("status_none")}`));
2444
- console.log(chalk7.dim(` ${t("status_hint")}`));
2625
+ console.log(chalk9.dim(` ${t("status_none")}`));
2626
+ console.log(chalk9.dim(` ${t("status_hint")}`));
2445
2627
  console.log();
2446
2628
  return;
2447
2629
  }
2448
2630
  for (const inst of installed) {
2449
2631
  console.log(
2450
- chalk7.green(" \u2713") + ` ${inst.displayName}` + chalk7.dim(` (${inst.tool}, ${inst.level})`)
2632
+ chalk9.green(" \u2713") + ` ${inst.displayName}` + chalk9.dim(` (${inst.tool}, ${inst.level})`)
2451
2633
  );
2452
2634
  console.log(
2453
- chalk7.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
2635
+ chalk9.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
2454
2636
  );
2455
- console.log(chalk7.dim(` ${inst.configRoot}`));
2456
- console.log(chalk7.dim(` ${t("status_install_time")}: ${inst.installTime}`));
2637
+ console.log(chalk9.dim(` ${inst.configRoot}`));
2638
+ console.log(chalk9.dim(` ${t("status_install_time")}: ${inst.installTime}`));
2457
2639
  console.log();
2458
2640
  }
2459
2641
  }
2460
2642
 
2461
2643
  // src/commands/install.ts
2462
2644
  import { confirm } from "@inquirer/prompts";
2463
- import chalk8 from "chalk";
2645
+ import chalk10 from "chalk";
2464
2646
  async function installCommand(names, options) {
2465
2647
  if (options.list) {
2466
2648
  try {
@@ -2471,79 +2653,138 @@ async function installCommand(names, options) {
2471
2653
  listAllSkills();
2472
2654
  return;
2473
2655
  }
2656
+ if (options.all) {
2657
+ const scanSpinner2 = createSpinner(t("loading_skills_list"));
2658
+ scanSpinner2.start();
2659
+ try {
2660
+ const scanRepoManager = createRepositoryManager();
2661
+ await scanRepoManager.ensureRepoAndScan();
2662
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
2663
+ } catch {
2664
+ scanSpinner2.warn(t("loading_skills_list_failed"));
2665
+ }
2666
+ const allSkills = getAllSkills();
2667
+ const skillIds = allSkills.map((s) => s.id);
2668
+ if (skillIds.length === 0) {
2669
+ logger.error(t("install_no_skill_selected"));
2670
+ return;
2671
+ }
2672
+ let tool2;
2673
+ if (options.tool) {
2674
+ tool2 = validateTool(options.tool);
2675
+ } else {
2676
+ const detected = await selectToolWithDetection();
2677
+ if (detected === "back" || detected === "cancel") return;
2678
+ tool2 = detected;
2679
+ }
2680
+ const level2 = options.level ? validateLevel(options.level) : "project";
2681
+ const repoManager = createRepositoryManager();
2682
+ const spinner = createSpinner(t("loading_plugin_data"));
2683
+ spinner.start();
2684
+ const repoPath = await repoManager.ensureRepo();
2685
+ spinner.succeed(t("loading_plugin_data_complete"));
2686
+ logger.info(`${t("skill_all_install_progress").replace("{count}", chalk10.bold(String(skillIds.length)))}`);
2687
+ const results = await installSkills(skillIds, tool2, level2, repoPath);
2688
+ let successCount = 0;
2689
+ let failCount = 0;
2690
+ const total = results.length;
2691
+ for (let i = 0; i < results.length; i++) {
2692
+ const result = results[i];
2693
+ const progress = `[${i + 1}/${total}]`;
2694
+ if (result.success) {
2695
+ logger.success(`${progress} ${result.skillId}`);
2696
+ successCount++;
2697
+ } else {
2698
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
2699
+ failCount++;
2700
+ }
2701
+ }
2702
+ const configRoot = getConfigRoot(tool2, level2);
2703
+ logger.blank();
2704
+ 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)))}`);
2705
+ logger.blank();
2706
+ logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
2707
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
2708
+ logger.blank();
2709
+ return;
2710
+ }
2474
2711
  if (names.length === 0) {
2475
- const scanSpinner2 = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
2712
+ const scanSpinner2 = createSpinner(t("loading_skills_list"));
2476
2713
  scanSpinner2.start();
2477
2714
  try {
2478
2715
  const scanRepoManager = createRepositoryManager();
2479
2716
  await scanRepoManager.ensureRepoAndScan();
2480
- scanSpinner2.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
2717
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
2481
2718
  } catch {
2482
- scanSpinner2.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
2719
+ scanSpinner2.warn(t("loading_skills_list_failed"));
2483
2720
  }
2484
2721
  let tool2;
2485
2722
  if (options.tool) {
2486
- tool2 = options.tool;
2723
+ tool2 = validateTool(options.tool);
2487
2724
  } else {
2488
2725
  const detected = await selectToolWithDetection();
2489
- if (detected === "back") return;
2726
+ if (detected === "back" || detected === "cancel") return;
2490
2727
  tool2 = detected;
2491
2728
  }
2729
+ const level2 = options.level ? validateLevel(options.level) : "project";
2492
2730
  let step = 1;
2493
2731
  while (true) {
2494
2732
  switch (step) {
2495
2733
  case 1: {
2496
- const selectedSkills = await interactiveSkillSelect();
2734
+ const selectedSkills = await interactiveSkillSelect(tool2, level2);
2497
2735
  if (selectedSkills === "back") {
2498
2736
  const redetected = await selectToolWithDetection();
2499
- if (redetected === "back") return;
2737
+ if (redetected === "back" || redetected === "cancel") return;
2500
2738
  tool2 = redetected;
2501
2739
  break;
2502
2740
  }
2741
+ if (selectedSkills === "cancel") return;
2503
2742
  if (selectedSkills.length === 0) {
2504
- logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Skill");
2743
+ logger.info(t("install_no_skill_selected"));
2505
2744
  return;
2506
2745
  }
2507
2746
  names = selectedSkills;
2508
2747
  const repoManager = createRepositoryManager();
2509
- const spinner = createSpinner(t("install_cloning"));
2748
+ const spinner = createSpinner(t("loading_plugin_data"));
2510
2749
  spinner.start();
2511
2750
  const repoPath = await repoManager.ensureRepo();
2512
- spinner.succeed(t("install_repo_ready"));
2513
- logger.info(`${t("skill_install_progress")} ${chalk8.bold(names.length)} \u4E2A Skills...`);
2514
- const level2 = options.level || "project";
2751
+ spinner.succeed(t("loading_plugin_data_complete"));
2752
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(names.length)))}...`);
2515
2753
  const results = await installSkills(names, tool2, level2, repoPath);
2516
2754
  let successCount = 0;
2517
2755
  let failCount = 0;
2518
- for (const result of results) {
2756
+ const total = results.length;
2757
+ for (let i = 0; i < results.length; i++) {
2758
+ const result = results[i];
2759
+ const progress = `[${i + 1}/${total}]`;
2519
2760
  if (result.success) {
2520
- logger.success(`${result.skillId}`);
2761
+ logger.success(`${progress} ${result.skillId}`);
2521
2762
  successCount++;
2522
2763
  } else {
2523
- logger.error(`${result.skillId}: ${result.error}`);
2764
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
2524
2765
  failCount++;
2525
2766
  }
2526
2767
  }
2527
2768
  const configRoot = getConfigRoot(tool2, level2);
2528
2769
  logger.blank();
2529
- logger.success(`${t("skill_install_done")}: ${chalk8.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk8.red(failCount + " \u5931\u8D25") : chalk8.dim(failCount + " \u5931\u8D25")}`);
2770
+ logger.success(`${t("skill_install_done")}: ${chalk10.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk10.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk10.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2530
2771
  logger.blank();
2531
- logger.info(`\u5B89\u88C5\u5230: ${chalk8.cyan(configRoot + "/skills/")}`);
2532
- logger.info(`\u542F\u52A8 ${chalk8.green(tool2)} \u5373\u53EF\u4F7F\u7528`);
2772
+ logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
2773
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
2533
2774
  logger.blank();
2534
2775
  return;
2535
2776
  }
2536
2777
  }
2537
2778
  }
2538
2779
  }
2539
- const scanSpinner = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
2780
+ const scanSpinner = createSpinner(t("loading_skills_list"));
2540
2781
  scanSpinner.start();
2541
2782
  try {
2542
2783
  const scanRepoManager = createRepositoryManager();
2543
2784
  await scanRepoManager.ensureRepoAndScan();
2544
- scanSpinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
2785
+ scanSpinner.succeed(t("loading_skills_list_complete"));
2545
2786
  } catch {
2546
- scanSpinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
2787
+ scanSpinner.warn(t("loading_skills_list_failed"));
2547
2788
  }
2548
2789
  const plugins = [];
2549
2790
  const skills = [];
@@ -2560,22 +2801,22 @@ async function installCommand(names, options) {
2560
2801
  }
2561
2802
  logger.error(`${t("error_plugin_not_found")}: ${name}`);
2562
2803
  const allPlugins = getAllPlugins();
2563
- logger.info("\u53EF\u7528\u63D2\u4EF6:");
2804
+ logger.info(t("available_plugins") + ":");
2564
2805
  for (const p of allPlugins) {
2565
2806
  logger.step(` ${p.id} (${p.aliases.join(", ")})`);
2566
2807
  }
2567
- logger.info(`\u53EF\u7528 Skills: ${chalk8.cyan("install-helper install --list")}`);
2808
+ logger.info(`${t("available_skills")}: ${chalk10.cyan("install-helper install --list")}`);
2568
2809
  return;
2569
2810
  }
2570
2811
  let tool;
2571
2812
  if (options.tool) {
2572
- tool = options.tool;
2813
+ tool = validateTool(options.tool);
2573
2814
  } else {
2574
2815
  const detected = await selectToolWithDetection();
2575
- if (detected === "back") return;
2816
+ if (detected === "back" || detected === "cancel") return;
2576
2817
  tool = detected;
2577
2818
  }
2578
- const level = options.level || "project";
2819
+ const level = options.level ? validateLevel(options.level) : "project";
2579
2820
  if (plugins.length > 0) {
2580
2821
  const configRoot = getConfigRoot(tool, level);
2581
2822
  const manifests = readAllManifests(configRoot);
@@ -2603,16 +2844,18 @@ async function installCommand(names, options) {
2603
2844
  }
2604
2845
  if (pluginsToInstall.length > 0) {
2605
2846
  const repoManager = createRepositoryManager();
2606
- const spinner = createSpinner(t("install_cloning"));
2847
+ const spinner = createSpinner(t("loading_plugin_data"));
2607
2848
  spinner.start();
2608
2849
  const repoPath = await repoManager.ensureRepo();
2609
- spinner.succeed(t("install_repo_ready"));
2850
+ spinner.succeed(t("loading_plugin_data_complete"));
2610
2851
  const allPlugins = getAllPlugins();
2611
2852
  const results = await installPlugins(
2612
2853
  pluginsToInstall,
2613
2854
  tool,
2614
2855
  level,
2615
- repoPath
2856
+ repoPath,
2857
+ void 0,
2858
+ options.yes
2616
2859
  );
2617
2860
  const summary = results.map((result) => {
2618
2861
  const plugin = allPlugins.find((p) => p.id === result.pluginId);
@@ -2630,32 +2873,35 @@ async function installCommand(names, options) {
2630
2873
  }
2631
2874
  if (skills.length > 0) {
2632
2875
  const repoManager = createRepositoryManager();
2633
- const spinner = createSpinner(t("install_cloning"));
2876
+ const spinner = createSpinner(t("loading_plugin_data"));
2634
2877
  spinner.start();
2635
2878
  const repoPath = await repoManager.ensureRepo();
2636
- spinner.succeed(t("install_repo_ready"));
2637
- logger.info(`${t("skill_install_progress")} ${chalk8.bold(skills.length)} \u4E2A Skills...`);
2879
+ spinner.succeed(t("loading_plugin_data_complete"));
2880
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(skills.length)))}...`);
2638
2881
  const results = await installSkills(skills, tool, level, repoPath);
2639
2882
  let successCount = 0;
2640
2883
  let failCount = 0;
2641
- for (const result of results) {
2884
+ const total = results.length;
2885
+ for (let i = 0; i < results.length; i++) {
2886
+ const result = results[i];
2887
+ const progress = `[${i + 1}/${total}]`;
2642
2888
  if (result.success) {
2643
- logger.success(`${result.skillId}`);
2889
+ logger.success(`${progress} ${result.skillId}`);
2644
2890
  successCount++;
2645
2891
  } else {
2646
- logger.error(`${result.skillId}: ${result.error}`);
2892
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
2647
2893
  failCount++;
2648
2894
  }
2649
2895
  }
2650
2896
  logger.blank();
2651
- logger.success(`${t("skill_install_done")}: ${chalk8.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk8.red(failCount + " \u5931\u8D25") : chalk8.dim(failCount + " \u5931\u8D25")}`);
2897
+ logger.success(`${t("skill_install_done")}: ${chalk10.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk10.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk10.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2652
2898
  logger.blank();
2653
- logger.info(`\u5B89\u88C5\u5230: ${chalk8.cyan(getConfigRoot(tool, level) + "/skills/")}`);
2654
- logger.info(`\u542F\u52A8 ${chalk8.green(tool)} \u5373\u53EF\u4F7F\u7528`);
2899
+ logger.info(`${t("install_to")}: ${chalk10.cyan(getConfigRoot(tool, level) + "/skills/")}`);
2900
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool))}`);
2655
2901
  logger.blank();
2656
2902
  }
2657
2903
  }
2658
- async function installPlugins(pluginIds, tool, level, repoPath) {
2904
+ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes) {
2659
2905
  const results = [];
2660
2906
  const total = pluginIds.length;
2661
2907
  for (let i = 0; i < pluginIds.length; i++) {
@@ -2667,7 +2913,9 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
2667
2913
  pluginId,
2668
2914
  tool,
2669
2915
  level,
2670
- repoPath
2916
+ repoPath,
2917
+ installPath,
2918
+ yes
2671
2919
  });
2672
2920
  if (result.success) {
2673
2921
  spinner.succeed(
@@ -2683,11 +2931,11 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
2683
2931
  }
2684
2932
 
2685
2933
  // src/commands/uninstall.ts
2686
- import { existsSync as existsSync12, unlinkSync as unlinkSync4, rmdirSync as rmdirSync2, readdirSync as readdirSync5, lstatSync as lstatSync4 } from "fs";
2687
- import chalk9 from "chalk";
2934
+ import { existsSync as existsSync13, unlinkSync as unlinkSync5, rmdirSync as rmdirSync2, readdirSync as readdirSync6, lstatSync as lstatSync4 } from "fs";
2935
+ import chalk11 from "chalk";
2688
2936
  async function uninstallCommand(names, options) {
2689
- const tool = options.tool || "opencode";
2690
- const level = options.level || "project";
2937
+ const tool = options.tool ? validateTool(options.tool) : "opencode";
2938
+ const level = options.level ? validateLevel(options.level) : "project";
2691
2939
  const plugins = [];
2692
2940
  const skills = [];
2693
2941
  for (const name of names) {
@@ -2701,40 +2949,44 @@ async function uninstallCommand(names, options) {
2701
2949
  skills.push(skill.id);
2702
2950
  continue;
2703
2951
  }
2704
- logger.error(`\u672A\u627E\u5230: ${name}`);
2952
+ logger.error(`${t("uninstall_not_found")}: ${name}`);
2705
2953
  }
2706
2954
  for (const pluginId of plugins) {
2707
2955
  const plugin = findPlugin(pluginId);
2708
2956
  if (!plugin) continue;
2709
2957
  const record = readRecord(plugin.id);
2710
2958
  if (!record) {
2711
- logger.warn(`${plugin.displayName} \u672A\u627E\u5230\u5B89\u88C5\u8BB0\u5F55\uFF0C\u53EF\u80FD\u672A\u5B89\u88C5\u6216\u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F`);
2712
- logger.info(`\u5982\u9700\u5F3A\u5236\u6E05\u7406\uFF0C\u8BF7\u624B\u52A8\u5220\u9664 .opencode/ \u6216 .claude/ \u76EE\u5F55\u4E2D\u7684\u76F8\u5173\u6587\u4EF6`);
2959
+ logger.warn(`${plugin.displayName} ${t("uninstall_no_record")}`);
2960
+ logger.info(`${t("uninstall_no_record_hint")}`);
2713
2961
  continue;
2714
2962
  }
2715
- logger.info(`\u6B63\u5728\u5378\u8F7D ${plugin.displayName}...`);
2963
+ logger.info(`${t("uninstall_in_progress")} ${plugin.displayName}...`);
2716
2964
  let removedFiles = 0;
2717
2965
  let removedDirs = 0;
2718
2966
  for (const filePath of record.files) {
2719
2967
  try {
2720
- if (existsSync12(filePath) || isSymlink3(filePath)) {
2721
- unlinkSync4(filePath);
2968
+ if (existsSync13(filePath) || isSymlink3(filePath)) {
2969
+ unlinkSync5(filePath);
2722
2970
  const name = filePath.split("/").pop() || filePath;
2723
- logger.step(` \u79FB\u9664: ${name}`);
2971
+ logger.step(` ${t("uninstall_remove_label")}: ${name}`);
2724
2972
  removedFiles++;
2725
2973
  }
2726
2974
  } catch {
2727
2975
  }
2728
2976
  }
2977
+ const configRoot = getConfigRoot(record.tool, record.level);
2729
2978
  const sortedDirs = [...record.directories].sort((a, b) => b.length - a.length);
2730
2979
  for (const dirPath of sortedDirs) {
2731
2980
  try {
2732
- if (existsSync12(dirPath)) {
2733
- const entries = readdirSync5(dirPath);
2981
+ if (dirPath === configRoot) {
2982
+ continue;
2983
+ }
2984
+ if (existsSync13(dirPath)) {
2985
+ const entries = readdirSync6(dirPath);
2734
2986
  if (entries.length === 0) {
2735
2987
  rmdirSync2(dirPath);
2736
2988
  const name = dirPath.split("/").pop() || dirPath;
2737
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${name}/`);
2989
+ logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
2738
2990
  removedDirs++;
2739
2991
  }
2740
2992
  }
@@ -2743,15 +2995,32 @@ async function uninstallCommand(names, options) {
2743
2995
  }
2744
2996
  deleteRecord(plugin.id);
2745
2997
  removeInstalledPlugin(plugin.id);
2998
+ const backups = findBackups(configRoot);
2999
+ const otherBackups = backups.filter((b) => b.pluginId !== pluginId);
3000
+ if (otherBackups.length > 0) {
3001
+ const choice = await showRestorePrompt(otherBackups);
3002
+ if (choice !== "none") {
3003
+ const restored = restoreBackup(choice, configRoot, record.tool);
3004
+ if (restored) {
3005
+ const backupInfo = otherBackups.find((b) => b.filePath === choice);
3006
+ logger.success(`${t("backup_restore_success")}: ${backupInfo?.pluginName || ""}`);
3007
+ deleteBackup(choice);
3008
+ }
3009
+ }
3010
+ }
3011
+ const currentBackups = backups.filter((b) => b.pluginId === pluginId);
3012
+ for (const backup of currentBackups) {
3013
+ deleteBackup(backup.filePath);
3014
+ }
2746
3015
  const totalRemoved = removedFiles + removedDirs;
2747
3016
  if (totalRemoved > 0) {
2748
- logger.success(`\u5DF2\u5378\u8F7D ${plugin.displayName}\uFF08\u79FB\u9664 ${removedFiles} \u4E2A\u6587\u4EF6\uFF0C${removedDirs} \u4E2A\u76EE\u5F55\uFF09`);
3017
+ logger.success(`${t("uninstall_removed")} ${plugin.displayName}\uFF08${t("uninstall_remove_file_format").replace("{count}", String(removedFiles))}\uFF0C${t("uninstall_remove_dir_format").replace("{count}", String(removedDirs))}\uFF09`);
2749
3018
  } else {
2750
- logger.warn(`${plugin.displayName} \u7684\u6587\u4EF6\u5DF2\u4E0D\u5B58\u5728\uFF0C\u5DF2\u6E05\u7406\u5B89\u88C5\u8BB0\u5F55`);
3019
+ logger.warn(`${plugin.displayName}${t("uninstall_files_gone")}`);
2751
3020
  }
2752
3021
  }
2753
3022
  if (skills.length > 0) {
2754
- logger.info(`\u6B63\u5728\u5378\u8F7D ${skills.length} \u4E2A Skills...`);
3023
+ logger.info(`${t("uninstall_skills_in_progress")} ${t("skill_count_format").replace("{count}", String(skills.length))}...`);
2755
3024
  const results = await uninstallSkills(skills, tool, level);
2756
3025
  let successCount = 0;
2757
3026
  let failCount = 0;
@@ -2763,7 +3032,7 @@ async function uninstallCommand(names, options) {
2763
3032
  }
2764
3033
  }
2765
3034
  logger.blank();
2766
- logger.success(`${t("skill_uninstall_done")}: ${chalk9.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk9.red(failCount + " \u5931\u8D25") : chalk9.dim(failCount + " \u5931\u8D25")}`);
3035
+ logger.success(`${t("skill_uninstall_done")}: ${chalk11.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk11.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk11.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2767
3036
  logger.blank();
2768
3037
  }
2769
3038
  }
@@ -2776,17 +3045,17 @@ function isSymlink3(path) {
2776
3045
  }
2777
3046
 
2778
3047
  // src/commands/update.ts
2779
- import chalk10 from "chalk";
3048
+ import chalk12 from "chalk";
2780
3049
  async function updateCommand(pluginNames, options) {
2781
3050
  let tool;
2782
3051
  if (options.tool) {
2783
- tool = options.tool;
3052
+ tool = validateTool(options.tool);
2784
3053
  } else {
2785
3054
  const detected = await selectToolWithDetection();
2786
- if (detected === "back") return;
3055
+ if (detected === "back" || detected === "cancel") return;
2787
3056
  tool = detected;
2788
3057
  }
2789
- const level = options.level || "project";
3058
+ const level = options.level ? validateLevel(options.level) : "project";
2790
3059
  let pluginsToUpdate = [];
2791
3060
  if (pluginNames.length === 0) {
2792
3061
  const configRoot = getConfigRoot(tool, level);
@@ -2794,7 +3063,7 @@ async function updateCommand(pluginNames, options) {
2794
3063
  pluginsToUpdate = manifests.map((m) => m.team);
2795
3064
  if (pluginsToUpdate.length === 0) {
2796
3065
  logger.info(t("update_no_plugins"));
2797
- logger.info(`\u4F7F\u7528 ${chalk10.cyan("install-helper install <plugin>")} \u5B89\u88C5\u63D2\u4EF6`);
3066
+ logger.info(t("update_install_hint").replace("{cmd}", chalk12.cyan("install-helper install <plugin>")));
2798
3067
  return;
2799
3068
  }
2800
3069
  } else {
@@ -2856,42 +3125,42 @@ async function updateCommand(pluginNames, options) {
2856
3125
  }
2857
3126
 
2858
3127
  // src/commands/info.ts
2859
- import chalk11 from "chalk";
2860
- import { existsSync as existsSync13, readdirSync as readdirSync6 } from "fs";
2861
- import { join as join12 } from "path";
3128
+ import chalk13 from "chalk";
3129
+ import { existsSync as existsSync14, readdirSync as readdirSync7 } from "fs";
3130
+ import { join as join15 } from "path";
2862
3131
  async function infoCommand(pluginName) {
2863
3132
  const plugin = findPlugin(pluginName);
2864
3133
  if (!plugin) {
2865
- console.log(chalk11.red(` ${t("info_not_found")}: ${pluginName}`));
3134
+ console.log(chalk13.red(` ${t("info_not_found")}: ${pluginName}`));
2866
3135
  return;
2867
3136
  }
2868
3137
  const installed = scanInstalled();
2869
3138
  const isInstalled = installed.some((p) => p.id === plugin.id);
2870
- const statusText = isInstalled ? chalk11.green(`\u2713 ${t("status_installed")}`) : chalk11.dim(`\u2014 ${t("status_not_installed")}`);
3139
+ const statusText = isInstalled ? chalk13.green(`\u2713 ${t("status_installed")}`) : chalk13.dim(`\u2014 ${t("status_not_installed")}`);
2871
3140
  console.log();
2872
- console.log(chalk11.bold(` ${plugin.displayName}`));
2873
- console.log(chalk11.dim(" " + "\u2500".repeat(40)));
3141
+ console.log(chalk13.bold(` ${plugin.displayName}`));
3142
+ console.log(chalk13.dim(" " + "\u2500".repeat(40)));
2874
3143
  console.log();
2875
- console.log(` ${chalk11.dim(t("info_description") + ":")} ${plugin.description}`);
2876
- console.log(` ${chalk11.dim("ID:")} ${plugin.id}`);
2877
- console.log(` ${chalk11.dim(t("info_status") + ":")} ${statusText}`);
2878
- console.log(` ${chalk11.dim(t("info_skills") + ":")} ${plugin.skills}`);
2879
- console.log(` ${chalk11.dim(t("info_agents") + ":")} ${plugin.agents}`);
2880
- console.log(` ${chalk11.dim("\u522B\u540D:")} ${plugin.aliases.join(", ")}`);
3144
+ console.log(` ${chalk13.dim(t("info_description") + ":")} ${plugin.description}`);
3145
+ console.log(` ${chalk13.dim("ID:")} ${plugin.id}`);
3146
+ console.log(` ${chalk13.dim(t("info_status") + ":")} ${statusText}`);
3147
+ console.log(` ${chalk13.dim(t("info_skills") + ":")} ${plugin.skills}`);
3148
+ console.log(` ${chalk13.dim(t("info_agents") + ":")} ${plugin.agents}`);
3149
+ console.log(` ${chalk13.dim(t("info_aliases") + ":")} ${plugin.aliases.join(", ")}`);
2881
3150
  console.log();
2882
3151
  try {
2883
3152
  const repoManager = createRepositoryManager();
2884
3153
  const repoPath = await repoManager.ensureRepo();
2885
- const quickstartPath = join12(repoPath, plugin.dir, "quickstart.md");
2886
- if (existsSync13(quickstartPath)) {
2887
- console.log(` ${chalk11.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
3154
+ const quickstartPath = join15(repoPath, plugin.dir, "quickstart.md");
3155
+ if (existsSync14(quickstartPath)) {
3156
+ console.log(` ${chalk13.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
2888
3157
  }
2889
- const agentsDir = join12(repoPath, plugin.dir, "agents");
2890
- if (existsSync13(agentsDir)) {
2891
- const agentFiles = readdirSync6(agentsDir).filter((f) => f.endsWith(".md"));
3158
+ const agentsDir = join15(repoPath, plugin.dir, "agents");
3159
+ if (existsSync14(agentsDir)) {
3160
+ const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md"));
2892
3161
  if (agentFiles.length > 0) {
2893
3162
  console.log();
2894
- console.log(` ${chalk11.bold("Agents:")}`);
3163
+ console.log(` ${chalk13.bold("Agents:")}`);
2895
3164
  for (const agent of agentFiles) {
2896
3165
  console.log(` \u2022 ${agent.replace(".md", "")}`);
2897
3166
  }
@@ -2903,39 +3172,39 @@ async function infoCommand(pluginName) {
2903
3172
  }
2904
3173
 
2905
3174
  // src/commands/lang.ts
2906
- import chalk12 from "chalk";
3175
+ import chalk14 from "chalk";
2907
3176
  async function langCommand(action, value) {
2908
3177
  const config = readConfig();
2909
3178
  if (!action || action === "show") {
2910
3179
  console.log();
2911
- console.log(chalk12.bold(` ${t("lang_title")}`));
2912
- console.log(chalk12.dim(" " + "\u2500".repeat(40)));
3180
+ console.log(chalk14.bold(` ${t("lang_title")}`));
3181
+ console.log(chalk14.dim(" " + "\u2500".repeat(40)));
2913
3182
  console.log();
2914
- console.log(` ${chalk12.dim(t("lang_current") + ":")} ${config.language}`);
3183
+ console.log(` ${chalk14.dim(t("lang_current") + ":")} ${config.language}`);
2915
3184
  console.log();
2916
- console.log(chalk12.dim(" \u652F\u6301\u7684\u8BED\u8A00: zh_CN, en_US"));
2917
- console.log(chalk12.dim(" \u7528\u6CD5: install-helper lang set <language>"));
3185
+ console.log(chalk14.dim(` ${t("lang_supported")}`));
3186
+ console.log(chalk14.dim(` ${t("lang_usage")}`));
2918
3187
  console.log();
2919
3188
  return;
2920
3189
  }
2921
3190
  if (action === "set") {
2922
3191
  if (!value) {
2923
- console.log(chalk12.red(" \u8BF7\u6307\u5B9A\u8BED\u8A00: install-helper lang set zh_CN"));
3192
+ console.log(chalk14.red(` ${t("lang_specify")}`));
2924
3193
  return;
2925
3194
  }
2926
3195
  if (value !== "zh_CN" && value !== "en_US") {
2927
- console.log(chalk12.red(` ${t("lang_invalid")}`));
3196
+ console.log(chalk14.red(` ${t("lang_invalid")}`));
2928
3197
  return;
2929
3198
  }
2930
3199
  updateConfig({ language: value });
2931
3200
  setLanguage(value);
2932
3201
  console.log();
2933
- console.log(chalk12.green(` \u2713 ${t("lang_set")} ${value}`));
3202
+ console.log(chalk14.green(` \u2713 ${t("lang_set")} ${value}`));
2934
3203
  console.log();
2935
3204
  return;
2936
3205
  }
2937
- console.log(chalk12.red(` \u672A\u77E5\u64CD\u4F5C: ${action}`));
2938
- console.log(chalk12.dim(" \u652F\u6301\u7684\u64CD\u4F5C: show, set"));
3206
+ console.log(chalk14.red(` ${t("lang_unknown_action")}: ${action}`));
3207
+ console.log(chalk14.dim(` ${t("lang_supported_actions")}`));
2939
3208
  }
2940
3209
 
2941
3210
  // src/cli.ts
@@ -2945,7 +3214,7 @@ function createCLI() {
2945
3214
  setLanguage(config.language);
2946
3215
  }
2947
3216
  const program2 = new Command();
2948
- program2.name("install-helper").description("Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.3");
3217
+ program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.30");
2949
3218
  program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
2950
3219
  await initCommand();
2951
3220
  });
@@ -2958,7 +3227,7 @@ function createCLI() {
2958
3227
  program2.command("status").description("Show installed plugins").action(async () => {
2959
3228
  await statusCommand();
2960
3229
  });
2961
- program2.command("install [names...]").description("Install plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, copilot)").option("-l, --level <level>", "Install level (project, global)", "project").option("-y, --yes", "Skip confirmation prompts").option("--list", "List all available skills by category").action(async (names, options) => {
3230
+ program2.command("install [names...]").description("Install plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, copilot)").option("-l, --level <level>", "Install level (project, global)", "project").option("-y, --yes", "Skip confirmation prompts").option("-a, --all", "Install ALL available skills").option("--list", "List all available skills by category").action(async (names, options) => {
2962
3231
  await installCommand(names, options);
2963
3232
  });
2964
3233
  program2.command("uninstall <names...>").description("Uninstall plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, copilot)").option("-l, --level <level>", "Install level (project, global)", "project").action(async (names, options) => {
@@ -2982,7 +3251,7 @@ function createCLI() {
2982
3251
  // src/index.ts
2983
3252
  process.on("uncaughtException", (error) => {
2984
3253
  if (error.name === "ExitPromptError") {
2985
- console.log("\n\u5DF2\u53D6\u6D88\u5B89\u88C5");
3254
+ console.log("\n" + t("init_cancelled"));
2986
3255
  process.exit(0);
2987
3256
  }
2988
3257
  throw error;