@cannbot-ai/install-helper 0.0.2-beta.3 → 0.0.2-beta.30
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 +124 -31
- package/dist/config/repository.yaml +25 -0
- package/dist/index.js +968 -699
- package/dist/index.js.map +1 -1
- package/dist/locales/en_US.json +174 -0
- package/dist/locales/zh_CN.json +174 -0
- package/dist/plugins.d/_defaults.yml +4 -0
- package/dist/plugins.d/catlass-op-generator.yml +7 -0
- package/dist/plugins.d/model-infer-optimize.yml +7 -0
- package/dist/plugins.d/ops-code-reviewer.yml +7 -0
- package/dist/plugins.d/ops-direct-invoke-flash.yml +7 -0
- package/dist/plugins.d/ops-direct-invoke.yml +7 -0
- package/dist/plugins.d/ops-registry-invoke.yml +7 -0
- package/dist/plugins.d/pypto-op-orchestrator.yml +7 -0
- package/dist/plugins.d/tilelang-op-orchestrator.yml +7 -0
- package/dist/plugins.d/torch-compile.yml +7 -0
- package/dist/plugins.d/triton-op-generator.yml +8 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -4,116 +4,56 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
|
-
import { select as
|
|
8
|
-
import
|
|
7
|
+
import { select as select4, Separator as Separator3 } from "@inquirer/prompts";
|
|
8
|
+
import chalk7 from "chalk";
|
|
9
9
|
|
|
10
10
|
// src/ui/wizard.ts
|
|
11
11
|
import { select, checkbox, Separator } from "@inquirer/prompts";
|
|
12
|
-
import
|
|
12
|
+
import chalk3 from "chalk";
|
|
13
13
|
|
|
14
14
|
// src/core/registry.ts
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
dir: "plugins-official/ops-direct-invoke-flash",
|
|
29
|
-
displayName: "AscendC Kernel \u4ECE\u96F6\u6784\u5EFA",
|
|
30
|
-
script: "init.sh",
|
|
31
|
-
aliases: ["flash", "kernel-flash"],
|
|
32
|
-
skills: 1,
|
|
33
|
-
agents: 1,
|
|
34
|
-
description: "Ascend C / Ascend950 Reg API \u6838\u51FD\u6570\u4ECE\u96F6\u6784\u5EFA"
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
id: "ops-registry-invoke",
|
|
38
|
-
dir: "plugins-official/ops-registry-invoke",
|
|
39
|
-
displayName: "AscendC \u7B97\u5B50\u6CE8\u518C\u8C03\u7528",
|
|
40
|
-
script: "init.sh",
|
|
41
|
-
aliases: ["ops-registry", "ascendc-registry", "registry"],
|
|
42
|
-
skills: 12,
|
|
43
|
-
agents: 4,
|
|
44
|
-
description: "Ascend C \u7B97\u5B50\u6CE8\u518C\u8C03\u7528\u5F00\u53D1\u6D41\u7A0B"
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
id: "pypto-op-orchestrator",
|
|
48
|
-
dir: "plugins-official/pypto-op-orchestrator",
|
|
49
|
-
displayName: "PyPTO \u7B97\u5B50",
|
|
50
|
-
script: "init.sh",
|
|
51
|
-
aliases: ["pypto", "pytorch"],
|
|
52
|
-
skills: 8,
|
|
53
|
-
agents: 3,
|
|
54
|
-
description: "PyPTO \u7B97\u5B50\u7AEF\u5230\u7AEF\u5F00\u53D1\u7F16\u6392"
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: "triton-op-generator",
|
|
58
|
-
dir: "plugins-official/triton-op-generator",
|
|
59
|
-
displayName: "Triton \u7B97\u5B50\u751F\u6210",
|
|
60
|
-
script: "install.sh",
|
|
61
|
-
aliases: ["triton"],
|
|
62
|
-
skills: 6,
|
|
63
|
-
agents: 0,
|
|
64
|
-
description: "Triton-Ascend \u7B97\u5B50\u4EE3\u7801\u751F\u6210\u4E0E\u4F18\u5316"
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
id: "tilelang-op-orchestrator",
|
|
68
|
-
dir: "plugins-official/tilelang-op-orchestrator",
|
|
69
|
-
displayName: "TileLang \u7B97\u5B50",
|
|
70
|
-
script: "init.sh",
|
|
71
|
-
aliases: ["tilelang"],
|
|
72
|
-
skills: 9,
|
|
73
|
-
agents: 3,
|
|
74
|
-
description: "TileLang \u7B97\u5B50\u5F00\u53D1\u6D41\u7A0B"
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
id: "model-infer-optimize",
|
|
78
|
-
dir: "plugins-official/model-infer-optimize",
|
|
79
|
-
displayName: "NPU \u63A8\u7406\u4F18\u5316",
|
|
80
|
-
script: "init.sh",
|
|
81
|
-
aliases: ["model-infer", "infer", "inference"],
|
|
82
|
-
skills: 11,
|
|
83
|
-
agents: 3,
|
|
84
|
-
description: "NPU \u6A21\u578B\u63A8\u7406\u7AEF\u5230\u7AEF\u4F18\u5316"
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
id: "catlass-op-generator",
|
|
88
|
-
dir: "plugins-official/catlass-op-generator",
|
|
89
|
-
displayName: "Catlass \u7B97\u5B50\u76F4\u8C03",
|
|
90
|
-
script: "init.sh",
|
|
91
|
-
aliases: ["catlass"],
|
|
92
|
-
skills: 10,
|
|
93
|
-
agents: 3,
|
|
94
|
-
description: "Catlass \u7B97\u5B50\u76F4\u8C03\u5F00\u53D1"
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
id: "ops-code-reviewer",
|
|
98
|
-
dir: "plugins-official/ops-code-reviewer",
|
|
99
|
-
displayName: "\u4EE3\u7801\u68C0\u89C6",
|
|
100
|
-
script: "init.sh",
|
|
101
|
-
aliases: ["code-review", "reviewer", "review"],
|
|
102
|
-
skills: 5,
|
|
103
|
-
agents: 1,
|
|
104
|
-
description: "\u4EE3\u7801\u68C0\u89C6\u4E0E\u89C4\u8303\u68C0\u67E5"
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
id: "torch-compile",
|
|
108
|
-
dir: "plugins-official/torch-compile",
|
|
109
|
-
displayName: "torch.compile \u56FE\u6A21\u5F0F",
|
|
110
|
-
script: "init.sh",
|
|
111
|
-
aliases: ["torch", "compile", "graph"],
|
|
112
|
-
skills: 6,
|
|
113
|
-
agents: 1,
|
|
114
|
-
description: "PyTorch torch.compile \u56FE\u6A21\u5F0F\u9002\u914D"
|
|
15
|
+
import { readdirSync, readFileSync } from "fs";
|
|
16
|
+
import { join, dirname } from "path";
|
|
17
|
+
import { fileURLToPath } from "url";
|
|
18
|
+
import { parse as parseYaml } from "yaml";
|
|
19
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
var __dirname = dirname(__filename);
|
|
21
|
+
function loadPluginsFromYaml() {
|
|
22
|
+
const pluginsDir = join(__dirname, "plugins.d");
|
|
23
|
+
const defaultsPath = join(pluginsDir, "_defaults.yml");
|
|
24
|
+
let defaults = {};
|
|
25
|
+
try {
|
|
26
|
+
defaults = parseYaml(readFileSync(defaultsPath, "utf-8")) || {};
|
|
27
|
+
} catch {
|
|
115
28
|
}
|
|
116
|
-
];
|
|
29
|
+
const plugins = [];
|
|
30
|
+
try {
|
|
31
|
+
for (const file of readdirSync(pluginsDir)) {
|
|
32
|
+
if (file.startsWith("_") || !file.endsWith(".yml")) continue;
|
|
33
|
+
try {
|
|
34
|
+
const content = parseYaml(readFileSync(join(pluginsDir, file), "utf-8"));
|
|
35
|
+
if (!content || !content.id) continue;
|
|
36
|
+
plugins.push({
|
|
37
|
+
id: content.id,
|
|
38
|
+
dir: content.dir,
|
|
39
|
+
displayName: content.displayName || content.id,
|
|
40
|
+
script: content.script || defaults.script || "init.sh",
|
|
41
|
+
aliases: content.aliases || [],
|
|
42
|
+
skills: content.skills ?? defaults.skills ?? 0,
|
|
43
|
+
agents: content.agents ?? defaults.agents ?? 0,
|
|
44
|
+
description: content.description || ""
|
|
45
|
+
});
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
plugins.sort(
|
|
52
|
+
(a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
|
|
53
|
+
);
|
|
54
|
+
return plugins;
|
|
55
|
+
}
|
|
56
|
+
var PLUGIN_REGISTRY = loadPluginsFromYaml();
|
|
117
57
|
function findPlugin(query) {
|
|
118
58
|
const normalized = query.toLowerCase().trim();
|
|
119
59
|
const exactMatch = PLUGIN_REGISTRY.find(
|
|
@@ -139,17 +79,68 @@ function getPluginById(id) {
|
|
|
139
79
|
import { execa } from "execa";
|
|
140
80
|
import { existsSync as existsSync2 } from "fs";
|
|
141
81
|
import { homedir as homedir2 } from "os";
|
|
142
|
-
import { join as
|
|
82
|
+
import { join as join4 } from "path";
|
|
143
83
|
|
|
144
84
|
// src/utils/paths.ts
|
|
145
85
|
import { homedir } from "os";
|
|
146
|
-
import { join } from "path";
|
|
86
|
+
import { join as join3 } from "path";
|
|
147
87
|
import { existsSync } from "fs";
|
|
88
|
+
|
|
89
|
+
// src/utils/i18n.ts
|
|
90
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
91
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
92
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
93
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
94
|
+
var __dirname2 = dirname2(__filename2);
|
|
95
|
+
function loadLocale(locale) {
|
|
96
|
+
const localePath = join2(__dirname2, "locales", `${locale}.json`);
|
|
97
|
+
try {
|
|
98
|
+
const content = readFileSync2(localePath, "utf-8");
|
|
99
|
+
return JSON.parse(content);
|
|
100
|
+
} catch {
|
|
101
|
+
const fallbackPath = join2(__dirname2, "locales", "zh_CN.json");
|
|
102
|
+
try {
|
|
103
|
+
const content = readFileSync2(fallbackPath, "utf-8");
|
|
104
|
+
return JSON.parse(content);
|
|
105
|
+
} catch {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
var currentLang = "zh_CN";
|
|
111
|
+
var messages = loadLocale("zh_CN");
|
|
112
|
+
function setLanguage(lang) {
|
|
113
|
+
currentLang = lang;
|
|
114
|
+
messages = loadLocale(lang);
|
|
115
|
+
}
|
|
116
|
+
function t(key) {
|
|
117
|
+
return messages[key] !== void 0 ? messages[key] : key;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/utils/paths.ts
|
|
121
|
+
var VALID_TOOLS = ["opencode", "claude", "trae", "cursor", "copilot"];
|
|
122
|
+
var VALID_LEVELS = ["project", "global"];
|
|
123
|
+
function validateTool(tool) {
|
|
124
|
+
if (!VALID_TOOLS.includes(tool)) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return tool;
|
|
130
|
+
}
|
|
131
|
+
function validateLevel(level) {
|
|
132
|
+
if (!VALID_LEVELS.includes(level)) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
t("error_invalid_level").replace("{level}", level).replace("{levels}", VALID_LEVELS.join(", "))
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return level;
|
|
138
|
+
}
|
|
148
139
|
function detectTraeVariant() {
|
|
149
140
|
const home = homedir();
|
|
150
|
-
if (existsSync(
|
|
151
|
-
if (existsSync(
|
|
152
|
-
if (existsSync(
|
|
141
|
+
if (existsSync(join3(home, ".trae-cn"))) return "ide";
|
|
142
|
+
if (existsSync(join3(home, ".marscode"))) return "plugin";
|
|
143
|
+
if (existsSync(join3(home, ".traecli"))) return "cli";
|
|
153
144
|
return "unknown";
|
|
154
145
|
}
|
|
155
146
|
function getConfigRoot(tool, level, base) {
|
|
@@ -157,66 +148,74 @@ function getConfigRoot(tool, level, base) {
|
|
|
157
148
|
if (level === "global") {
|
|
158
149
|
switch (tool) {
|
|
159
150
|
case "opencode":
|
|
160
|
-
return
|
|
151
|
+
return join3(home, ".config", "opencode");
|
|
161
152
|
case "claude":
|
|
162
|
-
return
|
|
153
|
+
return join3(home, ".claude");
|
|
163
154
|
case "trae": {
|
|
164
155
|
const variant = detectTraeVariant();
|
|
165
156
|
switch (variant) {
|
|
166
157
|
case "plugin":
|
|
167
|
-
return
|
|
158
|
+
return join3(home, ".marscode");
|
|
168
159
|
case "cli":
|
|
169
|
-
return
|
|
160
|
+
return join3(home, ".traecli");
|
|
170
161
|
default:
|
|
171
|
-
return
|
|
162
|
+
return join3(home, ".trae-cn");
|
|
172
163
|
}
|
|
173
164
|
}
|
|
174
165
|
case "cursor":
|
|
175
|
-
return
|
|
166
|
+
return join3(home, ".cursor");
|
|
176
167
|
case "copilot":
|
|
177
|
-
return
|
|
168
|
+
return join3(home, ".copilot");
|
|
169
|
+
default:
|
|
170
|
+
throw new Error(
|
|
171
|
+
t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
|
|
172
|
+
);
|
|
178
173
|
}
|
|
179
174
|
}
|
|
180
175
|
const baseDir = base || process.cwd();
|
|
181
176
|
switch (tool) {
|
|
182
177
|
case "opencode":
|
|
183
|
-
return
|
|
178
|
+
return join3(baseDir, ".opencode");
|
|
184
179
|
case "claude":
|
|
185
|
-
return
|
|
180
|
+
return join3(baseDir, ".claude");
|
|
186
181
|
case "trae": {
|
|
187
182
|
const variant = detectTraeVariant();
|
|
188
183
|
switch (variant) {
|
|
189
184
|
case "plugin":
|
|
190
|
-
return
|
|
185
|
+
return join3(baseDir, ".marscode");
|
|
191
186
|
case "cli":
|
|
192
|
-
return
|
|
187
|
+
return join3(baseDir, ".traecli");
|
|
193
188
|
default:
|
|
194
|
-
return
|
|
189
|
+
return join3(baseDir, ".trae");
|
|
195
190
|
}
|
|
196
191
|
}
|
|
197
192
|
case "cursor":
|
|
198
|
-
return
|
|
193
|
+
return join3(baseDir, ".cursor");
|
|
199
194
|
case "copilot":
|
|
200
|
-
return
|
|
195
|
+
return join3(baseDir, ".github");
|
|
196
|
+
default:
|
|
197
|
+
throw new Error(
|
|
198
|
+
t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
|
|
199
|
+
);
|
|
201
200
|
}
|
|
202
201
|
}
|
|
203
202
|
function getConfigFileName(tool) {
|
|
204
203
|
return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
205
204
|
}
|
|
206
205
|
function getSkillsDir(configRoot) {
|
|
207
|
-
return
|
|
206
|
+
return join3(configRoot, "skills");
|
|
208
207
|
}
|
|
209
208
|
function getAgentsDir(configRoot) {
|
|
210
|
-
return
|
|
209
|
+
return join3(configRoot, "agents");
|
|
211
210
|
}
|
|
212
211
|
function getManifestPath(configRoot) {
|
|
213
|
-
return
|
|
212
|
+
return join3(configRoot, "cannbot-manifest.json");
|
|
214
213
|
}
|
|
215
214
|
function getCannbotConfigDir() {
|
|
216
|
-
return
|
|
215
|
+
return join3(homedir(), ".cannbot");
|
|
217
216
|
}
|
|
218
217
|
function getCannbotRepoPath() {
|
|
219
|
-
return
|
|
218
|
+
return join3(getCannbotConfigDir(), "repo");
|
|
220
219
|
}
|
|
221
220
|
|
|
222
221
|
// src/core/detector.ts
|
|
@@ -232,7 +231,7 @@ async function getCommandVersion(cmd, args = ["--version"]) {
|
|
|
232
231
|
}
|
|
233
232
|
async function getCommandPath(cmd) {
|
|
234
233
|
try {
|
|
235
|
-
const result = await execa("
|
|
234
|
+
const result = await execa("which", [cmd], { timeout: 5e3 });
|
|
236
235
|
return result.stdout.trim();
|
|
237
236
|
} catch {
|
|
238
237
|
return void 0;
|
|
@@ -254,12 +253,12 @@ async function detectTrae() {
|
|
|
254
253
|
const home = homedir2();
|
|
255
254
|
const variant = detectTraeVariant();
|
|
256
255
|
let path;
|
|
257
|
-
if (variant === "ide" && existsSync2(
|
|
258
|
-
path =
|
|
259
|
-
} else if (variant === "plugin" && existsSync2(
|
|
260
|
-
path =
|
|
261
|
-
} else if (variant === "cli" && existsSync2(
|
|
262
|
-
path =
|
|
256
|
+
if (variant === "ide" && existsSync2(join4(home, ".trae-cn"))) {
|
|
257
|
+
path = join4(home, ".trae-cn");
|
|
258
|
+
} else if (variant === "plugin" && existsSync2(join4(home, ".marscode"))) {
|
|
259
|
+
path = join4(home, ".marscode");
|
|
260
|
+
} else if (variant === "cli" && existsSync2(join4(home, ".traecli"))) {
|
|
261
|
+
path = join4(home, ".traecli");
|
|
263
262
|
} else if (variant === "unknown") {
|
|
264
263
|
const cmdPath = await getCommandPath("trae");
|
|
265
264
|
if (cmdPath) path = cmdPath;
|
|
@@ -323,13 +322,13 @@ function getAllTools() {
|
|
|
323
322
|
}
|
|
324
323
|
|
|
325
324
|
// src/core/manifest.ts
|
|
326
|
-
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
327
|
-
import { join as
|
|
325
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
326
|
+
import { join as join5 } from "path";
|
|
328
327
|
function readManifest(configRoot) {
|
|
329
328
|
const manifestPath = getManifestPath(configRoot);
|
|
330
329
|
if (existsSync3(manifestPath)) {
|
|
331
330
|
try {
|
|
332
|
-
const content =
|
|
331
|
+
const content = readFileSync3(manifestPath, "utf-8");
|
|
333
332
|
return JSON.parse(content);
|
|
334
333
|
} catch {
|
|
335
334
|
}
|
|
@@ -342,16 +341,16 @@ function readAllManifests(configRoot) {
|
|
|
342
341
|
const standardPath = getManifestPath(configRoot);
|
|
343
342
|
if (existsSync3(standardPath)) {
|
|
344
343
|
try {
|
|
345
|
-
const content =
|
|
344
|
+
const content = readFileSync3(standardPath, "utf-8");
|
|
346
345
|
manifests.push(JSON.parse(content));
|
|
347
346
|
} catch {
|
|
348
347
|
}
|
|
349
348
|
}
|
|
350
349
|
for (const plugin of plugins) {
|
|
351
|
-
const pluginManifestPath =
|
|
350
|
+
const pluginManifestPath = join5(configRoot, `${plugin.id}-manifest.json`);
|
|
352
351
|
if (existsSync3(pluginManifestPath)) {
|
|
353
352
|
try {
|
|
354
|
-
const content =
|
|
353
|
+
const content = readFileSync3(pluginManifestPath, "utf-8");
|
|
355
354
|
manifests.push(JSON.parse(content));
|
|
356
355
|
} catch {
|
|
357
356
|
}
|
|
@@ -371,6 +370,9 @@ function scanInstalled() {
|
|
|
371
370
|
for (const manifest of manifests) {
|
|
372
371
|
const plugin = plugins.find((p) => p.id === manifest.team);
|
|
373
372
|
if (!plugin) continue;
|
|
373
|
+
if (!verifyPluginFilesExist(configRoot, manifest, tool, level)) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
374
376
|
const alreadyAdded = installed.some(
|
|
375
377
|
(p) => p.id === plugin.id && p.tool === tool && p.level === level
|
|
376
378
|
);
|
|
@@ -390,190 +392,31 @@ function scanInstalled() {
|
|
|
390
392
|
}
|
|
391
393
|
return installed;
|
|
392
394
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
install_enhanced_more: "\u66F4\u591A",
|
|
419
|
-
install_enhanced_check: "\u68C0\u67E5",
|
|
420
|
-
install_enhanced_docs: "\u6587\u6863",
|
|
421
|
-
update_title: "\u66F4\u65B0\u5DF2\u5B89\u88C5\u7684 Skills",
|
|
422
|
-
update_updating: "\u6B63\u5728\u66F4\u65B0",
|
|
423
|
-
update_done: "\u66F4\u65B0\u5B8C\u6210",
|
|
424
|
-
update_no_plugins: "\u6682\u65E0\u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6",
|
|
425
|
-
doctor_title: "CANNBot Doctor",
|
|
426
|
-
doctor_tools: "AI \u5DE5\u5177",
|
|
427
|
-
doctor_plugins: "\u5DF2\u5B89\u88C5\u63D2\u4EF6",
|
|
428
|
-
doctor_links: "\u94FE\u63A5\u5B8C\u6574\u6027",
|
|
429
|
-
doctor_config: "\u914D\u7F6E\u6587\u4EF6",
|
|
430
|
-
doctor_result: "\u7ED3\u679C",
|
|
431
|
-
doctor_fix_title: "\u81EA\u52A8\u4FEE\u590D",
|
|
432
|
-
doctor_fix_cleaning: "\u6E05\u7406\u5931\u6548\u94FE\u63A5",
|
|
433
|
-
doctor_fix_rebuilding: "\u91CD\u5EFA\u76EE\u5F55",
|
|
434
|
-
doctor_fix_done: "\u4FEE\u590D\u5B8C\u6210",
|
|
435
|
-
info_title: "\u63D2\u4EF6\u8BE6\u60C5",
|
|
436
|
-
info_description: "\u63CF\u8FF0",
|
|
437
|
-
info_skills: "Skills",
|
|
438
|
-
info_agents: "Agents",
|
|
439
|
-
info_status: "\u72B6\u6001",
|
|
440
|
-
info_quickstart: "\u5FEB\u901F\u5F00\u59CB",
|
|
441
|
-
info_not_found: "\u672A\u627E\u5230\u63D2\u4EF6",
|
|
442
|
-
lang_title: "\u8BED\u8A00\u8BBE\u7F6E",
|
|
443
|
-
lang_current: "\u5F53\u524D\u8BED\u8A00",
|
|
444
|
-
lang_set: "\u8BED\u8A00\u5DF2\u8BBE\u7F6E\u4E3A",
|
|
445
|
-
lang_invalid: "\u65E0\u6548\u7684\u8BED\u8A00\uFF0C\u652F\u6301: zh_CN, en_US",
|
|
446
|
-
list_title: "\u53EF\u7528\u573A\u666F",
|
|
447
|
-
list_id: "\u5E8F\u53F7",
|
|
448
|
-
list_name: "\u573A\u666F\u540D",
|
|
449
|
-
list_status: "\u72B6\u6001",
|
|
450
|
-
list_skills: "Skills",
|
|
451
|
-
list_agents: "Agents",
|
|
452
|
-
status_installed: "\u5DF2\u88C5",
|
|
453
|
-
status_not_installed: "\u672A\u88C5",
|
|
454
|
-
error_tool_not_found: "\u672A\u68C0\u6D4B\u5230\u4EFB\u4F55 AI \u7F16\u7A0B\u5DE5\u5177",
|
|
455
|
-
error_plugin_not_found: "\u672A\u627E\u5230\u63D2\u4EF6",
|
|
456
|
-
error_repo_not_found: "\u4ED3\u5E93\u672A\u627E\u5230",
|
|
457
|
-
quick_start: "\u542F\u52A8\u547D\u4EE4",
|
|
458
|
-
quick_start_hint: "\u8BD5\u8BD5\u8F93\u5165: \u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50",
|
|
459
|
-
skill_select_category: "\u9009\u62E9 Skill \u7C7B\u522B",
|
|
460
|
-
skill_select_items: "\u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills",
|
|
461
|
-
skill_already_installed: "\u5DF2\u5B89\u88C5",
|
|
462
|
-
skill_install_progress: "\u6B63\u5728\u5B89\u88C5",
|
|
463
|
-
skill_install_done: "Skill \u5B89\u88C5\u5B8C\u6210",
|
|
464
|
-
skill_uninstall_done: "Skill \u5378\u8F7D\u5B8C\u6210",
|
|
465
|
-
skill_list_title: "\u53EF\u7528 Skills",
|
|
466
|
-
wizard_select_mode: "\u9009\u62E9\u5B89\u88C5\u7C7B\u578B",
|
|
467
|
-
wizard_mode_plugin: "\u5B89\u88C5 Plugin",
|
|
468
|
-
wizard_mode_plugin_desc: "\u5B8C\u6574\u5F00\u53D1\u5DE5\u4F5C\u6D41\uFF08Skills + Agents + \u914D\u7F6E\u6587\u4EF6\uFF09",
|
|
469
|
-
wizard_mode_skill: "\u5B89\u88C5 Skill",
|
|
470
|
-
wizard_mode_skill_desc: "\u4EC5\u5B89\u88C5\u9886\u57DF\u77E5\u8BC6\u6280\u80FD\uFF0C\u8BA9 AI \u638C\u63E1\u7279\u5B9A\u80FD\u529B",
|
|
471
|
-
wizard_back: "\u8FD4\u56DE",
|
|
472
|
-
wizard_cancel: "\u9000\u51FA",
|
|
473
|
-
wizard_no_selection: "\u672A\u9009\u62E9\u4EFB\u4F55\u9879\uFF0C\u8BF7\u9009\u62E9\u64CD\u4F5C",
|
|
474
|
-
wizard_back_to_reselect: "\u8FD4\u56DE",
|
|
475
|
-
status_title: "\u5DF2\u5B89\u88C5\u63D2\u4EF6",
|
|
476
|
-
status_none: "\u6682\u65E0\u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6",
|
|
477
|
-
status_hint: "\u8FD0\u884C install-helper init \u5F00\u59CB\u5B89\u88C5",
|
|
478
|
-
status_install_time: "\u5B89\u88C5\u65F6\u95F4",
|
|
479
|
-
install_installed_to: "\u5B89\u88C5\u5230",
|
|
480
|
-
install_skills_count: "Skills",
|
|
481
|
-
install_agents_count: "Agents"
|
|
482
|
-
};
|
|
483
|
-
var en_US = {
|
|
484
|
-
wizard_title: "CANNBot Skills Setup Wizard",
|
|
485
|
-
wizard_detect: "Detecting installed AI coding tools...",
|
|
486
|
-
wizard_select_tool: "Select AI coding tool",
|
|
487
|
-
wizard_select_level: "Select install level",
|
|
488
|
-
wizard_select_plugins: "Select scenarios to install",
|
|
489
|
-
wizard_confirm: "Confirm installation",
|
|
490
|
-
wizard_detected_tool: "Detected",
|
|
491
|
-
wizard_confirm_tool: "Confirm using",
|
|
492
|
-
wizard_last_used: "last used",
|
|
493
|
-
wizard_already_installed: "installed",
|
|
494
|
-
install_cloning: "Cloning cannbot-skills repository...",
|
|
495
|
-
install_repo_ready: "Repository ready",
|
|
496
|
-
install_progress: "Installing",
|
|
497
|
-
install_done: "Installation complete",
|
|
498
|
-
install_success: "success",
|
|
499
|
-
install_failed: "failed",
|
|
500
|
-
install_already_installed: "already installed",
|
|
501
|
-
install_reinstall_confirm: "Reinstall (overwrite)",
|
|
502
|
-
install_skip: "skipped",
|
|
503
|
-
install_enhanced_next_steps: "Next steps",
|
|
504
|
-
install_enhanced_launch: "Launch",
|
|
505
|
-
install_enhanced_try: "Try",
|
|
506
|
-
install_enhanced_more: "More",
|
|
507
|
-
install_enhanced_check: "Check",
|
|
508
|
-
install_enhanced_docs: "Docs",
|
|
509
|
-
update_title: "Update installed Skills",
|
|
510
|
-
update_updating: "Updating",
|
|
511
|
-
update_done: "Update complete",
|
|
512
|
-
update_no_plugins: "No installed plugins",
|
|
513
|
-
doctor_title: "CANNBot Doctor",
|
|
514
|
-
doctor_tools: "AI Tools",
|
|
515
|
-
doctor_plugins: "Installed Plugins",
|
|
516
|
-
doctor_links: "Link Integrity",
|
|
517
|
-
doctor_config: "Configuration",
|
|
518
|
-
doctor_result: "Result",
|
|
519
|
-
doctor_fix_title: "Auto-fix",
|
|
520
|
-
doctor_fix_cleaning: "Cleaning broken links",
|
|
521
|
-
doctor_fix_rebuilding: "Rebuilding directories",
|
|
522
|
-
doctor_fix_done: "Fix complete",
|
|
523
|
-
info_title: "Plugin Details",
|
|
524
|
-
info_description: "Description",
|
|
525
|
-
info_skills: "Skills",
|
|
526
|
-
info_agents: "Agents",
|
|
527
|
-
info_status: "Status",
|
|
528
|
-
info_quickstart: "Quick Start",
|
|
529
|
-
info_not_found: "Plugin not found",
|
|
530
|
-
lang_title: "Language Settings",
|
|
531
|
-
lang_current: "Current language",
|
|
532
|
-
lang_set: "Language set to",
|
|
533
|
-
lang_invalid: "Invalid language, supported: zh_CN, en_US",
|
|
534
|
-
list_title: "Available Scenarios",
|
|
535
|
-
list_id: "#",
|
|
536
|
-
list_name: "Name",
|
|
537
|
-
list_status: "Status",
|
|
538
|
-
list_skills: "Skills",
|
|
539
|
-
list_agents: "Agents",
|
|
540
|
-
status_installed: "installed",
|
|
541
|
-
status_not_installed: "not installed",
|
|
542
|
-
error_tool_not_found: "No AI coding tool detected",
|
|
543
|
-
error_plugin_not_found: "Plugin not found",
|
|
544
|
-
error_repo_not_found: "Repository not found",
|
|
545
|
-
quick_start: "Launch command",
|
|
546
|
-
quick_start_hint: "Try: help me develop an Abs operator",
|
|
547
|
-
skill_select_category: "Select Skill category",
|
|
548
|
-
skill_select_items: "Select Skills to install (space to toggle, enter to confirm)",
|
|
549
|
-
skill_already_installed: "installed",
|
|
550
|
-
skill_install_progress: "Installing",
|
|
551
|
-
skill_install_done: "Skill installation complete",
|
|
552
|
-
skill_uninstall_done: "Skill uninstallation complete",
|
|
553
|
-
skill_list_title: "Available Skills",
|
|
554
|
-
wizard_select_mode: "Select installation mode",
|
|
555
|
-
wizard_mode_plugin: "Install Plugin",
|
|
556
|
-
wizard_mode_plugin_desc: "Full development workflow (Skills + Agents + config)",
|
|
557
|
-
wizard_mode_skill: "Install Skill",
|
|
558
|
-
wizard_mode_skill_desc: "Domain knowledge only, give AI specific capabilities",
|
|
559
|
-
wizard_back: "Go back",
|
|
560
|
-
wizard_cancel: "Cancel installation",
|
|
561
|
-
wizard_no_selection: "No items selected, please choose an action",
|
|
562
|
-
wizard_back_to_reselect: "Go back",
|
|
563
|
-
status_title: "Installed Plugins",
|
|
564
|
-
status_none: "No installed plugins",
|
|
565
|
-
status_hint: "Run install-helper init to start",
|
|
566
|
-
status_install_time: "Installed at",
|
|
567
|
-
install_installed_to: "Installed to",
|
|
568
|
-
install_skills_count: "Skills",
|
|
569
|
-
install_agents_count: "Agents"
|
|
570
|
-
};
|
|
571
|
-
var currentLang = "zh_CN";
|
|
572
|
-
function setLanguage(lang) {
|
|
573
|
-
currentLang = lang;
|
|
574
|
-
}
|
|
575
|
-
function t(key) {
|
|
576
|
-
return currentLang === "zh_CN" ? zh_CN[key] : en_US[key];
|
|
395
|
+
function verifyPluginFilesExist(configRoot, manifest, tool, level) {
|
|
396
|
+
const skillsDir = join5(configRoot, "skills");
|
|
397
|
+
const agentsDir = join5(configRoot, "agents");
|
|
398
|
+
if (manifest.installed_skills && manifest.installed_skills.length > 0) {
|
|
399
|
+
const hasAnySkill = manifest.installed_skills.some((skillId) => {
|
|
400
|
+
const skillPath = join5(skillsDir, skillId);
|
|
401
|
+
return existsSync3(skillPath);
|
|
402
|
+
});
|
|
403
|
+
if (!hasAnySkill) return false;
|
|
404
|
+
}
|
|
405
|
+
if (manifest.installed_agents && manifest.installed_agents.length > 0) {
|
|
406
|
+
const hasAnyAgent = manifest.installed_agents.some((agentId) => {
|
|
407
|
+
const agentPath = join5(agentsDir, agentId);
|
|
408
|
+
return existsSync3(agentPath);
|
|
409
|
+
});
|
|
410
|
+
if (!hasAnyAgent) return false;
|
|
411
|
+
}
|
|
412
|
+
let agentsMdPath;
|
|
413
|
+
if (tool === "opencode" && level === "project") {
|
|
414
|
+
agentsMdPath = join5(configRoot, "..", "AGENTS.md");
|
|
415
|
+
} else {
|
|
416
|
+
agentsMdPath = join5(configRoot, "AGENTS.md");
|
|
417
|
+
}
|
|
418
|
+
if (!existsSync3(agentsMdPath)) return false;
|
|
419
|
+
return true;
|
|
577
420
|
}
|
|
578
421
|
|
|
579
422
|
// src/utils/logger.ts
|
|
@@ -630,24 +473,24 @@ function printBoxTitle(title, width = 65) {
|
|
|
630
473
|
}
|
|
631
474
|
function showOperationHints(isCheckbox = false) {
|
|
632
475
|
if (isCheckbox) {
|
|
633
|
-
console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m
|
|
476
|
+
console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m${t("hint_select_space")}\x1B[0m ${t("hint_select_check")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}\uFF08${t("hint_checkbox_suffix")}\uFF09
|
|
634
477
|
`);
|
|
635
478
|
} else {
|
|
636
|
-
console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m
|
|
479
|
+
console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}
|
|
637
480
|
`);
|
|
638
481
|
}
|
|
639
482
|
}
|
|
640
483
|
|
|
641
484
|
// src/utils/config.ts
|
|
642
|
-
import { existsSync as existsSync4, mkdirSync, readFileSync as
|
|
643
|
-
import { join as
|
|
485
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
486
|
+
import { join as join6 } from "path";
|
|
644
487
|
import { homedir as homedir3 } from "os";
|
|
645
488
|
import { parse, stringify } from "yaml";
|
|
646
489
|
function getConfigDir() {
|
|
647
|
-
return
|
|
490
|
+
return join6(homedir3(), ".cannbot");
|
|
648
491
|
}
|
|
649
492
|
function getConfigPath() {
|
|
650
|
-
return
|
|
493
|
+
return join6(getConfigDir(), "config.yaml");
|
|
651
494
|
}
|
|
652
495
|
function readConfig() {
|
|
653
496
|
const configPath = getConfigPath();
|
|
@@ -658,7 +501,7 @@ function readConfig() {
|
|
|
658
501
|
};
|
|
659
502
|
}
|
|
660
503
|
try {
|
|
661
|
-
const content =
|
|
504
|
+
const content = readFileSync4(configPath, "utf-8");
|
|
662
505
|
const parsed = parse(content);
|
|
663
506
|
return {
|
|
664
507
|
language: parsed.language || "zh_CN",
|
|
@@ -705,16 +548,78 @@ function removeInstalledPlugin(pluginId) {
|
|
|
705
548
|
}
|
|
706
549
|
|
|
707
550
|
// src/ui/theme.ts
|
|
551
|
+
import chalk2 from "chalk";
|
|
708
552
|
var selectTheme = {
|
|
709
553
|
helpMode: "never"
|
|
710
554
|
};
|
|
555
|
+
function isCJK(code) {
|
|
556
|
+
return code >= 11904 && code <= 40959 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65103 || code >= 65280 && code <= 65519 || code >= 13312 && code <= 19903 || code >= 131072 && code <= 173791 || code >= 173824 && code <= 177983 || code >= 177984 && code <= 178207 || code >= 178208 && code <= 183983;
|
|
557
|
+
}
|
|
558
|
+
function isEmoji(code) {
|
|
559
|
+
return code >= 127744 && code <= 129791 || code >= 9728 && code <= 10175;
|
|
560
|
+
}
|
|
561
|
+
function getDisplayWidth2(str) {
|
|
562
|
+
let width = 0;
|
|
563
|
+
for (const ch of str) {
|
|
564
|
+
const code = ch.codePointAt(0);
|
|
565
|
+
if (isCJK(code) || isEmoji(code)) {
|
|
566
|
+
width += 2;
|
|
567
|
+
} else {
|
|
568
|
+
width += 1;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return width;
|
|
572
|
+
}
|
|
573
|
+
function wrapDescription(text) {
|
|
574
|
+
const prefix = " \u{1F4A1} ";
|
|
575
|
+
const indent = " ";
|
|
576
|
+
const cols = process.stdout.columns || 120;
|
|
577
|
+
const maxLen = Math.max(20, cols - getDisplayWidth2(prefix) - 2);
|
|
578
|
+
const tokens = [];
|
|
579
|
+
let i = 0;
|
|
580
|
+
while (i < text.length) {
|
|
581
|
+
const code = text[i].codePointAt(0);
|
|
582
|
+
if (isCJK(code)) {
|
|
583
|
+
tokens.push({ text: text[i], width: 2 });
|
|
584
|
+
i++;
|
|
585
|
+
} else {
|
|
586
|
+
let j = i;
|
|
587
|
+
while (j < text.length) {
|
|
588
|
+
const c = text[j].codePointAt(0);
|
|
589
|
+
if (isCJK(c)) break;
|
|
590
|
+
j++;
|
|
591
|
+
}
|
|
592
|
+
const word = text.slice(i, j);
|
|
593
|
+
tokens.push({ text: word, width: getDisplayWidth2(word) });
|
|
594
|
+
i = j;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const lines = [];
|
|
598
|
+
let line = "";
|
|
599
|
+
let lineWidth = 0;
|
|
600
|
+
for (const token of tokens) {
|
|
601
|
+
if (lineWidth + token.width > maxLen && line) {
|
|
602
|
+
lines.push(line.trimEnd());
|
|
603
|
+
line = token.text;
|
|
604
|
+
lineWidth = token.width;
|
|
605
|
+
} else {
|
|
606
|
+
line += token.text;
|
|
607
|
+
lineWidth += token.width;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
if (line) lines.push(line.trimEnd());
|
|
611
|
+
return lines.map((l, i2) => chalk2.cyan(i2 === 0 ? prefix + l : indent + l)).join("\n");
|
|
612
|
+
}
|
|
711
613
|
var checkboxTheme = {
|
|
712
614
|
icon: {
|
|
713
615
|
checked: "\x1B[32m\u2611\x1B[22m",
|
|
714
616
|
unchecked: "\u2610",
|
|
715
617
|
cursor: "\u276F"
|
|
716
618
|
},
|
|
717
|
-
helpMode: "never"
|
|
619
|
+
helpMode: "never",
|
|
620
|
+
style: {
|
|
621
|
+
description: (text) => wrapDescription(text)
|
|
622
|
+
}
|
|
718
623
|
};
|
|
719
624
|
|
|
720
625
|
// src/ui/wizard.ts
|
|
@@ -789,7 +694,7 @@ async function runWizard() {
|
|
|
789
694
|
step = 2;
|
|
790
695
|
break;
|
|
791
696
|
}
|
|
792
|
-
if (result === CANCEL
|
|
697
|
+
if (result === CANCEL) {
|
|
793
698
|
return { language: config.language || "zh_CN", tool: selectedTool, level, plugins: [], confirmed: false };
|
|
794
699
|
}
|
|
795
700
|
updateConfig({ lastTool: selectedTool, lastLevel: level });
|
|
@@ -799,7 +704,7 @@ async function runWizard() {
|
|
|
799
704
|
}
|
|
800
705
|
}
|
|
801
706
|
async function stepTool(detectedTools, lastTool) {
|
|
802
|
-
printBoxTitle("
|
|
707
|
+
printBoxTitle(t("wizard_step_1_title"));
|
|
803
708
|
if (detectedTools.length === 0) {
|
|
804
709
|
logger.error(t("error_tool_not_found"));
|
|
805
710
|
const choices2 = [
|
|
@@ -874,10 +779,10 @@ async function stepToolManual(lastTool) {
|
|
|
874
779
|
return result;
|
|
875
780
|
}
|
|
876
781
|
async function stepLevel(lastLevel) {
|
|
877
|
-
printBoxTitle("
|
|
782
|
+
printBoxTitle(t("wizard_step_2_title"));
|
|
878
783
|
const choices = [
|
|
879
|
-
{ name:
|
|
880
|
-
{ name:
|
|
784
|
+
{ name: `> project \u2014 ${t("install_level_project")}`, value: "project" },
|
|
785
|
+
{ name: `> global \u2014 ${t("install_level_global")}`, value: "global" },
|
|
881
786
|
new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
882
787
|
{ name: "<- " + t("wizard_back"), value: BACK },
|
|
883
788
|
{ name: "x " + t("wizard_cancel"), value: CANCEL }
|
|
@@ -895,7 +800,7 @@ async function stepLevel(lastLevel) {
|
|
|
895
800
|
return result;
|
|
896
801
|
}
|
|
897
802
|
async function stepPlugins(tool, level) {
|
|
898
|
-
printBoxTitle("
|
|
803
|
+
printBoxTitle(t("wizard_step_3_title"));
|
|
899
804
|
const plugins = getAllPlugins();
|
|
900
805
|
const configRoot = getConfigRoot(tool, level);
|
|
901
806
|
const manifests = readAllManifests(configRoot);
|
|
@@ -904,25 +809,22 @@ async function stepPlugins(tool, level) {
|
|
|
904
809
|
const isInstalled = installedSet.has(p.id);
|
|
905
810
|
const suffix = isInstalled ? ` [${t("wizard_already_installed")}]` : "";
|
|
906
811
|
return {
|
|
907
|
-
name: `${p.displayName}${suffix}
|
|
812
|
+
name: `${chalk3.cyan(p.displayName)}${suffix}`,
|
|
908
813
|
value: p.id,
|
|
909
|
-
checked: false
|
|
814
|
+
checked: false,
|
|
815
|
+
description: p.description
|
|
910
816
|
};
|
|
911
817
|
});
|
|
912
|
-
choices.push(new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
913
|
-
choices.push({ name: "<- " + t("wizard_back"), value: BACK, checked: false });
|
|
914
|
-
choices.push({ name: "x " + t("wizard_cancel"), value: CANCEL, checked: false });
|
|
915
818
|
showOperationHints(true);
|
|
916
819
|
const selected = await checkbox({
|
|
917
820
|
message: t("wizard_select_plugins"),
|
|
918
821
|
choices,
|
|
919
822
|
loop: false,
|
|
920
823
|
instructions: false,
|
|
921
|
-
theme: checkboxTheme
|
|
824
|
+
theme: checkboxTheme,
|
|
825
|
+
pageSize: 15
|
|
922
826
|
});
|
|
923
|
-
|
|
924
|
-
if (selected.includes(CANCEL)) return CANCEL;
|
|
925
|
-
const pluginIds = selected.filter((v) => v !== BACK && v !== CANCEL);
|
|
827
|
+
const pluginIds = selected;
|
|
926
828
|
if (pluginIds.length === 0) {
|
|
927
829
|
const action = await select({
|
|
928
830
|
message: t("wizard_no_selection"),
|
|
@@ -940,17 +842,17 @@ async function stepPlugins(tool, level) {
|
|
|
940
842
|
return pluginIds;
|
|
941
843
|
}
|
|
942
844
|
async function stepConfirm(tool, level, plugins) {
|
|
943
|
-
printBoxTitle("
|
|
845
|
+
printBoxTitle(t("wizard_step_4_title"));
|
|
944
846
|
const allPlugins = getAllPlugins();
|
|
945
847
|
const selectedPlugins = plugins.map((id) => allPlugins.find((p) => p.id === id));
|
|
946
848
|
const displayPath = getConfigRoot(tool, level);
|
|
947
849
|
const toolName = getToolDisplayName(tool);
|
|
948
850
|
const levelText = level === "project" ? "project" : "global";
|
|
949
|
-
logger.info(
|
|
950
|
-
logger.info(
|
|
851
|
+
logger.info(t("wizard_confirm_install_format").replace("{count}", chalk3.bold(String(plugins.length))).replace("{tool}", chalk3.green(toolName)).replace("{level}", chalk3.cyan(levelText)));
|
|
852
|
+
logger.info(`${t("wizard_install_path")}: ${chalk3.cyan(displayPath)}`);
|
|
951
853
|
for (const plugin of selectedPlugins) {
|
|
952
854
|
if (plugin) {
|
|
953
|
-
logger.step(` \u2022 ${
|
|
855
|
+
logger.step(` \u2022 ${chalk3.bold(plugin.displayName)}`);
|
|
954
856
|
}
|
|
955
857
|
}
|
|
956
858
|
logger.blank();
|
|
@@ -968,14 +870,19 @@ async function stepConfirm(tool, level, plugins) {
|
|
|
968
870
|
}
|
|
969
871
|
|
|
970
872
|
// src/core/repository.ts
|
|
971
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
972
|
-
import { join as
|
|
873
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
|
|
874
|
+
import { join as join8, dirname as dirname4 } from "path";
|
|
875
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
973
876
|
import { execa as execa2 } from "execa";
|
|
877
|
+
import { parse as parseYaml3 } from "yaml";
|
|
974
878
|
|
|
975
879
|
// src/core/scanner.ts
|
|
976
|
-
import { existsSync as existsSync5, readdirSync, readFileSync as
|
|
977
|
-
import { join as
|
|
978
|
-
import {
|
|
880
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
881
|
+
import { join as join7, dirname as dirname3 } from "path";
|
|
882
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
883
|
+
import { parse as parseYaml2 } from "yaml";
|
|
884
|
+
var __filename3 = fileURLToPath3(import.meta.url);
|
|
885
|
+
var __dirname3 = dirname3(__filename3);
|
|
979
886
|
var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
980
887
|
"node_modules",
|
|
981
888
|
".git",
|
|
@@ -996,36 +903,43 @@ var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
|
996
903
|
"docs",
|
|
997
904
|
"scripts"
|
|
998
905
|
]);
|
|
999
|
-
|
|
1000
|
-
"
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
]
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
]
|
|
906
|
+
function loadScanConfig() {
|
|
907
|
+
const configPath = join7(__dirname3, "config", "repository.yaml");
|
|
908
|
+
try {
|
|
909
|
+
const config = parseYaml2(readFileSync5(configPath, "utf-8"));
|
|
910
|
+
return {
|
|
911
|
+
skillDirs: config.scanDirs || ["ops", "model", "graph", "infra", "ops-lab"],
|
|
912
|
+
pluginDirs: config.pluginDirs || ["plugins-official", "plugins-community"]
|
|
913
|
+
};
|
|
914
|
+
} catch {
|
|
915
|
+
return {
|
|
916
|
+
skillDirs: ["ops", "model", "graph", "infra", "ops-lab"],
|
|
917
|
+
pluginDirs: ["plugins-official", "plugins-community"]
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
var scanConfig = loadScanConfig();
|
|
922
|
+
var SKILL_SCAN_DIRS = scanConfig.skillDirs;
|
|
923
|
+
var PLUGIN_SCAN_DIRS = scanConfig.pluginDirs;
|
|
1010
924
|
function scanSkills(repoPath) {
|
|
1011
925
|
const skills = [];
|
|
1012
926
|
const seen = /* @__PURE__ */ new Set();
|
|
1013
927
|
for (const dir of SKILL_SCAN_DIRS) {
|
|
1014
|
-
const fullPath =
|
|
928
|
+
const fullPath = join7(repoPath, dir);
|
|
1015
929
|
if (!existsSync5(fullPath)) continue;
|
|
1016
930
|
scanDirectory(fullPath, repoPath, dir, skills, seen);
|
|
1017
931
|
}
|
|
1018
932
|
for (const pluginDir of PLUGIN_SCAN_DIRS) {
|
|
1019
|
-
const pluginParent =
|
|
933
|
+
const pluginParent = join7(repoPath, pluginDir);
|
|
1020
934
|
if (!existsSync5(pluginParent)) continue;
|
|
1021
935
|
for (const plugin of safeReaddir(pluginParent)) {
|
|
1022
|
-
const pluginPath =
|
|
936
|
+
const pluginPath = join7(pluginParent, plugin);
|
|
1023
937
|
if (!isDirectory(pluginPath)) continue;
|
|
1024
|
-
const skillsDir =
|
|
938
|
+
const skillsDir = join7(pluginPath, "skills");
|
|
1025
939
|
if (existsSync5(skillsDir)) {
|
|
1026
940
|
scanDirectory(skillsDir, repoPath, `${pluginDir}/${plugin}/skills`, skills, seen);
|
|
1027
941
|
}
|
|
1028
|
-
const skillDir =
|
|
942
|
+
const skillDir = join7(pluginPath, "skill");
|
|
1029
943
|
if (existsSync5(skillDir)) {
|
|
1030
944
|
scanDirectory(skillDir, repoPath, `${pluginDir}/${plugin}/skill`, skills, seen);
|
|
1031
945
|
}
|
|
@@ -1035,7 +949,7 @@ function scanSkills(repoPath) {
|
|
|
1035
949
|
}
|
|
1036
950
|
function parseFrontmatter(filePath) {
|
|
1037
951
|
try {
|
|
1038
|
-
const content =
|
|
952
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
1039
953
|
const lines = content.split("\n");
|
|
1040
954
|
if (lines.length < 3) return null;
|
|
1041
955
|
if (lines[0].trim() !== "---") return null;
|
|
@@ -1048,7 +962,7 @@ function parseFrontmatter(filePath) {
|
|
|
1048
962
|
}
|
|
1049
963
|
if (endIndex === -1) return null;
|
|
1050
964
|
const yamlBlock = lines.slice(1, endIndex).map((l) => l.replace(/\r$/, "")).join("\n");
|
|
1051
|
-
const parsed =
|
|
965
|
+
const parsed = parseYaml2(yamlBlock);
|
|
1052
966
|
if (!parsed || typeof parsed !== "object") return null;
|
|
1053
967
|
if (!parsed.name || typeof parsed.name !== "string") return null;
|
|
1054
968
|
return {
|
|
@@ -1061,17 +975,17 @@ function parseFrontmatter(filePath) {
|
|
|
1061
975
|
}
|
|
1062
976
|
function getCurrentCommit(repoPath) {
|
|
1063
977
|
try {
|
|
1064
|
-
const headFile =
|
|
978
|
+
const headFile = join7(repoPath, ".git", "HEAD");
|
|
1065
979
|
if (!existsSync5(headFile)) return "unknown";
|
|
1066
|
-
const headContent =
|
|
980
|
+
const headContent = readFileSync5(headFile, "utf-8").trim();
|
|
1067
981
|
if (headContent.startsWith("ref: ")) {
|
|
1068
|
-
const refPath =
|
|
982
|
+
const refPath = join7(repoPath, ".git", headContent.slice(5));
|
|
1069
983
|
if (existsSync5(refPath)) {
|
|
1070
|
-
return
|
|
984
|
+
return readFileSync5(refPath, "utf-8").trim();
|
|
1071
985
|
}
|
|
1072
|
-
const packedRefsPath =
|
|
986
|
+
const packedRefsPath = join7(repoPath, ".git", "packed-refs");
|
|
1073
987
|
if (existsSync5(packedRefsPath)) {
|
|
1074
|
-
const packedRefs =
|
|
988
|
+
const packedRefs = readFileSync5(packedRefsPath, "utf-8");
|
|
1075
989
|
const refName = headContent.slice(5);
|
|
1076
990
|
const lines = packedRefs.split("\n");
|
|
1077
991
|
for (const line of lines) {
|
|
@@ -1090,7 +1004,7 @@ function readScanCache() {
|
|
|
1090
1004
|
try {
|
|
1091
1005
|
const cachePath = getCachePath();
|
|
1092
1006
|
if (!existsSync5(cachePath)) return null;
|
|
1093
|
-
const content =
|
|
1007
|
+
const content = readFileSync5(cachePath, "utf-8");
|
|
1094
1008
|
const cache = JSON.parse(content);
|
|
1095
1009
|
if (!cache.skills || !cache.timestamp) return null;
|
|
1096
1010
|
const age = Date.now() - cache.timestamp;
|
|
@@ -1103,7 +1017,7 @@ function readScanCache() {
|
|
|
1103
1017
|
function writeScanCache(cache) {
|
|
1104
1018
|
try {
|
|
1105
1019
|
const cachePath = getCachePath();
|
|
1106
|
-
const cacheDir =
|
|
1020
|
+
const cacheDir = dirname3(cachePath);
|
|
1107
1021
|
if (!existsSync5(cacheDir)) {
|
|
1108
1022
|
mkdirSync2(cacheDir, { recursive: true });
|
|
1109
1023
|
}
|
|
@@ -1112,29 +1026,14 @@ function writeScanCache(cache) {
|
|
|
1112
1026
|
}
|
|
1113
1027
|
}
|
|
1114
1028
|
function getCachePath() {
|
|
1115
|
-
|
|
1116
|
-
return join5(homeDir, ".cannbot", "scan-cache.json");
|
|
1029
|
+
return join7(getCannbotConfigDir(), "scan-cache.json");
|
|
1117
1030
|
}
|
|
1118
1031
|
function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
|
|
1119
|
-
const selfSkillMd = join5(dirPath, "SKILL.md");
|
|
1120
|
-
if (existsSync5(selfSkillMd)) {
|
|
1121
|
-
const frontmatter = parseFrontmatter(selfSkillMd);
|
|
1122
|
-
if (frontmatter && !seen.has(frontmatter.name)) {
|
|
1123
|
-
seen.add(frontmatter.name);
|
|
1124
|
-
skills.push({
|
|
1125
|
-
id: frontmatter.name,
|
|
1126
|
-
description: frontmatter.description,
|
|
1127
|
-
source: sourcePrefix,
|
|
1128
|
-
filePath: selfSkillMd
|
|
1129
|
-
});
|
|
1130
|
-
}
|
|
1131
|
-
return;
|
|
1132
|
-
}
|
|
1133
1032
|
for (const entry of safeReaddir(dirPath)) {
|
|
1134
|
-
const fullPath =
|
|
1033
|
+
const fullPath = join7(dirPath, entry);
|
|
1135
1034
|
if (EXCLUDE_DIRS.has(entry)) continue;
|
|
1136
1035
|
if (!isDirectory(fullPath)) continue;
|
|
1137
|
-
const skillMd =
|
|
1036
|
+
const skillMd = join7(fullPath, "SKILL.md");
|
|
1138
1037
|
if (existsSync5(skillMd)) {
|
|
1139
1038
|
const frontmatter = parseFrontmatter(skillMd);
|
|
1140
1039
|
if (frontmatter && !seen.has(frontmatter.name)) {
|
|
@@ -1148,12 +1047,12 @@ function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
|
|
|
1148
1047
|
}
|
|
1149
1048
|
continue;
|
|
1150
1049
|
}
|
|
1151
|
-
scanDirectory(fullPath, repoPath, sourcePrefix, skills, seen);
|
|
1050
|
+
scanDirectory(fullPath, repoPath, sourcePrefix + "/" + entry, skills, seen);
|
|
1152
1051
|
}
|
|
1153
1052
|
}
|
|
1154
1053
|
function safeReaddir(dir) {
|
|
1155
1054
|
try {
|
|
1156
|
-
return
|
|
1055
|
+
return readdirSync2(dir);
|
|
1157
1056
|
} catch {
|
|
1158
1057
|
return [];
|
|
1159
1058
|
}
|
|
@@ -1393,7 +1292,21 @@ function getAllCategories() {
|
|
|
1393
1292
|
}
|
|
1394
1293
|
|
|
1395
1294
|
// src/core/repository.ts
|
|
1396
|
-
var
|
|
1295
|
+
var __filename4 = fileURLToPath4(import.meta.url);
|
|
1296
|
+
var __dirname4 = dirname4(__filename4);
|
|
1297
|
+
function loadRepoUrl() {
|
|
1298
|
+
if (process.env.CANNBOT_REPO_URL) {
|
|
1299
|
+
return process.env.CANNBOT_REPO_URL;
|
|
1300
|
+
}
|
|
1301
|
+
const configPath = join8(__dirname4, "config", "repository.yaml");
|
|
1302
|
+
try {
|
|
1303
|
+
const config = parseYaml3(readFileSync6(configPath, "utf-8"));
|
|
1304
|
+
return config.repository?.url || "https://gitcode.com/cann/cannbot-skills.git";
|
|
1305
|
+
} catch {
|
|
1306
|
+
return "https://gitcode.com/cann/cannbot-skills.git";
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
var REPO_URL = loadRepoUrl();
|
|
1397
1310
|
var RepositoryManager = class {
|
|
1398
1311
|
repoPath;
|
|
1399
1312
|
constructor(customPath) {
|
|
@@ -1451,8 +1364,8 @@ var RepositoryManager = class {
|
|
|
1451
1364
|
}
|
|
1452
1365
|
isValidRepo(path) {
|
|
1453
1366
|
if (!existsSync6(path)) return false;
|
|
1454
|
-
const gitDir =
|
|
1455
|
-
const pluginsDir =
|
|
1367
|
+
const gitDir = join8(path, ".git");
|
|
1368
|
+
const pluginsDir = join8(path, "plugins-official");
|
|
1456
1369
|
return existsSync6(gitDir) && existsSync6(pluginsDir);
|
|
1457
1370
|
}
|
|
1458
1371
|
async cloneRepo() {
|
|
@@ -1487,18 +1400,18 @@ function createRepositoryManager(customPath) {
|
|
|
1487
1400
|
}
|
|
1488
1401
|
|
|
1489
1402
|
// src/core/installer.ts
|
|
1490
|
-
import { existsSync as
|
|
1491
|
-
import { join as
|
|
1403
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1404
|
+
import { join as join11 } from "path";
|
|
1492
1405
|
import { execa as execa3 } from "execa";
|
|
1493
1406
|
|
|
1494
1407
|
// src/core/record.ts
|
|
1495
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as
|
|
1496
|
-
import { join as
|
|
1408
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4, lstatSync, unlinkSync } from "fs";
|
|
1409
|
+
import { join as join9 } from "path";
|
|
1497
1410
|
function getInstallsDir() {
|
|
1498
|
-
return
|
|
1411
|
+
return join9(getCannbotConfigDir(), "installs");
|
|
1499
1412
|
}
|
|
1500
1413
|
function getRecordPath(pluginId) {
|
|
1501
|
-
return
|
|
1414
|
+
return join9(getInstallsDir(), `${pluginId}.json`);
|
|
1502
1415
|
}
|
|
1503
1416
|
function readRecord(pluginId) {
|
|
1504
1417
|
const recordPath = getRecordPath(pluginId);
|
|
@@ -1506,7 +1419,7 @@ function readRecord(pluginId) {
|
|
|
1506
1419
|
return null;
|
|
1507
1420
|
}
|
|
1508
1421
|
try {
|
|
1509
|
-
const content =
|
|
1422
|
+
const content = readFileSync7(recordPath, "utf-8");
|
|
1510
1423
|
return JSON.parse(content);
|
|
1511
1424
|
} catch {
|
|
1512
1425
|
return null;
|
|
@@ -1530,22 +1443,22 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
|
|
|
1530
1443
|
const files = [];
|
|
1531
1444
|
const directories = [];
|
|
1532
1445
|
if (manifest) {
|
|
1533
|
-
const skillsDir =
|
|
1446
|
+
const skillsDir = join9(configRoot, "skills");
|
|
1534
1447
|
if (existsSync7(skillsDir)) {
|
|
1535
1448
|
directories.push(skillsDir);
|
|
1536
1449
|
for (const skillName of manifest.installed_skills || []) {
|
|
1537
|
-
const skillPath =
|
|
1450
|
+
const skillPath = join9(skillsDir, skillName);
|
|
1538
1451
|
if (existsSync7(skillPath) || isSymlink(skillPath)) {
|
|
1539
1452
|
files.push(skillPath);
|
|
1540
1453
|
}
|
|
1541
1454
|
}
|
|
1542
1455
|
}
|
|
1543
|
-
const agentsDir =
|
|
1456
|
+
const agentsDir = join9(configRoot, "agents");
|
|
1544
1457
|
if (existsSync7(agentsDir)) {
|
|
1545
1458
|
directories.push(agentsDir);
|
|
1546
1459
|
for (const agentName of manifest.installed_agents || []) {
|
|
1547
|
-
const agentPath =
|
|
1548
|
-
const agentPathMd =
|
|
1460
|
+
const agentPath = join9(agentsDir, agentName);
|
|
1461
|
+
const agentPathMd = join9(agentsDir, agentName + ".md");
|
|
1549
1462
|
if (existsSync7(agentPath) || isSymlink(agentPath)) {
|
|
1550
1463
|
files.push(agentPath);
|
|
1551
1464
|
} else if (existsSync7(agentPathMd) || isSymlink(agentPathMd)) {
|
|
@@ -1554,35 +1467,34 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
|
|
|
1554
1467
|
}
|
|
1555
1468
|
}
|
|
1556
1469
|
}
|
|
1557
|
-
const workflowsLink =
|
|
1470
|
+
const workflowsLink = join9(configRoot, "workflows");
|
|
1558
1471
|
if (isSymlink(workflowsLink)) {
|
|
1559
1472
|
files.push(workflowsLink);
|
|
1560
1473
|
}
|
|
1561
|
-
const manifestPath =
|
|
1474
|
+
const manifestPath = join9(configRoot, "cannbot-manifest.json");
|
|
1562
1475
|
if (existsSync7(manifestPath)) {
|
|
1563
1476
|
files.push(manifestPath);
|
|
1564
1477
|
}
|
|
1565
|
-
const pluginManifestPath =
|
|
1478
|
+
const pluginManifestPath = join9(configRoot, `${pluginId}-manifest.json`);
|
|
1566
1479
|
if (existsSync7(pluginManifestPath)) {
|
|
1567
1480
|
files.push(pluginManifestPath);
|
|
1568
1481
|
}
|
|
1569
1482
|
const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
1570
|
-
const configFilePath = level === "project" ?
|
|
1483
|
+
const configFilePath = level === "project" ? join9(installPath, configFileName) : join9(configRoot, configFileName);
|
|
1571
1484
|
if (existsSync7(configFilePath)) {
|
|
1572
1485
|
files.push(configFilePath);
|
|
1573
1486
|
}
|
|
1574
1487
|
const repoLinks = ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
|
|
1575
1488
|
for (const repoName of repoLinks) {
|
|
1576
|
-
const repoLinkPath =
|
|
1489
|
+
const repoLinkPath = join9(installPath, repoName);
|
|
1577
1490
|
if (isSymlink(repoLinkPath)) {
|
|
1578
1491
|
files.push(repoLinkPath);
|
|
1579
1492
|
}
|
|
1580
|
-
const repoLinkInConfig =
|
|
1493
|
+
const repoLinkInConfig = join9(configRoot, repoName);
|
|
1581
1494
|
if (isSymlink(repoLinkInConfig)) {
|
|
1582
1495
|
files.push(repoLinkInConfig);
|
|
1583
1496
|
}
|
|
1584
1497
|
}
|
|
1585
|
-
directories.push(configRoot);
|
|
1586
1498
|
return {
|
|
1587
1499
|
pluginId,
|
|
1588
1500
|
displayName,
|
|
@@ -1603,7 +1515,7 @@ function isSymlink(path) {
|
|
|
1603
1515
|
}
|
|
1604
1516
|
}
|
|
1605
1517
|
function getSkillRecordPath() {
|
|
1606
|
-
return
|
|
1518
|
+
return join9(getInstallsDir(), "skills.json");
|
|
1607
1519
|
}
|
|
1608
1520
|
function readSkillRecord() {
|
|
1609
1521
|
const recordPath = getSkillRecordPath();
|
|
@@ -1611,7 +1523,7 @@ function readSkillRecord() {
|
|
|
1611
1523
|
return {};
|
|
1612
1524
|
}
|
|
1613
1525
|
try {
|
|
1614
|
-
const content =
|
|
1526
|
+
const content = readFileSync7(recordPath, "utf-8");
|
|
1615
1527
|
return JSON.parse(content);
|
|
1616
1528
|
} catch {
|
|
1617
1529
|
return {};
|
|
@@ -1659,7 +1571,186 @@ function removeSkillsFromRecord(skillIds, tool, level, installPath) {
|
|
|
1659
1571
|
}
|
|
1660
1572
|
function getInstalledSkills(tool, level, installPath) {
|
|
1661
1573
|
const record = readSkillRecord();
|
|
1662
|
-
|
|
1574
|
+
const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
|
|
1575
|
+
const configRoot = getConfigRoot(tool, level);
|
|
1576
|
+
const skillsDir = join9(configRoot, "skills");
|
|
1577
|
+
return recordedSkills.filter((skillId) => {
|
|
1578
|
+
const skillPath = join9(skillsDir, skillId);
|
|
1579
|
+
return existsSync7(skillPath) || isSymlink(skillPath);
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// src/core/backup.ts
|
|
1584
|
+
import { existsSync as existsSync8, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
1585
|
+
import { join as join10 } from "path";
|
|
1586
|
+
function getAgentsFileName(tool) {
|
|
1587
|
+
return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
1588
|
+
}
|
|
1589
|
+
function detectCurrentPlugin(configRoot, tool) {
|
|
1590
|
+
const manifest = readManifest(configRoot);
|
|
1591
|
+
if (manifest?.team) {
|
|
1592
|
+
const plugin = findPlugin(manifest.team);
|
|
1593
|
+
if (plugin) {
|
|
1594
|
+
return {
|
|
1595
|
+
pluginId: plugin.id,
|
|
1596
|
+
pluginName: plugin.displayName
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
const agentsFile = join10(configRoot, getAgentsFileName(tool));
|
|
1601
|
+
if (!existsSync8(agentsFile)) {
|
|
1602
|
+
return null;
|
|
1603
|
+
}
|
|
1604
|
+
const allRecords = readAllRecords();
|
|
1605
|
+
for (const record of allRecords) {
|
|
1606
|
+
if (record.configRoot === configRoot && record.tool === tool) {
|
|
1607
|
+
const plugin = findPlugin(record.pluginId);
|
|
1608
|
+
if (plugin) {
|
|
1609
|
+
return {
|
|
1610
|
+
pluginId: plugin.id,
|
|
1611
|
+
pluginName: plugin.displayName
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
return null;
|
|
1617
|
+
}
|
|
1618
|
+
function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
|
|
1619
|
+
const agentsFile = join10(configRoot, getAgentsFileName(tool));
|
|
1620
|
+
if (!existsSync8(agentsFile)) {
|
|
1621
|
+
return null;
|
|
1622
|
+
}
|
|
1623
|
+
const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
|
|
1624
|
+
const backupFileName = `${getAgentsFileName(tool)}.cannbot-backup.${fromPluginId}.${timestamp}`;
|
|
1625
|
+
const backupPath = join10(configRoot, backupFileName);
|
|
1626
|
+
try {
|
|
1627
|
+
copyFileSync(agentsFile, backupPath);
|
|
1628
|
+
return {
|
|
1629
|
+
filePath: backupPath,
|
|
1630
|
+
pluginId: fromPluginId,
|
|
1631
|
+
pluginName: fromPluginName,
|
|
1632
|
+
backupTime: timestamp
|
|
1633
|
+
};
|
|
1634
|
+
} catch {
|
|
1635
|
+
return null;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
function findBackups(configRoot) {
|
|
1639
|
+
if (!existsSync8(configRoot)) {
|
|
1640
|
+
return [];
|
|
1641
|
+
}
|
|
1642
|
+
const files = readdirSync3(configRoot);
|
|
1643
|
+
const backups = [];
|
|
1644
|
+
for (const file of files) {
|
|
1645
|
+
if (file.includes(".cannbot-backup.")) {
|
|
1646
|
+
const parts = file.split(".cannbot-backup.");
|
|
1647
|
+
if (parts.length === 2) {
|
|
1648
|
+
const pluginIdAndTime = parts[1];
|
|
1649
|
+
const lastDotIndex = pluginIdAndTime.lastIndexOf(".");
|
|
1650
|
+
if (lastDotIndex > 0) {
|
|
1651
|
+
const pluginId = pluginIdAndTime.substring(0, lastDotIndex);
|
|
1652
|
+
const timestamp = pluginIdAndTime.substring(lastDotIndex + 1);
|
|
1653
|
+
const plugin = findPlugin(pluginId);
|
|
1654
|
+
backups.push({
|
|
1655
|
+
filePath: join10(configRoot, file),
|
|
1656
|
+
pluginId,
|
|
1657
|
+
pluginName: plugin?.displayName || pluginId,
|
|
1658
|
+
backupTime: timestamp
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return backups.sort((a, b) => b.backupTime.localeCompare(a.backupTime));
|
|
1665
|
+
}
|
|
1666
|
+
function restoreBackup(backupPath, configRoot, tool) {
|
|
1667
|
+
try {
|
|
1668
|
+
const agentsFile = join10(configRoot, getAgentsFileName(tool));
|
|
1669
|
+
copyFileSync(backupPath, agentsFile);
|
|
1670
|
+
return true;
|
|
1671
|
+
} catch {
|
|
1672
|
+
return false;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
function deleteBackup(backupPath) {
|
|
1676
|
+
try {
|
|
1677
|
+
if (existsSync8(backupPath)) {
|
|
1678
|
+
unlinkSync2(backupPath);
|
|
1679
|
+
return true;
|
|
1680
|
+
}
|
|
1681
|
+
return false;
|
|
1682
|
+
} catch {
|
|
1683
|
+
return false;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
function formatTimestamp(date) {
|
|
1687
|
+
const year = date.getFullYear();
|
|
1688
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
1689
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
1690
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
1691
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
1692
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
1693
|
+
return `${year}${month}${day}-${hours}${minutes}${seconds}`;
|
|
1694
|
+
}
|
|
1695
|
+
function readAllRecords() {
|
|
1696
|
+
const records = [];
|
|
1697
|
+
const plugins = [
|
|
1698
|
+
"ops-direct-invoke",
|
|
1699
|
+
"ops-direct-invoke-flash",
|
|
1700
|
+
"ops-registry-invoke",
|
|
1701
|
+
"pypto-op-orchestrator",
|
|
1702
|
+
"triton-op-generator",
|
|
1703
|
+
"tilelang-op-orchestrator",
|
|
1704
|
+
"model-infer-optimize",
|
|
1705
|
+
"catlass-op-generator",
|
|
1706
|
+
"ops-code-reviewer",
|
|
1707
|
+
"torch-compile"
|
|
1708
|
+
];
|
|
1709
|
+
for (const pluginId of plugins) {
|
|
1710
|
+
const record = readRecord(pluginId);
|
|
1711
|
+
if (record) {
|
|
1712
|
+
records.push(record);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
return records;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// src/ui/backup-prompts.ts
|
|
1719
|
+
import { select as select2 } from "@inquirer/prompts";
|
|
1720
|
+
import chalk4 from "chalk";
|
|
1721
|
+
async function showOverwriteWarning(currentPluginName, newPluginName) {
|
|
1722
|
+
console.log();
|
|
1723
|
+
console.log(chalk4.yellow(` \u26A0\uFE0F ${t("backup_detected")}`));
|
|
1724
|
+
console.log();
|
|
1725
|
+
console.log(` ${t("backup_current")}: ${chalk4.cyan(currentPluginName)}`);
|
|
1726
|
+
console.log(` ${t("backup_installing")}: ${chalk4.cyan(newPluginName)}`);
|
|
1727
|
+
console.log();
|
|
1728
|
+
console.log(` ${t("backup_overwrite_warning")}`);
|
|
1729
|
+
console.log(` ${t("backup_auto_create")}`);
|
|
1730
|
+
console.log();
|
|
1731
|
+
const choice = await select2({
|
|
1732
|
+
message: t("backup_select_action"),
|
|
1733
|
+
choices: [
|
|
1734
|
+
{ name: t("backup_overwrite"), value: "overwrite" },
|
|
1735
|
+
{ name: t("backup_cancel"), value: "cancel" }
|
|
1736
|
+
]
|
|
1737
|
+
});
|
|
1738
|
+
return choice;
|
|
1739
|
+
}
|
|
1740
|
+
async function showRestorePrompt(backups) {
|
|
1741
|
+
console.log();
|
|
1742
|
+
console.log(chalk4.cyan(` ${t("backup_restore_prompt")}`));
|
|
1743
|
+
console.log();
|
|
1744
|
+
const choices = backups.map((b) => ({
|
|
1745
|
+
name: `${t("backup_restore")} ${b.pluginName} (${b.backupTime})`,
|
|
1746
|
+
value: b.filePath
|
|
1747
|
+
}));
|
|
1748
|
+
choices.push({ name: t("backup_only_uninstall"), value: "none" });
|
|
1749
|
+
const choice = await select2({
|
|
1750
|
+
message: t("backup_select_action"),
|
|
1751
|
+
choices
|
|
1752
|
+
});
|
|
1753
|
+
return choice;
|
|
1663
1754
|
}
|
|
1664
1755
|
|
|
1665
1756
|
// src/core/installer.ts
|
|
@@ -1675,8 +1766,8 @@ async function installPlugin(opts) {
|
|
|
1675
1766
|
warnings: []
|
|
1676
1767
|
};
|
|
1677
1768
|
}
|
|
1678
|
-
const scriptPath =
|
|
1679
|
-
if (!
|
|
1769
|
+
const scriptPath = join11(opts.repoPath, plugin.dir, plugin.script);
|
|
1770
|
+
if (!existsSync9(scriptPath)) {
|
|
1680
1771
|
return {
|
|
1681
1772
|
success: false,
|
|
1682
1773
|
pluginId: opts.pluginId,
|
|
@@ -1691,13 +1782,49 @@ async function installPlugin(opts) {
|
|
|
1691
1782
|
args.push(opts.installPath);
|
|
1692
1783
|
}
|
|
1693
1784
|
const cwd = opts.installPath || process.cwd();
|
|
1785
|
+
const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
|
|
1786
|
+
const agentsFile = join11(configRoot, getAgentsFileName(opts.tool));
|
|
1787
|
+
let backupInfo = null;
|
|
1788
|
+
if (existsSync9(agentsFile)) {
|
|
1789
|
+
const currentPlugin = detectCurrentPlugin(configRoot, opts.tool);
|
|
1790
|
+
if (currentPlugin && currentPlugin.pluginId !== opts.pluginId) {
|
|
1791
|
+
let choice = "overwrite";
|
|
1792
|
+
if (!opts.yes) {
|
|
1793
|
+
choice = await showOverwriteWarning(
|
|
1794
|
+
currentPlugin.pluginName,
|
|
1795
|
+
plugin.displayName
|
|
1796
|
+
);
|
|
1797
|
+
}
|
|
1798
|
+
if (choice === "cancel") {
|
|
1799
|
+
logger.info(t("backup_cancel"));
|
|
1800
|
+
return {
|
|
1801
|
+
success: false,
|
|
1802
|
+
pluginId: opts.pluginId,
|
|
1803
|
+
skillsCount: 0,
|
|
1804
|
+
agentsCount: 0,
|
|
1805
|
+
errors: [t("backup_cancel")],
|
|
1806
|
+
warnings: []
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
backupInfo = createBackup(
|
|
1810
|
+
configRoot,
|
|
1811
|
+
opts.tool,
|
|
1812
|
+
currentPlugin.pluginId,
|
|
1813
|
+
currentPlugin.pluginName
|
|
1814
|
+
);
|
|
1815
|
+
if (backupInfo) {
|
|
1816
|
+
logger.success(`${t("backup_created")}: ${backupInfo.filePath}`);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1694
1820
|
try {
|
|
1695
|
-
|
|
1821
|
+
const execOptions = {
|
|
1696
1822
|
cwd,
|
|
1697
1823
|
timeout: 3e5,
|
|
1698
|
-
stdio: "pipe"
|
|
1699
|
-
|
|
1700
|
-
|
|
1824
|
+
stdio: "pipe",
|
|
1825
|
+
input: opts.yes ? "y\n" : void 0
|
|
1826
|
+
};
|
|
1827
|
+
await execa3("bash", [scriptPath, ...args], execOptions);
|
|
1701
1828
|
const manifest = readManifest(configRoot);
|
|
1702
1829
|
let skillsCount = 0;
|
|
1703
1830
|
let agentsCount = 0;
|
|
@@ -1718,6 +1845,14 @@ async function installPlugin(opts) {
|
|
|
1718
1845
|
configRoot,
|
|
1719
1846
|
manifest
|
|
1720
1847
|
);
|
|
1848
|
+
if (backupInfo) {
|
|
1849
|
+
record.backup = {
|
|
1850
|
+
filePath: backupInfo.filePath,
|
|
1851
|
+
fromPluginId: backupInfo.pluginId,
|
|
1852
|
+
fromPluginName: backupInfo.pluginName,
|
|
1853
|
+
backupTime: backupInfo.backupTime
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1721
1856
|
writeRecord(record);
|
|
1722
1857
|
} catch {
|
|
1723
1858
|
}
|
|
@@ -1743,16 +1878,16 @@ async function installPlugin(opts) {
|
|
|
1743
1878
|
}
|
|
1744
1879
|
|
|
1745
1880
|
// src/core/skill-installer.ts
|
|
1746
|
-
import { existsSync as
|
|
1747
|
-
import { join as
|
|
1748
|
-
import { select as
|
|
1749
|
-
import
|
|
1881
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync3, readdirSync as readdirSync4, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
|
|
1882
|
+
import { join as join12 } from "path";
|
|
1883
|
+
import { select as select3, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
|
|
1884
|
+
import chalk5 from "chalk";
|
|
1750
1885
|
import Table from "cli-table3";
|
|
1751
1886
|
async function installSkills(skillIds, tool, level, repoPath) {
|
|
1752
1887
|
const configRoot = getConfigRoot(tool, level);
|
|
1753
|
-
const skillsDir =
|
|
1888
|
+
const skillsDir = join12(configRoot, "skills");
|
|
1754
1889
|
const installPath = level === "project" ? process.cwd() : configRoot;
|
|
1755
|
-
if (!
|
|
1890
|
+
if (!existsSync10(skillsDir)) {
|
|
1756
1891
|
mkdirSync5(skillsDir, { recursive: true });
|
|
1757
1892
|
}
|
|
1758
1893
|
const results = [];
|
|
@@ -1763,15 +1898,15 @@ async function installSkills(skillIds, tool, level, repoPath) {
|
|
|
1763
1898
|
results.push({ skillId, success: false, error: "Skill not found in registry" });
|
|
1764
1899
|
continue;
|
|
1765
1900
|
}
|
|
1766
|
-
const sourcePath =
|
|
1767
|
-
const targetPath =
|
|
1768
|
-
if (!
|
|
1901
|
+
const sourcePath = join12(repoPath, skill.source, skill.id);
|
|
1902
|
+
const targetPath = join12(skillsDir, skillId);
|
|
1903
|
+
if (!existsSync10(sourcePath)) {
|
|
1769
1904
|
results.push({ skillId, success: false, error: `Source not found: ${sourcePath}` });
|
|
1770
1905
|
continue;
|
|
1771
1906
|
}
|
|
1772
1907
|
try {
|
|
1773
|
-
if (
|
|
1774
|
-
|
|
1908
|
+
if (existsSync10(targetPath) || isSymlink2(targetPath)) {
|
|
1909
|
+
unlinkSync3(targetPath);
|
|
1775
1910
|
}
|
|
1776
1911
|
symlinkSync(realpathSync(sourcePath), targetPath);
|
|
1777
1912
|
results.push({ skillId, success: true });
|
|
@@ -1787,21 +1922,21 @@ async function installSkills(skillIds, tool, level, repoPath) {
|
|
|
1787
1922
|
}
|
|
1788
1923
|
async function uninstallSkills(skillIds, tool, level) {
|
|
1789
1924
|
const configRoot = getConfigRoot(tool, level);
|
|
1790
|
-
const skillsDir =
|
|
1925
|
+
const skillsDir = join12(configRoot, "skills");
|
|
1791
1926
|
const installPath = level === "project" ? process.cwd() : configRoot;
|
|
1792
1927
|
const results = [];
|
|
1793
1928
|
const removedIds = [];
|
|
1794
1929
|
for (const skillId of skillIds) {
|
|
1795
|
-
const targetPath =
|
|
1930
|
+
const targetPath = join12(skillsDir, skillId);
|
|
1796
1931
|
try {
|
|
1797
|
-
if (
|
|
1798
|
-
|
|
1932
|
+
if (existsSync10(targetPath) || isSymlink2(targetPath)) {
|
|
1933
|
+
unlinkSync3(targetPath);
|
|
1799
1934
|
results.push({ skillId, success: true });
|
|
1800
1935
|
removedIds.push(skillId);
|
|
1801
|
-
logger.step(`
|
|
1936
|
+
logger.step(` ${t("uninstall_remove_label")}: ${skillId}`);
|
|
1802
1937
|
} else {
|
|
1803
1938
|
results.push({ skillId, success: false, error: "Not installed" });
|
|
1804
|
-
logger.warn(`${skillId}
|
|
1939
|
+
logger.warn(`${skillId} ${t("uninstall_skill_not_installed")}`);
|
|
1805
1940
|
}
|
|
1806
1941
|
} catch (error) {
|
|
1807
1942
|
results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
|
|
@@ -1810,103 +1945,121 @@ async function uninstallSkills(skillIds, tool, level) {
|
|
|
1810
1945
|
if (removedIds.length > 0) {
|
|
1811
1946
|
removeSkillsFromRecord(removedIds, tool, level, installPath);
|
|
1812
1947
|
}
|
|
1813
|
-
if (
|
|
1948
|
+
if (existsSync10(skillsDir)) {
|
|
1814
1949
|
try {
|
|
1815
|
-
const entries =
|
|
1950
|
+
const entries = readdirSync4(skillsDir);
|
|
1816
1951
|
if (entries.length === 0) {
|
|
1817
1952
|
rmdirSync(skillsDir);
|
|
1818
|
-
logger.step(`
|
|
1819
|
-
}
|
|
1820
|
-
} catch {
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
if (existsSync9(configRoot)) {
|
|
1824
|
-
try {
|
|
1825
|
-
const entries = readdirSync3(configRoot);
|
|
1826
|
-
if (entries.length === 0) {
|
|
1827
|
-
rmdirSync(configRoot);
|
|
1828
|
-
logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${configRoot.split("/").pop()}/`);
|
|
1953
|
+
logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
|
|
1829
1954
|
}
|
|
1830
1955
|
} catch {
|
|
1831
1956
|
}
|
|
1832
1957
|
}
|
|
1833
1958
|
return results;
|
|
1834
1959
|
}
|
|
1835
|
-
|
|
1960
|
+
var SELECT_ALL = "__select_all__";
|
|
1961
|
+
var SELECT_ALL_CATEGORY = "__select_all_category__";
|
|
1962
|
+
var CONTINUE_SELECT = "__continue_select__";
|
|
1963
|
+
async function interactiveSkillSelect(tool, level) {
|
|
1836
1964
|
const BACK3 = "__back__";
|
|
1837
1965
|
const CANCEL3 = "__cancel__";
|
|
1838
1966
|
const categories = getAllCategories();
|
|
1967
|
+
const allSkillIds = getAllSkills().map((s) => s.id);
|
|
1968
|
+
let accumulated = [];
|
|
1839
1969
|
let step = 0;
|
|
1840
1970
|
let selectedCategoryId = "";
|
|
1841
1971
|
while (true) {
|
|
1842
1972
|
switch (step) {
|
|
1843
1973
|
case 0: {
|
|
1844
|
-
|
|
1974
|
+
const titleSuffix = accumulated.length > 0 ? `${t("wizard_step_3_skill_title")}\uFF08${t("skill_count_format").replace("{count}", String(accumulated.length))}\uFF09` : t("wizard_step_3_skill_title");
|
|
1975
|
+
printBoxTitle(titleSuffix);
|
|
1845
1976
|
const categoryChoices = [
|
|
1977
|
+
{
|
|
1978
|
+
name: `>> ${t("skill_select_all").replace("{count}", String(allSkillIds.length))}`,
|
|
1979
|
+
value: SELECT_ALL
|
|
1980
|
+
},
|
|
1981
|
+
new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
1846
1982
|
...categories.map((cat) => ({
|
|
1847
1983
|
name: `> ${cat.name} (${cat.skills.length} skills)`,
|
|
1848
1984
|
value: cat.id
|
|
1849
1985
|
})),
|
|
1850
1986
|
new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
1987
|
+
...accumulated.length > 0 ? [{ name: `>> ${t("skill_confirm_install_count").replace("{count}", String(accumulated.length))}`, value: "install" }] : [],
|
|
1851
1988
|
{ name: "<- " + t("wizard_back"), value: BACK3 },
|
|
1852
1989
|
{ name: "x " + t("wizard_cancel"), value: CANCEL3 }
|
|
1853
1990
|
];
|
|
1854
1991
|
showOperationHints();
|
|
1855
|
-
selectedCategoryId = await
|
|
1992
|
+
selectedCategoryId = await select3({
|
|
1856
1993
|
message: t("skill_select_category"),
|
|
1857
1994
|
choices: categoryChoices,
|
|
1858
1995
|
loop: false,
|
|
1859
|
-
theme: selectTheme
|
|
1996
|
+
theme: selectTheme,
|
|
1997
|
+
pageSize: 15
|
|
1860
1998
|
});
|
|
1861
1999
|
if (selectedCategoryId === BACK3) return "back";
|
|
1862
2000
|
if (selectedCategoryId === CANCEL3) return "cancel";
|
|
2001
|
+
if (selectedCategoryId === SELECT_ALL) {
|
|
2002
|
+
return allSkillIds;
|
|
2003
|
+
}
|
|
2004
|
+
if (selectedCategoryId === "install") {
|
|
2005
|
+
return accumulated;
|
|
2006
|
+
}
|
|
1863
2007
|
step = 1;
|
|
1864
2008
|
break;
|
|
1865
2009
|
}
|
|
1866
2010
|
case 1: {
|
|
1867
2011
|
const category = categories.find((c) => c.id === selectedCategoryId);
|
|
1868
2012
|
if (!category) return [];
|
|
1869
|
-
printBoxTitle(
|
|
1870
|
-
const
|
|
1871
|
-
const
|
|
1872
|
-
const installedSkills = [];
|
|
1873
|
-
for (const tk of tools) {
|
|
1874
|
-
for (const l of levels) {
|
|
1875
|
-
const path = l === "project" ? process.cwd() : getConfigRoot(tk, l);
|
|
1876
|
-
installedSkills.push(...getInstalledSkills(tk, l, path));
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
2013
|
+
printBoxTitle(t("wizard_step_4_skill_title").replace("{category}", category.name));
|
|
2014
|
+
const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
|
|
2015
|
+
const installedSkills = getInstalledSkills(tool, level, installPath);
|
|
1879
2016
|
const installedSet = new Set(installedSkills);
|
|
1880
|
-
const
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
name:
|
|
1885
|
-
value:
|
|
1886
|
-
checked:
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
2017
|
+
const categorySkillIds = new Set(category.skills.map((s) => s.id));
|
|
2018
|
+
const accumulatedInCategory = new Set(accumulated.filter((id) => categorySkillIds.has(id)));
|
|
2019
|
+
const skillChoices = [
|
|
2020
|
+
{
|
|
2021
|
+
name: `>> ${t("skill_select_all_category")}`,
|
|
2022
|
+
value: SELECT_ALL_CATEGORY,
|
|
2023
|
+
checked: accumulatedInCategory.size === category.skills.length,
|
|
2024
|
+
description: t("skill_select_all_category")
|
|
2025
|
+
},
|
|
2026
|
+
new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
2027
|
+
...category.skills.map((skill) => {
|
|
2028
|
+
const isInstalled = installedSet.has(skill.id);
|
|
2029
|
+
const isAccumulated = accumulatedInCategory.has(skill.id);
|
|
2030
|
+
const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : isAccumulated ? " [+]" : "";
|
|
2031
|
+
return {
|
|
2032
|
+
name: `${chalk5.cyan(skill.id)}${suffix}`,
|
|
2033
|
+
value: skill.id,
|
|
2034
|
+
checked: isAccumulated,
|
|
2035
|
+
description: skill.description
|
|
2036
|
+
};
|
|
2037
|
+
})
|
|
2038
|
+
];
|
|
1892
2039
|
showOperationHints(true);
|
|
1893
2040
|
const selectedSkills = await checkbox2({
|
|
1894
2041
|
message: t("skill_select_items"),
|
|
1895
2042
|
choices: skillChoices,
|
|
1896
2043
|
loop: false,
|
|
1897
2044
|
instructions: false,
|
|
1898
|
-
theme: checkboxTheme
|
|
2045
|
+
theme: checkboxTheme,
|
|
2046
|
+
pageSize: 15
|
|
1899
2047
|
});
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
2048
|
+
let skillIds;
|
|
2049
|
+
if (selectedSkills.includes(SELECT_ALL_CATEGORY)) {
|
|
2050
|
+
skillIds = category.skills.map((s) => s.id);
|
|
2051
|
+
} else {
|
|
2052
|
+
skillIds = selectedSkills.filter((id) => id !== SELECT_ALL_CATEGORY);
|
|
1903
2053
|
}
|
|
1904
|
-
if (selectedSkills.includes(CANCEL3)) return "cancel";
|
|
1905
|
-
const skillIds = selectedSkills.filter((v) => v !== BACK3 && v !== CANCEL3);
|
|
1906
2054
|
if (skillIds.length === 0) {
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
2055
|
+
accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
|
|
2056
|
+
const action2 = await select3({
|
|
2057
|
+
message: accumulated.length > 0 ? t("skill_continue_or_install").replace("{count}", String(accumulated.length)) : t("wizard_no_selection"),
|
|
2058
|
+
choices: accumulated.length > 0 ? [
|
|
2059
|
+
{ name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
|
|
2060
|
+
{ name: "<- " + t("wizard_back_to_reselect"), value: "back" },
|
|
2061
|
+
{ name: "x " + t("wizard_cancel"), value: "cancel" }
|
|
2062
|
+
] : [
|
|
1910
2063
|
new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
1911
2064
|
{ name: "<- " + t("wizard_back_to_reselect"), value: "back" },
|
|
1912
2065
|
{ name: "x " + t("wizard_cancel"), value: "cancel" }
|
|
@@ -1914,13 +2067,38 @@ async function interactiveSkillSelect() {
|
|
|
1914
2067
|
loop: false,
|
|
1915
2068
|
theme: selectTheme
|
|
1916
2069
|
});
|
|
1917
|
-
if (
|
|
2070
|
+
if (action2 === "install") return accumulated;
|
|
2071
|
+
if (action2 === "back") {
|
|
1918
2072
|
step = 0;
|
|
1919
2073
|
break;
|
|
1920
2074
|
}
|
|
1921
2075
|
return "cancel";
|
|
1922
2076
|
}
|
|
1923
|
-
|
|
2077
|
+
accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
|
|
2078
|
+
for (const id of skillIds) {
|
|
2079
|
+
if (!accumulated.includes(id)) {
|
|
2080
|
+
accumulated.push(id);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
const action = await select3({
|
|
2084
|
+
message: t("skill_continue_or_install").replace("{count}", String(accumulated.length)),
|
|
2085
|
+
choices: [
|
|
2086
|
+
{ name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
|
|
2087
|
+
{ name: "> " + t("skill_continue_select"), value: CONTINUE_SELECT },
|
|
2088
|
+
new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
2089
|
+
{ name: "x " + t("wizard_cancel"), value: "cancel" }
|
|
2090
|
+
],
|
|
2091
|
+
loop: false,
|
|
2092
|
+
theme: selectTheme
|
|
2093
|
+
});
|
|
2094
|
+
if (action === "install") {
|
|
2095
|
+
return accumulated;
|
|
2096
|
+
}
|
|
2097
|
+
if (action === CONTINUE_SELECT) {
|
|
2098
|
+
step = 0;
|
|
2099
|
+
break;
|
|
2100
|
+
}
|
|
2101
|
+
return "cancel";
|
|
1924
2102
|
}
|
|
1925
2103
|
}
|
|
1926
2104
|
}
|
|
@@ -1928,11 +2106,11 @@ async function interactiveSkillSelect() {
|
|
|
1928
2106
|
function listAllSkills() {
|
|
1929
2107
|
const categories = getAllCategories();
|
|
1930
2108
|
console.log();
|
|
1931
|
-
console.log(
|
|
1932
|
-
console.log(
|
|
2109
|
+
console.log(chalk5.bold(` ${t("skill_list_title")}`));
|
|
2110
|
+
console.log(chalk5.dim(" " + "\u2500".repeat(60)));
|
|
1933
2111
|
for (const category of categories) {
|
|
1934
2112
|
console.log();
|
|
1935
|
-
console.log(
|
|
2113
|
+
console.log(chalk5.bold(` ${category.name}`) + chalk5.dim(` (${category.skills.length} skills)`));
|
|
1936
2114
|
const table = new Table({
|
|
1937
2115
|
style: { head: [], border: [] },
|
|
1938
2116
|
colWidths: [35, 45],
|
|
@@ -1940,14 +2118,14 @@ function listAllSkills() {
|
|
|
1940
2118
|
});
|
|
1941
2119
|
for (const skill of category.skills) {
|
|
1942
2120
|
table.push([
|
|
1943
|
-
|
|
2121
|
+
chalk5.cyan(skill.id),
|
|
1944
2122
|
skill.description
|
|
1945
2123
|
]);
|
|
1946
2124
|
}
|
|
1947
2125
|
console.log(table.toString());
|
|
1948
2126
|
}
|
|
1949
2127
|
console.log();
|
|
1950
|
-
console.log(
|
|
2128
|
+
console.log(chalk5.dim(` ${t("skill_total_count").replace("{count}", String(getAllSkills().length))}`));
|
|
1951
2129
|
console.log();
|
|
1952
2130
|
}
|
|
1953
2131
|
function isSymlink2(path) {
|
|
@@ -1959,22 +2137,22 @@ function isSymlink2(path) {
|
|
|
1959
2137
|
}
|
|
1960
2138
|
|
|
1961
2139
|
// src/ui/display.ts
|
|
1962
|
-
import
|
|
2140
|
+
import chalk6 from "chalk";
|
|
1963
2141
|
import Table2 from "cli-table3";
|
|
1964
2142
|
function printPluginList(plugins, installed) {
|
|
1965
2143
|
const table = new Table2({
|
|
1966
2144
|
head: [
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
2145
|
+
chalk6.cyan(t("list_id")),
|
|
2146
|
+
chalk6.cyan(t("list_name")),
|
|
2147
|
+
chalk6.cyan(t("list_status")),
|
|
2148
|
+
chalk6.cyan(t("list_skills")),
|
|
2149
|
+
chalk6.cyan(t("list_agents"))
|
|
1972
2150
|
],
|
|
1973
2151
|
style: { head: [], border: [] }
|
|
1974
2152
|
});
|
|
1975
2153
|
plugins.forEach((plugin, index) => {
|
|
1976
2154
|
const isInstalled = installed.has(plugin.id);
|
|
1977
|
-
const status = isInstalled ?
|
|
2155
|
+
const status = isInstalled ? chalk6.green(`\u2713 ${t("status_installed")}`) : chalk6.dim(`\u2014`);
|
|
1978
2156
|
table.push([
|
|
1979
2157
|
String(index + 1),
|
|
1980
2158
|
plugin.displayName,
|
|
@@ -1984,7 +2162,8 @@ function printPluginList(plugins, installed) {
|
|
|
1984
2162
|
]);
|
|
1985
2163
|
});
|
|
1986
2164
|
console.log();
|
|
1987
|
-
|
|
2165
|
+
const countSuffix = t("count_suffix");
|
|
2166
|
+
console.log(chalk6.bold(` ${t("list_title")} (${plugins.length}${countSuffix ? " " + countSuffix : ""})`));
|
|
1988
2167
|
console.log(table.toString());
|
|
1989
2168
|
console.log();
|
|
1990
2169
|
}
|
|
@@ -1993,7 +2172,7 @@ function printInstallSummary(results) {
|
|
|
1993
2172
|
const totalCount = results.length;
|
|
1994
2173
|
console.log();
|
|
1995
2174
|
console.log(
|
|
1996
|
-
|
|
2175
|
+
chalk6.bold(
|
|
1997
2176
|
` ${t("install_done")}! ${successCount}/${totalCount} ${t("install_success")}`
|
|
1998
2177
|
)
|
|
1999
2178
|
);
|
|
@@ -2001,10 +2180,10 @@ function printInstallSummary(results) {
|
|
|
2001
2180
|
for (const result of results) {
|
|
2002
2181
|
if (result.success) {
|
|
2003
2182
|
console.log(
|
|
2004
|
-
|
|
2183
|
+
chalk6.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
|
|
2005
2184
|
);
|
|
2006
2185
|
} else {
|
|
2007
|
-
console.log(
|
|
2186
|
+
console.log(chalk6.red(" \u2717") + ` ${result.displayName}`);
|
|
2008
2187
|
}
|
|
2009
2188
|
}
|
|
2010
2189
|
console.log();
|
|
@@ -2017,22 +2196,25 @@ function printEnhancedSummary(results, tool, configRoot) {
|
|
|
2017
2196
|
return;
|
|
2018
2197
|
}
|
|
2019
2198
|
const toolName = getToolDisplayName(tool);
|
|
2199
|
+
const countSuffix = t("count_suffix");
|
|
2020
2200
|
const lines = [
|
|
2021
|
-
|
|
2201
|
+
chalk6.bold(` ${t("install_done")}!`),
|
|
2022
2202
|
"",
|
|
2023
|
-
` ${
|
|
2024
|
-
` ${
|
|
2203
|
+
` ${chalk6.dim(t("install_installed_to") + ":")} ${configRoot}`,
|
|
2204
|
+
` ${chalk6.dim(t("install_skills_count") + ":")} ${totalSkills}${countSuffix ? " " + countSuffix : ""} ${chalk6.dim("|")} ${chalk6.dim(t("install_agents_count") + ":")} ${totalAgents}${countSuffix ? " " + countSuffix : ""}`,
|
|
2025
2205
|
"",
|
|
2026
|
-
|
|
2027
|
-
` ${
|
|
2028
|
-
` ${
|
|
2029
|
-
` ${
|
|
2030
|
-
` ${
|
|
2206
|
+
chalk6.bold(` ${t("install_enhanced_next_steps")}:`),
|
|
2207
|
+
` ${chalk6.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk6.green(toolName.toLowerCase())}`,
|
|
2208
|
+
` ${chalk6.cyan("2.")} ${t("install_enhanced_try")}: ${chalk6.green(t("try_prompt"))}`,
|
|
2209
|
+
` ${chalk6.cyan("3.")} ${t("install_enhanced_more")}: ${chalk6.green("install-helper list")}`,
|
|
2210
|
+
` ${chalk6.cyan("4.")} ${t("install_enhanced_check")}: ${chalk6.green("install-helper doctor")}`,
|
|
2031
2211
|
""
|
|
2032
2212
|
];
|
|
2033
2213
|
if (successResults.length === 1) {
|
|
2214
|
+
const plugin = findPlugin(successResults[0].pluginId);
|
|
2215
|
+
const docsPath = plugin ? `${plugin.dir}/quickstart.md` : `${successResults[0].pluginId}/quickstart.md`;
|
|
2034
2216
|
lines.push(
|
|
2035
|
-
` ${
|
|
2217
|
+
` ${chalk6.dim(t("install_enhanced_docs"))}: ${chalk6.dim(docsPath)}`
|
|
2036
2218
|
);
|
|
2037
2219
|
}
|
|
2038
2220
|
console.log();
|
|
@@ -2043,34 +2225,34 @@ function printEnhancedSummary(results, tool, configRoot) {
|
|
|
2043
2225
|
}
|
|
2044
2226
|
|
|
2045
2227
|
// src/commands/init.ts
|
|
2046
|
-
import { existsSync as
|
|
2047
|
-
import { join as
|
|
2228
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
|
|
2229
|
+
import { join as join13 } from "path";
|
|
2048
2230
|
var BACK2 = "__back__";
|
|
2049
2231
|
var CANCEL2 = "__cancel__";
|
|
2050
2232
|
async function initCommand() {
|
|
2051
2233
|
const cwd = process.cwd();
|
|
2052
|
-
if (
|
|
2234
|
+
if (existsSync11(join13(cwd, "package.json")) && (() => {
|
|
2053
2235
|
try {
|
|
2054
|
-
const pkg = JSON.parse(
|
|
2236
|
+
const pkg = JSON.parse(readFileSync8(join13(cwd, "package.json"), "utf-8"));
|
|
2055
2237
|
return pkg.name === "@cannbot-ai/install-helper";
|
|
2056
2238
|
} catch {
|
|
2057
2239
|
return false;
|
|
2058
2240
|
}
|
|
2059
2241
|
})()) {
|
|
2060
|
-
logger.warn("
|
|
2061
|
-
logger.info("
|
|
2062
|
-
logger.info("
|
|
2242
|
+
logger.warn(t("init_pkg_dir_warn"));
|
|
2243
|
+
logger.info(t("init_pkg_dir_hint"));
|
|
2244
|
+
logger.info(t("init_pkg_dir_example"));
|
|
2063
2245
|
return;
|
|
2064
2246
|
}
|
|
2065
2247
|
printBanner(t("wizard_title"));
|
|
2066
|
-
const spinner = createSpinner("
|
|
2248
|
+
const spinner = createSpinner(t("loading_skills_list"));
|
|
2067
2249
|
spinner.start();
|
|
2068
2250
|
try {
|
|
2069
2251
|
const repoManager = createRepositoryManager();
|
|
2070
2252
|
await repoManager.ensureRepoAndScan();
|
|
2071
|
-
spinner.succeed("
|
|
2253
|
+
spinner.succeed(t("loading_skills_list_complete"));
|
|
2072
2254
|
} catch {
|
|
2073
|
-
spinner.warn("
|
|
2255
|
+
spinner.warn(t("loading_skills_list_failed"));
|
|
2074
2256
|
}
|
|
2075
2257
|
let step = 0;
|
|
2076
2258
|
let mode = "plugin";
|
|
@@ -2104,7 +2286,7 @@ async function initCommand() {
|
|
|
2104
2286
|
}
|
|
2105
2287
|
}
|
|
2106
2288
|
async function selectMode() {
|
|
2107
|
-
printBoxTitle("
|
|
2289
|
+
printBoxTitle(t("wizard_select_mode_title"));
|
|
2108
2290
|
const CANCEL3 = "__cancel__";
|
|
2109
2291
|
const choices = [
|
|
2110
2292
|
{
|
|
@@ -2119,7 +2301,7 @@ async function selectMode() {
|
|
|
2119
2301
|
{ name: "x " + t("wizard_cancel"), value: CANCEL3 }
|
|
2120
2302
|
];
|
|
2121
2303
|
showOperationHints();
|
|
2122
|
-
const result = await
|
|
2304
|
+
const result = await select4({
|
|
2123
2305
|
message: t("wizard_select_mode"),
|
|
2124
2306
|
choices,
|
|
2125
2307
|
loop: false,
|
|
@@ -2132,14 +2314,14 @@ async function pluginInstallFlow() {
|
|
|
2132
2314
|
const answers = await runWizard();
|
|
2133
2315
|
if (!answers.confirmed) {
|
|
2134
2316
|
if (answers.back) return "back";
|
|
2135
|
-
logger.info("
|
|
2317
|
+
logger.info(t("init_cancelled"));
|
|
2136
2318
|
return "done";
|
|
2137
2319
|
}
|
|
2138
2320
|
const repoManager = createRepositoryManager();
|
|
2139
|
-
const spinner = createSpinner("
|
|
2321
|
+
const spinner = createSpinner(t("loading_plugin_data"));
|
|
2140
2322
|
spinner.start();
|
|
2141
2323
|
const repoPath = await repoManager.ensureRepo();
|
|
2142
|
-
spinner.succeed("
|
|
2324
|
+
spinner.succeed(t("loading_plugin_data_complete"));
|
|
2143
2325
|
const allPlugins = getAllPlugins();
|
|
2144
2326
|
const selectedPlugins = answers.plugins.map(
|
|
2145
2327
|
(id) => allPlugins.find((p) => p.id === id)
|
|
@@ -2193,7 +2375,7 @@ async function skillInstallFlow() {
|
|
|
2193
2375
|
const result = await selectToolWithDetection();
|
|
2194
2376
|
if (result === "back") return "back";
|
|
2195
2377
|
if (result === "cancel") {
|
|
2196
|
-
logger.info("
|
|
2378
|
+
logger.info(t("init_cancelled"));
|
|
2197
2379
|
return "done";
|
|
2198
2380
|
}
|
|
2199
2381
|
tool = result;
|
|
@@ -2207,7 +2389,7 @@ async function skillInstallFlow() {
|
|
|
2207
2389
|
break;
|
|
2208
2390
|
}
|
|
2209
2391
|
if (result === CANCEL2) {
|
|
2210
|
-
logger.info("
|
|
2392
|
+
logger.info(t("init_cancelled"));
|
|
2211
2393
|
return "done";
|
|
2212
2394
|
}
|
|
2213
2395
|
level = result;
|
|
@@ -2215,25 +2397,25 @@ async function skillInstallFlow() {
|
|
|
2215
2397
|
break;
|
|
2216
2398
|
}
|
|
2217
2399
|
case 2: {
|
|
2218
|
-
const selectedSkills = await interactiveSkillSelect();
|
|
2400
|
+
const selectedSkills = await interactiveSkillSelect(tool, level);
|
|
2219
2401
|
if (selectedSkills === "back") {
|
|
2220
2402
|
step = 1;
|
|
2221
2403
|
break;
|
|
2222
2404
|
}
|
|
2223
2405
|
if (selectedSkills === "cancel") {
|
|
2224
|
-
logger.info("
|
|
2406
|
+
logger.info(t("init_cancelled"));
|
|
2225
2407
|
return "done";
|
|
2226
2408
|
}
|
|
2227
2409
|
if (selectedSkills.length === 0) {
|
|
2228
|
-
logger.info("
|
|
2410
|
+
logger.info(t("install_no_skill_selected"));
|
|
2229
2411
|
return "done";
|
|
2230
2412
|
}
|
|
2231
2413
|
const repoManager = createRepositoryManager();
|
|
2232
|
-
const spinner = createSpinner("
|
|
2414
|
+
const spinner = createSpinner(t("loading_plugin_data"));
|
|
2233
2415
|
spinner.start();
|
|
2234
2416
|
const repoPath = await repoManager.ensureRepo();
|
|
2235
|
-
spinner.succeed("
|
|
2236
|
-
logger.info(`${t("skill_install_progress")} ${
|
|
2417
|
+
spinner.succeed(t("loading_plugin_data_complete"));
|
|
2418
|
+
logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk7.bold(String(selectedSkills.length)))}...`);
|
|
2237
2419
|
const results = await installSkills(selectedSkills, tool, level, repoPath);
|
|
2238
2420
|
let successCount = 0;
|
|
2239
2421
|
let failCount = 0;
|
|
@@ -2251,10 +2433,10 @@ async function skillInstallFlow() {
|
|
|
2251
2433
|
}
|
|
2252
2434
|
const configRoot = getConfigRoot(tool, level);
|
|
2253
2435
|
logger.blank();
|
|
2254
|
-
logger.success(`${t("skill_install_done")}: ${
|
|
2436
|
+
logger.success(`${t("skill_install_done")}: ${chalk7.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk7.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk7.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
|
|
2255
2437
|
logger.blank();
|
|
2256
|
-
logger.info(
|
|
2257
|
-
logger.info(
|
|
2438
|
+
logger.info(`${t("install_to")}: ${chalk7.cyan(configRoot + "/skills/")}`);
|
|
2439
|
+
logger.info(`${t("start_to_use").replace("{tool}", chalk7.green(tool))}`);
|
|
2258
2440
|
logger.blank();
|
|
2259
2441
|
return "done";
|
|
2260
2442
|
}
|
|
@@ -2276,32 +2458,32 @@ async function listCommand() {
|
|
|
2276
2458
|
}
|
|
2277
2459
|
|
|
2278
2460
|
// src/commands/doctor.ts
|
|
2279
|
-
import
|
|
2280
|
-
import { existsSync as
|
|
2281
|
-
import { join as
|
|
2461
|
+
import chalk8 from "chalk";
|
|
2462
|
+
import { existsSync as existsSync12, readdirSync as readdirSync5, lstatSync as lstatSync3, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6 } from "fs";
|
|
2463
|
+
import { join as join14 } from "path";
|
|
2282
2464
|
async function doctorCommand(options = {}) {
|
|
2283
2465
|
console.log();
|
|
2284
|
-
console.log(
|
|
2285
|
-
console.log(
|
|
2466
|
+
console.log(chalk8.bold(` ${t("doctor_title")}`));
|
|
2467
|
+
console.log(chalk8.dim(" " + "\u2500".repeat(46)));
|
|
2286
2468
|
let errors = 0;
|
|
2287
2469
|
let warnings = 0;
|
|
2288
2470
|
let fixes = 0;
|
|
2289
2471
|
console.log();
|
|
2290
|
-
console.log(
|
|
2472
|
+
console.log(chalk8.bold(` ${t("doctor_tools")}`));
|
|
2291
2473
|
const detectedTools = await detectTools();
|
|
2292
2474
|
const allTools = getAllTools();
|
|
2293
2475
|
for (const tool of allTools) {
|
|
2294
2476
|
const detected = detectedTools.find((d) => d.name === tool);
|
|
2295
2477
|
if (detected) {
|
|
2296
2478
|
console.log(
|
|
2297
|
-
|
|
2479
|
+
chalk8.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
|
|
2298
2480
|
);
|
|
2299
2481
|
} else {
|
|
2300
|
-
console.log(
|
|
2482
|
+
console.log(chalk8.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 ${t("doctor_not_installed")}`);
|
|
2301
2483
|
}
|
|
2302
2484
|
}
|
|
2303
2485
|
console.log();
|
|
2304
|
-
console.log(
|
|
2486
|
+
console.log(chalk8.bold(` ${t("doctor_plugins")}`));
|
|
2305
2487
|
const plugins = getAllPlugins();
|
|
2306
2488
|
const installed = scanInstalled();
|
|
2307
2489
|
const installedMap = new Map(installed.map((p) => [p.id, p]));
|
|
@@ -2309,80 +2491,80 @@ async function doctorCommand(options = {}) {
|
|
|
2309
2491
|
const inst = installedMap.get(plugin.id);
|
|
2310
2492
|
if (inst) {
|
|
2311
2493
|
console.log(
|
|
2312
|
-
|
|
2494
|
+
chalk8.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
|
|
2313
2495
|
);
|
|
2314
2496
|
} else {
|
|
2315
|
-
console.log(
|
|
2497
|
+
console.log(chalk8.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 ${t("doctor_not_installed")}`);
|
|
2316
2498
|
}
|
|
2317
2499
|
}
|
|
2318
2500
|
console.log();
|
|
2319
|
-
console.log(
|
|
2501
|
+
console.log(chalk8.bold(` ${t("doctor_links")}`));
|
|
2320
2502
|
const primaryTool = detectedTools[0]?.name || "opencode";
|
|
2321
2503
|
const configRoot = getConfigRoot(primaryTool, "project");
|
|
2322
2504
|
const skillsDir = getSkillsDir(configRoot);
|
|
2323
2505
|
const agentsDir = getAgentsDir(configRoot);
|
|
2324
|
-
if (
|
|
2506
|
+
if (existsSync12(skillsDir)) {
|
|
2325
2507
|
const brokenLinks = checkBrokenLinks(skillsDir);
|
|
2326
2508
|
if (brokenLinks === 0) {
|
|
2327
|
-
const count =
|
|
2328
|
-
console.log(
|
|
2509
|
+
const count = readdirSync5(skillsDir).length;
|
|
2510
|
+
console.log(chalk8.green(" \u2713") + ` ${skillsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
|
|
2329
2511
|
} else {
|
|
2330
|
-
console.log(
|
|
2512
|
+
console.log(chalk8.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
|
|
2331
2513
|
warnings++;
|
|
2332
2514
|
if (options.fix) {
|
|
2333
2515
|
const fixed = fixBrokenLinks(skillsDir);
|
|
2334
2516
|
fixes += fixed;
|
|
2335
|
-
console.log(
|
|
2517
|
+
console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
|
|
2336
2518
|
}
|
|
2337
2519
|
}
|
|
2338
2520
|
} else {
|
|
2339
|
-
console.log(
|
|
2521
|
+
console.log(chalk8.dim(" \u2014") + ` ${skillsDir} \u2014 ${t("doctor_not_exist")}`);
|
|
2340
2522
|
if (options.fix) {
|
|
2341
2523
|
mkdirSync6(skillsDir, { recursive: true });
|
|
2342
2524
|
fixes++;
|
|
2343
|
-
console.log(
|
|
2525
|
+
console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
|
|
2344
2526
|
}
|
|
2345
2527
|
}
|
|
2346
|
-
if (
|
|
2528
|
+
if (existsSync12(agentsDir)) {
|
|
2347
2529
|
const brokenLinks = checkBrokenLinks(agentsDir);
|
|
2348
2530
|
if (brokenLinks === 0) {
|
|
2349
|
-
const count =
|
|
2350
|
-
console.log(
|
|
2531
|
+
const count = readdirSync5(agentsDir).length;
|
|
2532
|
+
console.log(chalk8.green(" \u2713") + ` ${agentsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
|
|
2351
2533
|
} else {
|
|
2352
|
-
console.log(
|
|
2534
|
+
console.log(chalk8.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
|
|
2353
2535
|
warnings++;
|
|
2354
2536
|
if (options.fix) {
|
|
2355
2537
|
const fixed = fixBrokenLinks(agentsDir);
|
|
2356
2538
|
fixes += fixed;
|
|
2357
|
-
console.log(
|
|
2539
|
+
console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
|
|
2358
2540
|
}
|
|
2359
2541
|
}
|
|
2360
2542
|
} else {
|
|
2361
|
-
console.log(
|
|
2543
|
+
console.log(chalk8.dim(" \u2014") + ` ${agentsDir} \u2014 ${t("doctor_not_exist")}`);
|
|
2362
2544
|
if (options.fix) {
|
|
2363
2545
|
mkdirSync6(agentsDir, { recursive: true });
|
|
2364
2546
|
fixes++;
|
|
2365
|
-
console.log(
|
|
2547
|
+
console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", agentsDir)}`);
|
|
2366
2548
|
}
|
|
2367
2549
|
}
|
|
2368
2550
|
console.log();
|
|
2369
|
-
console.log(
|
|
2551
|
+
console.log(chalk8.bold(` ${t("doctor_config")}`));
|
|
2370
2552
|
const configFile = getConfigFileName(primaryTool);
|
|
2371
|
-
const configPath =
|
|
2372
|
-
if (
|
|
2373
|
-
console.log(
|
|
2553
|
+
const configPath = join14(process.cwd(), configFile);
|
|
2554
|
+
if (existsSync12(configPath)) {
|
|
2555
|
+
console.log(chalk8.green(" \u2713") + ` ${configFile} ${t("doctor_config_exists")}`);
|
|
2374
2556
|
} else {
|
|
2375
|
-
console.log(
|
|
2557
|
+
console.log(chalk8.dim(" \u2014") + ` ${configFile} ${t("doctor_config_not_exist")}`);
|
|
2376
2558
|
}
|
|
2377
2559
|
console.log();
|
|
2378
|
-
console.log(
|
|
2560
|
+
console.log(chalk8.dim(" " + "\u2500".repeat(46)));
|
|
2379
2561
|
if (options.fix && fixes > 0) {
|
|
2380
2562
|
console.log(
|
|
2381
|
-
` ${t("doctor_result")}: ${warnings}
|
|
2563
|
+
` ${t("doctor_result")}: ${t("doctor_result_with_fix").replace("{warnings}", String(warnings)).replace("{errors}", String(errors)).replace("{fixes}", String(fixes))}`
|
|
2382
2564
|
);
|
|
2383
2565
|
} else {
|
|
2384
2566
|
console.log(
|
|
2385
|
-
` ${t("doctor_result")}: ${warnings}
|
|
2567
|
+
` ${t("doctor_result")}: ${t("doctor_result_without_fix").replace("{warnings}", String(warnings)).replace("{errors}", String(errors))}`
|
|
2386
2568
|
);
|
|
2387
2569
|
}
|
|
2388
2570
|
console.log();
|
|
@@ -2390,13 +2572,13 @@ async function doctorCommand(options = {}) {
|
|
|
2390
2572
|
function checkBrokenLinks(dir) {
|
|
2391
2573
|
let broken = 0;
|
|
2392
2574
|
try {
|
|
2393
|
-
const entries =
|
|
2575
|
+
const entries = readdirSync5(dir);
|
|
2394
2576
|
for (const entry of entries) {
|
|
2395
|
-
const fullPath =
|
|
2577
|
+
const fullPath = join14(dir, entry);
|
|
2396
2578
|
try {
|
|
2397
2579
|
const stats = lstatSync3(fullPath);
|
|
2398
2580
|
if (stats.isSymbolicLink()) {
|
|
2399
|
-
if (!
|
|
2581
|
+
if (!existsSync12(fullPath)) {
|
|
2400
2582
|
broken++;
|
|
2401
2583
|
}
|
|
2402
2584
|
}
|
|
@@ -2411,14 +2593,14 @@ function checkBrokenLinks(dir) {
|
|
|
2411
2593
|
function fixBrokenLinks(dir) {
|
|
2412
2594
|
let fixed = 0;
|
|
2413
2595
|
try {
|
|
2414
|
-
const entries =
|
|
2596
|
+
const entries = readdirSync5(dir);
|
|
2415
2597
|
for (const entry of entries) {
|
|
2416
|
-
const fullPath =
|
|
2598
|
+
const fullPath = join14(dir, entry);
|
|
2417
2599
|
try {
|
|
2418
2600
|
const stats = lstatSync3(fullPath);
|
|
2419
2601
|
if (stats.isSymbolicLink()) {
|
|
2420
|
-
if (!
|
|
2421
|
-
|
|
2602
|
+
if (!existsSync12(fullPath)) {
|
|
2603
|
+
unlinkSync4(fullPath);
|
|
2422
2604
|
fixed++;
|
|
2423
2605
|
}
|
|
2424
2606
|
}
|
|
@@ -2431,36 +2613,36 @@ function fixBrokenLinks(dir) {
|
|
|
2431
2613
|
}
|
|
2432
2614
|
|
|
2433
2615
|
// src/commands/status.ts
|
|
2434
|
-
import
|
|
2616
|
+
import chalk9 from "chalk";
|
|
2435
2617
|
async function statusCommand() {
|
|
2436
2618
|
const plugins = getAllPlugins();
|
|
2437
2619
|
const installed = scanInstalled();
|
|
2438
2620
|
const installedMap = new Map(installed.map((p) => [p.id, p]));
|
|
2439
2621
|
console.log();
|
|
2440
|
-
console.log(
|
|
2622
|
+
console.log(chalk9.bold(` ${t("status_title")}`));
|
|
2441
2623
|
console.log();
|
|
2442
2624
|
if (installed.length === 0) {
|
|
2443
|
-
console.log(
|
|
2444
|
-
console.log(
|
|
2625
|
+
console.log(chalk9.dim(` ${t("status_none")}`));
|
|
2626
|
+
console.log(chalk9.dim(` ${t("status_hint")}`));
|
|
2445
2627
|
console.log();
|
|
2446
2628
|
return;
|
|
2447
2629
|
}
|
|
2448
2630
|
for (const inst of installed) {
|
|
2449
2631
|
console.log(
|
|
2450
|
-
|
|
2632
|
+
chalk9.green(" \u2713") + ` ${inst.displayName}` + chalk9.dim(` (${inst.tool}, ${inst.level})`)
|
|
2451
2633
|
);
|
|
2452
2634
|
console.log(
|
|
2453
|
-
|
|
2635
|
+
chalk9.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
|
|
2454
2636
|
);
|
|
2455
|
-
console.log(
|
|
2456
|
-
console.log(
|
|
2637
|
+
console.log(chalk9.dim(` ${inst.configRoot}`));
|
|
2638
|
+
console.log(chalk9.dim(` ${t("status_install_time")}: ${inst.installTime}`));
|
|
2457
2639
|
console.log();
|
|
2458
2640
|
}
|
|
2459
2641
|
}
|
|
2460
2642
|
|
|
2461
2643
|
// src/commands/install.ts
|
|
2462
2644
|
import { confirm } from "@inquirer/prompts";
|
|
2463
|
-
import
|
|
2645
|
+
import chalk10 from "chalk";
|
|
2464
2646
|
async function installCommand(names, options) {
|
|
2465
2647
|
if (options.list) {
|
|
2466
2648
|
try {
|
|
@@ -2471,79 +2653,138 @@ async function installCommand(names, options) {
|
|
|
2471
2653
|
listAllSkills();
|
|
2472
2654
|
return;
|
|
2473
2655
|
}
|
|
2656
|
+
if (options.all) {
|
|
2657
|
+
const scanSpinner2 = createSpinner(t("loading_skills_list"));
|
|
2658
|
+
scanSpinner2.start();
|
|
2659
|
+
try {
|
|
2660
|
+
const scanRepoManager = createRepositoryManager();
|
|
2661
|
+
await scanRepoManager.ensureRepoAndScan();
|
|
2662
|
+
scanSpinner2.succeed(t("loading_skills_list_complete"));
|
|
2663
|
+
} catch {
|
|
2664
|
+
scanSpinner2.warn(t("loading_skills_list_failed"));
|
|
2665
|
+
}
|
|
2666
|
+
const allSkills = getAllSkills();
|
|
2667
|
+
const skillIds = allSkills.map((s) => s.id);
|
|
2668
|
+
if (skillIds.length === 0) {
|
|
2669
|
+
logger.error(t("install_no_skill_selected"));
|
|
2670
|
+
return;
|
|
2671
|
+
}
|
|
2672
|
+
let tool2;
|
|
2673
|
+
if (options.tool) {
|
|
2674
|
+
tool2 = validateTool(options.tool);
|
|
2675
|
+
} else {
|
|
2676
|
+
const detected = await selectToolWithDetection();
|
|
2677
|
+
if (detected === "back" || detected === "cancel") return;
|
|
2678
|
+
tool2 = detected;
|
|
2679
|
+
}
|
|
2680
|
+
const level2 = options.level ? validateLevel(options.level) : "project";
|
|
2681
|
+
const repoManager = createRepositoryManager();
|
|
2682
|
+
const spinner = createSpinner(t("loading_plugin_data"));
|
|
2683
|
+
spinner.start();
|
|
2684
|
+
const repoPath = await repoManager.ensureRepo();
|
|
2685
|
+
spinner.succeed(t("loading_plugin_data_complete"));
|
|
2686
|
+
logger.info(`${t("skill_all_install_progress").replace("{count}", chalk10.bold(String(skillIds.length)))}`);
|
|
2687
|
+
const results = await installSkills(skillIds, tool2, level2, repoPath);
|
|
2688
|
+
let successCount = 0;
|
|
2689
|
+
let failCount = 0;
|
|
2690
|
+
const total = results.length;
|
|
2691
|
+
for (let i = 0; i < results.length; i++) {
|
|
2692
|
+
const result = results[i];
|
|
2693
|
+
const progress = `[${i + 1}/${total}]`;
|
|
2694
|
+
if (result.success) {
|
|
2695
|
+
logger.success(`${progress} ${result.skillId}`);
|
|
2696
|
+
successCount++;
|
|
2697
|
+
} else {
|
|
2698
|
+
logger.error(`${progress} ${result.skillId}: ${result.error}`);
|
|
2699
|
+
failCount++;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
const configRoot = getConfigRoot(tool2, level2);
|
|
2703
|
+
logger.blank();
|
|
2704
|
+
logger.success(`${t("skill_install_done")}: ${chalk10.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk10.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk10.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
|
|
2705
|
+
logger.blank();
|
|
2706
|
+
logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
|
|
2707
|
+
logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
|
|
2708
|
+
logger.blank();
|
|
2709
|
+
return;
|
|
2710
|
+
}
|
|
2474
2711
|
if (names.length === 0) {
|
|
2475
|
-
const scanSpinner2 = createSpinner("
|
|
2712
|
+
const scanSpinner2 = createSpinner(t("loading_skills_list"));
|
|
2476
2713
|
scanSpinner2.start();
|
|
2477
2714
|
try {
|
|
2478
2715
|
const scanRepoManager = createRepositoryManager();
|
|
2479
2716
|
await scanRepoManager.ensureRepoAndScan();
|
|
2480
|
-
scanSpinner2.succeed("
|
|
2717
|
+
scanSpinner2.succeed(t("loading_skills_list_complete"));
|
|
2481
2718
|
} catch {
|
|
2482
|
-
scanSpinner2.warn("
|
|
2719
|
+
scanSpinner2.warn(t("loading_skills_list_failed"));
|
|
2483
2720
|
}
|
|
2484
2721
|
let tool2;
|
|
2485
2722
|
if (options.tool) {
|
|
2486
|
-
tool2 = options.tool;
|
|
2723
|
+
tool2 = validateTool(options.tool);
|
|
2487
2724
|
} else {
|
|
2488
2725
|
const detected = await selectToolWithDetection();
|
|
2489
|
-
if (detected === "back") return;
|
|
2726
|
+
if (detected === "back" || detected === "cancel") return;
|
|
2490
2727
|
tool2 = detected;
|
|
2491
2728
|
}
|
|
2729
|
+
const level2 = options.level ? validateLevel(options.level) : "project";
|
|
2492
2730
|
let step = 1;
|
|
2493
2731
|
while (true) {
|
|
2494
2732
|
switch (step) {
|
|
2495
2733
|
case 1: {
|
|
2496
|
-
const selectedSkills = await interactiveSkillSelect();
|
|
2734
|
+
const selectedSkills = await interactiveSkillSelect(tool2, level2);
|
|
2497
2735
|
if (selectedSkills === "back") {
|
|
2498
2736
|
const redetected = await selectToolWithDetection();
|
|
2499
|
-
if (redetected === "back") return;
|
|
2737
|
+
if (redetected === "back" || redetected === "cancel") return;
|
|
2500
2738
|
tool2 = redetected;
|
|
2501
2739
|
break;
|
|
2502
2740
|
}
|
|
2741
|
+
if (selectedSkills === "cancel") return;
|
|
2503
2742
|
if (selectedSkills.length === 0) {
|
|
2504
|
-
logger.info("
|
|
2743
|
+
logger.info(t("install_no_skill_selected"));
|
|
2505
2744
|
return;
|
|
2506
2745
|
}
|
|
2507
2746
|
names = selectedSkills;
|
|
2508
2747
|
const repoManager = createRepositoryManager();
|
|
2509
|
-
const spinner = createSpinner(t("
|
|
2748
|
+
const spinner = createSpinner(t("loading_plugin_data"));
|
|
2510
2749
|
spinner.start();
|
|
2511
2750
|
const repoPath = await repoManager.ensureRepo();
|
|
2512
|
-
spinner.succeed(t("
|
|
2513
|
-
logger.info(`${t("skill_install_progress")} ${
|
|
2514
|
-
const level2 = options.level || "project";
|
|
2751
|
+
spinner.succeed(t("loading_plugin_data_complete"));
|
|
2752
|
+
logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(names.length)))}...`);
|
|
2515
2753
|
const results = await installSkills(names, tool2, level2, repoPath);
|
|
2516
2754
|
let successCount = 0;
|
|
2517
2755
|
let failCount = 0;
|
|
2518
|
-
|
|
2756
|
+
const total = results.length;
|
|
2757
|
+
for (let i = 0; i < results.length; i++) {
|
|
2758
|
+
const result = results[i];
|
|
2759
|
+
const progress = `[${i + 1}/${total}]`;
|
|
2519
2760
|
if (result.success) {
|
|
2520
|
-
logger.success(`${result.skillId}`);
|
|
2761
|
+
logger.success(`${progress} ${result.skillId}`);
|
|
2521
2762
|
successCount++;
|
|
2522
2763
|
} else {
|
|
2523
|
-
logger.error(`${result.skillId}: ${result.error}`);
|
|
2764
|
+
logger.error(`${progress} ${result.skillId}: ${result.error}`);
|
|
2524
2765
|
failCount++;
|
|
2525
2766
|
}
|
|
2526
2767
|
}
|
|
2527
2768
|
const configRoot = getConfigRoot(tool2, level2);
|
|
2528
2769
|
logger.blank();
|
|
2529
|
-
logger.success(`${t("skill_install_done")}: ${
|
|
2770
|
+
logger.success(`${t("skill_install_done")}: ${chalk10.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk10.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk10.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
|
|
2530
2771
|
logger.blank();
|
|
2531
|
-
logger.info(
|
|
2532
|
-
logger.info(
|
|
2772
|
+
logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
|
|
2773
|
+
logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
|
|
2533
2774
|
logger.blank();
|
|
2534
2775
|
return;
|
|
2535
2776
|
}
|
|
2536
2777
|
}
|
|
2537
2778
|
}
|
|
2538
2779
|
}
|
|
2539
|
-
const scanSpinner = createSpinner("
|
|
2780
|
+
const scanSpinner = createSpinner(t("loading_skills_list"));
|
|
2540
2781
|
scanSpinner.start();
|
|
2541
2782
|
try {
|
|
2542
2783
|
const scanRepoManager = createRepositoryManager();
|
|
2543
2784
|
await scanRepoManager.ensureRepoAndScan();
|
|
2544
|
-
scanSpinner.succeed("
|
|
2785
|
+
scanSpinner.succeed(t("loading_skills_list_complete"));
|
|
2545
2786
|
} catch {
|
|
2546
|
-
scanSpinner.warn("
|
|
2787
|
+
scanSpinner.warn(t("loading_skills_list_failed"));
|
|
2547
2788
|
}
|
|
2548
2789
|
const plugins = [];
|
|
2549
2790
|
const skills = [];
|
|
@@ -2560,22 +2801,22 @@ async function installCommand(names, options) {
|
|
|
2560
2801
|
}
|
|
2561
2802
|
logger.error(`${t("error_plugin_not_found")}: ${name}`);
|
|
2562
2803
|
const allPlugins = getAllPlugins();
|
|
2563
|
-
logger.info("
|
|
2804
|
+
logger.info(t("available_plugins") + ":");
|
|
2564
2805
|
for (const p of allPlugins) {
|
|
2565
2806
|
logger.step(` ${p.id} (${p.aliases.join(", ")})`);
|
|
2566
2807
|
}
|
|
2567
|
-
logger.info(
|
|
2808
|
+
logger.info(`${t("available_skills")}: ${chalk10.cyan("install-helper install --list")}`);
|
|
2568
2809
|
return;
|
|
2569
2810
|
}
|
|
2570
2811
|
let tool;
|
|
2571
2812
|
if (options.tool) {
|
|
2572
|
-
tool = options.tool;
|
|
2813
|
+
tool = validateTool(options.tool);
|
|
2573
2814
|
} else {
|
|
2574
2815
|
const detected = await selectToolWithDetection();
|
|
2575
|
-
if (detected === "back") return;
|
|
2816
|
+
if (detected === "back" || detected === "cancel") return;
|
|
2576
2817
|
tool = detected;
|
|
2577
2818
|
}
|
|
2578
|
-
const level = options.level
|
|
2819
|
+
const level = options.level ? validateLevel(options.level) : "project";
|
|
2579
2820
|
if (plugins.length > 0) {
|
|
2580
2821
|
const configRoot = getConfigRoot(tool, level);
|
|
2581
2822
|
const manifests = readAllManifests(configRoot);
|
|
@@ -2603,16 +2844,18 @@ async function installCommand(names, options) {
|
|
|
2603
2844
|
}
|
|
2604
2845
|
if (pluginsToInstall.length > 0) {
|
|
2605
2846
|
const repoManager = createRepositoryManager();
|
|
2606
|
-
const spinner = createSpinner(t("
|
|
2847
|
+
const spinner = createSpinner(t("loading_plugin_data"));
|
|
2607
2848
|
spinner.start();
|
|
2608
2849
|
const repoPath = await repoManager.ensureRepo();
|
|
2609
|
-
spinner.succeed(t("
|
|
2850
|
+
spinner.succeed(t("loading_plugin_data_complete"));
|
|
2610
2851
|
const allPlugins = getAllPlugins();
|
|
2611
2852
|
const results = await installPlugins(
|
|
2612
2853
|
pluginsToInstall,
|
|
2613
2854
|
tool,
|
|
2614
2855
|
level,
|
|
2615
|
-
repoPath
|
|
2856
|
+
repoPath,
|
|
2857
|
+
void 0,
|
|
2858
|
+
options.yes
|
|
2616
2859
|
);
|
|
2617
2860
|
const summary = results.map((result) => {
|
|
2618
2861
|
const plugin = allPlugins.find((p) => p.id === result.pluginId);
|
|
@@ -2630,32 +2873,35 @@ async function installCommand(names, options) {
|
|
|
2630
2873
|
}
|
|
2631
2874
|
if (skills.length > 0) {
|
|
2632
2875
|
const repoManager = createRepositoryManager();
|
|
2633
|
-
const spinner = createSpinner(t("
|
|
2876
|
+
const spinner = createSpinner(t("loading_plugin_data"));
|
|
2634
2877
|
spinner.start();
|
|
2635
2878
|
const repoPath = await repoManager.ensureRepo();
|
|
2636
|
-
spinner.succeed(t("
|
|
2637
|
-
logger.info(`${t("skill_install_progress")} ${
|
|
2879
|
+
spinner.succeed(t("loading_plugin_data_complete"));
|
|
2880
|
+
logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(skills.length)))}...`);
|
|
2638
2881
|
const results = await installSkills(skills, tool, level, repoPath);
|
|
2639
2882
|
let successCount = 0;
|
|
2640
2883
|
let failCount = 0;
|
|
2641
|
-
|
|
2884
|
+
const total = results.length;
|
|
2885
|
+
for (let i = 0; i < results.length; i++) {
|
|
2886
|
+
const result = results[i];
|
|
2887
|
+
const progress = `[${i + 1}/${total}]`;
|
|
2642
2888
|
if (result.success) {
|
|
2643
|
-
logger.success(`${result.skillId}`);
|
|
2889
|
+
logger.success(`${progress} ${result.skillId}`);
|
|
2644
2890
|
successCount++;
|
|
2645
2891
|
} else {
|
|
2646
|
-
logger.error(`${result.skillId}: ${result.error}`);
|
|
2892
|
+
logger.error(`${progress} ${result.skillId}: ${result.error}`);
|
|
2647
2893
|
failCount++;
|
|
2648
2894
|
}
|
|
2649
2895
|
}
|
|
2650
2896
|
logger.blank();
|
|
2651
|
-
logger.success(`${t("skill_install_done")}: ${
|
|
2897
|
+
logger.success(`${t("skill_install_done")}: ${chalk10.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk10.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk10.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
|
|
2652
2898
|
logger.blank();
|
|
2653
|
-
logger.info(
|
|
2654
|
-
logger.info(
|
|
2899
|
+
logger.info(`${t("install_to")}: ${chalk10.cyan(getConfigRoot(tool, level) + "/skills/")}`);
|
|
2900
|
+
logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool))}`);
|
|
2655
2901
|
logger.blank();
|
|
2656
2902
|
}
|
|
2657
2903
|
}
|
|
2658
|
-
async function installPlugins(pluginIds, tool, level, repoPath) {
|
|
2904
|
+
async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes) {
|
|
2659
2905
|
const results = [];
|
|
2660
2906
|
const total = pluginIds.length;
|
|
2661
2907
|
for (let i = 0; i < pluginIds.length; i++) {
|
|
@@ -2667,7 +2913,9 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
|
|
|
2667
2913
|
pluginId,
|
|
2668
2914
|
tool,
|
|
2669
2915
|
level,
|
|
2670
|
-
repoPath
|
|
2916
|
+
repoPath,
|
|
2917
|
+
installPath,
|
|
2918
|
+
yes
|
|
2671
2919
|
});
|
|
2672
2920
|
if (result.success) {
|
|
2673
2921
|
spinner.succeed(
|
|
@@ -2683,11 +2931,11 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
|
|
|
2683
2931
|
}
|
|
2684
2932
|
|
|
2685
2933
|
// src/commands/uninstall.ts
|
|
2686
|
-
import { existsSync as
|
|
2687
|
-
import
|
|
2934
|
+
import { existsSync as existsSync13, unlinkSync as unlinkSync5, rmdirSync as rmdirSync2, readdirSync as readdirSync6, lstatSync as lstatSync4 } from "fs";
|
|
2935
|
+
import chalk11 from "chalk";
|
|
2688
2936
|
async function uninstallCommand(names, options) {
|
|
2689
|
-
const tool = options.tool
|
|
2690
|
-
const level = options.level
|
|
2937
|
+
const tool = options.tool ? validateTool(options.tool) : "opencode";
|
|
2938
|
+
const level = options.level ? validateLevel(options.level) : "project";
|
|
2691
2939
|
const plugins = [];
|
|
2692
2940
|
const skills = [];
|
|
2693
2941
|
for (const name of names) {
|
|
@@ -2701,40 +2949,44 @@ async function uninstallCommand(names, options) {
|
|
|
2701
2949
|
skills.push(skill.id);
|
|
2702
2950
|
continue;
|
|
2703
2951
|
}
|
|
2704
|
-
logger.error(
|
|
2952
|
+
logger.error(`${t("uninstall_not_found")}: ${name}`);
|
|
2705
2953
|
}
|
|
2706
2954
|
for (const pluginId of plugins) {
|
|
2707
2955
|
const plugin = findPlugin(pluginId);
|
|
2708
2956
|
if (!plugin) continue;
|
|
2709
2957
|
const record = readRecord(plugin.id);
|
|
2710
2958
|
if (!record) {
|
|
2711
|
-
logger.warn(`${plugin.displayName}
|
|
2712
|
-
logger.info(
|
|
2959
|
+
logger.warn(`${plugin.displayName} ${t("uninstall_no_record")}`);
|
|
2960
|
+
logger.info(`${t("uninstall_no_record_hint")}`);
|
|
2713
2961
|
continue;
|
|
2714
2962
|
}
|
|
2715
|
-
logger.info(
|
|
2963
|
+
logger.info(`${t("uninstall_in_progress")} ${plugin.displayName}...`);
|
|
2716
2964
|
let removedFiles = 0;
|
|
2717
2965
|
let removedDirs = 0;
|
|
2718
2966
|
for (const filePath of record.files) {
|
|
2719
2967
|
try {
|
|
2720
|
-
if (
|
|
2721
|
-
|
|
2968
|
+
if (existsSync13(filePath) || isSymlink3(filePath)) {
|
|
2969
|
+
unlinkSync5(filePath);
|
|
2722
2970
|
const name = filePath.split("/").pop() || filePath;
|
|
2723
|
-
logger.step(`
|
|
2971
|
+
logger.step(` ${t("uninstall_remove_label")}: ${name}`);
|
|
2724
2972
|
removedFiles++;
|
|
2725
2973
|
}
|
|
2726
2974
|
} catch {
|
|
2727
2975
|
}
|
|
2728
2976
|
}
|
|
2977
|
+
const configRoot = getConfigRoot(record.tool, record.level);
|
|
2729
2978
|
const sortedDirs = [...record.directories].sort((a, b) => b.length - a.length);
|
|
2730
2979
|
for (const dirPath of sortedDirs) {
|
|
2731
2980
|
try {
|
|
2732
|
-
if (
|
|
2733
|
-
|
|
2981
|
+
if (dirPath === configRoot) {
|
|
2982
|
+
continue;
|
|
2983
|
+
}
|
|
2984
|
+
if (existsSync13(dirPath)) {
|
|
2985
|
+
const entries = readdirSync6(dirPath);
|
|
2734
2986
|
if (entries.length === 0) {
|
|
2735
2987
|
rmdirSync2(dirPath);
|
|
2736
2988
|
const name = dirPath.split("/").pop() || dirPath;
|
|
2737
|
-
logger.step(`
|
|
2989
|
+
logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
|
|
2738
2990
|
removedDirs++;
|
|
2739
2991
|
}
|
|
2740
2992
|
}
|
|
@@ -2743,15 +2995,32 @@ async function uninstallCommand(names, options) {
|
|
|
2743
2995
|
}
|
|
2744
2996
|
deleteRecord(plugin.id);
|
|
2745
2997
|
removeInstalledPlugin(plugin.id);
|
|
2998
|
+
const backups = findBackups(configRoot);
|
|
2999
|
+
const otherBackups = backups.filter((b) => b.pluginId !== pluginId);
|
|
3000
|
+
if (otherBackups.length > 0) {
|
|
3001
|
+
const choice = await showRestorePrompt(otherBackups);
|
|
3002
|
+
if (choice !== "none") {
|
|
3003
|
+
const restored = restoreBackup(choice, configRoot, record.tool);
|
|
3004
|
+
if (restored) {
|
|
3005
|
+
const backupInfo = otherBackups.find((b) => b.filePath === choice);
|
|
3006
|
+
logger.success(`${t("backup_restore_success")}: ${backupInfo?.pluginName || ""}`);
|
|
3007
|
+
deleteBackup(choice);
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
const currentBackups = backups.filter((b) => b.pluginId === pluginId);
|
|
3012
|
+
for (const backup of currentBackups) {
|
|
3013
|
+
deleteBackup(backup.filePath);
|
|
3014
|
+
}
|
|
2746
3015
|
const totalRemoved = removedFiles + removedDirs;
|
|
2747
3016
|
if (totalRemoved > 0) {
|
|
2748
|
-
logger.success(
|
|
3017
|
+
logger.success(`${t("uninstall_removed")} ${plugin.displayName}\uFF08${t("uninstall_remove_file_format").replace("{count}", String(removedFiles))}\uFF0C${t("uninstall_remove_dir_format").replace("{count}", String(removedDirs))}\uFF09`);
|
|
2749
3018
|
} else {
|
|
2750
|
-
logger.warn(`${plugin.displayName}
|
|
3019
|
+
logger.warn(`${plugin.displayName}${t("uninstall_files_gone")}`);
|
|
2751
3020
|
}
|
|
2752
3021
|
}
|
|
2753
3022
|
if (skills.length > 0) {
|
|
2754
|
-
logger.info(
|
|
3023
|
+
logger.info(`${t("uninstall_skills_in_progress")} ${t("skill_count_format").replace("{count}", String(skills.length))}...`);
|
|
2755
3024
|
const results = await uninstallSkills(skills, tool, level);
|
|
2756
3025
|
let successCount = 0;
|
|
2757
3026
|
let failCount = 0;
|
|
@@ -2763,7 +3032,7 @@ async function uninstallCommand(names, options) {
|
|
|
2763
3032
|
}
|
|
2764
3033
|
}
|
|
2765
3034
|
logger.blank();
|
|
2766
|
-
logger.success(`${t("skill_uninstall_done")}: ${
|
|
3035
|
+
logger.success(`${t("skill_uninstall_done")}: ${chalk11.green(t("result_success_format").replace("{count}", String(successCount)))}, ${failCount > 0 ? chalk11.red(t("result_failed_format").replace("{count}", String(failCount))) : chalk11.dim(t("result_failed_format").replace("{count}", String(failCount)))}`);
|
|
2767
3036
|
logger.blank();
|
|
2768
3037
|
}
|
|
2769
3038
|
}
|
|
@@ -2776,17 +3045,17 @@ function isSymlink3(path) {
|
|
|
2776
3045
|
}
|
|
2777
3046
|
|
|
2778
3047
|
// src/commands/update.ts
|
|
2779
|
-
import
|
|
3048
|
+
import chalk12 from "chalk";
|
|
2780
3049
|
async function updateCommand(pluginNames, options) {
|
|
2781
3050
|
let tool;
|
|
2782
3051
|
if (options.tool) {
|
|
2783
|
-
tool = options.tool;
|
|
3052
|
+
tool = validateTool(options.tool);
|
|
2784
3053
|
} else {
|
|
2785
3054
|
const detected = await selectToolWithDetection();
|
|
2786
|
-
if (detected === "back") return;
|
|
3055
|
+
if (detected === "back" || detected === "cancel") return;
|
|
2787
3056
|
tool = detected;
|
|
2788
3057
|
}
|
|
2789
|
-
const level = options.level
|
|
3058
|
+
const level = options.level ? validateLevel(options.level) : "project";
|
|
2790
3059
|
let pluginsToUpdate = [];
|
|
2791
3060
|
if (pluginNames.length === 0) {
|
|
2792
3061
|
const configRoot = getConfigRoot(tool, level);
|
|
@@ -2794,7 +3063,7 @@ async function updateCommand(pluginNames, options) {
|
|
|
2794
3063
|
pluginsToUpdate = manifests.map((m) => m.team);
|
|
2795
3064
|
if (pluginsToUpdate.length === 0) {
|
|
2796
3065
|
logger.info(t("update_no_plugins"));
|
|
2797
|
-
logger.info(
|
|
3066
|
+
logger.info(t("update_install_hint").replace("{cmd}", chalk12.cyan("install-helper install <plugin>")));
|
|
2798
3067
|
return;
|
|
2799
3068
|
}
|
|
2800
3069
|
} else {
|
|
@@ -2856,42 +3125,42 @@ async function updateCommand(pluginNames, options) {
|
|
|
2856
3125
|
}
|
|
2857
3126
|
|
|
2858
3127
|
// src/commands/info.ts
|
|
2859
|
-
import
|
|
2860
|
-
import { existsSync as
|
|
2861
|
-
import { join as
|
|
3128
|
+
import chalk13 from "chalk";
|
|
3129
|
+
import { existsSync as existsSync14, readdirSync as readdirSync7 } from "fs";
|
|
3130
|
+
import { join as join15 } from "path";
|
|
2862
3131
|
async function infoCommand(pluginName) {
|
|
2863
3132
|
const plugin = findPlugin(pluginName);
|
|
2864
3133
|
if (!plugin) {
|
|
2865
|
-
console.log(
|
|
3134
|
+
console.log(chalk13.red(` ${t("info_not_found")}: ${pluginName}`));
|
|
2866
3135
|
return;
|
|
2867
3136
|
}
|
|
2868
3137
|
const installed = scanInstalled();
|
|
2869
3138
|
const isInstalled = installed.some((p) => p.id === plugin.id);
|
|
2870
|
-
const statusText = isInstalled ?
|
|
3139
|
+
const statusText = isInstalled ? chalk13.green(`\u2713 ${t("status_installed")}`) : chalk13.dim(`\u2014 ${t("status_not_installed")}`);
|
|
2871
3140
|
console.log();
|
|
2872
|
-
console.log(
|
|
2873
|
-
console.log(
|
|
3141
|
+
console.log(chalk13.bold(` ${plugin.displayName}`));
|
|
3142
|
+
console.log(chalk13.dim(" " + "\u2500".repeat(40)));
|
|
2874
3143
|
console.log();
|
|
2875
|
-
console.log(` ${
|
|
2876
|
-
console.log(` ${
|
|
2877
|
-
console.log(` ${
|
|
2878
|
-
console.log(` ${
|
|
2879
|
-
console.log(` ${
|
|
2880
|
-
console.log(` ${
|
|
3144
|
+
console.log(` ${chalk13.dim(t("info_description") + ":")} ${plugin.description}`);
|
|
3145
|
+
console.log(` ${chalk13.dim("ID:")} ${plugin.id}`);
|
|
3146
|
+
console.log(` ${chalk13.dim(t("info_status") + ":")} ${statusText}`);
|
|
3147
|
+
console.log(` ${chalk13.dim(t("info_skills") + ":")} ${plugin.skills}`);
|
|
3148
|
+
console.log(` ${chalk13.dim(t("info_agents") + ":")} ${plugin.agents}`);
|
|
3149
|
+
console.log(` ${chalk13.dim(t("info_aliases") + ":")} ${plugin.aliases.join(", ")}`);
|
|
2881
3150
|
console.log();
|
|
2882
3151
|
try {
|
|
2883
3152
|
const repoManager = createRepositoryManager();
|
|
2884
3153
|
const repoPath = await repoManager.ensureRepo();
|
|
2885
|
-
const quickstartPath =
|
|
2886
|
-
if (
|
|
2887
|
-
console.log(` ${
|
|
3154
|
+
const quickstartPath = join15(repoPath, plugin.dir, "quickstart.md");
|
|
3155
|
+
if (existsSync14(quickstartPath)) {
|
|
3156
|
+
console.log(` ${chalk13.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
|
|
2888
3157
|
}
|
|
2889
|
-
const agentsDir =
|
|
2890
|
-
if (
|
|
2891
|
-
const agentFiles =
|
|
3158
|
+
const agentsDir = join15(repoPath, plugin.dir, "agents");
|
|
3159
|
+
if (existsSync14(agentsDir)) {
|
|
3160
|
+
const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md"));
|
|
2892
3161
|
if (agentFiles.length > 0) {
|
|
2893
3162
|
console.log();
|
|
2894
|
-
console.log(` ${
|
|
3163
|
+
console.log(` ${chalk13.bold("Agents:")}`);
|
|
2895
3164
|
for (const agent of agentFiles) {
|
|
2896
3165
|
console.log(` \u2022 ${agent.replace(".md", "")}`);
|
|
2897
3166
|
}
|
|
@@ -2903,39 +3172,39 @@ async function infoCommand(pluginName) {
|
|
|
2903
3172
|
}
|
|
2904
3173
|
|
|
2905
3174
|
// src/commands/lang.ts
|
|
2906
|
-
import
|
|
3175
|
+
import chalk14 from "chalk";
|
|
2907
3176
|
async function langCommand(action, value) {
|
|
2908
3177
|
const config = readConfig();
|
|
2909
3178
|
if (!action || action === "show") {
|
|
2910
3179
|
console.log();
|
|
2911
|
-
console.log(
|
|
2912
|
-
console.log(
|
|
3180
|
+
console.log(chalk14.bold(` ${t("lang_title")}`));
|
|
3181
|
+
console.log(chalk14.dim(" " + "\u2500".repeat(40)));
|
|
2913
3182
|
console.log();
|
|
2914
|
-
console.log(` ${
|
|
3183
|
+
console.log(` ${chalk14.dim(t("lang_current") + ":")} ${config.language}`);
|
|
2915
3184
|
console.log();
|
|
2916
|
-
console.log(
|
|
2917
|
-
console.log(
|
|
3185
|
+
console.log(chalk14.dim(` ${t("lang_supported")}`));
|
|
3186
|
+
console.log(chalk14.dim(` ${t("lang_usage")}`));
|
|
2918
3187
|
console.log();
|
|
2919
3188
|
return;
|
|
2920
3189
|
}
|
|
2921
3190
|
if (action === "set") {
|
|
2922
3191
|
if (!value) {
|
|
2923
|
-
console.log(
|
|
3192
|
+
console.log(chalk14.red(` ${t("lang_specify")}`));
|
|
2924
3193
|
return;
|
|
2925
3194
|
}
|
|
2926
3195
|
if (value !== "zh_CN" && value !== "en_US") {
|
|
2927
|
-
console.log(
|
|
3196
|
+
console.log(chalk14.red(` ${t("lang_invalid")}`));
|
|
2928
3197
|
return;
|
|
2929
3198
|
}
|
|
2930
3199
|
updateConfig({ language: value });
|
|
2931
3200
|
setLanguage(value);
|
|
2932
3201
|
console.log();
|
|
2933
|
-
console.log(
|
|
3202
|
+
console.log(chalk14.green(` \u2713 ${t("lang_set")} ${value}`));
|
|
2934
3203
|
console.log();
|
|
2935
3204
|
return;
|
|
2936
3205
|
}
|
|
2937
|
-
console.log(
|
|
2938
|
-
console.log(
|
|
3206
|
+
console.log(chalk14.red(` ${t("lang_unknown_action")}: ${action}`));
|
|
3207
|
+
console.log(chalk14.dim(` ${t("lang_supported_actions")}`));
|
|
2939
3208
|
}
|
|
2940
3209
|
|
|
2941
3210
|
// src/cli.ts
|
|
@@ -2945,7 +3214,7 @@ function createCLI() {
|
|
|
2945
3214
|
setLanguage(config.language);
|
|
2946
3215
|
}
|
|
2947
3216
|
const program2 = new Command();
|
|
2948
|
-
program2.name("install-helper").description("Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.
|
|
3217
|
+
program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version("0.0.2-beta.30");
|
|
2949
3218
|
program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
|
|
2950
3219
|
await initCommand();
|
|
2951
3220
|
});
|
|
@@ -2958,7 +3227,7 @@ function createCLI() {
|
|
|
2958
3227
|
program2.command("status").description("Show installed plugins").action(async () => {
|
|
2959
3228
|
await statusCommand();
|
|
2960
3229
|
});
|
|
2961
|
-
program2.command("install [names...]").description("Install plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, copilot)").option("-l, --level <level>", "Install level (project, global)", "project").option("-y, --yes", "Skip confirmation prompts").option("--list", "List all available skills by category").action(async (names, options) => {
|
|
3230
|
+
program2.command("install [names...]").description("Install plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, copilot)").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) => {
|
|
2962
3231
|
await installCommand(names, options);
|
|
2963
3232
|
});
|
|
2964
3233
|
program2.command("uninstall <names...>").description("Uninstall plugins or skills (auto-detects type)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, copilot)").option("-l, --level <level>", "Install level (project, global)", "project").action(async (names, options) => {
|
|
@@ -2982,7 +3251,7 @@ function createCLI() {
|
|
|
2982
3251
|
// src/index.ts
|
|
2983
3252
|
process.on("uncaughtException", (error) => {
|
|
2984
3253
|
if (error.name === "ExitPromptError") {
|
|
2985
|
-
console.log("\n
|
|
3254
|
+
console.log("\n" + t("init_cancelled"));
|
|
2986
3255
|
process.exit(0);
|
|
2987
3256
|
}
|
|
2988
3257
|
throw error;
|