@cannbot-ai/install-helper 1.1.16 → 1.2.0-beta.1

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
@@ -268,7 +268,33 @@ var init_zh_CN = __esm({
268
268
  uninstall_uncheck_all_category: "\u5378\u8F7D\u672C\u7C7B\u522B\u5168\u90E8",
269
269
  uninstall_continue_or_confirm: "\u5DF2\u6807\u8BB0 {count} \u4E2A\u5F85\u5378\u8F7D\uFF0C\u8BF7\u9009\u62E9\u64CD\u4F5C",
270
270
  uninstall_marked_count: "\u5DF2\u6807\u8BB0 {count} \u4E2A\u5F85\u5378\u8F7D",
271
- uninstall_uncategorized: "\u672A\u5206\u7C7B"
271
+ uninstall_uncategorized: "\u672A\u5206\u7C7B",
272
+ error_invalid_source: "\u65E0\u6548\u7684\u63D2\u4EF6\u6765\u6E90: {source}\n\u652F\u6301\u7684\u6765\u6E90: {sources}",
273
+ info_source: "\u5B89\u88C5\u6CE8\u518C\u8868",
274
+ cannbot_tool_unsupported: "cannbot \u578B\u63D2\u4EF6\u6682\u4E0D\u652F\u6301\u5DE5\u5177 {tool}\uFF08\u4E0A\u6E38\u5B89\u88C5\u5668\u652F\u6301: {tools}\uFF09",
275
+ cannbot_level_unsupported: "cannbot \u578B\u63D2\u4EF6\u4EC5\u652F\u6301 project \u7EA7\u5B89\u88C5",
276
+ cannbot_node_required: "\u5B89\u88C5 cannbot \u578B\u63D2\u4EF6\u9700\u8981\u53EF\u7528\u7684 Node.js \u8FD0\u884C\u73AF\u5883\uFF0C\u5F53\u524D\u672A\u68C0\u6D4B\u5230 node\u3002\u8BF7\u5B89\u88C5 Node.js\uFF08\u226520\uFF09\u540E\u91CD\u8BD5",
277
+ cannbot_installer_failed: "\u83B7\u53D6 @cannbot-plugin/cannbot \u5B89\u88C5\u5668\u5931\u8D25: {error}",
278
+ cannbot_installer_refresh_failed: "\u5237\u65B0 cannbot \u5B89\u88C5\u5668\u5931\u8D25\uFF0C\u4F7F\u7528\u672C\u5730\u7F13\u5B58\u7EE7\u7EED",
279
+ cannbot_installer_path_invalid: "CANNBOT_INSTALLER_PATH \u6307\u5B9A\u7684\u5B89\u88C5\u5668\u8DEF\u5F84\u65E0\u6548: {path}",
280
+ cannbot_plugin_not_found: "cannbot \u5B89\u88C5\u5668\u5305\u4E2D\u672A\u627E\u5230\u63D2\u4EF6 {plugin}",
281
+ cannbot_registry_missing: "\u5B89\u88C5\u540E\u672A\u5728 cannbot-plugin.json \u4E2D\u627E\u5230\u6CE8\u518C\u8BB0\u5F55",
282
+ cannbot_delegate_failed: "cannbot \u5B89\u88C5\u5668\u6267\u884C\u5931\u8D25\uFF08\u9000\u51FA\u7801 {code}\uFF09\uFF0C\u8F93\u51FA\u89C1\u4E0A\u65B9\u65E5\u5FD7",
283
+ cannbot_bundled_fallback: "\u4E0A\u6E38\u5B89\u88C5\u5668\u6682\u4E0D\u8BC6\u522B\u65B0\u7248 plugins-official \u76EE\u5F55\uFF08\u7B49\u5F85\u5176\u53D1\u7248\u4FEE\u590D\uFF09\uFF0C\u5DF2\u81EA\u52A8\u56DE\u9000\u4E3A\u5B89\u88C5\u5668\u5305\u5185\u7F6E\u5FEB\u7167\uFF08--bundled\uFF09\u5B8C\u6210\u5B89\u88C5",
284
+ cannbot_migrated_symlink: "\u68C0\u6D4B\u5230 {file} \u4E3A\u65E7\u7248\u63D2\u4EF6\u8F6F\u94FE\uFF0C\u5DF2\u8F6C\u4E3A\u771F\u5B9E\u6587\u4EF6\uFF08\u5185\u5BB9\u4FDD\u7559\u5E76\u5DF2\u5907\u4EFD\uFF09",
285
+ cannbot_migrate_symlink_failed: "\u65E7\u7248\u8F6F\u94FE {file} \u8F6C\u771F\u5B9E\u6587\u4EF6\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u5904\u7406\u540E\u91CD\u8BD5",
286
+ cannbot_dual_skill_copy: "\u4EE5\u4E0B Skills \u540C\u65F6\u5B58\u5728\u4E8E\u65B0\u65E7\u4E24\u5957\u5E03\u5C40\u4E2D\uFF0CAI \u5DE5\u5177\u4F1A\u770B\u5230\u4E24\u4EFD\u526F\u672C: {skills}",
287
+ cannbot_registry_corrupted: "\u65E0\u6CD5\u89E3\u6790 cannbot-plugin.json: {file}",
288
+ cannbot_registry_schema_unsupported: "cannbot-plugin.json schemaVersion\uFF08{version}\uFF09\u4E0D\u53D7\u652F\u6301\uFF0C\u8BF7\u5347\u7EA7 install-helper \u540E\u518D\u6267\u884C\u5378\u8F7D",
289
+ cannbot_uninstall_no_record: "\u672A\u627E\u5230\u8BE5\u63D2\u4EF6\u7684 cannbot \u5B89\u88C5\u8BB0\u5F55",
290
+ cannbot_uninstall_native_hint: "\u8BE5\u63D2\u4EF6\u5728\u5DE5\u5177\u4FA7\u6CE8\u518C\u4E86\u539F\u751F\u63D2\u4EF6\uFF0C\u5982\u9700\u53D6\u6D88\u6CE8\u518C\u53EF\u6267\u884C: opencode plugin remove {spec}",
291
+ doctor_cannbot: "cannbot \u96C6\u6210",
292
+ doctor_cannbot_node_ok: "Node.js \u8FD0\u884C\u65F6\u53EF\u7528\uFF08\u652F\u6301 cannbot \u63D2\u4EF6\u59D4\u6258\u5B89\u88C5\uFF09",
293
+ doctor_cannbot_node_missing: "\u672A\u68C0\u6D4B\u5230 Node.js \u8FD0\u884C\u65F6\uFF0Ccannbot \u578B\u63D2\u4EF6\u4E0D\u53EF\u5B89\u88C5",
294
+ doctor_cannbot_missing_skills: "\u6CE8\u518C\u8868\u8BB0\u5F55\u7684 Skills \u4E2D\u6709 {count} \u4E2A\u5728\u78C1\u76D8\u4E0A\u4E0D\u5B58\u5728",
295
+ doctor_cannbot_marker_duplicate: "\u63D2\u4EF6 {plugin} \u5B58\u5728\u91CD\u590D\u7684\u6807\u8BB0\u5757",
296
+ doctor_cannbot_marker_unpaired: "\u6807\u8BB0\u5757\u4E0D\u914D\u5BF9\uFF08start={start}, end={end}\uFF09",
297
+ doctor_cannbot_orphan_record: "\u63D2\u4EF6 {plugin} \u7684\u59D4\u6258\u5B89\u88C5\u8BB0\u5F55\u5DF2\u60AC\u7A7A\uFF08cannbot-plugin.json \u4E2D\u65E0\u5BF9\u5E94\u8BB0\u5F55\uFF09"
272
298
  };
273
299
  }
274
300
  });
@@ -493,7 +519,33 @@ var init_en_US = __esm({
493
519
  uninstall_uncheck_all_category: "Uninstall all in this category",
494
520
  uninstall_continue_or_confirm: "{count} marked for uninstall, choose an action",
495
521
  uninstall_marked_count: "{count} marked for uninstall",
496
- uninstall_uncategorized: "Uncategorized"
522
+ uninstall_uncategorized: "Uncategorized",
523
+ error_invalid_source: "Invalid plugin source: {source}\nSupported sources: {sources}",
524
+ info_source: "Install registry",
525
+ cannbot_tool_unsupported: "Tool {tool} is not supported for cannbot plugins yet (upstream supports: {tools})",
526
+ cannbot_level_unsupported: "cannbot plugins support project-level installation only",
527
+ cannbot_node_required: "A working Node.js runtime is required to install cannbot plugins but node was not found. Please install Node.js (>=20) and retry",
528
+ cannbot_installer_failed: "Failed to fetch the @cannbot-plugin/cannbot installer: {error}",
529
+ cannbot_installer_refresh_failed: "Failed to refresh the cannbot installer; continuing with the local cache",
530
+ cannbot_installer_path_invalid: "The installer path set by CANNBOT_INSTALLER_PATH is invalid: {path}",
531
+ cannbot_plugin_not_found: "Plugin {plugin} was not found in the cannbot installer package",
532
+ cannbot_registry_missing: "No cannbot-plugin.json record found after installation",
533
+ cannbot_delegate_failed: "The cannbot installer exited with code {code}; see the output above",
534
+ cannbot_bundled_fallback: "The upstream installer predates the plugins-official rename (pending its next release); automatically fell back to the installer package's bundled snapshot (--bundled)",
535
+ cannbot_migrated_symlink: "{file} was a legacy plugin symlink; converted to a real file (content kept, backup saved)",
536
+ cannbot_migrate_symlink_failed: "Failed to convert the legacy symlink {file} to a real file; resolve it manually and retry",
537
+ cannbot_dual_skill_copy: "These Skills exist in both the legacy and cannbot layouts, the AI tool will see two copies: {skills}",
538
+ cannbot_registry_corrupted: "Cannot parse cannbot-plugin.json: {file}",
539
+ cannbot_registry_schema_unsupported: "cannbot-plugin.json schemaVersion ({version}) is not supported; upgrade install-helper before uninstalling",
540
+ cannbot_uninstall_no_record: "No cannbot install record found for this plugin",
541
+ cannbot_uninstall_native_hint: "This plugin registered a native plugin with the tool; unregister it via: opencode plugin remove {spec}",
542
+ doctor_cannbot: "cannbot integration",
543
+ doctor_cannbot_node_ok: "Node.js runtime available (cannbot delegation supported)",
544
+ doctor_cannbot_node_missing: "No Node.js runtime detected; cannbot plugins cannot be installed",
545
+ doctor_cannbot_missing_skills: "{count} skills recorded in the registry are missing on disk",
546
+ doctor_cannbot_marker_duplicate: "Plugin {plugin} has duplicate marker blocks",
547
+ doctor_cannbot_marker_unpaired: "Marker blocks are unpaired (start={start}, end={end})",
548
+ doctor_cannbot_orphan_record: "Delegated install record for {plugin} is orphaned (no matching cannbot-plugin.json entry)"
497
549
  };
498
550
  }
499
551
  });
@@ -717,7 +769,12 @@ var init_embedded_config = __esm({
717
769
  pluginDirs: [
718
770
  "plugins-official"
719
771
  ],
720
- cacheTtlHours: 24
772
+ cacheTtlHours: 24,
773
+ cannbotInstaller: {
774
+ package: "@cannbot-plugin/cannbot",
775
+ channel: "latest",
776
+ ttlHours: 24
777
+ }
721
778
  };
722
779
  }
723
780
  });
@@ -1436,6 +1493,16 @@ var init_embedded_plugins = __esm({
1436
1493
  });
1437
1494
 
1438
1495
  // src/core/registry.ts
1496
+ var registry_exports = {};
1497
+ __export(registry_exports, {
1498
+ PLUGIN_REGISTRY: () => PLUGIN_REGISTRY,
1499
+ findPlugin: () => findPlugin,
1500
+ getAllPlugins: () => getAllPlugins,
1501
+ getCannbotPlugins: () => getCannbotPlugins,
1502
+ getPluginById: () => getPluginById,
1503
+ mergeDynamicPlugins: () => mergeDynamicPlugins,
1504
+ setCannbotPlugins: () => setCannbotPlugins
1505
+ });
1439
1506
  import { readdirSync as readdirSync2, existsSync as existsSync5 } from "fs";
1440
1507
  import { join as join4 } from "path";
1441
1508
  function loadPlugins() {
@@ -1445,6 +1512,12 @@ function loadPlugins() {
1445
1512
  );
1446
1513
  return plugins;
1447
1514
  }
1515
+ function setCannbotPlugins(entries) {
1516
+ CANNBOT_PLUGINS = entries.map((entry) => ({ ...entry, source: "cannbot" }));
1517
+ }
1518
+ function getCannbotPlugins() {
1519
+ return [...CANNBOT_PLUGINS];
1520
+ }
1448
1521
  function mergeDynamicPlugins(repoPath) {
1449
1522
  if (!existsSync5(repoPath)) return;
1450
1523
  const existingIds = new Set(PLUGIN_REGISTRY.map((p) => p.id));
@@ -1485,8 +1558,19 @@ function mergeDynamicPlugins(repoPath) {
1485
1558
  );
1486
1559
  }
1487
1560
  }
