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

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,32 @@ 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_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",
284
+ 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",
285
+ 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}",
286
+ cannbot_registry_corrupted: "\u65E0\u6CD5\u89E3\u6790 cannbot-plugin.json: {file}",
287
+ 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",
288
+ cannbot_uninstall_no_record: "\u672A\u627E\u5230\u8BE5\u63D2\u4EF6\u7684 cannbot \u5B89\u88C5\u8BB0\u5F55",
289
+ 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}",
290
+ doctor_cannbot: "cannbot \u96C6\u6210",
291
+ doctor_cannbot_node_ok: "Node.js \u8FD0\u884C\u65F6\u53EF\u7528\uFF08\u652F\u6301 cannbot \u63D2\u4EF6\u59D4\u6258\u5B89\u88C5\uFF09",
292
+ doctor_cannbot_node_missing: "\u672A\u68C0\u6D4B\u5230 Node.js \u8FD0\u884C\u65F6\uFF0Ccannbot \u578B\u63D2\u4EF6\u4E0D\u53EF\u5B89\u88C5",
293
+ doctor_cannbot_missing_skills: "\u6CE8\u518C\u8868\u8BB0\u5F55\u7684 Skills \u4E2D\u6709 {count} \u4E2A\u5728\u78C1\u76D8\u4E0A\u4E0D\u5B58\u5728",
294
+ doctor_cannbot_marker_duplicate: "\u63D2\u4EF6 {plugin} \u5B58\u5728\u91CD\u590D\u7684\u6807\u8BB0\u5757",
295
+ doctor_cannbot_marker_unpaired: "\u6807\u8BB0\u5757\u4E0D\u914D\u5BF9\uFF08start={start}, end={end}\uFF09",
296
+ 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
297
  };
273
298
  }
274
299
  });
@@ -493,7 +518,32 @@ var init_en_US = __esm({
493
518
  uninstall_uncheck_all_category: "Uninstall all in this category",
494
519
  uninstall_continue_or_confirm: "{count} marked for uninstall, choose an action",
495
520
  uninstall_marked_count: "{count} marked for uninstall",
496
- uninstall_uncategorized: "Uncategorized"
521
+ uninstall_uncategorized: "Uncategorized",
522
+ error_invalid_source: "Invalid plugin source: {source}\nSupported sources: {sources}",
523
+ info_source: "Install registry",
524
+ cannbot_tool_unsupported: "Tool {tool} is not supported for cannbot plugins yet (upstream supports: {tools})",
525
+ cannbot_level_unsupported: "cannbot plugins support project-level installation only",
526
+ 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",
527
+ cannbot_installer_failed: "Failed to fetch the @cannbot-plugin/cannbot installer: {error}",
528
+ cannbot_installer_refresh_failed: "Failed to refresh the cannbot installer; continuing with the local cache",
529
+ cannbot_installer_path_invalid: "The installer path set by CANNBOT_INSTALLER_PATH is invalid: {path}",
530
+ cannbot_plugin_not_found: "Plugin {plugin} was not found in the cannbot installer package",
531
+ cannbot_registry_missing: "No cannbot-plugin.json record found after installation",
532
+ cannbot_delegate_failed: "The cannbot installer exited with code {code}; see the output above",
533
+ cannbot_migrated_symlink: "{file} was a legacy plugin symlink; converted to a real file (content kept, backup saved)",
534
+ cannbot_migrate_symlink_failed: "Failed to convert the legacy symlink {file} to a real file; resolve it manually and retry",
535
+ cannbot_dual_skill_copy: "These Skills exist in both the legacy and cannbot layouts, the AI tool will see two copies: {skills}",
536
+ cannbot_registry_corrupted: "Cannot parse cannbot-plugin.json: {file}",
537
+ cannbot_registry_schema_unsupported: "cannbot-plugin.json schemaVersion ({version}) is not supported; upgrade install-helper before uninstalling",
538
+ cannbot_uninstall_no_record: "No cannbot install record found for this plugin",
539
+ cannbot_uninstall_native_hint: "This plugin registered a native plugin with the tool; unregister it via: opencode plugin remove {spec}",
540
+ doctor_cannbot: "cannbot integration",
541
+ doctor_cannbot_node_ok: "Node.js runtime available (cannbot delegation supported)",
542
+ doctor_cannbot_node_missing: "No Node.js runtime detected; cannbot plugins cannot be installed",
543
+ doctor_cannbot_missing_skills: "{count} skills recorded in the registry are missing on disk",
544
+ doctor_cannbot_marker_duplicate: "Plugin {plugin} has duplicate marker blocks",
545
+ doctor_cannbot_marker_unpaired: "Marker blocks are unpaired (start={start}, end={end})",
546
+ doctor_cannbot_orphan_record: "Delegated install record for {plugin} is orphaned (no matching cannbot-plugin.json entry)"
497
547
  };
498
548
  }
499
549
  });
@@ -717,7 +767,12 @@ var init_embedded_config = __esm({
717
767
  pluginDirs: [
718
768
  "plugins-official"
719
769
  ],
720
- cacheTtlHours: 24
770
+ cacheTtlHours: 24,
771
+ cannbotInstaller: {
772
+ package: "@cannbot-plugin/cannbot",
773
+ channel: "latest",
774
+ ttlHours: 24
775
+ }
721
776
  };
722
777
  }
723
778
  });
@@ -1436,6 +1491,16 @@ var init_embedded_plugins = __esm({
1436
1491
  });
1437
1492
 
1438
1493
  // src/core/registry.ts
1494
+ var registry_exports = {};
1495
+ __export(registry_exports, {
1496
+ PLUGIN_REGISTRY: () => PLUGIN_REGISTRY,
1497
+ findPlugin: () => findPlugin,
1498
+ getAllPlugins: () => getAllPlugins,
1499
+ getCannbotPlugins: () => getCannbotPlugins,
1500
+ getPluginById: () => getPluginById,
1501
+ mergeDynamicPlugins: () => mergeDynamicPlugins,
1502
+ setCannbotPlugins: () => setCannbotPlugins
1503
+ });
1439
1504
  import { readdirSync as readdirSync2, existsSync as existsSync5 } from "fs";
1440
1505
  import { join as join4 } from "path";
1441
1506
  function loadPlugins() {
@@ -1445,6 +1510,12 @@ function loadPlugins() {
1445
1510
  );
1446
1511
  return plugins;
1447
1512
  }
1513
+ function setCannbotPlugins(entries) {
1514
+ CANNBOT_PLUGINS = entries.map((entry) => ({ ...entry, source: "cannbot" }));
1515
+ }
1516
+ function getCannbotPlugins() {
1517
+ return [...CANNBOT_PLUGINS];
1518
+ }
1448
1519
  function mergeDynamicPlugins(repoPath) {
1449
1520
  if (!existsSync5(repoPath)) return;
1450
1521
  const existingIds = new Set(PLUGIN_REGISTRY.map((p) => p.id));
@@ -1485,8 +1556,19 @@ function mergeDynamicPlugins(repoPath) {
1485
1556
  );
1486
1557
  }
1487
1558
  }
