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

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
@@ -145,251 +145,6 @@ import { join as join2 } from "path";
145
145
  import { homedir } from "os";
146
146
  import { join } from "path";
147
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
- }
221
-
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("which", [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
- }
324
-
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
148
 
394
149
  // src/utils/i18n.ts
395
150
  var zh_CN = {
@@ -454,6 +209,8 @@ var zh_CN = {
454
209
  error_tool_not_found: "\u672A\u68C0\u6D4B\u5230\u4EFB\u4F55 AI \u7F16\u7A0B\u5DE5\u5177",
455
210
  error_plugin_not_found: "\u672A\u627E\u5230\u63D2\u4EF6",
456
211
  error_repo_not_found: "\u4ED3\u5E93\u672A\u627E\u5230",
212
+ error_invalid_tool: "\u6682\u4E0D\u652F\u6301\u7684 AI \u5DE5\u5177: {tool}\n\u652F\u6301\u7684\u5DE5\u5177: {tools}",
213
+ error_invalid_level: "\u6682\u4E0D\u652F\u6301\u7684\u5B89\u88C5\u7EA7\u522B: {level}\n\u652F\u6301\u7684\u7EA7\u522B: {levels}",
457
214
  quick_start: "\u542F\u52A8\u547D\u4EE4",
458
215
  quick_start_hint: "\u8BD5\u8BD5\u8F93\u5165: \u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50",
459
216
  skill_select_category: "\u9009\u62E9 Skill \u7C7B\u522B",
@@ -525,7 +282,38 @@ var zh_CN = {
525
282
  wizard_step_4_skill_title: "[4/4] \u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills \u2014 {category}",
526
283
  install_level_project: "\u5B89\u88C5\u5230\u5F53\u524D\u76EE\u5F55\uFF08\u63A8\u8350\uFF0C\u4EC5\u5F53\u524D\u9879\u76EE\u53EF\u7528\uFF09",
527
284
  install_level_global: "\u5B89\u88C5\u5230\u5168\u5C40\uFF08\u6240\u6709\u9879\u76EE\u5171\u4EAB\uFF09",
528
- wizard_select_mode_title: "\u9009\u62E9\u5B89\u88C5\u7C7B\u578B"
285
+ wizard_select_mode_title: "\u9009\u62E9\u5B89\u88C5\u7C7B\u578B",
286
+ skill_count_format: "{count} \u4E2A Skills",
287
+ result_success_format: "{count} \u6210\u529F",
288
+ result_failed_format: "{count} \u5931\u8D25",
289
+ uninstall_remove_label: "\u79FB\u9664",
290
+ uninstall_remove_file_format: "\u79FB\u9664 {count} \u4E2A\u6587\u4EF6",
291
+ uninstall_remove_dir_format: "{count} \u4E2A\u76EE\u5F55",
292
+ uninstall_clean_empty_dir: "\u6E05\u7406\u7A7A\u76EE\u5F55",
293
+ uninstall_skill_not_installed: "\u672A\u5B89\u88C5",
294
+ available_plugins: "\u53EF\u7528\u63D2\u4EF6",
295
+ available_skills: "\u53EF\u7528 Skills",
296
+ skill_total_count: "\u5171 {count} \u4E2A Skills",
297
+ wizard_confirm_install_format: "\u5373\u5C06\u5B89\u88C5 {count} \u4E2A\u63D2\u4EF6\u5230 {tool}\uFF08{level} \u7EA7\u522B\uFF09\uFF1A",
298
+ wizard_install_path: "\u5B89\u88C5\u8DEF\u5F84",
299
+ update_install_hint: "\u4F7F\u7528 {cmd} \u5B89\u88C5\u63D2\u4EF6",
300
+ hint_select_move: "\u79FB\u52A8",
301
+ hint_select_space: "\u7A7A\u683C",
302
+ hint_select_check: "\u52FE\u9009",
303
+ hint_select_confirm: "\u786E\u8BA4",
304
+ hint_checkbox_suffix: "\u4E0D\u9009\u6309\u56DE\u8F66\u53EF\u8FD4\u56DE",
305
+ doctor_not_installed: "\u672A\u5B89\u88C5",
306
+ doctor_links_valid: "{count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548",
307
+ doctor_broken_links: "{count} \u4E2A\u5931\u6548\u94FE\u63A5",
308
+ doctor_fixed: "\u5DF2\u6E05\u7406 {count} \u4E2A\u5931\u6548\u94FE\u63A5",
309
+ doctor_not_exist: "\u4E0D\u5B58\u5728",
310
+ doctor_created: "\u5DF2\u521B\u5EFA {path}",
311
+ doctor_config_exists: "\u5B58\u5728",
312
+ doctor_config_not_exist: "\u4E0D\u5B58\u5728",
313
+ doctor_result_with_fix: "{warnings} \u4E2A\u8B66\u544A, {errors} \u4E2A\u9519\u8BEF, {fixes} \u9879\u5DF2\u4FEE\u590D",
314
+ doctor_result_without_fix: "{warnings} \u4E2A\u8B66\u544A, {errors} \u4E2A\u9519\u8BEF",
315
+ count_suffix: "\u4E2A",
316
+ try_prompt: "\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50"
529
317
  };
530
318
  var en_US = {
531
319
  wizard_title: "CANNBot Install Helper",
@@ -589,6 +377,8 @@ var en_US = {
589
377
  error_tool_not_found: "No AI coding tool detected",
590
378
  error_plugin_not_found: "Plugin not found",
591
379
  error_repo_not_found: "Repository not found",
380
+ error_invalid_tool: "Unsupported AI tool: {tool}\nSupported tools: {tools}",
381
+ error_invalid_level: "Unsupported install level: {level}\nSupported levels: {levels}",
592
382
  quick_start: "Launch command",
593
383
  quick_start_hint: "Try: help me develop an Abs operator",
594
384
  skill_select_category: "Select Skill category",
@@ -660,14 +450,347 @@ var en_US = {
660
450
  wizard_step_4_skill_title: "[4/4] Select Skills to install \u2014 {category}",
661
451
  install_level_project: "Install to current directory (recommended, current project only)",
662
452
  install_level_global: "Install globally (all projects)",
663
- wizard_select_mode_title: "Select installation type"
453
+ wizard_select_mode_title: "Select installation type",
454
+ skill_count_format: "{count} Skills",
455
+ result_success_format: "{count} succeeded",
456
+ result_failed_format: "{count} failed",
457
+ uninstall_remove_label: "Removed",
458
+ uninstall_remove_file_format: "Removed {count} files",
459
+ uninstall_remove_dir_format: "{count} directories",
460
+ uninstall_clean_empty_dir: "Cleaned empty directory",
461
+ uninstall_skill_not_installed: "Not installed",
462
+ available_plugins: "Available plugins",
463
+ available_skills: "Available Skills",
464
+ skill_total_count: "Total {count} Skills",
465
+ wizard_confirm_install_format: "About to install {count} plugins to {tool} ({level} level):",
466
+ wizard_install_path: "Install path",
467
+ update_install_hint: "Use {cmd} to install plugins",
468
+ hint_select_move: "Navigate",
469
+ hint_select_space: "Space",
470
+ hint_select_check: "Toggle",
471
+ hint_select_confirm: "Confirm",
472
+ hint_checkbox_suffix: "Press enter without selection to go back",
473
+ doctor_not_installed: "Not installed",
474
+ doctor_links_valid: "{count} symlinks all valid",
475
+ doctor_broken_links: "{count} broken links",
476
+ doctor_fixed: "Cleaned {count} broken links",
477
+ doctor_not_exist: "Does not exist",
478
+ doctor_created: "Created {path}",
479
+ doctor_config_exists: "Exists",
480
+ doctor_config_not_exist: "Does not exist",
481
+ doctor_result_with_fix: "{warnings} warnings, {errors} errors, {fixes} fixed",
482
+ doctor_result_without_fix: "{warnings} warnings, {errors} errors",
483
+ count_suffix: "",
484
+ try_prompt: "help me develop an Abs operator"
664
485
  };
665
486
  var currentLang = "zh_CN";
666
487
  function setLanguage(lang) {
667
488
  currentLang = lang;
668
489
  }
669
- function t(key) {
670
- return currentLang === "zh_CN" ? zh_CN[key] : en_US[key];
490
+ function t(key) {
491
+ return currentLang === "zh_CN" ? zh_CN[key] : en_US[key];
492
+ }
493
+
494
+ // src/utils/paths.ts
495
+ var VALID_TOOLS = ["opencode", "claude", "trae", "cursor", "copilot"];
496
+ var VALID_LEVELS = ["project", "global"];
497
+ function validateTool(tool) {
498
+ if (!VALID_TOOLS.includes(tool)) {
499
+ throw new Error(
500
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
501
+ );
502
+ }
503
+ return tool;
504
+ }
505
+ function validateLevel(level) {
506
+ if (!VALID_LEVELS.includes(level)) {
507
+ throw new Error(
508
+ t("error_invalid_level").replace("{level}", level).replace("{levels}", VALID_LEVELS.join(", "))
509
+ );
510
+ }
511
+ return level;
512
+ }
513
+ function detectTraeVariant() {
514
+ const home = homedir();
515
+ if (existsSync(join(home, ".trae-cn"))) return "ide";
516
+ if (existsSync(join(home, ".marscode"))) return "plugin";
517
+ if (existsSync(join(home, ".traecli"))) return "cli";
518
+ return "unknown";
519
+ }
520
+ function getConfigRoot(tool, level, base) {
521
+ const home = homedir();
522
+ if (level === "global") {
523
+ switch (tool) {
524
+ case "opencode":
525
+ return join(home, ".config", "opencode");
526
+ case "claude":
527
+ return join(home, ".claude");
528
+ case "trae": {
529
+ const variant = detectTraeVariant();
530
+ switch (variant) {
531
+ case "plugin":
532
+ return join(home, ".marscode");
533
+ case "cli":
534
+ return join(home, ".traecli");
535
+ default:
536
+ return join(home, ".trae-cn");
537
+ }
538
+ }
539
+ case "cursor":
540
+ return join(home, ".cursor");
541
+ case "copilot":
542
+ return join(home, ".copilot");
543
+ default:
544
+ throw new Error(
545
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
546
+ );
547
+ }
548
+ }
549
+ const baseDir = base || process.cwd();
550
+ switch (tool) {
551
+ case "opencode":
552
+ return join(baseDir, ".opencode");
553
+ case "claude":
554
+ return join(baseDir, ".claude");
555
+ case "trae": {
556
+ const variant = detectTraeVariant();
557
+ switch (variant) {
558
+ case "plugin":
559
+ return join(baseDir, ".marscode");
560
+ case "cli":
561
+ return join(baseDir, ".traecli");
562
+ default:
563
+ return join(baseDir, ".trae");
564
+ }
565
+ }
566
+ case "cursor":
567
+ return join(baseDir, ".cursor");
568
+ case "copilot":
569
+ return join(baseDir, ".github");
570
+ default:
571
+ throw new Error(
572
+ t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
573
+ );
574
+ }
575
+ }
576
+ function getConfigFileName(tool) {
577
+ return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
578
+ }
579
+ function getSkillsDir(configRoot) {
580
+ return join(configRoot, "skills");
581
+ }
582
+ function getAgentsDir(configRoot) {
583
+ return join(configRoot, "agents");
584
+ }
585
+ function getManifestPath(configRoot) {
586
+ return join(configRoot, "cannbot-manifest.json");
587
+ }
588
+ function getCannbotConfigDir() {
589
+ return join(homedir(), ".cannbot");
590
+ }
591
+ function getCannbotRepoPath() {
592
+ return join(getCannbotConfigDir(), "repo");
593
+ }
594
+
595
+ // src/core/detector.ts
596
+ async function getCommandVersion(cmd, args = ["--version"]) {
597
+ try {
598
+ const result = await execa(cmd, args, { timeout: 5e3 });
599
+ const output = result.stdout.trim();
600
+ const match = output.match(/(\d+\.\d+\.\d+)/);
601
+ return match ? match[1] : output.split("\n")[0];
602
+ } catch {
603
+ return void 0;
604
+ }
605
+ }
606
+ async function getCommandPath(cmd) {
607
+ try {
608
+ const result = await execa("which", [cmd], { timeout: 5e3 });
609
+ return result.stdout.trim();
610
+ } catch {
611
+ return void 0;
612
+ }
613
+ }
614
+ async function detectOpenCode() {
615
+ const path = await getCommandPath("opencode");
616
+ if (!path) return void 0;
617
+ const version = await getCommandVersion("opencode");
618
+ return { name: "opencode", version, path };
619
+ }
620
+ async function detectClaude() {
621
+ const path = await getCommandPath("claude");
622
+ if (!path) return void 0;
623
+ const version = await getCommandVersion("claude");
624
+ return { name: "claude", version, path };
625
+ }
626
+ async function detectTrae() {
627
+ const home = homedir2();
628
+ const variant = detectTraeVariant();
629
+ let path;
630
+ if (variant === "ide" && existsSync2(join2(home, ".trae-cn"))) {
631
+ path = join2(home, ".trae-cn");
632
+ } else if (variant === "plugin" && existsSync2(join2(home, ".marscode"))) {
633
+ path = join2(home, ".marscode");
634
+ } else if (variant === "cli" && existsSync2(join2(home, ".traecli"))) {
635
+ path = join2(home, ".traecli");
636
+ } else if (variant === "unknown") {
637
+ const cmdPath = await getCommandPath("trae");
638
+ if (cmdPath) path = cmdPath;
639
+ }
640
+ if (!path) return void 0;
641
+ return { name: "trae", version: variant, path };
642
+ }
643
+ async function detectCursor() {
644
+ const path = await getCommandPath("cursor");
645
+ if (path) {
646
+ const version = await getCommandVersion("cursor");
647
+ return { name: "cursor", version, path };
648
+ }
649
+ if (process.platform === "darwin") {
650
+ const appPath = "/Applications/Cursor.app";
651
+ if (existsSync2(appPath)) {
652
+ return { name: "cursor", path: appPath };
653
+ }
654
+ }
655
+ return void 0;
656
+ }
657
+ async function detectCopilot() {
658
+ const path = await getCommandPath("gh");
659
+ if (!path) return void 0;
660
+ try {
661
+ await execa("gh", ["extension", "list"], { timeout: 5e3 });
662
+ return { name: "copilot", path };
663
+ } catch {
664
+ return void 0;
665
+ }
666
+ }
667
+ async function detectTools() {
668
+ const detectors = [
669
+ detectOpenCode,
670
+ detectClaude,
671
+ detectTrae,
672
+ detectCursor,
673
+ detectCopilot
674
+ ];
675
+ const results = await Promise.allSettled(detectors.map((d) => d()));
676
+ const tools = [];
677
+ for (const result of results) {
678
+ if (result.status === "fulfilled" && result.value) {
679
+ tools.push(result.value);
680
+ }
681
+ }
682
+ return tools;
683
+ }
684
+ function getToolDisplayName(tool) {
685
+ const names = {
686
+ opencode: "OpenCode",
687
+ claude: "Claude Code",
688
+ trae: "Trae",
689
+ cursor: "Cursor",
690
+ copilot: "GitHub Copilot"
691
+ };
692
+ return names[tool];
693
+ }
694
+ function getAllTools() {
695
+ return ["opencode", "claude", "trae", "cursor", "copilot"];
696
+ }
697
+
698
+ // src/core/manifest.ts
699
+ import { existsSync as existsSync3, readFileSync } from "fs";
700
+ import { join as join3 } from "path";
701
+ function readManifest(configRoot) {
702
+ const manifestPath = getManifestPath(configRoot);
703
+ if (existsSync3(manifestPath)) {
704
+ try {
705
+ const content = readFileSync(manifestPath, "utf-8");
706
+ return JSON.parse(content);
707
+ } catch {
708
+ }
709
+ }
710
+ return null;
711
+ }
712
+ function readAllManifests(configRoot) {
713
+ const manifests = [];
714
+ const plugins = getAllPlugins();
715
+ const standardPath = getManifestPath(configRoot);
716
+ if (existsSync3(standardPath)) {
717
+ try {
718
+ const content = readFileSync(standardPath, "utf-8");
719
+ manifests.push(JSON.parse(content));
720
+ } catch {
721
+ }
722
+ }
723
+ for (const plugin of plugins) {
724
+ const pluginManifestPath = join3(configRoot, `${plugin.id}-manifest.json`);
725
+ if (existsSync3(pluginManifestPath)) {
726
+ try {
727
+ const content = readFileSync(pluginManifestPath, "utf-8");
728
+ manifests.push(JSON.parse(content));
729
+ } catch {
730
+ }
731
+ }
732
+ }
733
+ return manifests;
734
+ }
735
+ function scanInstalled() {
736
+ const plugins = getAllPlugins();
737
+ const installed = [];
738
+ const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
739
+ const levels = ["project", "global"];
740
+ for (const tool of tools) {
741
+ for (const level of levels) {
742
+ const configRoot = getConfigRoot(tool, level);
743
+ const manifests = readAllManifests(configRoot);
744
+ for (const manifest of manifests) {
745
+ const plugin = plugins.find((p) => p.id === manifest.team);
746
+ if (!plugin) continue;
747
+ if (!verifyPluginFilesExist(configRoot, manifest, tool, level)) {
748
+ continue;
749
+ }
750
+ const alreadyAdded = installed.some(
751
+ (p) => p.id === plugin.id && p.tool === tool && p.level === level
752
+ );
753
+ if (alreadyAdded) continue;
754
+ installed.push({
755
+ id: plugin.id,
756
+ displayName: plugin.displayName,
757
+ tool,
758
+ level,
759
+ skillsCount: manifest.installed_skills?.length || 0,
760
+ agentsCount: manifest.installed_agents?.length || 0,
761
+ installTime: manifest.install_time,
762
+ configRoot
763
+ });
764
+ }
765
+ }
766
+ }
767
+ return installed;
768
+ }
769
+ function verifyPluginFilesExist(configRoot, manifest, tool, level) {
770
+ const skillsDir = join3(configRoot, "skills");
771
+ const agentsDir = join3(configRoot, "agents");
772
+ if (manifest.installed_skills && manifest.installed_skills.length > 0) {
773
+ const hasAnySkill = manifest.installed_skills.some((skillId) => {
774
+ const skillPath = join3(skillsDir, skillId);
775
+ return existsSync3(skillPath);
776
+ });
777
+ if (!hasAnySkill) return false;
778
+ }
779
+ if (manifest.installed_agents && manifest.installed_agents.length > 0) {
780
+ const hasAnyAgent = manifest.installed_agents.some((agentId) => {
781
+ const agentPath = join3(agentsDir, agentId);
782
+ return existsSync3(agentPath);
783
+ });
784
+ if (!hasAnyAgent) return false;
785
+ }
786
+ let agentsMdPath;
787
+ if (tool === "opencode" && level === "project") {
788
+ agentsMdPath = join3(configRoot, "..", "AGENTS.md");
789
+ } else {
790
+ agentsMdPath = join3(configRoot, "AGENTS.md");
791
+ }
792
+ if (!existsSync3(agentsMdPath)) return false;
793
+ return true;
671
794
  }
672
795
 
673
796
  // src/utils/logger.ts
@@ -724,10 +847,10 @@ function printBoxTitle(title, width = 65) {
724
847
  }
725
848
  function showOperationHints(isCheckbox = false) {
726
849
  if (isCheckbox) {
727
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u7A7A\u683C\x1B[0m \u52FE\u9009 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4\uFF08\u4E0D\u9009\u6309\u56DE\u8F66\u53EF\u8FD4\u56DE\uFF09
850
+ console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m${t("hint_select_space")}\x1B[0m ${t("hint_select_check")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}\uFF08${t("hint_checkbox_suffix")}\uFF09
728
851
  `);
729
852
  } else {
730
- console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4
853
+ console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}
731
854
  `);
732
855
  }
733
856
  }
@@ -1036,8 +1159,8 @@ async function stepConfirm(tool, level, plugins) {
1036
1159
  const displayPath = getConfigRoot(tool, level);
1037
1160
  const toolName = getToolDisplayName(tool);
1038
1161
  const levelText = level === "project" ? "project" : "global";
1039
- logger.info(`\u5373\u5C06\u5B89\u88C5 ${chalk2.bold(plugins.length)} \u4E2A\u63D2\u4EF6\u5230 ${chalk2.green(toolName)}\uFF08${chalk2.cyan(levelText)} \u7EA7\u522B\uFF09\uFF1A`);
1040
- logger.info(`\u5B89\u88C5\u8DEF\u5F84: ${chalk2.cyan(displayPath)}`);
1162
+ logger.info(t("wizard_confirm_install_format").replace("{count}", chalk2.bold(String(plugins.length))).replace("{tool}", chalk2.green(toolName)).replace("{level}", chalk2.cyan(levelText)));
1163
+ logger.info(`${t("wizard_install_path")}: ${chalk2.cyan(displayPath)}`);
1041
1164
  for (const plugin of selectedPlugins) {
1042
1165
  if (plugin) {
1043
1166
  logger.step(` \u2022 ${chalk2.bold(plugin.displayName)}`);
@@ -1064,7 +1187,7 @@ import { execa as execa2 } from "execa";
1064
1187
 
1065
1188
  // src/core/scanner.ts
1066
1189
  import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1067
- import { join as join5 } from "path";
1190
+ import { join as join5, dirname } from "path";
1068
1191
  import { parse as parseYaml } from "yaml";
1069
1192
  var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
1070
1193
  "node_modules",
@@ -1202,8 +1325,7 @@ function writeScanCache(cache) {
1202
1325
  }
1203
1326
  }
1204
1327
  function getCachePath() {
1205
- const homeDir = process.env.HOME || process.env.USERPROFILE || "/tmp";
1206
- return join5(homeDir, ".cannbot", "scan-cache.json");
1328
+ return join5(getCannbotConfigDir(), "scan-cache.json");
1207
1329
  }
1208
1330
  function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
1209
1331
  const selfSkillMd = join5(dirPath, "SKILL.md");
@@ -1672,7 +1794,6 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
1672
1794
  files.push(repoLinkInConfig);
1673
1795
  }
1674
1796
  }
1675
- directories.push(configRoot);
1676
1797
  return {
1677
1798
  pluginId,
1678
1799
  displayName,
@@ -1749,11 +1870,17 @@ function removeSkillsFromRecord(skillIds, tool, level, installPath) {
1749
1870
  }
1750
1871
  function getInstalledSkills(tool, level, installPath) {
1751
1872
  const record = readSkillRecord();
1752
- return record[tool]?.[level]?.[installPath]?.skills || [];
1873
+ const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
1874
+ const configRoot = getConfigRoot(tool, level);
1875
+ const skillsDir = join7(configRoot, "skills");
1876
+ return recordedSkills.filter((skillId) => {
1877
+ const skillPath = join7(skillsDir, skillId);
1878
+ return existsSync7(skillPath) || isSymlink(skillPath);
1879
+ });
1753
1880
  }
1754
1881
 
1755
1882
  // src/core/backup.ts
1756
- import { existsSync as existsSync8, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync2 } from "fs";
1883
+ import { existsSync as existsSync8, copyFileSync, readdirSync as readdirSync2, unlinkSync as unlinkSync2 } from "fs";
1757
1884
  import { join as join8 } from "path";
1758
1885
  function getAgentsFileName(tool) {
1759
1886
  return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
@@ -1811,7 +1938,7 @@ function findBackups(configRoot) {
1811
1938
  if (!existsSync8(configRoot)) {
1812
1939
  return [];
1813
1940
  }
1814
- const files = readdirSync3(configRoot);
1941
+ const files = readdirSync2(configRoot);
1815
1942
  const backups = [];
1816
1943
  for (const file of files) {
1817
1944
  if (file.includes(".cannbot-backup.")) {
@@ -2050,7 +2177,7 @@ async function installPlugin(opts) {
2050
2177
  }
2051
2178
 
2052
2179
  // src/core/skill-installer.ts
2053
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync3, readdirSync as readdirSync4, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
2180
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync3, readdirSync as readdirSync3, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
2054
2181
  import { join as join10 } from "path";
2055
2182
  import { select as select3, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
2056
2183
  import chalk4 from "chalk";
@@ -2105,10 +2232,10 @@ async function uninstallSkills(skillIds, tool, level) {
2105
2232
  unlinkSync3(targetPath);
2106
2233
  results.push({ skillId, success: true });
2107
2234
  removedIds.push(skillId);
2108
- logger.step(` \u79FB\u9664: ${skillId}`);
2235
+ logger.step(` ${t("uninstall_remove_label")}: ${skillId}`);
2109
2236
  } else {
2110
2237
  results.push({ skillId, success: false, error: "Not installed" });
2111
- logger.warn(`${skillId} \u672A\u5B89\u88C5`);
2238
+ logger.warn(`${skillId} ${t("uninstall_skill_not_installed")}`);
2112
2239
  }
2113
2240
  } catch (error) {
2114
2241
  results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
@@ -2119,27 +2246,17 @@ async function uninstallSkills(skillIds, tool, level) {
2119
2246
  }
2120
2247
  if (existsSync10(skillsDir)) {
2121
2248
  try {
2122
- const entries = readdirSync4(skillsDir);
2249
+ const entries = readdirSync3(skillsDir);
2123
2250
  if (entries.length === 0) {
2124
2251
  rmdirSync(skillsDir);
2125
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: skills/`);
2126
- }
2127
- } catch {
2128
- }
2129
- }
2130
- if (existsSync10(configRoot)) {
2131
- try {
2132
- const entries = readdirSync4(configRoot);
2133
- if (entries.length === 0) {
2134
- rmdirSync(configRoot);
2135
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${configRoot.split("/").pop()}/`);
2252
+ logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
2136
2253
  }
2137
2254
  } catch {
2138
2255
  }
2139
2256
  }
2140
2257
  return results;
2141
2258
  }
2142
- async function interactiveSkillSelect() {
2259
+ async function interactiveSkillSelect(tool, level) {
2143
2260
  const BACK3 = "__back__";
2144
2261
  const CANCEL3 = "__cancel__";
2145
2262
  const categories = getAllCategories();
@@ -2175,15 +2292,8 @@ async function interactiveSkillSelect() {
2175
2292
  const category = categories.find((c) => c.id === selectedCategoryId);
2176
2293
  if (!category) return [];
2177
2294
  printBoxTitle(t("wizard_step_4_skill_title").replace("{category}", category.name));
2178
- const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
2179
- const levels = ["project", "global"];
2180
- const installedSkills = [];
2181
- for (const tk of tools) {
2182
- for (const l of levels) {
2183
- const path = l === "project" ? process.cwd() : getConfigRoot(tk, l);
2184
- installedSkills.push(...getInstalledSkills(tk, l, path));
2185
- }
2186
- }
2295
+ const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
2296
+ const installedSkills = getInstalledSkills(tool, level, installPath);
2187
2297
  const installedSet = new Set(installedSkills);
2188
2298
  const skillChoices = category.skills.map((skill) => {
2189
2299
  const isInstalled = installedSet.has(skill.id);
@@ -2248,7 +2358,7 @@ function listAllSkills() {
2248
2358
  console.log(table.toString());
2249
2359
  }
2250
2360
  console.log();
2251
- console.log(chalk4.dim(` \u5171 ${getAllSkills().length} \u4E2A Skills`));
2361
+ console.log(chalk4.dim(` ${t("skill_total_count").replace("{count}", String(getAllSkills().length))}`));
2252
2362
  console.log();
2253
2363
  }
2254
2364
  function isSymlink2(path) {
@@ -2285,7 +2395,8 @@ function printPluginList(plugins, installed) {
2285
2395
  ]);
2286
2396
  });
2287
2397
  console.log();
2288
- console.log(chalk5.bold(` ${t("list_title")} (${plugins.length} \u4E2A)`));
2398
+ const countSuffix = t("count_suffix");
2399
+ console.log(chalk5.bold(` ${t("list_title")} (${plugins.length}${countSuffix ? " " + countSuffix : ""})`));
2289
2400
  console.log(table.toString());
2290
2401
  console.log();
2291
2402
  }
@@ -2318,15 +2429,16 @@ function printEnhancedSummary(results, tool, configRoot) {
2318
2429
  return;
2319
2430
  }
2320
2431
  const toolName = getToolDisplayName(tool);
2432
+ const countSuffix = t("count_suffix");
2321
2433
  const lines = [
2322
2434
  chalk5.bold(` ${t("install_done")}!`),
2323
2435
  "",
2324
2436
  ` ${chalk5.dim(t("install_installed_to") + ":")} ${configRoot}`,
2325
- ` ${chalk5.dim(t("install_skills_count") + ":")} ${totalSkills} \u4E2A ${chalk5.dim("|")} ${chalk5.dim(t("install_agents_count") + ":")} ${totalAgents} \u4E2A`,
2437
+ ` ${chalk5.dim(t("install_skills_count") + ":")} ${totalSkills}${countSuffix ? " " + countSuffix : ""} ${chalk5.dim("|")} ${chalk5.dim(t("install_agents_count") + ":")} ${totalAgents}${countSuffix ? " " + countSuffix : ""}`,
2326
2438
  "",
2327
2439
  chalk5.bold(` ${t("install_enhanced_next_steps")}:`),
2328
2440
  ` ${chalk5.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk5.green(toolName.toLowerCase())}`,
2329
- ` ${chalk5.cyan("2.")} ${t("install_enhanced_try")}: ${chalk5.green("\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50")}`,
2441
+ ` ${chalk5.cyan("2.")} ${t("install_enhanced_try")}: ${chalk5.green(t("try_prompt"))}`,
2330
2442
  ` ${chalk5.cyan("3.")} ${t("install_enhanced_more")}: ${chalk5.green("install-helper list")}`,
2331
2443
  ` ${chalk5.cyan("4.")} ${t("install_enhanced_check")}: ${chalk5.green("install-helper doctor")}`,
2332
2444
  ""
@@ -2516,7 +2628,7 @@ async function skillInstallFlow() {
2516
2628
  break;
2517
2629
  }
2518
2630
  case 2: {
2519
- const selectedSkills = await interactiveSkillSelect();
2631
+ const selectedSkills = await interactiveSkillSelect(tool, level);
2520
2632
  if (selectedSkills === "back") {
2521
2633
  step = 1;
2522
2634
  break;
@@ -2534,7 +2646,7 @@ async function skillInstallFlow() {
2534
2646
  spinner.start();
2535
2647
  const repoPath = await repoManager.ensureRepo();
2536
2648
  spinner.succeed(t("loading_plugin_data_complete"));
2537
- logger.info(`${t("skill_install_progress")} ${chalk6.bold(selectedSkills.length)} \u4E2A Skills...`);
2649
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk6.bold(String(selectedSkills.length)))}...`);
2538
2650
  const results = await installSkills(selectedSkills, tool, level, repoPath);
2539
2651
  let successCount = 0;
2540
2652
  let failCount = 0;
@@ -2552,7 +2664,7 @@ async function skillInstallFlow() {
2552
2664
  }
2553
2665
  const configRoot = getConfigRoot(tool, level);
2554
2666
  logger.blank();
2555
- logger.success(`${t("skill_install_done")}: ${chalk6.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk6.red(failCount + " \u5931\u8D25") : chalk6.dim(failCount + " \u5931\u8D25")}`);
2667
+ logger.success(`${t("skill_install_done")}: ${chalk6.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk6.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk6.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2556
2668
  logger.blank();
2557
2669
  logger.info(`${t("install_to")}: ${chalk6.cyan(configRoot + "/skills/")}`);
2558
2670
  logger.info(`${t("start_to_use").replace("{tool}", chalk6.green(tool))}`);
@@ -2578,7 +2690,7 @@ async function listCommand() {
2578
2690
 
2579
2691
  // src/commands/doctor.ts
2580
2692
  import chalk7 from "chalk";
2581
- import { existsSync as existsSync12, readdirSync as readdirSync5, lstatSync as lstatSync3, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6 } from "fs";
2693
+ import { existsSync as existsSync12, readdirSync as readdirSync4, lstatSync as lstatSync3, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6 } from "fs";
2582
2694
  import { join as join12 } from "path";
2583
2695
  async function doctorCommand(options = {}) {
2584
2696
  console.log();
@@ -2598,7 +2710,7 @@ async function doctorCommand(options = {}) {
2598
2710
  chalk7.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
2599
2711
  );
2600
2712
  } else {
2601
- console.log(chalk7.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 \u672A\u5B89\u88C5`);
2713
+ console.log(chalk7.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 ${t("doctor_not_installed")}`);
2602
2714
  }
2603
2715
  }
2604
2716
  console.log();
@@ -2613,7 +2725,7 @@ async function doctorCommand(options = {}) {
2613
2725
  chalk7.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
2614
2726
  );
2615
2727
  } else {
2616
- console.log(chalk7.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 \u672A\u5B89\u88C5`);
2728
+ console.log(chalk7.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 ${t("doctor_not_installed")}`);
2617
2729
  }
2618
2730
  }
2619
2731
  console.log();
@@ -2625,45 +2737,45 @@ async function doctorCommand(options = {}) {
2625
2737
  if (existsSync12(skillsDir)) {
2626
2738
  const brokenLinks = checkBrokenLinks(skillsDir);
2627
2739
  if (brokenLinks === 0) {
2628
- const count = readdirSync5(skillsDir).length;
2629
- console.log(chalk7.green(" \u2713") + ` ${skillsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
2740
+ const count = readdirSync4(skillsDir).length;
2741
+ console.log(chalk7.green(" \u2713") + ` ${skillsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2630
2742
  } else {
2631
- console.log(chalk7.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
2743
+ console.log(chalk7.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2632
2744
  warnings++;
2633
2745
  if (options.fix) {
2634
2746
  const fixed = fixBrokenLinks(skillsDir);
2635
2747
  fixes += fixed;
2636
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
2748
+ console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2637
2749
  }
2638
2750
  }
2639
2751
  } else {
2640
- console.log(chalk7.dim(" \u2014") + ` ${skillsDir} \u2014 \u4E0D\u5B58\u5728`);
2752
+ console.log(chalk7.dim(" \u2014") + ` ${skillsDir} \u2014 ${t("doctor_not_exist")}`);
2641
2753
  if (options.fix) {
2642
2754
  mkdirSync6(skillsDir, { recursive: true });
2643
2755
  fixes++;
2644
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${skillsDir}`);
2756
+ console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
2645
2757
  }
2646
2758
  }
2647
2759
  if (existsSync12(agentsDir)) {
2648
2760
  const brokenLinks = checkBrokenLinks(agentsDir);
2649
2761
  if (brokenLinks === 0) {
2650
- const count = readdirSync5(agentsDir).length;
2651
- console.log(chalk7.green(" \u2713") + ` ${agentsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
2762
+ const count = readdirSync4(agentsDir).length;
2763
+ console.log(chalk7.green(" \u2713") + ` ${agentsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
2652
2764
  } else {
2653
- console.log(chalk7.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
2765
+ console.log(chalk7.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
2654
2766
  warnings++;
2655
2767
  if (options.fix) {
2656
2768
  const fixed = fixBrokenLinks(agentsDir);
2657
2769
  fixes += fixed;
2658
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
2770
+ console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
2659
2771
  }
2660
2772
  }
2661
2773
  } else {
2662
- console.log(chalk7.dim(" \u2014") + ` ${agentsDir} \u2014 \u4E0D\u5B58\u5728`);
2774
+ console.log(chalk7.dim(" \u2014") + ` ${agentsDir} \u2014 ${t("doctor_not_exist")}`);
2663
2775
  if (options.fix) {
2664
2776
  mkdirSync6(agentsDir, { recursive: true });
2665
2777
  fixes++;
2666
- console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${agentsDir}`);
2778
+ console.log(chalk7.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", agentsDir)}`);
2667
2779
  }
2668
2780
  }
2669
2781
  console.log();
@@ -2671,19 +2783,19 @@ async function doctorCommand(options = {}) {
2671
2783
  const configFile = getConfigFileName(primaryTool);
2672
2784
  const configPath = join12(process.cwd(), configFile);
2673
2785
  if (existsSync12(configPath)) {
2674
- console.log(chalk7.green(" \u2713") + ` ${configFile} \u5B58\u5728`);
2786
+ console.log(chalk7.green(" \u2713") + ` ${configFile} ${t("doctor_config_exists")}`);
2675
2787
  } else {
2676
- console.log(chalk7.dim(" \u2014") + ` ${configFile} \u4E0D\u5B58\u5728`);
2788
+ console.log(chalk7.dim(" \u2014") + ` ${configFile} ${t("doctor_config_not_exist")}`);
2677
2789
  }
2678
2790
  console.log();
2679
2791
  console.log(chalk7.dim(" " + "\u2500".repeat(46)));
2680
2792
  if (options.fix && fixes > 0) {
2681
2793
  console.log(
2682
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF, ${fixes} \u9879\u5DF2\u4FEE\u590D`
2794
+ ` ${t("doctor_result")}: ${t("doctor_result_with_fix").replace("{warnings}", String(warnings)).replace("{errors}", String(errors)).replace("{fixes}", String(fixes))}`
2683
2795
  );
2684
2796
  } else {
2685
2797
  console.log(
2686
- ` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF`
2798
+ ` ${t("doctor_result")}: ${t("doctor_result_without_fix").replace("{warnings}", String(warnings)).replace("{errors}", String(errors))}`
2687
2799
  );
2688
2800
  }
2689
2801
  console.log();
@@ -2691,7 +2803,7 @@ async function doctorCommand(options = {}) {
2691
2803
  function checkBrokenLinks(dir) {
2692
2804
  let broken = 0;
2693
2805
  try {
2694
- const entries = readdirSync5(dir);
2806
+ const entries = readdirSync4(dir);
2695
2807
  for (const entry of entries) {
2696
2808
  const fullPath = join12(dir, entry);
2697
2809
  try {
@@ -2712,7 +2824,7 @@ function checkBrokenLinks(dir) {
2712
2824
  function fixBrokenLinks(dir) {
2713
2825
  let fixed = 0;
2714
2826
  try {
2715
- const entries = readdirSync5(dir);
2827
+ const entries = readdirSync4(dir);
2716
2828
  for (const entry of entries) {
2717
2829
  const fullPath = join12(dir, entry);
2718
2830
  try {
@@ -2778,29 +2890,31 @@ async function installCommand(names, options) {
2778
2890
  try {
2779
2891
  const scanRepoManager = createRepositoryManager();
2780
2892
  await scanRepoManager.ensureRepoAndScan();
2781
- scanSpinner2.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
2893
+ scanSpinner2.succeed(t("loading_skills_list_complete"));
2782
2894
  } catch {
2783
- scanSpinner2.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
2895
+ scanSpinner2.warn(t("loading_skills_list_failed"));
2784
2896
  }
2785
2897
  let tool2;
2786
2898
  if (options.tool) {
2787
- tool2 = options.tool;
2899
+ tool2 = validateTool(options.tool);
2788
2900
  } else {
2789
2901
  const detected = await selectToolWithDetection();
2790
- if (detected === "back") return;
2902
+ if (detected === "back" || detected === "cancel") return;
2791
2903
  tool2 = detected;
2792
2904
  }
2905
+ const level2 = options.level ? validateLevel(options.level) : "project";
2793
2906
  let step = 1;
2794
2907
  while (true) {
2795
2908
  switch (step) {
2796
2909
  case 1: {
2797
- const selectedSkills = await interactiveSkillSelect();
2910
+ const selectedSkills = await interactiveSkillSelect(tool2, level2);
2798
2911
  if (selectedSkills === "back") {
2799
2912
  const redetected = await selectToolWithDetection();
2800
- if (redetected === "back") return;
2913
+ if (redetected === "back" || redetected === "cancel") return;
2801
2914
  tool2 = redetected;
2802
2915
  break;
2803
2916
  }
2917
+ if (selectedSkills === "cancel") return;
2804
2918
  if (selectedSkills.length === 0) {
2805
2919
  logger.info(t("install_no_skill_selected"));
2806
2920
  return;
@@ -2810,9 +2924,8 @@ async function installCommand(names, options) {
2810
2924
  const spinner = createSpinner(t("loading_plugin_data"));
2811
2925
  spinner.start();
2812
2926
  const repoPath = await repoManager.ensureRepo();
2813
- spinner.succeed("\u63D2\u4EF6\u6570\u636E\u52A0\u8F7D\u5B8C\u6210");
2814
- logger.info(`${t("skill_install_progress")} ${chalk9.bold(names.length)} \u4E2A Skills...`);
2815
- const level2 = options.level || "project";
2927
+ spinner.succeed(t("loading_plugin_data_complete"));
2928
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk9.bold(String(names.length)))}...`);
2816
2929
  const results = await installSkills(names, tool2, level2, repoPath);
2817
2930
  let successCount = 0;
2818
2931
  let failCount = 0;
@@ -2830,7 +2943,7 @@ async function installCommand(names, options) {
2830
2943
  }
2831
2944
  const configRoot = getConfigRoot(tool2, level2);
2832
2945
  logger.blank();
2833
- logger.success(`${t("skill_install_done")}: ${chalk9.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk9.red(failCount + " \u5931\u8D25") : chalk9.dim(failCount + " \u5931\u8D25")}`);
2946
+ logger.success(`${t("skill_install_done")}: ${chalk9.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk9.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk9.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2834
2947
  logger.blank();
2835
2948
  logger.info(`${t("install_to")}: ${chalk9.cyan(configRoot + "/skills/")}`);
2836
2949
  logger.info(`${t("start_to_use").replace("{tool}", chalk9.green(tool2))}`);
@@ -2845,9 +2958,9 @@ async function installCommand(names, options) {
2845
2958
  try {
2846
2959
  const scanRepoManager = createRepositoryManager();
2847
2960
  await scanRepoManager.ensureRepoAndScan();
2848
- scanSpinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
2961
+ scanSpinner.succeed(t("loading_skills_list_complete"));
2849
2962
  } catch {
2850
- scanSpinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
2963
+ scanSpinner.warn(t("loading_skills_list_failed"));
2851
2964
  }
2852
2965
  const plugins = [];
2853
2966
  const skills = [];
@@ -2864,22 +2977,22 @@ async function installCommand(names, options) {
2864
2977
  }
2865
2978
  logger.error(`${t("error_plugin_not_found")}: ${name}`);
2866
2979
  const allPlugins = getAllPlugins();
2867
- logger.info("\u53EF\u7528\u63D2\u4EF6:");
2980
+ logger.info(t("available_plugins") + ":");
2868
2981
  for (const p of allPlugins) {
2869
2982
  logger.step(` ${p.id} (${p.aliases.join(", ")})`);
2870
2983
  }
2871
- logger.info(`\u53EF\u7528 Skills: ${chalk9.cyan("install-helper install --list")}`);
2984
+ logger.info(`${t("available_skills")}: ${chalk9.cyan("install-helper install --list")}`);
2872
2985
  return;
2873
2986
  }
2874
2987
  let tool;
2875
2988
  if (options.tool) {
2876
- tool = options.tool;
2989
+ tool = validateTool(options.tool);
2877
2990
  } else {
2878
2991
  const detected = await selectToolWithDetection();
2879
- if (detected === "back") return;
2992
+ if (detected === "back" || detected === "cancel") return;
2880
2993
  tool = detected;
2881
2994
  }
2882
- const level = options.level || "project";
2995
+ const level = options.level ? validateLevel(options.level) : "project";
2883
2996
  if (plugins.length > 0) {
2884
2997
  const configRoot = getConfigRoot(tool, level);
2885
2998
  const manifests = readAllManifests(configRoot);
@@ -2940,7 +3053,7 @@ async function installCommand(names, options) {
2940
3053
  spinner.start();
2941
3054
  const repoPath = await repoManager.ensureRepo();
2942
3055
  spinner.succeed(t("loading_plugin_data_complete"));
2943
- logger.info(`${t("skill_install_progress")} ${chalk9.bold(skills.length)} \u4E2A Skills...`);
3056
+ logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk9.bold(String(skills.length)))}...`);
2944
3057
  const results = await installSkills(skills, tool, level, repoPath);
2945
3058
  let successCount = 0;
2946
3059
  let failCount = 0;
@@ -2957,7 +3070,7 @@ async function installCommand(names, options) {
2957
3070
  }
2958
3071
  }
2959
3072
  logger.blank();
2960
- logger.success(`${t("skill_install_done")}: ${chalk9.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk9.red(failCount + " \u5931\u8D25") : chalk9.dim(failCount + " \u5931\u8D25")}`);
3073
+ logger.success(`${t("skill_install_done")}: ${chalk9.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk9.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk9.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
2961
3074
  logger.blank();
2962
3075
  logger.info(`${t("install_to")}: ${chalk9.cyan(getConfigRoot(tool, level) + "/skills/")}`);
2963
3076
  logger.info(`${t("start_to_use").replace("{tool}", chalk9.green(tool))}`);
@@ -2992,11 +3105,11 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
2992
3105
  }
2993
3106
 
2994
3107
  // src/commands/uninstall.ts
2995
- import { existsSync as existsSync13, unlinkSync as unlinkSync5, rmdirSync as rmdirSync2, readdirSync as readdirSync6, lstatSync as lstatSync4 } from "fs";
3108
+ import { existsSync as existsSync13, unlinkSync as unlinkSync5, rmdirSync as rmdirSync2, readdirSync as readdirSync5, lstatSync as lstatSync4 } from "fs";
2996
3109
  import chalk10 from "chalk";
2997
3110
  async function uninstallCommand(names, options) {
2998
- const tool = options.tool || "opencode";
2999
- const level = options.level || "project";
3111
+ const tool = options.tool ? validateTool(options.tool) : "opencode";
3112
+ const level = options.level ? validateLevel(options.level) : "project";
3000
3113
  const plugins = [];
3001
3114
  const skills = [];
3002
3115
  for (const name of names) {
@@ -3029,21 +3142,25 @@ async function uninstallCommand(names, options) {
3029
3142
  if (existsSync13(filePath) || isSymlink3(filePath)) {
3030
3143
  unlinkSync5(filePath);
3031
3144
  const name = filePath.split("/").pop() || filePath;
3032
- logger.step(` \u79FB\u9664: ${name}`);
3145
+ logger.step(` ${t("uninstall_remove_label")}: ${name}`);
3033
3146
  removedFiles++;
3034
3147
  }
3035
3148
  } catch {
3036
3149
  }
3037
3150
  }
3151
+ const configRoot = getConfigRoot(record.tool, record.level);
3038
3152
  const sortedDirs = [...record.directories].sort((a, b) => b.length - a.length);
3039
3153
  for (const dirPath of sortedDirs) {
3040
3154
  try {
3155
+ if (dirPath === configRoot) {
3156
+ continue;
3157
+ }
3041
3158
  if (existsSync13(dirPath)) {
3042
- const entries = readdirSync6(dirPath);
3159
+ const entries = readdirSync5(dirPath);
3043
3160
  if (entries.length === 0) {
3044
3161
  rmdirSync2(dirPath);
3045
3162
  const name = dirPath.split("/").pop() || dirPath;
3046
- logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${name}/`);
3163
+ logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
3047
3164
  removedDirs++;
3048
3165
  }
3049
3166
  }
@@ -3052,7 +3169,6 @@ async function uninstallCommand(names, options) {
3052
3169
  }
3053
3170
  deleteRecord(plugin.id);
3054
3171
  removeInstalledPlugin(plugin.id);
3055
- const configRoot = getConfigRoot(record.tool, record.level);
3056
3172
  const backups = findBackups(configRoot);
3057
3173
  const otherBackups = backups.filter((b) => b.pluginId !== pluginId);
3058
3174
  if (otherBackups.length > 0) {
@@ -3072,13 +3188,13 @@ async function uninstallCommand(names, options) {
3072
3188
  }
3073
3189
  const totalRemoved = removedFiles + removedDirs;
3074
3190
  if (totalRemoved > 0) {
3075
- logger.success(`${t("uninstall_removed")} ${plugin.displayName}\uFF08\u79FB\u9664 ${removedFiles} \u4E2A\u6587\u4EF6\uFF0C${removedDirs} \u4E2A\u76EE\u5F55\uFF09`);
3191
+ logger.success(`${t("uninstall_removed")} ${plugin.displayName}\uFF08${t("uninstall_remove_file_format").replace("{count}", String(removedFiles))}\uFF0C${t("uninstall_remove_dir_format").replace("{count}", String(removedDirs))}\uFF09`);
3076
3192
  } else {
3077
3193
  logger.warn(`${plugin.displayName}${t("uninstall_files_gone")}`);
3078
3194
  }
3079
3195
  }
3080
3196
  if (skills.length > 0) {
3081
- logger.info(`${t("uninstall_skills_in_progress")} ${skills.length} \u4E2A Skills...`);
3197
+ logger.info(`${t("uninstall_skills_in_progress")} ${t("skill_count_format").replace("{count}", String(skills.length))}...`);
3082
3198
  const results = await uninstallSkills(skills, tool, level);
3083
3199
  let successCount = 0;
3084
3200
  let failCount = 0;
@@ -3090,7 +3206,7 @@ async function uninstallCommand(names, options) {
3090
3206
  }
3091
3207
  }
3092
3208
  logger.blank();
3093
- logger.success(`${t("skill_uninstall_done")}: ${chalk10.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk10.red(failCount + " \u5931\u8D25") : chalk10.dim(failCount + " \u5931\u8D25")}`);
3209
+ logger.success(`${t("skill_uninstall_done")}: ${chalk10.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk10.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk10.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
3094
3210
  logger.blank();
3095
3211
  }
3096
3212
  }
@@ -3107,13 +3223,13 @@ import chalk11 from "chalk";
3107
3223
  async function updateCommand(pluginNames, options) {
3108
3224
  let tool;
3109
3225
  if (options.tool) {
3110
- tool = options.tool;
3226
+ tool = validateTool(options.tool);
3111
3227
  } else {
3112
3228
  const detected = await selectToolWithDetection();
3113
- if (detected === "back") return;
3229
+ if (detected === "back" || detected === "cancel") return;
3114
3230
  tool = detected;
3115
3231
  }
3116
- const level = options.level || "project";
3232
+ const level = options.level ? validateLevel(options.level) : "project";
3117
3233
  let pluginsToUpdate = [];
3118
3234
  if (pluginNames.length === 0) {
3119
3235
  const configRoot = getConfigRoot(tool, level);
@@ -3121,7 +3237,7 @@ async function updateCommand(pluginNames, options) {
3121
3237
  pluginsToUpdate = manifests.map((m) => m.team);
3122
3238
  if (pluginsToUpdate.length === 0) {
3123
3239
  logger.info(t("update_no_plugins"));
3124
- logger.info(`\u4F7F\u7528 ${chalk11.cyan("install-helper install <plugin>")} \u5B89\u88C5\u63D2\u4EF6`);
3240
+ logger.info(t("update_install_hint").replace("{cmd}", chalk11.cyan("install-helper install <plugin>")));
3125
3241
  return;
3126
3242
  }
3127
3243
  } else {
@@ -3184,7 +3300,7 @@ async function updateCommand(pluginNames, options) {
3184
3300
 
3185
3301
  // src/commands/info.ts
3186
3302
  import chalk12 from "chalk";
3187
- import { existsSync as existsSync14, readdirSync as readdirSync7 } from "fs";
3303
+ import { existsSync as existsSync14, readdirSync as readdirSync6 } from "fs";
3188
3304
  import { join as join13 } from "path";
3189
3305
  async function infoCommand(pluginName) {
3190
3306
  const plugin = findPlugin(pluginName);
@@ -3215,7 +3331,7 @@ async function infoCommand(pluginName) {
3215
3331
  }
3216
3332
  const agentsDir = join13(repoPath, plugin.dir, "agents");
3217
3333
  if (existsSync14(agentsDir)) {
3218
- const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md"));
3334
+ const agentFiles = readdirSync6(agentsDir).filter((f) => f.endsWith(".md"));
3219
3335
  if (agentFiles.length > 0) {
3220
3336
  console.log();
3221
3337
  console.log(` ${chalk12.bold("Agents:")}`);
@@ -3272,7 +3388,7 @@ function createCLI() {
3272
3388
  setLanguage(config.language);
3273
3389
  }
3274
3390
  const program2 = new Command();
3275
- program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.9");
3391
+ program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("1.0.2");
3276
3392
  program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
3277
3393
  await initCommand();
3278
3394
  });
@@ -3309,7 +3425,7 @@ function createCLI() {
3309
3425
  // src/index.ts
3310
3426
  process.on("uncaughtException", (error) => {
3311
3427
  if (error.name === "ExitPromptError") {
3312
- console.log("\n\u5DF2\u53D6\u6D88\u5B89\u88C5");
3428
+ console.log("\n" + t("init_cancelled"));
3313
3429
  process.exit(0);
3314
3430
  }
3315
3431
  throw error;