@cannbot-ai/install-helper 0.0.2-beta.7 → 0.0.2-beta.71

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
@@ -5,410 +5,43 @@ import { Command } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
7
  import { select as select4, Separator as Separator3 } from "@inquirer/prompts";
8
- import chalk6 from "chalk";
8
+ import chalk7 from "chalk";
9
9
 
10
10
  // src/ui/wizard.ts
11
- import { select, checkbox, Separator } from "@inquirer/prompts";
12
- import chalk2 from "chalk";
11
+ import { select, Separator } from "@inquirer/prompts";
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"
115
- }
116
- ];
117
- function findPlugin(query) {
118
- const normalized = query.toLowerCase().trim();
119
- const exactMatch = PLUGIN_REGISTRY.find(
120
- (p) => p.id === normalized || p.id === query.trim()
121
- );
122
- if (exactMatch) return exactMatch;
123
- const aliasMatch = PLUGIN_REGISTRY.find(
124
- (p) => p.aliases.some((a) => a === normalized)
125
- );
126
- if (aliasMatch) return aliasMatch;
127
- const prefixMatch = PLUGIN_REGISTRY.find((p) => p.id.startsWith(normalized));
128
- if (prefixMatch) return prefixMatch;
129
- return void 0;
130
- }
131
- function getAllPlugins() {
132
- return PLUGIN_REGISTRY;
133
- }
134
- function getPluginById(id) {
135
- return PLUGIN_REGISTRY.find((p) => p.id === id);
136
- }
137
-
138
- // src/core/detector.ts
139
- import { execa } from "execa";
140
- import { existsSync as existsSync2 } from "fs";
141
- import { homedir as homedir2 } from "os";
142
- import { join as join2 } from "path";
15
+ import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
16
+ import { join as join2, dirname as dirname2 } from "path";
17
+ import { fileURLToPath as fileURLToPath2 } from "url";
18
+ import { parse as parseYaml } from "yaml";
143
19
 
144
- // src/utils/paths.ts
145
- import { homedir } from "os";
146
- import { join } from "path";
147
- import { existsSync } from "fs";
148
- function detectTraeVariant() {
149
- 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";
153
- return "unknown";
154
- }
155
- function getConfigRoot(tool, level, base) {
156
- const home = homedir();
157
- if (level === "global") {
158
- switch (tool) {
159
- case "opencode":
160
- return join(home, ".config", "opencode");
161
- case "claude":
162
- return join(home, ".claude");
163
- case "trae": {
164
- const variant = detectTraeVariant();
165
- switch (variant) {
166
- case "plugin":
167
- return join(home, ".marscode");
168
- case "cli":
169
- return join(home, ".traecli");
170
- default:
171
- return join(home, ".trae-cn");
172
- }
173
- }
174
- case "cursor":
175
- return join(home, ".cursor");
176
- case "copilot":
177
- return join(home, ".copilot");
178
- }
179
- }
180
- const baseDir = base || process.cwd();
181
- switch (tool) {
182
- case "opencode":
183
- return join(baseDir, ".opencode");
184
- case "claude":
185
- return join(baseDir, ".claude");
186
- case "trae": {
187
- const variant = detectTraeVariant();
188
- switch (variant) {
189
- case "plugin":
190
- return join(baseDir, ".marscode");
191
- case "cli":
192
- return join(baseDir, ".traecli");
193
- default:
194
- return join(baseDir, ".trae");
195
- }
196
- }
197
- case "cursor":
198
- return join(baseDir, ".cursor");
199
- case "copilot":
200
- return join(baseDir, ".github");
201
- }
202
- }
203
- function getConfigFileName(tool) {
204
- return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
205
- }
206
- function getSkillsDir(configRoot) {
207
- return join(configRoot, "skills");
208
- }
209
- function getAgentsDir(configRoot) {
210
- return join(configRoot, "agents");
211
- }
212
- function getManifestPath(configRoot) {
213
- return join(configRoot, "cannbot-manifest.json");
214
- }
215
- function getCannbotConfigDir() {
216
- return join(homedir(), ".cannbot");
217
- }
218
- function getCannbotRepoPath() {
219
- return join(getCannbotConfigDir(), "repo");
220
- }
20
+ // src/utils/logger.ts
21
+ import chalk2 from "chalk";
22
+ import ora from "ora";
221
23
 
222
- // src/core/detector.ts
223
- async function getCommandVersion(cmd, args = ["--version"]) {
224
- try {
225
- const result = await execa(cmd, args, { timeout: 5e3 });
226
- const output = result.stdout.trim();
227
- const match = output.match(/(\d+\.\d+\.\d+)/);
228
- return match ? match[1] : output.split("\n")[0];
229
- } catch {
230
- return void 0;
231
- }
232
- }
233
- async function getCommandPath(cmd) {
234
- try {
235
- const result = await execa("sh", ["-c", `command -v ${cmd}`], { timeout: 5e3 });
236
- return result.stdout.trim();
237
- } catch {
238
- return void 0;
239
- }
240
- }
241
- async function detectOpenCode() {
242
- const path = await getCommandPath("opencode");
243
- if (!path) return void 0;
244
- const version = await getCommandVersion("opencode");
245
- return { name: "opencode", version, path };
246
- }
247
- async function detectClaude() {
248
- const path = await getCommandPath("claude");
249
- if (!path) return void 0;
250
- const version = await getCommandVersion("claude");
251
- return { name: "claude", version, path };
252
- }
253
- async function detectTrae() {
254
- const home = homedir2();
255
- const variant = detectTraeVariant();
256
- 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");
263
- } else if (variant === "unknown") {
264
- const cmdPath = await getCommandPath("trae");
265
- if (cmdPath) path = cmdPath;
266
- }
267
- if (!path) return void 0;
268
- return { name: "trae", version: variant, path };
269
- }
270
- async function detectCursor() {
271
- const path = await getCommandPath("cursor");
272
- if (path) {
273
- const version = await getCommandVersion("cursor");
274
- return { name: "cursor", version, path };
275
- }
276
- if (process.platform === "darwin") {
277
- const appPath = "/Applications/Cursor.app";
278
- if (existsSync2(appPath)) {
279
- return { name: "cursor", path: appPath };
280
- }
281
- }
282
- return void 0;
283
- }
284
- async function detectCopilot() {
285
- const path = await getCommandPath("gh");
286
- if (!path) return void 0;
287
- try {
288
- await execa("gh", ["extension", "list"], { timeout: 5e3 });
289
- return { name: "copilot", path };
290
- } catch {
291
- return void 0;
292
- }
293
- }
294
- async function detectTools() {
295
- const detectors = [
296
- detectOpenCode,
297
- detectClaude,
298
- detectTrae,
299
- detectCursor,
300
- detectCopilot
301
- ];
302
- const results = await Promise.allSettled(detectors.map((d) => d()));
303
- const tools = [];
304
- for (const result of results) {
305
- if (result.status === "fulfilled" && result.value) {
306
- tools.push(result.value);
307
- }
308
- }
309
- return tools;
310
- }
311
- function getToolDisplayName(tool) {
312
- const names = {
313
- opencode: "OpenCode",
314
- claude: "Claude Code",
315
- trae: "Trae",
316
- cursor: "Cursor",
317
- copilot: "GitHub Copilot"
318
- };
319
- return names[tool];
320
- }
321
- function getAllTools() {
322
- return ["opencode", "claude", "trae", "cursor", "copilot"];
323
- }
24
+ // src/utils/i18n.ts
25
+ import { readFileSync, existsSync } from "fs";
26
+ import { join, dirname } from "path";
27
+ import { fileURLToPath } from "url";
324
28
 