1488
- function findPlugin(query) {
1559
+ function findPlugin(query, options) {
1489
1560
  const normalized = query.toLowerCase().trim();
1561
+ const wantCannbot = options?.source !== "skills";
1562
+ if (wantCannbot) {
1563
+ const cannbotExact = CANNBOT_PLUGINS.find(
1564
+ (p) => p.id === normalized || p.id === query.trim()
1565
+ );
1566
+ if (cannbotExact) return cannbotExact;
1567
+ const cannbotAlias = CANNBOT_PLUGINS.find(
1568
+ (p) => p.aliases.some((a) => a === normalized)
1569
+ );
1570
+ if (cannbotAlias) return cannbotAlias;
1571
+ }
1490
1572
  const exactMatch = PLUGIN_REGISTRY.find(
1491
1573
  (p) => p.id === normalized || p.id === query.trim()
1492
1574
  );
@@ -1495,17 +1577,25 @@ function findPlugin(query) {
1495
1577
  (p) => p.aliases.some((a) => a === normalized)
1496
1578
  );
1497
1579
  if (aliasMatch) return aliasMatch;
1580
+ if (wantCannbot) {
1581
+ const cannbotPrefixes = CANNBOT_PLUGINS.filter((p) => p.id.startsWith(normalized));
1582
+ if (cannbotPrefixes.length === 1) return cannbotPrefixes[0];
1583
+ }
1498
1584
  const prefixMatches = PLUGIN_REGISTRY.filter((p) => p.id.startsWith(normalized));
1499
1585
  if (prefixMatches.length === 1) return prefixMatches[0];
1500
1586
  return void 0;
1501
1587
  }
1502
1588
  function getAllPlugins() {
1503
- return PLUGIN_REGISTRY;
1589
+ return [...CANNBOT_PLUGINS, ...PLUGIN_REGISTRY].sort(
1590
+ (a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
1591
+ );
1504
1592
  }
1505
1593
  function getPluginById(id) {
1594
+ const cannbot = CANNBOT_PLUGINS.find((p) => p.id === id);
1595
+ if (cannbot) return cannbot;
1506
1596
  return PLUGIN_REGISTRY.find((p) => p.id === id);
1507
1597
  }
1508
- var PLUGIN_REGISTRY;
1598
+ var PLUGIN_REGISTRY, CANNBOT_PLUGINS;
1509
1599
  var init_registry = __esm({
1510
1600
  "src/core/registry.ts"() {
1511
1601
  "use strict";
@@ -1513,6 +1603,7 @@ var init_registry = __esm({
1513
1603
  init_scanner();
1514
1604
  init_embedded_plugins();
1515
1605
  PLUGIN_REGISTRY = loadPlugins();
1606
+ CANNBOT_PLUGINS = [];
1516
1607
  }
1517
1608
  });
1518
1609
 
@@ -2177,8 +2268,9 @@ async function stepPlugins(tool, level) {
2177
2268
  const choices = plugins.map((p) => {
2178
2269
  const isInstalled = installedSet.has(p.id);
2179
2270
  const suffix = isInstalled ? ` [${t("wizard_already_installed")}]` : "";
2271
+ const badge = p.source === "cannbot" ? chalk3.magenta(" [cannbot]") : "";
2180
2272
  return {
2181
- name: `> ${chalk3.cyan(p.displayName)}${suffix}`,
2273
+ name: `> ${chalk3.cyan(p.displayName)}${badge}${suffix}`,
2182
2274
  value: p.id,
2183
2275
  description: p.description
2184
2276
  };
@@ -2403,7 +2495,7 @@ var STATIC_SKILL_CATEGORIES = [
2403
2495
  },
2404
2496
  {
2405
2497
  id: "model",
2406
- name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316",
2498
+ name: "\u6A21\u578B\u63A8\u7406\u4E0E\u8BAD\u7EC3",
2407
2499
  skills: [
2408
2500
  { id: "model-infer-migrator", description: "\u6846\u67B6\u9002\u914D\u4E0E\u90E8\u7F72\u57FA\u7EBF", source: "model" },
2409
2501
  { id: "model-infer-parallel-analysis", description: "\u5E76\u884C\u7B56\u7565\u5206\u6790\uFF08TP/EP/DP\uFF09", source: "model" },
@@ -2417,7 +2509,9 @@ var STATIC_SKILL_CATEGORIES = [
2417
2509
  { id: "model-infer-precision-debug", description: "NPU \u63A8\u7406\u7CBE\u5EA6\u8BCA\u65AD", source: "model" },
2418
2510
  { id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" },
2419
2511
  { 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" },
2512
+ { 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" },
2513
+ { id: "model-train-precision-nonfinite", description: "PyTorch on NPU \u8BAD\u7EC3 NaN/Inf/Overflow \u8BCA\u65AD", source: "model" },
2514
+ { id: "model-train-precision-determinism", description: "PyTorch on NPU \u8BAD\u7EC3\u975E\u786E\u5B9A\u6027\u8BCA\u65AD", source: "model" },
2421
2515
  { id: "model-train-log-visualization", description: "NPU \u5927\u6A21\u578B\u8BAD\u7EC3\u65E5\u5FD7\u53EF\u89C6\u5316\u4E0E\u5BF9\u6BD4", source: "model" },
2422
2516
  { id: "model-train-oom-analysis", description: "PyTorch on NPU \u8BAD\u7EC3 OOM \u5185\u5B58\u8BCA\u65AD\u4E0E\u4F18\u5316", source: "model" },
2423
2517
  { 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 +2894,245 @@ function createRepositoryManager(customPath) {
2800
2894
  return new RepositoryManager(customPath);
2801
2895
  }
2802
2896
 
2803
- // src/core/installer.ts
2804
- init_registry();
2805
- init_manifest();
2897
+ // src/core/cannbot-delegate.ts
2898
+ import { existsSync as existsSync17, readFileSync as readFileSync12, writeFileSync as writeFileSync5, copyFileSync as copyFileSync3, unlinkSync as unlinkSync6, readdirSync as readdirSync8 } from "fs";
2899
+ import { join as join16, resolve as resolve2 } from "path";
2900
+ import { execa as execa6 } from "execa";
2901
+
2902
+ // src/core/cannbot-installer.ts
2806
2903
  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";
2904
+ init_logger();
2905
+ init_i18n();
2906
+ init_embedded_config();
2907
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync7, readdirSync as readdirSync4, writeFileSync as writeFileSync2 } from "fs";
2908
+ import { join as join10, dirname as dirname5 } from "path";
2909
+ import { fileURLToPath as fileURLToPath4 } from "url";
2910
+ import { execa as execa3, execaSync } from "execa";
2911
+ import { parse as parseYaml3 } from "yaml";
2912
+ var __filename4 = fileURLToPath4(import.meta.url);
2913
+ var __dirname4 = dirname5(__filename4);
2914
+ var DEFAULT_CONFIG = {
2915
+ package: "@cannbot-plugin/cannbot",
2916
+ channel: "latest",
2917
+ ttlHours: 24
2918
+ };
2919
+ var NPM_MIRRORS = [
2920
+ void 0,
2921
+ // user's npm config (registry.npmjs.org by default)
2922
+ "https://registry.npmmirror.com"
2923
+ ];
2924
+ function loadCannbotInstallerConfig() {
2925
+ let config = DEFAULT_CONFIG;
2926
+ const configCandidates = [
2927
+ join10(__dirname4, "config", "repository.yaml"),
2928
+ join10(__dirname4, "..", "config", "repository.yaml")
2929
+ ];
2930
+ for (const configPath of configCandidates) {
2931
+ try {
2932
+ const parsed = parseYaml3(readFileSync7(configPath, "utf-8"));
2933
+ if (!parsed?.cannbotInstaller) continue;
2934
+ config = {
2935
+ package: parsed.cannbotInstaller.package || DEFAULT_CONFIG.package,
2936
+ channel: parsed.cannbotInstaller.channel || DEFAULT_CONFIG.channel,
2937
+ ttlHours: parsed.cannbotInstaller.ttlHours || DEFAULT_CONFIG.ttlHours
2938
+ };
2939
+ break;
2940
+ } catch {
2941
+ }
2942
+ }
2943
+ if (config === DEFAULT_CONFIG) {
2944
+ const embedded = embedded_config_default;
2945
+ if (embedded.cannbotInstaller) {
2946
+ config = {
2947
+ package: embedded.cannbotInstaller.package || DEFAULT_CONFIG.package,
2948
+ channel: embedded.cannbotInstaller.channel || DEFAULT_CONFIG.channel,
2949
+ ttlHours: embedded.cannbotInstaller.ttlHours || DEFAULT_CONFIG.ttlHours
2950
+ };
2951
+ }
2952
+ }
2953
+ return {
2954
+ ...config,
2955
+ channel: process.env.CANNBOT_INSTALLER_CHANNEL || config.channel
2956
+ };
2957
+ }
2958
+ function getCannbotInstallerPrefix() {
2959
+ return join10(getCannbotConfigDir(), "installers", "cannbot");
2960
+ }
2961
+ function getCannbotPackageRoot() {
2962
+ const override = process.env.CANNBOT_INSTALLER_PATH;
2963
+ if (override) return override;
2964
+ const { package: packageName } = loadCannbotInstallerConfig();
2965
+ return join10(getCannbotInstallerPrefix(), "node_modules", ...packageName.split("/"));
2966
+ }
2967
+ function getCannbotBinPath() {
2968
+ return join10(getCannbotPackageRoot(), "bin", "cannbot.js");
2969
+ }
2970
+ function getStatePath() {
2971
+ return join10(getCannbotInstallerPrefix(), "state.json");
2972
+ }
2973
+ function readState() {
2974
+ try {
2975
+ return JSON.parse(readFileSync7(getStatePath(), "utf-8"));
2976
+ } catch {
2977
+ return null;
2978
+ }
2979
+ }
2980
+ function writeState(state) {
2981
+ const prefix = getCannbotInstallerPrefix();
2982
+ if (!existsSync11(prefix)) {
2983
+ mkdirSync4(prefix, { recursive: true });
2984
+ }
2985
+ writeFileSync2(getStatePath(), JSON.stringify(state, null, 2));
2986
+ }
2987
+ function isCannbotInstallerReady() {
2988
+ return existsSync11(getCannbotBinPath());
2989
+ }
2990
+ function getCannbotInstallerVersion() {
2991
+ try {
2992
+ const pkg = JSON.parse(readFileSync7(join10(getCannbotPackageRoot(), "package.json"), "utf-8"));
2993
+ return pkg.version || null;
2994
+ } catch {
2995
+ return null;
2996
+ }
2997
+ }
2998
+ function hasNodeRuntime() {
2999
+ try {
3000
+ const result = execaSync("node", ["--version"], { timeout: 1e4 });
3001
+ return result.exitCode === 0;
3002
+ } catch {
3003
+ return false;
3004
+ }
3005
+ }
3006
+ async function npmInstallInstaller(config, force) {
3007
+ const prefix = getCannbotInstallerPrefix();
3008
+ if (!existsSync11(prefix)) {
3009
+ mkdirSync4(prefix, { recursive: true });
3010
+ }
3011
+ const spec = `${config.package}@${config.channel}`;
3012
+ let lastError = null;
3013
+ for (const registry of NPM_MIRRORS) {
3014
+ const args = ["install", "--prefix", prefix, "--no-audit", "--no-fund", "--ignore-scripts", spec];
3015
+ if (registry) args.push("--registry", registry);
3016
+ try {
3017
+ await execa3("npm", args, { timeout: 3e5 });
3018
+ return;
3019
+ } catch (error) {
3020
+ lastError = error;
3021
+ if (force || !isCannbotInstallerReady()) {
3022
+ continue;
3023
+ }
3024
+ break;
3025
+ }
3026
+ }
3027
+ if (isCannbotInstallerReady() && !force) {
3028
+ logger.warn(t("cannbot_installer_refresh_failed"));
3029
+ return;
3030
+ }
3031
+ throw lastError instanceof Error ? lastError : new Error(t("error_unknown"));
3032
+ }
3033
+ async function ensureCannbotInstaller(options) {
3034
+ const config = loadCannbotInstallerConfig();
3035
+ const packageRoot = getCannbotPackageRoot();
3036
+ if (process.env.CANNBOT_INSTALLER_PATH) {
3037
+ if (!isCannbotInstallerReady()) {
3038
+ throw new Error(
3039
+ t("cannbot_installer_path_invalid").replace("{path}", process.env.CANNBOT_INSTALLER_PATH)
3040
+ );
3041
+ }
3042
+ return packageRoot;
3043
+ }
3044
+ const ready = isCannbotInstallerReady();
3045
+ if (!ready || options?.force) {
3046
+ await npmInstallInstaller(config, options?.force || !ready);
3047
+ writeState({
3048
+ version: getCannbotInstallerVersion() || "unknown",
3049
+ channel: config.channel,
3050
+ installedAt: Date.now()
3051
+ });
3052
+ return packageRoot;
3053
+ }
3054
+ const state = readState();
3055
+ const ttlMs = config.ttlHours * 60 * 60 * 1e3;
3056
+ if (!state || Date.now() - state.installedAt > ttlMs) {
3057
+ try {
3058
+ await npmInstallInstaller(config, false);
3059
+ writeState({
3060
+ version: getCannbotInstallerVersion() || "unknown",
3061
+ channel: config.channel,
3062
+ installedAt: Date.now()
3063
+ });
3064
+ } catch {
3065
+ }
3066
+ }
3067
+ return packageRoot;
3068
+ }
3069
+ function readManifest2(pluginDir) {
3070
+ try {
3071
+ return JSON.parse(readFileSync7(join10(pluginDir, ".claude-plugin", "plugin.json"), "utf-8"));
3072
+ } catch {
3073
+ return null;
3074
+ }
3075
+ }
3076
+ function discoverCannbotPlugins() {
3077
+ if (!isCannbotInstallerReady()) {
3078
+ return [];
3079
+ }
3080
+ const packageRoot = getCannbotPackageRoot();
3081
+ const entries = [];
3082
+ const parents = [join10(packageRoot, "dist", "plugins"), join10(packageRoot, "plugins")];
3083
+ for (const parent of parents) {
3084
+ if (!existsSync11(parent)) continue;
3085
+ let names = [];
3086
+ try {
3087
+ names = readdirSync4(parent);
3088
+ } catch {
3089
+ continue;
3090
+ }
3091
+ for (const name of names) {
3092
+ const pluginDir = join10(parent, name);
3093
+ const manifest = readManifest2(pluginDir);
3094
+ if (!manifest || typeof manifest.name !== "string") continue;
3095
+ if (entries.some((e) => e.id === manifest.name)) continue;
3096
+ entries.push({
3097
+ id: manifest.name,
3098
+ version: manifest.version || "",
3099
+ description: manifest.description || "",
3100
+ skillsCount: Array.isArray(manifest.skills) ? manifest.skills.length : 0,
3101
+ agentsCount: Array.isArray(manifest.agents) ? manifest.agents.length : 0,
3102
+ dir: join10(parent, name)
3103
+ });
3104
+ }
3105
+ }
3106
+ return entries;
3107
+ }
3108
+ async function ensureCannbotDiscovery() {
3109
+ try {
3110
+ await ensureCannbotInstaller();
3111
+ } catch {
3112
+ return false;
3113
+ }
3114
+ const entries = discoverCannbotPlugins();
3115
+ const { setCannbotPlugins: setCannbotPlugins2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
3116
+ setCannbotPlugins2(
3117
+ entries.map((entry) => ({
3118
+ id: entry.id,
3119
+ dir: entry.dir,
3120
+ displayName: entry.id,
3121
+ script: "cannbot",
3122
+ aliases: [],
3123
+ skills: entry.skillsCount,
3124
+ agents: entry.agentsCount,
3125
+ description: entry.description,
3126
+ version: entry.version,
3127
+ source: "cannbot"
3128
+ }))
3129
+ );
3130
+ return true;
3131
+ }
3132
+
3133
+ // src/core/cannbot-registry.ts
3134
+ 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";
3135
+ import { join as join12, resolve, sep as sep2, dirname as dirname6 } from "path";
2810
3136
 
2811
3137
  // src/core/record.ts
2812
3138
  init_paths();
@@ -2814,21 +3140,21 @@ init_fs();
2814
3140
  init_fs_helpers();
2815
3141
  init_logger();
2816
3142
  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";
3143
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync8, unlinkSync as unlinkSync3, readdirSync as readdirSync5 } from "fs";
3144
+ import { join as join11 } from "path";
2819
3145
  function getInstallsDir() {
2820
- return join10(getCannbotConfigDir(), "installs");
3146
+ return join11(getCannbotConfigDir(), "installs");
2821
3147
  }
2822
3148
  function getRecordPath(pluginId) {
2823
- return join10(getInstallsDir(), `${pluginId}.json`);
3149
+ return join11(getInstallsDir(), `${pluginId}.json`);
2824
3150
  }
2825
3151
  function readRecord(pluginId) {
2826
3152
  const recordPath = getRecordPath(pluginId);
2827
- if (!existsSync11(recordPath)) {
3153
+ if (!existsSync12(recordPath)) {
2828
3154
  return null;
2829
3155
  }
2830
3156
  try {
2831
- const content = readFileSync7(recordPath, "utf-8");
3157
+ const content = readFileSync8(recordPath, "utf-8");
2832
3158
  return JSON.parse(content);
2833
3159
  } catch {
2834
3160
  logger.warn(t("record_corrupted").replace("{file}", recordPath));
@@ -2837,15 +3163,15 @@ function readRecord(pluginId) {
2837
3163
  }
2838
3164
  function writeRecord(record) {
2839
3165
  const installsDir = getInstallsDir();
2840
- if (!existsSync11(installsDir)) {
2841
- mkdirSync4(installsDir, { recursive: true });
3166
+ if (!existsSync12(installsDir)) {
3167
+ mkdirSync5(installsDir, { recursive: true });
2842
3168
  }
2843
3169
  const recordPath = getRecordPath(record.pluginId);
2844
3170
  atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
2845
3171
  }
2846
3172
  function deleteRecord(pluginId) {
2847
3173
  const recordPath = getRecordPath(pluginId);
2848
- if (existsSync11(recordPath)) {
3174
+ if (existsSync12(recordPath)) {
2849
3175
  unlinkSync3(recordPath);
2850
3176
  }
2851
3177
  }
@@ -2854,59 +3180,59 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
2854
3180
  const directories = [];
2855
3181
  if (manifest) {
2856
3182
  const skillsDir = getSkillsRoot(tool, level, installPath);
2857
- if (existsSync11(skillsDir)) {
3183
+ if (existsSync12(skillsDir)) {
2858
3184
  directories.push(skillsDir);
2859
3185
  for (const skillName of manifest.installed_skills || []) {
2860
- const skillPath = join10(skillsDir, skillName);
2861
- if (existsSync11(skillPath) || isSymlink(skillPath)) {
3186
+ const skillPath = join11(skillsDir, skillName);
3187
+ if (existsSync12(skillPath) || isSymlink(skillPath)) {
2862
3188
  files.push(skillPath);
2863
3189
  }
2864
3190
  }
2865
3191
  }
2866
- const agentsDir = join10(configRoot, "agents");
2867
- if (existsSync11(agentsDir)) {
3192
+ const agentsDir = join11(configRoot, "agents");
3193
+ if (existsSync12(agentsDir)) {
2868
3194
  directories.push(agentsDir);
2869
3195
  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)) {
3196
+ const agentPath = join11(agentsDir, agentName);
3197
+ const agentPathMd = join11(agentsDir, agentName + ".md");
3198
+ if (existsSync12(agentPath) || isSymlink(agentPath)) {
2873
3199
  files.push(agentPath);
2874
- } else if (existsSync11(agentPathMd) || isSymlink(agentPathMd)) {
3200
+ } else if (existsSync12(agentPathMd) || isSymlink(agentPathMd)) {
2875
3201
  files.push(agentPathMd);
2876
3202
  }
2877
3203
  }
2878
3204
  }
2879
3205
  }
2880
- const workflowsLink = join10(configRoot, "workflows");
3206
+ const workflowsLink = join11(configRoot, "workflows");
2881
3207
  if (isSymlink(workflowsLink)) {
2882
3208
  files.push(workflowsLink);
2883
3209
  }
2884
- const manifestPath = join10(configRoot, "cannbot-manifest.json");
2885
- if (existsSync11(manifestPath)) {
3210
+ const manifestPath = join11(configRoot, "cannbot-manifest.json");
3211
+ if (existsSync12(manifestPath)) {
2886
3212
  files.push(manifestPath);
2887
3213
  }
2888
- const pluginManifestPath = join10(configRoot, `${pluginId}-manifest.json`);
2889
- if (existsSync11(pluginManifestPath)) {
3214
+ const pluginManifestPath = join11(configRoot, `${pluginId}-manifest.json`);
3215
+ if (existsSync12(pluginManifestPath)) {
2890
3216
  files.push(pluginManifestPath);
2891
3217
  }
2892
3218
  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)) {
3219
+ const configFilePath = level === "project" ? join11(installPath, configFileName) : join11(configRoot, configFileName);
3220
+ if (existsSync12(configFilePath) || isSymlink(configFilePath)) {
2895
3221
  files.push(configFilePath);
2896
3222
  }
2897
3223
  if (level === "project" && configRootConfigLink !== false) {
2898
- const configRootConfigPath = join10(configRoot, configFileName);
2899
- if (configRootConfigPath !== configFilePath && (existsSync11(configRootConfigPath) || isSymlink(configRootConfigPath))) {
3224
+ const configRootConfigPath = join11(configRoot, configFileName);
3225
+ if (configRootConfigPath !== configFilePath && (existsSync12(configRootConfigPath) || isSymlink(configRootConfigPath))) {
2900
3226
  files.push(configRootConfigPath);
2901
3227
  }
2902
3228
  }
2903
3229
  const repoLinks = externalRepoNames && externalRepoNames.length > 0 ? externalRepoNames : ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples", "ops-tensor"];
2904
3230
  for (const repoName of repoLinks) {
2905
- const repoLinkPath = join10(installPath, repoName);
3231
+ const repoLinkPath = join11(installPath, repoName);
2906
3232
  if (isSymlink(repoLinkPath)) {
2907
3233
  files.push(repoLinkPath);
2908
3234
  }
2909
- const repoLinkInConfig = join10(configRoot, repoName);
3235
+ const repoLinkInConfig = join11(configRoot, repoName);
2910
3236
  if (isSymlink(repoLinkInConfig)) {
2911
3237
  files.push(repoLinkInConfig);
2912
3238
  }
@@ -2924,15 +3250,15 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
2924
3250
  };
2925
3251
  }
2926
3252
  function getSkillRecordPath() {
2927
- return join10(getInstallsDir(), "skills.json");
3253
+ return join11(getInstallsDir(), "skills.json");
2928
3254
  }
2929
3255
  function readSkillRecord() {
2930
3256
  const recordPath = getSkillRecordPath();
2931
- if (!existsSync11(recordPath)) {
3257
+ if (!existsSync12(recordPath)) {
2932
3258
  return {};
2933
3259
  }
2934
3260
  try {
2935
- const content = readFileSync7(recordPath, "utf-8");
3261
+ const content = readFileSync8(recordPath, "utf-8");
2936
3262
  return JSON.parse(content);
2937
3263
  } catch {
2938
3264
  logger.warn(t("skill_record_corrupted"));
@@ -2941,8 +3267,8 @@ function readSkillRecord() {
2941
3267
  }
2942
3268
  function writeSkillRecord(record) {
2943
3269
  const installsDir = getInstallsDir();
2944
- if (!existsSync11(installsDir)) {
2945
- mkdirSync4(installsDir, { recursive: true });
3270
+ if (!existsSync12(installsDir)) {
3271
+ mkdirSync5(installsDir, { recursive: true });
2946
3272
  }
2947
3273
  const recordPath = getSkillRecordPath();
2948
3274
  atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
@@ -3004,19 +3330,19 @@ function getInstalledSkills(tool, level, installPath, allowFsScan = true) {
3004
3330
  const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
3005
3331
  const skillsDir = getSkillsRoot(tool, level);
3006
3332
  const fromRecord = recordedSkills.filter((skillId) => {
3007
- const skillPath = join10(skillsDir, skillId);
3008
- return existsSync11(skillPath) || isSymlink(skillPath);
3333
+ const skillPath = join11(skillsDir, skillId);
3334
+ return existsSync12(skillPath) || isSymlink(skillPath);
3009
3335
  });
3010
3336
  if (!allowFsScan) {
3011
3337
  return fromRecord;
3012
3338
  }
3013
3339
  const fromFs = [];
3014
- if (existsSync11(skillsDir)) {
3340
+ if (existsSync12(skillsDir)) {
3015
3341
  try {
3016
- const entries = readdirSync4(skillsDir);
3342
+ const entries = readdirSync5(skillsDir);
3017
3343
  for (const entry of entries) {
3018
3344
  if (entry === "." || entry === "..") continue;
3019
- const entryPath = join10(skillsDir, entry);
3345
+ const entryPath = join11(skillsDir, entry);
3020
3346
  if (isSymlink(entryPath) && !fromRecord.includes(entry)) {
3021
3347
  fromFs.push(entry);
3022
3348
  }
@@ -3035,15 +3361,347 @@ function getLastBatchSkills(tool, level, installPath) {
3035
3361
  const lastBatch = entry.batches[entry.batches.length - 1];
3036
3362
  const skillsDir = getSkillsRoot(tool, level, installPath);
3037
3363
  return lastBatch.skills.filter((skillId) => {
3038
- const skillPath = join10(skillsDir, skillId);
3039
- return existsSync11(skillPath) || isSymlink(skillPath);
3364
+ const skillPath = join11(skillsDir, skillId);
3365
+ return existsSync12(skillPath) || isSymlink(skillPath);
3040
3366
  });
3041
3367
  }
3042
3368
 
3369
+ // src/core/cannbot-registry.ts
3370
+ init_config();
3371
+ init_paths();
3372
+ init_logger();
3373
+ init_i18n();
3374
+ var CANNBOT_TOOL_LAYOUTS = {
3375
+ opencode: { configDirs: [".opencode"], defaultConfigDir: ".opencode" },
3376
+ codex: { configDirs: [".codex"], defaultConfigDir: ".codex" },
3377
+ claude: { configDirs: [".claude"], defaultConfigDir: ".claude" },
3378
+ trae: { configDirs: [".traecli", ".marscode", ".trae", ".trae-cn"], defaultConfigDir: ".trae" }
3379
+ };
3380
+ var CANNBOT_SUPPORTED_TOOLS = Object.keys(CANNBOT_TOOL_LAYOUTS);
3381
+ var CANNBOT_REGISTRY_SCHEMA_VERSION = 2;
3382
+ var REGISTRY_FILE_NAME = "cannbot-plugin.json";
3383
+ function toolConfigDir(target, tool) {
3384
+ const layout = CANNBOT_TOOL_LAYOUTS[tool];
3385
+ if (!layout) return join12(target, `.${tool}`);
3386
+ return layout.configDirs.map((name) => join12(target, name)).find((path) => existsSync13(path)) ?? join12(target, layout.defaultConfigDir);
3387
+ }
3388
+ function findCannbotRegistryPath(target, tool) {
3389
+ const configDir = toolConfigDir(target, tool);
3390
+ const registryPath = join12(configDir, REGISTRY_FILE_NAME);
3391
+ return existsSync13(registryPath) ? registryPath : null;
3392
+ }
3393
+ function readCannbotRegistry(target, tool) {
3394
+ const registryPath = findCannbotRegistryPath(target, tool);
3395
+ if (!registryPath) return null;
3396
+ let registry;
3397
+ try {
3398
+ registry = JSON.parse(readFileSync9(registryPath, "utf-8"));
3399
+ } catch {
3400
+ logger.warn(t("cannbot_registry_corrupted").replace("{file}", registryPath));
3401
+ return null;
3402
+ }
3403
+ if (typeof registry.schemaVersion !== "number" || !Array.isArray(registry.plugins)) {
3404
+ logger.warn(t("cannbot_registry_corrupted").replace("{file}", registryPath));
3405
+ return null;
3406
+ }
3407
+ return {
3408
+ registryPath,
3409
+ registry,
3410
+ schemaSupported: registry.schemaVersion === CANNBOT_REGISTRY_SCHEMA_VERSION
3411
+ };
3412
+ }
3413
+ function toInstalledEntry(target, registryPath, record) {
3414
+ return {
3415
+ id: record.plugin,
3416
+ version: record.pluginVersion || "",
3417
+ tool: record.tool,
3418
+ target,
3419
+ registryPath,
3420
+ skillsCount: Array.isArray(record.skills) ? record.skills.length : 0,
3421
+ agentsCount: Array.isArray(record.agents) ? record.agents.length : 0,
3422
+ record
3423
+ };
3424
+ }
3425
+ function scanCannbotInstalled(target) {
3426
+ const root = resolve(target || process.cwd());
3427
+ const installed = [];
3428
+ for (const tool of CANNBOT_SUPPORTED_TOOLS) {
3429
+ const result = readCannbotRegistry(root, tool);
3430
+ if (!result) continue;
3431
+ for (const record of result.registry.plugins) {
3432
+ if (!record?.plugin) continue;
3433
+ installed.push(toInstalledEntry(root, result.registryPath, record));
3434
+ }
3435
+ }
3436
+ return installed;
3437
+ }
3438
+ function removeMarkerBlock(content, pluginName) {
3439
+ const start = `<!-- cannbot:${pluginName}:start -->`;
3440
+ const end = `<!-- cannbot:${pluginName}:end -->`;
3441
+ const pattern = new RegExp(
3442
+ `${start.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`,
3443
+ "g"
3444
+ );
3445
+ return content.replace(pattern, "");
3446
+ }
3447
+ function countMarkerBlocks(content, pluginName) {
3448
+ const start = `<!-- cannbot:${pluginName}:start -->`;
3449
+ return content.split(start).length - 1;
3450
+ }
3451
+ function removeIfSafe(pathToRemove, allowedBase) {
3452
+ const resolved = resolve(pathToRemove);
3453
+ if (resolved !== allowedBase && !resolved.startsWith(allowedBase + sep2)) {
3454
+ return "skipped";
3455
+ }
3456
+ try {
3457
+ const stat = lstatSync2(resolved);
3458
+ if (stat.isDirectory()) {
3459
+ rmSync3(resolved, { recursive: true, force: true });
3460
+ } else {
3461
+ unlinkSync4(resolved);
3462
+ }
3463
+ return "removed";
3464
+ } catch {
3465
+ return "missing";
3466
+ }
3467
+ }
3468
+ function removeEmptyDirUpward(dirPath, stopAt) {
3469
+ let current = resolve(dirPath);
3470
+ const stop = resolve(stopAt);
3471
+ let removedAny = false;
3472
+ while (current.startsWith(stop + sep2)) {
3473
+ try {
3474
+ const entries = readdirSync6(current);
3475
+ if (entries.length > 0) break;
3476
+ rmdirSync(current);
3477
+ removedAny = true;
3478
+ } catch {
3479
+ break;
3480
+ }
3481
+ current = dirname6(current);
3482
+ }
3483
+ return removedAny;
3484
+ }
3485
+ function cleanHookSettings(hookSettingsPath, target, pluginId) {
3486
+ const resolved = resolve(target, hookSettingsPath);
3487
+ if (!existsSync13(resolved)) return;
3488
+ let settings;
3489
+ try {
3490
+ settings = JSON.parse(readFileSync9(resolved, "utf-8"));
3491
+ } catch {
3492
+ return;
3493
+ }
3494
+ if (!settings || typeof settings !== "object" || !settings.hooks) return;
3495
+ const pluginRootFragment = `.cannbot/plugins/${pluginId}`;
3496
+ const cleanHooks = (hooks) => {
3497
+ if (Array.isArray(hooks)) {
3498
+ return hooks.filter((entry) => !JSON.stringify(entry).includes(pluginRootFragment));
3499
+ }
3500
+ if (hooks && typeof hooks === "object") {
3501
+ const result = {};
3502
+ for (const [key, value] of Object.entries(hooks)) {
3503
+ const cleaned = cleanHooks(value);
3504
+ if (Array.isArray(cleaned) ? cleaned.length > 0 : cleaned !== void 0) {
3505
+ result[key] = cleaned;
3506
+ }
3507
+ }
3508
+ return result;
3509
+ }
3510
+ return hooks;
3511
+ };
3512
+ try {
3513
+ const cleaned = cleanHooks(settings.hooks);
3514
+ if (Array.isArray(cleaned) ? cleaned.length === 0 : Object.keys(cleaned).length === 0) {
3515
+ delete settings.hooks;
3516
+ } else {
3517
+ settings.hooks = cleaned;
3518
+ }
3519
+ writeFileSync3(resolved, `${JSON.stringify(settings, null, 2)}
3520
+ `);
3521
+ } catch {
3522
+ }
3523
+ }
3524
+ function cleanCodexNativePlugin(record, pluginId) {
3525
+ const native = record.nativePlugin;
3526
+ if (!native || typeof native !== "object") return;
3527
+ if (native.pluginRoot) {
3528
+ try {
3529
+ rmSync3(native.pluginRoot, { recursive: true, force: true });
3530
+ logger.step(` ${t("uninstall_remove_label")}: ${native.pluginRoot}`);
3531
+ } catch {
3532
+ }
3533
+ }
3534
+ if (native.marketplacePath && existsSync13(native.marketplacePath)) {
3535
+ try {
3536
+ const marketplace = JSON.parse(readFileSync9(native.marketplacePath, "utf-8"));
3537
+ if (Array.isArray(marketplace?.plugins)) {
3538
+ marketplace.plugins = marketplace.plugins.filter(
3539
+ (p) => !(p && (p.name === pluginId || p.name === native.marketplaceName))
3540
+ );
3541
+ if (marketplace.plugins.length === 0) {
3542
+ unlinkSync4(native.marketplacePath);
3543
+ } else {
3544
+ writeFileSync3(native.marketplacePath, `${JSON.stringify(marketplace, null, 2)}
3545
+ `);
3546
+ }
3547
+ }
3548
+ } catch {
3549
+ }
3550
+ }
3551
+ }
3552
+ function uninstallCannbotPlugin(pluginId, target) {
3553
+ const result = {
3554
+ success: false,
3555
+ pluginId,
3556
+ removedFiles: 0,
3557
+ removedDirs: 0,
3558
+ errors: []
3559
+ };
3560
+ const root = resolve(target || process.cwd());
3561
+ let read = null;
3562
+ let record;
3563
+ for (const tool of CANNBOT_SUPPORTED_TOOLS) {
3564
+ const candidate = readCannbotRegistry(root, tool);
3565
+ if (!candidate) continue;
3566
+ const found = candidate.registry.plugins.find((r) => r?.plugin === pluginId);
3567
+ if (found) {
3568
+ read = candidate;
3569
+ record = found;
3570
+ break;
3571
+ }
3572
+ }
3573
+ if (!read || !record) {
3574
+ result.errors.push(t("cannbot_uninstall_no_record"));
3575
+ return result;
3576
+ }
3577
+ if (!read.schemaSupported) {
3578
+ result.errors.push(
3579
+ t("cannbot_registry_schema_unsupported").replace("{version}", String(read.registry.schemaVersion))
3580
+ );
3581
+ return result;
3582
+ }
3583
+ const registryPath = read.registryPath;
3584
+ const allowedBase = root;
3585
+ for (const relPath of [...record.skills || [], ...record.agents || []]) {
3586
+ const absolute = join12(root, relPath);
3587
+ if (removeIfSafe(absolute, allowedBase) === "removed") {
3588
+ result.removedFiles++;
3589
+ removeEmptyDirUpward(dirname6(absolute), allowedBase);
3590
+ }
3591
+ }
3592
+ if (record.instructions) {
3593
+ const instructionsPath = join12(root, record.instructions);
3594
+ const resolved = resolve(instructionsPath);
3595
+ if (resolved.startsWith(allowedBase + sep2) && existsSync13(resolved)) {
3596
+ try {
3597
+ const content = readFileSync9(resolved, "utf-8");
3598
+ const cleaned = removeMarkerBlock(content, pluginId);
3599
+ if (cleaned.trim().length === 0) {
3600
+ unlinkSync4(resolved);
3601
+ result.removedFiles++;
3602
+ } else if (cleaned !== content) {
3603
+ writeFileSync3(resolved, cleaned);
3604
+ }
3605
+ } catch (error) {
3606
+ result.errors.push(error instanceof Error ? error.message : t("error_unknown"));
3607
+ }
3608
+ }
3609
+ }
3610
+ if (record.assets) {
3611
+ if (removeIfSafe(join12(root, record.assets), allowedBase) === "removed") {
3612
+ result.removedDirs++;
3613
+ removeEmptyDirUpward(join12(root, ".cannbot", "plugins"), allowedBase);
3614
+ }
3615
+ }
3616
+ const siblings = scanCannbotInstalled(root).map((e) => e.record).filter((r) => r.plugin !== pluginId);
3617
+ const siblingUses = (rel) => siblings.some((r) => [r.permissions, r.settings, r.hookSettings].filter(Boolean).includes(rel));
3618
+ if (record.permissions && !siblingUses(record.permissions)) {
3619
+ if (removeIfSafe(join12(root, record.permissions), allowedBase) === "removed") {
3620
+ result.removedDirs++;
3621
+ }
3622
+ }
3623
+ if (record.settings && !siblingUses(record.settings)) {
3624
+ if (removeIfSafe(join12(root, record.settings), allowedBase) === "removed") {
3625
+ result.removedFiles++;
3626
+ }
3627
+ }
3628
+ if (record.hookSettings) {
3629
+ cleanHookSettings(record.hookSettings, root, pluginId);
3630
+ }
3631
+ for (const relDep of record.dependencies || []) {
3632
+ if (removeIfSafe(join12(root, relDep), allowedBase) === "removed") {
3633
+ result.removedDirs++;
3634
+ }
3635
+ }
3636
+ if ((record.dependencies || []).length > 0) {
3637
+ removeEmptyDirUpward(join12(root, ".cannbot", "dependencies", pluginId), allowedBase);
3638
+ }
3639
+ cleanCodexNativePlugin(record, pluginId);
3640
+ if (record.nativePlugin && typeof record.nativePlugin === "object" && record.nativePlugin.package) {
3641
+ logger.warn(t("cannbot_uninstall_native_hint").replace("{spec}", String(record.nativePlugin.package)));
3642
+ }
3643
+ try {
3644
+ const registry = JSON.parse(readFileSync9(registryPath, "utf-8"));
3645
+ registry.plugins = registry.plugins.filter((r) => r?.plugin !== pluginId);
3646
+ if (registry.plugins.length === 0) {
3647
+ unlinkSync4(registryPath);
3648
+ removeEmptyDirUpward(dirname6(registryPath), allowedBase);
3649
+ } else {
3650
+ writeFileSync3(registryPath, `${JSON.stringify(registry, null, 2)}
3651
+ `);
3652
+ }
3653
+ } catch (error) {
3654
+ result.errors.push(error instanceof Error ? error.message : t("error_unknown"));
3655
+ }
3656
+ if (CANNBOT_TOOL_LAYOUTS[record.tool]) {
3657
+ const configDir = dirname6(registryPath);
3658
+ const layoutRoots = [join12(configDir, "agents"), join12(configDir, "skills")];
3659
+ if (record.tool === "opencode" || record.tool === "codex") {
3660
+ layoutRoots.push(join12(root, ".agents", "skills"), join12(root, ".agents"));
3661
+ }
3662
+ for (const dirPath of layoutRoots) {
3663
+ if (removeEmptyDirUpward(dirPath, allowedBase)) {
3664
+ result.removedDirs++;
3665
+ }
3666
+ }
3667
+ }
3668
+ deleteRecord(pluginId);
3669
+ removeInstalledPlugin(pluginId);
3670
+ result.success = result.errors.length === 0;
3671
+ return result;
3672
+ }
3673
+ function findOrphanedDelegatedRecords(target) {
3674
+ const orphans = [];
3675
+ const installed = new Set(scanCannbotInstalled(target).map((e) => e.id));
3676
+ const installsDir = join12(getCannbotConfigDir(), "installs");
3677
+ if (!existsSync13(installsDir)) return orphans;
3678
+ for (const file of readdirSync6(installsDir)) {
3679
+ if (!file.endsWith(".json") || file === "skills.json") continue;
3680
+ const pluginId = file.replace(/\.json$/, "");
3681
+ try {
3682
+ const rec = JSON.parse(readFileSync9(join12(installsDir, file), "utf-8"));
3683
+ if (rec?.kind !== "delegated") continue;
3684
+ if (!installed.has(pluginId)) {
3685
+ orphans.push(pluginId);
3686
+ }
3687
+ } catch {
3688
+ }
3689
+ }
3690
+ return orphans;
3691
+ }
3692
+
3693
+ // src/core/installer.ts
3694
+ init_registry();
3695
+ init_manifest();
3696
+ init_paths();
3697
+ import { existsSync as existsSync16, readFileSync as readFileSync11 } from "fs";
3698
+ import { join as join15, basename as basename2 } from "path";
3699
+ import { execa as execa5, execaSync as execaSync2 } from "execa";
3700
+
3043
3701
  // src/core/backup.ts
3044
3702
  init_manifest();
3045
- import { existsSync as existsSync12, copyFileSync, readdirSync as readdirSync5, unlinkSync as unlinkSync4 } from "fs";
3046
- import { join as join11 } from "path";
3703
+ import { existsSync as existsSync14, copyFileSync, readdirSync as readdirSync7, unlinkSync as unlinkSync5 } from "fs";
3704
+ import { join as join13 } from "path";
3047
3705
  init_registry();
3048
3706
  init_paths();
3049
3707
  function detectCurrentPlugin(configRoot, tool, installPath) {
@@ -3064,9 +3722,9 @@ function detectCurrentPlugin(configRoot, tool, installPath) {
3064
3722
  }
3065
3723
  }
3066
3724
  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))) {
3725
+ const configRootAgentsFile = join13(configRoot, agentsFileName);
3726
+ const installPathAgentsFile = installPath ? join13(installPath, agentsFileName) : null;
3727
+ if (!existsSync14(configRootAgentsFile) && !(installPathAgentsFile && existsSync14(installPathAgentsFile))) {
3070
3728
  return null;
3071
3729
  }
3072
3730
  const allRecords = readAllRecords();
@@ -3084,13 +3742,13 @@ function detectCurrentPlugin(configRoot, tool, installPath) {
3084
3742
  return null;
3085
3743
  }
3086
3744
  function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
3087
- const agentsFile = join11(configRoot, getAgentsFileName(tool));
3088
- if (!existsSync12(agentsFile)) {
3745
+ const agentsFile = join13(configRoot, getAgentsFileName(tool));
3746
+ if (!existsSync14(agentsFile)) {
3089
3747
  return null;
3090
3748
  }
3091
3749
  const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
3092
3750
  const backupFileName = `${getAgentsFileName(tool)}.cannbot-backup.${fromPluginId}.${timestamp}`;
3093
- const backupPath = join11(configRoot, backupFileName);
3751
+ const backupPath = join13(configRoot, backupFileName);
3094
3752
  try {
3095
3753
  copyFileSync(agentsFile, backupPath);
3096
3754
  return {
@@ -3104,10 +3762,10 @@ function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
3104
3762
  }
3105
3763
  }
3106
3764
  function findBackups(configRoot) {
3107
- if (!existsSync12(configRoot)) {
3765
+ if (!existsSync14(configRoot)) {
3108
3766
  return [];
3109
3767
  }
3110
- const files = readdirSync5(configRoot);
3768
+ const files = readdirSync7(configRoot);
3111
3769
  const backups = [];
3112
3770
  for (const file of files) {
3113
3771
  if (file.includes(".cannbot-backup.")) {
@@ -3120,7 +3778,7 @@ function findBackups(configRoot) {
3120
3778
  const timestamp = pluginIdAndTime.substring(lastDotIndex + 1);
3121
3779
  const plugin = findPlugin(pluginId);
3122
3780
  backups.push({
3123
- filePath: join11(configRoot, file),
3781
+ filePath: join13(configRoot, file),
3124
3782
  pluginId,
3125
3783
  pluginName: plugin?.displayName || pluginId,
3126
3784
  backupTime: timestamp
@@ -3133,7 +3791,7 @@ function findBackups(configRoot) {
3133
3791
  }
3134
3792
  function restoreBackup(backupPath, configRoot, tool) {
3135
3793
  try {
3136
- const agentsFile = join11(configRoot, getAgentsFileName(tool));
3794
+ const agentsFile = join13(configRoot, getAgentsFileName(tool));
3137
3795
  copyFileSync(backupPath, agentsFile);
3138
3796
  return true;
3139
3797
  } catch {
@@ -3142,8 +3800,8 @@ function restoreBackup(backupPath, configRoot, tool) {
3142
3800
  }
3143
3801
  function deleteBackup(backupPath) {
3144
3802
  try {
3145
- if (existsSync12(backupPath)) {
3146
- unlinkSync4(backupPath);
3803
+ if (existsSync14(backupPath)) {
3804
+ unlinkSync5(backupPath);
3147
3805
  return true;
3148
3806
  }
3149
3807
  return false;
@@ -3216,9 +3874,42 @@ init_paths();
3216
3874
  init_fs_helpers();
3217
3875
  init_logger();
3218
3876
  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";
3877
+ 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";
3878
+ import { join as join14, basename } from "path";
3879
+ import { execa as execa4 } from "execa";
3880
+ var ISSUE_HANDLER_ID = "gitcode-issue-handler";
3881
+ var ISSUE_HANDLER_CONFIG_FILES = ["classify_config.yaml", "operator_owners.yaml"];
3882
+ function shouldInitializeIssueHandlerConfig(level, installedSkills) {
3883
+ return level === "project" && installedSkills.includes(ISSUE_HANDLER_ID);
3884
+ }
3885
+ function instantiateIssueHandlerConfig(repoPath, targetRepoPath) {
3886
+ const configDir = join14(targetRepoPath, ".cannbot", ISSUE_HANDLER_ID, "config");
3887
+ const legacyConfigDir = targetRepoPath;
3888
+ try {
3889
+ mkdirSync6(configDir, { recursive: true });
3890
+ } catch (error) {
3891
+ logger.warn(`issue-handler config directory creation failed: ${error instanceof Error ? error.message : String(error)}`);
3892
+ return;
3893
+ }
3894
+ for (const fileName of ISSUE_HANDLER_CONFIG_FILES) {
3895
+ const targetPath = join14(configDir, fileName);
3896
+ if (existsSync15(targetPath) || isSymlink(targetPath)) continue;
3897
+ const legacyPath = join14(legacyConfigDir, fileName);
3898
+ const templatePath = join14(repoPath, "infra", ISSUE_HANDLER_ID, "assets", `${fileName}.template`);
3899
+ const sourcePath = existsSync15(legacyPath) ? legacyPath : templatePath;
3900
+ if (!existsSync15(sourcePath)) {
3901
+ logger.warn(`issue-handler config source not found: ${sourcePath}`);
3902
+ continue;
3903
+ }
3904
+ try {
3905
+ copyFileSync2(sourcePath, targetPath, fsConstants.COPYFILE_EXCL);
3906
+ } catch (error) {
3907
+ if (error?.code !== "EEXIST") {
3908
+ logger.warn(`issue-handler config install failed for ${fileName}: ${error instanceof Error ? error.message : String(error)}`);
3909
+ }
3910
+ }
3911
+ }
3912
+ }
3222
3913
  function installLink(source, target) {
3223
3914
  try {
3224
3915
  const resolved = realpathSync(source);
@@ -3234,7 +3925,7 @@ function installLink(source, target) {
3234
3925
  return "skipped";
3235
3926
  }
3236
3927
  }
3237
- if (existsSync13(target) || isSymlink(target)) {
3928
+ if (existsSync15(target) || isSymlink(target)) {
3238
3929
  removePath(target);
3239
3930
  }
3240
3931
  try {
@@ -3252,19 +3943,19 @@ function installLink(source, target) {
3252
3943
  }
3253
3944
  }
3254
3945
  function installConfigFile(pluginDir, configFile, tool, level, configRoot, installPath, configRootConfigLink) {
3255
- const sourcePath = join12(pluginDir, configFile);
3256
- if (!existsSync13(sourcePath)) return;
3946
+ const sourcePath = join14(pluginDir, configFile);
3947
+ if (!existsSync15(sourcePath)) return;
3257
3948
  const agentsFileName = getAgentsFileName(tool);
3258
3949
  const isProjectLevel = level === "project";
3259
3950
  let primaryTarget;
3260
3951
  if (isProjectLevel) {
3261
- primaryTarget = join12(installPath, agentsFileName);
3952
+ primaryTarget = join14(installPath, agentsFileName);
3262
3953
  } else {
3263
- primaryTarget = join12(configRoot, agentsFileName);
3954
+ primaryTarget = join14(configRoot, agentsFileName);
3264
3955
  }
3265
3956
  try {
3266
3957
  const resolvedSource = realpathSync(sourcePath);
3267
- if (existsSync13(primaryTarget) || isSymlink(primaryTarget)) {
3958
+ if (existsSync15(primaryTarget) || isSymlink(primaryTarget)) {
3268
3959
  removePath(primaryTarget);
3269
3960
  }
3270
3961
  try {
@@ -3279,10 +3970,10 @@ function installConfigFile(pluginDir, configFile, tool, level, configRoot, insta
3279
3970
  } catch (e) {
3280
3971
  logger.warn(t("install_config_failed").replace("{error}", e.message));
3281
3972
  }
3282
- if (isProjectLevel && primaryTarget !== join12(configRoot, agentsFileName)) {
3973
+ if (isProjectLevel && primaryTarget !== join14(configRoot, agentsFileName)) {
3283
3974
  if (configRootConfigLink === false) return;
3284
- const configRootTarget = join12(configRoot, agentsFileName);
3285
- if (existsSync13(configRootTarget) || isSymlink(configRootTarget)) {
3975
+ const configRootTarget = join14(configRoot, agentsFileName);
3976
+ if (existsSync15(configRootTarget) || isSymlink(configRootTarget)) {
3286
3977
  removePath(configRootTarget);
3287
3978
  }
3288
3979
  try {
@@ -3297,15 +3988,15 @@ function installConfigFile(pluginDir, configFile, tool, level, configRoot, insta
3297
3988
  async function installExternalRepos(repos, repoPath, configRoot, installPath, level) {
3298
3989
  if (!repos || repos.length === 0) return;
3299
3990
  for (const repo of repos) {
3300
- const targetDir = join12(repoPath, repo.dir);
3301
- const gitDir = join12(targetDir, ".git");
3302
- if (existsSync13(gitDir)) {
3991
+ const targetDir = join14(repoPath, repo.dir);
3992
+ const gitDir = join14(targetDir, ".git");
3993
+ if (existsSync15(gitDir)) {
3303
3994
  try {
3304
- await execa3("git", ["pull", "--quiet"], { cwd: targetDir, timeout: 12e4 });
3995
+ await execa4("git", ["pull", "--quiet"], { cwd: targetDir, timeout: 12e4 });
3305
3996
  } catch {
3306
3997
  logger.warn(t("external_repo_update_failed").replace("{dir}", repo.dir));
3307
3998
  }
3308
- } else if (!existsSync13(targetDir)) {
3999
+ } else if (!existsSync15(targetDir)) {
3309
4000
  const args = ["clone"];
3310
4001
  if (repo.depth) {
3311
4002
  args.push("--depth", String(repo.depth));
@@ -3315,11 +4006,11 @@ async function installExternalRepos(repos, repoPath, configRoot, installPath, le
3315
4006
  }
3316
4007
  args.push(repo.url, targetDir);
3317
4008
  try {
3318
- await execa3("git", args, { timeout: 12e4 });
4009
+ await execa4("git", args, { timeout: 12e4 });
3319
4010
  } catch {
3320
4011
  logger.warn(t("install_clone_failed").replace("{url}", repo.url).replace("{dir}", repo.dir));
3321
4012
  try {
3322
- rmSync3(targetDir, { recursive: true, force: true });
4013
+ rmSync4(targetDir, { recursive: true, force: true });
3323
4014
  } catch {
3324
4015
  }
3325
4016
  continue;
@@ -3329,9 +4020,9 @@ async function installExternalRepos(repos, repoPath, configRoot, installPath, le
3329
4020
  const resolved = realpathSync(targetDir);
3330
4021
  const shouldConfigRootLink = repo.configRootLink !== false && (level === "global" || repo.configRootLink === true);
3331
4022
  if (shouldConfigRootLink) {
3332
- const configRootLink = join12(configRoot, linkName);
4023
+ const configRootLink = join14(configRoot, linkName);
3333
4024
  try {
3334
- if (existsSync13(configRootLink) || isSymlink(configRootLink)) {
4025
+ if (existsSync15(configRootLink) || isSymlink(configRootLink)) {
3335
4026
  removePath(configRootLink);
3336
4027
  }
3337
4028
  try {
@@ -3346,9 +4037,9 @@ async function installExternalRepos(repos, repoPath, configRoot, installPath, le
3346
4037
  }
3347
4038
  }
3348
4039
  if (level === "project") {
3349
- const projectLink = join12(installPath, linkName);
4040
+ const projectLink = join14(installPath, linkName);
3350
4041
  try {
3351
- if (existsSync13(projectLink) || isSymlink(projectLink)) {
4042
+ if (existsSync15(projectLink) || isSymlink(projectLink)) {
3352
4043
  removePath(projectLink);
3353
4044
  }
3354
4045
  try {
@@ -3365,12 +4056,12 @@ async function installExternalRepos(repos, repoPath, configRoot, installPath, le
3365
4056
  }
3366
4057
  }
3367
4058
  function installWorkflows(pluginDir, configRoot) {
3368
- const workflowsDir = join12(pluginDir, "workflows");
3369
- if (!existsSync13(workflowsDir)) return;
3370
- const targetPath = join12(configRoot, "workflows");
4059
+ const workflowsDir = join14(pluginDir, "workflows");
4060
+ if (!existsSync15(workflowsDir)) return;
4061
+ const targetPath = join14(configRoot, "workflows");
3371
4062
  try {
3372
4063
  const resolved = realpathSync(workflowsDir);
3373
- if (existsSync13(targetPath) || isSymlink(targetPath)) {
4064
+ if (existsSync15(targetPath) || isSymlink(targetPath)) {
3374
4065
  removePath(targetPath);
3375
4066
  }
3376
4067
  try {
@@ -3396,36 +4087,36 @@ function writeCannbotManifest(configRoot, pluginId, version, level, tool, skills
3396
4087
  brand_dir: configRoot,
3397
4088
  install_time: (/* @__PURE__ */ new Date()).toISOString()
3398
4089
  };
3399
- const manifestPath = join12(configRoot, `${pluginId}-manifest.json`);
3400
- writeFileSync2(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
4090
+ const manifestPath = join14(configRoot, `${pluginId}-manifest.json`);
4091
+ writeFileSync4(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
3401
4092
  return manifest;
3402
4093
  }
3403
4094
  async function installViaManifest(plugin, repoPath, tool, level, installPath) {
3404
4095
  const cwd = installPath || process.cwd();
3405
4096
  const configRoot = getConfigRoot(tool, level, installPath);
3406
- const pluginDir = join12(repoPath, plugin.dir);
4097
+ const pluginDir = join14(repoPath, plugin.dir);
3407
4098
  try {
3408
4099
  const skillsDir = getSkillsRoot(tool, level, cwd);
3409
- const agentsDir = join12(configRoot, "agents");
3410
- mkdirSync5(skillsDir, { recursive: true });
3411
- mkdirSync5(agentsDir, { recursive: true });
4100
+ const agentsDir = join14(configRoot, "agents");
4101
+ mkdirSync6(skillsDir, { recursive: true });
4102
+ mkdirSync6(agentsDir, { recursive: true });
3412
4103
  const installedSkills = [];
3413
4104
  const installedAgents = [];
3414
4105
  for (const source of plugin.installSkills || []) {
3415
- const sourceDir = join12(repoPath, source.dir);
3416
- if (!existsSync13(sourceDir)) {
4106
+ const sourceDir = join14(repoPath, source.dir);
4107
+ if (!existsSync15(sourceDir)) {
3417
4108
  logger.warn(t("install_skill_source_not_found").replace("{dir}", source.dir));
3418
4109
  continue;
3419
4110
  }
3420
4111
  for (const skillEntry of source.skills) {
3421
4112
  const skillName = typeof skillEntry === "string" ? skillEntry : skillEntry.name;
3422
4113
  const skillAlias = typeof skillEntry === "string" ? skillName : skillEntry.as || skillName;
3423
- const skillPath = join12(sourceDir, skillName);
3424
- if (!existsSync13(skillPath)) {
4114
+ const skillPath = join14(sourceDir, skillName);
4115
+ if (!existsSync15(skillPath)) {
3425
4116
  logger.warn(t("install_skill_not_found").replace("{name}", skillName).replace("{dir}", source.dir));
3426
4117
  continue;
3427
4118
  }
3428
- const targetPath = join12(skillsDir, skillAlias);
4119
+ const targetPath = join14(skillsDir, skillAlias);
3429
4120
  const result = installLink(skillPath, targetPath);
3430
4121
  if (result !== "failed") {
3431
4122
  installedSkills.push(skillAlias);
@@ -3437,20 +4128,20 @@ async function installViaManifest(plugin, repoPath, tool, level, installPath) {
3437
4128
  for (const agentName of plugin.installAgents || []) {
3438
4129
  if (tool === "codex") {
3439
4130
  const baseName = agentName.replace(/\.md$/, "");
3440
- const tomlSource = join12(pluginDir, "agents", "codex", `${baseName}.toml`);
3441
- if (!existsSync13(tomlSource)) {
4131
+ const tomlSource = join14(pluginDir, "agents", "codex", `${baseName}.toml`);
4132
+ if (!existsSync15(tomlSource)) {
3442
4133
  logger.warn(t("install_agent_codex_toml_missing").replace("{name}", baseName).replace("{path}", tomlSource));
3443
4134
  continue;
3444
4135
  }
3445
- const canonicalMd = join12(pluginDir, "agents", `${baseName}.md`);
3446
- const canonicalPath = existsSync13(canonicalMd) ? realpathSync(canonicalMd) : canonicalMd;
3447
- const targetPath2 = join12(agentsDir, `${baseName}.toml`);
4136
+ const canonicalMd = join14(pluginDir, "agents", `${baseName}.md`);
4137
+ const canonicalPath = existsSync15(canonicalMd) ? realpathSync(canonicalMd) : canonicalMd;
4138
+ const targetPath2 = join14(agentsDir, `${baseName}.toml`);
3448
4139
  try {
3449
- const content = readFileSync8(tomlSource, "utf-8").split("__CANNBOT_AGENT_SOURCE__").join(canonicalPath);
3450
- if (existsSync13(targetPath2) || isSymlink(targetPath2)) {
4140
+ const content = readFileSync10(tomlSource, "utf-8").split("__CANNBOT_AGENT_SOURCE__").join(canonicalPath);
4141
+ if (existsSync15(targetPath2) || isSymlink(targetPath2)) {
3451
4142
  removePath(targetPath2);
3452
4143
  }
3453
- writeFileSync2(targetPath2, content, "utf-8");
4144
+ writeFileSync4(targetPath2, content, "utf-8");
3454
4145
  installedAgents.push(`${baseName}.toml`);
3455
4146
  } catch {
3456
4147
  logger.warn(t("install_agent_failed").replace("{name}", `${baseName}.toml`));
@@ -3458,12 +4149,12 @@ async function installViaManifest(plugin, repoPath, tool, level, installPath) {
3458
4149
  continue;
3459
4150
  }
3460
4151
  const agentFile = agentName.endsWith(".md") ? agentName : `${agentName}.md`;
3461
- const agentPath = join12(pluginDir, "agents", agentFile);
3462
- if (!existsSync13(agentPath)) {
4152
+ const agentPath = join14(pluginDir, "agents", agentFile);
4153
+ if (!existsSync15(agentPath)) {
3463
4154
  logger.warn(t("install_agent_not_found").replace("{name}", agentFile));
3464
4155
  continue;
3465
4156
  }
3466
- const targetPath = join12(agentsDir, agentFile);
4157
+ const targetPath = join14(agentsDir, agentFile);
3467
4158
  const result = installLink(agentPath, targetPath);
3468
4159
  if (result !== "failed") {
3469
4160
  installedAgents.push(agentFile);
@@ -3483,6 +4174,9 @@ async function installViaManifest(plugin, repoPath, tool, level, installPath) {
3483
4174
  installedSkills,
3484
4175
  installedAgents
3485
4176
  );
4177
+ if (shouldInitializeIssueHandlerConfig(level, installedSkills)) {
4178
+ instantiateIssueHandlerConfig(repoPath, cwd);
4179
+ }
3486
4180
  return {
3487
4181
  success: true,
3488
4182
  skillsCount: installedSkills.length,
@@ -3509,7 +4203,7 @@ function findShell() {
3509
4203
  for (const cmd of candidates) {
3510
4204
  try {
3511
4205
  const detectCmd = process.platform === "win32" ? "where" : "which";
3512
- execaSync(detectCmd, [cmd], { timeout: 3e3 });
4206
+ execaSync2(detectCmd, [cmd], { timeout: 3e3 });
3513
4207
  return cmd;
3514
4208
  } catch {
3515
4209
  }
@@ -3518,7 +4212,7 @@ function findShell() {
3518
4212
  }
3519
4213
  function scriptSupportsTool(scriptPath, tool) {
3520
4214
  try {
3521
- const scriptContent = readFileSync9(scriptPath, "utf-8");
4215
+ const scriptContent = readFileSync11(scriptPath, "utf-8");
3522
4216
  return new RegExp(`\\b${tool}\\b`).test(scriptContent);
3523
4217
  } catch {
3524
4218
  return true;
@@ -3536,12 +4230,12 @@ async function installPlugin(opts) {
3536
4230
  warnings: []
3537
4231
  };
3538
4232
  }
3539
- const pluginDir = join13(opts.repoPath, plugin.dir);
4233
+ const pluginDir = join15(opts.repoPath, plugin.dir);
3540
4234
  const cwd = opts.installPath || process.cwd();
3541
4235
  const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
3542
- const agentsFile = join13(configRoot, getAgentsFileName(opts.tool));
4236
+ const agentsFile = join15(configRoot, getAgentsFileName(opts.tool));
3543
4237
  let backupInfo = null;
3544
- if (existsSync14(agentsFile)) {
4238
+ if (existsSync16(agentsFile)) {
3545
4239
  const currentPlugin = detectCurrentPlugin(configRoot, opts.tool, opts.installPath);
3546
4240
  if (currentPlugin && currentPlugin.pluginId !== opts.pluginId) {
3547
4241
  let choice = "overwrite";
@@ -3636,8 +4330,8 @@ async function installPlugin(opts) {
3636
4330
  };
3637
4331
  }
3638
4332
  }
3639
- const scriptPath = join13(pluginDir, plugin.script);
3640
- if (!existsSync14(scriptPath)) {
4333
+ const scriptPath = join15(pluginDir, plugin.script);
4334
+ if (!existsSync16(scriptPath)) {
3641
4335
  return {
3642
4336
  success: false,
3643
4337
  pluginId: opts.pluginId,
@@ -3679,7 +4373,7 @@ async function installPlugin(opts) {
3679
4373
  stdio: "pipe",
3680
4374
  input: opts.yes ? "y\n" : void 0
3681
4375
  };
3682
- await execa4(shell, [scriptPath, ...args], execOptions);
4376
+ await execa5(shell, [scriptPath, ...args], execOptions);
3683
4377
  const manifest = readManifest(configRoot);
3684
4378
  let skillsCount = 0;
3685
4379
  let agentsCount = 0;
@@ -3735,6 +4429,188 @@ async function installPlugin(opts) {
3735
4429
  }
3736
4430
  }
3737
4431
 
4432
+ // src/core/cannbot-delegate.ts
4433
+ init_config();
4434
+ init_paths();
4435
+ init_fs_helpers();
4436
+ init_i18n();
4437
+ var DELEGATE_TOOLS = ["opencode", "claude", "trae", "codex"];
4438
+ function boundaryErrors(tool, level) {
4439
+ const errors = [];
4440
+ if (!DELEGATE_TOOLS.includes(tool)) {
4441
+ errors.push(
4442
+ t("cannbot_tool_unsupported").replace("{tool}", tool).replace("{tools}", DELEGATE_TOOLS.join(", "))
4443
+ );
4444
+ }
4445
+ if (level !== "project") {
4446
+ errors.push(t("cannbot_level_unsupported"));
4447
+ }
4448
+ return errors;
4449
+ }
4450
+ function migrateSymlinkedInstructions(target, tool) {
4451
+ const warnings = [];
4452
+ const fileName = getConfigFileName(tool);
4453
+ const candidates = [join16(target, fileName), join16(target, "AGENTS.md"), join16(target, "CLAUDE.md")];
4454
+ for (const configPath of candidates) {
4455
+ if (!isSymlink(configPath)) continue;
4456
+ try {
4457
+ const content = readFileSync12(configPath, "utf-8");
4458
+ const backupPath = `${configPath}.cannbot-migration.${Date.now()}`;
4459
+ copyFileSync3(configPath, backupPath);
4460
+ unlinkSync6(configPath);
4461
+ writeFileSync5(configPath, content);
4462
+ warnings.push(t("cannbot_migrated_symlink").replace("{file}", fileName));
4463
+ } catch {
4464
+ warnings.push(t("cannbot_migrate_symlink_failed").replace("{file}", fileName));
4465
+ }
4466
+ break;
4467
+ }
4468
+ return warnings;
4469
+ }
4470
+ function detectDualSkillCopies(tool, target, skillNames) {
4471
+ const duplicates = [];
4472
+ if (tool !== "opencode") return duplicates;
4473
+ const legacyRoot = getSkillsRoot("opencode", "project", target);
4474
+ if (!existsSync17(legacyRoot)) return duplicates;
4475
+ let legacyEntries = [];
4476
+ try {
4477
+ legacyEntries = readdirSync8(legacyRoot);
4478
+ } catch {
4479
+ return duplicates;
4480
+ }
4481
+ for (const name of skillNames) {
4482
+ if (legacyEntries.includes(name)) {
4483
+ duplicates.push(name);
4484
+ }
4485
+ }
4486
+ return duplicates;
4487
+ }
4488
+ function skillNameFromPath(rel) {
4489
+ const parts = rel.split("/");
4490
+ return parts[parts.length - 1] || rel;
4491
+ }
4492
+ async function delegateInstall(options) {
4493
+ const { pluginId, tool, level } = options;
4494
+ const target = resolve2(options.target || process.cwd());
4495
+ const displayName = options.displayName || pluginId;
4496
+ const result = {
4497
+ success: false,
4498
+ pluginId,
4499
+ skillsCount: 0,
4500
+ agentsCount: 0,
4501
+ errors: [],
4502
+ warnings: []
4503
+ };
4504
+ result.errors.push(...boundaryErrors(tool, level));
4505
+ if (result.errors.length > 0) {
4506
+ return result;
4507
+ }
4508
+ if (!hasNodeRuntime()) {
4509
+ result.errors.push(t("cannbot_node_required"));
4510
+ return result;
4511
+ }
4512
+ try {
4513
+ await ensureCannbotInstaller();
4514
+ } catch (error) {
4515
+ result.errors.push(
4516
+ t("cannbot_installer_failed").replace(
4517
+ "{error}",
4518
+ error instanceof Error ? error.message : t("error_unknown")
4519
+ )
4520
+ );
4521
+ return result;
4522
+ }
4523
+ if (!discoverCannbotPlugins().some((p) => p.id === pluginId)) {
4524
+ try {
4525
+ await ensureCannbotInstaller({ force: true });
4526
+ } catch {
4527
+ }
4528
+ if (!discoverCannbotPlugins().some((p) => p.id === pluginId)) {
4529
+ result.errors.push(t("cannbot_plugin_not_found").replace("{plugin}", pluginId));
4530
+ return result;
4531
+ }
4532
+ }
4533
+ result.warnings.push(...migrateSymlinkedInstructions(target, tool));
4534
+ try {
4535
+ await execa6(
4536
+ "node",
4537
+ [getCannbotBinPath(), "install", pluginId, "--tool", tool, "--target", target],
4538
+ {
4539
+ cwd: target,
4540
+ stdout: "inherit",
4541
+ stderr: "inherit",
4542
+ timeout: 6e5
4543
+ }
4544
+ );
4545
+ } catch (error) {
4546
+ const exitCode = error.exitCode;
4547
+ result.errors.push(
4548
+ t("cannbot_delegate_failed").replace(
4549
+ "{code}",
4550
+ exitCode !== void 0 ? String(exitCode) : "?"
4551
+ )
4552
+ );
4553
+ return result;
4554
+ }
4555
+ const registryRead = readCannbotRegistry(target, tool);
4556
+ const record = registryRead?.registry.plugins.find((r) => r?.plugin === pluginId);
4557
+ if (!record) {
4558
+ result.errors.push(t("cannbot_registry_missing"));
4559
+ return result;
4560
+ }
4561
+ result.skillsCount = Array.isArray(record.skills) ? record.skills.length : 0;
4562
+ result.agentsCount = Array.isArray(record.agents) ? record.agents.length : 0;
4563
+ const files = [];
4564
+ const directories = [];
4565
+ for (const rel of [...record.skills || [], ...record.agents || []]) {
4566
+ files.push(join16(target, rel));
4567
+ }
4568
+ if (record.assets) {
4569
+ directories.push(join16(target, record.assets));
4570
+ }
4571
+ if (record.permissions) {
4572
+ directories.push(join16(target, record.permissions));
4573
+ }
4574
+ const registryPath = findCannbotRegistryPath(target, tool);
4575
+ writeRecord({
4576
+ pluginId,
4577
+ displayName,
4578
+ tool,
4579
+ level: "project",
4580
+ installPath: target,
4581
+ configRoot: registryPath ? join16(registryPath, "..") : target,
4582
+ installTime: (/* @__PURE__ */ new Date()).toISOString(),
4583
+ files,
4584
+ directories,
4585
+ kind: "delegated"
4586
+ });
4587
+ addInstalledPlugin(pluginId);
4588
+ const duplicates = detectDualSkillCopies(
4589
+ tool,
4590
+ target,
4591
+ (record.skills || []).map(skillNameFromPath)
4592
+ );
4593
+ if (duplicates.length > 0) {
4594
+ result.warnings.push(t("cannbot_dual_skill_copy").replace("{skills}", duplicates.join(", ")));
4595
+ }
4596
+ result.success = true;
4597
+ return result;
4598
+ }
4599
+ async function installPluginRouted(options) {
4600
+ const isCannbot = options.source === "cannbot" || options.plugin?.source === "cannbot";
4601
+ if (isCannbot) {
4602
+ return delegateInstall({
4603
+ pluginId: options.pluginId,
4604
+ displayName: options.plugin?.displayName,
4605
+ tool: options.tool,
4606
+ level: options.level,
4607
+ target: options.installPath || process.cwd(),
4608
+ yes: options.yes
4609
+ });
4610
+ }
4611
+ return installPlugin(options);
4612
+ }
4613
+
3738
4614
  // src/commands/init.ts
3739
4615
  init_registry();
3740
4616
 
@@ -3744,8 +4620,8 @@ init_fs_helpers();
3744
4620
  init_constants();
3745
4621
  init_logger();
3746
4622
  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";
4623
+ 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";
4624
+ import { join as join17, dirname as dirname7, basename as basename3, isAbsolute } from "path";
3749
4625
  import { select as select3, checkbox, Separator as Separator2 } from "@inquirer/prompts";
3750
4626
  import chalk5 from "chalk";
3751
4627
  import Table from "cli-table3";
@@ -3755,8 +4631,8 @@ async function installSkills(skillIds, tool, level, repoPath) {
3755
4631
  const configRoot = getConfigRoot(tool, level);
3756
4632
  const skillsDir = getSkillsRoot(tool, level);
3757
4633
  const installPath = level === "project" ? process.cwd() : configRoot;
3758
- if (!existsSync15(skillsDir)) {
3759
- mkdirSync6(skillsDir, { recursive: true });
4634
+ if (!existsSync18(skillsDir)) {
4635
+ mkdirSync7(skillsDir, { recursive: true });
3760
4636
  }
3761
4637
  const results = [];
3762
4638
  const installedIds = [];
@@ -3767,8 +4643,8 @@ async function installSkills(skillIds, tool, level, repoPath) {
3767
4643
  continue;
3768
4644
  }
3769
4645
  const sourcePath = resolveSkillSourcePath(skill, repoPath);
3770
- const targetPath = join14(skillsDir, skillId);
3771
- if (!existsSync15(sourcePath)) {
4646
+ const targetPath = join17(skillsDir, skillId);
4647
+ if (!existsSync18(sourcePath)) {
3772
4648
  results.push({ skillId, success: false, error: t("error_source_not_found").replace("{path}", sourcePath) });
3773
4649
  continue;
3774
4650
  }
@@ -3790,7 +4666,7 @@ async function installSkills(skillIds, tool, level, repoPath) {
3790
4666
  continue;
3791
4667
  }
3792
4668
  }
3793
- if (existsSync15(targetPath) || isSymlink(targetPath)) {
4669
+ if (existsSync18(targetPath) || isSymlink(targetPath)) {
3794
4670
  removePath(targetPath);
3795
4671
  }
3796
4672
  try {
@@ -3814,6 +4690,9 @@ async function installSkills(skillIds, tool, level, repoPath) {
3814
4690
  }
3815
4691
  if (installedIds.length > 0) {
3816
4692
  addSkillsToRecord(installedIds, tool, level, installPath);
4693
+ if (shouldInitializeIssueHandlerConfig(level, installedIds)) {
4694
+ instantiateIssueHandlerConfig(repoPath, installPath);
4695
+ }
3817
4696
  }
3818
4697
  return results;
3819
4698
  }
@@ -3824,9 +4703,9 @@ async function uninstallSkills(skillIds, tool, level) {
3824
4703
  const results = [];
3825
4704
  const removedIds = [];
3826
4705
  for (const skillId of skillIds) {
3827
- const targetPath = join14(skillsDir, skillId);
4706
+ const targetPath = join17(skillsDir, skillId);
3828
4707
  try {
3829
- if (existsSync15(targetPath) || isSymlink(targetPath)) {
4708
+ if (existsSync18(targetPath) || isSymlink(targetPath)) {
3830
4709
  removePath(targetPath);
3831
4710
  results.push({ skillId, success: true });
3832
4711
  removedIds.push(skillId);
@@ -3842,17 +4721,17 @@ async function uninstallSkills(skillIds, tool, level) {
3842
4721
  if (removedIds.length > 0) {
3843
4722
  removeSkillsFromRecord(removedIds, tool, level, installPath);
3844
4723
  }
3845
- if (existsSync15(skillsDir)) {
4724
+ if (existsSync18(skillsDir)) {
3846
4725
  try {
3847
- const entries = readdirSync6(skillsDir);
4726
+ const entries = readdirSync9(skillsDir);
3848
4727
  if (entries.length === 0) {
3849
- rmSync4(skillsDir, { recursive: true });
4728
+ rmSync5(skillsDir, { recursive: true });
3850
4729
  logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
3851
4730
  if (tool === "codex") {
3852
- const agentsRoot = dirname5(skillsDir);
4731
+ const agentsRoot = dirname7(skillsDir);
3853
4732
  try {
3854
- if (existsSync15(agentsRoot) && readdirSync6(agentsRoot).length === 0) {
3855
- rmSync4(agentsRoot, { recursive: true });
4733
+ if (existsSync18(agentsRoot) && readdirSync9(agentsRoot).length === 0) {
4734
+ rmSync5(agentsRoot, { recursive: true });
3856
4735
  logger.step(` ${t("uninstall_clean_empty_dir")}: ${basename3(agentsRoot)}/`);
3857
4736
  }
3858
4737
  } catch {
@@ -4261,12 +5140,12 @@ function listAllSkills() {
4261
5140
  }
4262
5141
  function resolveSkillSourcePath(skill, repoPath) {
4263
5142
  if (skill.filePath) {
4264
- const absPath = isAbsolute(skill.filePath) ? skill.filePath : join14(repoPath, skill.filePath);
4265
- if (existsSync15(absPath)) {
4266
- return dirname5(absPath);
5143
+ const absPath = isAbsolute(skill.filePath) ? skill.filePath : join17(repoPath, skill.filePath);
5144
+ if (existsSync18(absPath)) {
5145
+ return dirname7(absPath);
4267
5146
  }
4268
5147
  }
4269
- return join14(repoPath, skill.source, skill.id);
5148
+ return join17(repoPath, skill.source, skill.id);
4270
5149
  }
4271
5150
 
4272
5151
  // src/ui/display.ts
@@ -4290,9 +5169,10 @@ function printPluginList(plugins, installed) {
4290
5169
  plugins.forEach((plugin, index) => {
4291
5170
  const isInstalled = installed.has(plugin.id);
4292
5171
  const status = isInstalled ? chalk6.green(`\u2713 ${t("status_installed")}`) : chalk6.dim(`\u2014`);
5172
+ const badge = plugin.source === "cannbot" ? chalk6.magenta(" [cannbot]") + (plugin.version ? chalk6.dim(` v${plugin.version}`) : "") : "";
4293
5173
  table.push([
4294
5174
  String(index + 1),
4295
- plugin.displayName,
5175
+ plugin.displayName + badge,
4296
5176
  status,
4297
5177
  plugin.description || chalk6.dim("\u2014")
4298
5178
  ]);
@@ -4367,13 +5247,13 @@ init_config();
4367
5247
  init_paths();
4368
5248
  init_theme();
4369
5249
  init_constants();
4370
- import { existsSync as existsSync16, readFileSync as readFileSync10 } from "fs";
4371
- import { join as join15 } from "path";
5250
+ import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
5251
+ import { join as join18 } from "path";
4372
5252
  async function initCommand() {
4373
5253
  const cwd = process.cwd();
4374
- if (existsSync16(join15(cwd, "package.json")) && (() => {
5254
+ if (existsSync19(join18(cwd, "package.json")) && (() => {
4375
5255
  try {
4376
- const pkg = JSON.parse(readFileSync10(join15(cwd, "package.json"), "utf-8"));
5256
+ const pkg = JSON.parse(readFileSync13(join18(cwd, "package.json"), "utf-8"));
4377
5257
  return pkg.name === "@cannbot-ai/install-helper";
4378
5258
  } catch {
4379
5259
  return false;
@@ -4394,6 +5274,7 @@ async function initCommand() {
4394
5274
  } catch {
4395
5275
  spinner.warn(t("loading_skills_list_failed"));
4396
5276
  }
5277
+ await ensureCannbotDiscovery();
4397
5278
  let step = 0;
4398
5279
  let mode = "plugin";
4399
5280
  while (true) {
@@ -4480,11 +5361,12 @@ async function pluginInstallFlow() {
4480
5361
  const progress = `[${i + 1}/${total}]`;
4481
5362
  const pluginSpinner = createSpinner(`${progress} ${t("install_progress")} ${displayName}...`);
4482
5363
  pluginSpinner.start();
4483
- const result = await installPlugin({
5364
+ const result = await installPluginRouted({
4484
5365
  pluginId,
4485
5366
  tool: answers.tool,
4486
5367
  level: answers.level,
4487
- repoPath
5368
+ repoPath,
5369
+ plugin
4488
5370
  });
4489
5371
  if (result.success) {
4490
5372
  pluginSpinner.succeed(
@@ -4496,6 +5378,9 @@ async function pluginInstallFlow() {
4496
5378
  `${progress} ${displayName} \u2014 ${result.errors.join(", ")}`
4497
5379
  );
4498
5380
  }
5381
+ for (const warning of result.warnings || []) {
5382
+ logger.warn(warning);
5383
+ }
4499
5384
  results.push(result);
4500
5385
  }
4501
5386
  const summary = results.map((result, index) => ({
@@ -4603,9 +5488,24 @@ async function listCommand() {
4603
5488
  await repoManager.ensureRepoAndScan();
4604
5489
  } catch {
4605
5490
  }
5491
+ await ensureCannbotDiscovery();
4606
5492
  const plugins = getAllPlugins();
4607
5493
  const installed = scanInstalled();
4608
5494
  const installedMap = new Map(installed.map((p) => [p.id, p]));
5495
+ for (const entry of scanCannbotInstalled()) {
5496
+ if (!installedMap.has(entry.id)) {
5497
+ installedMap.set(entry.id, {
5498
+ id: entry.id,
5499
+ displayName: entry.id,
5500
+ tool: entry.tool,
5501
+ level: "project",
5502
+ skillsCount: entry.skillsCount,
5503
+ agentsCount: entry.agentsCount,
5504
+ installTime: "",
5505
+ configRoot: entry.registryPath
5506
+ });
5507
+ }
5508
+ }
4609
5509
  printPluginList(plugins, installedMap);
4610
5510
  }
4611
5511
 
@@ -4613,11 +5513,12 @@ async function listCommand() {
4613
5513
  init_detector();
4614
5514
  init_registry();
4615
5515
  init_manifest();
5516
+ import chalk8 from "chalk";
5517
+ import { existsSync as existsSync20, readdirSync as readdirSync10, lstatSync as lstatSync3, unlinkSync as unlinkSync7, mkdirSync as mkdirSync8, readFileSync as readFileSync14 } from "fs";
5518
+ import { join as join19 } from "path";
5519
+ import { homedir as homedir3 } from "os";
4616
5520
  init_paths();
4617
5521
  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
5522
  async function doctorCommand(options = {}) {
4622
5523
  console.log();
4623
5524
  console.log(chalk8.bold(` ${t("doctor_title")}`));
@@ -4672,10 +5573,10 @@ async function doctorCommand(options = {}) {
4672
5573
  for (const { configRoot, tool, level } of configRootsToCheck) {
4673
5574
  const skillsDir = getSkillsRoot(tool, level);
4674
5575
  const agentsDir = getAgentsDir(configRoot);
4675
- if (existsSync17(skillsDir)) {
5576
+ if (existsSync20(skillsDir)) {
4676
5577
  const brokenLinks = checkBrokenLinks(skillsDir);
4677
5578
  if (brokenLinks === 0) {
4678
- const count = readdirSync7(skillsDir).length;
5579
+ const count = readdirSync10(skillsDir).length;
4679
5580
  console.log(chalk8.green(" \u2713") + ` ${skillsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
4680
5581
  } else {
4681
5582
  console.log(chalk8.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
@@ -4689,15 +5590,15 @@ async function doctorCommand(options = {}) {
4689
5590
  } else {
4690
5591
  console.log(chalk8.dim(" \u2014") + ` ${skillsDir} \u2014 ${t("doctor_not_exist")}`);
4691
5592
  if (options.fix) {
4692
- mkdirSync7(skillsDir, { recursive: true });
5593
+ mkdirSync8(skillsDir, { recursive: true });
4693
5594
  fixes++;
4694
5595
  console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
4695
5596
  }
4696
5597
  }
4697
- if (existsSync17(agentsDir)) {
5598
+ if (existsSync20(agentsDir)) {
4698
5599
  const brokenLinks = checkBrokenLinks(agentsDir);
4699
5600
  if (brokenLinks === 0) {
4700
- const count = readdirSync7(agentsDir).length;
5601
+ const count = readdirSync10(agentsDir).length;
4701
5602
  console.log(chalk8.green(" \u2713") + ` ${agentsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
4702
5603
  } else {
4703
5604
  console.log(chalk8.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
@@ -4711,7 +5612,7 @@ async function doctorCommand(options = {}) {
4711
5612
  } else {
4712
5613
  console.log(chalk8.dim(" \u2014") + ` ${agentsDir} \u2014 ${t("doctor_not_exist")}`);
4713
5614
  if (options.fix) {
4714
- mkdirSync7(agentsDir, { recursive: true });
5615
+ mkdirSync8(agentsDir, { recursive: true });
4715
5616
  fixes++;
4716
5617
  console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", agentsDir)}`);
4717
5618
  }
@@ -4721,13 +5622,16 @@ async function doctorCommand(options = {}) {
4721
5622
  console.log(chalk8.bold(` ${t("doctor_config")}`));
4722
5623
  for (const { configRoot, tool, level } of configRootsToCheck) {
4723
5624
  const configFile = getConfigFileName(tool);
4724
- const configPath = level === "project" ? join16(configRoot, "..", configFile) : join16(configRoot, configFile);
4725
- if (existsSync17(configPath)) {
5625
+ const configPath = level === "project" ? join19(configRoot, "..", configFile) : join19(configRoot, configFile);
5626
+ if (existsSync20(configPath)) {
4726
5627
  console.log(chalk8.green(" \u2713") + ` ${configPath} ${t("doctor_config_exists")}`);
4727
5628
  } else {
4728
5629
  console.log(chalk8.dim(" \u2014") + ` ${configPath} ${t("doctor_config_not_exist")}`);
4729
5630
  }
4730
5631
  }
5632
+ const cannbotResult = doctorCannbotSection(options);
5633
+ warnings += cannbotResult.warnings;
5634
+ fixes += cannbotResult.fixes;
4731
5635
  console.log();
4732
5636
  console.log(chalk8.dim(" " + "\u2500".repeat(46)));
4733
5637
  if (options.fix && fixes > 0) {
@@ -4741,16 +5645,107 @@ async function doctorCommand(options = {}) {
4741
5645
  }
4742
5646
  console.log();
4743
5647
  }
5648
+ function doctorCannbotSection(options) {
5649
+ let warnings = 0;
5650
+ let fixes = 0;
5651
+ console.log();
5652
+ console.log(chalk8.bold(` ${t("doctor_cannbot")}`));
5653
+ if (hasNodeRuntime()) {
5654
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_cannbot_node_ok")}`);
5655
+ } else {
5656
+ console.log(chalk8.yellow(" \u26A0") + ` ${t("doctor_cannbot_node_missing")}`);
5657
+ warnings++;
5658
+ }
5659
+ const installerConfig = loadCannbotInstallerConfig();
5660
+ if (isCannbotInstallerReady()) {
5661
+ const version = getCannbotInstallerVersion() || "?";
5662
+ console.log(
5663
+ chalk8.green(" \u2713") + ` ${installerConfig.package} v${version} ${chalk8.dim(`(${installerConfig.channel})`)}`
5664
+ );
5665
+ } else {
5666
+ console.log(
5667
+ chalk8.dim(" \u2014") + ` ${installerConfig.package} \u2014 ${t("doctor_not_installed")}`
5668
+ );
5669
+ }
5670
+ const cannbotInstalled = scanCannbotInstalled();
5671
+ if (cannbotInstalled.length === 0) {
5672
+ console.log(chalk8.dim(" \u2014") + ` cannbot-plugin.json \u2014 ${t("doctor_not_exist")}`);
5673
+ } else {
5674
+ for (const entry of cannbotInstalled) {
5675
+ console.log(
5676
+ chalk8.green(" \u2713") + ` ${entry.id} ${chalk8.dim(`(${entry.tool}, ${entry.skillsCount} skills, ${entry.agentsCount} agents)`)}`
5677
+ );
5678
+ const missing = (entry.record.skills || []).filter((rel) => {
5679
+ const p = join19(entry.target, rel);
5680
+ return !existsSync20(p) && !lstatTryIsSymlink(p);
5681
+ });
5682
+ if (missing.length > 0) {
5683
+ console.log(
5684
+ chalk8.yellow(" \u26A0") + ` ${entry.id}: ${t("doctor_cannbot_missing_skills").replace("{count}", String(missing.length))}`
5685
+ );
5686
+ warnings++;
5687
+ }
5688
+ }
5689
+ }
5690
+ for (const fileName of ["AGENTS.md", "CLAUDE.md"]) {
5691
+ const filePath = join19(process.cwd(), fileName);
5692
+ if (!existsSync20(filePath)) continue;
5693
+ try {
5694
+ const content = readFileSync14(filePath, "utf-8");
5695
+ for (const entry of cannbotInstalled) {
5696
+ const blocks = countMarkerBlocks(content, entry.id);
5697
+ if (blocks > 1) {
5698
+ console.log(
5699
+ chalk8.yellow(" \u26A0") + ` ${fileName}: ${t("doctor_cannbot_marker_duplicate").replace("{plugin}", entry.id)}`
5700
+ );
5701
+ warnings++;
5702
+ }
5703
+ }
5704
+ const starts = (content.match(/<!-- cannbot:[\w.-]+:start -->/g) || []).length;
5705
+ const ends = (content.match(/<!-- cannbot:[\w.-]+:end -->/g) || []).length;
5706
+ if (starts !== ends) {
5707
+ console.log(
5708
+ chalk8.yellow(" \u26A0") + ` ${fileName}: ${t("doctor_cannbot_marker_unpaired").replace("{start}", String(starts)).replace("{end}", String(ends))}`
5709
+ );
5710
+ warnings++;
5711
+ }
5712
+ } catch {
5713
+ }
5714
+ }
5715
+ const orphans = findOrphanedDelegatedRecords();
5716
+ for (const pluginId of orphans) {
5717
+ console.log(
5718
+ chalk8.yellow(" \u26A0") + ` ${t("doctor_cannbot_orphan_record").replace("{plugin}", pluginId)}`
5719
+ );
5720
+ warnings++;
5721
+ if (options.fix) {
5722
+ try {
5723
+ unlinkSync7(join19(homedir3(), ".cannbot", "installs", `${pluginId}.json`));
5724
+ fixes++;
5725
+ console.log(chalk8.green(" \u2713") + ` ${t("doctor_fixed").replace("{count}", "1")}: ${pluginId}`);
5726
+ } catch {
5727
+ }
5728
+ }
5729
+ }
5730
+ return { warnings, fixes };
5731
+ }
5732
+ function lstatTryIsSymlink(path) {
5733
+ try {
5734
+ return lstatSync3(path).isSymbolicLink();
5735
+ } catch {
5736
+ return false;
5737
+ }
5738
+ }
4744
5739
  function checkBrokenLinks(dir) {
4745
5740
  let broken = 0;
4746
5741
  try {
4747
- const entries = readdirSync7(dir);
5742
+ const entries = readdirSync10(dir);
4748
5743
  for (const entry of entries) {
4749
- const fullPath = join16(dir, entry);
5744
+ const fullPath = join19(dir, entry);
4750
5745
  try {
4751
- const stats = lstatSync2(fullPath);
5746
+ const stats = lstatSync3(fullPath);
4752
5747
  if (stats.isSymbolicLink()) {
4753
- if (!existsSync17(fullPath)) {
5748
+ if (!existsSync20(fullPath)) {
4754
5749
  broken++;
4755
5750
  }
4756
5751
  }
@@ -4765,14 +5760,14 @@ function checkBrokenLinks(dir) {
4765
5760
  function fixBrokenLinks(dir) {
4766
5761
  let fixed = 0;
4767
5762
  try {
4768
- const entries = readdirSync7(dir);
5763
+ const entries = readdirSync10(dir);
4769
5764
  for (const entry of entries) {
4770
- const fullPath = join16(dir, entry);
5765
+ const fullPath = join19(dir, entry);
4771
5766
  try {
4772
- const stats = lstatSync2(fullPath);
5767
+ const stats = lstatSync3(fullPath);
4773
5768
  if (stats.isSymbolicLink()) {
4774
- if (!existsSync17(fullPath)) {
4775
- unlinkSync5(fullPath);
5769
+ if (!existsSync20(fullPath)) {
5770
+ unlinkSync7(fullPath);
4776
5771
  fixed++;
4777
5772
  }
4778
5773
  }
@@ -4786,15 +5781,15 @@ function fixBrokenLinks(dir) {
4786
5781
 
4787
5782
  // src/commands/status.ts
4788
5783
  init_manifest();
4789
- init_i18n();
4790
5784
  import chalk9 from "chalk";
5785
+ init_i18n();
4791
5786
  async function statusCommand() {
4792
5787
  const installed = scanInstalled();
4793
- const installedMap = new Map(installed.map((p) => [p.id, p]));
5788
+ const cannbotInstalled = scanCannbotInstalled();
4794
5789
  console.log();
4795
5790
  console.log(chalk9.bold(` ${t("status_title")}`));
4796
5791
  console.log();
4797
- if (installed.length === 0) {
5792
+ if (installed.length === 0 && cannbotInstalled.length === 0) {
4798
5793
  console.log(chalk9.dim(` ${t("status_none")}`));
4799
5794
  console.log(chalk9.dim(` ${t("status_hint")}`));
4800
5795
  console.log();
@@ -4811,6 +5806,17 @@ async function statusCommand() {
4811
5806
  console.log(chalk9.dim(` ${t("status_install_time")}: ${inst.installTime}`));
4812
5807
  console.log();
4813
5808
  }
5809
+ for (const entry of cannbotInstalled) {
5810
+ const versionSuffix = entry.version ? chalk9.dim(` v${entry.version}`) : "";
5811
+ console.log(
5812
+ chalk9.green(" \u2713") + ` ${entry.id}${versionSuffix}` + chalk9.magenta(" [cannbot]") + chalk9.dim(` (${entry.tool}, project)`)
5813
+ );
5814
+ console.log(
5815
+ chalk9.dim(` ${entry.skillsCount} skills, ${entry.agentsCount} agents`)
5816
+ );
5817
+ console.log(chalk9.dim(` ${entry.target}`));
5818
+ console.log();
5819
+ }
4814
5820
  }
4815
5821
 
4816
5822
  // src/commands/install.ts
@@ -4823,7 +5829,15 @@ init_config();
4823
5829
  init_paths();
4824
5830
  import { confirm as confirm2 } from "@inquirer/prompts";
4825
5831
  import chalk10 from "chalk";
5832
+ function validateSource(source) {
5833
+ if (!source) return void 0;
5834
+ if (source !== "cannbot" && source !== "skills") {
5835
+ throw new Error(t("error_invalid_source").replace("{source}", source).replace("{sources}", "cannbot, skills"));
5836
+ }
5837
+ return source;
5838
+ }
4826
5839
  async function installCommand(names, options) {
5840
+ const source = validateSource(options.source);
4827
5841
  if (options.list) {
4828
5842
  try {
4829
5843
  const repoManager = createRepositoryManager();
@@ -4988,10 +6002,11 @@ async function installCommand(names, options) {
4988
6002
  } catch {
4989
6003
  scanSpinner.warn(t("loading_skills_list_failed"));
4990
6004
  }
6005
+ await ensureCannbotDiscovery();
4991
6006
  const plugins = [];
4992
6007
  const skills = [];
4993
6008
  for (const name of names) {
4994
- const plugin = findPlugin(name);
6009
+ const plugin = findPlugin(name, { source });
4995
6010
  if (plugin) {
4996
6011
  plugins.push(plugin.id);
4997
6012
  continue;
@@ -5005,7 +6020,8 @@ async function installCommand(names, options) {
5005
6020
  const allPlugins = getAllPlugins();
5006
6021
  logger.info(t("available_plugins") + ":");
5007
6022
  for (const p of allPlugins) {
5008
- logger.step(` ${p.id} (${p.aliases.join(", ")})`);
6023
+ const badge = p.source === "cannbot" ? chalk10.magenta(" [cannbot]") : "";
6024
+ logger.step(` ${p.id}${badge} (${p.aliases.join(", ")})`);
5009
6025
  }
5010
6026
  logger.info(`${t("available_skills")}: ${chalk10.cyan("install-helper install --list")}`);
5011
6027
  return;
@@ -5029,7 +6045,9 @@ async function installCommand(names, options) {
5029
6045
  const pluginsToInstall = [];
5030
6046
  const skippedPlugins = [];
5031
6047
  for (const pluginId of plugins) {
5032
- if (installedSet.has(pluginId) && !options.yes) {
6048
+ const pluginEntry = findPlugin(pluginId, { source });
6049
+ const alreadyInstalled = pluginEntry?.source === "cannbot" ? scanCannbotInstalled().some((e) => e.id === pluginId && e.tool === tool) : installedSet.has(pluginId);
6050
+ if (alreadyInstalled && !options.yes) {
5033
6051
  const plugin = findPlugin(pluginId);
5034
6052
  const displayName = plugin?.displayName || pluginId;
5035
6053
  logger.warn(`${displayName} [${t("install_already_installed")}]`);
@@ -5066,7 +6084,8 @@ async function installCommand(names, options) {
5066
6084
  level,
5067
6085
  repoPath,
5068
6086
  void 0,
5069
- options.yes
6087
+ options.yes,
6088
+ source
5070
6089
  );
5071
6090
  const summary = results.map((result) => {
5072
6091
  const plugin = allPlugins.find((p) => p.id === result.pluginId);
@@ -5118,7 +6137,7 @@ async function installCommand(names, options) {
5118
6137
  logger.blank();
5119
6138
  }
5120
6139
  }
5121
- async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes) {
6140
+ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes, source) {
5122
6141
  const results = [];
5123
6142
  const total = pluginIds.length;
5124
6143
  for (let i = 0; i < pluginIds.length; i++) {
@@ -5126,13 +6145,16 @@ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes
5126
6145
  const progress = `[${i + 1}/${total}]`;
5127
6146
  const spinner = createSpinner(`${progress} ${t("install_progress")} ${pluginId}...`);
5128
6147
  spinner.start();
5129
- const result = await installPlugin({
6148
+ const plugin = findPlugin(pluginId);
6149
+ const result = await installPluginRouted({
5130
6150
  pluginId,
5131
6151
  tool,
5132
6152
  level,
5133
6153
  repoPath,
5134
6154
  installPath,
5135
- yes
6155
+ yes,
6156
+ plugin,
6157
+ source
5136
6158
  });
5137
6159
  if (result.success) {
5138
6160
  spinner.succeed(
@@ -5142,6 +6164,9 @@ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes
5142
6164
  } else {
5143
6165
  spinner.fail(`${progress} ${pluginId} \u2014 ${result.errors.join(", ")}`);
5144
6166
  }
6167
+ for (const warning of result.warnings || []) {
6168
+ logger.warn(warning);
6169
+ }
5145
6170
  results.push(result);
5146
6171
  }
5147
6172
  return results;
@@ -5149,8 +6174,8 @@ async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes
5149
6174
 
5150
6175
  // src/commands/uninstall.ts
5151
6176
  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";
6177
+ import { existsSync as existsSync21, unlinkSync as unlinkSync8, rmdirSync as rmdirSync2, readdirSync as readdirSync11, readFileSync as readFileSync15 } from "fs";
6178
+ import { resolve as resolve3, basename as basename4, sep as sep3, join as join20 } from "path";
5154
6179
  import chalk11 from "chalk";
5155
6180
  import { select as select5, checkbox as checkbox2, Separator as Separator4, confirm as confirm3 } from "@inquirer/prompts";
5156
6181
  init_config();
@@ -5179,10 +6204,16 @@ async function uninstallCommand(names, options) {
5179
6204
  return;
5180
6205
  }
5181
6206
  const plugins = [];
6207
+ const cannbotPlugins = [];
5182
6208
  const skills = [];
6209
+ const cannbotInstalledIds = new Set(scanCannbotInstalled().map((e) => e.id));
5183
6210
  for (const name of names) {
6211
+ if (cannbotInstalledIds.has(name)) {
6212
+ cannbotPlugins.push(name);
6213
+ continue;
6214
+ }
5184
6215
  const plugin = findPlugin(name);
5185
- if (plugin) {
6216
+ if (plugin && plugin.source !== "cannbot") {
5186
6217
  plugins.push(plugin.id);
5187
6218
  continue;
5188
6219
  }
@@ -5201,9 +6232,16 @@ async function uninstallCommand(names, options) {
5201
6232
  }
5202
6233
  logger.error(`${t("uninstall_not_found")}: ${name}`);
5203
6234
  }
6235
+ await uninstallCannbotPlugins(cannbotPlugins);
5204
6236
  await uninstallPlugins(plugins, options.yes || false);
5205
6237
  await uninstallSkillsByName(skills, tool, level);
5206
6238
  }
6239
+ async function uninstallCannbotPlugins(pluginIds) {
6240
+ for (const pluginId of pluginIds) {
6241
+ logger.info(`${t("uninstall_in_progress")} ${pluginId}...`);
6242
+ await uninstallCannbotPluginById(pluginId);
6243
+ }
6244
+ }
5207
6245
  function isRecordedSkill(skillId) {
5208
6246
  const record = readSkillRecord();
5209
6247
  for (const toolName of Object.keys(record)) {
@@ -5221,12 +6259,13 @@ async function uninstallAll(tool, level, yes) {
5221
6259
  const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
5222
6260
  const installedSkills = getInstalledSkills(tool, level, installPath, !yes);
5223
6261
  const installedPlugins = scanInstalled().filter((p) => p.tool === tool && p.level === level);
5224
- const totalItems = installedSkills.length + installedPlugins.length;
6262
+ const cannbotInstalled = level === "project" ? scanCannbotInstalled().filter((e) => e.tool === tool) : [];
6263
+ const totalItems = installedSkills.length + installedPlugins.length + cannbotInstalled.length;
5225
6264
  if (totalItems === 0) {
5226
6265
  logger.warn(t("uninstall_no_installed"));
5227
6266
  return;
5228
6267
  }
5229
- logger.info(t("uninstall_all_summary").replace("{skills}", String(installedSkills.length)).replace("{plugins}", String(installedPlugins.length)));
6268
+ logger.info(t("uninstall_all_summary").replace("{skills}", String(installedSkills.length)).replace("{plugins}", String(installedPlugins.length + cannbotInstalled.length)));
5230
6269
  logger.blank();
5231
6270
  for (const s of installedSkills) {
5232
6271
  logger.step(` \u2022 ${chalk11.cyan(s)}`);
@@ -5234,6 +6273,9 @@ async function uninstallAll(tool, level, yes) {
5234
6273
  for (const p of installedPlugins) {
5235
6274
  logger.step(` \u2022 ${chalk11.cyan(p.displayName)} ${chalk11.dim(`(${p.id})`)}`);
5236
6275
  }
6276
+ for (const c of cannbotInstalled) {
6277
+ logger.step(` \u2022 ${chalk11.cyan(c.id)} ${chalk11.magenta("[cannbot]")}`);
6278
+ }
5237
6279
  logger.blank();
5238
6280
  if (!yes) {
5239
6281
  const confirmed = await confirm3({
@@ -5249,6 +6291,9 @@ async function uninstallAll(tool, level, yes) {
5249
6291
  logger.info(`${t("uninstall_skills_in_progress")} ${t("skill_count_format").replace("{count}", String(installedSkills.length))}...`);
5250
6292
  await uninstallSkills(installedSkills, tool, level);
5251
6293
  }
6294
+ for (const entry of cannbotInstalled) {
6295
+ await uninstallCannbotPluginById(entry.id);
6296
+ }
5252
6297
  if (installedPlugins.length > 0) {
5253
6298
  logger.info(`${t("uninstall_plugin_batch_progress").replace("{count}", String(installedPlugins.length))}...`);
5254
6299
  for (const inst of installedPlugins) {
@@ -5259,6 +6304,18 @@ async function uninstallAll(tool, level, yes) {
5259
6304
  logger.success(t("uninstall_all_done"));
5260
6305
  logger.blank();
5261
6306
  }
6307
+ async function uninstallCannbotPluginById(pluginId) {
6308
+ const result = uninstallCannbotPlugin(pluginId);
6309
+ if (result.success) {
6310
+ logger.success(
6311
+ t("uninstall_summary_format").replace("{name}", pluginId).replace("{files}", String(result.removedFiles)).replace("{dirs}", String(result.removedDirs))
6312
+ );
6313
+ } else {
6314
+ for (const error of result.errors) {
6315
+ logger.error(`${pluginId}: ${error}`);
6316
+ }
6317
+ }
6318
+ }
5262
6319
  async function uninstallRecent(tool, level, yes) {
5263
6320
  const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
5264
6321
  const lastBatch = getLastBatchSkills(tool, level, installPath);
@@ -5399,7 +6456,8 @@ async function interactiveSkillUninstall(tool, level) {
5399
6456
  }
5400
6457
  async function interactivePluginUninstall(tool, level) {
5401
6458
  const installed = scanInstalled().filter((p) => p.tool === tool && p.level === level);
5402
- if (installed.length === 0) {
6459
+ const cannbotInstalled = level === "project" ? scanCannbotInstalled().filter((e) => e.tool === tool) : [];
6460
+ if (installed.length === 0 && cannbotInstalled.length === 0) {
5403
6461
  logger.warn(t("uninstall_no_installed_plugins"));
5404
6462
  return;
5405
6463
  }
@@ -5409,6 +6467,12 @@ async function interactivePluginUninstall(tool, level) {
5409
6467
  value: inst.id,
5410
6468
  checked: false,
5411
6469
  description: `${inst.skillsCount} skills, ${inst.agentsCount} agents`
6470
+ })),
6471
+ ...cannbotInstalled.map((entry) => ({
6472
+ name: `${chalk11.cyan(entry.id)} ${chalk11.magenta("[cannbot]")}`,
6473
+ value: entry.id,
6474
+ checked: false,
6475
+ description: `${entry.skillsCount} skills, ${entry.agentsCount} agents`
5412
6476
  }))
5413
6477
  ];
5414
6478
  printBoxTitle(t("uninstall_interactive_title"));
@@ -5423,21 +6487,29 @@ async function interactivePluginUninstall(tool, level) {
5423
6487
  theme: checkboxTheme,
5424
6488
  pageSize: 15
5425
6489
  });
5426
- const toUninstall = installed.filter((inst) => selected.includes(inst.id));
5427
- if (toUninstall.length === 0) {
6490
+ const toUninstallLegacy = installed.filter((inst) => selected.includes(inst.id));
6491
+ const toUninstallCannbot = cannbotInstalled.filter((entry) => selected.includes(entry.id));
6492
+ if (toUninstallLegacy.length === 0 && toUninstallCannbot.length === 0) {
5428
6493
  logger.info(t("uninstall_nothing_to_uninstall"));
5429
6494
  return;
5430
6495
  }
5431
6496
  const confirmed = await confirm3({
5432
- message: t("uninstall_confirm_prompt").replace("{count}", chalk11.bold(String(toUninstall.length))),
6497
+ message: t("uninstall_confirm_prompt").replace(
6498
+ "{count}",
6499
+ chalk11.bold(String(toUninstallLegacy.length + toUninstallCannbot.length))
6500
+ ),
5433
6501
  default: false
5434
6502
  });
5435
6503
  if (!confirmed) {
5436
6504
  logger.info(t("init_cancelled"));
5437
6505
  return;
5438
6506
  }
5439
- logger.info(`${t("uninstall_plugin_batch_progress").replace("{count}", String(toUninstall.length))}...`);
5440
- for (const inst of toUninstall) {
6507
+ const totalCount = toUninstallLegacy.length + toUninstallCannbot.length;
6508
+ logger.info(`${t("uninstall_plugin_batch_progress").replace("{count}", String(totalCount))}...`);
6509
+ for (const entry of toUninstallCannbot) {
6510
+ await uninstallCannbotPluginById(entry.id);
6511
+ }
6512
+ for (const inst of toUninstallLegacy) {
5441
6513
  await uninstallPluginById(inst.id, true);
5442
6514
  }
5443
6515
  logger.blank();
@@ -5472,11 +6544,11 @@ async function uninstallPluginById(pluginId, batchMode) {
5472
6544
  for (const toolName of VALID_TOOLS) {
5473
6545
  for (const lvl of ["project", "global"]) {
5474
6546
  const configRoot2 = getConfigRoot(toolName, lvl);
5475
- const manifestPath = join17(configRoot2, `${pluginId}-manifest.json`);
5476
- if (!existsSync18(manifestPath)) continue;
6547
+ const manifestPath = join20(configRoot2, `${pluginId}-manifest.json`);
6548
+ if (!existsSync21(manifestPath)) continue;
5477
6549
  let manifest = null;
5478
6550
  try {
5479
- manifest = JSON.parse(readFileSync11(manifestPath, "utf-8"));
6551
+ manifest = JSON.parse(readFileSync15(manifestPath, "utf-8"));
5480
6552
  } catch {
5481
6553
  continue;
5482
6554
  }
@@ -5504,29 +6576,29 @@ async function uninstallPluginById(pluginId, batchMode) {
5504
6576
  }
5505
6577
  logger.info(`${t("uninstall_in_progress")} ${plugin.displayName}...`);
5506
6578
  const configRoot = getConfigRoot(record.tool, record.level);
5507
- const allowedBases = [resolve(configRoot), resolve(record.installPath)];
6579
+ const allowedBases = [resolve3(configRoot), resolve3(record.installPath)];
5508
6580
  if (record.tool === "codex") {
5509
- allowedBases.push(resolve(getSkillsRoot(record.tool, record.level, record.installPath)));
6581
+ allowedBases.push(resolve3(getSkillsRoot(record.tool, record.level, record.installPath)));
5510
6582
  }
5511
6583
  const isSafePath = (p) => {
5512
- const resolved = resolve(p);
5513
- return allowedBases.some((base) => resolved === base || resolved.startsWith(base + sep2));
6584
+ const resolved = resolve3(p);
6585
+ return allowedBases.some((base) => resolved === base || resolved.startsWith(base + sep3));
5514
6586
  };
5515
6587
  let removedFiles = 0;
5516
6588
  let removedDirs = 0;
5517
6589
  const configFileName = record.tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
5518
- const configRootConfigPath = join17(configRoot, configFileName);
5519
- const installPathConfigPath = join17(record.installPath, configFileName);
6590
+ const configRootConfigPath = join20(configRoot, configFileName);
6591
+ const installPathConfigPath = join20(record.installPath, configFileName);
5520
6592
  const extraFiles = [];
5521
- if (record.level === "project" && configRootConfigPath !== installPathConfigPath && !record.files.includes(configRootConfigPath) && (existsSync18(configRootConfigPath) || isSymlink(configRootConfigPath))) {
6593
+ if (record.level === "project" && configRootConfigPath !== installPathConfigPath && !record.files.includes(configRootConfigPath) && (existsSync21(configRootConfigPath) || isSymlink(configRootConfigPath))) {
5522
6594
  extraFiles.push(configRootConfigPath);
5523
6595
  }
5524
6596
  const allFiles = [...record.files, ...extraFiles];
5525
6597
  for (const filePath of allFiles) {
5526
6598
  if (!isSafePath(filePath)) continue;
5527
6599
  try {
5528
- if (existsSync18(filePath) || isSymlink(filePath)) {
5529
- unlinkSync6(filePath);
6600
+ if (existsSync21(filePath) || isSymlink(filePath)) {
6601
+ unlinkSync8(filePath);
5530
6602
  const name = basename4(filePath);
5531
6603
  logger.step(` ${t("uninstall_remove_label")}: ${name}`);
5532
6604
  removedFiles++;
@@ -5537,13 +6609,13 @@ async function uninstallPluginById(pluginId, batchMode) {
5537
6609
  const sortedDirs = [...record.directories].filter(isSafePath).sort((a, b) => b.length - a.length);
5538
6610
  for (const dirPath of sortedDirs) {
5539
6611
  try {
5540
- if (resolve(dirPath) === resolve(configRoot)) {
6612
+ if (resolve3(dirPath) === resolve3(configRoot)) {
5541
6613
  continue;
5542
6614
  }
5543
- if (existsSync18(dirPath)) {
5544
- const entries = readdirSync8(dirPath);
6615
+ if (existsSync21(dirPath)) {
6616
+ const entries = readdirSync11(dirPath);
5545
6617
  if (entries.length === 0) {
5546
- rmdirSync(dirPath);
6618
+ rmdirSync2(dirPath);
5547
6619
  const name = basename4(dirPath);
5548
6620
  logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
5549
6621
  removedDirs++;
@@ -5554,9 +6626,9 @@ async function uninstallPluginById(pluginId, batchMode) {
5554
6626
  }
5555
6627
  if (record.tool === "codex") {
5556
6628
  try {
5557
- const agentsRoot = resolve(getSkillsRoot(record.tool, record.level, record.installPath), "..");
5558
- if (existsSync18(agentsRoot) && readdirSync8(agentsRoot).length === 0) {
5559
- rmdirSync(agentsRoot);
6629
+ const agentsRoot = resolve3(getSkillsRoot(record.tool, record.level, record.installPath), "..");
6630
+ if (existsSync21(agentsRoot) && readdirSync11(agentsRoot).length === 0) {
6631
+ rmdirSync2(agentsRoot);
5560
6632
  logger.step(` ${t("uninstall_clean_empty_dir")}: ${basename4(agentsRoot)}/`);
5561
6633
  removedDirs++;
5562
6634
  }
@@ -5628,15 +6700,20 @@ async function updateCommand(pluginNames, options) {
5628
6700
  updateSpinner.fail(t("repo_clone_failed").replace("{error}", error instanceof Error ? error.message : t("error_unknown")).replace("{url}", "").replace("{dir}", ""));
5629
6701
  return;
5630
6702
  }
6703
+ await ensureCannbotDiscovery();
5631
6704
  const installed = scanInstalled();
6705
+ const cannbotInstalled = scanCannbotInstalled();
5632
6706
  let targets = [];
5633
6707
  if (pluginNames.length === 0) {
5634
- if (installed.length === 0) {
6708
+ if (installed.length === 0 && cannbotInstalled.length === 0) {
5635
6709
  logger.info(t("update_no_plugins"));
5636
6710
  logger.info(t("update_install_hint").replace("{cmd}", chalk12.cyan("install-helper install <plugin>")));
5637
6711
  return;
5638
6712
  }
5639
- targets = installed.map((p) => ({ pluginId: p.id, tool: p.tool, level: p.level }));
6713
+ targets = [
6714
+ ...installed.map((p) => ({ pluginId: p.id, tool: p.tool, level: p.level, source: "skills" })),
6715
+ ...cannbotInstalled.map((e) => ({ pluginId: e.id, tool: e.tool, level: "project", source: "cannbot" }))
6716
+ ];
5640
6717
  } else {
5641
6718
  let defaultTool;
5642
6719
  let defaultLevel;
@@ -5647,20 +6724,25 @@ async function updateCommand(pluginNames, options) {
5647
6724
  defaultLevel = validateLevel(options.level);
5648
6725
  }
5649
6726
  for (const name of pluginNames) {
6727
+ const cannbotMatches = cannbotInstalled.filter((e) => e.id === name);
5650
6728
  const plugin = findPlugin(name);
5651
- if (!plugin) {
6729
+ if (cannbotMatches.length === 0 && !plugin) {
5652
6730
  logger.error(`${t("error_plugin_not_found")}: ${name}`);
5653
6731
  continue;
5654
6732
  }
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 });
6733
+ const legacyMatches = cannbotMatches.length === 0 && plugin && plugin.source !== "cannbot" ? installed.filter((p) => p.id === plugin.id) : [];
6734
+ const matches = [
6735
+ ...cannbotMatches.map((e) => ({ pluginId: e.id, tool: e.tool, level: "project", source: "cannbot" })),
6736
+ ...legacyMatches.map((p) => ({ pluginId: p.id, tool: p.tool, level: p.level, source: "skills" }))
6737
+ ];
6738
+ if (matches.length > 0) {
6739
+ for (const match of matches) {
6740
+ if (defaultTool && match.tool !== defaultTool) continue;
6741
+ if (defaultLevel && match.level !== defaultLevel) continue;
6742
+ targets.push(match);
5661
6743
  }
5662
6744
  } else {
5663
- logger.warn(`${plugin.displayName} ${t("error_not_installed")}, ${t("update_skipped")}`);
6745
+ logger.warn(`${plugin?.displayName || name} ${t("error_not_installed")}, ${t("update_skipped")}`);
5664
6746
  }
5665
6747
  }
5666
6748
  }
@@ -5668,6 +6750,18 @@ async function updateCommand(pluginNames, options) {
5668
6750
  logger.info(t("update_no_plugins"));
5669
6751
  return;
5670
6752
  }
6753
+ if (targets.some((target) => target.source === "cannbot")) {
6754
+ try {
6755
+ await ensureCannbotInstaller({ force: true });
6756
+ } catch (error) {
6757
+ logger.warn(
6758
+ t("cannbot_installer_failed").replace(
6759
+ "{error}",
6760
+ error instanceof Error ? error.message : t("error_unknown")
6761
+ )
6762
+ );
6763
+ }
6764
+ }
5671
6765
  const allPlugins = getAllPlugins();
5672
6766
  const results = [];
5673
6767
  const total = targets.length;
@@ -5678,12 +6772,14 @@ async function updateCommand(pluginNames, options) {
5678
6772
  const progress = `[${i + 1}/${total}]`;
5679
6773
  const pluginSpinner = createSpinner(`${progress} ${t("update_updating")} ${displayName}...`);
5680
6774
  pluginSpinner.start();
5681
- const result = await installPlugin({
6775
+ const result = await installPluginRouted({
5682
6776
  pluginId: target.pluginId,
5683
6777
  tool: target.tool,
5684
6778
  level: target.level,
5685
6779
  repoPath,
5686
- yes: options.yes
6780
+ yes: options.yes,
6781
+ plugin,
6782
+ source: target.source
5687
6783
  });
5688
6784
  if (result.success) {
5689
6785
  pluginSpinner.succeed(
@@ -5694,6 +6790,9 @@ async function updateCommand(pluginNames, options) {
5694
6790
  `${progress} ${displayName} \u2014 ${result.errors.join(", ")}`
5695
6791
  );
5696
6792
  }
6793
+ for (const warning of result.warnings || []) {
6794
+ logger.warn(warning);
6795
+ }
5697
6796
  results.push(result);
5698
6797
  }
5699
6798
  const summary = results.map((result) => {
@@ -5713,8 +6812,8 @@ async function updateCommand(pluginNames, options) {
5713
6812
  // src/commands/info.ts
5714
6813
  init_registry();
5715
6814
  import chalk13 from "chalk";
5716
- import { existsSync as existsSync19, readdirSync as readdirSync9 } from "fs";
5717
- import { join as join18 } from "path";
6815
+ import { existsSync as existsSync22, readdirSync as readdirSync12 } from "fs";
6816
+ import { join as join21 } from "path";
5718
6817
  init_manifest();
5719
6818
  init_i18n();
5720
6819
  async function infoCommand(pluginName) {
@@ -5725,29 +6824,42 @@ async function infoCommand(pluginName) {
5725
6824
  }
5726
6825
  const installed = scanInstalled();
5727
6826
  const inst = installed.find((p) => p.id === plugin.id);
5728
- const isInstalled = !!inst;
6827
+ const cannbotEntry = plugin.source === "cannbot" ? scanCannbotInstalled().find((e) => e.id === plugin.id) : void 0;
6828
+ const isInstalled = !!inst || !!cannbotEntry;
5729
6829
  const statusText = isInstalled ? chalk13.green(`\u2713 ${t("status_installed")}`) : chalk13.dim(`\u2014 ${t("status_not_installed")}`);
5730
6830
  console.log();
5731
6831
  console.log(chalk13.bold(` ${plugin.displayName}`));
6832
+ if (plugin.source === "cannbot") {
6833
+ console.log(chalk13.magenta(" [cannbot]") + chalk13.dim(` v${plugin.version || "?"}`));
6834
+ }
5732
6835
  console.log(chalk13.dim(" " + "\u2500".repeat(40)));
5733
6836
  console.log();
5734
6837
  console.log(` ${chalk13.dim(t("info_description") + ":")} ${plugin.description}`);
5735
6838
  console.log(` ${chalk13.dim("ID:")} ${plugin.id}`);
5736
6839
  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(", ")}`);
6840
+ console.log(` ${chalk13.dim(t("info_skills") + ":")} ${inst || cannbotEntry ? cannbotEntry?.skillsCount ?? inst?.skillsCount : plugin.skills}`);
6841
+ console.log(` ${chalk13.dim(t("info_agents") + ":")} ${inst || cannbotEntry ? cannbotEntry?.agentsCount ?? inst?.agentsCount : plugin.agents}`);
6842
+ if (plugin.source !== "cannbot") {
6843
+ console.log(` ${chalk13.dim(t("info_aliases") + ":")} ${plugin.aliases.join(", ")}`);
6844
+ }
5740
6845
  console.log();
6846
+ if (plugin.source === "cannbot") {
6847
+ if (cannbotEntry) {
6848
+ console.log(` ${chalk13.dim(t("info_source") + ":")} ${cannbotEntry.registryPath}`);
6849
+ }
6850
+ console.log();
6851
+ return;
6852
+ }
5741
6853
  try {
5742
6854
  const repoManager = createRepositoryManager();
5743
6855
  const repoPath = await repoManager.ensureRepo();
5744
- const quickstartPath = join18(repoPath, plugin.dir, "quickstart.md");
5745
- if (existsSync19(quickstartPath)) {
6856
+ const quickstartPath = join21(repoPath, plugin.dir, "quickstart.md");
6857
+ if (existsSync22(quickstartPath)) {
5746
6858
  console.log(` ${chalk13.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
5747
6859
  }
5748
- const agentsDir = join18(repoPath, plugin.dir, "agents");
5749
- if (existsSync19(agentsDir)) {
5750
- const agentFiles = readdirSync9(agentsDir).filter((f) => f.endsWith(".md"));
6860
+ const agentsDir = join21(repoPath, plugin.dir, "agents");
6861
+ if (existsSync22(agentsDir)) {
6862
+ const agentFiles = readdirSync12(agentsDir).filter((f) => f.endsWith(".md"));
5751
6863
  if (agentFiles.length > 0) {
5752
6864
  console.log();
5753
6865
  console.log(` ${chalk13.bold("Agents:")}`);
@@ -5802,9 +6914,9 @@ async function langCommand(action, value) {
5802
6914
  // src/cli.ts
5803
6915
  init_config();
5804
6916
  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";
6917
+ import { readFileSync as readFileSync16, existsSync as existsSync23 } from "fs";
6918
+ import { fileURLToPath as fileURLToPath5 } from "url";
6919
+ import { dirname as dirname8, join as join22 } from "path";
5808
6920
  function readVersion() {
5809
6921
  try {
5810
6922
  if (typeof INSTALL_HELPER_VERSION !== "undefined" && INSTALL_HELPER_VERSION) {
@@ -5813,16 +6925,16 @@ function readVersion() {
5813
6925
  } catch {
5814
6926
  }
5815
6927
  try {
5816
- const __filename4 = fileURLToPath4(import.meta.url);
5817
- const __dirname4 = dirname6(__filename4);
6928
+ const __filename5 = fileURLToPath5(import.meta.url);
6929
+ const __dirname5 = dirname8(__filename5);
5818
6930
  const candidates = [
5819
- join19(__dirname4, "..", "package.json"),
5820
- join19(__dirname4, "package.json"),
5821
- join19(process.cwd(), "package.json")
6931
+ join22(__dirname5, "..", "package.json"),
6932
+ join22(__dirname5, "package.json"),
6933
+ join22(process.cwd(), "package.json")
5822
6934
  ];
5823
6935
  for (const pkgPath of candidates) {
5824
- if (existsSync20(pkgPath)) {
5825
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
6936
+ if (existsSync23(pkgPath)) {
6937
+ const pkg = JSON.parse(readFileSync16(pkgPath, "utf-8"));
5826
6938
  if (pkg.version && (pkg.name === "@cannbot-ai/install-helper" || pkg.name?.startsWith("@cannbot-ai/install-helper-"))) {
5827
6939
  return pkg.version;
5828
6940
  }
@@ -5852,7 +6964,7 @@ function createCLI() {
5852
6964
  program2.command("status").description("Show installed plugins").action(async () => {
5853
6965
  await statusCommand();
5854
6966
  });
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) => {
6967
+ 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
6968
  await installCommand(names, options);
5857
6969
  });
5858
6970
  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) => {