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