325
- // src/core/manifest.ts
326
- import { existsSync as existsSync3, readFileSync } from "fs";
327
- import { join as join3 } from "path";
328
- function readManifest(configRoot) {
329
- const manifestPath = getManifestPath(configRoot);
330
- if (existsSync3(manifestPath)) {
331
- try {
332
- const content = readFileSync(manifestPath, "utf-8");
333
- return JSON.parse(content);
334
- } catch {
335
- }
336
- }
337
- return null;
338
- }
339
- function readAllManifests(configRoot) {
340
- const manifests = [];
341
- const plugins = getAllPlugins();
342
- const standardPath = getManifestPath(configRoot);
343
- if (existsSync3(standardPath)) {
344
- try {
345
- const content = readFileSync(standardPath, "utf-8");
346
- manifests.push(JSON.parse(content));
347
- } catch {
348
- }
349
- }
350
- for (const plugin of plugins) {
351
- const pluginManifestPath = join3(configRoot, `${plugin.id}-manifest.json`);
352
- if (existsSync3(pluginManifestPath)) {
353
- try {
354
- const content = readFileSync(pluginManifestPath, "utf-8");
355
- manifests.push(JSON.parse(content));
356
- } catch {
357
- }
358
- }
359
- }
360
- return manifests;
361
- }
362
- function scanInstalled() {
363
- const plugins = getAllPlugins();
364
- const installed = [];
365
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
366
- const levels = ["project", "global"];
367
- for (const tool of tools) {
368
- for (const level of levels) {
369
- const configRoot = getConfigRoot(tool, level);
370
- const manifests = readAllManifests(configRoot);
371
- for (const manifest of manifests) {
372
- const plugin = plugins.find((p) => p.id === manifest.team);
373
- if (!plugin) continue;
374
- const alreadyAdded = installed.some(
375
- (p) => p.id === plugin.id && p.tool === tool && p.level === level
376
- );
377
- if (alreadyAdded) continue;
378
- installed.push({
379
- id: plugin.id,
380
- displayName: plugin.displayName,
381
- tool,
382
- level,
383
- skillsCount: manifest.installed_skills?.length || 0,
384
- agentsCount: manifest.installed_agents?.length || 0,
385
- installTime: manifest.install_time,
386
- configRoot
387
- });
388
- }
389
- }
390
- }
391
- return installed;
392
- }
393
-
394
- // src/utils/i18n.ts
395
- var zh_CN = {
29
+ // src/locales/zh_CN.json
30
+ var zh_CN_default = {
396
31
  wizard_title: "CANNBot Install Helper",
397
32
  wizard_detect: "\u6B63\u5728\u68C0\u6D4B\u5DF2\u5B89\u88C5\u7684 AI \u7F16\u7A0B\u5DE5\u5177...",
398
33
  wizard_select_tool: "\u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177",
399
34
  wizard_select_level: "\u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E",
400
- wizard_select_plugins: "\u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6",
35
+ wizard_select_plugin: "\u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6",
401
36
  wizard_confirm: "\u786E\u8BA4\u5B89\u88C5",
402
37
  wizard_detected_tool: "\u68C0\u6D4B\u5230",
403
38
  wizard_confirm_tool: "\u4F7F\u7528",
404
39
  wizard_last_used: "\u4E0A\u6B21\u4F7F\u7528",
405
40
  wizard_already_installed: "\u5DF2\u5B89\u88C5",
406
- install_cloning: "\u6B63\u5728\u514B\u9686 cannbot-skills \u4ED3\u5E93...",
407
41
  install_repo_ready: "\u4ED3\u5E93\u5C31\u7EEA",
408
42
  install_progress: "\u6B63\u5728\u5B89\u88C5",
409
43
  install_done: "\u5B89\u88C5\u5B8C\u6210",
410
44
  install_success: "\u6210\u529F",
411
- install_failed: "\u5931\u8D25",
412
45
  install_already_installed: "\u5DF2\u5B89\u88C5",
413
46
  install_reinstall_confirm: "\u662F\u5426\u91CD\u65B0\u5B89\u88C5\uFF08\u8986\u76D6\uFF09",
414
47
  install_skip: "\u8DF3\u8FC7",
@@ -418,7 +51,6 @@ var zh_CN = {
418
51
  install_enhanced_more: "\u66F4\u591A",
419
52
  install_enhanced_check: "\u68C0\u67E5",
420
53
  install_enhanced_docs: "\u6587\u6863",
421
- update_title: "\u66F4\u65B0\u5DF2\u5B89\u88C5\u7684 Skills",
422
54
  update_updating: "\u6B63\u5728\u66F4\u65B0",
423
55
  update_done: "\u66F4\u65B0\u5B8C\u6210",
424
56
  update_no_plugins: "\u6682\u65E0\u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6",
@@ -428,11 +60,8 @@ var zh_CN = {
428
60
  doctor_links: "\u94FE\u63A5\u5B8C\u6574\u6027",
429
61
  doctor_config: "\u914D\u7F6E\u6587\u4EF6",
430
62
  doctor_result: "\u7ED3\u679C",
431
- doctor_fix_title: "\u81EA\u52A8\u4FEE\u590D",
432
63
  doctor_fix_cleaning: "\u6E05\u7406\u5931\u6548\u94FE\u63A5",
433
64
  doctor_fix_rebuilding: "\u91CD\u5EFA\u76EE\u5F55",
434
- doctor_fix_done: "\u4FEE\u590D\u5B8C\u6210",
435
- info_title: "\u63D2\u4EF6\u8BE6\u60C5",
436
65
  info_description: "\u63CF\u8FF0",
437
66
  info_skills: "Skills",
438
67
  info_agents: "Agents",
@@ -447,18 +76,29 @@ var zh_CN = {
447
76
  list_id: "\u5E8F\u53F7",
448
77
  list_name: "\u573A\u666F\u540D",
449
78
  list_status: "\u72B6\u6001",
450
- list_skills: "Skills",
451
- list_agents: "Agents",
79
+ list_description: "\u63CF\u8FF0",
452
80
  status_installed: "\u5DF2\u88C5",
453
81
  status_not_installed: "\u672A\u88C5",
454
82
  error_tool_not_found: "\u672A\u68C0\u6D4B\u5230\u4EFB\u4F55 AI \u7F16\u7A0B\u5DE5\u5177",
455
83
  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",
84
+ error_invalid_tool: "\u6682\u4E0D\u652F\u6301\u7684 AI \u5DE5\u5177: {tool}\n\u652F\u6301\u7684\u5DE5\u5177: {tools}",
85
+ error_invalid_level: "\u6682\u4E0D\u652F\u6301\u7684\u5B89\u88C5\u7EA7\u522B: {level}\n\u652F\u6301\u7684\u7EA7\u522B: {levels}",
86
+ error_skill_not_found: "\u672A\u5728\u6CE8\u518C\u8868\u4E2D\u627E\u5230 Skill",
87
+ error_source_not_found: "\u672A\u627E\u5230\u6E90\u6587\u4EF6: {path}",
88
+ error_script_not_found: "\u672A\u627E\u5230\u5B89\u88C5\u811A\u672C: {path}",
89
+ error_unknown: "\u672A\u77E5\u9519\u8BEF",
90
+ error_not_installed: "\u672A\u5B89\u88C5",
91
+ error_no_shell: "\u672A\u627E\u5230 bash \u6216 sh\uFF0C\u65E0\u6CD5\u6267\u884C\u5B89\u88C5\u811A\u672C\u3002\u8BF7\u5B89\u88C5 Git Bash \u6216 WSL",
92
+ error_symlink_windows: "\u521B\u5EFA\u7B26\u53F7\u94FE\u63A5\u5931\u8D25\uFF0C\u8BF7\u4EE5\u7BA1\u7406\u5458\u8EAB\u4EFD\u8FD0\u884C\u6216\u5F00\u542F Windows \u5F00\u53D1\u8005\u6A21\u5F0F",
459
93
  skill_select_category: "\u9009\u62E9 Skill \u7C7B\u522B",
460
94
  skill_select_items: "\u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills",
461
95
  skill_already_installed: "\u5DF2\u5B89\u88C5",
96
+ skill_select_all: "\u5168\u9009\u6240\u6709 Skills\uFF08\u5171 {count} \u4E2A\uFF09",
97
+ skill_select_all_category: "\u5168\u9009\u5F53\u524D\u7C7B\u522B",
98
+ skill_continue_or_install: "\u5DF2\u9009 {count} \u4E2A Skills\uFF0C\u8BF7\u9009\u62E9\u64CD\u4F5C",
99
+ skill_continue_select: "\u7EE7\u7EED\u9009\u62E9\u5176\u4ED6\u7C7B\u522B",
100
+ skill_confirm_install_count: "\u786E\u8BA4\u5B89\u88C5\uFF08\u5DF2\u9009 {count} \u4E2A Skills\uFF09",
101
+ skill_all_install_progress: "\u6B63\u5728\u5B89\u88C5\u5168\u90E8 {count} \u4E2A Skills...",
462
102
  skill_install_progress: "\u6B63\u5728\u5B89\u88C5",
463
103
  skill_install_done: "Skill \u5B89\u88C5\u5B8C\u6210",
464
104
  skill_uninstall_done: "Skill \u5378\u8F7D\u5B8C\u6210",
@@ -489,7 +129,7 @@ var zh_CN = {
489
129
  uninstall_no_record: "\u672A\u627E\u5230\u5B89\u88C5\u8BB0\u5F55\uFF0C\u53EF\u80FD\u672A\u5B89\u88C5\u6216\u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F",
490
130
  uninstall_no_record_hint: "\u5982\u9700\u5F3A\u5236\u6E05\u7406\uFF0C\u8BF7\u624B\u52A8\u5220\u9664 .opencode/ \u6216 .claude/ \u76EE\u5F55\u4E2D\u7684\u76F8\u5173\u6587\u4EF6",
491
131
  uninstall_in_progress: "\u6B63\u5728\u5378\u8F7D",
492
- uninstall_removed: "\u5DF2\u5378\u8F7D",
132
+ uninstall_summary_format: "{name}\uFF08\u79FB\u9664 {files} \u4E2A\u6587\u4EF6\uFF0C{dirs} \u4E2A\u76EE\u5F55\uFF09",
493
133
  uninstall_files_gone: "\u7684\u6587\u4EF6\u5DF2\u4E0D\u5B58\u5728\uFF0C\u5DF2\u6E05\u7406\u5B89\u88C5\u8BB0\u5F55",
494
134
  uninstall_skills_in_progress: "\u6B63\u5728\u5378\u8F7D",
495
135
  init_pkg_dir_warn: "\u5F53\u524D\u76EE\u5F55\u770B\u8D77\u6765\u662F install-helper \u5305\u76EE\u5F55",
@@ -508,25 +148,89 @@ var zh_CN = {
508
148
  backup_only_uninstall: "\u4EC5\u5378\u8F7D\uFF08\u4E0D\u6062\u590D\uFF09",
509
149
  backup_select_action: "\u8BF7\u9009\u62E9\u64CD\u4F5C",
510
150
  backup_restore_success: "\u5DF2\u6062\u590D\u914D\u7F6E",
511
- backup_created: "\u5DF2\u521B\u5EFA\u5907\u4EFD"
151
+ backup_created: "\u5DF2\u521B\u5EFA\u5907\u4EFD",
152
+ backup_failed: "\u5907\u4EFD\u521B\u5EFA\u5931\u8D25\uFF0C\u5B89\u88C5\u5DF2\u4E2D\u6B62\u4EE5\u4FDD\u62A4\u73B0\u6709\u914D\u7F6E",
153
+ loading_skills_list: "\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...",
154
+ loading_skills_list_complete: "Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210",
155
+ loading_plugin_data: "\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...",
156
+ loading_plugin_data_complete: "\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210",
157
+ loading_skills_list_failed: "Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E",
158
+ repo_pull_failed: "\u65E0\u6CD5\u62C9\u53D6\u6700\u65B0\u5185\u5BB9\uFF0C\u4F7F\u7528\u7F13\u5B58\u6570\u636E",
159
+ repo_cloning: "\u9996\u6B21\u4F7F\u7528\uFF0C\u6B63\u5728\u514B\u9686 cannbot-skills \u4ED3\u5E93...",
160
+ config_corrupted: "\u914D\u7F6E\u6587\u4EF6\u5DF2\u635F\u574F\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u914D\u7F6E",
161
+ record_corrupted: "\u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F\uFF1A{file}",
162
+ skill_record_corrupted: "Skill \u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F",
163
+ plugin_yaml_error: "\u63D2\u4EF6\u914D\u7F6E\u89E3\u6790\u5931\u8D25\uFF1A{file}",
164
+ record_write_failed: "\u5B89\u88C5\u8BB0\u5F55\u5199\u5165\u5931\u8D25\uFF0C\u5378\u8F7D\u65F6\u53EF\u80FD\u65E0\u6CD5\u81EA\u52A8\u6E05\u7406",
165
+ install_config_failed: "\u914D\u7F6E\u6587\u4EF6\u5B89\u88C5\u5931\u8D25: {error}",
166
+ install_clone_failed: "git clone \u5931\u8D25: {url}\uFF0C\u8BF7\u624B\u52A8\u514B\u9686: git clone {url} {dir}",
167
+ install_link_config_failed: "\u94FE\u63A5 {name} \u5230\u914D\u7F6E\u76EE\u5F55\u5931\u8D25",
168
+ install_link_project_failed: "\u94FE\u63A5 {name} \u5230\u9879\u76EE\u76EE\u5F55\u5931\u8D25",
169
+ install_link_workflows_failed: "\u94FE\u63A5 workflows \u5230\u914D\u7F6E\u76EE\u5F55\u5931\u8D25",
170
+ install_skill_source_not_found: "Skill \u6E90\u76EE\u5F55\u672A\u627E\u5230: {dir}",
171
+ install_skill_not_found: "Skill \u672A\u627E\u5230: {name} (\u5728 {dir})",
172
+ install_skill_failed: "Skill \u5B89\u88C5\u5931\u8D25: {name}",
173
+ install_agent_not_found: "Agent \u672A\u627E\u5230: {name}",
174
+ install_agent_failed: "Agent \u5B89\u88C5\u5931\u8D25: {name}",
175
+ install_no_skill_selected: "\u672A\u9009\u62E9\u4EFB\u4F55 Skill",
176
+ install_to: "\u5B89\u88C5\u5230",
177
+ start_to_use: "\u542F\u52A8 {tool} \u5373\u53EF\u4F7F\u7528",
178
+ wizard_step_1_title: "[1/4] \u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177",
179
+ wizard_step_2_title: "[2/4] \u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E",
180
+ wizard_step_3_title: "[3/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6",
181
+ wizard_step_4_title: "[4/4] \u786E\u8BA4\u5B89\u88C5",
182
+ wizard_step_3_skill_title: "[3/4] \u9009\u62E9 Skill \u7C7B\u522B",
183
+ wizard_step_4_skill_title: "[4/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills \u2014 {category}",
184
+ install_level_project: "\u5B89\u88C5\u5230\u5F53\u524D\u76EE\u5F55\uFF08\u63A8\u8350\uFF0C\u4EC5\u5F53\u524D\u9879\u76EE\u53EF\u7528\uFF09",
185
+ install_level_global: "\u5B89\u88C5\u5230\u5168\u5C40\uFF08\u6240\u6709\u9879\u76EE\u5171\u4EAB\uFF09",
186
+ wizard_select_mode_title: "\u9009\u62E9\u5B89\u88C5\u7C7B\u578B",
187
+ skill_count_format: "{count} \u4E2A Skills",
188
+ result_success_format: "{count} \u6210\u529F",
189
+ result_failed_format: "{count} \u5931\u8D25",
190
+ uninstall_remove_label: "\u79FB\u9664",
191
+ uninstall_clean_empty_dir: "\u6E05\u7406\u7A7A\u76EE\u5F55",
192
+ uninstall_skill_not_installed: "\u672A\u5B89\u88C5",
193
+ available_plugins: "\u53EF\u7528\u63D2\u4EF6",
194
+ available_skills: "\u53EF\u7528 Skills",
195
+ skill_total_count: "\u5171 {count} \u4E2A Skills",
196
+ wizard_confirm_install_format: "\u5373\u5C06\u5B89\u88C5 {count} \u4E2A\u63D2\u4EF6\u5230 {tool}\uFF08{level} \u7EA7\u522B\uFF09\uFF1A",
197
+ wizard_install_path: "\u5B89\u88C5\u8DEF\u5F84",
198
+ update_install_hint: "\u4F7F\u7528 {cmd} \u5B89\u88C5\u63D2\u4EF6",
199
+ hint_select_move: "\u79FB\u52A8",
200
+ hint_select_space: "\u7A7A\u683C",
201
+ hint_select_check: "\u52FE\u9009",
202
+ hint_select_confirm: "\u786E\u8BA4",
203
+ hint_checkbox_suffix: "\u4E0D\u9009\u6309\u56DE\u8F66\u53EF\u8FD4\u56DE",
204
+ doctor_not_installed: "\u672A\u5B89\u88C5",
205
+ doctor_links_valid: "{count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548",
206
+ doctor_broken_links: "{count} \u4E2A\u5931\u6548\u94FE\u63A5",
207
+ doctor_fixed: "\u5DF2\u6E05\u7406 {count} \u4E2A\u5931\u6548\u94FE\u63A5",
208
+ doctor_not_exist: "\u4E0D\u5B58\u5728",
209
+ doctor_created: "\u5DF2\u521B\u5EFA {path}",
210
+ doctor_config_exists: "\u5B58\u5728",
211
+ doctor_config_not_exist: "\u4E0D\u5B58\u5728",
212
+ doctor_result_with_fix: "{warnings} \u4E2A\u8B66\u544A, {fixes} \u9879\u5DF2\u4FEE\u590D",
213
+ doctor_result_without_fix: "{warnings} \u4E2A\u8B66\u544A",
214
+ count_suffix: "\u4E2A",
215
+ try_prompt: "\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50"
512
216
  };
513
- var en_US = {
217
+
218
+ // src/locales/en_US.json
219
+ var en_US_default = {
514
220
  wizard_title: "CANNBot Install Helper",
515
221
  wizard_detect: "Detecting installed AI coding tools...",
516
222
  wizard_select_tool: "Select AI coding tool",
517
223
  wizard_select_level: "Select install level",
518
- wizard_select_plugins: "Select scenarios to install",
224
+ wizard_select_plugin: "Select a scenario to install",
519
225
  wizard_confirm: "Confirm installation",
520
226
  wizard_detected_tool: "Detected",
521
227
  wizard_confirm_tool: "Confirm using",
522
228
  wizard_last_used: "last used",
523
229
  wizard_already_installed: "installed",
524
- install_cloning: "Cloning cannbot-skills repository...",
525
230
  install_repo_ready: "Repository ready",
526
231
  install_progress: "Installing",
527
232
  install_done: "Installation complete",
528
233
  install_success: "success",
529
- install_failed: "failed",
530
234
  install_already_installed: "already installed",
531
235
  install_reinstall_confirm: "Reinstall (overwrite)",
532
236
  install_skip: "skipped",
@@ -536,7 +240,6 @@ var en_US = {
536
240
  install_enhanced_more: "More",
537
241
  install_enhanced_check: "Check",
538
242
  install_enhanced_docs: "Docs",
539
- update_title: "Update installed Skills",
540
243
  update_updating: "Updating",
541
244
  update_done: "Update complete",
542
245
  update_no_plugins: "No installed plugins",
@@ -546,11 +249,8 @@ var en_US = {
546
249
  doctor_links: "Link Integrity",
547
250
  doctor_config: "Configuration",
548
251
  doctor_result: "Result",
549
- doctor_fix_title: "Auto-fix",
550
252
  doctor_fix_cleaning: "Cleaning broken links",
551
253
  doctor_fix_rebuilding: "Rebuilding directories",
552
- doctor_fix_done: "Fix complete",
553
- info_title: "Plugin Details",
554
254
  info_description: "Description",
555
255
  info_skills: "Skills",
556
256
  info_agents: "Agents",
@@ -565,18 +265,29 @@ var en_US = {
565
265
  list_id: "#",
566
266
  list_name: "Name",
567
267
  list_status: "Status",
568
- list_skills: "Skills",
569
- list_agents: "Agents",
268
+ list_description: "Description",
570
269
  status_installed: "installed",
571
270
  status_not_installed: "not installed",
572
271
  error_tool_not_found: "No AI coding tool detected",
573
272
  error_plugin_not_found: "Plugin not found",
574
- error_repo_not_found: "Repository not found",
575
- quick_start: "Launch command",
576
- quick_start_hint: "Try: help me develop an Abs operator",
273
+ error_invalid_tool: "Unsupported AI tool: {tool}\nSupported tools: {tools}",
274
+ error_invalid_level: "Unsupported install level: {level}\nSupported levels: {levels}",
275
+ error_skill_not_found: "Skill not found in registry",
276
+ error_source_not_found: "Source not found: {path}",
277
+ error_script_not_found: "Script not found: {path}",
278
+ error_unknown: "Unknown error",
279
+ error_not_installed: "Not installed",
280
+ error_no_shell: "bash or sh not found, cannot execute install script. Please install Git Bash or WSL",
281
+ error_symlink_windows: "Failed to create symlink, please run as administrator or enable Windows Developer Mode",
577
282
  skill_select_category: "Select Skill category",
578
283
  skill_select_items: "Select Skills to install (space to toggle, enter to confirm)",
579
284
  skill_already_installed: "installed",
285
+ skill_select_all: "Select ALL Skills ({count} total)",
286
+ skill_select_all_category: "Select all in this category",
287
+ skill_continue_or_install: "{count} Skills selected, choose an action",
288
+ skill_continue_select: "Continue selecting from other categories",
289
+ skill_confirm_install_count: "Install now ({count} Skills selected)",
290
+ skill_all_install_progress: "Installing all {count} Skills...",
580
291
  skill_install_progress: "Installing",
581
292
  skill_install_done: "Skill installation complete",
582
293
  skill_uninstall_done: "Skill uninstallation complete",
@@ -607,7 +318,7 @@ var en_US = {
607
318
  uninstall_no_record: "No install record found, may not be installed or record is corrupted",
608
319
  uninstall_no_record_hint: "For manual cleanup, delete related files in .opencode/ or .claude/ directory",
609
320
  uninstall_in_progress: "Uninstalling",
610
- uninstall_removed: "Uninstalled",
321
+ uninstall_summary_format: "{name} (removed {files} files, {dirs} directories)",
611
322
  uninstall_files_gone: " files no longer exist, install record cleaned",
612
323
  uninstall_skills_in_progress: "Uninstalling",
613
324
  init_pkg_dir_warn: "Current directory appears to be the install-helper package directory",
@@ -626,32 +337,200 @@ var en_US = {
626
337
  backup_only_uninstall: "Uninstall only (no restore)",
627
338
  backup_select_action: "Please select an action",
628
339
  backup_restore_success: "Configuration restored",
629
- backup_created: "Backup created"
340
+ backup_created: "Backup created",
341
+ backup_failed: "Backup creation failed, installation aborted to protect existing config",
342
+ loading_skills_list: "Loading Skills list...",
343
+ loading_skills_list_complete: "Skills list loaded",
344
+ loading_plugin_data: "Loading plugin data...",
345
+ loading_plugin_data_complete: "Plugin data loaded",
346
+ loading_skills_list_failed: "Failed to load Skills list, using built-in data",
347
+ repo_pull_failed: "Failed to pull latest updates, using cached data",
348
+ repo_cloning: "First-time setup: cloning cannbot-skills repository...",
349
+ config_corrupted: "Config file corrupted, using defaults",
350
+ record_corrupted: "Install record corrupted: {file}",
351
+ skill_record_corrupted: "Skill install record corrupted",
352
+ plugin_yaml_error: "Plugin config parse error: {file}",
353
+ record_write_failed: "Install record write failed, uninstall may not work properly",
354
+ install_config_failed: "Failed to install config file: {error}",
355
+ install_clone_failed: "git clone failed: {url}, clone manually: git clone {url} {dir}",
356
+ install_link_config_failed: "Failed to link {name} to config directory",
357
+ install_link_project_failed: "Failed to link {name} to project directory",
358
+ install_link_workflows_failed: "Failed to link workflows to config directory",
359
+ install_skill_source_not_found: "Skill source not found: {dir}",
360
+ install_skill_not_found: "Skill not found: {name} in {dir}",
361
+ install_skill_failed: "Failed to install skill: {name}",
362
+ install_agent_not_found: "Agent not found: {name}",
363
+ install_agent_failed: "Failed to install agent: {name}",
364
+ install_no_skill_selected: "No Skill selected",
365
+ install_to: "Install to",
366
+ start_to_use: "Start {tool} to use",
367
+ wizard_step_1_title: "[1/4] Select AI coding tool",
368
+ wizard_step_2_title: "[2/4] Select installation location",
369
+ wizard_step_3_title: "[3/4] Select plugins to install",
370
+ wizard_step_4_title: "[4/4] Confirm installation",
371
+ wizard_step_3_skill_title: "[3/4] Select Skill category",
372
+ wizard_step_4_skill_title: "[4/4] Select Skills to install \u2014 {category}",
373
+ install_level_project: "Install to current directory (recommended, current project only)",
374
+ install_level_global: "Install globally (all projects)",
375
+ wizard_select_mode_title: "Select installation type",
376
+ skill_count_format: "{count} Skills",
377
+ result_success_format: "{count} succeeded",
378
+ result_failed_format: "{count} failed",
379
+ uninstall_remove_label: "Removed",
380
+ uninstall_clean_empty_dir: "Cleaned empty directory",
381
+ uninstall_skill_not_installed: "Not installed",
382
+ available_plugins: "Available plugins",
383
+ available_skills: "Available Skills",
384
+ skill_total_count: "Total {count} Skills",
385
+ wizard_confirm_install_format: "About to install {count} plugins to {tool} ({level} level):",
386
+ wizard_install_path: "Install path",
387
+ update_install_hint: "Use {cmd} to install plugins",
388
+ hint_select_move: "Navigate",
389
+ hint_select_space: "Space",
390
+ hint_select_check: "Toggle",
391
+ hint_select_confirm: "Confirm",
392
+ hint_checkbox_suffix: "Press enter without selection to go back",
393
+ doctor_not_installed: "Not installed",
394
+ doctor_links_valid: "{count} symlinks all valid",
395
+ doctor_broken_links: "{count} broken links",
396
+ doctor_fixed: "Cleaned {count} broken links",
397
+ doctor_not_exist: "Does not exist",
398
+ doctor_created: "Created {path}",
399
+ doctor_config_exists: "Exists",
400
+ doctor_config_not_exist: "Does not exist",
401
+ doctor_result_with_fix: "{warnings} warnings, {fixes} fixed",
402
+ doctor_result_without_fix: "{warnings} warnings",
403
+ count_suffix: "",
404
+ try_prompt: "help me develop an Abs operator"
405
+ };
406
+
407
+ // src/utils/i18n.ts
408
+ var __filename = fileURLToPath(import.meta.url);
409
+ var __dirname = dirname(__filename);
410
+ var EMBEDDED_LOCALES = {
411
+ zh_CN: zh_CN_default,
412
+ en_US: en_US_default
630
413
  };
414
+ function loadLocale(locale) {
415
+ const embedded = EMBEDDED_LOCALES[locale];
416
+ if (embedded) return embedded;
417
+ const localePath = join(__dirname, "locales", `${locale}.json`);
418
+ if (existsSync(localePath)) {
419
+ try {
420
+ const content = readFileSync(localePath, "utf-8");
421
+ return JSON.parse(content);
422
+ } catch {
423
+ }
424
+ }
425
+ const fallbackPath = join(__dirname, "locales", "zh_CN.json");
426
+ if (existsSync(fallbackPath)) {
427
+ try {
428
+ const content = readFileSync(fallbackPath, "utf-8");
429
+ return JSON.parse(content);
430
+ } catch {
431
+ }
432
+ }
433
+ return EMBEDDED_LOCALES.zh_CN || {};
434
+ }
631
435
  var currentLang = "zh_CN";
436
+ var messages = loadLocale("zh_CN");
632
437
  function setLanguage(lang) {
633
438
  currentLang = lang;
439
+ messages = loadLocale(lang);
634
440
  }
635
441
  function t(key) {
636
- return currentLang === "zh_CN" ? zh_CN[key] : en_US[key];
442
+ return messages[key] !== void 0 ? messages[key] : key;
637
443
  }
638
444
 
639
- // src/utils/logger.ts
445
+ // src/ui/theme.ts
640
446
  import chalk from "chalk";
641
- import ora from "ora";
447
+ var selectTheme = {
448
+ helpMode: "never"
449
+ };
450
+ function isCJK(code) {
451
+ 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;
452
+ }
453
+ function isEmoji(code) {
454
+ return code >= 127744 && code <= 129791 || code >= 9728 && code <= 10175;
455
+ }
456
+ function getDisplayWidth(str) {
457
+ let width = 0;
458
+ for (const ch of str) {
459
+ const code = ch.codePointAt(0);
460
+ if (isCJK(code) || isEmoji(code)) {
461
+ width += 2;
462
+ } else {
463
+ width += 1;
464
+ }
465
+ }
466
+ return width;
467
+ }
468
+ function wrapDescription(text) {
469
+ const prefix = " \u{1F4A1} ";
470
+ const indent = " ";
471
+ const cols = process.stdout.columns || 120;
472
+ const maxLen = Math.max(20, cols - getDisplayWidth(prefix) - 2);
473
+ const tokens = [];
474
+ let i = 0;
475
+ while (i < text.length) {
476
+ const code = text[i].codePointAt(0);
477
+ if (isCJK(code)) {
478
+ tokens.push({ text: text[i], width: 2 });
479
+ i++;
480
+ } else {
481
+ let j = i;
482
+ while (j < text.length) {
483
+ const c = text[j].codePointAt(0);
484
+ if (isCJK(c)) break;
485
+ j++;
486
+ }
487
+ const word = text.slice(i, j);
488
+ tokens.push({ text: word, width: getDisplayWidth(word) });
489
+ i = j;
490
+ }
491
+ }
492
+ const lines = [];
493
+ let line = "";
494
+ let lineWidth = 0;
495
+ for (const token of tokens) {
496
+ if (lineWidth + token.width > maxLen && line) {
497
+ lines.push(line.trimEnd());
498
+ line = token.text;
499
+ lineWidth = token.width;
500
+ } else {
501
+ line += token.text;
502
+ lineWidth += token.width;
503
+ }
504
+ }
505
+ if (line) lines.push(line.trimEnd());
506
+ return lines.map((l, i2) => chalk.cyan(i2 === 0 ? prefix + l : indent + l)).join("\n");
507
+ }
508
+ var checkboxTheme = {
509
+ icon: {
510
+ checked: "\x1B[32m\u2611\x1B[22m",
511
+ unchecked: "\u2610",
512
+ cursor: "\u276F"
513
+ },
514
+ helpMode: "never",
515
+ style: {
516
+ description: (text) => wrapDescription(text)
517
+ }
518
+ };
519
+
520
+ // src/utils/logger.ts
642
521
  var logger = {
643
- success: (msg) => console.log(chalk.green("\u2713") + " " + msg),
644
- error: (msg) => console.log(chalk.red("\u2717") + " " + msg),
645
- warn: (msg) => console.log(chalk.yellow("\u26A0") + " " + msg),
646
- info: (msg) => console.log(chalk.cyan("\u2192") + " " + msg),
647
- step: (msg) => console.log(chalk.dim(msg)),
522
+ success: (msg) => console.log(chalk2.green("\u2713") + " " + msg),
523
+ error: (msg) => console.log(chalk2.red("\u2717") + " " + msg),
524
+ warn: (msg) => console.log(chalk2.yellow("\u26A0") + " " + msg),
525
+ info: (msg) => console.log(chalk2.cyan("\u2192") + " " + msg),
526
+ step: (msg) => console.log(chalk2.dim(msg)),
648
527
  blank: () => console.log()
649
528
  };
650
529
  function createSpinner(text) {
651
530
  return ora({ text, color: "cyan" });
652
531
  }
653
532
  function printBanner(subtitle) {
654
- console.log(chalk.cyan(`
533
+ console.log(chalk2.cyan(`
655
534
  ____ _ _ _ _ _ ____ _
656
535
  / ___| / \\ | \\ | | \\ | | __ ) ___ | |_
657
536
  | | / _ \\ | \\| | \\| | _ \\ / _ \\| __|
@@ -659,66 +538,992 @@ function printBanner(subtitle) {
659
538
  \\____/_/ \\_\\_| \\_|_| \\_|____/ \\___/ \\__|
660
539
  `));
661
540
  if (subtitle) {
662
- console.log(chalk.bold(` ${subtitle}`));
541
+ console.log(chalk2.bold(` ${subtitle}`));
663
542
  console.log();
664
543
  }
665
544
  }
666
- function getDisplayWidth(str) {
667
- let width = 0;
668
- for (const ch of str) {
669
- const code = ch.codePointAt(0);
670
- if (code >= 11904 && code <= 40959 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65103 || code >= 65280 && code <= 65519 || code >= 13312 && code <= 19903 || code >= 131072 && code <= 173791 || code >= 173824 && code <= 177983 || code >= 177984 && code <= 178207 || code >= 178208 && code <= 183983) {
671
- width += 2;
672
- } else {
673
- width += 1;
545
+ function printBoxTitle(title, width = 65) {
546
+ const border = "\u2550".repeat(width);
547
+ const displayWidth = getDisplayWidth(title);
548
+ const totalPadding = width - displayWidth;
549
+ const leftPadding = Math.floor(totalPadding / 2);
550
+ const rightPadding = totalPadding - leftPadding;
551
+ const paddedTitle = " ".repeat(Math.max(0, leftPadding)) + title + " ".repeat(Math.max(0, rightPadding));
552
+ console.log();
553
+ console.log(chalk2.cyan.bold(` \u2554${border}\u2557`));
554
+ console.log(chalk2.cyan.bold(` \u2551`) + chalk2.cyan.bold(paddedTitle) + chalk2.cyan.bold(`\u2551`));
555
+ console.log(chalk2.cyan.bold(` \u255A${border}\u255D`));
556
+ console.log();
557
+ }
558
+ function showOperationHints(isCheckbox = false) {
559
+ if (isCheckbox) {
560
+ 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
561
+ `);
562
+ } else {
563
+ console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}
564
+ `);
565
+ }
566
+ }
567
+
568
+ // src/utils/fs-helpers.ts
569
+ import { lstatSync, statSync } from "fs";
570
+ function isSymlink(path) {
571
+ try {
572
+ return lstatSync(path).isSymbolicLink();
573
+ } catch {
574
+ return false;
575
+ }
576
+ }
577
+ function isDirectory(path) {
578
+ try {
579
+ return statSync(path).isDirectory();
580
+ } catch {
581
+ return false;
582
+ }
583
+ }
584
+
585
+ // src/embedded-plugins.json
586
+ var embedded_plugins_default = [
587
+ {
588
+ id: "ops-direct-invoke",
589
+ dir: "plugins-official/ops-direct-invoke",
590
+ displayName: "AscendC Kernel \u76F4\u8C03",
591
+ script: "init.sh",
592
+ aliases: [
593
+ "ops-direct",
594
+ "ascendc-direct",
595
+ "direct",
596
+ "kernel"
597
+ ],
598
+ skills: 16,
599
+ agents: 3,
600
+ description: "Ascend C \u76F4\u8C03\u5168\u6D41\u7A0B\uFF1A\u73AF\u5883\u2192\u8BBE\u8BA1\u2192\u5F00\u53D1\u2192\u5BA1\u67E5\u2192\u6027\u80FD\u9A8C\u6536",
601
+ version: "1.2.2",
602
+ configFile: "AGENTS.md",
603
+ configRootConfigLink: false,
604
+ installSkills: [
605
+ {
606
+ dir: "ops",
607
+ skills: [
608
+ "ascendc-tiling-design",
609
+ "npu-arch",
610
+ "ascendc-api-best-practices",
611
+ "ascendc-regbase-best-practice",
612
+ "ascendc-blaze-best-practice",
613
+ "ops-precision-standard",
614
+ "ascendc-docs-search",
615
+ "ascendc-env-check",
616
+ "ascendc-precision-debug",
617
+ "ops-profiling",
618
+ "ascendc-direct-invoke-template",
619
+ "torch-ascendc-op-extension",
620
+ "ascendc-runtime-debug",
621
+ "ascendc-crash-debug",
622
+ "ascendc-code-review"
623
+ ]
624
+ },
625
+ {
626
+ dir: "infra",
627
+ skills: [
628
+ "gitcode-toolkit",
629
+ "gitcode-pr-handler",
630
+ "gitcode-issue-gen",
631
+ "gitcode-issue-handler"
632
+ ]
633
+ }
634
+ ],
635
+ installAgents: [
636
+ "ascendc-kernel-architect",
637
+ "ascendc-kernel-design-reviewer",
638
+ "ascendc-kernel-developer",
639
+ "ascendc-kernel-reviewer"
640
+ ],
641
+ externalRepos: [
642
+ {
643
+ url: "https://gitcode.com/cann/asc-devkit.git",
644
+ dir: "plugins-official/ops-direct-invoke/asc-devkit",
645
+ configRootLink: false
646
+ }
647
+ ]
648
+ },
649
+ {
650
+ id: "ops-direct-invoke-flash",
651
+ dir: "plugins-official/ops-direct-invoke-flash",
652
+ displayName: "AscendC Kernel \u76F4\u8C03\uFF08\u5FEB\u901F\u7248\uFF09",
653
+ script: "init.sh",
654
+ aliases: [
655
+ "flash",
656
+ "kernel-flash"
657
+ ],
658
+ skills: 1,
659
+ agents: 1,
660
+ description: "\u4ECE\u6570\u5B66\u516C\u5F0F\u6216\u4EE3\u7801\u7247\u6BB5\u5FEB\u901F\u6784\u5EFA\u5E76\u9A8C\u8BC1 Ascend NPU \u6838\u51FD\u6570",
661
+ version: "1.2.2",
662
+ configFile: "AGENTS.md",
663
+ configRootConfigLink: true,
664
+ installSkills: [
665
+ {
666
+ dir: "plugins-official/ops-direct-invoke-flash/skills",
667
+ skills: [
668
+ "ops-direct-invoke-flash"
669
+ ]
670
+ },
671
+ {
672
+ dir: "ops",
673
+ skills: [
674
+ "ascendc-st-design",
675
+ "ascendc-whitebox-design"
676
+ ]
677
+ },
678
+ {
679
+ dir: "infra",
680
+ skills: [
681
+ "gitcode-toolkit",
682
+ "gitcode-pr-handler",
683
+ "gitcode-issue-gen",
684
+ "gitcode-issue-handler"
685
+ ]
686
+ }
687
+ ],
688
+ installAgents: [
689
+ "ops-direct-invoke-flash-reviewer"
690
+ ]
691
+ },
692
+ {
693
+ id: "ops-registry-invoke",
694
+ dir: "plugins-official/ops-registry-invoke",
695
+ displayName: "AscendC \u7B97\u5B50\u6CE8\u518C\u8C03\u7528",
696
+ script: "init.sh",
697
+ aliases: [
698
+ "ops-registry",
699
+ "ascendc-registry",
700
+ "registry"
701
+ ],
702
+ skills: 12,
703
+ agents: 4,
704
+ description: "\u751F\u4EA7\u7EA7 ACLNN \u6CE8\u518C\u7B97\u5B50\u5168\u6D41\u7A0B\uFF1A\u4EE3\u7801\u2192UT/ST\u2192\u4E0A\u5E93",
705
+ version: "1.2.2",
706
+ configFile: "AGENTS.md",
707
+ configRootConfigLink: false,
708
+ installSkills: [
709
+ {
710
+ dir: "ops",
711
+ skills: [
712
+ "ascendc-blaze-best-practice",
713
+ "ascendc-api-best-practices",
714
+ "ascendc-code-review",
715
+ "ascendc-crash-debug",
716
+ "ascendc-direct-invoke-template",
717
+ "ascendc-docs-gen",
718
+ "ascendc-docs-search",
719
+ "ascendc-env-check",
720
+ "npu-arch",
721
+ "ascendc-performance-best-practices",
722
+ "ascendc-precision-debug",
723
+ "ascendc-regbase-best-practice",
724
+ "ascendc-registry-invoke-template",
725
+ "ascendc-runtime-debug",
726
+ "ascendc-st-design",
727
+ "ascendc-tiling-design",
728
+ "ascendc-ut-develop",
729
+ "ascendc-whitebox-design",
730
+ "ops-precision-standard",
731
+ "ops-profiling",
732
+ "ops-spec-gen"
733
+ ]
734
+ },
735
+ {
736
+ dir: "infra",
737
+ skills: [
738
+ "gitcode-toolkit",
739
+ "gitcode-pr-handler",
740
+ "gitcode-issue-gen",
741
+ "gitcode-issue-handler"
742
+ ]
743
+ },
744
+ {
745
+ dir: "plugins-official/ops-registry-invoke",
746
+ skills: [
747
+ {
748
+ name: "workflow",
749
+ as: "ops-registry-invoke-workflow"
750
+ }
751
+ ]
752
+ }
753
+ ],
754
+ installAgents: [
755
+ "ascendc-ops-architect",
756
+ "ascendc-ops-developer",
757
+ "ascendc-ops-tester"
758
+ ],
759
+ externalRepos: [
760
+ {
761
+ url: "https://gitcode.com/cann/asc-devkit.git",
762
+ dir: "plugins-official/ops-registry-invoke/asc-devkit",
763
+ configRootLink: false
764
+ }
765
+ ]
766
+ },
767
+ {
768
+ id: "catlass-op-generator",
769
+ dir: "plugins-official/catlass-op-generator",
770
+ displayName: "Catlass \u7B97\u5B50\u76F4\u8C03",
771
+ script: "init.sh",
772
+ aliases: [
773
+ "catlass"
774
+ ],
775
+ skills: 10,
776
+ agents: 3,
777
+ description: "Catlass \u6A21\u677F\u62FC\u88C5 GEMM/Matmul/Epilogue \u7B97\u5B50\u5F00\u53D1",
778
+ version: "1.2.2",
779
+ configFile: "AGENTS.md",
780
+ configRootConfigLink: true,
781
+ installSkills: [
782
+ {
783
+ dir: "ops",
784
+ skills: [
785
+ "catlass-op-design",
786
+ "catlass-op-develop",
787
+ "catlass-op-perf-tune",
788
+ "ascendc-tiling-design",
789
+ "npu-arch",
790
+ "ascendc-api-best-practices",
791
+ "ops-precision-standard",
792
+ "ascendc-docs-search",
793
+ "ascendc-env-check",
794
+ "ascendc-precision-debug",
795
+ "ops-profiling",
796
+ "ascendc-runtime-debug",
797
+ "ascendc-code-review",
798
+ "torch-ascendc-op-extension"
799
+ ]
800
+ }
801
+ ],
802
+ installAgents: [
803
+ "catlass-op-architect",
804
+ "catlass-op-generator",
805
+ "catlass-op-reviewer"
806
+ ],
807
+ externalRepos: [
808
+ {
809
+ url: "https://gitcode.com/cann/asc-devkit.git",
810
+ dir: "plugins-official/catlass-op-generator/asc-devkit",
811
+ configRootLink: false
812
+ }
813
+ ]
814
+ },
815
+ {
816
+ id: "model-infer-optimize",
817
+ dir: "plugins-official/model-infer-optimize",
818
+ displayName: "NPU \u63A8\u7406\u4F18\u5316",
819
+ script: "init.sh",
820
+ aliases: [
821
+ "model-infer",
822
+ "infer",
823
+ "inference"
824
+ ],
825
+ skills: 11,
826
+ agents: 3,
827
+ description: "NPU \u6A21\u578B\u63A8\u7406\u7AEF\u5230\u7AEF\u4F18\u5316\uFF08\u5E76\u884C/KVCache/\u878D\u5408/\u91CF\u5316/\u56FE\u6A21\u5F0F/\u591A\u6D41/SuperKernel\uFF09",
828
+ version: "1.2.2",
829
+ configFile: "AGENTS.md",
830
+ configRootConfigLink: true,
831
+ installSkills: [
832
+ {
833
+ dir: "model",
834
+ skills: [
835
+ "model-infer-migrator",
836
+ "model-infer-parallel-analysis",
837
+ "model-infer-parallel-impl",
838
+ "model-infer-kvcache",
839
+ "model-infer-fusion",
840
+ "model-infer-quantization",
841
+ "model-infer-graph-mode",
842
+ "model-infer-precision-debug",
843
+ "model-infer-runtime-debug",
844
+ "model-infer-multi-stream",
845
+ "model-infer-prefetch",
846
+ "model-infer-superkernel"
847
+ ]
848
+ }
849
+ ],
850
+ installAgents: [
851
+ "model-infer-analyzer",
852
+ "model-infer-implementer",
853
+ "model-infer-reviewer"
854
+ ],
855
+ externalRepos: [
856
+ {
857
+ url: "https://gitcode.com/cann/cann-recipes-infer.git",
858
+ dir: "plugins-official/model-infer-optimize/cann-recipes-infer",
859
+ depth: 1,
860
+ configRootLink: true
861
+ }
862
+ ]
863
+ },
864
+ {
865
+ id: "pypto-op-orchestrator",
866
+ dir: "plugins-official/pypto-op-orchestrator",
867
+ displayName: "PyPTO \u7B97\u5B50\u5F00\u53D1",
868
+ script: "init.sh",
869
+ aliases: [
870
+ "pypto",
871
+ "pytorch"
872
+ ],
873
+ skills: 8,
874
+ agents: 3,
875
+ description: "PyPTO Python API \u7B97\u5B50\u5168\u6D41\u7A0B\u5F00\u53D1\uFF0C\u8986\u76D6\u9700\u6C42\u7406\u89E3\u3001API \u63A2\u7D22\u3001\u5B9E\u73B0\u4E0E\u8C03\u4F18",
876
+ version: "1.2.2",
877
+ configFile: "AGENTS.md",
878
+ configRootConfigLink: false,
879
+ installSkills: [
880
+ {
881
+ dir: "ops",
882
+ skills: [
883
+ "pypto-intent-understand",
884
+ "pypto-api-explore",
885
+ "pypto-op-design",
886
+ "pypto-op-develop",
887
+ "pypto-precision-debug",
888
+ "pypto-precision-compare",
889
+ "pypto-op-perf-tune",
890
+ "pypto-golden-generate"
891
+ ]
892
+ }
893
+ ],
894
+ installAgents: [
895
+ "pypto-op-analyst",
896
+ "pypto-op-developer",
897
+ "pypto-op-perf-tuner"
898
+ ],
899
+ externalRepos: [
900
+ {
901
+ url: "https://gitcode.com/cann/pypto.git",
902
+ dir: "plugins-official/pypto-op-orchestrator/pypto",
903
+ configRootLink: false
904
+ }
905
+ ]
906
+ },
907
+ {
908
+ id: "tilelang-op-orchestrator",
909
+ dir: "plugins-official/tilelang-op-orchestrator",
910
+ displayName: "TileLang \u7B97\u5B50\u5F00\u53D1",
911
+ script: "init.sh",
912
+ aliases: [
913
+ "tilelang"
914
+ ],
915
+ skills: 9,
916
+ agents: 3,
917
+ description: "TileLang Python DSL + @tilelang.jit \u7B97\u5B50\u5F00\u53D1\uFF08\u652F\u6301 Developer/Expert \u53CC\u6A21\u5F0F\uFF09",
918
+ version: "1.2.2",
919
+ configFile: "AGENTS.md",
920
+ configRootConfigLink: false,
921
+ installSkills: [
922
+ {
923
+ dir: "ops",
924
+ skills: [
925
+ "tilelang-api-best-practices",
926
+ "tilelang-env-check",
927
+ "tilelang-op-design",
928
+ "tilelang-op-develop",
929
+ "tilelang-op-test-design",
930
+ "tilelang-perf-optimization",
931
+ "tilelang-programming-model-guide",
932
+ "tilelang-review",
933
+ "tilelang-submodule-pull"
934
+ ]
935
+ }
936
+ ],
937
+ installAgents: [
938
+ "tilelang-op-analyst",
939
+ "tilelang-op-developer",
940
+ "tilelang-op-perf-tuner"
941
+ ],
942
+ externalRepos: [
943
+ {
944
+ url: "https://github.com/tile-ai/tilelang-ascend.git",
945
+ dir: "plugins-official/tilelang-op-orchestrator/tilelang-ascend",
946
+ depth: 1,
947
+ recursive: true,
948
+ configRootLink: false
949
+ }
950
+ ]
951
+ },
952
+ {
953
+ id: "triton-op-generator",
954
+ dir: "plugins-official/triton-op-generator",
955
+ displayName: "Triton \u7B97\u5B50\u5F00\u53D1",
956
+ script: "init.sh",
957
+ aliases: [
958
+ "triton"
959
+ ],
960
+ skills: 6,
961
+ agents: 1,
962
+ description: "Triton-Ascend \u7B97\u5B50 6 \u9636\u6BB5\u81EA\u52A8\u751F\u6210\u4E0E\u4F18\u5316\uFF08\u63CF\u8FF0\u2192\u4EE3\u7801\u2192\u9A8C\u8BC1\u2192\u4F18\u5316\uFF09",
963
+ version: "1.2.2",
964
+ configFile: "AGENTS.md",
965
+ configRootConfigLink: false,
966
+ installSkills: [
967
+ {
968
+ dir: "ops",
969
+ skills: [
970
+ "triton-task-extractor",
971
+ "triton-op-designer",
972
+ "triton-op-coding",
973
+ "triton-op-verifier",
974
+ "triton-latency-optimizer",
975
+ "npu-arch"
976
+ ]
977
+ }
978
+ ],
979
+ installAgents: []
980
+ },
981
+ {
982
+ id: "torch-compile",
983
+ dir: "plugins-official/torch-compile",
984
+ displayName: "torch.compile \u56FE\u6A21\u5F0F",
985
+ script: "init.sh",
986
+ aliases: [
987
+ "torch",
988
+ "compile",
989
+ "graph"
990
+ ],
991
+ skills: 6,
992
+ agents: 1,
993
+ description: "NPU \u56FE\u6A21\u5F0F npugraph_ex \u914D\u7F6E\u3001\u811A\u672C\u8FC1\u79FB\u3001\u7F16\u8BD1\u8BCA\u65AD\u4E0E\u81EA\u5B9A\u4E49\u7B97\u5B50\u5165\u56FE",
994
+ version: "1.2.2",
995
+ configFile: "AGENTS.md",
996
+ configRootConfigLink: true,
997
+ installSkills: [
998
+ {
999
+ dir: "graph",
1000
+ skills: [
1001
+ "torch-npugraph-ex-knowledge",
1002
+ "torch-npugraph-ex-template",
1003
+ "torch-npugraph-ex-dfx-triage",
1004
+ "torch-npugraph-ex-compile-error-diagnosis",
1005
+ "torch-npugraph-ex-runtime-error-diagnosis",
1006
+ "torch-custom-ops-guide"
1007
+ ]
1008
+ }
1009
+ ],
1010
+ installAgents: [
1011
+ "torch-npugraph-ex"
1012
+ ]
1013
+ },
1014
+ {
1015
+ id: "ops-code-reviewer",
1016
+ dir: "plugins-official/ops-code-reviewer",
1017
+ displayName: "\u4EE3\u7801\u68C0\u89C6",
1018
+ script: "init.sh",
1019
+ aliases: [
1020
+ "code-review",
1021
+ "reviewer",
1022
+ "review"
1023
+ ],
1024
+ skills: 5,
1025
+ agents: 1,
1026
+ description: "Ascend C \u7B97\u5B50\u4EE3\u7801 4 \u9636\u6BB5\u5168\u91CF\u68C0\u89C6\uFF08\u4E3B Agent \u7F16\u6392 + \u5B50 Agent \u5E76\u884C\u68C0\u89C6\uFF09",
1027
+ version: "1.2.2",
1028
+ configFile: "AGENTS.md",
1029
+ configRootConfigLink: false,
1030
+ installSkills: [
1031
+ {
1032
+ dir: "ops",
1033
+ skills: [
1034
+ "ascendc-code-review",
1035
+ "ascendc-docs-search",
1036
+ "ascendc-task-focus",
1037
+ "ascendc-api-best-practices",
1038
+ "npu-arch"
1039
+ ]
1040
+ }
1041
+ ],
1042
+ installAgents: [
1043
+ "ascendc-code-summarizer",
1044
+ "ascendc-ops-reviewer"
1045
+ ]
1046
+ }
1047
+ ];
1048
+
1049
+ // src/core/registry.ts
1050
+ var __filename2 = fileURLToPath2(import.meta.url);
1051
+ var __dirname2 = dirname2(__filename2);
1052
+ function findPluginsDir() {
1053
+ const candidates = [
1054
+ join2(__dirname2, "plugins.d"),
1055
+ join2(__dirname2, "..", "plugins.d"),
1056
+ join2(__dirname2, "..", "..", "plugins.d")
1057
+ ];
1058
+ for (const dir of candidates) {
1059
+ if (existsSync2(dir)) return dir;
1060
+ }
1061
+ return candidates[0];
1062
+ }
1063
+ function loadPluginsFromYaml() {
1064
+ const pluginsDir = findPluginsDir();
1065
+ const defaultsPath = join2(pluginsDir, "_defaults.yml");
1066
+ let defaults = {};
1067
+ try {
1068
+ defaults = parseYaml(readFileSync2(defaultsPath, "utf-8")) || {};
1069
+ } catch {
1070
+ }
1071
+ const plugins = [];
1072
+ try {
1073
+ for (const file of readdirSync(pluginsDir)) {
1074
+ if (file.startsWith("_") || !file.endsWith(".yml")) continue;
1075
+ try {
1076
+ const content = parseYaml(readFileSync2(join2(pluginsDir, file), "utf-8"));
1077
+ if (!content || !content.id) continue;
1078
+ plugins.push({
1079
+ id: content.id,
1080
+ dir: content.dir,
1081
+ displayName: content.displayName || content.id,
1082
+ script: content.script || defaults.script || "init.sh",
1083
+ aliases: content.aliases || [],
1084
+ skills: content.skills ?? defaults.skills ?? 0,
1085
+ agents: content.agents ?? defaults.agents ?? 0,
1086
+ description: content.description || "",
1087
+ version: content.version,
1088
+ configFile: content.configFile,
1089
+ configRootConfigLink: content.configRootConfigLink,
1090
+ installSkills: content.installSkills,
1091
+ installAgents: content.installAgents,
1092
+ externalRepos: content.externalRepos
1093
+ });
1094
+ } catch {
1095
+ logger.warn(t("plugin_yaml_error").replace("{file}", file));
1096
+ }
1097
+ }
1098
+ } catch {
1099
+ }
1100
+ if (plugins.length === 0) {
1101
+ return [...embedded_plugins_default];
1102
+ }
1103
+ plugins.sort(
1104
+ (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
1105
+ );
1106
+ return plugins;
1107
+ }
1108
+ var PLUGIN_REGISTRY = loadPluginsFromYaml();
1109
+ var PLUGIN_DIRS = ["plugins-official", "plugins-community"];
1110
+ function mergeDynamicPlugins(repoPath) {
1111
+ if (!existsSync2(repoPath)) return;
1112
+ const existingIds = new Set(PLUGIN_REGISTRY.map((p) => p.id));
1113
+ const dynamic = [];
1114
+ for (const pluginDir of PLUGIN_DIRS) {
1115
+ const parent = join2(repoPath, pluginDir);
1116
+ if (!existsSync2(parent)) continue;
1117
+ let entries = [];
1118
+ try {
1119
+ entries = readdirSync(parent);
1120
+ } catch {
1121
+ continue;
1122
+ }
1123
+ for (const entry of entries) {
1124
+ const pluginPath = join2(parent, entry);
1125
+ if (!isDirectory(pluginPath)) continue;
1126
+ if (entry === "install-helper") continue;
1127
+ const initSh = join2(pluginPath, "init.sh");
1128
+ if (!existsSync2(initSh)) continue;
1129
+ const pluginId = entry;
1130
+ if (existingIds.has(pluginId)) continue;
1131
+ dynamic.push({
1132
+ id: pluginId,
1133
+ dir: `${pluginDir}/${entry}`,
1134
+ displayName: entry,
1135
+ script: "init.sh",
1136
+ aliases: [],
1137
+ skills: 0,
1138
+ agents: 0,
1139
+ description: ""
1140
+ });
1141
+ existingIds.add(pluginId);
1142
+ }
1143
+ }
1144
+ if (dynamic.length > 0) {
1145
+ PLUGIN_REGISTRY = [...PLUGIN_REGISTRY, ...dynamic].sort(
1146
+ (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
1147
+ );
1148
+ }
1149
+ }
1150
+ function findPlugin(query) {
1151
+ const normalized = query.toLowerCase().trim();
1152
+ const exactMatch = PLUGIN_REGISTRY.find(
1153
+ (p) => p.id === normalized || p.id === query.trim()
1154
+ );
1155
+ if (exactMatch) return exactMatch;
1156
+ const aliasMatch = PLUGIN_REGISTRY.find(
1157
+ (p) => p.aliases.some((a) => a === normalized)
1158
+ );
1159
+ if (aliasMatch) return aliasMatch;
1160
+ const prefixMatch = PLUGIN_REGISTRY.find((p) => p.id.startsWith(normalized));
1161
+ if (prefixMatch) return prefixMatch;
1162
+ return void 0;
1163
+ }
1164
+ function getAllPlugins() {
1165
+ return PLUGIN_REGISTRY;
1166
+ }
1167
+ function getPluginById(id) {
1168
+ return PLUGIN_REGISTRY.find((p) => p.id === id);
1169
+ }
1170
+
1171
+ // src/core/detector.ts
1172
+ import { execa } from "execa";
1173
+ import { existsSync as existsSync4 } from "fs";
1174
+ import { homedir as homedir2 } from "os";
1175
+ import { join as join4 } from "path";
1176
+
1177
+ // src/utils/paths.ts
1178
+ import { homedir } from "os";
1179
+ import { join as join3 } from "path";
1180
+ import { existsSync as existsSync3 } from "fs";
1181
+ var VALID_TOOLS = ["opencode", "claude", "trae", "cursor", "copilot"];
1182
+ var VALID_LEVELS = ["project", "global"];
1183
+ function validateTool(tool) {
1184
+ if (!VALID_TOOLS.includes(tool)) {
1185
+ throw new Error(
1186
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
1187
+ );
1188
+ }
1189
+ return tool;
1190
+ }
1191
+ function validateLevel(level) {
1192
+ if (!VALID_LEVELS.includes(level)) {
1193
+ throw new Error(
1194
+ t("error_invalid_level").replace("{level}", level).replace("{levels}", VALID_LEVELS.join(", "))
1195
+ );
1196
+ }
1197
+ return level;
1198
+ }
1199
+ function detectTraeVariant() {
1200
+ const home = homedir();
1201
+ if (existsSync3(join3(home, ".trae-cn"))) return "ide";
1202
+ if (existsSync3(join3(home, ".marscode"))) return "plugin";
1203
+ if (existsSync3(join3(home, ".traecli"))) return "cli";
1204
+ return "unknown";
1205
+ }
1206
+ function getConfigRoot(tool, level, base) {
1207
+ const home = homedir();
1208
+ if (level === "global") {
1209
+ switch (tool) {
1210
+ case "opencode":
1211
+ return join3(home, ".config", "opencode");
1212
+ case "claude":
1213
+ return join3(home, ".claude");
1214
+ case "trae": {
1215
+ const variant = detectTraeVariant();
1216
+ switch (variant) {
1217
+ case "plugin":
1218
+ return join3(home, ".marscode");
1219
+ case "cli":
1220
+ return join3(home, ".traecli");
1221
+ default:
1222
+ return join3(home, ".trae-cn");
1223
+ }
1224
+ }
1225
+ case "cursor":
1226
+ return join3(home, ".cursor");
1227
+ case "copilot":
1228
+ return join3(home, ".copilot");
1229
+ default:
1230
+ throw new Error(
1231
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
1232
+ );
1233
+ }
1234
+ }
1235
+ const baseDir = base || process.cwd();
1236
+ switch (tool) {
1237
+ case "opencode":
1238
+ return join3(baseDir, ".opencode");
1239
+ case "claude":
1240
+ return join3(baseDir, ".claude");
1241
+ case "trae": {
1242
+ const variant = detectTraeVariant();
1243
+ switch (variant) {
1244
+ case "plugin":
1245
+ return join3(baseDir, ".marscode");
1246
+ case "cli":
1247
+ return join3(baseDir, ".traecli");
1248
+ default:
1249
+ return join3(baseDir, ".trae");
1250
+ }
1251
+ }
1252
+ case "cursor":
1253
+ return join3(baseDir, ".cursor");
1254
+ case "copilot":
1255
+ return join3(baseDir, ".github");
1256
+ default:
1257
+ throw new Error(
1258
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
1259
+ );
1260
+ }
1261
+ }
1262
+ function getConfigFileName(tool) {
1263
+ return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1264
+ }
1265
+ function getAgentsFileName(tool) {
1266
+ return getConfigFileName(tool);
1267
+ }
1268
+ function getSkillsDir(configRoot) {
1269
+ return join3(configRoot, "skills");
1270
+ }
1271
+ function getAgentsDir(configRoot) {
1272
+ return join3(configRoot, "agents");
1273
+ }
1274
+ function getManifestPath(configRoot) {
1275
+ return join3(configRoot, "cannbot-manifest.json");
1276
+ }
1277
+ function getCannbotConfigDir() {
1278
+ return join3(homedir(), ".cannbot");
1279
+ }
1280
+ function getCannbotRepoPath() {
1281
+ return join3(getCannbotConfigDir(), "repo");
1282
+ }
1283
+
1284
+ // src/core/detector.ts
1285
+ async function getCommandVersion(cmd, args = ["--version"]) {
1286
+ try {
1287
+ const result = await execa(cmd, args, { timeout: 5e3 });
1288
+ const output = result.stdout.trim();
1289
+ const match = output.match(/(\d+\.\d+\.\d+)/);
1290
+ return match ? match[1] : output.split("\n")[0];
1291
+ } catch {
1292
+ return void 0;
1293
+ }
1294
+ }
1295
+ async function getCommandPath(cmd) {
1296
+ try {
1297
+ const whichCmd = process.platform === "win32" ? "where" : "which";
1298
+ const result = await execa(whichCmd, [cmd], { timeout: 5e3 });
1299
+ return result.stdout.trim().split("\n")[0];
1300
+ } catch {
1301
+ return void 0;
1302
+ }
1303
+ }
1304
+ async function detectOpenCode() {
1305
+ const path = await getCommandPath("opencode");
1306
+ if (!path) return void 0;
1307
+ const version = await getCommandVersion("opencode");
1308
+ return { name: "opencode", version, path };
1309
+ }
1310
+ async function detectClaude() {
1311
+ const path = await getCommandPath("claude");
1312
+ if (!path) return void 0;
1313
+ const version = await getCommandVersion("claude");
1314
+ return { name: "claude", version, path };
1315
+ }
1316
+ async function detectTrae() {
1317
+ const home = homedir2();
1318
+ const variant = detectTraeVariant();
1319
+ let path;
1320
+ if (variant === "ide" && existsSync4(join4(home, ".trae-cn"))) {
1321
+ path = join4(home, ".trae-cn");
1322
+ } else if (variant === "plugin" && existsSync4(join4(home, ".marscode"))) {
1323
+ path = join4(home, ".marscode");
1324
+ } else if (variant === "cli" && existsSync4(join4(home, ".traecli"))) {
1325
+ path = join4(home, ".traecli");
1326
+ } else if (variant === "unknown") {
1327
+ const cmdPath = await getCommandPath("trae");
1328
+ if (cmdPath) path = cmdPath;
1329
+ }
1330
+ if (!path) return void 0;
1331
+ return { name: "trae", version: variant, path };
1332
+ }
1333
+ async function detectCursor() {
1334
+ const path = await getCommandPath("cursor");
1335
+ if (path) {
1336
+ const version = await getCommandVersion("cursor");
1337
+ return { name: "cursor", version, path };
1338
+ }
1339
+ if (process.platform === "darwin") {
1340
+ const appPath = "/Applications/Cursor.app";
1341
+ if (existsSync4(appPath)) {
1342
+ return { name: "cursor", path: appPath };
1343
+ }
1344
+ }
1345
+ return void 0;
1346
+ }
1347
+ async function detectCopilot() {
1348
+ const path = await getCommandPath("gh");
1349
+ if (!path) return void 0;
1350
+ try {
1351
+ await execa("gh", ["extension", "list"], { timeout: 5e3 });
1352
+ return { name: "copilot", path };
1353
+ } catch {
1354
+ return void 0;
1355
+ }
1356
+ }
1357
+ async function detectTools() {
1358
+ const detectors = [
1359
+ detectOpenCode,
1360
+ detectClaude,
1361
+ detectTrae,
1362
+ detectCursor,
1363
+ detectCopilot
1364
+ ];
1365
+ const results = await Promise.allSettled(detectors.map((d) => d()));
1366
+ const tools = [];
1367
+ for (const result of results) {
1368
+ if (result.status === "fulfilled" && result.value) {
1369
+ tools.push(result.value);
1370
+ }
1371
+ }
1372
+ return tools;
1373
+ }
1374
+ function getToolDisplayName(tool) {
1375
+ const names = {
1376
+ opencode: "OpenCode",
1377
+ claude: "Claude Code",
1378
+ trae: "Trae",
1379
+ cursor: "Cursor",
1380
+ copilot: "GitHub Copilot"
1381
+ };
1382
+ return names[tool];
1383
+ }
1384
+ function getAllTools() {
1385
+ return VALID_TOOLS;
1386
+ }
1387
+
1388
+ // src/core/manifest.ts
1389
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1390
+ import { join as join5 } from "path";
1391
+ function readManifest(configRoot) {
1392
+ const manifestPath = getManifestPath(configRoot);
1393
+ if (existsSync5(manifestPath)) {
1394
+ try {
1395
+ const content = readFileSync3(manifestPath, "utf-8");
1396
+ return JSON.parse(content);
1397
+ } catch {
1398
+ }
1399
+ }
1400
+ return null;
1401
+ }
1402
+ function readAllManifests(configRoot) {
1403
+ const manifests = [];
1404
+ const plugins = getAllPlugins();
1405
+ const standardPath = getManifestPath(configRoot);
1406
+ if (existsSync5(standardPath)) {
1407
+ try {
1408
+ const content = readFileSync3(standardPath, "utf-8");
1409
+ manifests.push(JSON.parse(content));
1410
+ } catch {
674
1411
  }
675
1412
  }
676
- return width;
1413
+ for (const plugin of plugins) {
1414
+ const pluginManifestPath = join5(configRoot, `${plugin.id}-manifest.json`);
1415
+ if (existsSync5(pluginManifestPath)) {
1416
+ try {
1417
+ const content = readFileSync3(pluginManifestPath, "utf-8");
1418
+ manifests.push(JSON.parse(content));
1419
+ } catch {
1420
+ }
1421
+ }
1422
+ }
1423
+ return manifests;
677
1424
  }
678
- function printBoxTitle(title, width = 65) {
679
- const border = "\u2550".repeat(width);
680
- const displayWidth = getDisplayWidth(title);
681
- const totalPadding = width - displayWidth;
682
- const leftPadding = Math.floor(totalPadding / 2);
683
- const rightPadding = totalPadding - leftPadding;
684
- const paddedTitle = " ".repeat(Math.max(0, leftPadding)) + title + " ".repeat(Math.max(0, rightPadding));
685
- console.log();
686
- console.log(chalk.cyan.bold(` \u2554${border}\u2557`));
687
- console.log(chalk.cyan.bold(` \u2551`) + chalk.cyan.bold(paddedTitle) + chalk.cyan.bold(`\u2551`));
688
- console.log(chalk.cyan.bold(` \u255A${border}\u255D`));
689
- console.log();
1425
+ function scanInstalled() {
1426
+ const plugins = getAllPlugins();
1427
+ const installed = [];
1428
+ const tools = VALID_TOOLS;
1429
+ const levels = ["project", "global"];
1430
+ for (const tool of tools) {
1431
+ for (const level of levels) {
1432
+ const configRoot = getConfigRoot(tool, level);
1433
+ const manifests = readAllManifests(configRoot);
1434
+ for (const manifest of manifests) {
1435
+ const plugin = plugins.find((p) => p.id === manifest.team);
1436
+ if (!plugin) continue;
1437
+ if (!verifyPluginFilesExist(configRoot, manifest, tool, level)) {
1438
+ continue;
1439
+ }
1440
+ const alreadyAdded = installed.some(
1441
+ (p) => p.id === plugin.id && p.tool === tool && p.level === level
1442
+ );
1443
+ if (alreadyAdded) continue;
1444
+ installed.push({
1445
+ id: plugin.id,
1446
+ displayName: plugin.displayName,
1447
+ tool,
1448
+ level,
1449
+ skillsCount: manifest.installed_skills?.length || 0,
1450
+ agentsCount: manifest.installed_agents?.length || 0,
1451
+ installTime: manifest.install_time,
1452
+ configRoot
1453
+ });
1454
+ }
1455
+ }
1456
+ }
1457
+ return installed;
690
1458
  }
691
- function showOperationHints(isCheckbox = false) {
692
- if (isCheckbox) {
693
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u7A7A\u683C\x1B[0m \u52FE\u9009 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4\uFF08\u4E0D\u9009\u6309\u56DE\u8F66\u53EF\u8FD4\u56DE\uFF09
694
- `);
1459
+ function verifyPluginFilesExist(configRoot, manifest, tool, level) {
1460
+ const skillsDir = join5(configRoot, "skills");
1461
+ const agentsDir = join5(configRoot, "agents");
1462
+ if (manifest.installed_skills && manifest.installed_skills.length > 0) {
1463
+ const hasAnySkill = manifest.installed_skills.some((skillId) => {
1464
+ const skillPath = join5(skillsDir, skillId);
1465
+ return existsSync5(skillPath);
1466
+ });
1467
+ if (!hasAnySkill) return false;
1468
+ }
1469
+ if (manifest.installed_agents && manifest.installed_agents.length > 0) {
1470
+ const hasAnyAgent = manifest.installed_agents.some((agentId) => {
1471
+ const agentPath = join5(agentsDir, agentId);
1472
+ return existsSync5(agentPath);
1473
+ });
1474
+ if (!hasAnyAgent) return false;
1475
+ }
1476
+ let agentsMdPath;
1477
+ if (tool === "opencode" && level === "project") {
1478
+ agentsMdPath = join5(configRoot, "..", "AGENTS.md");
695
1479
  } else {
696
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4
697
- `);
1480
+ agentsMdPath = join5(configRoot, "AGENTS.md");
698
1481
  }
1482
+ if (!existsSync5(agentsMdPath)) return false;
1483
+ return true;
699
1484
  }
700
1485
 
701
1486
  // src/utils/config.ts
702
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
703
- import { join as join4 } from "path";
704
- import { homedir as homedir3 } from "os";
1487
+ import { existsSync as existsSync7, mkdirSync, readFileSync as readFileSync4 } from "fs";
1488
+ import { join as join6 } from "path";
705
1489
  import { parse, stringify } from "yaml";
1490
+
1491
+ // src/utils/fs.ts
1492
+ import { writeFileSync, renameSync, existsSync as existsSync6, unlinkSync } from "fs";
1493
+ import { dirname as dirname3 } from "path";
1494
+ function atomicWriteFileSync(filePath, data, encoding = "utf-8") {
1495
+ const dir = dirname3(filePath);
1496
+ if (!existsSync6(dir)) {
1497
+ throw new Error(`Directory does not exist: ${dir}`);
1498
+ }
1499
+ const tmpPath = `${filePath}.tmp`;
1500
+ writeFileSync(tmpPath, data, encoding);
1501
+ if (existsSync6(filePath)) {
1502
+ try {
1503
+ unlinkSync(filePath);
1504
+ } catch {
1505
+ }
1506
+ }
1507
+ renameSync(tmpPath, filePath);
1508
+ }
1509
+
1510
+ // src/utils/config.ts
706
1511
  function getConfigDir() {
707
- return join4(homedir3(), ".cannbot");
1512
+ return getCannbotConfigDir();
708
1513
  }
709
1514
  function getConfigPath() {
710
- return join4(getConfigDir(), "config.yaml");
1515
+ return join6(getConfigDir(), "config.yaml");
711
1516
  }
712
1517
  function readConfig() {
713
1518
  const configPath = getConfigPath();
714
- if (!existsSync4(configPath)) {
1519
+ if (!existsSync7(configPath)) {
715
1520
  return {
716
1521
  language: "zh_CN",
717
1522
  installedPlugins: []
718
1523
  };
719
1524
  }
720
1525
  try {
721
- const content = readFileSync2(configPath, "utf-8");
1526
+ const content = readFileSync4(configPath, "utf-8");
722
1527
  const parsed = parse(content);
723
1528
  return {
724
1529
  language: parsed.language || "zh_CN",
@@ -728,6 +1533,7 @@ function readConfig() {
728
1533
  installedPlugins: parsed.installedPlugins || []
729
1534
  };
730
1535
  } catch {
1536
+ logger.warn(t("config_corrupted"));
731
1537
  return {
732
1538
  language: "zh_CN",
733
1539
  installedPlugins: []
@@ -736,12 +1542,12 @@ function readConfig() {
736
1542
  }
737
1543
  function writeConfig(config) {
738
1544
  const configDir = getConfigDir();
739
- if (!existsSync4(configDir)) {
1545
+ if (!existsSync7(configDir)) {
740
1546
  mkdirSync(configDir, { recursive: true });
741
1547
  }
742
1548
  const configPath = getConfigPath();
743
1549
  const content = stringify(config);
744
- writeFileSync(configPath, content, "utf-8");
1550
+ atomicWriteFileSync(configPath, content);
745
1551
  }
746
1552
  function updateConfig(updates) {
747
1553
  const config = readConfig();
@@ -764,22 +1570,11 @@ function removeInstalledPlugin(pluginId) {
764
1570
  writeConfig(config);
765
1571
  }
766
1572
 
767
- // src/ui/theme.ts
768
- var selectTheme = {
769
- helpMode: "never"
770
- };
771
- var checkboxTheme = {
772
- icon: {
773
- checked: "\x1B[32m\u2611\x1B[22m",
774
- unchecked: "\u2610",
775
- cursor: "\u276F"
776
- },
777
- helpMode: "never"
778
- };
779
-
780
- // src/ui/wizard.ts
1573
+ // src/utils/constants.ts
781
1574
  var BACK = "__back__";
782
1575
  var CANCEL = "__cancel__";
1576
+
1577
+ // src/ui/wizard.ts
783
1578
  async function selectToolWithDetection() {
784
1579
  const config = readConfig();
785
1580
  logger.blank();
@@ -849,7 +1644,7 @@ async function runWizard() {
849
1644
  step = 2;
850
1645
  break;
851
1646
  }
852
- if (result === CANCEL || result === false) {
1647
+ if (result === CANCEL) {
853
1648
  return { language: config.language || "zh_CN", tool: selectedTool, level, plugins: [], confirmed: false };
854
1649
  }
855
1650
  updateConfig({ lastTool: selectedTool, lastLevel: level });
@@ -859,11 +1654,11 @@ async function runWizard() {
859
1654
  }
860
1655
  }
861
1656
  async function stepTool(detectedTools, lastTool) {
862
- printBoxTitle("[1/4] \u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177");
1657
+ printBoxTitle(t("wizard_step_1_title"));
863
1658
  if (detectedTools.length === 0) {
864
1659
  logger.error(t("error_tool_not_found"));
865
1660
  const choices2 = [
866
- ...["opencode", "claude", "trae", "cursor", "copilot"].map((tool) => ({
1661
+ ...VALID_TOOLS.map((tool) => ({
867
1662
  name: `> ${getToolDisplayName(tool)}`,
868
1663
  value: tool
869
1664
  })),
@@ -934,10 +1729,10 @@ async function stepToolManual(lastTool) {
934
1729
  return result;
935
1730
  }
936
1731
  async function stepLevel(lastLevel) {
937
- printBoxTitle("[2/4] \u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E");
1732
+ printBoxTitle(t("wizard_step_2_title"));
938
1733
  const choices = [
939
- { name: "> project \u2014 \u5B89\u88C5\u5230\u5F53\u524D\u76EE\u5F55\uFF08\u63A8\u8350\uFF0C\u4EC5\u5F53\u524D\u9879\u76EE\u53EF\u7528\uFF09", value: "project" },
940
- { name: "> global \u2014 \u5B89\u88C5\u5230\u5168\u5C40\uFF08\u6240\u6709\u9879\u76EE\u5171\u4EAB\uFF09", value: "global" },
1734
+ { name: `> project \u2014 ${t("install_level_project")}`, value: "project" },
1735
+ { name: `> global \u2014 ${t("install_level_global")}`, value: "global" },
941
1736
  new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
942
1737
  { name: "<- " + t("wizard_back"), value: BACK },
943
1738
  { name: "x " + t("wizard_cancel"), value: CANCEL }
@@ -955,7 +1750,7 @@ async function stepLevel(lastLevel) {
955
1750
  return result;
956
1751
  }
957
1752
  async function stepPlugins(tool, level) {
958
- printBoxTitle("[3/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6");
1753
+ printBoxTitle(t("wizard_step_3_title"));
959
1754
  const plugins = getAllPlugins();
960
1755
  const configRoot = getConfigRoot(tool, level);
961
1756
  const manifests = readAllManifests(configRoot);
@@ -964,49 +1759,38 @@ async function stepPlugins(tool, level) {
964
1759
  const isInstalled = installedSet.has(p.id);
965
1760
  const suffix = isInstalled ? ` [${t("wizard_already_installed")}]` : "";
966
1761
  return {
967
- name: `${p.displayName}${suffix} \u2014 ${p.description}`,
1762
+ name: `> ${chalk3.cyan(p.displayName)}${suffix}`,
968
1763
  value: p.id,
969
- checked: false
1764
+ description: p.description
970
1765
  };
971
1766
  });
972
- showOperationHints(true);
973
- const selected = await checkbox({
974
- message: t("wizard_select_plugins"),
1767
+ choices.push(new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1768
+ choices.push({ name: "<- " + t("wizard_back"), value: BACK });
1769
+ choices.push({ name: "x " + t("wizard_cancel"), value: CANCEL });
1770
+ showOperationHints();
1771
+ const selected = await select({
1772
+ message: t("wizard_select_plugin"),
975
1773
  choices,
976
1774
  loop: false,
977
- instructions: false,
978
- theme: checkboxTheme,
1775
+ theme: selectTheme,
979
1776
  pageSize: 15
980
1777
  });
981
- const pluginIds = selected;
982
- if (pluginIds.length === 0) {
983
- const action = await select({
984
- message: t("wizard_no_selection"),
985
- choices: [
986
- new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
987
- { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
988
- { name: "x " + t("wizard_cancel"), value: "cancel" }
989
- ],
990
- loop: false,
991
- theme: selectTheme
992
- });
993
- if (action === "back") return BACK;
994
- return CANCEL;
995
- }
996
- return pluginIds;
1778
+ if (selected === BACK) return BACK;
1779
+ if (selected === CANCEL) return CANCEL;
1780
+ return [selected];
997
1781
  }
998
1782
  async function stepConfirm(tool, level, plugins) {
999
- printBoxTitle("[4/4] \u786E\u8BA4\u5B89\u88C5");
1783
+ printBoxTitle(t("wizard_step_4_title"));
1000
1784
  const allPlugins = getAllPlugins();
1001
1785
  const selectedPlugins = plugins.map((id) => allPlugins.find((p) => p.id === id));
1002
1786
  const displayPath = getConfigRoot(tool, level);
1003
1787
  const toolName = getToolDisplayName(tool);
1004
1788
  const levelText = level === "project" ? "project" : "global";
1005
- 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`);
1006
- logger.info(`\u5B89\u88C5\u8DEF\u5F84: ${chalk2.cyan(displayPath)}`);
1789
+ logger.info(t("wizard_confirm_install_format").replace("{count}", chalk3.bold(String(plugins.length))).replace("{tool}", chalk3.green(toolName)).replace("{level}", chalk3.cyan(levelText)));
1790
+ logger.info(`${t("wizard_install_path")}: ${chalk3.cyan(displayPath)}`);
1007
1791
  for (const plugin of selectedPlugins) {
1008
1792
  if (plugin) {
1009
- logger.step(` \u2022 ${chalk2.bold(plugin.displayName)}`);
1793
+ logger.step(` \u2022 ${chalk3.bold(plugin.displayName)}`);
1010
1794
  }
1011
1795
  }
1012
1796
  logger.blank();
@@ -1024,14 +1808,19 @@ async function stepConfirm(tool, level, plugins) {
1024
1808
  }
1025
1809
 
1026
1810
  // src/core/repository.ts
1027
- import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
1028
- import { join as join6 } from "path";
1811
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
1812
+ import { join as join8, dirname as dirname5 } from "path";
1813
+ import { fileURLToPath as fileURLToPath4 } from "url";
1029
1814
  import { execa as execa2 } from "execa";
1815
+ import { parse as parseYaml3 } from "yaml";
1030
1816
 
1031
1817
  // src/core/scanner.ts
1032
- import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1033
- import { join as join5 } from "path";
1034
- import { parse as parseYaml } from "yaml";
1818
+ import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync5, mkdirSync as mkdirSync2 } from "fs";
1819
+ import { join as join7, dirname as dirname4 } from "path";
1820
+ import { fileURLToPath as fileURLToPath3 } from "url";
1821
+ import { parse as parseYaml2 } from "yaml";
1822
+ var __filename3 = fileURLToPath3(import.meta.url);
1823
+ var __dirname3 = dirname4(__filename3);
1035
1824
  var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
1036
1825
  "node_modules",
1037
1826
  ".git",
@@ -1052,46 +1841,70 @@ var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
1052
1841
  "docs",
1053
1842
  "scripts"
1054
1843
  ]);
1055
- var SKILL_SCAN_DIRS = [
1056
- "ops",
1057
- "model",
1058
- "graph",
1059
- "infra",
1060
- "ops-lab"
1061
- ];
1062
- var PLUGIN_SCAN_DIRS = [
1063
- "plugins-official",
1064
- "plugins-community"
1065
- ];
1844
+ function loadScanConfig() {
1845
+ const configPath = join7(__dirname3, "config", "repository.yaml");
1846
+ try {
1847
+ const config = parseYaml2(readFileSync5(configPath, "utf-8"));
1848
+ const ttlHours = config.scanCacheTtlHours || 24;
1849
+ return {
1850
+ skillDirs: config.scanDirs || ["ops", "model", "graph", "infra", "ops-lab", "runtime"],
1851
+ pluginDirs: config.pluginDirs || ["plugins-official", "plugins-community"],
1852
+ cacheTtlMs: ttlHours * 60 * 60 * 1e3
1853
+ };
1854
+ } catch {
1855
+ return {
1856
+ skillDirs: ["ops", "model", "graph", "infra", "ops-lab", "runtime"],
1857
+ pluginDirs: ["plugins-official", "plugins-community"],
1858
+ cacheTtlMs: 24 * 60 * 60 * 1e3
1859
+ };
1860
+ }
1861
+ }
1862
+ var scanConfig = loadScanConfig();
1863
+ var SKILL_SCAN_DIRS = scanConfig.skillDirs;
1864
+ var PLUGIN_SCAN_DIRS = scanConfig.pluginDirs;
1066
1865
  function scanSkills(repoPath) {
1067
1866
  const skills = [];
1068
1867
  const seen = /* @__PURE__ */ new Set();
1069
1868
  for (const dir of SKILL_SCAN_DIRS) {
1070
- const fullPath = join5(repoPath, dir);
1071
- if (!existsSync5(fullPath)) continue;
1869
+ const fullPath = join7(repoPath, dir);
1870
+ if (!existsSync8(fullPath)) continue;
1072
1871
  scanDirectory(fullPath, repoPath, dir, skills, seen);
1073
1872
  }
1074
1873
  for (const pluginDir of PLUGIN_SCAN_DIRS) {
1075
- const pluginParent = join5(repoPath, pluginDir);
1076
- if (!existsSync5(pluginParent)) continue;
1874
+ const pluginParent = join7(repoPath, pluginDir);
1875
+ if (!existsSync8(pluginParent)) continue;
1077
1876
  for (const plugin of safeReaddir(pluginParent)) {
1078
- const pluginPath = join5(pluginParent, plugin);
1877
+ const pluginPath = join7(pluginParent, plugin);
1079
1878
  if (!isDirectory(pluginPath)) continue;
1080
- const skillsDir = join5(pluginPath, "skills");
1081
- if (existsSync5(skillsDir)) {
1879
+ const skillsDir = join7(pluginPath, "skills");
1880
+ if (existsSync8(skillsDir)) {
1082
1881
  scanDirectory(skillsDir, repoPath, `${pluginDir}/${plugin}/skills`, skills, seen);
1083
1882
  }
1084
- const skillDir = join5(pluginPath, "skill");
1085
- if (existsSync5(skillDir)) {
1883
+ const skillDir = join7(pluginPath, "skill");
1884
+ if (existsSync8(skillDir)) {
1086
1885
  scanDirectory(skillDir, repoPath, `${pluginDir}/${plugin}/skill`, skills, seen);
1087
1886
  }
1887
+ const rootSkillMd = join7(pluginPath, "SKILL.md");
1888
+ const initSh = join7(pluginPath, "init.sh");
1889
+ if (existsSync8(rootSkillMd) && !existsSync8(initSh)) {
1890
+ const frontmatter = parseFrontmatter(rootSkillMd);
1891
+ if (frontmatter && !seen.has(frontmatter.name)) {
1892
+ seen.add(frontmatter.name);
1893
+ skills.push({
1894
+ id: frontmatter.name,
1895
+ description: frontmatter.description,
1896
+ source: `${pluginDir}/${plugin}`,
1897
+ filePath: rootSkillMd
1898
+ });
1899
+ }
1900
+ }
1088
1901
  }
1089
1902
  }
1090
1903
  return skills;
1091
1904
  }
1092
1905
  function parseFrontmatter(filePath) {
1093
1906
  try {
1094
- const content = readFileSync3(filePath, "utf-8");
1907
+ const content = readFileSync5(filePath, "utf-8");
1095
1908
  const lines = content.split("\n");
1096
1909
  if (lines.length < 3) return null;
1097
1910
  if (lines[0].trim() !== "---") return null;
@@ -1104,11 +1917,15 @@ function parseFrontmatter(filePath) {
1104
1917
  }
1105
1918
  if (endIndex === -1) return null;
1106
1919
  const yamlBlock = lines.slice(1, endIndex).map((l) => l.replace(/\r$/, "")).join("\n");
1107
- const parsed = parseYaml(yamlBlock);
1920
+ const parsed = parseYaml2(yamlBlock);
1108
1921
  if (!parsed || typeof parsed !== "object") return null;
1109
1922
  if (!parsed.name || typeof parsed.name !== "string") return null;
1923
+ const trimmedName = parsed.name.trim();
1924
+ if (!trimmedName || /[\/\\]|\.\./.test(trimmedName) || !/^[a-zA-Z0-9._-]+$/.test(trimmedName)) {
1925
+ return null;
1926
+ }
1110
1927
  return {
1111
- name: parsed.name.trim(),
1928
+ name: trimmedName,
1112
1929
  description: (parsed.description || "").trim()
1113
1930
  };
1114
1931
  } catch {
@@ -1117,17 +1934,17 @@ function parseFrontmatter(filePath) {
1117
1934
  }
1118
1935
  function getCurrentCommit(repoPath) {
1119
1936
  try {
1120
- const headFile = join5(repoPath, ".git", "HEAD");
1121
- if (!existsSync5(headFile)) return "unknown";
1122
- const headContent = readFileSync3(headFile, "utf-8").trim();
1937
+ const headFile = join7(repoPath, ".git", "HEAD");
1938
+ if (!existsSync8(headFile)) return "unknown";
1939
+ const headContent = readFileSync5(headFile, "utf-8").trim();
1123
1940
  if (headContent.startsWith("ref: ")) {
1124
- const refPath = join5(repoPath, ".git", headContent.slice(5));
1125
- if (existsSync5(refPath)) {
1126
- return readFileSync3(refPath, "utf-8").trim();
1941
+ const refPath = join7(repoPath, ".git", headContent.slice(5));
1942
+ if (existsSync8(refPath)) {
1943
+ return readFileSync5(refPath, "utf-8").trim();
1127
1944
  }
1128
- const packedRefsPath = join5(repoPath, ".git", "packed-refs");
1129
- if (existsSync5(packedRefsPath)) {
1130
- const packedRefs = readFileSync3(packedRefsPath, "utf-8");
1945
+ const packedRefsPath = join7(repoPath, ".git", "packed-refs");
1946
+ if (existsSync8(packedRefsPath)) {
1947
+ const packedRefs = readFileSync5(packedRefsPath, "utf-8");
1131
1948
  const refName = headContent.slice(5);
1132
1949
  const lines = packedRefs.split("\n");
1133
1950
  for (const line of lines) {
@@ -1145,12 +1962,12 @@ function getCurrentCommit(repoPath) {
1145
1962
  function readScanCache() {
1146
1963
  try {
1147
1964
  const cachePath = getCachePath();
1148
- if (!existsSync5(cachePath)) return null;
1149
- const content = readFileSync3(cachePath, "utf-8");
1965
+ if (!existsSync8(cachePath)) return null;
1966
+ const content = readFileSync5(cachePath, "utf-8");
1150
1967
  const cache = JSON.parse(content);
1151
1968
  if (!cache.skills || !cache.timestamp) return null;
1152
1969
  const age = Date.now() - cache.timestamp;
1153
- if (age > 24 * 60 * 60 * 1e3) return null;
1970
+ if (age > scanConfig.cacheTtlMs) return null;
1154
1971
  return cache;
1155
1972
  } catch {
1156
1973
  return null;
@@ -1159,39 +1976,24 @@ function readScanCache() {
1159
1976
  function writeScanCache(cache) {
1160
1977
  try {
1161
1978
  const cachePath = getCachePath();
1162
- const cacheDir = dirname(cachePath);
1163
- if (!existsSync5(cacheDir)) {
1979
+ const cacheDir = dirname4(cachePath);
1980
+ if (!existsSync8(cacheDir)) {
1164
1981
  mkdirSync2(cacheDir, { recursive: true });
1165
1982
  }
1166
- writeFileSync2(cachePath, JSON.stringify(cache, null, 2));
1983
+ atomicWriteFileSync(cachePath, JSON.stringify(cache, null, 2));
1167
1984
  } catch {
1168
1985
  }
1169
1986
  }
1170
1987
  function getCachePath() {
1171
- const homeDir = process.env.HOME || process.env.USERPROFILE || "/tmp";
1172
- return join5(homeDir, ".cannbot", "scan-cache.json");
1988
+ return join7(getCannbotConfigDir(), "scan-cache.json");
1173
1989
  }
1174
1990
  function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1175
- const selfSkillMd = join5(dirPath, "SKILL.md");
1176
- if (existsSync5(selfSkillMd)) {
1177
- const frontmatter = parseFrontmatter(selfSkillMd);
1178
- if (frontmatter && !seen.has(frontmatter.name)) {
1179
- seen.add(frontmatter.name);
1180
- skills.push({
1181
- id: frontmatter.name,
1182
- description: frontmatter.description,
1183
- source: sourcePrefix,
1184
- filePath: selfSkillMd
1185
- });
1186
- }
1187
- return;
1188
- }
1189
1991
  for (const entry of safeReaddir(dirPath)) {
1190
- const fullPath = join5(dirPath, entry);
1992
+ const fullPath = join7(dirPath, entry);
1191
1993
  if (EXCLUDE_DIRS.has(entry)) continue;
1192
1994
  if (!isDirectory(fullPath)) continue;
1193
- const skillMd = join5(fullPath, "SKILL.md");
1194
- if (existsSync5(skillMd)) {
1995
+ const skillMd = join7(fullPath, "SKILL.md");
1996
+ if (existsSync8(skillMd)) {
1195
1997
  const frontmatter = parseFrontmatter(skillMd);
1196
1998
  if (frontmatter && !seen.has(frontmatter.name)) {
1197
1999
  seen.add(frontmatter.name);
@@ -1204,38 +2006,32 @@ function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1204
2006
  }
1205
2007
  continue;
1206
2008
  }
1207
- scanDirectory(fullPath, repoPath, sourcePrefix, skills, seen);
2009
+ scanDirectory(fullPath, repoPath, sourcePrefix + "/" + entry, skills, seen);
1208
2010
  }
1209
2011
  }
1210
2012
  function safeReaddir(dir) {
1211
2013
  try {
1212
- return readdirSync(dir);
2014
+ return readdirSync2(dir);
1213
2015
  } catch {
1214
2016
  return [];
1215
2017
  }
1216
2018
  }
1217
- function isDirectory(path) {
1218
- try {
1219
- return statSync(path).isDirectory();
1220
- } catch {
1221
- return false;
1222
- }
1223
- }
1224
2019
 
1225
2020
  // src/core/skill-registry.ts
1226
2021
  var CATEGORY_DEFS = [
1227
- { id: "knowledge", name: "\u77E5\u8BC6\u4E0E\u53C2\u8003" },
1228
- { id: "env-tools", name: "\u73AF\u5883\u4E0E\u5DE5\u5177" },
1229
- { id: "debug", name: "\u8C03\u8BD5\u4E0E\u8BCA\u65AD" },
1230
- { id: "testing", name: "\u6D4B\u8BD5\u4E0E\u8D28\u91CF" },
1231
- { id: "ascendc", name: "AscendC \u5F00\u53D1" },
1232
- { id: "pypto", name: "PyPTO \u5F00\u53D1" },
1233
- { id: "tilelang", name: "TileLang \u5F00\u53D1" },
1234
- { id: "triton", name: "Triton \u5F00\u53D1" },
1235
- { id: "model", name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316" },
1236
- { id: "graph", name: "\u56FE\u6A21\u5F0F" },
1237
- { id: "platform", name: "\u5E73\u53F0\u5DE5\u5177" },
1238
- { id: "other", name: "\u5176\u4ED6 Skills" }
2022
+ { id: "knowledge", name: "\u77E5\u8BC6\u4E0E\u53C2\u8003", nameEn: "Knowledge & Reference" },
2023
+ { id: "env-tools", name: "\u73AF\u5883\u4E0E\u5DE5\u5177", nameEn: "Environment & Tools" },
2024
+ { id: "debug", name: "\u8C03\u8BD5\u4E0E\u8BCA\u65AD", nameEn: "Debug & Diagnostics" },
2025
+ { id: "testing", name: "\u6D4B\u8BD5\u4E0E\u8D28\u91CF", nameEn: "Testing & Quality" },
2026
+ { id: "ascendc", name: "AscendC \u5F00\u53D1", nameEn: "AscendC Development" },
2027
+ { id: "pypto", name: "PyPTO \u5F00\u53D1", nameEn: "PyPTO Development" },
2028
+ { id: "tilelang", name: "TileLang \u5F00\u53D1", nameEn: "TileLang Development" },
2029
+ { id: "triton", name: "Triton \u5F00\u53D1", nameEn: "Triton Development" },
2030
+ { id: "model", name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316", nameEn: "Model Inference Optimization" },
2031
+ { id: "runtime", name: "Runtime \u8FC1\u79FB", nameEn: "Runtime Migration" },
2032
+ { id: "graph", name: "\u56FE\u6A21\u5F0F", nameEn: "Graph Mode" },
2033
+ { id: "platform", name: "\u5E73\u53F0\u5DE5\u5177", nameEn: "Platform Tools" },
2034
+ { id: "other", name: "\u5176\u4ED6 Skills", nameEn: "Other Skills" }
1239
2035
  ];
1240
2036
  var dynamicSkills = [];
1241
2037
  var initialized = false;
@@ -1243,7 +2039,8 @@ function initFromScan(scanned) {
1243
2039
  dynamicSkills = scanned.map((s) => ({
1244
2040
  id: s.id,
1245
2041
  description: s.description,
1246
- source: s.source
2042
+ source: s.source,
2043
+ filePath: s.filePath
1247
2044
  }));
1248
2045
  initialized = true;
1249
2046
  }
@@ -1319,9 +2116,7 @@ var STATIC_SKILL_CATEGORIES = [
1319
2116
  { id: "catlass-op-develop", description: "Catlass \u7B97\u5B50\u5F00\u53D1", source: "ops" },
1320
2117
  { id: "catlass-op-perf-tune", description: "Catlass \u6027\u80FD\u8C03\u4F18", source: "ops" },
1321
2118
  { id: "cuda2ascend-simt", description: "CUDA \u8FC1\u79FB\u5230 Ascend C SIMT", source: "ops-lab" },
1322
- { id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" },
1323
- { id: "ops-registry-invoke-workflow", description: "\u6CE8\u518C\u8C03\u7528\u5DE5\u4F5C\u6D41", source: "plugins-official/ops-registry-invoke" },
1324
- { id: "ops-easyasc-dsl", description: "EasyASC DSL \u7B97\u5B50\u5F00\u53D1", source: "plugins-community/ops-easyasc-dsl/skill" }
2119
+ { id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" }
1325
2120
  ]
1326
2121
  },
1327
2122
  {
@@ -1378,7 +2173,9 @@ var STATIC_SKILL_CATEGORIES = [
1378
2173
  { id: "model-infer-prefetch", description: "\u6743\u91CD\u9884\u53D6\u9002\u914D", source: "model" },
1379
2174
  { id: "model-infer-superkernel", description: "SuperKernel \u9002\u914D", source: "model" },
1380
2175
  { id: "model-infer-precision-debug", description: "NPU \u63A8\u7406\u7CBE\u5EA6\u8BCA\u65AD", source: "model" },
1381
- { id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" }
2176
+ { id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" },
2177
+ { id: "model-infer-harmony", description: "\u7AEF\u4FA7\u9E3F\u8499 ASR \u91CF\u5316\u8F6C\u6362\u4E0E\u6253\u5305", source: "model" },
2178
+ { id: "science-model-npu-migration", description: "NPU \u4EE3\u7801\u7EA7\u8FC1\u79FB\uFF08\u73AF\u5883\u95E8\u7981/\u811A\u672C\u9002\u914D/\u7CBE\u5EA6\u6027\u80FD\u5BF9\u6BD4\uFF09", source: "plugins-community/science-model-npu-migration", filePath: "plugins-community/science-model-npu-migration/SKILL.md" }
1382
2179
  ]
1383
2180
  },
1384
2181
  {
@@ -1394,6 +2191,13 @@ var STATIC_SKILL_CATEGORIES = [
1394
2191
  { id: "torch-custom-ops-guide", description: "\u81EA\u5B9A\u4E49\u7B97\u5B50\u5165\u56FE\u6307\u5357", source: "graph" }
1395
2192
  ]
1396
2193
  },
2194
+ {
2195
+ id: "runtime",
2196
+ name: "Runtime \u8FC1\u79FB",
2197
+ skills: [
2198
+ { id: "runtime_migration", description: "CUDA \u5E94\u7528\u8FC1\u79FB\u5230 CANN \u5E73\u53F0\u6307\u5357", source: "runtime" }
2199
+ ]
2200
+ },
1397
2201
  {
1398
2202
  id: "platform",
1399
2203
  name: "\u5E73\u53F0\u5DE5\u5177",
@@ -1437,9 +2241,10 @@ function getAllSkills() {
1437
2241
  }
1438
2242
  function getAllCategories() {
1439
2243
  const skills = getActiveSkills();
2244
+ const isEn = t("list_name") === "Name";
1440
2245
  return CATEGORY_DEFS.map((def) => ({
1441
2246
  id: def.id,
1442
- name: def.name,
2247
+ name: isEn ? def.nameEn : def.name,
1443
2248
  skills: skills.filter((s) => {
1444
2249
  const catId = CATEGORY_MAP[s.id];
1445
2250
  if (catId) return catId === def.id;
@@ -1449,11 +2254,27 @@ function getAllCategories() {
1449
2254
  }
1450
2255
 
1451
2256
  // src/core/repository.ts
1452
- var REPO_URL = "https://gitcode.com/cann/cannbot-skills.git";
2257
+ var __filename4 = fileURLToPath4(import.meta.url);
2258
+ var __dirname4 = dirname5(__filename4);
2259
+ function loadRepoUrl() {
2260
+ if (process.env.CANNBOT_REPO_URL) {
2261
+ return process.env.CANNBOT_REPO_URL;
2262
+ }
2263
+ const configPath = join8(__dirname4, "config", "repository.yaml");
2264
+ try {
2265
+ const config = parseYaml3(readFileSync6(configPath, "utf-8"));
2266
+ return config.repository?.url || "https://gitcode.com/cann/cannbot-skills.git";
2267
+ } catch {
2268
+ return "https://gitcode.com/cann/cannbot-skills.git";
2269
+ }
2270
+ }
2271
+ var REPO_URL = loadRepoUrl();
1453
2272
  var RepositoryManager = class {
1454
2273
  repoPath;
2274
+ customPath;
1455
2275
  constructor(customPath) {
1456
2276
  this.repoPath = customPath;
2277
+ this.customPath = customPath;
1457
2278
  }
1458
2279
  async ensureRepo() {
1459
2280
  if (this.repoPath && this.isValidRepo(this.repoPath)) {
@@ -1474,10 +2295,16 @@ var RepositoryManager = class {
1474
2295
  this.repoPath = cachedPath;
1475
2296
  return cachedPath;
1476
2297
  }
1477
- return await this.cloneRepo();
2298
+ const clonedPath = await this.cloneRepo();
2299
+ return clonedPath;
1478
2300
  }
1479
2301
  async ensureRepoAndScan() {
1480
2302
  const repoPath = await this.ensureRepo();
2303
+ const isUserProvided = !!this.customPath || !!process.env.CANNBOT_REPO_PATH;
2304
+ if (!isUserProvided) {
2305
+ await this.updateRepo();
2306
+ }
2307
+ mergeDynamicPlugins(repoPath);
1481
2308
  const cache = readScanCache();
1482
2309
  if (cache && cache.repoCommit === getCurrentCommit(repoPath)) {
1483
2310
  initFromScan(cache.skills);
@@ -1497,7 +2324,9 @@ var RepositoryManager = class {
1497
2324
  try {
1498
2325
  await execa2("git", ["pull", "--quiet"], { cwd: repoPath, timeout: 3e4 });
1499
2326
  } catch {
2327
+ logger.warn(t("repo_pull_failed"));
1500
2328
  }
2329
+ mergeDynamicPlugins(repoPath);
1501
2330
  }
1502
2331
  getRepoPath() {
1503
2332
  if (!this.repoPath) {
@@ -1506,24 +2335,25 @@ var RepositoryManager = class {
1506
2335
  return this.repoPath;
1507
2336
  }
1508
2337
  isValidRepo(path) {
1509
- if (!existsSync6(path)) return false;
1510
- const gitDir = join6(path, ".git");
1511
- const pluginsDir = join6(path, "plugins-official");
1512
- return existsSync6(gitDir) && existsSync6(pluginsDir);
2338
+ if (!existsSync9(path)) return false;
2339
+ const gitDir = join8(path, ".git");
2340
+ const pluginsDir = join8(path, "plugins-official");
2341
+ return existsSync9(gitDir) && existsSync9(pluginsDir);
1513
2342
  }
1514
2343
  async cloneRepo() {
1515
2344
  const configDir = getCannbotConfigDir();
1516
- if (!existsSync6(configDir)) {
2345
+ if (!existsSync9(configDir)) {
1517
2346
  mkdirSync3(configDir, { recursive: true });
1518
2347
  }
1519
2348
  const targetPath = getCannbotRepoPath();
1520
- if (existsSync6(targetPath)) {
2349
+ if (existsSync9(targetPath)) {
1521
2350
  if (this.isValidRepo(targetPath)) {
1522
2351
  this.repoPath = targetPath;
1523
2352
  return targetPath;
1524
2353
  }
1525
2354
  }
1526
2355
  try {
2356
+ logger.info(t("repo_cloning"));
1527
2357
  await execa2(
1528
2358
  "git",
1529
2359
  ["clone", "--depth", "1", REPO_URL, targetPath],
@@ -1533,7 +2363,7 @@ var RepositoryManager = class {
1533
2363
  return targetPath;
1534
2364
  } catch (error) {
1535
2365
  throw new Error(
1536
- `Failed to clone repository: ${error instanceof Error ? error.message : "Unknown error"}`
2366
+ `Failed to clone repository: ${error instanceof Error ? error.message : t("error_unknown")}`
1537
2367
  );
1538
2368
  }
1539
2369
  }
@@ -1543,102 +2373,102 @@ function createRepositoryManager(customPath) {
1543
2373
  }
1544
2374
 
1545
2375
  // src/core/installer.ts
1546
- import { existsSync as existsSync9 } from "fs";
1547
- import { join as join9 } from "path";
1548
- import { execa as execa3 } from "execa";
2376
+ import { existsSync as existsSync13 } from "fs";
2377
+ import { join as join12, basename as basename2 } from "path";
2378
+ import { execa as execa4, execaSync } from "execa";
1549
2379
 
1550
2380
  // src/core/record.ts
1551
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, lstatSync, unlinkSync } from "fs";
1552
- import { join as join7 } from "path";
2381
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
2382
+ import { join as join9 } from "path";
1553
2383
  function getInstallsDir() {
1554
- return join7(getCannbotConfigDir(), "installs");
2384
+ return join9(getCannbotConfigDir(), "installs");
1555
2385
  }
1556
2386
  function getRecordPath(pluginId) {
1557
- return join7(getInstallsDir(), `${pluginId}.json`);
2387
+ return join9(getInstallsDir(), `${pluginId}.json`);
1558
2388
  }
1559
2389
  function readRecord(pluginId) {
1560
2390
  const recordPath = getRecordPath(pluginId);
1561
- if (!existsSync7(recordPath)) {
2391
+ if (!existsSync10(recordPath)) {
1562
2392
  return null;
1563
2393
  }
1564
2394
  try {
1565
- const content = readFileSync4(recordPath, "utf-8");
2395
+ const content = readFileSync7(recordPath, "utf-8");
1566
2396
  return JSON.parse(content);
1567
2397
  } catch {
2398
+ logger.warn(t("record_corrupted").replace("{file}", recordPath));
1568
2399
  return null;
1569
2400
  }
1570
2401
  }
1571
2402
  function writeRecord(record) {
1572
2403
  const installsDir = getInstallsDir();
1573
- if (!existsSync7(installsDir)) {
2404
+ if (!existsSync10(installsDir)) {
1574
2405
  mkdirSync4(installsDir, { recursive: true });
1575
2406
  }
1576
2407
  const recordPath = getRecordPath(record.pluginId);
1577
- writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
2408
+ atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
1578
2409
  }
1579
2410
  function deleteRecord(pluginId) {
1580
2411
  const recordPath = getRecordPath(pluginId);
1581
- if (existsSync7(recordPath)) {
1582
- unlinkSync(recordPath);
2412
+ if (existsSync10(recordPath)) {
2413
+ unlinkSync2(recordPath);
1583
2414
  }
1584
2415
  }
1585
- function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest) {
2416
+ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest, externalRepoNames) {
1586
2417
  const files = [];
1587
2418
  const directories = [];
1588
2419
  if (manifest) {
1589
- const skillsDir = join7(configRoot, "skills");
1590
- if (existsSync7(skillsDir)) {
2420
+ const skillsDir = join9(configRoot, "skills");
2421
+ if (existsSync10(skillsDir)) {
1591
2422
  directories.push(skillsDir);
1592
2423
  for (const skillName of manifest.installed_skills || []) {
1593
- const skillPath = join7(skillsDir, skillName);
1594
- if (existsSync7(skillPath) || isSymlink(skillPath)) {
2424
+ const skillPath = join9(skillsDir, skillName);
2425
+ if (existsSync10(skillPath) || isSymlink(skillPath)) {
1595
2426
  files.push(skillPath);
1596
2427
  }
1597
2428
  }
1598
2429
  }
1599
- const agentsDir = join7(configRoot, "agents");
1600
- if (existsSync7(agentsDir)) {
2430
+ const agentsDir = join9(configRoot, "agents");
2431
+ if (existsSync10(agentsDir)) {
1601
2432
  directories.push(agentsDir);
1602
2433
  for (const agentName of manifest.installed_agents || []) {
1603
- const agentPath = join7(agentsDir, agentName);
1604
- const agentPathMd = join7(agentsDir, agentName + ".md");
1605
- if (existsSync7(agentPath) || isSymlink(agentPath)) {
2434
+ const agentPath = join9(agentsDir, agentName);
2435
+ const agentPathMd = join9(agentsDir, agentName + ".md");
2436
+ if (existsSync10(agentPath) || isSymlink(agentPath)) {
1606
2437
  files.push(agentPath);
1607
- } else if (existsSync7(agentPathMd) || isSymlink(agentPathMd)) {
2438
+ } else if (existsSync10(agentPathMd) || isSymlink(agentPathMd)) {
1608
2439
  files.push(agentPathMd);
1609
2440
  }
1610
2441
  }
1611
2442
  }
1612
2443
  }
1613
- const workflowsLink = join7(configRoot, "workflows");
2444
+ const workflowsLink = join9(configRoot, "workflows");
1614
2445
  if (isSymlink(workflowsLink)) {
1615
2446
  files.push(workflowsLink);
1616
2447
  }
1617
- const manifestPath = join7(configRoot, "cannbot-manifest.json");
1618
- if (existsSync7(manifestPath)) {
2448
+ const manifestPath = join9(configRoot, "cannbot-manifest.json");
2449
+ if (existsSync10(manifestPath)) {
1619
2450
  files.push(manifestPath);
1620
2451
  }
1621
- const pluginManifestPath = join7(configRoot, `${pluginId}-manifest.json`);
1622
- if (existsSync7(pluginManifestPath)) {
2452
+ const pluginManifestPath = join9(configRoot, `${pluginId}-manifest.json`);
2453
+ if (existsSync10(pluginManifestPath)) {
1623
2454
  files.push(pluginManifestPath);
1624
2455
  }
1625
2456
  const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1626
- const configFilePath = level === "project" ? join7(installPath, configFileName) : join7(configRoot, configFileName);
1627
- if (existsSync7(configFilePath)) {
2457
+ const configFilePath = level === "project" ? join9(installPath, configFileName) : join9(configRoot, configFileName);
2458
+ if (existsSync10(configFilePath)) {
1628
2459
  files.push(configFilePath);
1629
2460
  }
1630
- const repoLinks = ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
2461
+ const repoLinks = externalRepoNames && externalRepoNames.length > 0 ? externalRepoNames : ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
1631
2462
  for (const repoName of repoLinks) {
1632
- const repoLinkPath = join7(installPath, repoName);
2463
+ const repoLinkPath = join9(installPath, repoName);
1633
2464
  if (isSymlink(repoLinkPath)) {
1634
2465
  files.push(repoLinkPath);
1635
2466
  }
1636
- const repoLinkInConfig = join7(configRoot, repoName);
2467
+ const repoLinkInConfig = join9(configRoot, repoName);
1637
2468
  if (isSymlink(repoLinkInConfig)) {
1638
2469
  files.push(repoLinkInConfig);
1639
2470
  }
1640
2471
  }
1641
- directories.push(configRoot);
1642
2472
  return {
1643
2473
  pluginId,
1644
2474
  displayName,
@@ -1651,35 +2481,29 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
1651
2481
  directories
1652
2482
  };
1653
2483
  }
1654
- function isSymlink(path) {
1655
- try {
1656
- return lstatSync(path).isSymbolicLink();
1657
- } catch {
1658
- return false;
1659
- }
1660
- }
1661
2484
  function getSkillRecordPath() {
1662
- return join7(getInstallsDir(), "skills.json");
2485
+ return join9(getInstallsDir(), "skills.json");
1663
2486
  }
1664
2487
  function readSkillRecord() {
1665
2488
  const recordPath = getSkillRecordPath();
1666
- if (!existsSync7(recordPath)) {
2489
+ if (!existsSync10(recordPath)) {
1667
2490
  return {};
1668
2491
  }
1669
2492
  try {
1670
- const content = readFileSync4(recordPath, "utf-8");
2493
+ const content = readFileSync7(recordPath, "utf-8");
1671
2494
  return JSON.parse(content);
1672
2495
  } catch {
2496
+ logger.warn(t("skill_record_corrupted"));
1673
2497
  return {};
1674
2498
  }
1675
2499
  }
1676
2500
  function writeSkillRecord(record) {
1677
2501
  const installsDir = getInstallsDir();
1678
- if (!existsSync7(installsDir)) {
2502
+ if (!existsSync10(installsDir)) {
1679
2503
  mkdirSync4(installsDir, { recursive: true });
1680
2504
  }
1681
2505
  const recordPath = getSkillRecordPath();
1682
- writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
2506
+ atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
1683
2507
  }
1684
2508
  function addSkillsToRecord(skillIds, tool, level, installPath) {
1685
2509
  const record = readSkillRecord();
@@ -1715,15 +2539,18 @@ function removeSkillsFromRecord(skillIds, tool, level, installPath) {
1715
2539
  }
1716
2540
  function getInstalledSkills(tool, level, installPath) {
1717
2541
  const record = readSkillRecord();
1718
- return record[tool]?.[level]?.[installPath]?.skills || [];
2542
+ const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
2543
+ const configRoot = getConfigRoot(tool, level);
2544
+ const skillsDir = join9(configRoot, "skills");
2545
+ return recordedSkills.filter((skillId) => {
2546
+ const skillPath = join9(skillsDir, skillId);
2547
+ return existsSync10(skillPath) || isSymlink(skillPath);
2548
+ });
1719
2549
  }
1720
2550
 
1721
2551
  // src/core/backup.ts
1722
- import { existsSync as existsSync8, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync2 } from "fs";
1723
- import { join as join8 } from "path";
1724
- function getAgentsFileName(tool) {
1725
- return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
1726
- }
2552
+ import { existsSync as existsSync11, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync3 } from "fs";
2553
+ import { join as join10 } from "path";
1727
2554
  function detectCurrentPlugin(configRoot, tool) {
1728
2555
  const manifest = readManifest(configRoot);
1729
2556
  if (manifest?.team) {
@@ -1735,8 +2562,8 @@ function detectCurrentPlugin(configRoot, tool) {
1735
2562
  };
1736
2563
  }
1737
2564
  }
1738
- const agentsFile = join8(configRoot, getAgentsFileName(tool));
1739
- if (!existsSync8(agentsFile)) {
2565
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
2566
+ if (!existsSync11(agentsFile)) {
1740
2567
  return null;
1741
2568
  }
1742
2569
  const allRecords = readAllRecords();
@@ -1754,13 +2581,13 @@ function detectCurrentPlugin(configRoot, tool) {
1754
2581
  return null;
1755
2582
  }
1756
2583
  function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
1757
- const agentsFile = join8(configRoot, getAgentsFileName(tool));
1758
- if (!existsSync8(agentsFile)) {
2584
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
2585
+ if (!existsSync11(agentsFile)) {
1759
2586
  return null;
1760
2587
  }
1761
2588
  const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
1762
2589
  const backupFileName = `${getAgentsFileName(tool)}.cannbot-backup.${fromPluginId}.${timestamp}`;
1763
- const backupPath = join8(configRoot, backupFileName);
2590
+ const backupPath = join10(configRoot, backupFileName);
1764
2591
  try {
1765
2592
  copyFileSync(agentsFile, backupPath);
1766
2593
  return {
@@ -1774,7 +2601,7 @@ function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
1774
2601
  }
1775
2602
  }
1776
2603
  function findBackups(configRoot) {
1777
- if (!existsSync8(configRoot)) {
2604
+ if (!existsSync11(configRoot)) {
1778
2605
  return [];
1779
2606
  }
1780
2607
  const files = readdirSync3(configRoot);
@@ -1790,7 +2617,7 @@ function findBackups(configRoot) {
1790
2617
  const timestamp = pluginIdAndTime.substring(lastDotIndex + 1);
1791
2618
  const plugin = findPlugin(pluginId);
1792
2619
  backups.push({
1793
- filePath: join8(configRoot, file),
2620
+ filePath: join10(configRoot, file),
1794
2621
  pluginId,
1795
2622
  pluginName: plugin?.displayName || pluginId,
1796
2623
  backupTime: timestamp
@@ -1803,7 +2630,7 @@ function findBackups(configRoot) {
1803
2630
  }
1804
2631
  function restoreBackup(backupPath, configRoot, tool) {
1805
2632
  try {
1806
- const agentsFile = join8(configRoot, getAgentsFileName(tool));
2633
+ const agentsFile = join10(configRoot, getAgentsFileName(tool));
1807
2634
  copyFileSync(backupPath, agentsFile);
1808
2635
  return true;
1809
2636
  } catch {
@@ -1812,8 +2639,8 @@ function restoreBackup(backupPath, configRoot, tool) {
1812
2639
  }
1813
2640
  function deleteBackup(backupPath) {
1814
2641
  try {
1815
- if (existsSync8(backupPath)) {
1816
- unlinkSync2(backupPath);
2642
+ if (existsSync11(backupPath)) {
2643
+ unlinkSync3(backupPath);
1817
2644
  return true;
1818
2645
  }
1819
2646
  return false;
@@ -1832,104 +2659,339 @@ function formatTimestamp(date) {
1832
2659
  }
1833
2660
  function readAllRecords() {
1834
2661
  const records = [];
1835
- const plugins = [
1836
- "ops-direct-invoke",
1837
- "ops-direct-invoke-flash",
1838
- "ops-registry-invoke",
1839
- "pypto-op-orchestrator",
1840
- "triton-op-generator",
1841
- "tilelang-op-orchestrator",
1842
- "model-infer-optimize",
1843
- "catlass-op-generator",
1844
- "ops-code-reviewer",
1845
- "torch-compile"
1846
- ];
1847
- for (const pluginId of plugins) {
1848
- const record = readRecord(pluginId);
2662
+ const plugins = getAllPlugins();
2663
+ for (const plugin of plugins) {
2664
+ const record = readRecord(plugin.id);
1849
2665
  if (record) {
1850
2666
  records.push(record);
1851
2667
  }
1852
2668
  }
1853
- return records;
1854
- }
1855
-
1856
- // src/ui/backup-prompts.ts
1857
- import { select as select2 } from "@inquirer/prompts";
1858
- import chalk3 from "chalk";
1859
- async function showOverwriteWarning(currentPluginName, newPluginName) {
1860
- console.log();
1861
- console.log(chalk3.yellow(` \u26A0\uFE0F ${t("backup_detected")}`));
1862
- console.log();
1863
- console.log(` ${t("backup_current")}: ${chalk3.cyan(currentPluginName)}`);
1864
- console.log(` ${t("backup_installing")}: ${chalk3.cyan(newPluginName)}`);
1865
- console.log();
1866
- console.log(` ${t("backup_overwrite_warning")}`);
1867
- console.log(` ${t("backup_auto_create")}`);
1868
- console.log();
1869
- const choice = await select2({
1870
- message: t("backup_select_action"),
1871
- choices: [
1872
- { name: t("backup_overwrite"), value: "overwrite" },
1873
- { name: t("backup_cancel"), value: "cancel" }
1874
- ]
1875
- });
1876
- return choice;
2669
+ return records;
2670
+ }
2671
+
2672
+ // src/ui/backup-prompts.ts
2673
+ import { select as select2 } from "@inquirer/prompts";
2674
+ import chalk4 from "chalk";
2675
+ async function showOverwriteWarning(currentPluginName, newPluginName) {
2676
+ console.log();
2677
+ console.log(chalk4.yellow(` \u26A0\uFE0F ${t("backup_detected")}`));
2678
+ console.log();
2679
+ console.log(` ${t("backup_current")}: ${chalk4.cyan(currentPluginName)}`);
2680
+ console.log(` ${t("backup_installing")}: ${chalk4.cyan(newPluginName)}`);
2681
+ console.log();
2682
+ console.log(` ${t("backup_overwrite_warning")}`);
2683
+ console.log(` ${t("backup_auto_create")}`);
2684
+ console.log();
2685
+ const choice = await select2({
2686
+ message: t("backup_select_action"),
2687
+ choices: [
2688
+ { name: t("backup_overwrite"), value: "overwrite" },
2689
+ { name: t("backup_cancel"), value: "cancel" }
2690
+ ]
2691
+ });
2692
+ return choice;
2693
+ }
2694
+ async function showRestorePrompt(backups) {
2695
+ console.log();
2696
+ console.log(chalk4.cyan(` ${t("backup_restore_prompt")}`));
2697
+ console.log();
2698
+ const choices = backups.map((b) => ({
2699
+ name: `${t("backup_restore")} ${b.pluginName} (${b.backupTime})`,
2700
+ value: b.filePath
2701
+ }));
2702
+ choices.push({ name: t("backup_only_uninstall"), value: "none" });
2703
+ const choice = await select2({
2704
+ message: t("backup_select_action"),
2705
+ choices
2706
+ });
2707
+ return choice;
2708
+ }
2709
+
2710
+ // src/core/plugin-installer.ts
2711
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync4, realpathSync, copyFileSync as copyFileSync2, cpSync, writeFileSync as writeFileSync2, rmSync } from "fs";
2712
+ import { join as join11, basename } from "path";
2713
+ import { execa as execa3 } from "execa";
2714
+ function installLink(source, target) {
2715
+ try {
2716
+ const resolved = realpathSync(source);
2717
+ if (existsSync12(target) || isSymlink(target)) {
2718
+ unlinkSync4(target);
2719
+ }
2720
+ try {
2721
+ symlinkSync(resolved, target);
2722
+ return "symlink";
2723
+ } catch (e) {
2724
+ if (e.code === "EPERM") {
2725
+ cpSync(resolved, target, { recursive: true });
2726
+ return "copy";
2727
+ }
2728
+ throw e;
2729
+ }
2730
+ } catch {
2731
+ return "failed";
2732
+ }
2733
+ }
2734
+ function installConfigFile(pluginDir, configFile, tool, level, configRoot, installPath, configRootConfigLink) {
2735
+ const sourcePath = join11(pluginDir, configFile);
2736
+ if (!existsSync12(sourcePath)) return;
2737
+ const agentsFileName = getAgentsFileName(tool);
2738
+ const isProjectLevel = level === "project";
2739
+ let primaryTarget;
2740
+ if (isProjectLevel) {
2741
+ primaryTarget = join11(installPath, agentsFileName);
2742
+ } else {
2743
+ primaryTarget = join11(configRoot, agentsFileName);
2744
+ }
2745
+ try {
2746
+ const resolvedSource = realpathSync(sourcePath);
2747
+ if (existsSync12(primaryTarget) || isSymlink(primaryTarget)) {
2748
+ unlinkSync4(primaryTarget);
2749
+ }
2750
+ try {
2751
+ symlinkSync(resolvedSource, primaryTarget);
2752
+ } catch (e) {
2753
+ if (e.code === "EPERM") {
2754
+ copyFileSync2(sourcePath, primaryTarget);
2755
+ } else {
2756
+ throw e;
2757
+ }
2758
+ }
2759
+ } catch (e) {
2760
+ logger.warn(t("install_config_failed").replace("{error}", e.message));
2761
+ }
2762
+ if (isProjectLevel && primaryTarget !== join11(configRoot, agentsFileName)) {
2763
+ if (configRootConfigLink === false) return;
2764
+ const configRootTarget = join11(configRoot, agentsFileName);
2765
+ if (existsSync12(configRootTarget) || isSymlink(configRootTarget)) {
2766
+ unlinkSync4(configRootTarget);
2767
+ }
2768
+ try {
2769
+ symlinkSync(realpathSync(sourcePath), configRootTarget);
2770
+ } catch (e) {
2771
+ if (e.code === "EPERM") {
2772
+ copyFileSync2(sourcePath, configRootTarget);
2773
+ }
2774
+ }
2775
+ }
2776
+ }
2777
+ async function installExternalRepos(repos, repoPath, configRoot, installPath, level) {
2778
+ if (!repos || repos.length === 0) return;
2779
+ for (const repo of repos) {
2780
+ const targetDir = join11(repoPath, repo.dir);
2781
+ if (!existsSync12(targetDir)) {
2782
+ const args = ["clone"];
2783
+ if (repo.depth) {
2784
+ args.push("--depth", String(repo.depth));
2785
+ }
2786
+ if (repo.recursive) {
2787
+ args.push("--recursive");
2788
+ }
2789
+ args.push(repo.url, targetDir);
2790
+ try {
2791
+ await execa3("git", args, { timeout: 12e4 });
2792
+ } catch {
2793
+ logger.warn(t("install_clone_failed").replace("{url}", repo.url).replace("{dir}", repo.dir));
2794
+ try {
2795
+ rmSync(targetDir, { recursive: true, force: true });
2796
+ } catch {
2797
+ }
2798
+ continue;
2799
+ }
2800
+ }
2801
+ const linkName = basename(repo.dir);
2802
+ const resolved = realpathSync(targetDir);
2803
+ const shouldConfigRootLink = repo.configRootLink !== false && (level === "global" || repo.configRootLink === true);
2804
+ if (shouldConfigRootLink) {
2805
+ const configRootLink = join11(configRoot, linkName);
2806
+ try {
2807
+ if (existsSync12(configRootLink) || isSymlink(configRootLink)) {
2808
+ unlinkSync4(configRootLink);
2809
+ }
2810
+ try {
2811
+ symlinkSync(resolved, configRootLink);
2812
+ } catch (e) {
2813
+ if (e.code === "EPERM") {
2814
+ cpSync(resolved, configRootLink, { recursive: true });
2815
+ }
2816
+ }
2817
+ } catch {
2818
+ logger.warn(t("install_link_config_failed").replace("{name}", linkName));
2819
+ }
2820
+ }
2821
+ if (level === "project") {
2822
+ const projectLink = join11(installPath, linkName);
2823
+ try {
2824
+ if (existsSync12(projectLink) || isSymlink(projectLink)) {
2825
+ unlinkSync4(projectLink);
2826
+ }
2827
+ try {
2828
+ symlinkSync(resolved, projectLink);
2829
+ } catch (e) {
2830
+ if (e.code === "EPERM") {
2831
+ cpSync(resolved, projectLink, { recursive: true });
2832
+ }
2833
+ }
2834
+ } catch {
2835
+ logger.warn(t("install_link_project_failed").replace("{name}", linkName));
2836
+ }
2837
+ }
2838
+ }
2839
+ }
2840
+ function installWorkflows(pluginDir, configRoot) {
2841
+ const workflowsDir = join11(pluginDir, "workflows");
2842
+ if (!existsSync12(workflowsDir)) return;
2843
+ const targetPath = join11(configRoot, "workflows");
2844
+ try {
2845
+ const resolved = realpathSync(workflowsDir);
2846
+ if (existsSync12(targetPath) || isSymlink(targetPath)) {
2847
+ unlinkSync4(targetPath);
2848
+ }
2849
+ try {
2850
+ symlinkSync(resolved, targetPath);
2851
+ } catch (e) {
2852
+ if (e.code === "EPERM") {
2853
+ cpSync(resolved, targetPath, { recursive: true });
2854
+ }
2855
+ }
2856
+ } catch {
2857
+ logger.warn(t("install_link_workflows_failed"));
2858
+ }
1877
2859
  }
1878
- async function showRestorePrompt(backups) {
1879
- console.log();
1880
- console.log(chalk3.cyan(` ${t("backup_restore_prompt")}`));
1881
- console.log();
1882
- const choices = backups.map((b) => ({
1883
- name: `${t("backup_restore")} ${b.pluginName} (${b.backupTime})`,
1884
- value: b.filePath
1885
- }));
1886
- choices.push({ name: t("backup_only_uninstall"), value: "none" });
1887
- const choice = await select2({
1888
- message: t("backup_select_action"),
1889
- choices
1890
- });
1891
- return choice;
2860
+ function writeCannbotManifest(configRoot, pluginId, version, level, tool, skills, agents) {
2861
+ const manifest = {
2862
+ brand: "CANNBot",
2863
+ version,
2864
+ team: pluginId,
2865
+ level,
2866
+ tool,
2867
+ installed_skills: skills,
2868
+ installed_agents: agents,
2869
+ brand_dir: configRoot,
2870
+ install_time: (/* @__PURE__ */ new Date()).toISOString()
2871
+ };
2872
+ const manifestPath = join11(configRoot, "cannbot-manifest.json");
2873
+ writeFileSync2(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
2874
+ return manifest;
1892
2875
  }
1893
-
1894
- // src/core/installer.ts
1895
- async function installPlugin(opts) {
1896
- const plugin = getPluginById(opts.pluginId);
1897
- if (!plugin) {
2876
+ async function installViaManifest(plugin, repoPath, tool, level, installPath) {
2877
+ const cwd = installPath || process.cwd();
2878
+ const configRoot = getConfigRoot(tool, level, installPath);
2879
+ const pluginDir = join11(repoPath, plugin.dir);
2880
+ try {
2881
+ const skillsDir = join11(configRoot, "skills");
2882
+ const agentsDir = join11(configRoot, "agents");
2883
+ mkdirSync5(skillsDir, { recursive: true });
2884
+ mkdirSync5(agentsDir, { recursive: true });
2885
+ const installedSkills = [];
2886
+ const installedAgents = [];
2887
+ for (const source of plugin.installSkills || []) {
2888
+ const sourceDir = join11(repoPath, source.dir);
2889
+ if (!existsSync12(sourceDir)) {
2890
+ logger.warn(t("install_skill_source_not_found").replace("{dir}", source.dir));
2891
+ continue;
2892
+ }
2893
+ for (const skillEntry of source.skills) {
2894
+ const skillName = typeof skillEntry === "string" ? skillEntry : skillEntry.name;
2895
+ const skillAlias = typeof skillEntry === "string" ? skillName : skillEntry.as || skillName;
2896
+ const skillPath = join11(sourceDir, skillName);
2897
+ if (!existsSync12(skillPath)) {
2898
+ logger.warn(t("install_skill_not_found").replace("{name}", skillName).replace("{dir}", source.dir));
2899
+ continue;
2900
+ }
2901
+ const targetPath = join11(skillsDir, skillAlias);
2902
+ const result = installLink(skillPath, targetPath);
2903
+ if (result !== "failed") {
2904
+ installedSkills.push(skillAlias);
2905
+ } else {
2906
+ logger.warn(t("install_skill_failed").replace("{name}", skillAlias));
2907
+ }
2908
+ }
2909
+ }
2910
+ for (const agentName of plugin.installAgents || []) {
2911
+ const agentFile = agentName.endsWith(".md") ? agentName : `${agentName}.md`;
2912
+ const agentPath = join11(pluginDir, "agents", agentFile);
2913
+ if (!existsSync12(agentPath)) {
2914
+ logger.warn(t("install_agent_not_found").replace("{name}", agentFile));
2915
+ continue;
2916
+ }
2917
+ const targetPath = join11(agentsDir, agentFile);
2918
+ const result = installLink(agentPath, targetPath);
2919
+ if (result !== "failed") {
2920
+ installedAgents.push(agentFile);
2921
+ } else {
2922
+ logger.warn(t("install_agent_failed").replace("{name}", agentFile));
2923
+ }
2924
+ }
2925
+ installConfigFile(pluginDir, plugin.configFile || "AGENTS.md", tool, level, configRoot, cwd, plugin.configRootConfigLink);
2926
+ installWorkflows(pluginDir, configRoot);
2927
+ await installExternalRepos(plugin.externalRepos, repoPath, configRoot, cwd, level);
2928
+ const cannbotManifest = writeCannbotManifest(
2929
+ configRoot,
2930
+ plugin.id,
2931
+ plugin.version || "1.0.0",
2932
+ level,
2933
+ tool,
2934
+ installedSkills,
2935
+ installedAgents
2936
+ );
2937
+ return {
2938
+ success: true,
2939
+ skillsCount: installedSkills.length,
2940
+ agentsCount: installedAgents.length,
2941
+ errors: [],
2942
+ manifest: cannbotManifest
2943
+ };
2944
+ } catch (error) {
1898
2945
  return {
1899
2946
  success: false,
1900
- pluginId: opts.pluginId,
1901
2947
  skillsCount: 0,
1902
2948
  agentsCount: 0,
1903
- errors: [`Plugin not found: ${opts.pluginId}`],
1904
- warnings: []
2949
+ errors: [error instanceof Error ? error.message : t("error_unknown")],
2950
+ manifest: null
1905
2951
  };
1906
2952
  }
1907
- const scriptPath = join9(opts.repoPath, plugin.dir, plugin.script);
1908
- if (!existsSync9(scriptPath)) {
2953
+ }
2954
+
2955
+ // src/core/installer.ts
2956
+ function findShell() {
2957
+ const candidates = ["bash", "sh"];
2958
+ for (const cmd of candidates) {
2959
+ try {
2960
+ const detectCmd = process.platform === "win32" ? "where" : "which";
2961
+ execaSync(detectCmd, [cmd], { timeout: 3e3 });
2962
+ return cmd;
2963
+ } catch {
2964
+ }
2965
+ }
2966
+ return null;
2967
+ }
2968
+ async function installPlugin(opts) {
2969
+ const plugin = getPluginById(opts.pluginId);
2970
+ if (!plugin) {
1909
2971
  return {
1910
2972
  success: false,
1911
2973
  pluginId: opts.pluginId,
1912
2974
  skillsCount: 0,
1913
2975
  agentsCount: 0,
1914
- errors: [`Script not found: ${scriptPath}`],
2976
+ errors: [t("error_plugin_not_found") + ": " + opts.pluginId],
1915
2977
  warnings: []
1916
2978
  };
1917
2979
  }
1918
- const args = [opts.level, opts.tool];
1919
- if (opts.installPath) {
1920
- args.push(opts.installPath);
1921
- }
2980
+ const pluginDir = join12(opts.repoPath, plugin.dir);
1922
2981
  const cwd = opts.installPath || process.cwd();
1923
2982
  const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
1924
- const agentsFile = join9(configRoot, getAgentsFileName(opts.tool));
2983
+ const agentsFile = join12(configRoot, getAgentsFileName(opts.tool));
1925
2984
  let backupInfo = null;
1926
- if (existsSync9(agentsFile)) {
2985
+ if (existsSync13(agentsFile)) {
1927
2986
  const currentPlugin = detectCurrentPlugin(configRoot, opts.tool);
1928
2987
  if (currentPlugin && currentPlugin.pluginId !== opts.pluginId) {
1929
- const choice = await showOverwriteWarning(
1930
- currentPlugin.pluginName,
1931
- plugin.displayName
1932
- );
2988
+ let choice = "overwrite";
2989
+ if (!opts.yes) {
2990
+ choice = await showOverwriteWarning(
2991
+ currentPlugin.pluginName,
2992
+ plugin.displayName
2993
+ );
2994
+ }
1933
2995
  if (choice === "cancel") {
1934
2996
  logger.info(t("backup_cancel"));
1935
2997
  return {
@@ -1947,17 +3009,105 @@ async function installPlugin(opts) {
1947
3009
  currentPlugin.pluginId,
1948
3010
  currentPlugin.pluginName
1949
3011
  );
1950
- if (backupInfo) {
1951
- logger.success(`${t("backup_created")}: ${backupInfo.filePath}`);
3012
+ if (!backupInfo) {
3013
+ logger.error(t("backup_failed"));
3014
+ return {
3015
+ success: false,
3016
+ pluginId: opts.pluginId,
3017
+ skillsCount: 0,
3018
+ agentsCount: 0,
3019
+ errors: [t("backup_failed")],
3020
+ warnings: []
3021
+ };
3022
+ }
3023
+ logger.success(`${t("backup_created")}: ${backupInfo.filePath}`);
3024
+ }
3025
+ }
3026
+ if (plugin.installSkills && plugin.installSkills.length > 0) {
3027
+ const result = await installViaManifest(
3028
+ plugin,
3029
+ opts.repoPath,
3030
+ opts.tool,
3031
+ opts.level,
3032
+ opts.installPath
3033
+ );
3034
+ if (result.success) {
3035
+ try {
3036
+ const record = scanInstalledFiles(
3037
+ opts.pluginId,
3038
+ plugin.displayName,
3039
+ opts.tool,
3040
+ opts.level,
3041
+ cwd,
3042
+ configRoot,
3043
+ result.manifest,
3044
+ plugin.externalRepos?.map((r) => basename2(r.dir))
3045
+ );
3046
+ if (backupInfo) {
3047
+ record.backup = {
3048
+ filePath: backupInfo.filePath,
3049
+ fromPluginId: backupInfo.pluginId,
3050
+ fromPluginName: backupInfo.pluginName,
3051
+ backupTime: backupInfo.backupTime
3052
+ };
3053
+ }
3054
+ writeRecord(record);
3055
+ } catch {
3056
+ logger.warn(t("record_write_failed"));
1952
3057
  }
3058
+ return {
3059
+ success: true,
3060
+ pluginId: opts.pluginId,
3061
+ skillsCount: result.skillsCount,
3062
+ agentsCount: result.agentsCount,
3063
+ errors: result.errors,
3064
+ warnings: []
3065
+ };
3066
+ } else {
3067
+ return {
3068
+ success: false,
3069
+ pluginId: opts.pluginId,
3070
+ skillsCount: 0,
3071
+ agentsCount: 0,
3072
+ errors: result.errors,
3073
+ warnings: []
3074
+ };
1953
3075
  }
1954
3076
  }
3077
+ const scriptPath = join12(pluginDir, plugin.script);
3078
+ if (!existsSync13(scriptPath)) {
3079
+ return {
3080
+ success: false,
3081
+ pluginId: opts.pluginId,
3082
+ skillsCount: 0,
3083
+ agentsCount: 0,
3084
+ errors: [t("error_script_not_found").replace("{path}", scriptPath)],
3085
+ warnings: []
3086
+ };
3087
+ }
3088
+ const args = [opts.level, opts.tool];
3089
+ if (opts.installPath) {
3090
+ args.push(opts.installPath);
3091
+ }
1955
3092
  try {
1956
- await execa3("bash", [scriptPath, ...args], {
3093
+ const shell = findShell();
3094
+ if (!shell) {
3095
+ return {
3096
+ success: false,
3097
+ pluginId: opts.pluginId,
3098
+ skillsCount: 0,
3099
+ agentsCount: 0,
3100
+ errors: [t("error_no_shell")],
3101
+ warnings: []
3102
+ };
3103
+ }
3104
+ const execOptions = {
1957
3105
  cwd,
1958
3106
  timeout: 3e5,
1959
- stdio: "pipe"
1960
- });
3107
+ stdio: "pipe",
3108
+ input: opts.yes ? "y\n" : void 0
3109
+ };
3110
+ await execa4(shell, [scriptPath, ...args], execOptions);
1961
3111
  const manifest = readManifest(configRoot);
1962
3112
  let skillsCount = 0;
1963
3113
  let agentsCount = 0;
@@ -1976,7 +3126,8 @@ async function installPlugin(opts) {
1976
3126
  opts.level,
1977
3127
  cwd,
1978
3128
  configRoot,
1979
- manifest
3129
+ manifest,
3130
+ plugin.externalRepos?.map((r) => basename2(r.dir))
1980
3131
  );
1981
3132
  if (backupInfo) {
1982
3133
  record.backup = {
@@ -1988,6 +3139,7 @@ async function installPlugin(opts) {
1988
3139
  }
1989
3140
  writeRecord(record);
1990
3141
  } catch {
3142
+ logger.warn(t("record_write_failed"));
1991
3143
  }
1992
3144
  return {
1993
3145
  success: true,
@@ -1998,7 +3150,7 @@ async function installPlugin(opts) {
1998
3150
  warnings: []
1999
3151
  };
2000
3152
  } catch (error) {
2001
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3153
+ const errorMessage = error instanceof Error ? error.message : t("error_unknown");
2002
3154
  return {
2003
3155
  success: false,
2004
3156
  pluginId: opts.pluginId,
@@ -2011,41 +3163,46 @@ async function installPlugin(opts) {
2011
3163
  }
2012
3164
 
2013
3165
  // src/core/skill-installer.ts
2014
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync3, readdirSync as readdirSync4, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
2015
- import { join as join10 } from "path";
2016
- import { select as select3, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
2017
- import chalk4 from "chalk";
3166
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, symlinkSync as symlinkSync2, unlinkSync as unlinkSync5, readdirSync as readdirSync4, rmdirSync, realpathSync as realpathSync2 } from "fs";
3167
+ import { join as join13, dirname as dirname6 } from "path";
3168
+ import { select as select3, checkbox, Separator as Separator2 } from "@inquirer/prompts";
3169
+ import chalk5 from "chalk";
2018
3170
  import Table from "cli-table3";
2019
3171
  async function installSkills(skillIds, tool, level, repoPath) {
2020
3172
  const configRoot = getConfigRoot(tool, level);
2021
- const skillsDir = join10(configRoot, "skills");
3173
+ const skillsDir = join13(configRoot, "skills");
2022
3174
  const installPath = level === "project" ? process.cwd() : configRoot;
2023
- if (!existsSync10(skillsDir)) {
2024
- mkdirSync5(skillsDir, { recursive: true });
3175
+ if (!existsSync14(skillsDir)) {
3176
+ mkdirSync6(skillsDir, { recursive: true });
2025
3177
  }
2026
3178
  const results = [];
2027
3179
  const installedIds = [];
2028
3180
  for (const skillId of skillIds) {
2029
3181
  const skill = findSkill(skillId);
2030
3182
  if (!skill) {
2031
- results.push({ skillId, success: false, error: "Skill not found in registry" });
3183
+ results.push({ skillId, success: false, error: t("error_skill_not_found") });
2032
3184
  continue;
2033
3185
  }
2034
- const sourcePath = join10(repoPath, skill.source, skill.id);
2035
- const targetPath = join10(skillsDir, skillId);
2036
- if (!existsSync10(sourcePath)) {
2037
- results.push({ skillId, success: false, error: `Source not found: ${sourcePath}` });
3186
+ const sourcePath = resolveSkillSourcePath(skill, repoPath);
3187
+ const targetPath = join13(skillsDir, skillId);
3188
+ if (!existsSync14(sourcePath)) {
3189
+ results.push({ skillId, success: false, error: t("error_source_not_found").replace("{path}", sourcePath) });
2038
3190
  continue;
2039
3191
  }
2040
3192
  try {
2041
- if (existsSync10(targetPath) || isSymlink2(targetPath)) {
2042
- unlinkSync3(targetPath);
3193
+ const resolvedSource = realpathSync2(sourcePath);
3194
+ if (existsSync14(targetPath) || isSymlink(targetPath)) {
3195
+ unlinkSync5(targetPath);
2043
3196
  }
2044
- symlinkSync(realpathSync(sourcePath), targetPath);
3197
+ symlinkSync2(resolvedSource, targetPath);
2045
3198
  results.push({ skillId, success: true });
2046
3199
  installedIds.push(skillId);
2047
3200
  } catch (error) {
2048
- results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
3201
+ let msg = error instanceof Error ? error.message : t("error_unknown");
3202
+ if (process.platform === "win32" && msg.includes("EPERM")) {
3203
+ msg = t("error_symlink_windows");
3204
+ }
3205
+ results.push({ skillId, success: false, error: msg });
2049
3206
  }
2050
3207
  }
2051
3208
  if (installedIds.length > 0) {
@@ -2055,69 +3212,69 @@ async function installSkills(skillIds, tool, level, repoPath) {
2055
3212
  }
2056
3213
  async function uninstallSkills(skillIds, tool, level) {
2057
3214
  const configRoot = getConfigRoot(tool, level);
2058
- const skillsDir = join10(configRoot, "skills");
3215
+ const skillsDir = join13(configRoot, "skills");
2059
3216
  const installPath = level === "project" ? process.cwd() : configRoot;
2060
3217
  const results = [];
2061
3218
  const removedIds = [];
2062
3219
  for (const skillId of skillIds) {
2063
- const targetPath = join10(skillsDir, skillId);
3220
+ const targetPath = join13(skillsDir, skillId);
2064
3221
  try {
2065
- if (existsSync10(targetPath) || isSymlink2(targetPath)) {
2066
- unlinkSync3(targetPath);
3222
+ if (existsSync14(targetPath) || isSymlink(targetPath)) {
3223
+ unlinkSync5(targetPath);
2067
3224
  results.push({ skillId, success: true });
2068
3225
  removedIds.push(skillId);
2069
- logger.step(` \u79FB\u9664: ${skillId}`);
3226
+ logger.step(` ${t("uninstall_remove_label")}: ${skillId}`);
2070
3227
  } else {
2071
- results.push({ skillId, success: false, error: "Not installed" });
2072
- logger.warn(`${skillId} \u672A\u5B89\u88C5`);
3228
+ results.push({ skillId, success: false, error: t("error_not_installed") });
3229
+ logger.warn(`${skillId} ${t("uninstall_skill_not_installed")}`);
2073
3230
  }
2074
3231
  } catch (error) {
2075
- results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
3232
+ results.push({ skillId, success: false, error: error instanceof Error ? error.message : t("error_unknown") });
2076
3233
  }
2077
3234
  }
2078
3235
  if (removedIds.length > 0) {
2079
3236
  removeSkillsFromRecord(removedIds, tool, level, installPath);
2080
3237
  }
2081
- if (existsSync10(skillsDir)) {
3238
+ if (existsSync14(skillsDir)) {
2082
3239
  try {
2083
3240
  const entries = readdirSync4(skillsDir);
2084
3241
  if (entries.length === 0) {
2085
3242
  rmdirSync(skillsDir);
2086
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: skills/`);
2087
- }
2088
- } catch {
2089
- }
2090
- }
2091
- if (existsSync10(configRoot)) {
2092
- try {
2093
- const entries = readdirSync4(configRoot);
2094
- if (entries.length === 0) {
2095
- rmdirSync(configRoot);
2096
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${configRoot.split("/").pop()}/`);
3243
+ logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
2097
3244
  }
2098
3245
  } catch {
2099
3246
  }
2100
3247
  }
2101
3248
  return results;
2102
3249
  }
2103
- async function interactiveSkillSelect() {
2104
- const BACK3 = "__back__";
2105
- const CANCEL3 = "__cancel__";
3250
+ var SELECT_ALL = "__select_all__";
3251
+ var SELECT_ALL_CATEGORY = "__select_all_category__";
3252
+ var CONTINUE_SELECT = "__continue_select__";
3253
+ async function interactiveSkillSelect(tool, level) {
2106
3254
  const categories = getAllCategories();
3255
+ const allSkillIds = getAllSkills().map((s) => s.id);
3256
+ let accumulated = [];
2107
3257
  let step = 0;
2108
3258
  let selectedCategoryId = "";
2109
3259
  while (true) {
2110
3260
  switch (step) {
2111
3261
  case 0: {
2112
- printBoxTitle("[3/4] \u9009\u62E9 Skill \u7C7B\u522B");
3262
+ 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");
3263
+ printBoxTitle(titleSuffix);
2113
3264
  const categoryChoices = [
3265
+ {
3266
+ name: `>> ${t("skill_select_all").replace("{count}", String(allSkillIds.length))}`,
3267
+ value: SELECT_ALL
3268
+ },
3269
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2114
3270
  ...categories.map((cat) => ({
2115
3271
  name: `> ${cat.name} (${cat.skills.length} skills)`,
2116
3272
  value: cat.id
2117
3273
  })),
2118
3274
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2119
- { name: "<- " + t("wizard_back"), value: BACK3 },
2120
- { name: "x " + t("wizard_cancel"), value: CANCEL3 }
3275
+ ...accumulated.length > 0 ? [{ name: `>> ${t("skill_confirm_install_count").replace("{count}", String(accumulated.length))}`, value: "install" }] : [],
3276
+ { name: "<- " + t("wizard_back"), value: BACK },
3277
+ { name: "x " + t("wizard_cancel"), value: CANCEL }
2121
3278
  ];
2122
3279
  showOperationHints();
2123
3280
  selectedCategoryId = await select3({
@@ -2127,36 +3284,48 @@ async function interactiveSkillSelect() {
2127
3284
  theme: selectTheme,
2128
3285
  pageSize: 15
2129
3286
  });
2130
- if (selectedCategoryId === BACK3) return "back";
2131
- if (selectedCategoryId === CANCEL3) return "cancel";
3287
+ if (selectedCategoryId === BACK) return "back";
3288
+ if (selectedCategoryId === CANCEL) return "cancel";
3289
+ if (selectedCategoryId === SELECT_ALL) {
3290
+ return allSkillIds;
3291
+ }
3292
+ if (selectedCategoryId === "install") {
3293
+ return accumulated;
3294
+ }
2132
3295
  step = 1;
2133
3296
  break;
2134
3297
  }
2135
3298
  case 1: {
2136
3299
  const category = categories.find((c) => c.id === selectedCategoryId);
2137
3300
  if (!category) return [];
2138
- printBoxTitle(`[4/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills \u2014 ${category.name}`);
2139
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
2140
- const levels = ["project", "global"];
2141
- const installedSkills = [];
2142
- for (const tk of tools) {
2143
- for (const l of levels) {
2144
- const path = l === "project" ? process.cwd() : getConfigRoot(tk, l);
2145
- installedSkills.push(...getInstalledSkills(tk, l, path));
2146
- }
2147
- }
3301
+ printBoxTitle(t("wizard_step_4_skill_title").replace("{category}", category.name));
3302
+ const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
3303
+ const installedSkills = getInstalledSkills(tool, level, installPath);
2148
3304
  const installedSet = new Set(installedSkills);
2149
- const skillChoices = category.skills.map((skill) => {
2150
- const isInstalled = installedSet.has(skill.id);
2151
- const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : "";
2152
- return {
2153
- name: `${skill.id}${suffix} \u2014 ${skill.description}`,
2154
- value: skill.id,
2155
- checked: false
2156
- };
2157
- });
3305
+ const categorySkillIds = new Set(category.skills.map((s) => s.id));
3306
+ const accumulatedInCategory = new Set(accumulated.filter((id) => categorySkillIds.has(id)));
3307
+ const skillChoices = [
3308
+ {
3309
+ name: `>> ${t("skill_select_all_category")}`,
3310
+ value: SELECT_ALL_CATEGORY,
3311
+ checked: accumulatedInCategory.size === category.skills.length,
3312
+ description: t("skill_select_all_category")
3313
+ },
3314
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
3315
+ ...category.skills.map((skill) => {
3316
+ const isInstalled = installedSet.has(skill.id);
3317
+ const isAccumulated = accumulatedInCategory.has(skill.id);
3318
+ const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : isAccumulated ? " [+]" : "";
3319
+ return {
3320
+ name: `${chalk5.cyan(skill.id)}${suffix}`,
3321
+ value: skill.id,
3322
+ checked: isAccumulated,
3323
+ description: skill.description
3324
+ };
3325
+ })
3326
+ ];
2158
3327
  showOperationHints(true);
2159
- const selectedSkills = await checkbox2({
3328
+ const selectedSkills = await checkbox({
2160
3329
  message: t("skill_select_items"),
2161
3330
  choices: skillChoices,
2162
3331
  loop: false,
@@ -2164,11 +3333,21 @@ async function interactiveSkillSelect() {
2164
3333
  theme: checkboxTheme,
2165
3334
  pageSize: 15
2166
3335
  });
2167
- const skillIds = selectedSkills;
3336
+ let skillIds;
3337
+ if (selectedSkills.includes(SELECT_ALL_CATEGORY)) {
3338
+ skillIds = category.skills.map((s) => s.id);
3339
+ } else {
3340
+ skillIds = selectedSkills.filter((id) => id !== SELECT_ALL_CATEGORY);
3341
+ }
2168
3342
  if (skillIds.length === 0) {
2169
- const action = await select3({
2170
- message: t("wizard_no_selection"),
2171
- choices: [
3343
+ accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
3344
+ const action2 = await select3({
3345
+ message: accumulated.length > 0 ? t("skill_continue_or_install").replace("{count}", String(accumulated.length)) : t("wizard_no_selection"),
3346
+ choices: accumulated.length > 0 ? [
3347
+ { name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
3348
+ { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
3349
+ { name: "x " + t("wizard_cancel"), value: "cancel" }
3350
+ ] : [
2172
3351
  new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2173
3352
  { name: "<- " + t("wizard_back_to_reselect"), value: "back" },
2174
3353
  { name: "x " + t("wizard_cancel"), value: "cancel" }
@@ -2176,13 +3355,38 @@ async function interactiveSkillSelect() {
2176
3355
  loop: false,
2177
3356
  theme: selectTheme
2178
3357
  });
2179
- if (action === "back") {
3358
+ if (action2 === "install") return accumulated;
3359
+ if (action2 === "back") {
2180
3360
  step = 0;
2181
3361
  break;
2182
3362
  }
2183
3363
  return "cancel";
2184
3364
  }
2185
- return skillIds;
3365
+ accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
3366
+ for (const id of skillIds) {
3367
+ if (!accumulated.includes(id)) {
3368
+ accumulated.push(id);
3369
+ }
3370
+ }
3371
+ const action = await select3({
3372
+ message: t("skill_continue_or_install").replace("{count}", String(accumulated.length)),
3373
+ choices: [
3374
+ { name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
3375
+ { name: "> " + t("skill_continue_select"), value: CONTINUE_SELECT },
3376
+ new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
3377
+ { name: "x " + t("wizard_cancel"), value: "cancel" }
3378
+ ],
3379
+ loop: false,
3380
+ theme: selectTheme
3381
+ });
3382
+ if (action === "install") {
3383
+ return accumulated;
3384
+ }
3385
+ if (action === CONTINUE_SELECT) {
3386
+ step = 0;
3387
+ break;
3388
+ }
3389
+ return "cancel";
2186
3390
  }
2187
3391
  }
2188
3392
  }
@@ -2190,11 +3394,11 @@ async function interactiveSkillSelect() {
2190
3394
  function listAllSkills() {
2191
3395
  const categories = getAllCategories();
2192
3396
  console.log();
2193
- console.log(chalk4.bold(` ${t("skill_list_title")}`));
2194
- console.log(chalk4.dim(" " + "\u2500".repeat(60)));
3397
+ console.log(chalk5.bold(` ${t("skill_list_title")}`));
3398
+ console.log(chalk5.dim(" " + "\u2500".repeat(60)));
2195
3399
  for (const category of categories) {
2196
3400
  console.log();
2197
- console.log(chalk4.bold(` ${category.name}`) + chalk4.dim(` (${category.skills.length} skills)`));
3401
+ console.log(chalk5.bold(` ${category.name}`) + chalk5.dim(` (${category.skills.length} skills)`));
2198
3402
  const table = new Table({
2199
3403
  style: { head: [], border: [] },
2200
3404
  colWidths: [35, 45],
@@ -2202,51 +3406,54 @@ function listAllSkills() {
2202
3406
  });
2203
3407
  for (const skill of category.skills) {
2204
3408
  table.push([
2205
- chalk4.cyan(skill.id),
3409
+ chalk5.cyan(skill.id),
2206
3410
  skill.description
2207
3411
  ]);
2208
3412
  }
2209
3413
  console.log(table.toString());
2210
3414
  }
2211
3415
  console.log();
2212
- console.log(chalk4.dim(` \u5171 ${getAllSkills().length} \u4E2A Skills`));
3416
+ console.log(chalk5.dim(` ${t("skill_total_count").replace("{count}", String(getAllSkills().length))}`));
2213
3417
  console.log();
2214
3418
  }
2215
- function isSymlink2(path) {
2216
- try {
2217
- return lstatSync2(path).isSymbolicLink();
2218
- } catch {
2219
- return false;
3419
+ function resolveSkillSourcePath(skill, repoPath) {
3420
+ if (skill.filePath) {
3421
+ const absPath = skill.filePath.startsWith("/") ? skill.filePath : join13(repoPath, skill.filePath);
3422
+ if (existsSync14(absPath)) {
3423
+ return dirname6(absPath);
3424
+ }
2220
3425
  }
3426
+ return join13(repoPath, skill.source, skill.id);
2221
3427
  }
2222
3428
 
2223
3429
  // src/ui/display.ts
2224
- import chalk5 from "chalk";
3430
+ import chalk6 from "chalk";
2225
3431
  import Table2 from "cli-table3";
2226
3432
  function printPluginList(plugins, installed) {
2227
3433
  const table = new Table2({
2228
3434
  head: [
2229
- chalk5.cyan(t("list_id")),
2230
- chalk5.cyan(t("list_name")),
2231
- chalk5.cyan(t("list_status")),
2232
- chalk5.cyan(t("list_skills")),
2233
- chalk5.cyan(t("list_agents"))
3435
+ chalk6.cyan(t("list_id")),
3436
+ chalk6.cyan(t("list_name")),
3437
+ chalk6.cyan(t("list_status")),
3438
+ chalk6.cyan(t("list_description"))
2234
3439
  ],
2235
- style: { head: [], border: [] }
3440
+ style: { head: [], border: [] },
3441
+ colWidths: [6, 32, 8, 50],
3442
+ wordWrap: true
2236
3443
  });
2237
3444
  plugins.forEach((plugin, index) => {
2238
3445
  const isInstalled = installed.has(plugin.id);
2239
- const status = isInstalled ? chalk5.green(`\u2713 ${t("status_installed")}`) : chalk5.dim(`\u2014`);
3446
+ const status = isInstalled ? chalk6.green(`\u2713 ${t("status_installed")}`) : chalk6.dim(`\u2014`);
2240
3447
  table.push([
2241
3448
  String(index + 1),
2242
3449
  plugin.displayName,
2243
3450
  status,
2244
- String(plugin.skills),
2245
- String(plugin.agents)
3451
+ plugin.description || chalk6.dim("\u2014")
2246
3452
  ]);
2247
3453
  });
2248
3454
  console.log();
2249
- console.log(chalk5.bold(` ${t("list_title")} (${plugins.length} \u4E2A)`));
3455
+ const countSuffix = t("count_suffix");
3456
+ console.log(chalk6.bold(` ${t("list_title")} (${plugins.length}${countSuffix ? " " + countSuffix : ""})`));
2250
3457
  console.log(table.toString());
2251
3458
  console.log();
2252
3459
  }
@@ -2255,7 +3462,7 @@ function printInstallSummary(results) {
2255
3462
  const totalCount = results.length;
2256
3463
  console.log();
2257
3464
  console.log(
2258
- chalk5.bold(
3465
+ chalk6.bold(
2259
3466
  ` ${t("install_done")}! ${successCount}/${totalCount} ${t("install_success")}`
2260
3467
  )
2261
3468
  );
@@ -2263,10 +3470,10 @@ function printInstallSummary(results) {
2263
3470
  for (const result of results) {
2264
3471
  if (result.success) {
2265
3472
  console.log(
2266
- chalk5.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
3473
+ chalk6.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
2267
3474
  );
2268
3475
  } else {
2269
- console.log(chalk5.red(" \u2717") + ` ${result.displayName}`);
3476
+ console.log(chalk6.red(" \u2717") + ` ${result.displayName}`);
2270
3477
  }
2271
3478
  }
2272
3479
  console.log();
@@ -2279,22 +3486,25 @@ function printEnhancedSummary(results, tool, configRoot) {
2279
3486
  return;
2280
3487
  }
2281
3488
  const toolName = getToolDisplayName(tool);
3489
+ const countSuffix = t("count_suffix");
2282
3490
  const lines = [
2283
- chalk5.bold(` ${t("install_done")}!`),
3491
+ chalk6.bold(` ${t("install_done")}!`),
2284
3492
  "",
2285
- ` ${chalk5.dim(t("install_installed_to") + ":")} ${configRoot}`,
2286
- ` ${chalk5.dim(t("install_skills_count") + ":")} ${totalSkills} \u4E2A ${chalk5.dim("|")} ${chalk5.dim(t("install_agents_count") + ":")} ${totalAgents} \u4E2A`,
3493
+ ` ${chalk6.dim(t("install_installed_to") + ":")} ${configRoot}`,
3494
+ ` ${chalk6.dim(t("install_skills_count") + ":")} ${totalSkills}${countSuffix ? " " + countSuffix : ""} ${chalk6.dim("|")} ${chalk6.dim(t("install_agents_count") + ":")} ${totalAgents}${countSuffix ? " " + countSuffix : ""}`,
2287
3495
  "",
2288
- chalk5.bold(` ${t("install_enhanced_next_steps")}:`),
2289
- ` ${chalk5.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk5.green(toolName.toLowerCase())}`,
2290
- ` ${chalk5.cyan("2.")} ${t("install_enhanced_try")}: ${chalk5.green("\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50")}`,
2291
- ` ${chalk5.cyan("3.")} ${t("install_enhanced_more")}: ${chalk5.green("install-helper list")}`,
2292
- ` ${chalk5.cyan("4.")} ${t("install_enhanced_check")}: ${chalk5.green("install-helper doctor")}`,
3496
+ chalk6.bold(` ${t("install_enhanced_next_steps")}:`),
3497
+ ` ${chalk6.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk6.green(toolName.toLowerCase())}`,
3498
+ ` ${chalk6.cyan("2.")} ${t("install_enhanced_try")}: ${chalk6.green(t("try_prompt"))}`,
3499
+ ` ${chalk6.cyan("3.")} ${t("install_enhanced_more")}: ${chalk6.green("install-helper list")}`,
3500
+ ` ${chalk6.cyan("4.")} ${t("install_enhanced_check")}: ${chalk6.green("install-helper doctor")}`,
2293
3501
  ""
2294
3502
  ];
2295
3503
  if (successResults.length === 1) {
3504
+ const plugin = findPlugin(successResults[0].pluginId);
3505
+ const docsPath = plugin ? `${plugin.dir}/quickstart.md` : `${successResults[0].pluginId}/quickstart.md`;
2296
3506
  lines.push(
2297
- ` ${chalk5.dim(t("install_enhanced_docs"))}: ${chalk5.dim(`plugins-official/${successResults[0].pluginId}/quickstart.md`)}`
3507
+ ` ${chalk6.dim(t("install_enhanced_docs"))}: ${chalk6.dim(docsPath)}`
2298
3508
  );
2299
3509
  }
2300
3510
  console.log();
@@ -2305,15 +3515,13 @@ function printEnhancedSummary(results, tool, configRoot) {
2305
3515
  }
2306
3516
 
2307
3517
  // src/commands/init.ts
2308
- import { existsSync as existsSync11, readFileSync as readFileSync5 } from "fs";
2309
- import { join as join11 } from "path";
2310
- var BACK2 = "__back__";
2311
- var CANCEL2 = "__cancel__";
3518
+ import { existsSync as existsSync15, readFileSync as readFileSync8 } from "fs";
3519
+ import { join as join14 } from "path";
2312
3520
  async function initCommand() {
2313
3521
  const cwd = process.cwd();
2314
- if (existsSync11(join11(cwd, "package.json")) && (() => {
3522
+ if (existsSync15(join14(cwd, "package.json")) && (() => {
2315
3523
  try {
2316
- const pkg = JSON.parse(readFileSync5(join11(cwd, "package.json"), "utf-8"));
3524
+ const pkg = JSON.parse(readFileSync8(join14(cwd, "package.json"), "utf-8"));
2317
3525
  return pkg.name === "@cannbot-ai/install-helper";
2318
3526
  } catch {
2319
3527
  return false;
@@ -2325,14 +3533,14 @@ async function initCommand() {
2325
3533
  return;
2326
3534
  }
2327
3535
  printBanner(t("wizard_title"));
2328
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
3536
+ const spinner = createSpinner(t("loading_skills_list"));
2329
3537
  spinner.start();
2330
3538
  try {
2331
3539
  const repoManager = createRepositoryManager();
2332
3540
  await repoManager.ensureRepoAndScan();
2333
- spinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
3541
+ spinner.succeed(t("loading_skills_list_complete"));
2334
3542
  } catch {
2335
- spinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
3543
+ spinner.warn(t("loading_skills_list_failed"));
2336
3544
  }
2337
3545
  let step = 0;
2338
3546
  let mode = "plugin";
@@ -2366,8 +3574,7 @@ async function initCommand() {
2366
3574
  }
2367
3575
  }
2368
3576
  async function selectMode() {
2369
- printBoxTitle("\u9009\u62E9\u5B89\u88C5\u7C7B\u578B");
2370
- const CANCEL3 = "__cancel__";
3577
+ printBoxTitle(t("wizard_select_mode_title"));
2371
3578
  const choices = [
2372
3579
  {
2373
3580
  name: `> ${t("wizard_mode_plugin")} \u2014 ${t("wizard_mode_plugin_desc")}`,
@@ -2378,7 +3585,7 @@ async function selectMode() {
2378
3585
  value: "skill"
2379
3586
  },
2380
3587
  new Separator3("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
2381
- { name: "x " + t("wizard_cancel"), value: CANCEL3 }
3588
+ { name: "x " + t("wizard_cancel"), value: CANCEL }
2382
3589
  ];
2383
3590
  showOperationHints();
2384
3591
  const result = await select4({
@@ -2387,7 +3594,7 @@ async function selectMode() {
2387
3594
  loop: false,
2388
3595
  theme: selectTheme
2389
3596
  });
2390
- if (result === CANCEL3) return null;
3597
+ if (result === CANCEL) return null;
2391
3598
  return result;
2392
3599
  }
2393
3600
  async function pluginInstallFlow() {
@@ -2398,10 +3605,10 @@ async function pluginInstallFlow() {
2398
3605
  return "done";
2399
3606
  }
2400
3607
  const repoManager = createRepositoryManager();
2401
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
3608
+ const spinner = createSpinner(t("loading_plugin_data"));
2402
3609
  spinner.start();
2403
3610
  const repoPath = await repoManager.ensureRepo();
2404
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
3611
+ spinner.succeed(t("loading_plugin_data_complete"));
2405
3612
  const allPlugins = getAllPlugins();
2406
3613
  const selectedPlugins = answers.plugins.map(
2407
3614
  (id) => allPlugins.find((p) => p.id === id)
@@ -2464,11 +3671,11 @@ async function skillInstallFlow() {
2464
3671
  }
2465
3672
  case 1: {
2466
3673
  const result = await stepLevel();
2467
- if (result === BACK2) {
3674
+ if (result === BACK) {
2468
3675
  step = 0;
2469
3676
  break;
2470
3677
  }
2471
- if (result === CANCEL2) {
3678
+ if (result === CANCEL) {
2472
3679
  logger.info(t("init_cancelled"));
2473
3680
  return "done";
2474
3681
  }
@@ -2477,7 +3684,7 @@ async function skillInstallFlow() {
2477
3684
  break;
2478
3685
  }
2479
3686
  case 2: {
2480
- const selectedSkills = await interactiveSkillSelect();
3687
+ const selectedSkills = await interactiveSkillSelect(tool, level);
2481
3688
  if (selectedSkills === "back") {
2482
3689
  step = 1;
2483
3690
  break;
@@ -2487,15 +3694,15 @@ async function skillInstallFlow() {
2487
3694
  return "done";
2488
3695
  }
2489
3696
  if (selectedSkills.length === 0) {
2490
- logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Skill");
3697
+ logger.info(t("install_no_skill_selected"));
2491
3698
  return "done";
2492
3699
  }
2493
3700
  const repoManager = createRepositoryManager();
2494
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
3701
+ const spinner = createSpinner(t("loading_plugin_data"));
2495
3702
  spinner.start();
2496
3703
  const repoPath = await repoManager.ensureRepo();
2497
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2498
- logger.info(`${t("skill_install_progress")} ${chalk6.bold(selectedSkills.length)} \u4E2A Skills...`);
3704
+ spinner.succeed(t("loading_plugin_data_complete"));
3705
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk7.bold(String(selectedSkills.length)))}...`);
2499
3706
  const results = await installSkills(selectedSkills, tool, level, repoPath);
2500
3707
  let successCount = 0;
2501
3708
  let failCount = 0;
@@ -2513,10 +3720,10 @@ async function skillInstallFlow() {
2513
3720
  }
2514
3721
  const configRoot = getConfigRoot(tool, level);
2515
3722
  logger.blank();
2516
- logger.success(`${t("skill_install_done")}: ${chalk6.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk6.red(failCount + " \u5931\u8D25") : chalk6.dim(failCount + " \u5931\u8D25")}`);
3723
+ 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)))}`);
2517
3724
  logger.blank();
2518
- logger.info(`\u5B89\u88C5\u5230: ${chalk6.cyan(configRoot + "/skills/")}`);
2519
- logger.info(`\u542F\u52A8 ${chalk6.green(tool)} \u5373\u53EF\u4F7F\u7528`);
3725
+ logger.info(`${t("install_to")}: ${chalk7.cyan(join14(configRoot, "skills"))}`);
3726
+ logger.info(`${t("start_to_use").replace("{tool}", chalk7.green(tool))}`);
2520
3727
  logger.blank();
2521
3728
  return "done";
2522
3729
  }
@@ -2538,32 +3745,31 @@ async function listCommand() {
2538
3745
  }
2539
3746
 
2540
3747
  // src/commands/doctor.ts
2541
- import chalk7 from "chalk";
2542
- import { existsSync as existsSync12, readdirSync as readdirSync5, lstatSync as lstatSync3, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6 } from "fs";
2543
- import { join as join12 } from "path";
3748
+ import chalk8 from "chalk";
3749
+ import { existsSync as existsSync16, readdirSync as readdirSync5, lstatSync as lstatSync2, unlinkSync as unlinkSync6, mkdirSync as mkdirSync7 } from "fs";
3750
+ import { join as join15 } from "path";
2544
3751
  async function doctorCommand(options = {}) {
2545
3752
  console.log();
2546
- console.log(chalk7.bold(` ${t("doctor_title")}`));
2547
- console.log(chalk7.dim(" " + "\u2500".repeat(46)));
2548
- let errors = 0;
3753
+ console.log(chalk8.bold(` ${t("doctor_title")}`));
3754
+ console.log(chalk8.dim(" " + "\u2500".repeat(46)));
2549
3755
  let warnings = 0;
2550
3756
  let fixes = 0;
2551
3757
  console.log();
2552
- console.log(chalk7.bold(` ${t("doctor_tools")}`));
3758
+ console.log(chalk8.bold(` ${t("doctor_tools")}`));
2553
3759
  const detectedTools = await detectTools();
2554
3760
  const allTools = getAllTools();
2555
3761
  for (const tool of allTools) {
2556
3762
  const detected = detectedTools.find((d) => d.name === tool);
2557
3763
  if (detected) {
2558
3764
  console.log(
2559
- chalk7.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
3765
+ chalk8.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
2560
3766
  );
2561
3767
  } else {
2562
- console.log(chalk7.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 \u672A\u5B89\u88C5`);
3768
+ console.log(chalk8.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 ${t("doctor_not_installed")}`);
2563
3769
  }
2564
3770
  }
2565
3771
  console.log();
2566
- console.log(chalk7.bold(` ${t("doctor_plugins")}`));
3772
+ console.log(chalk8.bold(` ${t("doctor_plugins")}`));
2567
3773
  const plugins = getAllPlugins();
2568
3774
  const installed = scanInstalled();
2569
3775
  const installedMap = new Map(installed.map((p) => [p.id, p]));
@@ -2571,80 +3777,80 @@ async function doctorCommand(options = {}) {
2571
3777
  const inst = installedMap.get(plugin.id);
2572
3778
  if (inst) {
2573
3779
  console.log(
2574
- chalk7.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
3780
+ chalk8.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
2575
3781
  );
2576
3782
  } else {
2577
- console.log(chalk7.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 \u672A\u5B89\u88C5`);
3783
+ console.log(chalk8.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 ${t("doctor_not_installed")}`);
2578
3784
  }
2579
3785
  }
2580
3786
  console.log();
2581
- console.log(chalk7.bold(` ${t("doctor_links")}`));
3787
+ console.log(chalk8.bold(` ${t("doctor_links")}`));
2582
3788
  const primaryTool = detectedTools[0]?.name || "opencode";
2583
3789
  const configRoot = getConfigRoot(primaryTool, "project");
2584
3790
  const skillsDir = getSkillsDir(configRoot);
2585
3791
  const agentsDir = getAgentsDir(configRoot);
2586
- if (existsSync12(skillsDir)) {
3792
+ if (existsSync16(skillsDir)) {
2587
3793
  const brokenLinks = checkBrokenLinks(skillsDir);
2588
3794
  if (brokenLinks === 0) {
2589
3795
  const count = readdirSync5(skillsDir).length;
2590
- console.log(chalk7.green(" \u2713") + ` ${skillsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
3796
+ console.log(chalk8.green(" \u2713") + ` ${skillsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2591
3797
  } else {
2592
- console.log(chalk7.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
3798
+ console.log(chalk8.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2593
3799
  warnings++;
2594
3800
  if (options.fix) {
2595
3801
  const fixed = fixBrokenLinks(skillsDir);
2596
3802
  fixes += fixed;
2597
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
3803
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2598
3804
  }
2599
3805
  }
2600
3806
  } else {
2601
- console.log(chalk7.dim(" \u2014") + ` ${skillsDir} \u2014 \u4E0D\u5B58\u5728`);
3807
+ console.log(chalk8.dim(" \u2014") + ` ${skillsDir} \u2014 ${t("doctor_not_exist")}`);
2602
3808
  if (options.fix) {
2603
- mkdirSync6(skillsDir, { recursive: true });
3809
+ mkdirSync7(skillsDir, { recursive: true });
2604
3810
  fixes++;
2605
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${skillsDir}`);
3811
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
2606
3812
  }
2607
3813
  }
2608
- if (existsSync12(agentsDir)) {
3814
+ if (existsSync16(agentsDir)) {
2609
3815
  const brokenLinks = checkBrokenLinks(agentsDir);
2610
3816
  if (brokenLinks === 0) {
2611
3817
  const count = readdirSync5(agentsDir).length;
2612
- console.log(chalk7.green(" \u2713") + ` ${agentsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
3818
+ console.log(chalk8.green(" \u2713") + ` ${agentsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2613
3819
  } else {
2614
- console.log(chalk7.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
3820
+ console.log(chalk8.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2615
3821
  warnings++;
2616
3822
  if (options.fix) {
2617
3823
  const fixed = fixBrokenLinks(agentsDir);
2618
3824
  fixes += fixed;
2619
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
3825
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2620
3826
  }
2621
3827
  }
2622
3828
  } else {
2623
- console.log(chalk7.dim(" \u2014") + ` ${agentsDir} \u2014 \u4E0D\u5B58\u5728`);
3829
+ console.log(chalk8.dim(" \u2014") + ` ${agentsDir} \u2014 ${t("doctor_not_exist")}`);
2624
3830
  if (options.fix) {
2625
- mkdirSync6(agentsDir, { recursive: true });
3831
+ mkdirSync7(agentsDir, { recursive: true });
2626
3832
  fixes++;
2627
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${agentsDir}`);
3833
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", agentsDir)}`);
2628
3834
  }
2629
3835
  }
2630
3836
  console.log();
2631
- console.log(chalk7.bold(` ${t("doctor_config")}`));
3837
+ console.log(chalk8.bold(` ${t("doctor_config")}`));
2632
3838
  const configFile = getConfigFileName(primaryTool);
2633
- const configPath = join12(process.cwd(), configFile);
2634
- if (existsSync12(configPath)) {
2635
- console.log(chalk7.green(" \u2713") + ` ${configFile} \u5B58\u5728`);
3839
+ const configPath = join15(process.cwd(), configFile);
3840
+ if (existsSync16(configPath)) {
3841
+ console.log(chalk8.green(" \u2713") + ` ${configFile} ${t("doctor_config_exists")}`);
2636
3842
  } else {
2637
- console.log(chalk7.dim(" \u2014") + ` ${configFile} \u4E0D\u5B58\u5728`);
3843
+ console.log(chalk8.dim(" \u2014") + ` ${configFile} ${t("doctor_config_not_exist")}`);
2638
3844
  }
2639
3845
  console.log();
2640
- console.log(chalk7.dim(" " + "\u2500".repeat(46)));
3846
+ console.log(chalk8.dim(" " + "\u2500".repeat(46)));
2641
3847
  if (options.fix && fixes > 0) {
2642
3848
  console.log(
2643
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF, ${fixes} \u9879\u5DF2\u4FEE\u590D`
3849
+ ` ${t("doctor_result")}: ${t("doctor_result_with_fix").replace("{warnings}", String(warnings)).replace("{fixes}", String(fixes))}`
2644
3850
  );
2645
3851
  } else {
2646
3852
  console.log(
2647
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF`
3853
+ ` ${t("doctor_result")}: ${t("doctor_result_without_fix").replace("{warnings}", String(warnings))}`
2648
3854
  );
2649
3855
  }
2650
3856
  console.log();
@@ -2654,11 +3860,11 @@ function checkBrokenLinks(dir) {
2654
3860
  try {
2655
3861
  const entries = readdirSync5(dir);
2656
3862
  for (const entry of entries) {
2657
- const fullPath = join12(dir, entry);
3863
+ const fullPath = join15(dir, entry);
2658
3864
  try {
2659
- const stats = lstatSync3(fullPath);
3865
+ const stats = lstatSync2(fullPath);
2660
3866
  if (stats.isSymbolicLink()) {
2661
- if (!existsSync12(fullPath)) {
3867
+ if (!existsSync16(fullPath)) {
2662
3868
  broken++;
2663
3869
  }
2664
3870
  }
@@ -2675,12 +3881,12 @@ function fixBrokenLinks(dir) {
2675
3881
  try {
2676
3882
  const entries = readdirSync5(dir);
2677
3883
  for (const entry of entries) {
2678
- const fullPath = join12(dir, entry);
3884
+ const fullPath = join15(dir, entry);
2679
3885
  try {
2680
- const stats = lstatSync3(fullPath);
3886
+ const stats = lstatSync2(fullPath);
2681
3887
  if (stats.isSymbolicLink()) {
2682
- if (!existsSync12(fullPath)) {
2683
- unlinkSync4(fullPath);
3888
+ if (!existsSync16(fullPath)) {
3889
+ unlinkSync6(fullPath);
2684
3890
  fixed++;
2685
3891
  }
2686
3892
  }
@@ -2693,36 +3899,36 @@ function fixBrokenLinks(dir) {
2693
3899
  }
2694
3900
 
2695
3901
  // src/commands/status.ts
2696
- import chalk8 from "chalk";
3902
+ import chalk9 from "chalk";
2697
3903
  async function statusCommand() {
2698
- const plugins = getAllPlugins();
2699
3904
  const installed = scanInstalled();
2700
3905
  const installedMap = new Map(installed.map((p) => [p.id, p]));
2701
3906
  console.log();
2702
- console.log(chalk8.bold(` ${t("status_title")}`));
3907
+ console.log(chalk9.bold(` ${t("status_title")}`));
2703
3908
  console.log();
2704
3909
  if (installed.length === 0) {
2705
- console.log(chalk8.dim(` ${t("status_none")}`));
2706
- console.log(chalk8.dim(` ${t("status_hint")}`));
3910
+ console.log(chalk9.dim(` ${t("status_none")}`));
3911
+ console.log(chalk9.dim(` ${t("status_hint")}`));
2707
3912
  console.log();
2708
3913
  return;
2709
3914
  }
2710
3915
  for (const inst of installed) {
2711
3916
  console.log(
2712
- chalk8.green(" \u2713") + ` ${inst.displayName}` + chalk8.dim(` (${inst.tool}, ${inst.level})`)
3917
+ chalk9.green(" \u2713") + ` ${inst.displayName}` + chalk9.dim(` (${inst.tool}, ${inst.level})`)
2713
3918
  );
2714
3919
  console.log(
2715
- chalk8.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
3920
+ chalk9.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
2716
3921
  );
2717
- console.log(chalk8.dim(` ${inst.configRoot}`));
2718
- console.log(chalk8.dim(` ${t("status_install_time")}: ${inst.installTime}`));
3922
+ console.log(chalk9.dim(` ${inst.configRoot}`));
3923
+ console.log(chalk9.dim(` ${t("status_install_time")}: ${inst.installTime}`));
2719
3924
  console.log();
2720
3925
  }
2721
3926
  }
2722
3927
 
2723
3928
  // src/commands/install.ts
3929
+ import { join as join16 } from "path";
2724
3930
  import { confirm } from "@inquirer/prompts";
2725
- import chalk9 from "chalk";
3931
+ import chalk10 from "chalk";
2726
3932
  async function installCommand(names, options) {
2727
3933
  if (options.list) {
2728
3934
  try {
@@ -2733,47 +3939,115 @@ async function installCommand(names, options) {
2733
3939
  listAllSkills();
2734
3940
  return;
2735
3941
  }
3942
+ if (options.all) {
3943
+ const scanSpinner2 = createSpinner(t("loading_skills_list"));
3944
+ scanSpinner2.start();
3945
+ try {
3946
+ const scanRepoManager = createRepositoryManager();
3947
+ await scanRepoManager.ensureRepoAndScan();
3948
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
3949
+ } catch {
3950
+ scanSpinner2.warn(t("loading_skills_list_failed"));
3951
+ }
3952
+ const allSkills = getAllSkills();
3953
+ const skillIds = allSkills.map((s) => s.id);
3954
+ if (skillIds.length === 0) {
3955
+ logger.error(t("install_no_skill_selected"));
3956
+ return;
3957
+ }
3958
+ let tool2;
3959
+ if (options.tool) {
3960
+ tool2 = validateTool(options.tool);
3961
+ } else {
3962
+ const detected = await selectToolWithDetection();
3963
+ if (detected === "back" || detected === "cancel") {
3964
+ logger.info(t("init_cancelled"));
3965
+ return;
3966
+ }
3967
+ tool2 = detected;
3968
+ }
3969
+ const level2 = options.level ? validateLevel(options.level) : "project";
3970
+ const repoManager = createRepositoryManager();
3971
+ const spinner = createSpinner(t("loading_plugin_data"));
3972
+ spinner.start();
3973
+ const repoPath = await repoManager.ensureRepo();
3974
+ spinner.succeed(t("loading_plugin_data_complete"));
3975
+ logger.info(`${t("skill_all_install_progress").replace("{count}", chalk10.bold(String(skillIds.length)))}`);
3976
+ const results = await installSkills(skillIds, tool2, level2, repoPath);
3977
+ let successCount = 0;
3978
+ let failCount = 0;
3979
+ const total = results.length;
3980
+ for (let i = 0; i < results.length; i++) {
3981
+ const result = results[i];
3982
+ const progress = `[${i + 1}/${total}]`;
3983
+ if (result.success) {
3984
+ logger.success(`${progress} ${result.skillId}`);
3985
+ successCount++;
3986
+ } else {
3987
+ logger.error(`${progress} ${result.skillId}: ${result.error}`);
3988
+ failCount++;
3989
+ }
3990
+ }
3991
+ const configRoot = getConfigRoot(tool2, level2);
3992
+ logger.blank();
3993
+ 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)))}`);
3994
+ logger.blank();
3995
+ logger.info(`${t("install_to")}: ${chalk10.cyan(join16(configRoot, "skills"))}`);
3996
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
3997
+ logger.blank();
3998
+ return;
3999
+ }
2736
4000
  if (names.length === 0) {
2737
- const scanSpinner2 = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
4001
+ const scanSpinner2 = createSpinner(t("loading_skills_list"));
2738
4002
  scanSpinner2.start();
2739
4003
  try {
2740
4004
  const scanRepoManager = createRepositoryManager();
2741
4005
  await scanRepoManager.ensureRepoAndScan();
2742
- scanSpinner2.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
4006
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
2743
4007
  } catch {
2744
- scanSpinner2.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
4008
+ scanSpinner2.warn(t("loading_skills_list_failed"));
2745
4009
  }
2746
4010
  let tool2;
2747
4011
  if (options.tool) {
2748
- tool2 = options.tool;
4012
+ tool2 = validateTool(options.tool);
2749
4013
  } else {
2750
4014
  const detected = await selectToolWithDetection();
2751
- if (detected === "back") return;
4015
+ if (detected === "back" || detected === "cancel") {
4016
+ logger.info(t("init_cancelled"));
4017
+ return;
4018
+ }
2752
4019
  tool2 = detected;
2753
4020
  }
4021
+ const level2 = options.level ? validateLevel(options.level) : "project";
2754
4022
  let step = 1;
2755
4023
  while (true) {
2756
4024
  switch (step) {
2757
4025
  case 1: {
2758
- const selectedSkills = await interactiveSkillSelect();
4026
+ const selectedSkills = await interactiveSkillSelect(tool2, level2);
2759
4027
  if (selectedSkills === "back") {
2760
4028
  const redetected = await selectToolWithDetection();
2761
- if (redetected === "back") return;
4029
+ if (redetected === "back" || redetected === "cancel") {
4030
+ logger.info(t("init_cancelled"));
4031
+ return;
4032
+ }
2762
4033
  tool2 = redetected;
2763
4034
  break;
2764
4035
  }
4036
+ if (selectedSkills === "cancel") {
4037
+ logger.info(t("init_cancelled"));
4038
+ return;
4039
+ }
2765
4040
  if (selectedSkills.length === 0) {
2766
- logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Skill");
4041
+ logger.info(t("install_no_skill_selected"));
2767
4042
  return;
2768
4043
  }
2769
4044
  names = selectedSkills;
2770
4045
  const repoManager = createRepositoryManager();
2771
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
4046
+ const spinner = createSpinner(t("loading_plugin_data"));
2772
4047
  spinner.start();
2773
4048
  const repoPath = await repoManager.ensureRepo();
2774
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2775
- logger.info(`${t("skill_install_progress")} ${chalk9.bold(names.length)} \u4E2A Skills...`);
2776
- const level2 = options.level || "project";
4049
+ spinner.succeed(t("loading_plugin_data_complete"));
4050
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(names.length)))}...`);
2777
4051
  const results = await installSkills(names, tool2, level2, repoPath);
2778
4052
  let successCount = 0;
2779
4053
  let failCount = 0;
@@ -2791,24 +4065,24 @@ async function installCommand(names, options) {
2791
4065
  }
2792
4066
  const configRoot = getConfigRoot(tool2, level2);
2793
4067
  logger.blank();
2794
- logger.success(`${t("skill_install_done")}: ${chalk9.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk9.red(failCount + " \u5931\u8D25") : chalk9.dim(failCount + " \u5931\u8D25")}`);
4068
+ 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)))}`);
2795
4069
  logger.blank();
2796
- logger.info(`\u5B89\u88C5\u5230: ${chalk9.cyan(configRoot + "/skills/")}`);
2797
- logger.info(`\u542F\u52A8 ${chalk9.green(tool2)} \u5373\u53EF\u4F7F\u7528`);
4070
+ logger.info(`${t("install_to")}: ${chalk10.cyan(join16(configRoot, "skills"))}`);
4071
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
2798
4072
  logger.blank();
2799
4073
  return;
2800
4074
  }
2801
4075
  }
2802
4076
  }
2803
4077
  }
2804
- const scanSpinner = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
4078
+ const scanSpinner = createSpinner(t("loading_skills_list"));
2805
4079
  scanSpinner.start();
2806
4080
  try {
2807
4081
  const scanRepoManager = createRepositoryManager();
2808
4082
  await scanRepoManager.ensureRepoAndScan();
2809
- scanSpinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
4083
+ scanSpinner.succeed(t("loading_skills_list_complete"));
2810
4084
  } catch {
2811
- scanSpinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
4085
+ scanSpinner.warn(t("loading_skills_list_failed"));
2812
4086
  }
2813
4087
  const plugins = [];
2814
4088
  const skills = [];
@@ -2825,22 +4099,25 @@ async function installCommand(names, options) {
2825
4099
  }
2826
4100
  logger.error(`${t("error_plugin_not_found")}: ${name}`);
2827
4101
  const allPlugins = getAllPlugins();
2828
- logger.info("\u53EF\u7528\u63D2\u4EF6:");
4102
+ logger.info(t("available_plugins") + ":");
2829
4103
  for (const p of allPlugins) {
2830
4104
  logger.step(` ${p.id} (${p.aliases.join(", ")})`);
2831
4105
  }
2832
- logger.info(`\u53EF\u7528 Skills: ${chalk9.cyan("install-helper install --list")}`);
4106
+ logger.info(`${t("available_skills")}: ${chalk10.cyan("install-helper install --list")}`);
2833
4107
  return;
2834
4108
  }
2835
4109
  let tool;
2836
4110
  if (options.tool) {
2837
- tool = options.tool;
4111
+ tool = validateTool(options.tool);
2838
4112
  } else {
2839
4113
  const detected = await selectToolWithDetection();
2840
- if (detected === "back") return;
4114
+ if (detected === "back" || detected === "cancel") {
4115
+ logger.info(t("init_cancelled"));
4116
+ return;
4117
+ }
2841
4118
  tool = detected;
2842
4119
  }
2843
- const level = options.level || "project";
4120
+ const level = options.level ? validateLevel(options.level) : "project";
2844
4121
  if (plugins.length > 0) {
2845
4122
  const configRoot = getConfigRoot(tool, level);
2846
4123
  const manifests = readAllManifests(configRoot);
@@ -2868,16 +4145,18 @@ async function installCommand(names, options) {
2868
4145
  }
2869
4146
  if (pluginsToInstall.length > 0) {
2870
4147
  const repoManager = createRepositoryManager();
2871
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
4148
+ const spinner = createSpinner(t("loading_plugin_data"));
2872
4149
  spinner.start();
2873
4150
  const repoPath = await repoManager.ensureRepo();
2874
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
4151
+ spinner.succeed(t("loading_plugin_data_complete"));
2875
4152
  const allPlugins = getAllPlugins();
2876
4153
  const results = await installPlugins(
2877
4154
  pluginsToInstall,
2878
4155
  tool,
2879
4156
  level,
2880
- repoPath
4157
+ repoPath,
4158
+ void 0,
4159
+ options.yes
2881
4160
  );
2882
4161
  const summary = results.map((result) => {
2883
4162
  const plugin = allPlugins.find((p) => p.id === result.pluginId);
@@ -2895,11 +4174,11 @@ async function installCommand(names, options) {
2895
4174
  }
2896
4175
  if (skills.length > 0) {
2897
4176
  const repoManager = createRepositoryManager();
2898
- const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6\u6570\u636E...");
4177
+ const spinner = createSpinner(t("loading_plugin_data"));
2899
4178
  spinner.start();
2900
4179
  const repoPath = await repoManager.ensureRepo();
2901
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2902
- logger.info(`${t("skill_install_progress")} ${chalk9.bold(skills.length)} \u4E2A Skills...`);
4180
+ spinner.succeed(t("loading_plugin_data_complete"));
4181
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(skills.length)))}...`);
2903
4182
  const results = await installSkills(skills, tool, level, repoPath);
2904
4183
  let successCount = 0;
2905
4184
  let failCount = 0;
@@ -2916,14 +4195,14 @@ async function installCommand(names, options) {
2916
4195
  }
2917
4196
  }
2918
4197
  logger.blank();
2919
- logger.success(`${t("skill_install_done")}: ${chalk9.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk9.red(failCount + " \u5931\u8D25") : chalk9.dim(failCount + " \u5931\u8D25")}`);
4198
+ 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)))}`);
2920
4199
  logger.blank();
2921
- logger.info(`\u5B89\u88C5\u5230: ${chalk9.cyan(getConfigRoot(tool, level) + "/skills/")}`);
2922
- logger.info(`\u542F\u52A8 ${chalk9.green(tool)} \u5373\u53EF\u4F7F\u7528`);
4200
+ logger.info(`${t("install_to")}: ${chalk10.cyan(join16(getConfigRoot(tool, level), "skills"))}`);
4201
+ logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool))}`);
2923
4202
  logger.blank();
2924
4203
  }
2925
4204
  }
2926
- async function installPlugins(pluginIds, tool, level, repoPath) {
4205
+ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes) {
2927
4206
  const results = [];
2928
4207
  const total = pluginIds.length;
2929
4208
  for (let i = 0; i < pluginIds.length; i++) {
@@ -2935,7 +4214,9 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
2935
4214
  pluginId,
2936
4215
  tool,
2937
4216
  level,
2938
- repoPath
4217
+ repoPath,
4218
+ installPath,
4219
+ yes
2939
4220
  });
2940
4221
  if (result.success) {
2941
4222
  spinner.succeed(
@@ -2951,11 +4232,12 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
2951
4232
  }
2952
4233
 
2953
4234
  // src/commands/uninstall.ts
2954
- import { existsSync as existsSync13, unlinkSync as unlinkSync5, rmdirSync as rmdirSync2, readdirSync as readdirSync6, lstatSync as lstatSync4 } from "fs";
2955
- import chalk10 from "chalk";
4235
+ import { existsSync as existsSync17, unlinkSync as unlinkSync7, rmdirSync as rmdirSync2, readdirSync as readdirSync6 } from "fs";
4236
+ import { resolve, basename as basename3, sep } from "path";
4237
+ import chalk11 from "chalk";
2956
4238
  async function uninstallCommand(names, options) {
2957
- const tool = options.tool || "opencode";
2958
- const level = options.level || "project";
4239
+ const tool = options.tool ? validateTool(options.tool) : "opencode";
4240
+ const level = options.level ? validateLevel(options.level) : "project";
2959
4241
  const plugins = [];
2960
4242
  const skills = [];
2961
4243
  for (const name of names) {
@@ -2981,28 +4263,38 @@ async function uninstallCommand(names, options) {
2981
4263
  continue;
2982
4264
  }
2983
4265
  logger.info(`${t("uninstall_in_progress")} ${plugin.displayName}...`);
4266
+ const configRoot = getConfigRoot(record.tool, record.level);
4267
+ const allowedBases = [resolve(configRoot), resolve(record.installPath)];
4268
+ const isSafePath = (p) => {
4269
+ const resolved = resolve(p);
4270
+ return allowedBases.some((base) => resolved === base || resolved.startsWith(base + sep));
4271
+ };
2984
4272
  let removedFiles = 0;
2985
4273
  let removedDirs = 0;
2986
4274
  for (const filePath of record.files) {
4275
+ if (!isSafePath(filePath)) continue;
2987
4276
  try {
2988
- if (existsSync13(filePath) || isSymlink3(filePath)) {
2989
- unlinkSync5(filePath);
2990
- const name = filePath.split("/").pop() || filePath;
2991
- logger.step(` \u79FB\u9664: ${name}`);
4277
+ if (existsSync17(filePath) || isSymlink(filePath)) {
4278
+ unlinkSync7(filePath);
4279
+ const name = basename3(filePath);
4280
+ logger.step(` ${t("uninstall_remove_label")}: ${name}`);
2992
4281
  removedFiles++;
2993
4282
  }
2994
4283
  } catch {
2995
4284
  }
2996
4285
  }
2997
- const sortedDirs = [...record.directories].sort((a, b) => b.length - a.length);
4286
+ const sortedDirs = [...record.directories].filter(isSafePath).sort((a, b) => b.length - a.length);
2998
4287
  for (const dirPath of sortedDirs) {
2999
4288
  try {
3000
- if (existsSync13(dirPath)) {
4289
+ if (resolve(dirPath) === resolve(configRoot)) {
4290
+ continue;
4291
+ }
4292
+ if (existsSync17(dirPath)) {
3001
4293
  const entries = readdirSync6(dirPath);
3002
4294
  if (entries.length === 0) {
3003
4295
  rmdirSync2(dirPath);
3004
- const name = dirPath.split("/").pop() || dirPath;
3005
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${name}/`);
4296
+ const name = basename3(dirPath);
4297
+ logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
3006
4298
  removedDirs++;
3007
4299
  }
3008
4300
  }
@@ -3011,7 +4303,6 @@ async function uninstallCommand(names, options) {
3011
4303
  }
3012
4304
  deleteRecord(plugin.id);
3013
4305
  removeInstalledPlugin(plugin.id);
3014
- const configRoot = getConfigRoot(record.tool, record.level);
3015
4306
  const backups = findBackups(configRoot);
3016
4307
  const otherBackups = backups.filter((b) => b.pluginId !== pluginId);
3017
4308
  if (otherBackups.length > 0) {
@@ -3031,13 +4322,13 @@ async function uninstallCommand(names, options) {
3031
4322
  }
3032
4323
  const totalRemoved = removedFiles + removedDirs;
3033
4324
  if (totalRemoved > 0) {
3034
- logger.success(`${t("uninstall_removed")} ${plugin.displayName}\uFF08\u79FB\u9664 ${removedFiles} \u4E2A\u6587\u4EF6\uFF0C${removedDirs} \u4E2A\u76EE\u5F55\uFF09`);
4325
+ logger.success(t("uninstall_summary_format").replace("{name}", plugin.displayName).replace("{files}", String(removedFiles)).replace("{dirs}", String(removedDirs)));
3035
4326
  } else {
3036
4327
  logger.warn(`${plugin.displayName}${t("uninstall_files_gone")}`);
3037
4328
  }
3038
4329
  }
3039
4330
  if (skills.length > 0) {
3040
- logger.info(`${t("uninstall_skills_in_progress")} ${skills.length} \u4E2A Skills...`);
4331
+ logger.info(`${t("uninstall_skills_in_progress")} ${t("skill_count_format").replace("{count}", String(skills.length))}...`);
3041
4332
  const results = await uninstallSkills(skills, tool, level);
3042
4333
  let successCount = 0;
3043
4334
  let failCount = 0;
@@ -3049,30 +4340,26 @@ async function uninstallCommand(names, options) {
3049
4340
  }
3050
4341
  }
3051
4342
  logger.blank();
3052
- logger.success(`${t("skill_uninstall_done")}: ${chalk10.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk10.red(failCount + " \u5931\u8D25") : chalk10.dim(failCount + " \u5931\u8D25")}`);
4343
+ 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)))}`);
3053
4344
  logger.blank();
3054
4345
  }
3055
4346
  }
3056
- function isSymlink3(path) {
3057
- try {
3058
- return lstatSync4(path).isSymbolicLink();
3059
- } catch {
3060
- return false;
3061
- }
3062
- }
3063
4347
 
3064
4348
  // src/commands/update.ts
3065
- import chalk11 from "chalk";
4349
+ import chalk12 from "chalk";
3066
4350
  async function updateCommand(pluginNames, options) {
3067
4351
  let tool;
3068
4352
  if (options.tool) {
3069
- tool = options.tool;
4353
+ tool = validateTool(options.tool);
3070
4354
  } else {
3071
4355
  const detected = await selectToolWithDetection();
3072
- if (detected === "back") return;
4356
+ if (detected === "back" || detected === "cancel") {
4357
+ logger.info(t("init_cancelled"));
4358
+ return;
4359
+ }
3073
4360
  tool = detected;
3074
4361
  }
3075
- const level = options.level || "project";
4362
+ const level = options.level ? validateLevel(options.level) : "project";
3076
4363
  let pluginsToUpdate = [];
3077
4364
  if (pluginNames.length === 0) {
3078
4365
  const configRoot = getConfigRoot(tool, level);
@@ -3080,7 +4367,7 @@ async function updateCommand(pluginNames, options) {
3080
4367
  pluginsToUpdate = manifests.map((m) => m.team);
3081
4368
  if (pluginsToUpdate.length === 0) {
3082
4369
  logger.info(t("update_no_plugins"));
3083
- logger.info(`\u4F7F\u7528 ${chalk11.cyan("install-helper install <plugin>")} \u5B89\u88C5\u63D2\u4EF6`);
4370
+ logger.info(t("update_install_hint").replace("{cmd}", chalk12.cyan("install-helper install <plugin>")));
3084
4371
  return;
3085
4372
  }
3086
4373
  } else {
@@ -3096,9 +4383,7 @@ async function updateCommand(pluginNames, options) {
3096
4383
  const repoManager = createRepositoryManager();
3097
4384
  const updateSpinner = createSpinner(t("update_updating") + "...");
3098
4385
  updateSpinner.start();
3099
- await repoManager.updateRepo();
3100
- const repoPath = repoManager.getRepoPath();
3101
- await repoManager.ensureRepoAndScan();
4386
+ const repoPath = await repoManager.ensureRepoAndScan();
3102
4387
  updateSpinner.succeed(t("install_repo_ready"));
3103
4388
  const allPlugins = getAllPlugins();
3104
4389
  const results = [];
@@ -3142,42 +4427,43 @@ async function updateCommand(pluginNames, options) {
3142
4427
  }
3143
4428
 
3144
4429
  // src/commands/info.ts
3145
- import chalk12 from "chalk";
3146
- import { existsSync as existsSync14, readdirSync as readdirSync7 } from "fs";
3147
- import { join as join13 } from "path";
4430
+ import chalk13 from "chalk";
4431
+ import { existsSync as existsSync18, readdirSync as readdirSync7 } from "fs";
4432
+ import { join as join17 } from "path";
3148
4433
  async function infoCommand(pluginName) {
3149
4434
  const plugin = findPlugin(pluginName);
3150
4435
  if (!plugin) {
3151
- console.log(chalk12.red(` ${t("info_not_found")}: ${pluginName}`));
4436
+ console.log(chalk13.red(` ${t("info_not_found")}: ${pluginName}`));
3152
4437
  return;
3153
4438
  }
3154
4439
  const installed = scanInstalled();
3155
- const isInstalled = installed.some((p) => p.id === plugin.id);
3156
- const statusText = isInstalled ? chalk12.green(`\u2713 ${t("status_installed")}`) : chalk12.dim(`\u2014 ${t("status_not_installed")}`);
4440
+ const inst = installed.find((p) => p.id === plugin.id);
4441
+ const isInstalled = !!inst;
4442
+ const statusText = isInstalled ? chalk13.green(`\u2713 ${t("status_installed")}`) : chalk13.dim(`\u2014 ${t("status_not_installed")}`);
3157
4443
  console.log();
3158
- console.log(chalk12.bold(` ${plugin.displayName}`));
3159
- console.log(chalk12.dim(" " + "\u2500".repeat(40)));
4444
+ console.log(chalk13.bold(` ${plugin.displayName}`));
4445
+ console.log(chalk13.dim(" " + "\u2500".repeat(40)));
3160
4446
  console.log();
3161
- console.log(` ${chalk12.dim(t("info_description") + ":")} ${plugin.description}`);
3162
- console.log(` ${chalk12.dim("ID:")} ${plugin.id}`);
3163
- console.log(` ${chalk12.dim(t("info_status") + ":")} ${statusText}`);
3164
- console.log(` ${chalk12.dim(t("info_skills") + ":")} ${plugin.skills}`);
3165
- console.log(` ${chalk12.dim(t("info_agents") + ":")} ${plugin.agents}`);
3166
- console.log(` ${chalk12.dim(t("info_aliases") + ":")} ${plugin.aliases.join(", ")}`);
4447
+ console.log(` ${chalk13.dim(t("info_description") + ":")} ${plugin.description}`);
4448
+ console.log(` ${chalk13.dim("ID:")} ${plugin.id}`);
4449
+ console.log(` ${chalk13.dim(t("info_status") + ":")} ${statusText}`);
4450
+ console.log(` ${chalk13.dim(t("info_skills") + ":")} ${inst ? inst.skillsCount : plugin.skills}`);
4451
+ console.log(` ${chalk13.dim(t("info_agents") + ":")} ${inst ? inst.agentsCount : plugin.agents}`);
4452
+ console.log(` ${chalk13.dim(t("info_aliases") + ":")} ${plugin.aliases.join(", ")}`);
3167
4453
  console.log();
3168
4454
  try {
3169
4455
  const repoManager = createRepositoryManager();
3170
4456
  const repoPath = await repoManager.ensureRepo();
3171
- const quickstartPath = join13(repoPath, plugin.dir, "quickstart.md");
3172
- if (existsSync14(quickstartPath)) {
3173
- console.log(` ${chalk12.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
4457
+ const quickstartPath = join17(repoPath, plugin.dir, "quickstart.md");
4458
+ if (existsSync18(quickstartPath)) {
4459
+ console.log(` ${chalk13.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
3174
4460
  }
3175
- const agentsDir = join13(repoPath, plugin.dir, "agents");
3176
- if (existsSync14(agentsDir)) {
4461
+ const agentsDir = join17(repoPath, plugin.dir, "agents");
4462
+ if (existsSync18(agentsDir)) {
3177
4463
  const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md"));
3178
4464
  if (agentFiles.length > 0) {
3179
4465
  console.log();
3180
- console.log(` ${chalk12.bold("Agents:")}`);
4466
+ console.log(` ${chalk13.bold("Agents:")}`);
3181
4467
  for (const agent of agentFiles) {
3182
4468
  console.log(` \u2022 ${agent.replace(".md", "")}`);
3183
4469
  }
@@ -3189,49 +4475,80 @@ async function infoCommand(pluginName) {
3189
4475
  }
3190
4476
 
3191
4477
  // src/commands/lang.ts
3192
- import chalk13 from "chalk";
4478
+ import chalk14 from "chalk";
3193
4479
  async function langCommand(action, value) {
3194
4480
  const config = readConfig();
3195
4481
  if (!action || action === "show") {
3196
4482
  console.log();
3197
- console.log(chalk13.bold(` ${t("lang_title")}`));
3198
- console.log(chalk13.dim(" " + "\u2500".repeat(40)));
4483
+ console.log(chalk14.bold(` ${t("lang_title")}`));
4484
+ console.log(chalk14.dim(" " + "\u2500".repeat(40)));
3199
4485
  console.log();
3200
- console.log(` ${chalk13.dim(t("lang_current") + ":")} ${config.language}`);
4486
+ console.log(` ${chalk14.dim(t("lang_current") + ":")} ${config.language}`);
3201
4487
  console.log();
3202
- console.log(chalk13.dim(` ${t("lang_supported")}`));
3203
- console.log(chalk13.dim(` ${t("lang_usage")}`));
4488
+ console.log(chalk14.dim(` ${t("lang_supported")}`));
4489
+ console.log(chalk14.dim(` ${t("lang_usage")}`));
3204
4490
  console.log();
3205
4491
  return;
3206
4492
  }
3207
4493
  if (action === "set") {
3208
4494
  if (!value) {
3209
- console.log(chalk13.red(` ${t("lang_specify")}`));
4495
+ console.log(chalk14.red(` ${t("lang_specify")}`));
3210
4496
  return;
3211
4497
  }
3212
4498
  if (value !== "zh_CN" && value !== "en_US") {
3213
- console.log(chalk13.red(` ${t("lang_invalid")}`));
4499
+ console.log(chalk14.red(` ${t("lang_invalid")}`));
3214
4500
  return;
3215
4501
  }
3216
4502
  updateConfig({ language: value });
3217
4503
  setLanguage(value);
3218
4504
  console.log();
3219
- console.log(chalk13.green(` \u2713 ${t("lang_set")} ${value}`));
4505
+ console.log(chalk14.green(` \u2713 ${t("lang_set")} ${value}`));
3220
4506
  console.log();
3221
4507
  return;
3222
4508
  }
3223
- console.log(chalk13.red(` ${t("lang_unknown_action")}: ${action}`));
3224
- console.log(chalk13.dim(` ${t("lang_supported_actions")}`));
4509
+ console.log(chalk14.red(` ${t("lang_unknown_action")}: ${action}`));
4510
+ console.log(chalk14.dim(` ${t("lang_supported_actions")}`));
3225
4511
  }
3226
4512
 
3227
4513
  // src/cli.ts
4514
+ import { readFileSync as readFileSync9, existsSync as existsSync19 } from "fs";
4515
+ import { fileURLToPath as fileURLToPath5 } from "url";
4516
+ import { dirname as dirname7, join as join18 } from "path";
4517
+ function readVersion() {
4518
+ try {
4519
+ if (typeof INSTALL_HELPER_VERSION !== "undefined" && INSTALL_HELPER_VERSION) {
4520
+ return INSTALL_HELPER_VERSION;
4521
+ }
4522
+ } catch {
4523
+ }
4524
+ try {
4525
+ const __filename5 = fileURLToPath5(import.meta.url);
4526
+ const __dirname5 = dirname7(__filename5);
4527
+ const candidates = [
4528
+ join18(__dirname5, "..", "package.json"),
4529
+ join18(__dirname5, "package.json"),
4530
+ join18(process.cwd(), "package.json")
4531
+ ];
4532
+ for (const pkgPath of candidates) {
4533
+ if (existsSync19(pkgPath)) {
4534
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
4535
+ if (pkg.version && (pkg.name === "@cannbot-ai/install-helper" || pkg.name?.startsWith("@cannbot-ai/install-helper-"))) {
4536
+ return pkg.version;
4537
+ }
4538
+ }
4539
+ }
4540
+ return "0.0.0";
4541
+ } catch {
4542
+ return "0.0.0";
4543
+ }
4544
+ }
3228
4545
  function createCLI() {
3229
4546
  const config = readConfig();
3230
4547
  if (config.language) {
3231
4548
  setLanguage(config.language);
3232
4549
  }
3233
4550
  const program2 = new Command();
3234
- program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.7");
4551
+ program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version(readVersion());
3235
4552
  program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
3236
4553
  await initCommand();
3237
4554
  });
@@ -3244,7 +4561,7 @@ function createCLI() {
3244
4561
  program2.command("status").description("Show installed plugins").action(async () => {
3245
4562
  await statusCommand();
3246
4563
  });
3247
- 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) => {
4564
+ 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) => {
3248
4565
  await installCommand(names, options);
3249
4566
  });
3250
4567
  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) => {
@@ -3266,13 +4583,28 @@ function createCLI() {
3266
4583
  }
3267
4584
 
3268
4585
  // src/index.ts
3269
- process.on("uncaughtException", (error) => {
3270
- if (error.name === "ExitPromptError") {
3271
- console.log("\n\u5DF2\u53D6\u6D88\u5B89\u88C5");
4586
+ function isExitError(error) {
4587
+ if (!error) return false;
4588
+ return error.name === "ExitPromptError" || error.message?.includes("force closed") || error.message?.includes("SIGINT") || error.constructor?.name === "ExitPromptError";
4589
+ }
4590
+ function handleExit(error) {
4591
+ if (isExitError(error)) {
4592
+ console.log("\n" + t("init_cancelled"));
3272
4593
  process.exit(0);
3273
4594
  }
4595
+ }
4596
+ process.on("SIGINT", () => {
4597
+ console.log("\n" + t("init_cancelled"));
4598
+ process.exit(130);
4599
+ });
4600
+ process.on("uncaughtException", (error) => {
4601
+ handleExit(error);
3274
4602
  throw error;
3275
4603
  });
4604
+ process.on("unhandledRejection", (error) => {
4605
+ handleExit(error);
4606
+ process.exit(1);
4607
+ });
3276
4608
  var program = createCLI();
3277
4609
  program.parse();
3278
4610
  //# sourceMappingURL=index.js.map