1488
- function findPlugin(query) {
1561
+ function findPlugin(query, options) {
1489
1562
  const normalized = query.toLowerCase().trim();
1563
+ const wantCannbot = options?.source !== "skills";
1564
+ if (wantCannbot) {
1565
+ const cannbotExact = CANNBOT_PLUGINS.find(
1566
+ (p) => p.id === normalized || p.id === query.trim()
1567
+ );
1568
+ if (cannbotExact) return cannbotExact;
1569
+ const cannbotAlias = CANNBOT_PLUGINS.find(
1570
+ (p) => p.aliases.some((a) => a === normalized)
1571
+ );
1572
+ if (cannbotAlias) return cannbotAlias;
1573
+ }
1490
1574
  const exactMatch = PLUGIN_REGISTRY.find(
1491
1575
  (p) => p.id === normalized || p.id === query.trim()
1492
1576
  );
@@ -1495,17 +1579,25 @@ function findPlugin(query) {
1495
1579
  (p) => p.aliases.some((a) => a === normalized)
1496
1580
  );
1497
1581
  if (aliasMatch) return aliasMatch;
1582
+ if (wantCannbot) {
1583
+ const cannbotPrefixes = CANNBOT_PLUGINS.filter((p) => p.id.startsWith(normalized));
1584
+ if (cannbotPrefixes.length === 1) return cannbotPrefixes[0];
1585
+ }
1498
1586
  const prefixMatches = PLUGIN_REGISTRY.filter((p) => p.id.startsWith(normalized));
1499
1587
  if (prefixMatches.length === 1) return prefixMatches[0];
1500
1588
  return void 0;
1501
1589
  }
1502
1590
  function getAllPlugins() {
1503
- return PLUGIN_REGISTRY;
1591
+ return [...CANNBOT_PLUGINS, ...PLUGIN_REGISTRY].sort(
1592
+ (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
1593
+ );
1504
1594
  }
1505
1595
  function getPluginById(id) {
1596
+ const cannbot = CANNBOT_PLUGINS.find((p) => p.id === id);
1597
+ if (cannbot) return cannbot;
1506
1598
  return PLUGIN_REGISTRY.find((p) => p.id === id);
1507
1599
  }
1508
- var PLUGIN_REGISTRY;
1600
+ var PLUGIN_REGISTRY, CANNBOT_PLUGINS;
1509
1601
  var init_registry = __esm({
1510
1602
  "src/core/registry.ts"() {
1511
1603
  "use strict";
@@ -1513,6 +1605,7 @@ var init_registry = __esm({
1513
1605
  init_scanner();
1514
1606
  init_embedded_plugins();
1515
1607
  PLUGIN_REGISTRY = loadPlugins();
1608
+ CANNBOT_PLUGINS = [];
1516
1609
  }
1517
1610
  });
1518
1611
 
@@ -2177,8 +2270,9 @@ async function stepPlugins(tool, level) {
2177
2270
  const choices = plugins.map((p) => {
2178
2271
  const isInstalled = installedSet.has(p.id);
2179
2272
  const suffix = isInstalled ? ` [${t("wizard_already_installed")}]` : "";
2273
+ const badge = p.source === "cannbot" ? chalk3.magenta(" [cannbot]") : "";
2180
2274
  return {
2181
- name: `> ${chalk3.cyan(p.displayName)}${suffix}`,
2275
+ name: `> ${chalk3.cyan(p.displayName)}${badge}${suffix}`,
2182
2276
  value: p.id,
2183
2277
  description: p.description
2184
2278
  };
@@ -2403,7 +2497,7 @@ var STATIC_SKILL_CATEGORIES = [
2403
2497
  },
2404
2498
  {
2405
2499
  id: "model",
2406
- name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316",
2500
+ name: "\u6A21\u578B\u63A8\u7406\u4E0E\u8BAD\u7EC3",
2407
2501
  skills: [
2408
2502
  { id: "model-infer-migrator", description: "\u6846\u67B6\u9002\u914D\u4E0E\u90E8\u7F72\u57FA\u7EBF", source: "model" },
2409
2503
  { id: "model-infer-parallel-analysis", description: "\u5E76\u884C\u7B56\u7565\u5206\u6790\uFF08TP/EP/DP\uFF09", source: "model" },
@@ -2417,7 +2511,9 @@ var STATIC_SKILL_CATEGORIES = [
2417
2511
  { id: "model-infer-precision-debug", description: "NPU \u63A8\u7406\u7CBE\u5EA6\u8BCA\u65AD", source: "model" },
2418
2512
  { id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" },
2419
2513
  { id: "model-infer-harmony", description: "\u7AEF\u4FA7\u9E3F\u8499 ASR \u91CF\u5316\u8F6C\u6362\u4E0E\u6253\u5305", source: "model" },
2420
- { id: "model-train-accuracy-debug", description: "PyTorch on NPU \u5927\u6A21\u578B\u8BAD\u7EC3\u7CBE\u5EA6\u5F02\u5E38\u5B9A\u4F4D", source: "model" },
2514
+ { id: "model-train-precision-numerical-mismatch", description: "PyTorch on NPU \u8BAD\u7EC3\u6B63\u5411/\u53CD\u5411\u6709\u9650\u503C\u504F\u5DEE\u8BCA\u65AD", source: "model" },
2515
+ { id: "model-train-precision-nonfinite", description: "PyTorch on NPU \u8BAD\u7EC3 NaN/Inf/Overflow \u8BCA\u65AD", source: "model" },
2516
+ { id: "model-train-precision-determinism", description: "PyTorch on NPU \u8BAD\u7EC3\u975E\u786E\u5B9A\u6027\u8BCA\u65AD", source: "model" },
2421
2517
  { id: "model-train-log-visualization", description: "NPU \u5927\u6A21\u578B\u8BAD\u7EC3\u65E5\u5FD7\u53EF\u89C6\u5316\u4E0E\u5BF9\u6BD4", source: "model" },
2422
2518
  { id: "model-train-oom-analysis", description: "PyTorch on NPU \u8BAD\u7EC3 OOM \u5185\u5B58\u8BCA\u65AD\u4E0E\u4F18\u5316", source: "model" },
2423
2519
  { 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" }
@@ -2800,13 +2896,250 @@ function createRepositoryManager(customPath) {
2800
2896
  return new RepositoryManager(customPath);
2801
2897
  }
2802
2898
 
2803
- // src/core/installer.ts
2804
- init_registry();
2805
- init_manifest();
2899
+ // src/core/cannbot-delegate.ts
2900
+ import { existsSync as existsSync17, readFileSync as readFileSync12, writeFileSync as writeFileSync5, copyFileSync as copyFileSync3, unlinkSync as unlinkSync6, readdirSync as readdirSync8 } from "fs";
2901
+ import { join as join16, resolve as resolve2 } from "path";
2902
+ import { execa as execa6 } from "execa";
2903
+
2904
+ // src/core/cannbot-installer.ts
2806
2905
  init_paths();
2807
- import { existsSync as existsSync14, readFileSync as readFileSync9 } from "fs";
2808
- import { join as join13, basename as basename2 } from "path";
2809
- import { execa as execa4, execaSync } from "execa";
2906
+ init_logger();
2907
+ init_i18n();
2908
+ init_embedded_config();
2909
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync7, readdirSync as readdirSync4, writeFileSync as writeFileSync2 } from "fs";
2910
+ import { join as join10, dirname as dirname5 } from "path";
2911
+ import { fileURLToPath as fileURLToPath4 } from "url";
2912
+ import { execa as execa3, execaSync } from "execa";
2913
+ import { parse as parseYaml3 } from "yaml";
2914
+ var __filename4 = fileURLToPath4(import.meta.url);
2915
+ var __dirname4 = dirname5(__filename4);
2916
+ var DEFAULT_CONFIG = {
2917
+ package: "@cannbot-plugin/cannbot",
2918
+ channel: "latest",
2919
+ ttlHours: 24
2920
+ };
2921
+ var NPM_MIRRORS = [
2922
+ void 0,
2923
+ // user's npm config (registry.npmjs.org by default)
2924
+ "https://registry.npmmirror.com"
2925
+ ];
2926
+ function loadCannbotInstallerConfig() {
2927
+ let config = DEFAULT_CONFIG;
2928
+ const configCandidates = [
2929
+ join10(__dirname4, "config", "repository.yaml"),
2930
+ join10(__dirname4, "..", "config", "repository.yaml")
2931
+ ];
2932
+ for (const configPath of configCandidates) {
2933
+ try {
2934
+ const parsed = parseYaml3(readFileSync7(configPath, "utf-8"));
2935
+ if (!parsed?.cannbotInstaller) continue;
2936
+ config = {
2937
+ package: parsed.cannbotInstaller.package || DEFAULT_CONFIG.package,
2938
+ channel: parsed.cannbotInstaller.channel || DEFAULT_CONFIG.channel,
2939
+ ttlHours: parsed.cannbotInstaller.ttlHours || DEFAULT_CONFIG.ttlHours
2940
+ };
2941
+ break;
2942
+ } catch {
2943
+ }
2944
+ }
2945
+ if (config === DEFAULT_CONFIG) {
2946
+ const embedded = embedded_config_default;
2947
+ if (embedded.cannbotInstaller) {
2948
+ config = {
2949
+ package: embedded.cannbotInstaller.package || DEFAULT_CONFIG.package,
2950
+ channel: embedded.cannbotInstaller.channel || DEFAULT_CONFIG.channel,
2951
+ ttlHours: embedded.cannbotInstaller.ttlHours || DEFAULT_CONFIG.ttlHours
2952
+ };
2953
+ }
2954
+ }
2955
+ return {
2956
+ ...config,
2957
+ channel: process.env.CANNBOT_INSTALLER_CHANNEL || config.channel
2958
+ };
2959
+ }
2960
+ function getCannbotInstallerPrefix() {
2961
+ return join10(getCannbotConfigDir(), "installers", "cannbot");
2962
+ }
2963
+ function getCannbotPackageRoot() {
2964
+ const override = process.env.CANNBOT_INSTALLER_PATH;
2965
+ if (override) return override;
2966
+ const { package: packageName } = loadCannbotInstallerConfig();
2967
+ return join10(getCannbotInstallerPrefix(), "node_modules", ...packageName.split("/"));
2968
+ }
2969
+ function getCannbotBinPath() {
2970
+ return join10(getCannbotPackageRoot(), "bin", "cannbot.js");
2971
+ }
2972
+ function getStatePath() {
2973
+ return join10(getCannbotInstallerPrefix(), "state.json");
2974
+ }
2975
+ function readState() {
2976
+ try {
2977
+ return JSON.parse(readFileSync7(getStatePath(), "utf-8"));
2978
+ } catch {
2979
+ return null;
2980
+ }
2981
+ }
2982
+ function writeState(state) {
2983
+ const prefix = getCannbotInstallerPrefix();
2984
+ if (!existsSync11(prefix)) {
2985
+ mkdirSync4(prefix, { recursive: true });
2986
+ }
2987
+ writeFileSync2(getStatePath(), JSON.stringify(state, null, 2));
2988
+ }
2989
+ function isCannbotInstallerReady() {
2990
+ return existsSync11(getCannbotBinPath());
2991
+ }
2992
+ function getCannbotInstallerVersion() {
2993
+ try {
2994
+ const pkg = JSON.parse(readFileSync7(join10(getCannbotPackageRoot(), "package.json"), "utf-8"));
2995
+ return pkg.version || null;
2996
+ } catch {
2997
+ return null;
2998
+ }
2999
+ }
3000
+ function hasNodeRuntime() {
3001
+ try {
3002
+ const result = execaSync("node", ["--version"], { timeout: 1e4 });
3003
+ return result.exitCode === 0;
3004
+ } catch {
3005
+ return false;
3006
+ }
3007
+ }
3008
+ async function npmInstallInstaller(config, force) {
3009
+ const prefix = getCannbotInstallerPrefix();
3010
+ if (!existsSync11(prefix)) {
3011
+ mkdirSync4(prefix, { recursive: true });
3012
+ }
3013
+ const spec = `${config.package}@${config.channel}`;
3014
+ let lastError = null;
3015
+ for (const registry of NPM_MIRRORS) {
3016
+ const args = ["install", "--prefix", prefix, "--no-audit", "--no-fund", "--ignore-scripts", spec];
3017
+ if (registry) args.push("--registry", registry);
3018
+ try {
3019
+ await execa3("npm", args, { timeout: 3e5 });
3020
+ return;
3021
+ } catch (error) {
3022
+ lastError = error;
3023
+ if (force || !isCannbotInstallerReady()) {
3024
+ continue;
3025
+ }
3026
+ break;
3027
+ }
3028
+ }
3029
+ if (isCannbotInstallerReady() && !force) {
3030
+ logger.warn(t("cannbot_installer_refresh_failed"));
3031
+ return;
3032
+ }
3033
+ throw lastError instanceof Error ? lastError : new Error(t("error_unknown"));
3034
+ }
3035
+ async function ensureCannbotInstaller(options) {
3036
+ const config = loadCannbotInstallerConfig();
3037
+ const packageRoot = getCannbotPackageRoot();
3038
+ if (process.env.CANNBOT_INSTALLER_PATH) {
3039
+ if (!isCannbotInstallerReady()) {
3040
+ throw new Error(
3041
+ t("cannbot_installer_path_invalid").replace("{path}", process.env.CANNBOT_INSTALLER_PATH)
3042
+ );
3043
+ }
3044
+ return packageRoot;
3045
+ }
3046
+ const ready = isCannbotInstallerReady();
3047
+ if (!ready || options?.force) {
3048
+ await npmInstallInstaller(config, options?.force || !ready);
3049
+ writeState({
3050
+ version: getCannbotInstallerVersion() || "unknown",
3051
+ channel: config.channel,
3052
+ installedAt: Date.now()
3053
+ });
3054
+ return packageRoot;
3055
+ }
3056
+ const state = readState();
3057
+ const ttlMs = config.ttlHours * 60 * 60 * 1e3;
3058
+ if (!state || Date.now() - state.installedAt > ttlMs) {
3059
+ try {
3060
+ await npmInstallInstaller(config, false);
3061
+ writeState({
3062
+ version: getCannbotInstallerVersion() || "unknown",
3063
+ channel: config.channel,
3064
+ installedAt: Date.now()
3065
+ });
3066
+ } catch {
3067
+ }
3068
+ }
3069
+ return packageRoot;
3070
+ }
3071
+ function readManifest2(pluginDir) {
3072
+ try {
3073
+ return JSON.parse(readFileSync7(join10(pluginDir, ".claude-plugin", "plugin.json"), "utf-8"));
3074
+ } catch {
3075
+ return null;
3076
+ }
3077
+ }
3078
+ function discoverCannbotPlugins() {
3079
+ if (!isCannbotInstallerReady()) {
3080
+ return [];
3081
+ }
3082
+ const packageRoot = getCannbotPackageRoot();
3083
+ const entries = [];
3084
+ const parents = [
3085
+ join10(packageRoot, "dist", "plugins"),
3086
+ join10(packageRoot, "dist", "plugins-official"),
3087
+ join10(packageRoot, "plugins"),
3088
+ join10(packageRoot, "plugins-official")
3089
+ ];
3090
+ for (const parent of parents) {
3091
+ if (!existsSync11(parent)) continue;
3092
+ let names = [];
3093
+ try {
3094
+ names = readdirSync4(parent);
3095
+ } catch {
3096
+ continue;
3097
+ }
3098
+ for (const name of names) {
3099
+ const pluginDir = join10(parent, name);
3100
+ const manifest = readManifest2(pluginDir);
3101
+ if (!manifest || typeof manifest.name !== "string") continue;
3102
+ if (entries.some((e) => e.id === manifest.name)) continue;
3103
+ entries.push({
3104
+ id: manifest.name,
3105
+ version: manifest.version || "",
3106
+ description: manifest.description || "",
3107
+ skillsCount: Array.isArray(manifest.skills) ? manifest.skills.length : 0,
3108
+ agentsCount: Array.isArray(manifest.agents) ? manifest.agents.length : 0,
3109
+ dir: join10(parent, name)
3110
+ });
3111
+ }
3112
+ }
3113
+ return entries;
3114
+ }
3115
+ async function ensureCannbotDiscovery() {
3116
+ try {
3117
+ await ensureCannbotInstaller();
3118
+ } catch {
3119
+ return false;
3120
+ }
3121
+ const entries = discoverCannbotPlugins();
3122
+ const { setCannbotPlugins: setCannbotPlugins2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
3123
+ setCannbotPlugins2(
3124
+ entries.map((entry) => ({
3125
+ id: entry.id,
3126
+ dir: entry.dir,
3127
+ displayName: entry.id,
3128
+ script: "cannbot",
3129
+ aliases: [],
3130
+ skills: entry.skillsCount,
3131
+ agents: entry.agentsCount,
3132
+ description: entry.description,
3133
+ version: entry.version,
3134
+ source: "cannbot"
3135
+ }))
3136
+ );
3137
+ return true;
3138
+ }
3139
+
3140
+ // src/core/cannbot-registry.ts
3141
+ import { existsSync as existsSync13, readFileSync as readFileSync9, readdirSync as readdirSync6, rmSync as rmSync3, rmdirSync, unlinkSync as unlinkSync4, lstatSync as lstatSync2, writeFileSync as writeFileSync3 } from "fs";
3142
+ import { join as join12, resolve, sep as sep2, dirname as dirname6 } from "path";
2810
3143
 
2811
3144
  // src/core/record.ts
2812
3145
  init_paths();
@@ -2814,21 +3147,21 @@ init_fs();
2814
3147
  init_fs_helpers();
2815
3148
  init_logger();
2816
3149
  init_i18n();
2817
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync7, unlinkSync as unlinkSync3, readdirSync as readdirSync4 } from "fs";
2818
- import { join as join10 } from "path";
3150
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync8, unlinkSync as unlinkSync3, readdirSync as readdirSync5 } from "fs";
3151
+ import { join as join11 } from "path";
2819
3152
  function getInstallsDir() {
2820
- return join10(getCannbotConfigDir(), "installs");
3153
+ return join11(getCannbotConfigDir(), "installs");
2821
3154
  }
2822
3155
  function getRecordPath(pluginId) {
2823
- return join10(getInstallsDir(), `${pluginId}.json`);
3156
+ return join11(getInstallsDir(), `${pluginId}.json`);
2824
3157
  }
2825
3158
  function readRecord(pluginId) {
2826
3159
  const recordPath = getRecordPath(pluginId);
2827
- if (!existsSync11(recordPath)) {
3160
+ if (!existsSync12(recordPath)) {
2828
3161
  return null;
2829
3162
  }
2830
3163
  try {
2831
- const content = readFileSync7(recordPath, "utf-8");
3164
+ const content = readFileSync8(recordPath, "utf-8");
2832
3165
  return JSON.parse(content);
2833
3166
  } catch {
2834
3167
  logger.warn(t("record_corrupted").replace("{file}", recordPath));
@@ -2837,15 +3170,15 @@ function readRecord(pluginId) {
2837
3170
  }
2838
3171
  function writeRecord(record) {
2839
3172
  const installsDir = getInstallsDir();
2840
- if (!existsSync11(installsDir)) {
2841
- mkdirSync4(installsDir, { recursive: true });
3173
+ if (!existsSync12(installsDir)) {
3174
+ mkdirSync5(installsDir, { recursive: true });
2842
3175
  }
2843
3176
  const recordPath = getRecordPath(record.pluginId);
2844
3177
  atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
2845
3178
  }
2846
3179
  function deleteRecord(pluginId) {
2847
3180
  const recordPath = getRecordPath(pluginId);
2848
- if (existsSync11(recordPath)) {
3181
+ if (existsSync12(recordPath)) {
2849
3182
  unlinkSync3(recordPath);
2850
3183
  }
2851
3184
  }
@@ -2854,59 +3187,59 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
2854
3187
  const directories = [];
2855
3188
  if (manifest) {
2856
3189
  const skillsDir = getSkillsRoot(tool, level, installPath);
2857
- if (existsSync11(skillsDir)) {
3190
+ if (existsSync12(skillsDir)) {
2858
3191
  directories.push(skillsDir);
2859
3192
  for (const skillName of manifest.installed_skills || []) {
2860
- const skillPath = join10(skillsDir, skillName);
2861
- if (existsSync11(skillPath) || isSymlink(skillPath)) {
3193
+ const skillPath = join11(skillsDir, skillName);
3194
+ if (existsSync12(skillPath) || isSymlink(skillPath)) {
2862
3195
  files.push(skillPath);
2863
3196
  }
2864
3197
  }
2865
3198
  }
2866
- const agentsDir = join10(configRoot, "agents");
2867
- if (existsSync11(agentsDir)) {
3199
+ const agentsDir = join11(configRoot, "agents");
3200
+ if (existsSync12(agentsDir)) {
2868
3201
  directories.push(agentsDir);
2869
3202
  for (const agentName of manifest.installed_agents || []) {
2870
- const agentPath = join10(agentsDir, agentName);
2871
- const agentPathMd = join10(agentsDir, agentName + ".md");
2872
- if (existsSync11(agentPath) || isSymlink(agentPath)) {
3203
+ const agentPath = join11(agentsDir, agentName);
3204
+ const agentPathMd = join11(agentsDir, agentName + ".md");
3205
+ if (existsSync12(agentPath) || isSymlink(agentPath)) {
2873
3206
  files.push(agentPath);
2874
- } else if (existsSync11(agentPathMd) || isSymlink(agentPathMd)) {
3207
+ } else if (existsSync12(agentPathMd) || isSymlink(agentPathMd)) {
2875
3208
  files.push(agentPathMd);
2876
3209
  }
2877
3210
  }
2878
3211
  }
2879
3212
  }
2880
- const workflowsLink = join10(configRoot, "workflows");
3213
+ const workflowsLink = join11(configRoot, "workflows");
2881
3214
  if (isSymlink(workflowsLink)) {
2882
3215
  files.push(workflowsLink);
2883
3216
  }
2884
- const manifestPath = join10(configRoot, "cannbot-manifest.json");
2885
- if (existsSync11(manifestPath)) {
3217
+ const manifestPath = join11(configRoot, "cannbot-manifest.json");
3218
+ if (existsSync12(manifestPath)) {
2886
3219
  files.push(manifestPath);
2887
3220
  }
2888
- const pluginManifestPath = join10(configRoot, `${pluginId}-manifest.json`);
2889
- if (existsSync11(pluginManifestPath)) {
3221
+ const pluginManifestPath = join11(configRoot, `${pluginId}-manifest.json`);
3222
+ if (existsSync12(pluginManifestPath)) {
2890
3223
  files.push(pluginManifestPath);
2891
3224
  }
2892
3225
  const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
2893
- const configFilePath = level === "project" ? join10(installPath, configFileName) : join10(configRoot, configFileName);
2894
- if (existsSync11(configFilePath) || isSymlink(configFilePath)) {
3226
+ const configFilePath = level === "project" ? join11(installPath, configFileName) : join11(configRoot, configFileName);
3227
+ if (existsSync12(configFilePath) || isSymlink(configFilePath)) {
2895
3228
  files.push(configFilePath);
2896
3229
  }
2897
3230
  if (level === "project" && configRootConfigLink !== false) {
2898
- const configRootConfigPath = join10(configRoot, configFileName);
2899
- if (configRootConfigPath !== configFilePath && (existsSync11(configRootConfigPath) || isSymlink(configRootConfigPath))) {
3231
+ const configRootConfigPath = join11(configRoot, configFileName);
3232
+ if (configRootConfigPath !== configFilePath && (existsSync12(configRootConfigPath) || isSymlink(configRootConfigPath))) {
2900
3233
  files.push(configRootConfigPath);
2901
3234
  }
2902
3235
  }
2903
3236
  const repoLinks = externalRepoNames && externalRepoNames.length > 0 ? externalRepoNames : ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples", "ops-tensor"];
2904
3237
  for (const repoName of repoLinks) {
2905
- const repoLinkPath = join10(installPath, repoName);
3238
+ const repoLinkPath = join11(installPath, repoName);
2906
3239
  if (isSymlink(repoLinkPath)) {
2907
3240
  files.push(repoLinkPath);
2908
3241
  }
2909
- const repoLinkInConfig = join10(configRoot, repoName);
3242
+ const repoLinkInConfig = join11(configRoot, repoName);
2910
3243
  if (isSymlink(repoLinkInConfig)) {
2911
3244
  files.push(repoLinkInConfig);
2912
3245
  }
@@ -2924,15 +3257,15 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
2924
3257
  };
2925
3258
  }
2926
3259
  function getSkillRecordPath() {
2927
- return join10(getInstallsDir(), "skills.json");
3260
+ return join11(getInstallsDir(), "skills.json");
2928
3261
  }
2929
3262
  function readSkillRecord() {
2930
3263
  const recordPath = getSkillRecordPath();
2931
- if (!existsSync11(recordPath)) {
3264
+ if (!existsSync12(recordPath)) {
2932
3265
  return {};
2933
3266
  }
2934
3267
  try {
2935
- const content = readFileSync7(recordPath, "utf-8");
3268
+ const content = readFileSync8(recordPath, "utf-8");
2936
3269
  return JSON.parse(content);
2937
3270
  } catch {
2938
3271
  logger.warn(t("skill_record_corrupted"));
@@ -2941,8 +3274,8 @@ function readSkillRecord() {
2941
3274
  }
2942
3275
  function writeSkillRecord(record) {
2943
3276
  const installsDir = getInstallsDir();
2944
- if (!existsSync11(installsDir)) {
2945
- mkdirSync4(installsDir, { recursive: true });
3277
+ if (!existsSync12(installsDir)) {
3278
+ mkdirSync5(installsDir, { recursive: true });
2946
3279
  }
2947
3280
  const recordPath = getSkillRecordPath();
2948
3281
  atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
@@ -3004,19 +3337,19 @@ function getInstalledSkills(tool, level, installPath, allowFsScan = true) {
3004
3337
  const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
3005
3338
  const skillsDir = getSkillsRoot(tool, level);
3006
3339
  const fromRecord = recordedSkills.filter((skillId) => {
3007
- const skillPath = join10(skillsDir, skillId);
3008
- return existsSync11(skillPath) || isSymlink(skillPath);
3340
+ const skillPath = join11(skillsDir, skillId);
3341
+ return existsSync12(skillPath) || isSymlink(skillPath);
3009
3342
  });
3010
3343
  if (!allowFsScan) {
3011
3344
  return fromRecord;
3012
3345
  }
3013
3346
  const fromFs = [];
3014
- if (existsSync11(skillsDir)) {
3347
+ if (existsSync12(skillsDir)) {
3015
3348
  try {
3016
- const entries = readdirSync4(skillsDir);
3349
+ const entries = readdirSync5(skillsDir);
3017
3350
  for (const entry of entries) {
3018
3351
  if (entry === "." || entry === "..") continue;
3019
- const entryPath = join10(skillsDir, entry);
3352
+ const entryPath = join11(skillsDir, entry);
3020
3353
  if (isSymlink(entryPath) && !fromRecord.includes(entry)) {
3021
3354
  fromFs.push(entry);
3022
3355
  }
@@ -3035,15 +3368,389 @@ function getLastBatchSkills(tool, level, installPath) {
3035
3368
  const lastBatch = entry.batches[entry.batches.length - 1];
3036
3369
  const skillsDir = getSkillsRoot(tool, level, installPath);
3037
3370
  return lastBatch.skills.filter((skillId) => {
3038
- const skillPath = join10(skillsDir, skillId);
3039
- return existsSync11(skillPath) || isSymlink(skillPath);
3371
+ const skillPath = join11(skillsDir, skillId);
3372
+ return existsSync12(skillPath) || isSymlink(skillPath);
3040
3373
  });
3041
3374
  }
3042
3375
 
3376
+ // src/core/cannbot-registry.ts
3377
+ init_config();
3378
+ init_paths();
3379
+ init_logger();
3380
+ init_i18n();
3381
+ var CANNBOT_TOOL_LAYOUTS = {
3382
+ opencode: { configDirs: [".opencode"], defaultConfigDir: ".opencode" },
3383
+ codex: { configDirs: [".codex"], defaultConfigDir: ".codex" },
3384
+ claude: { configDirs: [".claude"], defaultConfigDir: ".claude" },
3385
+ trae: { configDirs: [".traecli", ".marscode", ".trae", ".trae-cn"], defaultConfigDir: ".trae" }
3386
+ };
3387
+ var CANNBOT_SUPPORTED_TOOLS = Object.keys(CANNBOT_TOOL_LAYOUTS);
3388
+ var CANNBOT_REGISTRY_SCHEMA_VERSION = 2;
3389
+ var REGISTRY_FILE_NAME = "cannbot-plugin.json";
3390
+ function toolConfigDir(target, tool) {
3391
+ const layout = CANNBOT_TOOL_LAYOUTS[tool];
3392
+ if (!layout) return join12(target, `.${tool}`);
3393
+ return layout.configDirs.map((name) => join12(target, name)).find((path) => existsSync13(path)) ?? join12(target, layout.defaultConfigDir);
3394
+ }
3395
+ function findCannbotRegistryPath(target, tool) {
3396
+ const configDir = toolConfigDir(target, tool);
3397
+ const registryPath = join12(configDir, REGISTRY_FILE_NAME);
3398
+ return existsSync13(registryPath) ? registryPath : null;
3399
+ }
3400
+ function readCannbotRegistry(target, tool) {
3401
+ const registryPath = findCannbotRegistryPath(target, tool);
3402
+ if (!registryPath) return null;
3403
+ let registry;
3404
+ try {
3405
+ registry = JSON.parse(readFileSync9(registryPath, "utf-8"));
3406
+ } catch {
3407
+ logger.warn(t("cannbot_registry_corrupted").replace("{file}", registryPath));
3408
+ return null;
3409
+ }
3410
+ if (typeof registry.schemaVersion !== "number" || !Array.isArray(registry.plugins)) {
3411
+ logger.warn(t("cannbot_registry_corrupted").replace("{file}", registryPath));
3412
+ return null;
3413
+ }
3414
+ return {
3415
+ registryPath,
3416
+ registry,
3417
+ schemaSupported: registry.schemaVersion === CANNBOT_REGISTRY_SCHEMA_VERSION
3418
+ };
3419
+ }
3420
+ function toInstalledEntry(target, registryPath, record) {
3421
+ return {
3422
+ id: record.plugin,
3423
+ version: record.pluginVersion || "",
3424
+ tool: record.tool,
3425
+ target,
3426
+ registryPath,
3427
+ skillsCount: Array.isArray(record.skills) ? record.skills.length : 0,
3428
+ agentsCount: Array.isArray(record.agents) ? record.agents.length : 0,
3429
+ record
3430
+ };
3431
+ }
3432
+ function scanCannbotInstalled(target) {
3433
+ const root = resolve(target || process.cwd());
3434
+ const installed = [];
3435
+ for (const tool of CANNBOT_SUPPORTED_TOOLS) {
3436
+ const result = readCannbotRegistry(root, tool);
3437
+ if (!result) continue;
3438
+ for (const record of result.registry.plugins) {
3439
+ if (!record?.plugin) continue;
3440
+ installed.push(toInstalledEntry(root, result.registryPath, record));
3441
+ }
3442
+ }
3443
+ return installed;
3444
+ }
3445
+ function removeMarkerBlock(content, pluginName) {
3446
+ const start = `<!-- cannbot:${pluginName}:start -->`;
3447
+ const end = `<!-- cannbot:${pluginName}:end -->`;
3448
+ const pattern = new RegExp(
3449
+ `${start.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`,
3450
+ "g"
3451
+ );
3452
+ return content.replace(pattern, "");
3453
+ }
3454
+ function countMarkerBlocks(content, pluginName) {
3455
+ const start = `<!-- cannbot:${pluginName}:start -->`;
3456
+ return content.split(start).length - 1;
3457
+ }
3458
+ function removeIfSafe(pathToRemove, allowedBase) {
3459
+ const resolved = resolve(pathToRemove);
3460
+ if (resolved !== allowedBase && !resolved.startsWith(allowedBase + sep2)) {
3461
+ return "skipped";
3462
+ }
3463
+ try {
3464
+ const stat = lstatSync2(resolved);
3465
+ if (stat.isDirectory()) {
3466
+ rmSync3(resolved, { recursive: true, force: true });
3467
+ } else {
3468
+ unlinkSync4(resolved);
3469
+ }
3470
+ return "removed";
3471
+ } catch {
3472
+ return "missing";
3473
+ }
3474
+ }
3475
+ function removeEmptyDirUpward(dirPath, stopAt) {
3476
+ let current = resolve(dirPath);
3477
+ const stop = resolve(stopAt);
3478
+ let removedAny = false;
3479
+ while (current.startsWith(stop + sep2)) {
3480
+ try {
3481
+ const entries = readdirSync6(current);
3482
+ if (entries.length > 0) break;
3483
+ rmdirSync(current);
3484
+ removedAny = true;
3485
+ } catch {
3486
+ break;
3487
+ }
3488
+ current = dirname6(current);
3489
+ }
3490
+ return removedAny;
3491
+ }
3492
+ function cleanHookSettings(hookSettingsPath, target, pluginId) {
3493
+ const resolved = resolve(target, hookSettingsPath);
3494
+ if (!existsSync13(resolved)) return;
3495
+ let settings;
3496
+ try {
3497
+ settings = JSON.parse(readFileSync9(resolved, "utf-8"));
3498
+ } catch {
3499
+ return;
3500
+ }
3501
+ if (!settings || typeof settings !== "object" || !settings.hooks) return;
3502
+ const pluginRootFragment = `.cannbot/plugins/${pluginId}`;
3503
+ const cleanHooks = (hooks) => {
3504
+ if (Array.isArray(hooks)) {
3505
+ return hooks.filter((entry) => !JSON.stringify(entry).includes(pluginRootFragment));
3506
+ }
3507
+ if (hooks && typeof hooks === "object") {
3508
+ const result = {};
3509
+ for (const [key, value] of Object.entries(hooks)) {
3510
+ const cleaned = cleanHooks(value);
3511
+ if (Array.isArray(cleaned) ? cleaned.length > 0 : cleaned !== void 0) {
3512
+ result[key] = cleaned;
3513
+ }
3514
+ }
3515
+ return result;
3516
+ }
3517
+ return hooks;
3518
+ };
3519
+ try {
3520
+ const cleaned = cleanHooks(settings.hooks);
3521
+ if (Array.isArray(cleaned) ? cleaned.length === 0 : Object.keys(cleaned).length === 0) {
3522
+ delete settings.hooks;
3523
+ } else {
3524
+ settings.hooks = cleaned;
3525
+ }
3526
+ writeFileSync3(resolved, `${JSON.stringify(settings, null, 2)}
3527
+ `);
3528
+ } catch {
3529
+ }
3530
+ }
3531
+ function cleanCodexNativePlugin(record, pluginId) {
3532
+ const native = record.nativePlugin;
3533
+ if (!native || typeof native !== "object") return;
3534
+ if (native.pluginRoot) {
3535
+ try {
3536
+ rmSync3(native.pluginRoot, { recursive: true, force: true });
3537
+ logger.step(` ${t("uninstall_remove_label")}: ${native.pluginRoot}`);
3538
+ } catch {
3539
+ }
3540
+ }
3541
+ if (native.marketplacePath && existsSync13(native.marketplacePath)) {
3542
+ try {
3543
+ const marketplace = JSON.parse(readFileSync9(native.marketplacePath, "utf-8"));
3544
+ if (Array.isArray(marketplace?.plugins)) {
3545
+ marketplace.plugins = marketplace.plugins.filter(
3546
+ (p) => !(p && (p.name === pluginId || p.name === native.marketplaceName))
3547
+ );
3548
+ if (marketplace.plugins.length === 0) {
3549
+ unlinkSync4(native.marketplacePath);
3550
+ } else {
3551
+ writeFileSync3(native.marketplacePath, `${JSON.stringify(marketplace, null, 2)}
3552
+ `);
3553
+ }
3554
+ }
3555
+ } catch {
3556
+ }
3557
+ }
3558
+ }
3559
+ function cleanOpenCodePluginRegistration(root) {
3560
+ const configPath = join12(root, ".opencode", "opencode.json");
3561
+ if (!existsSync13(configPath)) return;
3562
+ let config;
3563
+ try {
3564
+ config = JSON.parse(readFileSync9(configPath, "utf-8"));
3565
+ } catch {
3566
+ return;
3567
+ }
3568
+ if (!Array.isArray(config.plugin)) return;
3569
+ const CLEAN_PACKAGE_PREFIX = "@cannbot-plugin/cannbot";
3570
+ config.plugin = config.plugin.filter(
3571
+ (entry) => !(typeof entry === "string" && entry.startsWith(CLEAN_PACKAGE_PREFIX))
3572
+ );
3573
+ try {
3574
+ if (Array.isArray(config.plugin) && config.plugin.length === 0) {
3575
+ delete config.plugin;
3576
+ }
3577
+ if (Object.keys(config).length === 0) {
3578
+ unlinkSync4(configPath);
3579
+ removeOpenCodePluginGitignore(dirname6(configPath));
3580
+ } else {
3581
+ writeFileSync3(configPath, `${JSON.stringify(config, null, 2)}
3582
+ `);
3583
+ }
3584
+ } catch {
3585
+ }
3586
+ }
3587
+ function removeOpenCodePluginGitignore(configDir) {
3588
+ const gitignorePath = join12(configDir, ".gitignore");
3589
+ if (!existsSync13(gitignorePath)) return;
3590
+ try {
3591
+ const content = readFileSync9(gitignorePath, "utf-8");
3592
+ if (content.includes("bun.lock") && content.includes("node_modules")) {
3593
+ unlinkSync4(gitignorePath);
3594
+ }
3595
+ } catch {
3596
+ }
3597
+ }
3598
+ function uninstallCannbotPlugin(pluginId, target) {
3599
+ const result = {
3600
+ success: false,
3601
+ pluginId,
3602
+ removedFiles: 0,
3603
+ removedDirs: 0,
3604
+ errors: []
3605
+ };
3606
+ const root = resolve(target || process.cwd());
3607
+ let read = null;
3608
+ let record;
3609
+ for (const tool of CANNBOT_SUPPORTED_TOOLS) {
3610
+ const candidate = readCannbotRegistry(root, tool);
3611
+ if (!candidate) continue;
3612
+ const found = candidate.registry.plugins.find((r) => r?.plugin === pluginId);
3613
+ if (found) {
3614
+ read = candidate;
3615
+ record = found;
3616
+ break;
3617
+ }
3618
+ }
3619
+ if (!read || !record) {
3620
+ result.errors.push(t("cannbot_uninstall_no_record"));
3621
+ return result;
3622
+ }
3623
+ if (!read.schemaSupported) {
3624
+ result.errors.push(
3625
+ t("cannbot_registry_schema_unsupported").replace("{version}", String(read.registry.schemaVersion))
3626
+ );
3627
+ return result;
3628
+ }
3629
+ const registryPath = read.registryPath;
3630
+ const allowedBase = root;
3631
+ for (const relPath of [...record.skills || [], ...record.agents || []]) {
3632
+ const absolute = join12(root, relPath);
3633
+ if (removeIfSafe(absolute, allowedBase) === "removed") {
3634
+ result.removedFiles++;
3635
+ removeEmptyDirUpward(dirname6(absolute), allowedBase);
3636
+ }
3637
+ }
3638
+ if (record.instructions) {
3639
+ const instructionsPath = join12(root, record.instructions);
3640
+ const resolved = resolve(instructionsPath);
3641
+ if (resolved.startsWith(allowedBase + sep2) && existsSync13(resolved)) {
3642
+ try {
3643
+ const content = readFileSync9(resolved, "utf-8");
3644
+ const cleaned = removeMarkerBlock(content, pluginId);
3645
+ if (cleaned.trim().length === 0) {
3646
+ unlinkSync4(resolved);
3647
+ result.removedFiles++;
3648
+ } else if (cleaned !== content) {
3649
+ writeFileSync3(resolved, cleaned);
3650
+ }
3651
+ } catch (error) {
3652
+ result.errors.push(error instanceof Error ? error.message : t("error_unknown"));
3653
+ }
3654
+ }
3655
+ }
3656
+ if (record.assets) {
3657
+ if (removeIfSafe(join12(root, record.assets), allowedBase) === "removed") {
3658
+ result.removedDirs++;
3659
+ removeEmptyDirUpward(join12(root, ".cannbot", "plugins"), allowedBase);
3660
+ }
3661
+ }
3662
+ const siblings = scanCannbotInstalled(root).map((e) => e.record).filter((r) => r.plugin !== pluginId);
3663
+ const siblingUses = (rel) => siblings.some((r) => [r.permissions, r.settings, r.hookSettings].filter(Boolean).includes(rel));
3664
+ if (record.permissions && !siblingUses(record.permissions)) {
3665
+ if (removeIfSafe(join12(root, record.permissions), allowedBase) === "removed") {
3666
+ result.removedDirs++;
3667
+ }
3668
+ }
3669
+ if (record.settings && !siblingUses(record.settings)) {
3670
+ if (removeIfSafe(join12(root, record.settings), allowedBase) === "removed") {
3671
+ result.removedFiles++;
3672
+ }
3673
+ }
3674
+ if (record.hookSettings) {
3675
+ cleanHookSettings(record.hookSettings, root, pluginId);
3676
+ }
3677
+ for (const relDep of record.dependencies || []) {
3678
+ if (removeIfSafe(join12(root, relDep), allowedBase) === "removed") {
3679
+ result.removedDirs++;
3680
+ }
3681
+ }
3682
+ if ((record.dependencies || []).length > 0) {
3683
+ removeEmptyDirUpward(join12(root, ".cannbot", "dependencies", pluginId), allowedBase);
3684
+ }
3685
+ cleanCodexNativePlugin(record, pluginId);
3686
+ if (record.nativePlugin && typeof record.nativePlugin === "object" && record.nativePlugin.package) {
3687
+ logger.warn(t("cannbot_uninstall_native_hint").replace("{spec}", String(record.nativePlugin.package)));
3688
+ }
3689
+ if (record.tool === "opencode" && siblings.length === 0) {
3690
+ cleanOpenCodePluginRegistration(root);
3691
+ }
3692
+ try {
3693
+ const registry = JSON.parse(readFileSync9(registryPath, "utf-8"));
3694
+ registry.plugins = registry.plugins.filter((r) => r?.plugin !== pluginId);
3695
+ if (registry.plugins.length === 0) {
3696
+ unlinkSync4(registryPath);
3697
+ removeEmptyDirUpward(dirname6(registryPath), allowedBase);
3698
+ } else {
3699
+ writeFileSync3(registryPath, `${JSON.stringify(registry, null, 2)}
3700
+ `);
3701
+ }
3702
+ } catch (error) {
3703
+ result.errors.push(error instanceof Error ? error.message : t("error_unknown"));
3704
+ }
3705
+ if (CANNBOT_TOOL_LAYOUTS[record.tool]) {
3706
+ const configDir = dirname6(registryPath);
3707
+ const layoutRoots = [join12(configDir, "agents"), join12(configDir, "skills")];
3708
+ if (record.tool === "opencode" || record.tool === "codex") {
3709
+ layoutRoots.push(join12(root, ".agents", "skills"), join12(root, ".agents"));
3710
+ }
3711
+ for (const dirPath of layoutRoots) {
3712
+ if (removeEmptyDirUpward(dirPath, allowedBase)) {
3713
+ result.removedDirs++;
3714
+ }
3715
+ }
3716
+ }
3717
+ deleteRecord(pluginId);
3718
+ removeInstalledPlugin(pluginId);
3719
+ result.success = result.errors.length === 0;
3720
+ return result;
3721
+ }
3722
+ function findOrphanedDelegatedRecords(target) {
3723
+ const orphans = [];
3724
+ const installed = new Set(scanCannbotInstalled(target).map((e) => e.id));
3725
+ const installsDir = join12(getCannbotConfigDir(), "installs");
3726
+ if (!existsSync13(installsDir)) return orphans;
3727
+ for (const file of readdirSync6(installsDir)) {
3728
+ if (!file.endsWith(".json") || file === "skills.json") continue;
3729
+ const pluginId = file.replace(/\.json$/, "");
3730
+ try {
3731
+ const rec = JSON.parse(readFileSync9(join12(installsDir, file), "utf-8"));
3732
+ if (rec?.kind !== "delegated") continue;
3733
+ if (!installed.has(pluginId)) {
3734
+ orphans.push(pluginId);
3735
+ }
3736
+ } catch {
3737
+ }
3738
+ }
3739
+ return orphans;
3740
+ }
3741
+
3742
+ // src/core/installer.ts
3743
+ init_registry();
3744
+ init_manifest();
3745
+ init_paths();
3746
+ import { existsSync as existsSync16, readFileSync as readFileSync11 } from "fs";
3747
+ import { join as join15, basename as basename2 } from "path";
3748
+ import { execa as execa5, execaSync as execaSync2 } from "execa";
3749
+
3043
3750
  // src/core/backup.ts
3044
3751
  init_manifest();
3045
- import { existsSync as existsSync12, copyFileSync, readdirSync as readdirSync5, unlinkSync as unlinkSync4 } from "fs";
3046
- import { join as join11 } from "path";
3752
+ import { existsSync as existsSync14, copyFileSync, readdirSync as readdirSync7, unlinkSync as unlinkSync5 } from "fs";
3753
+ import { join as join13 } from "path";
3047
3754
  init_registry();
3048
3755
  init_paths();
3049
3756
  function detectCurrentPlugin(configRoot, tool, installPath) {
@@ -3064,9 +3771,9 @@ function detectCurrentPlugin(configRoot, tool, installPath) {
3064
3771
  }
3065
3772
  }
3066
3773
  const agentsFileName = getAgentsFileName(tool);
3067
- const configRootAgentsFile = join11(configRoot, agentsFileName);
3068
- const installPathAgentsFile = installPath ? join11(installPath, agentsFileName) : null;
3069
- if (!existsSync12(configRootAgentsFile) && !(installPathAgentsFile && existsSync12(installPathAgentsFile))) {
3774
+ const configRootAgentsFile = join13(configRoot, agentsFileName);
3775
+ const installPathAgentsFile = installPath ? join13(installPath, agentsFileName) : null;
3776
+ if (!existsSync14(configRootAgentsFile) && !(installPathAgentsFile && existsSync14(installPathAgentsFile))) {
3070
3777
  return null;
3071
3778
  }
3072
3779
  const allRecords = readAllRecords();
@@ -3084,13 +3791,13 @@ function detectCurrentPlugin(configRoot, tool, installPath) {
3084
3791
  return null;
3085
3792
  }
3086
3793
  function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
3087
- const agentsFile = join11(configRoot, getAgentsFileName(tool));
3088
- if (!existsSync12(agentsFile)) {
3794
+ const agentsFile = join13(configRoot, getAgentsFileName(tool));
3795
+ if (!existsSync14(agentsFile)) {
3089
3796
  return null;
3090
3797
  }
3091
3798
  const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
3092
3799
  const backupFileName = `${getAgentsFileName(tool)}.cannbot-backup.${fromPluginId}.${timestamp}`;
3093
- const backupPath = join11(configRoot, backupFileName);
3800
+ const backupPath = join13(configRoot, backupFileName);
3094
3801
  try {
3095
3802
  copyFileSync(agentsFile, backupPath);
3096
3803
  return {
@@ -3104,10 +3811,10 @@ function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
3104
3811
  }
3105
3812
  }
3106
3813
  function findBackups(configRoot) {
3107
- if (!existsSync12(configRoot)) {
3814
+ if (!existsSync14(configRoot)) {
3108
3815
  return [];
3109
3816
  }
3110
- const files = readdirSync5(configRoot);
3817
+ const files = readdirSync7(configRoot);
3111
3818
  const backups = [];
3112
3819
  for (const file of files) {
3113
3820
  if (file.includes(".cannbot-backup.")) {
@@ -3120,7 +3827,7 @@ function findBackups(configRoot) {
3120
3827
  const timestamp = pluginIdAndTime.substring(lastDotIndex + 1);
3121
3828
  const plugin = findPlugin(pluginId);
3122
3829
  backups.push({
3123
- filePath: join11(configRoot, file),
3830
+ filePath: join13(configRoot, file),
3124
3831
  pluginId,
3125
3832
  pluginName: plugin?.displayName || pluginId,
3126
3833
  backupTime: timestamp
@@ -3133,7 +3840,7 @@ function findBackups(configRoot) {
3133
3840
  }
3134
3841
  function restoreBackup(backupPath, configRoot, tool) {
3135
3842
  try {
3136
- const agentsFile = join11(configRoot, getAgentsFileName(tool));
3843
+ const agentsFile = join13(configRoot, getAgentsFileName(tool));
3137
3844
  copyFileSync(backupPath, agentsFile);
3138
3845
  return true;
3139
3846
  } catch {
@@ -3142,8 +3849,8 @@ function restoreBackup(backupPath, configRoot, tool) {
3142
3849
  }
3143
3850
  function deleteBackup(backupPath) {
3144
3851
  try {
3145
- if (existsSync12(backupPath)) {
3146
- unlinkSync4(backupPath);
3852
+ if (existsSync14(backupPath)) {
3853
+ unlinkSync5(backupPath);
3147
3854
  return true;
3148
3855
  }
3149
3856
  return false;
@@ -3216,9 +3923,42 @@ init_paths();
3216
3923
  init_fs_helpers();
3217
3924
  init_logger();
3218
3925
  init_i18n();
3219
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, symlinkSync, realpathSync, readlinkSync, copyFileSync as copyFileSync2, cpSync, writeFileSync as writeFileSync2, rmSync as rmSync3, readFileSync as readFileSync8 } from "fs";
3220
- import { join as join12, basename } from "path";
3221
- import { execa as execa3 } from "execa";
3926
+ import { existsSync as existsSync15, mkdirSync as mkdirSync6, symlinkSync, realpathSync, readlinkSync, copyFileSync as copyFileSync2, cpSync, writeFileSync as writeFileSync4, rmSync as rmSync4, readFileSync as readFileSync10, constants as fsConstants } from "fs";
3927
+ import { join as join14, basename } from "path";
3928
+ import { execa as execa4 } from "execa";
3929
+ var ISSUE_HANDLER_ID = "gitcode-issue-handler";
3930
+ var ISSUE_HANDLER_CONFIG_FILES = ["classify_config.yaml", "operator_owners.yaml"];
3931
+ function shouldInitializeIssueHandlerConfig(level, installedSkills) {
3932
+ return level === "project" && installedSkills.includes(ISSUE_HANDLER_ID);
3933
+ }
3934
+ function instantiateIssueHandlerConfig(repoPath, targetRepoPath) {
3935
+ const configDir = join14(targetRepoPath, ".cannbot", ISSUE_HANDLER_ID, "config");
3936
+ const legacyConfigDir = targetRepoPath;
3937
+ try {
3938
+ mkdirSync6(configDir, { recursive: true });
3939
+ } catch (error) {
3940
+ logger.warn(`issue-handler config directory creation failed: ${error instanceof Error ? error.message : String(error)}`);
3941
+ return;
3942
+ }
3943
+ for (const fileName of ISSUE_HANDLER_CONFIG_FILES) {
3944
+ const targetPath = join14(configDir, fileName);
3945
+ if (existsSync15(targetPath) || isSymlink(targetPath)) continue;
3946
+ const legacyPath = join14(legacyConfigDir, fileName);
3947
+ const templatePath = join14(repoPath, "infra", ISSUE_HANDLER_ID, "assets", `${fileName}.template`);
3948
+ const sourcePath = existsSync15(legacyPath) ? legacyPath : templatePath;
3949
+ if (!existsSync15(sourcePath)) {
3950
+ logger.warn(`issue-handler config source not found: ${sourcePath}`);
3951
+ continue;
3952
+ }
3953
+ try {
3954
+ copyFileSync2(sourcePath, targetPath, fsConstants.COPYFILE_EXCL);
3955
+ } catch (error) {
3956
+ if (error?.code !== "EEXIST") {
3957
+ logger.warn(`issue-handler config install failed for ${fileName}: ${error instanceof Error ? error.message : String(error)}`);
3958
+ }
3959
+ }
3960
+ }
3961
+ }
3222
3962
  function installLink(source, target) {
3223
3963
  try {
3224
3964
  const resolved = realpathSync(source);
@@ -3234,7 +3974,7 @@ function installLink(source, target) {
3234
3974
  return "skipped";
3235
3975
  }
3236
3976
  }
3237
- if (existsSync13(target) || isSymlink(target)) {
3977
+ if (existsSync15(target) || isSymlink(target)) {
3238
3978
  removePath(target);
3239
3979
  }
3240
3980
  try {
@@ -3252,19 +3992,19 @@ function installLink(source, target) {
3252
3992
  }
3253
3993
  }
3254
3994
  function installConfigFile(pluginDir, configFile, tool, level, configRoot, installPath, configRootConfigLink) {
3255
- const sourcePath = join12(pluginDir, configFile);
3256
- if (!existsSync13(sourcePath)) return;
3995
+ const sourcePath = join14(pluginDir, configFile);
3996
+ if (!existsSync15(sourcePath)) return;
3257
3997
  const agentsFileName = getAgentsFileName(tool);
3258
3998
  const isProjectLevel = level === "project";
3259
3999
  let primaryTarget;
3260
4000
  if (isProjectLevel) {
3261
- primaryTarget = join12(installPath, agentsFileName);
4001
+ primaryTarget = join14(installPath, agentsFileName);
3262
4002
  } else {
3263
- primaryTarget = join12(configRoot, agentsFileName);
4003
+ primaryTarget = join14(configRoot, agentsFileName);
3264
4004
  }
3265
4005
  try {
3266
4006
  const resolvedSource = realpathSync(sourcePath);
3267
- if (existsSync13(primaryTarget) || isSymlink(primaryTarget)) {
4007
+ if (existsSync15(primaryTarget) || isSymlink(primaryTarget)) {
3268
4008
  removePath(primaryTarget);
3269
4009
  }
3270
4010
  try {
@@ -3279,10 +4019,10 @@ function installConfigFile(pluginDir, configFile, tool, level, configRoot, insta
3279
4019
  } catch (e) {
3280
4020
  logger.warn(t("install_config_failed").replace("{error}", e.message));
3281
4021
  }
3282
- if (isProjectLevel && primaryTarget !== join12(configRoot, agentsFileName)) {
4022
+ if (isProjectLevel && primaryTarget !== join14(configRoot, agentsFileName)) {
3283
4023
  if (configRootConfigLink === false) return;
3284
- const configRootTarget = join12(configRoot, agentsFileName);
3285
- if (existsSync13(configRootTarget) || isSymlink(configRootTarget)) {
4024
+ const configRootTarget = join14(configRoot, agentsFileName);
4025
+ if (existsSync15(configRootTarget) || isSymlink(configRootTarget)) {
3286
4026
  removePath(configRootTarget);
3287
4027
  }
3288
4028
  try {
@@ -3297,15 +4037,15 @@ function installConfigFile(pluginDir, configFile, tool, level, configRoot, insta
3297
4037
  async function installExternalRepos(repos, repoPath, configRoot, installPath, level) {
3298
4038
  if (!repos || repos.length === 0) return;
3299
4039
  for (const repo of repos) {
3300
- const targetDir = join12(repoPath, repo.dir);
3301
- const gitDir = join12(targetDir, ".git");
3302
- if (existsSync13(gitDir)) {
4040
+ const targetDir = join14(repoPath, repo.dir);
4041
+ const gitDir = join14(targetDir, ".git");
4042
+ if (existsSync15(gitDir)) {
3303
4043
  try {
3304
- await execa3("git", ["pull", "--quiet"], { cwd: targetDir, timeout: 12e4 });
4044
+ await execa4("git", ["pull", "--quiet"], { cwd: targetDir, timeout: 12e4 });
3305
4045
  } catch {
3306
4046
  logger.warn(t("external_repo_update_failed").replace("{dir}", repo.dir));
3307
4047
  }
3308
- } else if (!existsSync13(targetDir)) {
4048
+ } else if (!existsSync15(targetDir)) {
3309
4049
  const args = ["clone"];
3310
4050
  if (repo.depth) {
3311
4051
  args.push("--depth", String(repo.depth));
@@ -3315,11 +4055,11 @@ async function installExternalRepos(repos, repoPath, configRoot, installPath, le
3315
4055
  }
3316
4056
  args.push(repo.url, targetDir);
3317
4057
  try {
3318
- await execa3("git", args, { timeout: 12e4 });
4058
+ await execa4("git", args, { timeout: 12e4 });
3319
4059
  } catch {
3320
4060
  logger.warn(t("install_clone_failed").replace("{url}", repo.url).replace("{dir}", repo.dir));
3321
4061
  try {
3322
- rmSync3(targetDir, { recursive: true, force: true });
4062
+ rmSync4(targetDir, { recursive: true, force: true });
3323
4063
  } catch {
3324
4064
  }
3325
4065
  continue;
@@ -3329,9 +4069,9 @@ async function installExternalRepos(repos, repoPath, configRoot, installPath, le
3329
4069
  const resolved = realpathSync(targetDir);
3330
4070
  const shouldConfigRootLink = repo.configRootLink !== false && (level === "global" || repo.configRootLink === true);
3331
4071
  if (shouldConfigRootLink) {
3332
- const configRootLink = join12(configRoot, linkName);
4072
+ const configRootLink = join14(configRoot, linkName);
3333
4073
  try {
3334
- if (existsSync13(configRootLink) || isSymlink(configRootLink)) {
4074
+ if (existsSync15(configRootLink) || isSymlink(configRootLink)) {
3335
4075
  removePath(configRootLink);
3336
4076
  }
3337
4077
  try {
@@ -3346,9 +4086,9 @@ async function installExternalRepos(repos, repoPath, configRoot, installPath, le
3346
4086
  }
3347
4087
  }
3348
4088
  if (level === "project") {
3349
- const projectLink = join12(installPath, linkName);
4089
+ const projectLink = join14(installPath, linkName);
3350
4090
  try {
3351
- if (existsSync13(projectLink) || isSymlink(projectLink)) {
4091
+ if (existsSync15(projectLink) || isSymlink(projectLink)) {
3352
4092
  removePath(projectLink);
3353
4093
  }
3354
4094
  try {
@@ -3365,12 +4105,12 @@ async function installExternalRepos(repos, repoPath, configRoot, installPath, le
3365
4105
  }
3366
4106
  }
3367
4107
  function installWorkflows(pluginDir, configRoot) {
3368
- const workflowsDir = join12(pluginDir, "workflows");
3369
- if (!existsSync13(workflowsDir)) return;
3370
- const targetPath = join12(configRoot, "workflows");
4108
+ const workflowsDir = join14(pluginDir, "workflows");
4109
+ if (!existsSync15(workflowsDir)) return;
4110
+ const targetPath = join14(configRoot, "workflows");
3371
4111
  try {
3372
4112
  const resolved = realpathSync(workflowsDir);
3373
- if (existsSync13(targetPath) || isSymlink(targetPath)) {
4113
+ if (existsSync15(targetPath) || isSymlink(targetPath)) {
3374
4114
  removePath(targetPath);
3375
4115
  }
3376
4116
  try {
@@ -3396,36 +4136,36 @@ function writeCannbotManifest(configRoot, pluginId, version, level, tool, skills
3396
4136
  brand_dir: configRoot,
3397
4137
  install_time: (/* @__PURE__ */ new Date()).toISOString()
3398
4138
  };
3399
- const manifestPath = join12(configRoot, `${pluginId}-manifest.json`);
3400
- writeFileSync2(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
4139
+ const manifestPath = join14(configRoot, `${pluginId}-manifest.json`);
4140
+ writeFileSync4(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3401
4141
  return manifest;
3402
4142
  }
3403
4143
  async function installViaManifest(plugin, repoPath, tool, level, installPath) {
3404
4144
  const cwd = installPath || process.cwd();
3405
4145
  const configRoot = getConfigRoot(tool, level, installPath);
3406
- const pluginDir = join12(repoPath, plugin.dir);
4146
+ const pluginDir = join14(repoPath, plugin.dir);
3407
4147
  try {
3408
4148
  const skillsDir = getSkillsRoot(tool, level, cwd);
3409
- const agentsDir = join12(configRoot, "agents");
3410
- mkdirSync5(skillsDir, { recursive: true });
3411
- mkdirSync5(agentsDir, { recursive: true });
4149
+ const agentsDir = join14(configRoot, "agents");
4150
+ mkdirSync6(skillsDir, { recursive: true });
4151
+ mkdirSync6(agentsDir, { recursive: true });
3412
4152
  const installedSkills = [];
3413
4153
  const installedAgents = [];
3414
4154
  for (const source of plugin.installSkills || []) {
3415
- const sourceDir = join12(repoPath, source.dir);
3416
- if (!existsSync13(sourceDir)) {
4155
+ const sourceDir = join14(repoPath, source.dir);
4156
+ if (!existsSync15(sourceDir)) {
3417
4157
  logger.warn(t("install_skill_source_not_found").replace("{dir}", source.dir));
3418
4158
  continue;
3419
4159
  }
3420
4160
  for (const skillEntry of source.skills) {
3421
4161
  const skillName = typeof skillEntry === "string" ? skillEntry : skillEntry.name;
3422
4162
  const skillAlias = typeof skillEntry === "string" ? skillName : skillEntry.as || skillName;
3423
- const skillPath = join12(sourceDir, skillName);
3424
- if (!existsSync13(skillPath)) {
4163
+ const skillPath = join14(sourceDir, skillName);
4164
+ if (!existsSync15(skillPath)) {
3425
4165
  logger.warn(t("install_skill_not_found").replace("{name}", skillName).replace("{dir}", source.dir));
3426
4166
  continue;
3427
4167
  }
3428
- const targetPath = join12(skillsDir, skillAlias);
4168
+ const targetPath = join14(skillsDir, skillAlias);
3429
4169
  const result = installLink(skillPath, targetPath);
3430
4170
  if (result !== "failed") {
3431
4171
  installedSkills.push(skillAlias);
@@ -3437,20 +4177,20 @@ async function installViaManifest(plugin, repoPath, tool, level, installPath) {
3437
4177
  for (const agentName of plugin.installAgents || []) {
3438
4178
  if (tool === "codex") {
3439
4179
  const baseName = agentName.replace(/\.md$/, "");
3440
- const tomlSource = join12(pluginDir, "agents", "codex", `${baseName}.toml`);
3441
- if (!existsSync13(tomlSource)) {
4180
+ const tomlSource = join14(pluginDir, "agents", "codex", `${baseName}.toml`);
4181
+ if (!existsSync15(tomlSource)) {
3442
4182
  logger.warn(t("install_agent_codex_toml_missing").replace("{name}", baseName).replace("{path}", tomlSource));
3443
4183
  continue;
3444
4184
  }
3445
- const canonicalMd = join12(pluginDir, "agents", `${baseName}.md`);
3446
- const canonicalPath = existsSync13(canonicalMd) ? realpathSync(canonicalMd) : canonicalMd;
3447
- const targetPath2 = join12(agentsDir, `${baseName}.toml`);
4185
+ const canonicalMd = join14(pluginDir, "agents", `${baseName}.md`);
4186
+ const canonicalPath = existsSync15(canonicalMd) ? realpathSync(canonicalMd) : canonicalMd;
4187
+ const targetPath2 = join14(agentsDir, `${baseName}.toml`);
3448
4188
  try {
3449
- const content = readFileSync8(tomlSource, "utf-8").split("__CANNBOT_AGENT_SOURCE__").join(canonicalPath);
3450
- if (existsSync13(targetPath2) || isSymlink(targetPath2)) {
4189
+ const content = readFileSync10(tomlSource, "utf-8").split("__CANNBOT_AGENT_SOURCE__").join(canonicalPath);
4190
+ if (existsSync15(targetPath2) || isSymlink(targetPath2)) {
3451
4191
  removePath(targetPath2);
3452
4192
  }
3453
- writeFileSync2(targetPath2, content, "utf-8");
4193
+ writeFileSync4(targetPath2, content, "utf-8");
3454
4194
  installedAgents.push(`${baseName}.toml`);
3455
4195
  } catch {
3456
4196
  logger.warn(t("install_agent_failed").replace("{name}", `${baseName}.toml`));
@@ -3458,12 +4198,12 @@ async function installViaManifest(plugin, repoPath, tool, level, installPath) {
3458
4198
  continue;
3459
4199
  }
3460
4200
  const agentFile = agentName.endsWith(".md") ? agentName : `${agentName}.md`;
3461
- const agentPath = join12(pluginDir, "agents", agentFile);
3462
- if (!existsSync13(agentPath)) {
4201
+ const agentPath = join14(pluginDir, "agents", agentFile);
4202
+ if (!existsSync15(agentPath)) {
3463
4203
  logger.warn(t("install_agent_not_found").replace("{name}", agentFile));
3464
4204
  continue;
3465
4205
  }
3466
- const targetPath = join12(agentsDir, agentFile);
4206
+ const targetPath = join14(agentsDir, agentFile);
3467
4207
  const result = installLink(agentPath, targetPath);
3468
4208
  if (result !== "failed") {
3469
4209
  installedAgents.push(agentFile);
@@ -3483,6 +4223,9 @@ async function installViaManifest(plugin, repoPath, tool, level, installPath) {
3483
4223
  installedSkills,
3484
4224
  installedAgents
3485
4225
  );
4226
+ if (shouldInitializeIssueHandlerConfig(level, installedSkills)) {
4227
+ instantiateIssueHandlerConfig(repoPath, cwd);
4228
+ }
3486
4229
  return {
3487
4230
  success: true,
3488
4231
  skillsCount: installedSkills.length,
@@ -3509,7 +4252,7 @@ function findShell() {
3509
4252
  for (const cmd of candidates) {
3510
4253
  try {
3511
4254
  const detectCmd = process.platform === "win32" ? "where" : "which";
3512
- execaSync(detectCmd, [cmd], { timeout: 3e3 });
4255
+ execaSync2(detectCmd, [cmd], { timeout: 3e3 });
3513
4256
  return cmd;
3514
4257
  } catch {
3515
4258
  }
@@ -3518,7 +4261,7 @@ function findShell() {
3518
4261
  }
3519
4262
  function scriptSupportsTool(scriptPath, tool) {
3520
4263
  try {
3521
- const scriptContent = readFileSync9(scriptPath, "utf-8");
4264
+ const scriptContent = readFileSync11(scriptPath, "utf-8");
3522
4265
  return new RegExp(`\\b${tool}\\b`).test(scriptContent);
3523
4266
  } catch {
3524
4267
  return true;
@@ -3536,12 +4279,12 @@ async function installPlugin(opts) {
3536
4279
  warnings: []
3537
4280
  };
3538
4281
  }
3539
- const pluginDir = join13(opts.repoPath, plugin.dir);
4282
+ const pluginDir = join15(opts.repoPath, plugin.dir);
3540
4283
  const cwd = opts.installPath || process.cwd();
3541
4284
  const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
3542
- const agentsFile = join13(configRoot, getAgentsFileName(opts.tool));
4285
+ const agentsFile = join15(configRoot, getAgentsFileName(opts.tool));
3543
4286
  let backupInfo = null;
3544
- if (existsSync14(agentsFile)) {
4287
+ if (existsSync16(agentsFile)) {
3545
4288
  const currentPlugin = detectCurrentPlugin(configRoot, opts.tool, opts.installPath);
3546
4289
  if (currentPlugin && currentPlugin.pluginId !== opts.pluginId) {
3547
4290
  let choice = "overwrite";
@@ -3636,8 +4379,8 @@ async function installPlugin(opts) {
3636
4379
  };
3637
4380
  }
3638
4381
  }
3639
- const scriptPath = join13(pluginDir, plugin.script);
3640
- if (!existsSync14(scriptPath)) {
4382
+ const scriptPath = join15(pluginDir, plugin.script);
4383
+ if (!existsSync16(scriptPath)) {
3641
4384
  return {
3642
4385
  success: false,
3643
4386
  pluginId: opts.pluginId,
@@ -3679,7 +4422,7 @@ async function installPlugin(opts) {
3679
4422
  stdio: "pipe",
3680
4423
  input: opts.yes ? "y\n" : void 0
3681
4424
  };
3682
- await execa4(shell, [scriptPath, ...args], execOptions);
4425
+ await execa5(shell, [scriptPath, ...args], execOptions);
3683
4426
  const manifest = readManifest(configRoot);
3684
4427
  let skillsCount = 0;
3685
4428
  let agentsCount = 0;
@@ -3735,6 +4478,210 @@ async function installPlugin(opts) {
3735
4478
  }
3736
4479
  }
3737
4480
 
4481
+ // src/core/cannbot-delegate.ts
4482
+ init_config();
4483
+ init_paths();
4484
+ init_fs_helpers();
4485
+ init_i18n();
4486
+ var DELEGATE_TOOLS = ["opencode", "claude", "trae", "codex"];
4487
+ function boundaryErrors(tool, level) {
4488
+ const errors = [];
4489
+ if (!DELEGATE_TOOLS.includes(tool)) {
4490
+ errors.push(
4491
+ t("cannbot_tool_unsupported").replace("{tool}", tool).replace("{tools}", DELEGATE_TOOLS.join(", "))
4492
+ );
4493
+ }
4494
+ if (level !== "project") {
4495
+ errors.push(t("cannbot_level_unsupported"));
4496
+ }
4497
+ return errors;
4498
+ }
4499
+ function migrateSymlinkedInstructions(target, tool) {
4500
+ const warnings = [];
4501
+ const fileName = getConfigFileName(tool);
4502
+ const candidates = [join16(target, fileName), join16(target, "AGENTS.md"), join16(target, "CLAUDE.md")];
4503
+ for (const configPath of candidates) {
4504
+ if (!isSymlink(configPath)) continue;
4505
+ try {
4506
+ const content = readFileSync12(configPath, "utf-8");
4507
+ const backupPath = `${configPath}.cannbot-migration.${Date.now()}`;
4508
+ copyFileSync3(configPath, backupPath);
4509
+ unlinkSync6(configPath);
4510
+ writeFileSync5(configPath, content);
4511
+ warnings.push(t("cannbot_migrated_symlink").replace("{file}", fileName));
4512
+ } catch {
4513
+ warnings.push(t("cannbot_migrate_symlink_failed").replace("{file}", fileName));
4514
+ }
4515
+ break;
4516
+ }
4517
+ return warnings;
4518
+ }
4519
+ function detectDualSkillCopies(tool, target, skillNames) {
4520
+ const duplicates = [];
4521
+ if (tool !== "opencode") return duplicates;
4522
+ const legacyRoot = getSkillsRoot("opencode", "project", target);
4523
+ if (!existsSync17(legacyRoot)) return duplicates;
4524
+ let legacyEntries = [];
4525
+ try {
4526
+ legacyEntries = readdirSync8(legacyRoot);
4527
+ } catch {
4528
+ return duplicates;
4529
+ }
4530
+ for (const name of skillNames) {
4531
+ if (legacyEntries.includes(name)) {
4532
+ duplicates.push(name);
4533
+ }
4534
+ }
4535
+ return duplicates;
4536
+ }
4537
+ function skillNameFromPath(rel) {
4538
+ const parts = rel.split("/");
4539
+ return parts[parts.length - 1] || rel;
4540
+ }
4541
+ async function runDelegate(args, target) {
4542
+ try {
4543
+ const result = await execa6("node", [getCannbotBinPath(), ...args], {
4544
+ cwd: target,
4545
+ timeout: 6e5
4546
+ });
4547
+ process.stdout.write(result.stdout || "");
4548
+ process.stderr.write(result.stderr || "");
4549
+ return { ok: true, stdout: result.stdout || "", stderr: result.stderr || "", exitCode: 0 };
4550
+ } catch (error) {
4551
+ const err = error;
4552
+ process.stdout.write(err.stdout || "");
4553
+ process.stderr.write(err.stderr || "");
4554
+ if (!err.stdout && !err.stderr && err.message) {
4555
+ process.stderr.write(`${err.message}
4556
+ `);
4557
+ }
4558
+ return {
4559
+ ok: false,
4560
+ stdout: err.stdout || "",
4561
+ stderr: err.stderr || "",
4562
+ exitCode: err.exitCode
4563
+ };
4564
+ }
4565
+ }
4566
+ async function delegateInstall(options) {
4567
+ const { pluginId, tool, level } = options;
4568
+ const target = resolve2(options.target || process.cwd());
4569
+ const displayName = options.displayName || pluginId;
4570
+ const result = {
4571
+ success: false,
4572
+ pluginId,
4573
+ skillsCount: 0,
4574
+ agentsCount: 0,
4575
+ errors: [],
4576
+ warnings: []
4577
+ };
4578
+ result.errors.push(...boundaryErrors(tool, level));
4579
+ if (result.errors.length > 0) {
4580
+ return result;
4581
+ }
4582
+ if (!hasNodeRuntime()) {
4583
+ result.errors.push(t("cannbot_node_required"));
4584
+ return result;
4585
+ }
4586
+ try {
4587
+ await ensureCannbotInstaller();
4588
+ } catch (error) {
4589
+ result.errors.push(
4590
+ t("cannbot_installer_failed").replace(
4591
+ "{error}",
4592
+ error instanceof Error ? error.message : t("error_unknown")
4593
+ )
4594
+ );
4595
+ return result;
4596
+ }
4597
+ if (!discoverCannbotPlugins().some((p) => p.id === pluginId)) {
4598
+ try {
4599
+ await ensureCannbotInstaller({ force: true });
4600
+ } catch {
4601
+ }
4602
+ if (!discoverCannbotPlugins().some((p) => p.id === pluginId)) {
4603
+ result.errors.push(t("cannbot_plugin_not_found").replace("{plugin}", pluginId));
4604
+ return result;
4605
+ }
4606
+ }
4607
+ result.warnings.push(...migrateSymlinkedInstructions(target, tool));
4608
+ let run = await runDelegate(["install", pluginId, "--tool", tool, "--target", target], target);
4609
+ const hitRenameGap = !run.ok && (run.stdout.includes(`plugin not found: ${pluginId}`) || run.stderr.includes(`plugin not found: ${pluginId}`));
4610
+ if (hitRenameGap) {
4611
+ result.warnings.push(t("cannbot_bundled_fallback"));
4612
+ run = await runDelegate(
4613
+ ["install", pluginId, "--tool", tool, "--target", target, "--bundled"],
4614
+ target
4615
+ );
4616
+ }
4617
+ if (!run.ok) {
4618
+ result.errors.push(
4619
+ t("cannbot_delegate_failed").replace(
4620
+ "{code}",
4621
+ run.exitCode !== void 0 ? String(run.exitCode) : "?"
4622
+ )
4623
+ );
4624
+ return result;
4625
+ }
4626
+ const registryRead = readCannbotRegistry(target, tool);
4627
+ const record = registryRead?.registry.plugins.find((r) => r?.plugin === pluginId);
4628
+ if (!record) {
4629
+ result.errors.push(t("cannbot_registry_missing"));
4630
+ return result;
4631
+ }
4632
+ result.skillsCount = Array.isArray(record.skills) ? record.skills.length : 0;
4633
+ result.agentsCount = Array.isArray(record.agents) ? record.agents.length : 0;
4634
+ const files = [];
4635
+ const directories = [];
4636
+ for (const rel of [...record.skills || [], ...record.agents || []]) {
4637
+ files.push(join16(target, rel));
4638
+ }
4639
+ if (record.assets) {
4640
+ directories.push(join16(target, record.assets));
4641
+ }
4642
+ if (record.permissions) {
4643
+ directories.push(join16(target, record.permissions));
4644
+ }
4645
+ const registryPath = findCannbotRegistryPath(target, tool);
4646
+ writeRecord({
4647
+ pluginId,
4648
+ displayName,
4649
+ tool,
4650
+ level: "project",
4651
+ installPath: target,
4652
+ configRoot: registryPath ? join16(registryPath, "..") : target,
4653
+ installTime: (/* @__PURE__ */ new Date()).toISOString(),
4654
+ files,
4655
+ directories,
4656
+ kind: "delegated"
4657
+ });
4658
+ addInstalledPlugin(pluginId);
4659
+ const duplicates = detectDualSkillCopies(
4660
+ tool,
4661
+ target,
4662
+ (record.skills || []).map(skillNameFromPath)
4663
+ );
4664
+ if (duplicates.length > 0) {
4665
+ result.warnings.push(t("cannbot_dual_skill_copy").replace("{skills}", duplicates.join(", ")));
4666
+ }
4667
+ result.success = true;
4668
+ return result;
4669
+ }
4670
+ async function installPluginRouted(options) {
4671
+ const isCannbot = options.source === "cannbot" || options.plugin?.source === "cannbot";
4672
+ if (isCannbot) {
4673
+ return delegateInstall({
4674
+ pluginId: options.pluginId,
4675
+ displayName: options.plugin?.displayName,
4676
+ tool: options.tool,
4677
+ level: options.level,
4678
+ target: options.installPath || process.cwd(),
4679
+ yes: options.yes
4680
+ });
4681
+ }
4682
+ return installPlugin(options);
4683
+ }
4684
+
3738
4685
  // src/commands/init.ts
3739
4686
  init_registry();
3740
4687
 
@@ -3744,8 +4691,8 @@ init_fs_helpers();
3744
4691
  init_constants();
3745
4692
  init_logger();
3746
4693
  init_i18n();
3747
- import { existsSync as existsSync15, mkdirSync as mkdirSync6, symlinkSync as symlinkSync2, readdirSync as readdirSync6, rmSync as rmSync4, realpathSync as realpathSync2, readlinkSync as readlinkSync2, cpSync as cpSync2 } from "fs";
3748
- import { join as join14, dirname as dirname5, basename as basename3, isAbsolute } from "path";
4694
+ import { existsSync as existsSync18, mkdirSync as mkdirSync7, symlinkSync as symlinkSync2, readdirSync as readdirSync9, rmSync as rmSync5, realpathSync as realpathSync2, readlinkSync as readlinkSync2, cpSync as cpSync2 } from "fs";
4695
+ import { join as join17, dirname as dirname7, basename as basename3, isAbsolute } from "path";
3749
4696
  import { select as select3, checkbox, Separator as Separator2 } from "@inquirer/prompts";
3750
4697
  import chalk5 from "chalk";
3751
4698
  import Table from "cli-table3";
@@ -3755,8 +4702,8 @@ async function installSkills(skillIds, tool, level, repoPath) {
3755
4702
  const configRoot = getConfigRoot(tool, level);
3756
4703
  const skillsDir = getSkillsRoot(tool, level);
3757
4704
  const installPath = level === "project" ? process.cwd() : configRoot;
3758
- if (!existsSync15(skillsDir)) {
3759
- mkdirSync6(skillsDir, { recursive: true });
4705
+ if (!existsSync18(skillsDir)) {
4706
+ mkdirSync7(skillsDir, { recursive: true });
3760
4707
  }
3761
4708
  const results = [];
3762
4709
  const installedIds = [];
@@ -3767,8 +4714,8 @@ async function installSkills(skillIds, tool, level, repoPath) {
3767
4714
  continue;
3768
4715
  }
3769
4716
  const sourcePath = resolveSkillSourcePath(skill, repoPath);
3770
- const targetPath = join14(skillsDir, skillId);
3771
- if (!existsSync15(sourcePath)) {
4717
+ const targetPath = join17(skillsDir, skillId);
4718
+ if (!existsSync18(sourcePath)) {
3772
4719
  results.push({ skillId, success: false, error: t("error_source_not_found").replace("{path}", sourcePath) });
3773
4720
  continue;
3774
4721
  }
@@ -3790,7 +4737,7 @@ async function installSkills(skillIds, tool, level, repoPath) {
3790
4737
  continue;
3791
4738
  }
3792
4739
  }
3793
- if (existsSync15(targetPath) || isSymlink(targetPath)) {
4740
+ if (existsSync18(targetPath) || isSymlink(targetPath)) {
3794
4741
  removePath(targetPath);
3795
4742
  }
3796
4743
  try {
@@ -3814,6 +4761,9 @@ async function installSkills(skillIds, tool, level, repoPath) {
3814
4761
  }
3815
4762
  if (installedIds.length > 0) {
3816
4763
  addSkillsToRecord(installedIds, tool, level, installPath);
4764
+ if (shouldInitializeIssueHandlerConfig(level, installedIds)) {
4765
+ instantiateIssueHandlerConfig(repoPath, installPath);
4766
+ }
3817
4767
  }
3818
4768
  return results;
3819
4769
  }
@@ -3824,9 +4774,9 @@ async function uninstallSkills(skillIds, tool, level) {
3824
4774
  const results = [];
3825
4775
  const removedIds = [];
3826
4776
  for (const skillId of skillIds) {
3827
- const targetPath = join14(skillsDir, skillId);
4777
+ const targetPath = join17(skillsDir, skillId);
3828
4778
  try {
3829
- if (existsSync15(targetPath) || isSymlink(targetPath)) {
4779
+ if (existsSync18(targetPath) || isSymlink(targetPath)) {
3830
4780
  removePath(targetPath);
3831
4781
  results.push({ skillId, success: true });
3832
4782
  removedIds.push(skillId);
@@ -3842,17 +4792,17 @@ async function uninstallSkills(skillIds, tool, level) {
3842
4792
  if (removedIds.length > 0) {
3843
4793
  removeSkillsFromRecord(removedIds, tool, level, installPath);
3844
4794
  }
3845
- if (existsSync15(skillsDir)) {
4795
+ if (existsSync18(skillsDir)) {
3846
4796
  try {
3847
- const entries = readdirSync6(skillsDir);
4797
+ const entries = readdirSync9(skillsDir);
3848
4798
  if (entries.length === 0) {
3849
- rmSync4(skillsDir, { recursive: true });
4799
+ rmSync5(skillsDir, { recursive: true });
3850
4800
  logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
3851
4801
  if (tool === "codex") {
3852
- const agentsRoot = dirname5(skillsDir);
4802
+ const agentsRoot = dirname7(skillsDir);
3853
4803
  try {
3854
- if (existsSync15(agentsRoot) && readdirSync6(agentsRoot).length === 0) {
3855
- rmSync4(agentsRoot, { recursive: true });
4804
+ if (existsSync18(agentsRoot) && readdirSync9(agentsRoot).length === 0) {
4805
+ rmSync5(agentsRoot, { recursive: true });
3856
4806
  logger.step(` ${t("uninstall_clean_empty_dir")}: ${basename3(agentsRoot)}/`);
3857
4807
  }
3858
4808
  } catch {
@@ -4261,12 +5211,12 @@ function listAllSkills() {
4261
5211
  }
4262
5212
  function resolveSkillSourcePath(skill, repoPath) {
4263
5213
  if (skill.filePath) {
4264
- const absPath = isAbsolute(skill.filePath) ? skill.filePath : join14(repoPath, skill.filePath);
4265
- if (existsSync15(absPath)) {
4266
- return dirname5(absPath);
5214
+ const absPath = isAbsolute(skill.filePath) ? skill.filePath : join17(repoPath, skill.filePath);
5215
+ if (existsSync18(absPath)) {
5216
+ return dirname7(absPath);
4267
5217
  }
4268
5218
  }
4269
- return join14(repoPath, skill.source, skill.id);
5219
+ return join17(repoPath, skill.source, skill.id);
4270
5220
  }
4271
5221
 
4272
5222
  // src/ui/display.ts
@@ -4290,9 +5240,10 @@ function printPluginList(plugins, installed) {
4290
5240
  plugins.forEach((plugin, index) => {
4291
5241
  const isInstalled = installed.has(plugin.id);
4292
5242
  const status = isInstalled ? chalk6.green(`\u2713 ${t("status_installed")}`) : chalk6.dim(`\u2014`);
5243
+ const badge = plugin.source === "cannbot" ? chalk6.magenta(" [cannbot]") + (plugin.version ? chalk6.dim(` v${plugin.version}`) : "") : "";
4293
5244
  table.push([
4294
5245
  String(index + 1),
4295
- plugin.displayName,
5246
+ plugin.displayName + badge,
4296
5247
  status,
4297
5248
  plugin.description || chalk6.dim("\u2014")
4298
5249
  ]);
@@ -4367,13 +5318,13 @@ init_config();
4367
5318
  init_paths();
4368
5319
  init_theme();
4369
5320
  init_constants();
4370
- import { existsSync as existsSync16, readFileSync as readFileSync10 } from "fs";
4371
- import { join as join15 } from "path";
5321
+ import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
5322
+ import { join as join18 } from "path";
4372
5323
  async function initCommand() {
4373
5324
  const cwd = process.cwd();
4374
- if (existsSync16(join15(cwd, "package.json")) && (() => {
5325
+ if (existsSync19(join18(cwd, "package.json")) && (() => {
4375
5326
  try {
4376
- const pkg = JSON.parse(readFileSync10(join15(cwd, "package.json"), "utf-8"));
5327
+ const pkg = JSON.parse(readFileSync13(join18(cwd, "package.json"), "utf-8"));
4377
5328
  return pkg.name === "@cannbot-ai/install-helper";
4378
5329
  } catch {
4379
5330
  return false;
@@ -4394,6 +5345,7 @@ async function initCommand() {
4394
5345
  } catch {
4395
5346
  spinner.warn(t("loading_skills_list_failed"));
4396
5347
  }
5348
+ await ensureCannbotDiscovery();
4397
5349
  let step = 0;
4398
5350
  let mode = "plugin";
4399
5351
  while (true) {
@@ -4480,11 +5432,12 @@ async function pluginInstallFlow() {
4480
5432
  const progress = `[${i + 1}/${total}]`;
4481
5433
  const pluginSpinner = createSpinner(`${progress} ${t("install_progress")} ${displayName}...`);
4482
5434
  pluginSpinner.start();
4483
- const result = await installPlugin({
5435
+ const result = await installPluginRouted({
4484
5436
  pluginId,
4485
5437
  tool: answers.tool,
4486
5438
  level: answers.level,
4487
- repoPath
5439
+ repoPath,
5440
+ plugin
4488
5441
  });
4489
5442
  if (result.success) {
4490
5443
  pluginSpinner.succeed(
@@ -4496,6 +5449,9 @@ async function pluginInstallFlow() {
4496
5449
  `${progress} ${displayName} \u2014 ${result.errors.join(", ")}`
4497
5450
  );
4498
5451
  }
5452
+ for (const warning of result.warnings || []) {
5453
+ logger.warn(warning);
5454
+ }
4499
5455
  results.push(result);
4500
5456
  }
4501
5457
  const summary = results.map((result, index) => ({
@@ -4603,9 +5559,24 @@ async function listCommand() {
4603
5559
  await repoManager.ensureRepoAndScan();
4604
5560
  } catch {
4605
5561
  }
5562
+ await ensureCannbotDiscovery();
4606
5563
  const plugins = getAllPlugins();
4607
5564
  const installed = scanInstalled();
4608
5565
  const installedMap = new Map(installed.map((p) => [p.id, p]));
5566
+ for (const entry of scanCannbotInstalled()) {
5567
+ if (!installedMap.has(entry.id)) {
5568
+ installedMap.set(entry.id, {
5569
+ id: entry.id,
5570
+ displayName: entry.id,
5571
+ tool: entry.tool,
5572
+ level: "project",
5573
+ skillsCount: entry.skillsCount,
5574
+ agentsCount: entry.agentsCount,
5575
+ installTime: "",
5576
+ configRoot: entry.registryPath
5577
+ });
5578
+ }
5579
+ }
4609
5580
  printPluginList(plugins, installedMap);
4610
5581
  }
4611
5582
 
@@ -4613,11 +5584,12 @@ async function listCommand() {
4613
5584
  init_detector();
4614
5585
  init_registry();
4615
5586
  init_manifest();
5587
+ import chalk8 from "chalk";
5588
+ import { existsSync as existsSync20, readdirSync as readdirSync10, lstatSync as lstatSync3, unlinkSync as unlinkSync7, mkdirSync as mkdirSync8, readFileSync as readFileSync14 } from "fs";
5589
+ import { join as join19 } from "path";
5590
+ import { homedir as homedir3 } from "os";
4616
5591
  init_paths();
4617
5592
  init_i18n();
4618
- import chalk8 from "chalk";
4619
- import { existsSync as existsSync17, readdirSync as readdirSync7, lstatSync as lstatSync2, unlinkSync as unlinkSync5, mkdirSync as mkdirSync7 } from "fs";
4620
- import { join as join16 } from "path";
4621
5593
  async function doctorCommand(options = {}) {
4622
5594
  console.log();
4623
5595
  console.log(chalk8.bold(` ${t("doctor_title")}`));
@@ -4672,10 +5644,10 @@ async function doctorCommand(options = {}) {
4672
5644
  for (const { configRoot, tool, level } of configRootsToCheck) {
4673
5645
  const skillsDir = getSkillsRoot(tool, level);
4674
5646
  const agentsDir = getAgentsDir(configRoot);
4675
- if (existsSync17(skillsDir)) {
5647
+ if (existsSync20(skillsDir)) {
4676
5648
  const brokenLinks = checkBrokenLinks(skillsDir);
4677
5649
  if (brokenLinks === 0) {
4678
- const count = readdirSync7(skillsDir).length;
5650
+ const count = readdirSync10(skillsDir).length;
4679
5651
  console.log(chalk8.green(" \u2713") + ` ${skillsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
4680
5652
  } else {
4681
5653
  console.log(chalk8.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
@@ -4689,15 +5661,15 @@ async function doctorCommand(options = {}) {
4689
5661
  } else {
4690
5662
  console.log(chalk8.dim(" \u2014") + ` ${skillsDir} \u2014 ${t("doctor_not_exist")}`);
4691
5663
  if (options.fix) {
4692
- mkdirSync7(skillsDir, { recursive: true });
5664
+ mkdirSync8(skillsDir, { recursive: true });
4693
5665
  fixes++;
4694
5666
  console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
4695
5667
  }
4696
5668
  }
4697
- if (existsSync17(agentsDir)) {
5669
+ if (existsSync20(agentsDir)) {
4698
5670
  const brokenLinks = checkBrokenLinks(agentsDir);
4699
5671
  if (brokenLinks === 0) {
4700
- const count = readdirSync7(agentsDir).length;
5672
+ const count = readdirSync10(agentsDir).length;
4701
5673
  console.log(chalk8.green(" \u2713") + ` ${agentsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
4702
5674
  } else {
4703
5675
  console.log(chalk8.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
@@ -4711,7 +5683,7 @@ async function doctorCommand(options = {}) {
4711
5683
  } else {
4712
5684
  console.log(chalk8.dim(" \u2014") + ` ${agentsDir} \u2014 ${t("doctor_not_exist")}`);
4713
5685
  if (options.fix) {
4714
- mkdirSync7(agentsDir, { recursive: true });
5686
+ mkdirSync8(agentsDir, { recursive: true });
4715
5687
  fixes++;
4716
5688
  console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", agentsDir)}`);
4717
5689
  }
@@ -4721,13 +5693,16 @@ async function doctorCommand(options = {}) {
4721
5693
  console.log(chalk8.bold(` ${t("doctor_config")}`));
4722
5694
  for (const { configRoot, tool, level } of configRootsToCheck) {
4723
5695
  const configFile = getConfigFileName(tool);
4724
- const configPath = level === "project" ? join16(configRoot, "..", configFile) : join16(configRoot, configFile);
4725
- if (existsSync17(configPath)) {
5696
+ const configPath = level === "project" ? join19(configRoot, "..", configFile) : join19(configRoot, configFile);
5697
+ if (existsSync20(configPath)) {
4726
5698
  console.log(chalk8.green(" \u2713") + ` ${configPath} ${t("doctor_config_exists")}`);
4727
5699
  } else {
4728
5700
  console.log(chalk8.dim(" \u2014") + ` ${configPath} ${t("doctor_config_not_exist")}`);
4729
5701
  }
4730
5702
  }
5703
+ const cannbotResult = doctorCannbotSection(options);
5704
+ warnings += cannbotResult.warnings;
5705
+ fixes += cannbotResult.fixes;
4731
5706
  console.log();
4732
5707
  console.log(chalk8.dim(" " + "\u2500".repeat(46)));
4733
5708
  if (options.fix && fixes > 0) {
@@ -4741,16 +5716,107 @@ async function doctorCommand(options = {}) {
4741
5716
  }
4742
5717
  console.log();
4743
5718
  }
5719
+ function doctorCannbotSection(options) {
5720
+ let warnings = 0;
5721
+ let fixes = 0;
5722
+ console.log();
5723
+ console.log(chalk8.bold(` ${t("doctor_cannbot")}`));
5724
+ if (hasNodeRuntime()) {
5725
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_cannbot_node_ok")}`);
5726
+ } else {
5727
+ console.log(chalk8.yellow(" \u26A0") + ` ${t("doctor_cannbot_node_missing")}`);
5728
+ warnings++;
5729
+ }
5730
+ const installerConfig = loadCannbotInstallerConfig();
5731
+ if (isCannbotInstallerReady()) {
5732
+ const version = getCannbotInstallerVersion() || "?";
5733
+ console.log(
5734
+ chalk8.green(" \u2713") + ` ${installerConfig.package} v${version} ${chalk8.dim(`(${installerConfig.channel})`)}`
5735
+ );
5736
+ } else {
5737
+ console.log(
5738
+ chalk8.dim(" \u2014") + ` ${installerConfig.package} \u2014 ${t("doctor_not_installed")}`
5739
+ );
5740
+ }
5741
+ const cannbotInstalled = scanCannbotInstalled();
5742
+ if (cannbotInstalled.length === 0) {
5743
+ console.log(chalk8.dim(" \u2014") + ` cannbot-plugin.json \u2014 ${t("doctor_not_exist")}`);
5744
+ } else {
5745
+ for (const entry of cannbotInstalled) {
5746
+ console.log(
5747
+ chalk8.green(" \u2713") + ` ${entry.id} ${chalk8.dim(`(${entry.tool}, ${entry.skillsCount} skills, ${entry.agentsCount} agents)`)}`
5748
+ );
5749
+ const missing = (entry.record.skills || []).filter((rel) => {
5750
+ const p = join19(entry.target, rel);
5751
+ return !existsSync20(p) && !lstatTryIsSymlink(p);
5752
+ });
5753
+ if (missing.length > 0) {
5754
+ console.log(
5755
+ chalk8.yellow(" \u26A0") + ` ${entry.id}: ${t("doctor_cannbot_missing_skills").replace("{count}", String(missing.length))}`
5756
+ );
5757
+ warnings++;
5758
+ }
5759
+ }
5760
+ }
5761
+ for (const fileName of ["AGENTS.md", "CLAUDE.md"]) {
5762
+ const filePath = join19(process.cwd(), fileName);
5763
+ if (!existsSync20(filePath)) continue;
5764
+ try {
5765
+ const content = readFileSync14(filePath, "utf-8");
5766
+ for (const entry of cannbotInstalled) {
5767
+ const blocks = countMarkerBlocks(content, entry.id);
5768
+ if (blocks > 1) {
5769
+ console.log(
5770
+ chalk8.yellow(" \u26A0") + ` ${fileName}: ${t("doctor_cannbot_marker_duplicate").replace("{plugin}", entry.id)}`
5771
+ );
5772
+ warnings++;
5773
+ }
5774
+ }
5775
+ const starts = (content.match(/<!-- cannbot:[\w.-]+:start -->/g) || []).length;
5776
+ const ends = (content.match(/<!-- cannbot:[\w.-]+:end -->/g) || []).length;
5777
+ if (starts !== ends) {
5778
+ console.log(
5779
+ chalk8.yellow(" \u26A0") + ` ${fileName}: ${t("doctor_cannbot_marker_unpaired").replace("{start}", String(starts)).replace("{end}", String(ends))}`
5780
+ );
5781
+ warnings++;
5782
+ }
5783
+ } catch {
5784
+ }
5785
+ }
5786
+ const orphans = findOrphanedDelegatedRecords();
5787
+ for (const pluginId of orphans) {
5788
+ console.log(
5789
+ chalk8.yellow(" \u26A0") + ` ${t("doctor_cannbot_orphan_record").replace("{plugin}", pluginId)}`
5790
+ );
5791
+ warnings++;
5792
+ if (options.fix) {
5793
+ try {
5794
+ unlinkSync7(join19(homedir3(), ".cannbot", "installs", `${pluginId}.json`));
5795
+ fixes++;
5796
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fixed").replace("{count}", "1")}: ${pluginId}`);
5797
+ } catch {
5798
+ }
5799
+ }
5800
+ }
5801
+ return { warnings, fixes };
5802
+ }
5803
+ function lstatTryIsSymlink(path) {
5804
+ try {
5805
+ return lstatSync3(path).isSymbolicLink();
5806
+ } catch {
5807
+ return false;
5808
+ }
5809
+ }
4744
5810
  function checkBrokenLinks(dir) {
4745
5811
  let broken = 0;
4746
5812
  try {
4747
- const entries = readdirSync7(dir);
5813
+ const entries = readdirSync10(dir);
4748
5814
  for (const entry of entries) {
4749
- const fullPath = join16(dir, entry);
5815
+ const fullPath = join19(dir, entry);
4750
5816
  try {
4751
- const stats = lstatSync2(fullPath);
5817
+ const stats = lstatSync3(fullPath);
4752
5818
  if (stats.isSymbolicLink()) {
4753
- if (!existsSync17(fullPath)) {
5819
+ if (!existsSync20(fullPath)) {
4754
5820
  broken++;
4755
5821
  }
4756
5822
  }
@@ -4765,14 +5831,14 @@ function checkBrokenLinks(dir) {
4765
5831
  function fixBrokenLinks(dir) {
4766
5832
  let fixed = 0;
4767
5833
  try {
4768
- const entries = readdirSync7(dir);
5834
+ const entries = readdirSync10(dir);
4769
5835
  for (const entry of entries) {
4770
- const fullPath = join16(dir, entry);
5836
+ const fullPath = join19(dir, entry);
4771
5837
  try {
4772
- const stats = lstatSync2(fullPath);
5838
+ const stats = lstatSync3(fullPath);
4773
5839
  if (stats.isSymbolicLink()) {
4774
- if (!existsSync17(fullPath)) {
4775
- unlinkSync5(fullPath);
5840
+ if (!existsSync20(fullPath)) {
5841
+ unlinkSync7(fullPath);
4776
5842
  fixed++;
4777
5843
  }
4778
5844
  }
@@ -4786,15 +5852,15 @@ function fixBrokenLinks(dir) {
4786
5852
 
4787
5853
  // src/commands/status.ts
4788
5854
  init_manifest();
4789
- init_i18n();
4790
5855
  import chalk9 from "chalk";
5856
+ init_i18n();
4791
5857
  async function statusCommand() {
4792
5858
  const installed = scanInstalled();
4793
- const installedMap = new Map(installed.map((p) => [p.id, p]));
5859
+ const cannbotInstalled = scanCannbotInstalled();
4794
5860
  console.log();
4795
5861
  console.log(chalk9.bold(` ${t("status_title")}`));
4796
5862
  console.log();
4797
- if (installed.length === 0) {
5863
+ if (installed.length === 0 && cannbotInstalled.length === 0) {
4798
5864
  console.log(chalk9.dim(` ${t("status_none")}`));
4799
5865
  console.log(chalk9.dim(` ${t("status_hint")}`));
4800
5866
  console.log();
@@ -4811,6 +5877,17 @@ async function statusCommand() {
4811
5877
  console.log(chalk9.dim(` ${t("status_install_time")}: ${inst.installTime}`));
4812
5878
  console.log();
4813
5879
  }
5880
+ for (const entry of cannbotInstalled) {
5881
+ const versionSuffix = entry.version ? chalk9.dim(` v${entry.version}`) : "";
5882
+ console.log(
5883
+ chalk9.green(" \u2713") + ` ${entry.id}${versionSuffix}` + chalk9.magenta(" [cannbot]") + chalk9.dim(` (${entry.tool}, project)`)
5884
+ );
5885
+ console.log(
5886
+ chalk9.dim(` ${entry.skillsCount} skills, ${entry.agentsCount} agents`)
5887
+ );
5888
+ console.log(chalk9.dim(` ${entry.target}`));
5889
+ console.log();
5890
+ }
4814
5891
  }
4815
5892
 
4816
5893
  // src/commands/install.ts
@@ -4823,7 +5900,15 @@ init_config();
4823
5900
  init_paths();
4824
5901
  import { confirm as confirm2 } from "@inquirer/prompts";
4825
5902
  import chalk10 from "chalk";
5903
+ function validateSource(source) {
5904
+ if (!source) return void 0;
5905
+ if (source !== "cannbot" && source !== "skills") {
5906
+ throw new Error(t("error_invalid_source").replace("{source}", source).replace("{sources}", "cannbot, skills"));
5907
+ }
5908
+ return source;
5909
+ }
4826
5910
  async function installCommand(names, options) {
5911
+ const source = validateSource(options.source);
4827
5912
  if (options.list) {
4828
5913
  try {
4829
5914
  const repoManager = createRepositoryManager();
@@ -4988,10 +6073,11 @@ async function installCommand(names, options) {
4988
6073
  } catch {
4989
6074
  scanSpinner.warn(t("loading_skills_list_failed"));
4990
6075
  }
6076
+ await ensureCannbotDiscovery();
4991
6077
  const plugins = [];
4992
6078
  const skills = [];
4993
6079
  for (const name of names) {
4994
- const plugin = findPlugin(name);
6080
+ const plugin = findPlugin(name, { source });
4995
6081
  if (plugin) {
4996
6082
  plugins.push(plugin.id);
4997
6083
  continue;
@@ -5005,7 +6091,8 @@ async function installCommand(names, options) {
5005
6091
  const allPlugins = getAllPlugins();
5006
6092
  logger.info(t("available_plugins") + ":");
5007
6093
  for (const p of allPlugins) {
5008
- logger.step(` ${p.id} (${p.aliases.join(", ")})`);
6094
+ const badge = p.source === "cannbot" ? chalk10.magenta(" [cannbot]") : "";
6095
+ logger.step(` ${p.id}${badge} (${p.aliases.join(", ")})`);
5009
6096
  }
5010
6097
  logger.info(`${t("available_skills")}: ${chalk10.cyan("install-helper install --list")}`);
5011
6098
  return;
@@ -5029,7 +6116,9 @@ async function installCommand(names, options) {
5029
6116
  const pluginsToInstall = [];
5030
6117
  const skippedPlugins = [];
5031
6118
  for (const pluginId of plugins) {
5032
- if (installedSet.has(pluginId) && !options.yes) {
6119
+ const pluginEntry = findPlugin(pluginId, { source });
6120
+ const alreadyInstalled = pluginEntry?.source === "cannbot" ? scanCannbotInstalled().some((e) => e.id === pluginId && e.tool === tool) : installedSet.has(pluginId);
6121
+ if (alreadyInstalled && !options.yes) {
5033
6122
  const plugin = findPlugin(pluginId);
5034
6123
  const displayName = plugin?.displayName || pluginId;
5035
6124
  logger.warn(`${displayName} [${t("install_already_installed")}]`);
@@ -5066,7 +6155,8 @@ async function installCommand(names, options) {
5066
6155
  level,
5067
6156
  repoPath,
5068
6157
  void 0,
5069
- options.yes
6158
+ options.yes,
6159
+ source
5070
6160
  );
5071
6161
  const summary = results.map((result) => {
5072
6162
  const plugin = allPlugins.find((p) => p.id === result.pluginId);
@@ -5118,7 +6208,7 @@ async function installCommand(names, options) {
5118
6208
  logger.blank();
5119
6209
  }
5120
6210
  }
5121
- async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes) {
6211
+ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes, source) {
5122
6212
  const results = [];
5123
6213
  const total = pluginIds.length;
5124
6214
  for (let i = 0; i < pluginIds.length; i++) {
@@ -5126,13 +6216,16 @@ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes
5126
6216
  const progress = `[${i + 1}/${total}]`;
5127
6217
  const spinner = createSpinner(`${progress} ${t("install_progress")} ${pluginId}...`);
5128
6218
  spinner.start();
5129
- const result = await installPlugin({
6219
+ const plugin = findPlugin(pluginId);
6220
+ const result = await installPluginRouted({
5130
6221
  pluginId,
5131
6222
  tool,
5132
6223
  level,
5133
6224
  repoPath,
5134
6225
  installPath,
5135
- yes
6226
+ yes,
6227
+ plugin,
6228
+ source
5136
6229
  });
5137
6230
  if (result.success) {
5138
6231
  spinner.succeed(
@@ -5142,6 +6235,9 @@ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes
5142
6235
  } else {
5143
6236
  spinner.fail(`${progress} ${pluginId} \u2014 ${result.errors.join(", ")}`);
5144
6237
  }
6238
+ for (const warning of result.warnings || []) {
6239
+ logger.warn(warning);
6240
+ }
5145
6241
  results.push(result);
5146
6242
  }
5147
6243
  return results;
@@ -5149,8 +6245,8 @@ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes
5149
6245
 
5150
6246
  // src/commands/uninstall.ts
5151
6247
  init_registry();
5152
- import { existsSync as existsSync18, unlinkSync as unlinkSync6, rmdirSync, readdirSync as readdirSync8, readFileSync as readFileSync11 } from "fs";
5153
- import { resolve, basename as basename4, sep as sep2, join as join17 } from "path";
6248
+ import { existsSync as existsSync21, unlinkSync as unlinkSync8, rmdirSync as rmdirSync2, readdirSync as readdirSync11, readFileSync as readFileSync15 } from "fs";
6249
+ import { resolve as resolve3, basename as basename4, sep as sep3, join as join20 } from "path";
5154
6250
  import chalk11 from "chalk";
5155
6251
  import { select as select5, checkbox as checkbox2, Separator as Separator4, confirm as confirm3 } from "@inquirer/prompts";
5156
6252
  init_config();
@@ -5179,10 +6275,16 @@ async function uninstallCommand(names, options) {
5179
6275
  return;
5180
6276
  }
5181
6277
  const plugins = [];
6278
+ const cannbotPlugins = [];
5182
6279
  const skills = [];
6280
+ const cannbotInstalledIds = new Set(scanCannbotInstalled().map((e) => e.id));
5183
6281
  for (const name of names) {
6282
+ if (cannbotInstalledIds.has(name)) {
6283
+ cannbotPlugins.push(name);
6284
+ continue;
6285
+ }
5184
6286
  const plugin = findPlugin(name);
5185
- if (plugin) {
6287
+ if (plugin && plugin.source !== "cannbot") {
5186
6288
  plugins.push(plugin.id);
5187
6289
  continue;
5188
6290
  }
@@ -5201,9 +6303,16 @@ async function uninstallCommand(names, options) {
5201
6303
  }
5202
6304
  logger.error(`${t("uninstall_not_found")}: ${name}`);
5203
6305
  }
6306
+ await uninstallCannbotPlugins(cannbotPlugins);
5204
6307
  await uninstallPlugins(plugins, options.yes || false);
5205
6308
  await uninstallSkillsByName(skills, tool, level);
5206
6309
  }
6310
+ async function uninstallCannbotPlugins(pluginIds) {
6311
+ for (const pluginId of pluginIds) {
6312
+ logger.info(`${t("uninstall_in_progress")} ${pluginId}...`);
6313
+ await uninstallCannbotPluginById(pluginId);
6314
+ }
6315
+ }
5207
6316
  function isRecordedSkill(skillId) {
5208
6317
  const record = readSkillRecord();
5209
6318
  for (const toolName of Object.keys(record)) {
@@ -5221,12 +6330,13 @@ async function uninstallAll(tool, level, yes) {
5221
6330
  const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
5222
6331
  const installedSkills = getInstalledSkills(tool, level, installPath, !yes);
5223
6332
  const installedPlugins = scanInstalled().filter((p) => p.tool === tool && p.level === level);
5224
- const totalItems = installedSkills.length + installedPlugins.length;
6333
+ const cannbotInstalled = level === "project" ? scanCannbotInstalled().filter((e) => e.tool === tool) : [];
6334
+ const totalItems = installedSkills.length + installedPlugins.length + cannbotInstalled.length;
5225
6335
  if (totalItems === 0) {
5226
6336
  logger.warn(t("uninstall_no_installed"));
5227
6337
  return;
5228
6338
  }
5229
- logger.info(t("uninstall_all_summary").replace("{skills}", String(installedSkills.length)).replace("{plugins}", String(installedPlugins.length)));
6339
+ logger.info(t("uninstall_all_summary").replace("{skills}", String(installedSkills.length)).replace("{plugins}", String(installedPlugins.length + cannbotInstalled.length)));
5230
6340
  logger.blank();
5231
6341
  for (const s of installedSkills) {
5232
6342
  logger.step(` \u2022 ${chalk11.cyan(s)}`);
@@ -5234,6 +6344,9 @@ async function uninstallAll(tool, level, yes) {
5234
6344
  for (const p of installedPlugins) {
5235
6345
  logger.step(` \u2022 ${chalk11.cyan(p.displayName)} ${chalk11.dim(`(${p.id})`)}`);
5236
6346
  }
6347
+ for (const c of cannbotInstalled) {
6348
+ logger.step(` \u2022 ${chalk11.cyan(c.id)} ${chalk11.magenta("[cannbot]")}`);
6349
+ }
5237
6350
  logger.blank();
5238
6351
  if (!yes) {
5239
6352
  const confirmed = await confirm3({
@@ -5249,6 +6362,9 @@ async function uninstallAll(tool, level, yes) {
5249
6362
  logger.info(`${t("uninstall_skills_in_progress")} ${t("skill_count_format").replace("{count}", String(installedSkills.length))}...`);
5250
6363
  await uninstallSkills(installedSkills, tool, level);
5251
6364
  }
6365
+ for (const entry of cannbotInstalled) {
6366
+ await uninstallCannbotPluginById(entry.id);
6367
+ }
5252
6368
  if (installedPlugins.length > 0) {
5253
6369
  logger.info(`${t("uninstall_plugin_batch_progress").replace("{count}", String(installedPlugins.length))}...`);
5254
6370
  for (const inst of installedPlugins) {
@@ -5259,6 +6375,18 @@ async function uninstallAll(tool, level, yes) {
5259
6375
  logger.success(t("uninstall_all_done"));
5260
6376
  logger.blank();
5261
6377
  }
6378
+ async function uninstallCannbotPluginById(pluginId) {
6379
+ const result = uninstallCannbotPlugin(pluginId);
6380
+ if (result.success) {
6381
+ logger.success(
6382
+ t("uninstall_summary_format").replace("{name}", pluginId).replace("{files}", String(result.removedFiles)).replace("{dirs}", String(result.removedDirs))
6383
+ );
6384
+ } else {
6385
+ for (const error of result.errors) {
6386
+ logger.error(`${pluginId}: ${error}`);
6387
+ }
6388
+ }
6389
+ }
5262
6390
  async function uninstallRecent(tool, level, yes) {
5263
6391
  const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
5264
6392
  const lastBatch = getLastBatchSkills(tool, level, installPath);
@@ -5399,7 +6527,8 @@ async function interactiveSkillUninstall(tool, level) {
5399
6527
  }
5400
6528
  async function interactivePluginUninstall(tool, level) {
5401
6529
  const installed = scanInstalled().filter((p) => p.tool === tool && p.level === level);
5402
- if (installed.length === 0) {
6530
+ const cannbotInstalled = level === "project" ? scanCannbotInstalled().filter((e) => e.tool === tool) : [];
6531
+ if (installed.length === 0 && cannbotInstalled.length === 0) {
5403
6532
  logger.warn(t("uninstall_no_installed_plugins"));
5404
6533
  return;
5405
6534
  }
@@ -5409,6 +6538,12 @@ async function interactivePluginUninstall(tool, level) {
5409
6538
  value: inst.id,
5410
6539
  checked: false,
5411
6540
  description: `${inst.skillsCount} skills, ${inst.agentsCount} agents`
6541
+ })),
6542
+ ...cannbotInstalled.map((entry) => ({
6543
+ name: `${chalk11.cyan(entry.id)} ${chalk11.magenta("[cannbot]")}`,
6544
+ value: entry.id,
6545
+ checked: false,
6546
+ description: `${entry.skillsCount} skills, ${entry.agentsCount} agents`
5412
6547
  }))
5413
6548
  ];
5414
6549
  printBoxTitle(t("uninstall_interactive_title"));
@@ -5423,21 +6558,29 @@ async function interactivePluginUninstall(tool, level) {
5423
6558
  theme: checkboxTheme,
5424
6559
  pageSize: 15
5425
6560
  });
5426
- const toUninstall = installed.filter((inst) => selected.includes(inst.id));
5427
- if (toUninstall.length === 0) {
6561
+ const toUninstallLegacy = installed.filter((inst) => selected.includes(inst.id));
6562
+ const toUninstallCannbot = cannbotInstalled.filter((entry) => selected.includes(entry.id));
6563
+ if (toUninstallLegacy.length === 0 && toUninstallCannbot.length === 0) {
5428
6564
  logger.info(t("uninstall_nothing_to_uninstall"));
5429
6565
  return;
5430
6566
  }
5431
6567
  const confirmed = await confirm3({
5432
- message: t("uninstall_confirm_prompt").replace("{count}", chalk11.bold(String(toUninstall.length))),
6568
+ message: t("uninstall_confirm_prompt").replace(
6569
+ "{count}",
6570
+ chalk11.bold(String(toUninstallLegacy.length + toUninstallCannbot.length))
6571
+ ),
5433
6572
  default: false
5434
6573
  });
5435
6574
  if (!confirmed) {
5436
6575
  logger.info(t("init_cancelled"));
5437
6576
  return;
5438
6577
  }
5439
- logger.info(`${t("uninstall_plugin_batch_progress").replace("{count}", String(toUninstall.length))}...`);
5440
- for (const inst of toUninstall) {
6578
+ const totalCount = toUninstallLegacy.length + toUninstallCannbot.length;
6579
+ logger.info(`${t("uninstall_plugin_batch_progress").replace("{count}", String(totalCount))}...`);
6580
+ for (const entry of toUninstallCannbot) {
6581
+ await uninstallCannbotPluginById(entry.id);
6582
+ }
6583
+ for (const inst of toUninstallLegacy) {
5441
6584
  await uninstallPluginById(inst.id, true);
5442
6585
  }
5443
6586
  logger.blank();
@@ -5472,11 +6615,11 @@ async function uninstallPluginById(pluginId, batchMode) {
5472
6615
  for (const toolName of VALID_TOOLS) {
5473
6616
  for (const lvl of ["project", "global"]) {
5474
6617
  const configRoot2 = getConfigRoot(toolName, lvl);
5475
- const manifestPath = join17(configRoot2, `${pluginId}-manifest.json`);
5476
- if (!existsSync18(manifestPath)) continue;
6618
+ const manifestPath = join20(configRoot2, `${pluginId}-manifest.json`);
6619
+ if (!existsSync21(manifestPath)) continue;
5477
6620
  let manifest = null;
5478
6621
  try {
5479
- manifest = JSON.parse(readFileSync11(manifestPath, "utf-8"));
6622
+ manifest = JSON.parse(readFileSync15(manifestPath, "utf-8"));
5480
6623
  } catch {
5481
6624
  continue;
5482
6625
  }
@@ -5504,29 +6647,29 @@ async function uninstallPluginById(pluginId, batchMode) {
5504
6647
  }
5505
6648
  logger.info(`${t("uninstall_in_progress")} ${plugin.displayName}...`);
5506
6649
  const configRoot = getConfigRoot(record.tool, record.level);
5507
- const allowedBases = [resolve(configRoot), resolve(record.installPath)];
6650
+ const allowedBases = [resolve3(configRoot), resolve3(record.installPath)];
5508
6651
  if (record.tool === "codex") {
5509
- allowedBases.push(resolve(getSkillsRoot(record.tool, record.level, record.installPath)));
6652
+ allowedBases.push(resolve3(getSkillsRoot(record.tool, record.level, record.installPath)));
5510
6653
  }
5511
6654
  const isSafePath = (p) => {
5512
- const resolved = resolve(p);
5513
- return allowedBases.some((base) => resolved === base || resolved.startsWith(base + sep2));
6655
+ const resolved = resolve3(p);
6656
+ return allowedBases.some((base) => resolved === base || resolved.startsWith(base + sep3));
5514
6657
  };
5515
6658
  let removedFiles = 0;
5516
6659
  let removedDirs = 0;
5517
6660
  const configFileName = record.tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
5518
- const configRootConfigPath = join17(configRoot, configFileName);
5519
- const installPathConfigPath = join17(record.installPath, configFileName);
6661
+ const configRootConfigPath = join20(configRoot, configFileName);
6662
+ const installPathConfigPath = join20(record.installPath, configFileName);
5520
6663
  const extraFiles = [];
5521
- if (record.level === "project" && configRootConfigPath !== installPathConfigPath && !record.files.includes(configRootConfigPath) && (existsSync18(configRootConfigPath) || isSymlink(configRootConfigPath))) {
6664
+ if (record.level === "project" && configRootConfigPath !== installPathConfigPath && !record.files.includes(configRootConfigPath) && (existsSync21(configRootConfigPath) || isSymlink(configRootConfigPath))) {
5522
6665
  extraFiles.push(configRootConfigPath);
5523
6666
  }
5524
6667
  const allFiles = [...record.files, ...extraFiles];
5525
6668
  for (const filePath of allFiles) {
5526
6669
  if (!isSafePath(filePath)) continue;
5527
6670
  try {
5528
- if (existsSync18(filePath) || isSymlink(filePath)) {
5529
- unlinkSync6(filePath);
6671
+ if (existsSync21(filePath) || isSymlink(filePath)) {
6672
+ unlinkSync8(filePath);
5530
6673
  const name = basename4(filePath);
5531
6674
  logger.step(` ${t("uninstall_remove_label")}: ${name}`);
5532
6675
  removedFiles++;
@@ -5537,13 +6680,13 @@ async function uninstallPluginById(pluginId, batchMode) {
5537
6680
  const sortedDirs = [...record.directories].filter(isSafePath).sort((a, b) => b.length - a.length);
5538
6681
  for (const dirPath of sortedDirs) {
5539
6682
  try {
5540
- if (resolve(dirPath) === resolve(configRoot)) {
6683
+ if (resolve3(dirPath) === resolve3(configRoot)) {
5541
6684
  continue;
5542
6685
  }
5543
- if (existsSync18(dirPath)) {
5544
- const entries = readdirSync8(dirPath);
6686
+ if (existsSync21(dirPath)) {
6687
+ const entries = readdirSync11(dirPath);
5545
6688
  if (entries.length === 0) {
5546
- rmdirSync(dirPath);
6689
+ rmdirSync2(dirPath);
5547
6690
  const name = basename4(dirPath);
5548
6691
  logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
5549
6692
  removedDirs++;
@@ -5554,9 +6697,9 @@ async function uninstallPluginById(pluginId, batchMode) {
5554
6697
  }
5555
6698
  if (record.tool === "codex") {
5556
6699
  try {
5557
- const agentsRoot = resolve(getSkillsRoot(record.tool, record.level, record.installPath), "..");
5558
- if (existsSync18(agentsRoot) && readdirSync8(agentsRoot).length === 0) {
5559
- rmdirSync(agentsRoot);
6700
+ const agentsRoot = resolve3(getSkillsRoot(record.tool, record.level, record.installPath), "..");
6701
+ if (existsSync21(agentsRoot) && readdirSync11(agentsRoot).length === 0) {
6702
+ rmdirSync2(agentsRoot);
5560
6703
  logger.step(` ${t("uninstall_clean_empty_dir")}: ${basename4(agentsRoot)}/`);
5561
6704
  removedDirs++;
5562
6705
  }
@@ -5628,15 +6771,20 @@ async function updateCommand(pluginNames, options) {
5628
6771
  updateSpinner.fail(t("repo_clone_failed").replace("{error}", error instanceof Error ? error.message : t("error_unknown")).replace("{url}", "").replace("{dir}", ""));
5629
6772
  return;
5630
6773
  }
6774
+ await ensureCannbotDiscovery();
5631
6775
  const installed = scanInstalled();
6776
+ const cannbotInstalled = scanCannbotInstalled();
5632
6777
  let targets = [];
5633
6778
  if (pluginNames.length === 0) {
5634
- if (installed.length === 0) {
6779
+ if (installed.length === 0 && cannbotInstalled.length === 0) {
5635
6780
  logger.info(t("update_no_plugins"));
5636
6781
  logger.info(t("update_install_hint").replace("{cmd}", chalk12.cyan("install-helper install <plugin>")));
5637
6782
  return;
5638
6783
  }
5639
- targets = installed.map((p) => ({ pluginId: p.id, tool: p.tool, level: p.level }));
6784
+ targets = [
6785
+ ...installed.map((p) => ({ pluginId: p.id, tool: p.tool, level: p.level, source: "skills" })),
6786
+ ...cannbotInstalled.map((e) => ({ pluginId: e.id, tool: e.tool, level: "project", source: "cannbot" }))
6787
+ ];
5640
6788
  } else {
5641
6789
  let defaultTool;
5642
6790
  let defaultLevel;
@@ -5647,20 +6795,25 @@ async function updateCommand(pluginNames, options) {
5647
6795
  defaultLevel = validateLevel(options.level);
5648
6796
  }
5649
6797
  for (const name of pluginNames) {
6798
+ const cannbotMatches = cannbotInstalled.filter((e) => e.id === name);
5650
6799
  const plugin = findPlugin(name);
5651
- if (!plugin) {
6800
+ if (cannbotMatches.length === 0 && !plugin) {
5652
6801
  logger.error(`${t("error_plugin_not_found")}: ${name}`);
5653
6802
  continue;
5654
6803
  }
5655
- const installedForPlugin = installed.filter((p) => p.id === plugin.id);
5656
- if (installedForPlugin.length > 0) {
5657
- for (const inst of installedForPlugin) {
5658
- if (defaultTool && inst.tool !== defaultTool) continue;
5659
- if (defaultLevel && inst.level !== defaultLevel) continue;
5660
- targets.push({ pluginId: plugin.id, tool: inst.tool, level: inst.level });
6804
+ const legacyMatches = cannbotMatches.length === 0 && plugin && plugin.source !== "cannbot" ? installed.filter((p) => p.id === plugin.id) : [];
6805
+ const matches = [
6806
+ ...cannbotMatches.map((e) => ({ pluginId: e.id, tool: e.tool, level: "project", source: "cannbot" })),
6807
+ ...legacyMatches.map((p) => ({ pluginId: p.id, tool: p.tool, level: p.level, source: "skills" }))
6808
+ ];
6809
+ if (matches.length > 0) {
6810
+ for (const match of matches) {
6811
+ if (defaultTool && match.tool !== defaultTool) continue;
6812
+ if (defaultLevel && match.level !== defaultLevel) continue;
6813
+ targets.push(match);
5661
6814
  }
5662
6815
  } else {
5663
- logger.warn(`${plugin.displayName} ${t("error_not_installed")}, ${t("update_skipped")}`);
6816
+ logger.warn(`${plugin?.displayName || name} ${t("error_not_installed")}, ${t("update_skipped")}`);
5664
6817
  }
5665
6818
  }
5666
6819
  }
@@ -5668,6 +6821,18 @@ async function updateCommand(pluginNames, options) {
5668
6821
  logger.info(t("update_no_plugins"));
5669
6822
  return;
5670
6823
  }
6824
+ if (targets.some((target) => target.source === "cannbot")) {
6825
+ try {
6826
+ await ensureCannbotInstaller({ force: true });
6827
+ } catch (error) {
6828
+ logger.warn(
6829
+ t("cannbot_installer_failed").replace(
6830
+ "{error}",
6831
+ error instanceof Error ? error.message : t("error_unknown")
6832
+ )
6833
+ );
6834
+ }
6835
+ }
5671
6836
  const allPlugins = getAllPlugins();
5672
6837
  const results = [];
5673
6838
  const total = targets.length;
@@ -5678,12 +6843,14 @@ async function updateCommand(pluginNames, options) {
5678
6843
  const progress = `[${i + 1}/${total}]`;
5679
6844
  const pluginSpinner = createSpinner(`${progress} ${t("update_updating")} ${displayName}...`);
5680
6845
  pluginSpinner.start();
5681
- const result = await installPlugin({
6846
+ const result = await installPluginRouted({
5682
6847
  pluginId: target.pluginId,
5683
6848
  tool: target.tool,
5684
6849
  level: target.level,
5685
6850
  repoPath,
5686
- yes: options.yes
6851
+ yes: options.yes,
6852
+ plugin,
6853
+ source: target.source
5687
6854
  });
5688
6855
  if (result.success) {
5689
6856
  pluginSpinner.succeed(
@@ -5694,6 +6861,9 @@ async function updateCommand(pluginNames, options) {
5694
6861
  `${progress} ${displayName} \u2014 ${result.errors.join(", ")}`
5695
6862
  );
5696
6863
  }
6864
+ for (const warning of result.warnings || []) {
6865
+ logger.warn(warning);
6866
+ }
5697
6867
  results.push(result);
5698
6868
  }
5699
6869
  const summary = results.map((result) => {
@@ -5713,8 +6883,8 @@ async function updateCommand(pluginNames, options) {
5713
6883
  // src/commands/info.ts
5714
6884
  init_registry();
5715
6885
  import chalk13 from "chalk";
5716
- import { existsSync as existsSync19, readdirSync as readdirSync9 } from "fs";
5717
- import { join as join18 } from "path";
6886
+ import { existsSync as existsSync22, readdirSync as readdirSync12 } from "fs";
6887
+ import { join as join21 } from "path";
5718
6888
  init_manifest();
5719
6889
  init_i18n();
5720
6890
  async function infoCommand(pluginName) {
@@ -5725,29 +6895,42 @@ async function infoCommand(pluginName) {
5725
6895
  }
5726
6896
  const installed = scanInstalled();
5727
6897
  const inst = installed.find((p) => p.id === plugin.id);
5728
- const isInstalled = !!inst;
6898
+ const cannbotEntry = plugin.source === "cannbot" ? scanCannbotInstalled().find((e) => e.id === plugin.id) : void 0;
6899
+ const isInstalled = !!inst || !!cannbotEntry;
5729
6900
  const statusText = isInstalled ? chalk13.green(`\u2713 ${t("status_installed")}`) : chalk13.dim(`\u2014 ${t("status_not_installed")}`);
5730
6901
  console.log();
5731
6902
  console.log(chalk13.bold(` ${plugin.displayName}`));
6903
+ if (plugin.source === "cannbot") {
6904
+ console.log(chalk13.magenta(" [cannbot]") + chalk13.dim(` v${plugin.version || "?"}`));
6905
+ }
5732
6906
  console.log(chalk13.dim(" " + "\u2500".repeat(40)));
5733
6907
  console.log();
5734
6908
  console.log(` ${chalk13.dim(t("info_description") + ":")} ${plugin.description}`);
5735
6909
  console.log(` ${chalk13.dim("ID:")} ${plugin.id}`);
5736
6910
  console.log(` ${chalk13.dim(t("info_status") + ":")} ${statusText}`);
5737
- console.log(` ${chalk13.dim(t("info_skills") + ":")} ${inst ? inst.skillsCount : plugin.skills}`);
5738
- console.log(` ${chalk13.dim(t("info_agents") + ":")} ${inst ? inst.agentsCount : plugin.agents}`);
5739
- console.log(` ${chalk13.dim(t("info_aliases") + ":")} ${plugin.aliases.join(", ")}`);
6911
+ console.log(` ${chalk13.dim(t("info_skills") + ":")} ${inst || cannbotEntry ? cannbotEntry?.skillsCount ?? inst?.skillsCount : plugin.skills}`);
6912
+ console.log(` ${chalk13.dim(t("info_agents") + ":")} ${inst || cannbotEntry ? cannbotEntry?.agentsCount ?? inst?.agentsCount : plugin.agents}`);
6913
+ if (plugin.source !== "cannbot") {
6914
+ console.log(` ${chalk13.dim(t("info_aliases") + ":")} ${plugin.aliases.join(", ")}`);
6915
+ }
5740
6916
  console.log();
6917
+ if (plugin.source === "cannbot") {
6918
+ if (cannbotEntry) {
6919
+ console.log(` ${chalk13.dim(t("info_source") + ":")} ${cannbotEntry.registryPath}`);
6920
+ }
6921
+ console.log();
6922
+ return;
6923
+ }
5741
6924
  try {
5742
6925
  const repoManager = createRepositoryManager();
5743
6926
  const repoPath = await repoManager.ensureRepo();
5744
- const quickstartPath = join18(repoPath, plugin.dir, "quickstart.md");
5745
- if (existsSync19(quickstartPath)) {
6927
+ const quickstartPath = join21(repoPath, plugin.dir, "quickstart.md");
6928
+ if (existsSync22(quickstartPath)) {
5746
6929
  console.log(` ${chalk13.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
5747
6930
  }
5748
- const agentsDir = join18(repoPath, plugin.dir, "agents");
5749
- if (existsSync19(agentsDir)) {
5750
- const agentFiles = readdirSync9(agentsDir).filter((f) => f.endsWith(".md"));
6931
+ const agentsDir = join21(repoPath, plugin.dir, "agents");
6932
+ if (existsSync22(agentsDir)) {
6933
+ const agentFiles = readdirSync12(agentsDir).filter((f) => f.endsWith(".md"));
5751
6934
  if (agentFiles.length > 0) {
5752
6935
  console.log();
5753
6936
  console.log(` ${chalk13.bold("Agents:")}`);
@@ -5802,9 +6985,9 @@ async function langCommand(action, value) {
5802
6985
  // src/cli.ts
5803
6986
  init_config();
5804
6987
  init_i18n();
5805
- import { readFileSync as readFileSync12, existsSync as existsSync20 } from "fs";
5806
- import { fileURLToPath as fileURLToPath4 } from "url";
5807
- import { dirname as dirname6, join as join19 } from "path";
6988
+ import { readFileSync as readFileSync16, existsSync as existsSync23 } from "fs";
6989
+ import { fileURLToPath as fileURLToPath5 } from "url";
6990
+ import { dirname as dirname8, join as join22 } from "path";
5808
6991
  function readVersion() {
5809
6992
  try {
5810
6993
  if (typeof INSTALL_HELPER_VERSION !== "undefined" && INSTALL_HELPER_VERSION) {
@@ -5813,16 +6996,16 @@ function readVersion() {
5813
6996
  } catch {
5814
6997
  }
5815
6998
  try {
5816
- const __filename4 = fileURLToPath4(import.meta.url);
5817
- const __dirname4 = dirname6(__filename4);
6999
+ const __filename5 = fileURLToPath5(import.meta.url);
7000
+ const __dirname5 = dirname8(__filename5);
5818
7001
  const candidates = [
5819
- join19(__dirname4, "..", "package.json"),
5820
- join19(__dirname4, "package.json"),
5821
- join19(process.cwd(), "package.json")
7002
+ join22(__dirname5, "..", "package.json"),
7003
+ join22(__dirname5, "package.json"),
7004
+ join22(process.cwd(), "package.json")
5822
7005
  ];
5823
7006
  for (const pkgPath of candidates) {
5824
- if (existsSync20(pkgPath)) {
5825
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
7007
+ if (existsSync23(pkgPath)) {
7008
+ const pkg = JSON.parse(readFileSync16(pkgPath, "utf-8"));
5826
7009
  if (pkg.version && (pkg.name === "@cannbot-ai/install-helper" || pkg.name?.startsWith("@cannbot-ai/install-helper-"))) {
5827
7010
  return pkg.version;
5828
7011
  }
@@ -5852,7 +7035,7 @@ function createCLI() {
5852
7035
  program2.command("status").description("Show installed plugins").action(async () => {
5853
7036
  await statusCommand();
5854
7037
  });
5855
- program2.command("install [names...]").description("Install plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, codex, copilot, codearts)").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) => {
7038
+ program2.command("install [names...]").description("Install plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, codex, copilot, codearts)").option("-l, --level <level>", "Install level (project, global)", "project").option("-s, --source <source>", "Plugin source for same-name plugins (cannbot, skills)").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) => {
5856
7039
  await installCommand(names, options);
5857
7040
  });
5858
7041
  program2.command("uninstall [names...]").description("Uninstall plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, codex, copilot, codearts)").option("-l, --level <level>", "Install level (project, global)", "project").option("-y, --yes", "Skip confirmation prompts").option("-a, --all", "Uninstall ALL installed skills and plugins").option("--recent", "Uninstall the most recently installed skill batch").action(async (names, options) => {