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