@cannbot-ai/install-helper 0.0.2-beta.4 → 0.0.2-beta.40
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 +122 -31
- package/dist/config/repository.yaml +28 -0
- package/dist/index.js +1575 -929
- package/dist/index.js.map +1 -1
- package/dist/locales/en_US.json +180 -0
- package/dist/locales/zh_CN.json +180 -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 +7 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,116 +4,286 @@
|
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
15
|
+
import { readdirSync, readFileSync as readFileSync2, existsSync } from "fs";
|
|
16
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
17
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
18
|
+
import { parse as parseYaml } from "yaml";
|
|
19
|
+
|
|
20
|
+
// src/utils/logger.ts
|
|
21
|
+
import chalk2 from "chalk";
|
|
22
|
+
import ora from "ora";
|
|
23
|
+
|
|
24
|
+
// src/utils/i18n.ts
|
|
25
|
+
import { readFileSync } from "fs";
|
|
26
|
+
import { join, dirname } from "path";
|
|
27
|
+
import { fileURLToPath } from "url";
|
|
28
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
29
|
+
var __dirname = dirname(__filename);
|
|
30
|
+
function loadLocale(locale) {
|
|
31
|
+
const localePath = join(__dirname, "locales", `${locale}.json`);
|
|
32
|
+
try {
|
|
33
|
+
const content = readFileSync(localePath, "utf-8");
|
|
34
|
+
return JSON.parse(content);
|
|
35
|
+
} catch {
|
|
36
|
+
const fallbackPath = join(__dirname, "locales", "zh_CN.json");
|
|
37
|
+
try {
|
|
38
|
+
const content = readFileSync(fallbackPath, "utf-8");
|
|
39
|
+
return JSON.parse(content);
|
|
40
|
+
} catch {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
var currentLang = "zh_CN";
|
|
46
|
+
var messages = loadLocale("zh_CN");
|
|
47
|
+
function setLanguage(lang) {
|
|
48
|
+
currentLang = lang;
|
|
49
|
+
messages = loadLocale(lang);
|
|
50
|
+
}
|
|
51
|
+
function t(key) {
|
|
52
|
+
return messages[key] !== void 0 ? messages[key] : key;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/ui/theme.ts
|
|
56
|
+
import chalk from "chalk";
|
|
57
|
+
var selectTheme = {
|
|
58
|
+
helpMode: "never"
|
|
59
|
+
};
|
|
60
|
+
function isCJK(code) {
|
|
61
|
+
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;
|
|
62
|
+
}
|
|
63
|
+
function isEmoji(code) {
|
|
64
|
+
return code >= 127744 && code <= 129791 || code >= 9728 && code <= 10175;
|
|
65
|
+
}
|
|
66
|
+
function getDisplayWidth(str) {
|
|
67
|
+
let width = 0;
|
|
68
|
+
for (const ch of str) {
|
|
69
|
+
const code = ch.codePointAt(0);
|
|
70
|
+
if (isCJK(code) || isEmoji(code)) {
|
|
71
|
+
width += 2;
|
|
72
|
+
} else {
|
|
73
|
+
width += 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return width;
|
|
77
|
+
}
|
|
78
|
+
function wrapDescription(text) {
|
|
79
|
+
const prefix = " \u{1F4A1} ";
|
|
80
|
+
const indent = " ";
|
|
81
|
+
const cols = process.stdout.columns || 120;
|
|
82
|
+
const maxLen = Math.max(20, cols - getDisplayWidth(prefix) - 2);
|
|
83
|
+
const tokens = [];
|
|
84
|
+
let i = 0;
|
|
85
|
+
while (i < text.length) {
|
|
86
|
+
const code = text[i].codePointAt(0);
|
|
87
|
+
if (isCJK(code)) {
|
|
88
|
+
tokens.push({ text: text[i], width: 2 });
|
|
89
|
+
i++;
|
|
90
|
+
} else {
|
|
91
|
+
let j = i;
|
|
92
|
+
while (j < text.length) {
|
|
93
|
+
const c = text[j].codePointAt(0);
|
|
94
|
+
if (isCJK(c)) break;
|
|
95
|
+
j++;
|
|
96
|
+
}
|
|
97
|
+
const word = text.slice(i, j);
|
|
98
|
+
tokens.push({ text: word, width: getDisplayWidth(word) });
|
|
99
|
+
i = j;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const lines = [];
|
|
103
|
+
let line = "";
|
|
104
|
+
let lineWidth = 0;
|
|
105
|
+
for (const token of tokens) {
|
|
106
|
+
if (lineWidth + token.width > maxLen && line) {
|
|
107
|
+
lines.push(line.trimEnd());
|
|
108
|
+
line = token.text;
|
|
109
|
+
lineWidth = token.width;
|
|
110
|
+
} else {
|
|
111
|
+
line += token.text;
|
|
112
|
+
lineWidth += token.width;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (line) lines.push(line.trimEnd());
|
|
116
|
+
return lines.map((l, i2) => chalk.cyan(i2 === 0 ? prefix + l : indent + l)).join("\n");
|
|
117
|
+
}
|
|
118
|
+
var checkboxTheme = {
|
|
119
|
+
icon: {
|
|
120
|
+
checked: "\x1B[32m\u2611\x1B[22m",
|
|
121
|
+
unchecked: "\u2610",
|
|
122
|
+
cursor: "\u276F"
|
|
105
123
|
},
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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"
|
|
124
|
+
helpMode: "never",
|
|
125
|
+
style: {
|
|
126
|
+
description: (text) => wrapDescription(text)
|
|
115
127
|
}
|
|
116
|
-
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// src/utils/logger.ts
|
|
131
|
+
var logger = {
|
|
132
|
+
success: (msg) => console.log(chalk2.green("\u2713") + " " + msg),
|
|
133
|
+
error: (msg) => console.log(chalk2.red("\u2717") + " " + msg),
|
|
134
|
+
warn: (msg) => console.log(chalk2.yellow("\u26A0") + " " + msg),
|
|
135
|
+
info: (msg) => console.log(chalk2.cyan("\u2192") + " " + msg),
|
|
136
|
+
step: (msg) => console.log(chalk2.dim(msg)),
|
|
137
|
+
blank: () => console.log()
|
|
138
|
+
};
|
|
139
|
+
function createSpinner(text) {
|
|
140
|
+
return ora({ text, color: "cyan" });
|
|
141
|
+
}
|
|
142
|
+
function printBanner(subtitle) {
|
|
143
|
+
console.log(chalk2.cyan(`
|
|
144
|
+
____ _ _ _ _ _ ____ _
|
|
145
|
+
/ ___| / \\ | \\ | | \\ | | __ ) ___ | |_
|
|
146
|
+
| | / _ \\ | \\| | \\| | _ \\ / _ \\| __|
|
|
147
|
+
| |___ / ___ \\| |\\ | |\\ | |_) | (_) | |_
|
|
148
|
+
\\____/_/ \\_\\_| \\_|_| \\_|____/ \\___/ \\__|
|
|
149
|
+
`));
|
|
150
|
+
if (subtitle) {
|
|
151
|
+
console.log(chalk2.bold(` ${subtitle}`));
|
|
152
|
+
console.log();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function printBoxTitle(title, width = 65) {
|
|
156
|
+
const border = "\u2550".repeat(width);
|
|
157
|
+
const displayWidth = getDisplayWidth(title);
|
|
158
|
+
const totalPadding = width - displayWidth;
|
|
159
|
+
const leftPadding = Math.floor(totalPadding / 2);
|
|
160
|
+
const rightPadding = totalPadding - leftPadding;
|
|
161
|
+
const paddedTitle = " ".repeat(Math.max(0, leftPadding)) + title + " ".repeat(Math.max(0, rightPadding));
|
|
162
|
+
console.log();
|
|
163
|
+
console.log(chalk2.cyan.bold(` \u2554${border}\u2557`));
|
|
164
|
+
console.log(chalk2.cyan.bold(` \u2551`) + chalk2.cyan.bold(paddedTitle) + chalk2.cyan.bold(`\u2551`));
|
|
165
|
+
console.log(chalk2.cyan.bold(` \u255A${border}\u255D`));
|
|
166
|
+
console.log();
|
|
167
|
+
}
|
|
168
|
+
function showOperationHints(isCheckbox = false) {
|
|
169
|
+
if (isCheckbox) {
|
|
170
|
+
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
|
|
171
|
+
`);
|
|
172
|
+
} else {
|
|
173
|
+
console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m ${t("hint_select_move")} | \x1B[36m\u23CE\x1B[0m ${t("hint_select_confirm")}
|
|
174
|
+
`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/utils/fs-helpers.ts
|
|
179
|
+
import { lstatSync, statSync } from "fs";
|
|
180
|
+
function isSymlink(path) {
|
|
181
|
+
try {
|
|
182
|
+
return lstatSync(path).isSymbolicLink();
|
|
183
|
+
} catch {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function isDirectory(path) {
|
|
188
|
+
try {
|
|
189
|
+
return statSync(path).isDirectory();
|
|
190
|
+
} catch {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/core/registry.ts
|
|
196
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
197
|
+
var __dirname2 = dirname2(__filename2);
|
|
198
|
+
function findPluginsDir() {
|
|
199
|
+
const candidates = [
|
|
200
|
+
join2(__dirname2, "plugins.d"),
|
|
201
|
+
join2(__dirname2, "..", "plugins.d"),
|
|
202
|
+
join2(__dirname2, "..", "..", "plugins.d")
|
|
203
|
+
];
|
|
204
|
+
for (const dir of candidates) {
|
|
205
|
+
if (existsSync(dir)) return dir;
|
|
206
|
+
}
|
|
207
|
+
return candidates[0];
|
|
208
|
+
}
|
|
209
|
+
function loadPluginsFromYaml() {
|
|
210
|
+
const pluginsDir = findPluginsDir();
|
|
211
|
+
const defaultsPath = join2(pluginsDir, "_defaults.yml");
|
|
212
|
+
let defaults = {};
|
|
213
|
+
try {
|
|
214
|
+
defaults = parseYaml(readFileSync2(defaultsPath, "utf-8")) || {};
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
const plugins = [];
|
|
218
|
+
try {
|
|
219
|
+
for (const file of readdirSync(pluginsDir)) {
|
|
220
|
+
if (file.startsWith("_") || !file.endsWith(".yml")) continue;
|
|
221
|
+
try {
|
|
222
|
+
const content = parseYaml(readFileSync2(join2(pluginsDir, file), "utf-8"));
|
|
223
|
+
if (!content || !content.id) continue;
|
|
224
|
+
plugins.push({
|
|
225
|
+
id: content.id,
|
|
226
|
+
dir: content.dir,
|
|
227
|
+
displayName: content.displayName || content.id,
|
|
228
|
+
script: content.script || defaults.script || "init.sh",
|
|
229
|
+
aliases: content.aliases || [],
|
|
230
|
+
skills: content.skills ?? defaults.skills ?? 0,
|
|
231
|
+
agents: content.agents ?? defaults.agents ?? 0,
|
|
232
|
+
description: content.description || ""
|
|
233
|
+
});
|
|
234
|
+
} catch {
|
|
235
|
+
logger.warn(t("plugin_yaml_error").replace("{file}", file));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
} catch {
|
|
239
|
+
}
|
|
240
|
+
plugins.sort(
|
|
241
|
+
(a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
|
|
242
|
+
);
|
|
243
|
+
return plugins;
|
|
244
|
+
}
|
|
245
|
+
var PLUGIN_REGISTRY = loadPluginsFromYaml();
|
|
246
|
+
var PLUGIN_DIRS = ["plugins-official", "plugins-community"];
|
|
247
|
+
function mergeDynamicPlugins(repoPath) {
|
|
248
|
+
if (!existsSync(repoPath)) return;
|
|
249
|
+
const existingIds = new Set(PLUGIN_REGISTRY.map((p) => p.id));
|
|
250
|
+
const dynamic = [];
|
|
251
|
+
for (const pluginDir of PLUGIN_DIRS) {
|
|
252
|
+
const parent = join2(repoPath, pluginDir);
|
|
253
|
+
if (!existsSync(parent)) continue;
|
|
254
|
+
let entries = [];
|
|
255
|
+
try {
|
|
256
|
+
entries = readdirSync(parent);
|
|
257
|
+
} catch {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
for (const entry of entries) {
|
|
261
|
+
const pluginPath = join2(parent, entry);
|
|
262
|
+
if (!isDirectory(pluginPath)) continue;
|
|
263
|
+
if (entry === "install-helper") continue;
|
|
264
|
+
const initSh = join2(pluginPath, "init.sh");
|
|
265
|
+
if (!existsSync(initSh)) continue;
|
|
266
|
+
const pluginId = entry;
|
|
267
|
+
if (existingIds.has(pluginId)) continue;
|
|
268
|
+
dynamic.push({
|
|
269
|
+
id: pluginId,
|
|
270
|
+
dir: `${pluginDir}/${entry}`,
|
|
271
|
+
displayName: entry,
|
|
272
|
+
script: "init.sh",
|
|
273
|
+
aliases: [],
|
|
274
|
+
skills: 0,
|
|
275
|
+
agents: 0,
|
|
276
|
+
description: ""
|
|
277
|
+
});
|
|
278
|
+
existingIds.add(pluginId);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (dynamic.length > 0) {
|
|
282
|
+
PLUGIN_REGISTRY = [...PLUGIN_REGISTRY, ...dynamic].sort(
|
|
283
|
+
(a, b) => a.displayName < b.displayName ? -1 : a.displayName > b.displayName ? 1 : 0
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
117
287
|
function findPlugin(query) {
|
|
118
288
|
const normalized = query.toLowerCase().trim();
|
|
119
289
|
const exactMatch = PLUGIN_REGISTRY.find(
|
|
@@ -137,19 +307,37 @@ function getPluginById(id) {
|
|
|
137
307
|
|
|
138
308
|
// src/core/detector.ts
|
|
139
309
|
import { execa } from "execa";
|
|
140
|
-
import { existsSync as
|
|
310
|
+
import { existsSync as existsSync3 } from "fs";
|
|
141
311
|
import { homedir as homedir2 } from "os";
|
|
142
|
-
import { join as
|
|
312
|
+
import { join as join4 } from "path";
|
|
143
313
|
|
|
144
314
|
// src/utils/paths.ts
|
|
145
315
|
import { homedir } from "os";
|
|
146
|
-
import { join } from "path";
|
|
147
|
-
import { existsSync } from "fs";
|
|
316
|
+
import { join as join3 } from "path";
|
|
317
|
+
import { existsSync as existsSync2 } from "fs";
|
|
318
|
+
var VALID_TOOLS = ["opencode", "claude", "trae", "cursor", "copilot"];
|
|
319
|
+
var VALID_LEVELS = ["project", "global"];
|
|
320
|
+
function validateTool(tool) {
|
|
321
|
+
if (!VALID_TOOLS.includes(tool)) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
return tool;
|
|
327
|
+
}
|
|
328
|
+
function validateLevel(level) {
|
|
329
|
+
if (!VALID_LEVELS.includes(level)) {
|
|
330
|
+
throw new Error(
|
|
331
|
+
t("error_invalid_level").replace("{level}", level).replace("{levels}", VALID_LEVELS.join(", "))
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
return level;
|
|
335
|
+
}
|
|
148
336
|
function detectTraeVariant() {
|
|
149
337
|
const home = homedir();
|
|
150
|
-
if (
|
|
151
|
-
if (
|
|
152
|
-
if (
|
|
338
|
+
if (existsSync2(join3(home, ".trae-cn"))) return "ide";
|
|
339
|
+
if (existsSync2(join3(home, ".marscode"))) return "plugin";
|
|
340
|
+
if (existsSync2(join3(home, ".traecli"))) return "cli";
|
|
153
341
|
return "unknown";
|
|
154
342
|
}
|
|
155
343
|
function getConfigRoot(tool, level, base) {
|
|
@@ -157,66 +345,77 @@ function getConfigRoot(tool, level, base) {
|
|
|
157
345
|
if (level === "global") {
|
|
158
346
|
switch (tool) {
|
|
159
347
|
case "opencode":
|
|
160
|
-
return
|
|
348
|
+
return join3(home, ".config", "opencode");
|
|
161
349
|
case "claude":
|
|
162
|
-
return
|
|
350
|
+
return join3(home, ".claude");
|
|
163
351
|
case "trae": {
|
|
164
352
|
const variant = detectTraeVariant();
|
|
165
353
|
switch (variant) {
|
|
166
354
|
case "plugin":
|
|
167
|
-
return
|
|
355
|
+
return join3(home, ".marscode");
|
|
168
356
|
case "cli":
|
|
169
|
-
return
|
|
357
|
+
return join3(home, ".traecli");
|
|
170
358
|
default:
|
|
171
|
-
return
|
|
359
|
+
return join3(home, ".trae-cn");
|
|
172
360
|
}
|
|
173
361
|
}
|
|
174
362
|
case "cursor":
|
|
175
|
-
return
|
|
363
|
+
return join3(home, ".cursor");
|
|
176
364
|
case "copilot":
|
|
177
|
-
return
|
|
365
|
+
return join3(home, ".copilot");
|
|
366
|
+
default:
|
|
367
|
+
throw new Error(
|
|
368
|
+
t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
|
|
369
|
+
);
|
|
178
370
|
}
|
|
179
371
|
}
|
|
180
372
|
const baseDir = base || process.cwd();
|
|
181
373
|
switch (tool) {
|
|
182
374
|
case "opencode":
|
|
183
|
-
return
|
|
375
|
+
return join3(baseDir, ".opencode");
|
|
184
376
|
case "claude":
|
|
185
|
-
return
|
|
377
|
+
return join3(baseDir, ".claude");
|
|
186
378
|
case "trae": {
|
|
187
379
|
const variant = detectTraeVariant();
|
|
188
380
|
switch (variant) {
|
|
189
381
|
case "plugin":
|
|
190
|
-
return
|
|
382
|
+
return join3(baseDir, ".marscode");
|
|
191
383
|
case "cli":
|
|
192
|
-
return
|
|
384
|
+
return join3(baseDir, ".traecli");
|
|
193
385
|
default:
|
|
194
|
-
return
|
|
386
|
+
return join3(baseDir, ".trae");
|
|
195
387
|
}
|
|
196
388
|
}
|
|
197
389
|
case "cursor":
|
|
198
|
-
return
|
|
390
|
+
return join3(baseDir, ".cursor");
|
|
199
391
|
case "copilot":
|
|
200
|
-
return
|
|
392
|
+
return join3(baseDir, ".github");
|
|
393
|
+
default:
|
|
394
|
+
throw new Error(
|
|
395
|
+
t("error_invalid_tool").replace("{tool}", tool).replace("{tools}", VALID_TOOLS.join(", "))
|
|
396
|
+
);
|
|
201
397
|
}
|
|
202
398
|
}
|
|
203
399
|
function getConfigFileName(tool) {
|
|
204
400
|
return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
205
401
|
}
|
|
402
|
+
function getAgentsFileName(tool) {
|
|
403
|
+
return getConfigFileName(tool);
|
|
404
|
+
}
|
|
206
405
|
function getSkillsDir(configRoot) {
|
|
207
|
-
return
|
|
406
|
+
return join3(configRoot, "skills");
|
|
208
407
|
}
|
|
209
408
|
function getAgentsDir(configRoot) {
|
|
210
|
-
return
|
|
409
|
+
return join3(configRoot, "agents");
|
|
211
410
|
}
|
|
212
411
|
function getManifestPath(configRoot) {
|
|
213
|
-
return
|
|
412
|
+
return join3(configRoot, "cannbot-manifest.json");
|
|
214
413
|
}
|
|
215
414
|
function getCannbotConfigDir() {
|
|
216
|
-
return
|
|
415
|
+
return join3(homedir(), ".cannbot");
|
|
217
416
|
}
|
|
218
417
|
function getCannbotRepoPath() {
|
|
219
|
-
return
|
|
418
|
+
return join3(getCannbotConfigDir(), "repo");
|
|
220
419
|
}
|
|
221
420
|
|
|
222
421
|
// src/core/detector.ts
|
|
@@ -232,8 +431,9 @@ async function getCommandVersion(cmd, args = ["--version"]) {
|
|
|
232
431
|
}
|
|
233
432
|
async function getCommandPath(cmd) {
|
|
234
433
|
try {
|
|
235
|
-
const
|
|
236
|
-
|
|
434
|
+
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
435
|
+
const result = await execa(whichCmd, [cmd], { timeout: 5e3 });
|
|
436
|
+
return result.stdout.trim().split("\n")[0];
|
|
237
437
|
} catch {
|
|
238
438
|
return void 0;
|
|
239
439
|
}
|
|
@@ -254,12 +454,12 @@ async function detectTrae() {
|
|
|
254
454
|
const home = homedir2();
|
|
255
455
|
const variant = detectTraeVariant();
|
|
256
456
|
let path;
|
|
257
|
-
if (variant === "ide" &&
|
|
258
|
-
path =
|
|
259
|
-
} else if (variant === "plugin" &&
|
|
260
|
-
path =
|
|
261
|
-
} else if (variant === "cli" &&
|
|
262
|
-
path =
|
|
457
|
+
if (variant === "ide" && existsSync3(join4(home, ".trae-cn"))) {
|
|
458
|
+
path = join4(home, ".trae-cn");
|
|
459
|
+
} else if (variant === "plugin" && existsSync3(join4(home, ".marscode"))) {
|
|
460
|
+
path = join4(home, ".marscode");
|
|
461
|
+
} else if (variant === "cli" && existsSync3(join4(home, ".traecli"))) {
|
|
462
|
+
path = join4(home, ".traecli");
|
|
263
463
|
} else if (variant === "unknown") {
|
|
264
464
|
const cmdPath = await getCommandPath("trae");
|
|
265
465
|
if (cmdPath) path = cmdPath;
|
|
@@ -275,7 +475,7 @@ async function detectCursor() {
|
|
|
275
475
|
}
|
|
276
476
|
if (process.platform === "darwin") {
|
|
277
477
|
const appPath = "/Applications/Cursor.app";
|
|
278
|
-
if (
|
|
478
|
+
if (existsSync3(appPath)) {
|
|
279
479
|
return { name: "cursor", path: appPath };
|
|
280
480
|
}
|
|
281
481
|
}
|
|
@@ -319,17 +519,17 @@ function getToolDisplayName(tool) {
|
|
|
319
519
|
return names[tool];
|
|
320
520
|
}
|
|
321
521
|
function getAllTools() {
|
|
322
|
-
return
|
|
522
|
+
return VALID_TOOLS;
|
|
323
523
|
}
|
|
324
524
|
|
|
325
525
|
// src/core/manifest.ts
|
|
326
|
-
import { existsSync as
|
|
327
|
-
import { join as
|
|
526
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
527
|
+
import { join as join5 } from "path";
|
|
328
528
|
function readManifest(configRoot) {
|
|
329
529
|
const manifestPath = getManifestPath(configRoot);
|
|
330
|
-
if (
|
|
530
|
+
if (existsSync4(manifestPath)) {
|
|
331
531
|
try {
|
|
332
|
-
const content =
|
|
532
|
+
const content = readFileSync3(manifestPath, "utf-8");
|
|
333
533
|
return JSON.parse(content);
|
|
334
534
|
} catch {
|
|
335
535
|
}
|
|
@@ -340,18 +540,18 @@ function readAllManifests(configRoot) {
|
|
|
340
540
|
const manifests = [];
|
|
341
541
|
const plugins = getAllPlugins();
|
|
342
542
|
const standardPath = getManifestPath(configRoot);
|
|
343
|
-
if (
|
|
543
|
+
if (existsSync4(standardPath)) {
|
|
344
544
|
try {
|
|
345
|
-
const content =
|
|
545
|
+
const content = readFileSync3(standardPath, "utf-8");
|
|
346
546
|
manifests.push(JSON.parse(content));
|
|
347
547
|
} catch {
|
|
348
548
|
}
|
|
349
549
|
}
|
|
350
550
|
for (const plugin of plugins) {
|
|
351
|
-
const pluginManifestPath =
|
|
352
|
-
if (
|
|
551
|
+
const pluginManifestPath = join5(configRoot, `${plugin.id}-manifest.json`);
|
|
552
|
+
if (existsSync4(pluginManifestPath)) {
|
|
353
553
|
try {
|
|
354
|
-
const content =
|
|
554
|
+
const content = readFileSync3(pluginManifestPath, "utf-8");
|
|
355
555
|
manifests.push(JSON.parse(content));
|
|
356
556
|
} catch {
|
|
357
557
|
}
|
|
@@ -362,7 +562,7 @@ function readAllManifests(configRoot) {
|
|
|
362
562
|
function scanInstalled() {
|
|
363
563
|
const plugins = getAllPlugins();
|
|
364
564
|
const installed = [];
|
|
365
|
-
const tools =
|
|
565
|
+
const tools = VALID_TOOLS;
|
|
366
566
|
const levels = ["project", "global"];
|
|
367
567
|
for (const tool of tools) {
|
|
368
568
|
for (const level of levels) {
|
|
@@ -371,6 +571,9 @@ function scanInstalled() {
|
|
|
371
571
|
for (const manifest of manifests) {
|
|
372
572
|
const plugin = plugins.find((p) => p.id === manifest.team);
|
|
373
573
|
if (!plugin) continue;
|
|
574
|
+
if (!verifyPluginFilesExist(configRoot, manifest, tool, level)) {
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
374
577
|
const alreadyAdded = installed.some(
|
|
375
578
|
(p) => p.id === plugin.id && p.tool === tool && p.level === level
|
|
376
579
|
);
|
|
@@ -390,275 +593,74 @@ function scanInstalled() {
|
|
|
390
593
|
}
|
|
391
594
|
return installed;
|
|
392
595
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
wizard_detected_tool: "\u68C0\u6D4B\u5230",
|
|
403
|
-
wizard_confirm_tool: "\u4F7F\u7528",
|
|
404
|
-
wizard_last_used: "\u4E0A\u6B21\u4F7F\u7528",
|
|
405
|
-
wizard_already_installed: "\u5DF2\u5B89\u88C5",
|
|
406
|
-
install_cloning: "\u6B63\u5728\u514B\u9686 cannbot-skills \u4ED3\u5E93...",
|
|
407
|
-
install_repo_ready: "\u4ED3\u5E93\u5C31\u7EEA",
|
|
408
|
-
install_progress: "\u6B63\u5728\u5B89\u88C5",
|
|
409
|
-
install_done: "\u5B89\u88C5\u5B8C\u6210",
|
|
410
|
-
install_success: "\u6210\u529F",
|
|
411
|
-
install_failed: "\u5931\u8D25",
|
|
412
|
-
install_already_installed: "\u5DF2\u5B89\u88C5",
|
|
413
|
-
install_reinstall_confirm: "\u662F\u5426\u91CD\u65B0\u5B89\u88C5\uFF08\u8986\u76D6\uFF09",
|
|
414
|
-
install_skip: "\u8DF3\u8FC7",
|
|
415
|
-
install_enhanced_next_steps: "\u4E0B\u4E00\u6B65",
|
|
416
|
-
install_enhanced_launch: "\u542F\u52A8",
|
|
417
|
-
install_enhanced_try: "\u8BD5\u8BD5",
|
|
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];
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// src/utils/logger.ts
|
|
580
|
-
import chalk from "chalk";
|
|
581
|
-
import ora from "ora";
|
|
582
|
-
var logger = {
|
|
583
|
-
success: (msg) => console.log(chalk.green("\u2713") + " " + msg),
|
|
584
|
-
error: (msg) => console.log(chalk.red("\u2717") + " " + msg),
|
|
585
|
-
warn: (msg) => console.log(chalk.yellow("\u26A0") + " " + msg),
|
|
586
|
-
info: (msg) => console.log(chalk.cyan("\u2192") + " " + msg),
|
|
587
|
-
step: (msg) => console.log(chalk.dim(msg)),
|
|
588
|
-
blank: () => console.log()
|
|
589
|
-
};
|
|
590
|
-
function createSpinner(text) {
|
|
591
|
-
return ora({ text, color: "cyan" });
|
|
592
|
-
}
|
|
593
|
-
function printBanner(subtitle) {
|
|
594
|
-
console.log(chalk.cyan(`
|
|
595
|
-
____ _ _ _ _ _ ____ _
|
|
596
|
-
/ ___| / \\ | \\ | | \\ | | __ ) ___ | |_
|
|
597
|
-
| | / _ \\ | \\| | \\| | _ \\ / _ \\| __|
|
|
598
|
-
| |___ / ___ \\| |\\ | |\\ | |_) | (_) | |_
|
|
599
|
-
\\____/_/ \\_\\_| \\_|_| \\_|____/ \\___/ \\__|
|
|
600
|
-
`));
|
|
601
|
-
if (subtitle) {
|
|
602
|
-
console.log(chalk.bold(` ${subtitle}`));
|
|
603
|
-
console.log();
|
|
596
|
+
function verifyPluginFilesExist(configRoot, manifest, tool, level) {
|
|
597
|
+
const skillsDir = join5(configRoot, "skills");
|
|
598
|
+
const agentsDir = join5(configRoot, "agents");
|
|
599
|
+
if (manifest.installed_skills && manifest.installed_skills.length > 0) {
|
|
600
|
+
const hasAnySkill = manifest.installed_skills.some((skillId) => {
|
|
601
|
+
const skillPath = join5(skillsDir, skillId);
|
|
602
|
+
return existsSync4(skillPath);
|
|
603
|
+
});
|
|
604
|
+
if (!hasAnySkill) return false;
|
|
604
605
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
if (
|
|
611
|
-
width += 2;
|
|
612
|
-
} else {
|
|
613
|
-
width += 1;
|
|
614
|
-
}
|
|
606
|
+
if (manifest.installed_agents && manifest.installed_agents.length > 0) {
|
|
607
|
+
const hasAnyAgent = manifest.installed_agents.some((agentId) => {
|
|
608
|
+
const agentPath = join5(agentsDir, agentId);
|
|
609
|
+
return existsSync4(agentPath);
|
|
610
|
+
});
|
|
611
|
+
if (!hasAnyAgent) return false;
|
|
615
612
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const border = "\u2550".repeat(width);
|
|
620
|
-
const displayWidth = getDisplayWidth(title);
|
|
621
|
-
const totalPadding = width - displayWidth;
|
|
622
|
-
const leftPadding = Math.floor(totalPadding / 2);
|
|
623
|
-
const rightPadding = totalPadding - leftPadding;
|
|
624
|
-
const paddedTitle = " ".repeat(Math.max(0, leftPadding)) + title + " ".repeat(Math.max(0, rightPadding));
|
|
625
|
-
console.log();
|
|
626
|
-
console.log(chalk.cyan.bold(` \u2554${border}\u2557`));
|
|
627
|
-
console.log(chalk.cyan.bold(` \u2551`) + chalk.cyan.bold(paddedTitle) + chalk.cyan.bold(`\u2551`));
|
|
628
|
-
console.log(chalk.cyan.bold(` \u255A${border}\u255D`));
|
|
629
|
-
console.log();
|
|
630
|
-
}
|
|
631
|
-
function showOperationHints(isCheckbox = false) {
|
|
632
|
-
if (isCheckbox) {
|
|
633
|
-
console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u7A7A\u683C\x1B[0m \u9009\u62E9 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4
|
|
634
|
-
`);
|
|
613
|
+
let agentsMdPath;
|
|
614
|
+
if (tool === "opencode" && level === "project") {
|
|
615
|
+
agentsMdPath = join5(configRoot, "..", "AGENTS.md");
|
|
635
616
|
} else {
|
|
636
|
-
|
|
637
|
-
`);
|
|
617
|
+
agentsMdPath = join5(configRoot, "AGENTS.md");
|
|
638
618
|
}
|
|
619
|
+
if (!existsSync4(agentsMdPath)) return false;
|
|
620
|
+
return true;
|
|
639
621
|
}
|
|
640
622
|
|
|
641
623
|
// src/utils/config.ts
|
|
642
|
-
import { existsSync as
|
|
643
|
-
import { join as
|
|
644
|
-
import { homedir as homedir3 } from "os";
|
|
624
|
+
import { existsSync as existsSync6, mkdirSync, readFileSync as readFileSync4 } from "fs";
|
|
625
|
+
import { join as join6 } from "path";
|
|
645
626
|
import { parse, stringify } from "yaml";
|
|
627
|
+
|
|
628
|
+
// src/utils/fs.ts
|
|
629
|
+
import { writeFileSync, renameSync, existsSync as existsSync5, unlinkSync } from "fs";
|
|
630
|
+
import { dirname as dirname3 } from "path";
|
|
631
|
+
function atomicWriteFileSync(filePath, data, encoding = "utf-8") {
|
|
632
|
+
const dir = dirname3(filePath);
|
|
633
|
+
if (!existsSync5(dir)) {
|
|
634
|
+
throw new Error(`Directory does not exist: ${dir}`);
|
|
635
|
+
}
|
|
636
|
+
const tmpPath = `${filePath}.tmp`;
|
|
637
|
+
writeFileSync(tmpPath, data, encoding);
|
|
638
|
+
if (existsSync5(filePath)) {
|
|
639
|
+
try {
|
|
640
|
+
unlinkSync(filePath);
|
|
641
|
+
} catch {
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
renameSync(tmpPath, filePath);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// src/utils/config.ts
|
|
646
648
|
function getConfigDir() {
|
|
647
|
-
return
|
|
649
|
+
return getCannbotConfigDir();
|
|
648
650
|
}
|
|
649
651
|
function getConfigPath() {
|
|
650
|
-
return
|
|
652
|
+
return join6(getConfigDir(), "config.yaml");
|
|
651
653
|
}
|
|
652
654
|
function readConfig() {
|
|
653
655
|
const configPath = getConfigPath();
|
|
654
|
-
if (!
|
|
656
|
+
if (!existsSync6(configPath)) {
|
|
655
657
|
return {
|
|
656
658
|
language: "zh_CN",
|
|
657
659
|
installedPlugins: []
|
|
658
660
|
};
|
|
659
661
|
}
|
|
660
662
|
try {
|
|
661
|
-
const content =
|
|
663
|
+
const content = readFileSync4(configPath, "utf-8");
|
|
662
664
|
const parsed = parse(content);
|
|
663
665
|
return {
|
|
664
666
|
language: parsed.language || "zh_CN",
|
|
@@ -668,6 +670,7 @@ function readConfig() {
|
|
|
668
670
|
installedPlugins: parsed.installedPlugins || []
|
|
669
671
|
};
|
|
670
672
|
} catch {
|
|
673
|
+
logger.warn(t("config_corrupted"));
|
|
671
674
|
return {
|
|
672
675
|
language: "zh_CN",
|
|
673
676
|
installedPlugins: []
|
|
@@ -676,12 +679,12 @@ function readConfig() {
|
|
|
676
679
|
}
|
|
677
680
|
function writeConfig(config) {
|
|
678
681
|
const configDir = getConfigDir();
|
|
679
|
-
if (!
|
|
682
|
+
if (!existsSync6(configDir)) {
|
|
680
683
|
mkdirSync(configDir, { recursive: true });
|
|
681
684
|
}
|
|
682
685
|
const configPath = getConfigPath();
|
|
683
686
|
const content = stringify(config);
|
|
684
|
-
|
|
687
|
+
atomicWriteFileSync(configPath, content);
|
|
685
688
|
}
|
|
686
689
|
function updateConfig(updates) {
|
|
687
690
|
const config = readConfig();
|
|
@@ -704,22 +707,11 @@ function removeInstalledPlugin(pluginId) {
|
|
|
704
707
|
writeConfig(config);
|
|
705
708
|
}
|
|
706
709
|
|
|
707
|
-
// src/
|
|
708
|
-
var selectTheme = {
|
|
709
|
-
helpMode: "never"
|
|
710
|
-
};
|
|
711
|
-
var checkboxTheme = {
|
|
712
|
-
icon: {
|
|
713
|
-
checked: "\x1B[32m\u2611\x1B[22m",
|
|
714
|
-
unchecked: "\u2610",
|
|
715
|
-
cursor: "\u276F"
|
|
716
|
-
},
|
|
717
|
-
helpMode: "never"
|
|
718
|
-
};
|
|
719
|
-
|
|
720
|
-
// src/ui/wizard.ts
|
|
710
|
+
// src/utils/constants.ts
|
|
721
711
|
var BACK = "__back__";
|
|
722
712
|
var CANCEL = "__cancel__";
|
|
713
|
+
|
|
714
|
+
// src/ui/wizard.ts
|
|
723
715
|
async function selectToolWithDetection() {
|
|
724
716
|
const config = readConfig();
|
|
725
717
|
logger.blank();
|
|
@@ -789,7 +781,7 @@ async function runWizard() {
|
|
|
789
781
|
step = 2;
|
|
790
782
|
break;
|
|
791
783
|
}
|
|
792
|
-
if (result === CANCEL
|
|
784
|
+
if (result === CANCEL) {
|
|
793
785
|
return { language: config.language || "zh_CN", tool: selectedTool, level, plugins: [], confirmed: false };
|
|
794
786
|
}
|
|
795
787
|
updateConfig({ lastTool: selectedTool, lastLevel: level });
|
|
@@ -799,11 +791,11 @@ async function runWizard() {
|
|
|
799
791
|
}
|
|
800
792
|
}
|
|
801
793
|
async function stepTool(detectedTools, lastTool) {
|
|
802
|
-
printBoxTitle("
|
|
794
|
+
printBoxTitle(t("wizard_step_1_title"));
|
|
803
795
|
if (detectedTools.length === 0) {
|
|
804
796
|
logger.error(t("error_tool_not_found"));
|
|
805
797
|
const choices2 = [
|
|
806
|
-
...
|
|
798
|
+
...VALID_TOOLS.map((tool) => ({
|
|
807
799
|
name: `> ${getToolDisplayName(tool)}`,
|
|
808
800
|
value: tool
|
|
809
801
|
})),
|
|
@@ -874,10 +866,10 @@ async function stepToolManual(lastTool) {
|
|
|
874
866
|
return result;
|
|
875
867
|
}
|
|
876
868
|
async function stepLevel(lastLevel) {
|
|
877
|
-
printBoxTitle("
|
|
869
|
+
printBoxTitle(t("wizard_step_2_title"));
|
|
878
870
|
const choices = [
|
|
879
|
-
{ name:
|
|
880
|
-
{ name:
|
|
871
|
+
{ name: `> project \u2014 ${t("install_level_project")}`, value: "project" },
|
|
872
|
+
{ name: `> global \u2014 ${t("install_level_global")}`, value: "global" },
|
|
881
873
|
new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
882
874
|
{ name: "<- " + t("wizard_back"), value: BACK },
|
|
883
875
|
{ name: "x " + t("wizard_cancel"), value: CANCEL }
|
|
@@ -895,7 +887,7 @@ async function stepLevel(lastLevel) {
|
|
|
895
887
|
return result;
|
|
896
888
|
}
|
|
897
889
|
async function stepPlugins(tool, level) {
|
|
898
|
-
printBoxTitle("
|
|
890
|
+
printBoxTitle(t("wizard_step_3_title"));
|
|
899
891
|
const plugins = getAllPlugins();
|
|
900
892
|
const configRoot = getConfigRoot(tool, level);
|
|
901
893
|
const manifests = readAllManifests(configRoot);
|
|
@@ -904,14 +896,12 @@ async function stepPlugins(tool, level) {
|
|
|
904
896
|
const isInstalled = installedSet.has(p.id);
|
|
905
897
|
const suffix = isInstalled ? ` [${t("wizard_already_installed")}]` : "";
|
|
906
898
|
return {
|
|
907
|
-
name: `${p.displayName}${suffix}
|
|
899
|
+
name: `${chalk3.cyan(p.displayName)}${suffix}`,
|
|
908
900
|
value: p.id,
|
|
909
|
-
checked: false
|
|
901
|
+
checked: false,
|
|
902
|
+
description: p.description
|
|
910
903
|
};
|
|
911
904
|
});
|
|
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
905
|
showOperationHints(true);
|
|
916
906
|
const selected = await checkbox({
|
|
917
907
|
message: t("wizard_select_plugins"),
|
|
@@ -921,9 +911,7 @@ async function stepPlugins(tool, level) {
|
|
|
921
911
|
theme: checkboxTheme,
|
|
922
912
|
pageSize: 15
|
|
923
913
|
});
|
|
924
|
-
|
|
925
|
-
if (selected.includes(CANCEL)) return CANCEL;
|
|
926
|
-
const pluginIds = selected.filter((v) => v !== BACK && v !== CANCEL);
|
|
914
|
+
const pluginIds = selected;
|
|
927
915
|
if (pluginIds.length === 0) {
|
|
928
916
|
const action = await select({
|
|
929
917
|
message: t("wizard_no_selection"),
|
|
@@ -941,17 +929,17 @@ async function stepPlugins(tool, level) {
|
|
|
941
929
|
return pluginIds;
|
|
942
930
|
}
|
|
943
931
|
async function stepConfirm(tool, level, plugins) {
|
|
944
|
-
printBoxTitle("
|
|
932
|
+
printBoxTitle(t("wizard_step_4_title"));
|
|
945
933
|
const allPlugins = getAllPlugins();
|
|
946
934
|
const selectedPlugins = plugins.map((id) => allPlugins.find((p) => p.id === id));
|
|
947
935
|
const displayPath = getConfigRoot(tool, level);
|
|
948
936
|
const toolName = getToolDisplayName(tool);
|
|
949
937
|
const levelText = level === "project" ? "project" : "global";
|
|
950
|
-
logger.info(
|
|
951
|
-
logger.info(
|
|
938
|
+
logger.info(t("wizard_confirm_install_format").replace("{count}", chalk3.bold(String(plugins.length))).replace("{tool}", chalk3.green(toolName)).replace("{level}", chalk3.cyan(levelText)));
|
|
939
|
+
logger.info(`${t("wizard_install_path")}: ${chalk3.cyan(displayPath)}`);
|
|
952
940
|
for (const plugin of selectedPlugins) {
|
|
953
941
|
if (plugin) {
|
|
954
|
-
logger.step(` \u2022 ${
|
|
942
|
+
logger.step(` \u2022 ${chalk3.bold(plugin.displayName)}`);
|
|
955
943
|
}
|
|
956
944
|
}
|
|
957
945
|
logger.blank();
|
|
@@ -969,14 +957,19 @@ async function stepConfirm(tool, level, plugins) {
|
|
|
969
957
|
}
|
|
970
958
|
|
|
971
959
|
// src/core/repository.ts
|
|
972
|
-
import { existsSync as
|
|
973
|
-
import { join as
|
|
960
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync6 } from "fs";
|
|
961
|
+
import { join as join8, dirname as dirname5 } from "path";
|
|
962
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
974
963
|
import { execa as execa2 } from "execa";
|
|
964
|
+
import { parse as parseYaml3 } from "yaml";
|
|
975
965
|
|
|
976
966
|
// src/core/scanner.ts
|
|
977
|
-
import { existsSync as
|
|
978
|
-
import { join as
|
|
979
|
-
import {
|
|
967
|
+
import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
968
|
+
import { join as join7, dirname as dirname4 } from "path";
|
|
969
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
970
|
+
import { parse as parseYaml2 } from "yaml";
|
|
971
|
+
var __filename3 = fileURLToPath3(import.meta.url);
|
|
972
|
+
var __dirname3 = dirname4(__filename3);
|
|
980
973
|
var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
981
974
|
"node_modules",
|
|
982
975
|
".git",
|
|
@@ -997,46 +990,70 @@ var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
|
997
990
|
"docs",
|
|
998
991
|
"scripts"
|
|
999
992
|
]);
|
|
1000
|
-
|
|
1001
|
-
"
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
]
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
993
|
+
function loadScanConfig() {
|
|
994
|
+
const configPath = join7(__dirname3, "config", "repository.yaml");
|
|
995
|
+
try {
|
|
996
|
+
const config = parseYaml2(readFileSync5(configPath, "utf-8"));
|
|
997
|
+
const ttlHours = config.scanCacheTtlHours || 24;
|
|
998
|
+
return {
|
|
999
|
+
skillDirs: config.scanDirs || ["ops", "model", "graph", "infra", "ops-lab", "runtime"],
|
|
1000
|
+
pluginDirs: config.pluginDirs || ["plugins-official", "plugins-community"],
|
|
1001
|
+
cacheTtlMs: ttlHours * 60 * 60 * 1e3
|
|
1002
|
+
};
|
|
1003
|
+
} catch {
|
|
1004
|
+
return {
|
|
1005
|
+
skillDirs: ["ops", "model", "graph", "infra", "ops-lab", "runtime"],
|
|
1006
|
+
pluginDirs: ["plugins-official", "plugins-community"],
|
|
1007
|
+
cacheTtlMs: 24 * 60 * 60 * 1e3
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
var scanConfig = loadScanConfig();
|
|
1012
|
+
var SKILL_SCAN_DIRS = scanConfig.skillDirs;
|
|
1013
|
+
var PLUGIN_SCAN_DIRS = scanConfig.pluginDirs;
|
|
1011
1014
|
function scanSkills(repoPath) {
|
|
1012
1015
|
const skills = [];
|
|
1013
1016
|
const seen = /* @__PURE__ */ new Set();
|
|
1014
1017
|
for (const dir of SKILL_SCAN_DIRS) {
|
|
1015
|
-
const fullPath =
|
|
1016
|
-
if (!
|
|
1018
|
+
const fullPath = join7(repoPath, dir);
|
|
1019
|
+
if (!existsSync7(fullPath)) continue;
|
|
1017
1020
|
scanDirectory(fullPath, repoPath, dir, skills, seen);
|
|
1018
1021
|
}
|
|
1019
1022
|
for (const pluginDir of PLUGIN_SCAN_DIRS) {
|
|
1020
|
-
const pluginParent =
|
|
1021
|
-
if (!
|
|
1023
|
+
const pluginParent = join7(repoPath, pluginDir);
|
|
1024
|
+
if (!existsSync7(pluginParent)) continue;
|
|
1022
1025
|
for (const plugin of safeReaddir(pluginParent)) {
|
|
1023
|
-
const pluginPath =
|
|
1026
|
+
const pluginPath = join7(pluginParent, plugin);
|
|
1024
1027
|
if (!isDirectory(pluginPath)) continue;
|
|
1025
|
-
const skillsDir =
|
|
1026
|
-
if (
|
|
1028
|
+
const skillsDir = join7(pluginPath, "skills");
|
|
1029
|
+
if (existsSync7(skillsDir)) {
|
|
1027
1030
|
scanDirectory(skillsDir, repoPath, `${pluginDir}/${plugin}/skills`, skills, seen);
|
|
1028
1031
|
}
|
|
1029
|
-
const skillDir =
|
|
1030
|
-
if (
|
|
1032
|
+
const skillDir = join7(pluginPath, "skill");
|
|
1033
|
+
if (existsSync7(skillDir)) {
|
|
1031
1034
|
scanDirectory(skillDir, repoPath, `${pluginDir}/${plugin}/skill`, skills, seen);
|
|
1032
1035
|
}
|
|
1036
|
+
const rootSkillMd = join7(pluginPath, "SKILL.md");
|
|
1037
|
+
const initSh = join7(pluginPath, "init.sh");
|
|
1038
|
+
if (existsSync7(rootSkillMd) && !existsSync7(initSh)) {
|
|
1039
|
+
const frontmatter = parseFrontmatter(rootSkillMd);
|
|
1040
|
+
if (frontmatter && !seen.has(frontmatter.name)) {
|
|
1041
|
+
seen.add(frontmatter.name);
|
|
1042
|
+
skills.push({
|
|
1043
|
+
id: frontmatter.name,
|
|
1044
|
+
description: frontmatter.description,
|
|
1045
|
+
source: `${pluginDir}/${plugin}`,
|
|
1046
|
+
filePath: rootSkillMd
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1033
1050
|
}
|
|
1034
1051
|
}
|
|
1035
1052
|
return skills;
|
|
1036
1053
|
}
|
|
1037
1054
|
function parseFrontmatter(filePath) {
|
|
1038
1055
|
try {
|
|
1039
|
-
const content =
|
|
1056
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
1040
1057
|
const lines = content.split("\n");
|
|
1041
1058
|
if (lines.length < 3) return null;
|
|
1042
1059
|
if (lines[0].trim() !== "---") return null;
|
|
@@ -1049,11 +1066,15 @@ function parseFrontmatter(filePath) {
|
|
|
1049
1066
|
}
|
|
1050
1067
|
if (endIndex === -1) return null;
|
|
1051
1068
|
const yamlBlock = lines.slice(1, endIndex).map((l) => l.replace(/\r$/, "")).join("\n");
|
|
1052
|
-
const parsed =
|
|
1069
|
+
const parsed = parseYaml2(yamlBlock);
|
|
1053
1070
|
if (!parsed || typeof parsed !== "object") return null;
|
|
1054
1071
|
if (!parsed.name || typeof parsed.name !== "string") return null;
|
|
1072
|
+
const trimmedName = parsed.name.trim();
|
|
1073
|
+
if (!trimmedName || /[\/\\]|\.\./.test(trimmedName) || !/^[a-zA-Z0-9._-]+$/.test(trimmedName)) {
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1055
1076
|
return {
|
|
1056
|
-
name:
|
|
1077
|
+
name: trimmedName,
|
|
1057
1078
|
description: (parsed.description || "").trim()
|
|
1058
1079
|
};
|
|
1059
1080
|
} catch {
|
|
@@ -1062,17 +1083,17 @@ function parseFrontmatter(filePath) {
|
|
|
1062
1083
|
}
|
|
1063
1084
|
function getCurrentCommit(repoPath) {
|
|
1064
1085
|
try {
|
|
1065
|
-
const headFile =
|
|
1066
|
-
if (!
|
|
1067
|
-
const headContent =
|
|
1086
|
+
const headFile = join7(repoPath, ".git", "HEAD");
|
|
1087
|
+
if (!existsSync7(headFile)) return "unknown";
|
|
1088
|
+
const headContent = readFileSync5(headFile, "utf-8").trim();
|
|
1068
1089
|
if (headContent.startsWith("ref: ")) {
|
|
1069
|
-
const refPath =
|
|
1070
|
-
if (
|
|
1071
|
-
return
|
|
1090
|
+
const refPath = join7(repoPath, ".git", headContent.slice(5));
|
|
1091
|
+
if (existsSync7(refPath)) {
|
|
1092
|
+
return readFileSync5(refPath, "utf-8").trim();
|
|
1072
1093
|
}
|
|
1073
|
-
const packedRefsPath =
|
|
1074
|
-
if (
|
|
1075
|
-
const packedRefs =
|
|
1094
|
+
const packedRefsPath = join7(repoPath, ".git", "packed-refs");
|
|
1095
|
+
if (existsSync7(packedRefsPath)) {
|
|
1096
|
+
const packedRefs = readFileSync5(packedRefsPath, "utf-8");
|
|
1076
1097
|
const refName = headContent.slice(5);
|
|
1077
1098
|
const lines = packedRefs.split("\n");
|
|
1078
1099
|
for (const line of lines) {
|
|
@@ -1090,12 +1111,12 @@ function getCurrentCommit(repoPath) {
|
|
|
1090
1111
|
function readScanCache() {
|
|
1091
1112
|
try {
|
|
1092
1113
|
const cachePath = getCachePath();
|
|
1093
|
-
if (!
|
|
1094
|
-
const content =
|
|
1114
|
+
if (!existsSync7(cachePath)) return null;
|
|
1115
|
+
const content = readFileSync5(cachePath, "utf-8");
|
|
1095
1116
|
const cache = JSON.parse(content);
|
|
1096
1117
|
if (!cache.skills || !cache.timestamp) return null;
|
|
1097
1118
|
const age = Date.now() - cache.timestamp;
|
|
1098
|
-
if (age >
|
|
1119
|
+
if (age > scanConfig.cacheTtlMs) return null;
|
|
1099
1120
|
return cache;
|
|
1100
1121
|
} catch {
|
|
1101
1122
|
return null;
|
|
@@ -1104,39 +1125,24 @@ function readScanCache() {
|
|
|
1104
1125
|
function writeScanCache(cache) {
|
|
1105
1126
|
try {
|
|
1106
1127
|
const cachePath = getCachePath();
|
|
1107
|
-
const cacheDir =
|
|
1108
|
-
if (!
|
|
1128
|
+
const cacheDir = dirname4(cachePath);
|
|
1129
|
+
if (!existsSync7(cacheDir)) {
|
|
1109
1130
|
mkdirSync2(cacheDir, { recursive: true });
|
|
1110
1131
|
}
|
|
1111
|
-
|
|
1132
|
+
atomicWriteFileSync(cachePath, JSON.stringify(cache, null, 2));
|
|
1112
1133
|
} catch {
|
|
1113
1134
|
}
|
|
1114
1135
|
}
|
|
1115
1136
|
function getCachePath() {
|
|
1116
|
-
|
|
1117
|
-
return join5(homeDir, ".cannbot", "scan-cache.json");
|
|
1137
|
+
return join7(getCannbotConfigDir(), "scan-cache.json");
|
|
1118
1138
|
}
|
|
1119
1139
|
function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
|
|
1120
|
-
const selfSkillMd = join5(dirPath, "SKILL.md");
|
|
1121
|
-
if (existsSync5(selfSkillMd)) {
|
|
1122
|
-
const frontmatter = parseFrontmatter(selfSkillMd);
|
|
1123
|
-
if (frontmatter && !seen.has(frontmatter.name)) {
|
|
1124
|
-
seen.add(frontmatter.name);
|
|
1125
|
-
skills.push({
|
|
1126
|
-
id: frontmatter.name,
|
|
1127
|
-
description: frontmatter.description,
|
|
1128
|
-
source: sourcePrefix,
|
|
1129
|
-
filePath: selfSkillMd
|
|
1130
|
-
});
|
|
1131
|
-
}
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
1140
|
for (const entry of safeReaddir(dirPath)) {
|
|
1135
|
-
const fullPath =
|
|
1141
|
+
const fullPath = join7(dirPath, entry);
|
|
1136
1142
|
if (EXCLUDE_DIRS.has(entry)) continue;
|
|
1137
1143
|
if (!isDirectory(fullPath)) continue;
|
|
1138
|
-
const skillMd =
|
|
1139
|
-
if (
|
|
1144
|
+
const skillMd = join7(fullPath, "SKILL.md");
|
|
1145
|
+
if (existsSync7(skillMd)) {
|
|
1140
1146
|
const frontmatter = parseFrontmatter(skillMd);
|
|
1141
1147
|
if (frontmatter && !seen.has(frontmatter.name)) {
|
|
1142
1148
|
seen.add(frontmatter.name);
|
|
@@ -1149,38 +1155,32 @@ function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
|
|
|
1149
1155
|
}
|
|
1150
1156
|
continue;
|
|
1151
1157
|
}
|
|
1152
|
-
scanDirectory(fullPath, repoPath, sourcePrefix, skills, seen);
|
|
1158
|
+
scanDirectory(fullPath, repoPath, sourcePrefix + "/" + entry, skills, seen);
|
|
1153
1159
|
}
|
|
1154
1160
|
}
|
|
1155
1161
|
function safeReaddir(dir) {
|
|
1156
1162
|
try {
|
|
1157
|
-
return
|
|
1163
|
+
return readdirSync2(dir);
|
|
1158
1164
|
} catch {
|
|
1159
1165
|
return [];
|
|
1160
1166
|
}
|
|
1161
1167
|
}
|
|
1162
|
-
function isDirectory(path) {
|
|
1163
|
-
try {
|
|
1164
|
-
return statSync(path).isDirectory();
|
|
1165
|
-
} catch {
|
|
1166
|
-
return false;
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
1168
|
|
|
1170
1169
|
// src/core/skill-registry.ts
|
|
1171
1170
|
var CATEGORY_DEFS = [
|
|
1172
|
-
{ id: "knowledge", name: "\u77E5\u8BC6\u4E0E\u53C2\u8003" },
|
|
1173
|
-
{ id: "env-tools", name: "\u73AF\u5883\u4E0E\u5DE5\u5177" },
|
|
1174
|
-
{ id: "debug", name: "\u8C03\u8BD5\u4E0E\u8BCA\u65AD" },
|
|
1175
|
-
{ id: "testing", name: "\u6D4B\u8BD5\u4E0E\u8D28\u91CF" },
|
|
1176
|
-
{ id: "ascendc", name: "AscendC \u5F00\u53D1" },
|
|
1177
|
-
{ id: "pypto", name: "PyPTO \u5F00\u53D1" },
|
|
1178
|
-
{ id: "tilelang", name: "TileLang \u5F00\u53D1" },
|
|
1179
|
-
{ id: "triton", name: "Triton \u5F00\u53D1" },
|
|
1180
|
-
{ id: "model", name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316" },
|
|
1181
|
-
{ id: "
|
|
1182
|
-
{ id: "
|
|
1183
|
-
{ id: "
|
|
1171
|
+
{ id: "knowledge", name: "\u77E5\u8BC6\u4E0E\u53C2\u8003", nameEn: "Knowledge & Reference" },
|
|
1172
|
+
{ id: "env-tools", name: "\u73AF\u5883\u4E0E\u5DE5\u5177", nameEn: "Environment & Tools" },
|
|
1173
|
+
{ id: "debug", name: "\u8C03\u8BD5\u4E0E\u8BCA\u65AD", nameEn: "Debug & Diagnostics" },
|
|
1174
|
+
{ id: "testing", name: "\u6D4B\u8BD5\u4E0E\u8D28\u91CF", nameEn: "Testing & Quality" },
|
|
1175
|
+
{ id: "ascendc", name: "AscendC \u5F00\u53D1", nameEn: "AscendC Development" },
|
|
1176
|
+
{ id: "pypto", name: "PyPTO \u5F00\u53D1", nameEn: "PyPTO Development" },
|
|
1177
|
+
{ id: "tilelang", name: "TileLang \u5F00\u53D1", nameEn: "TileLang Development" },
|
|
1178
|
+
{ id: "triton", name: "Triton \u5F00\u53D1", nameEn: "Triton Development" },
|
|
1179
|
+
{ id: "model", name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316", nameEn: "Model Inference Optimization" },
|
|
1180
|
+
{ id: "runtime", name: "Runtime \u8FC1\u79FB", nameEn: "Runtime Migration" },
|
|
1181
|
+
{ id: "graph", name: "\u56FE\u6A21\u5F0F", nameEn: "Graph Mode" },
|
|
1182
|
+
{ id: "platform", name: "\u5E73\u53F0\u5DE5\u5177", nameEn: "Platform Tools" },
|
|
1183
|
+
{ id: "other", name: "\u5176\u4ED6 Skills", nameEn: "Other Skills" }
|
|
1184
1184
|
];
|
|
1185
1185
|
var dynamicSkills = [];
|
|
1186
1186
|
var initialized = false;
|
|
@@ -1188,7 +1188,8 @@ function initFromScan(scanned) {
|
|
|
1188
1188
|
dynamicSkills = scanned.map((s) => ({
|
|
1189
1189
|
id: s.id,
|
|
1190
1190
|
description: s.description,
|
|
1191
|
-
source: s.source
|
|
1191
|
+
source: s.source,
|
|
1192
|
+
filePath: s.filePath
|
|
1192
1193
|
}));
|
|
1193
1194
|
initialized = true;
|
|
1194
1195
|
}
|
|
@@ -1264,9 +1265,7 @@ var STATIC_SKILL_CATEGORIES = [
|
|
|
1264
1265
|
{ id: "catlass-op-develop", description: "Catlass \u7B97\u5B50\u5F00\u53D1", source: "ops" },
|
|
1265
1266
|
{ id: "catlass-op-perf-tune", description: "Catlass \u6027\u80FD\u8C03\u4F18", source: "ops" },
|
|
1266
1267
|
{ id: "cuda2ascend-simt", description: "CUDA \u8FC1\u79FB\u5230 Ascend C SIMT", source: "ops-lab" },
|
|
1267
|
-
{ id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" }
|
|
1268
|
-
{ id: "ops-registry-invoke-workflow", description: "\u6CE8\u518C\u8C03\u7528\u5DE5\u4F5C\u6D41", source: "plugins-official/ops-registry-invoke" },
|
|
1269
|
-
{ id: "ops-easyasc-dsl", description: "EasyASC DSL \u7B97\u5B50\u5F00\u53D1", source: "plugins-community/ops-easyasc-dsl/skill" }
|
|
1268
|
+
{ id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" }
|
|
1270
1269
|
]
|
|
1271
1270
|
},
|
|
1272
1271
|
{
|
|
@@ -1323,7 +1322,9 @@ var STATIC_SKILL_CATEGORIES = [
|
|
|
1323
1322
|
{ id: "model-infer-prefetch", description: "\u6743\u91CD\u9884\u53D6\u9002\u914D", source: "model" },
|
|
1324
1323
|
{ id: "model-infer-superkernel", description: "SuperKernel \u9002\u914D", source: "model" },
|
|
1325
1324
|
{ id: "model-infer-precision-debug", description: "NPU \u63A8\u7406\u7CBE\u5EA6\u8BCA\u65AD", source: "model" },
|
|
1326
|
-
{ id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" }
|
|
1325
|
+
{ id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" },
|
|
1326
|
+
{ id: "model-infer-harmony", description: "\u7AEF\u4FA7\u9E3F\u8499 ASR \u91CF\u5316\u8F6C\u6362\u4E0E\u6253\u5305", source: "model" },
|
|
1327
|
+
{ id: "science-model-npu-migration", description: "NPU \u4EE3\u7801\u7EA7\u8FC1\u79FB\uFF08\u73AF\u5883\u95E8\u7981/\u811A\u672C\u9002\u914D/\u7CBE\u5EA6\u6027\u80FD\u5BF9\u6BD4\uFF09", source: "plugins-community/science-model-npu-migration", filePath: "plugins-community/science-model-npu-migration/SKILL.md" }
|
|
1327
1328
|
]
|
|
1328
1329
|
},
|
|
1329
1330
|
{
|
|
@@ -1339,6 +1340,13 @@ var STATIC_SKILL_CATEGORIES = [
|
|
|
1339
1340
|
{ id: "torch-custom-ops-guide", description: "\u81EA\u5B9A\u4E49\u7B97\u5B50\u5165\u56FE\u6307\u5357", source: "graph" }
|
|
1340
1341
|
]
|
|
1341
1342
|
},
|
|
1343
|
+
{
|
|
1344
|
+
id: "runtime",
|
|
1345
|
+
name: "Runtime \u8FC1\u79FB",
|
|
1346
|
+
skills: [
|
|
1347
|
+
{ id: "runtime_migration", description: "CUDA \u5E94\u7528\u8FC1\u79FB\u5230 CANN \u5E73\u53F0\u6307\u5357", source: "runtime" }
|
|
1348
|
+
]
|
|
1349
|
+
},
|
|
1342
1350
|
{
|
|
1343
1351
|
id: "platform",
|
|
1344
1352
|
name: "\u5E73\u53F0\u5DE5\u5177",
|
|
@@ -1382,9 +1390,10 @@ function getAllSkills() {
|
|
|
1382
1390
|
}
|
|
1383
1391
|
function getAllCategories() {
|
|
1384
1392
|
const skills = getActiveSkills();
|
|
1393
|
+
const isEn = t("list_name") === "Name";
|
|
1385
1394
|
return CATEGORY_DEFS.map((def) => ({
|
|
1386
1395
|
id: def.id,
|
|
1387
|
-
name: def.name,
|
|
1396
|
+
name: isEn ? def.nameEn : def.name,
|
|
1388
1397
|
skills: skills.filter((s) => {
|
|
1389
1398
|
const catId = CATEGORY_MAP[s.id];
|
|
1390
1399
|
if (catId) return catId === def.id;
|
|
@@ -1394,7 +1403,21 @@ function getAllCategories() {
|
|
|
1394
1403
|
}
|
|
1395
1404
|
|
|
1396
1405
|
// src/core/repository.ts
|
|
1397
|
-
var
|
|
1406
|
+
var __filename4 = fileURLToPath4(import.meta.url);
|
|
1407
|
+
var __dirname4 = dirname5(__filename4);
|
|
1408
|
+
function loadRepoUrl() {
|
|
1409
|
+
if (process.env.CANNBOT_REPO_URL) {
|
|
1410
|
+
return process.env.CANNBOT_REPO_URL;
|
|
1411
|
+
}
|
|
1412
|
+
const configPath = join8(__dirname4, "config", "repository.yaml");
|
|
1413
|
+
try {
|
|
1414
|
+
const config = parseYaml3(readFileSync6(configPath, "utf-8"));
|
|
1415
|
+
return config.repository?.url || "https://gitcode.com/cann/cannbot-skills.git";
|
|
1416
|
+
} catch {
|
|
1417
|
+
return "https://gitcode.com/cann/cannbot-skills.git";
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
var REPO_URL = loadRepoUrl();
|
|
1398
1421
|
var RepositoryManager = class {
|
|
1399
1422
|
repoPath;
|
|
1400
1423
|
constructor(customPath) {
|
|
@@ -1419,10 +1442,13 @@ var RepositoryManager = class {
|
|
|
1419
1442
|
this.repoPath = cachedPath;
|
|
1420
1443
|
return cachedPath;
|
|
1421
1444
|
}
|
|
1422
|
-
|
|
1445
|
+
const clonedPath = await this.cloneRepo();
|
|
1446
|
+
return clonedPath;
|
|
1423
1447
|
}
|
|
1424
1448
|
async ensureRepoAndScan() {
|
|
1425
1449
|
const repoPath = await this.ensureRepo();
|
|
1450
|
+
await this.updateRepo();
|
|
1451
|
+
mergeDynamicPlugins(repoPath);
|
|
1426
1452
|
const cache = readScanCache();
|
|
1427
1453
|
if (cache && cache.repoCommit === getCurrentCommit(repoPath)) {
|
|
1428
1454
|
initFromScan(cache.skills);
|
|
@@ -1442,7 +1468,9 @@ var RepositoryManager = class {
|
|
|
1442
1468
|
try {
|
|
1443
1469
|
await execa2("git", ["pull", "--quiet"], { cwd: repoPath, timeout: 3e4 });
|
|
1444
1470
|
} catch {
|
|
1471
|
+
logger.warn(t("repo_pull_failed"));
|
|
1445
1472
|
}
|
|
1473
|
+
mergeDynamicPlugins(repoPath);
|
|
1446
1474
|
}
|
|
1447
1475
|
getRepoPath() {
|
|
1448
1476
|
if (!this.repoPath) {
|
|
@@ -1451,18 +1479,18 @@ var RepositoryManager = class {
|
|
|
1451
1479
|
return this.repoPath;
|
|
1452
1480
|
}
|
|
1453
1481
|
isValidRepo(path) {
|
|
1454
|
-
if (!
|
|
1455
|
-
const gitDir =
|
|
1456
|
-
const pluginsDir =
|
|
1457
|
-
return
|
|
1482
|
+
if (!existsSync8(path)) return false;
|
|
1483
|
+
const gitDir = join8(path, ".git");
|
|
1484
|
+
const pluginsDir = join8(path, "plugins-official");
|
|
1485
|
+
return existsSync8(gitDir) && existsSync8(pluginsDir);
|
|
1458
1486
|
}
|
|
1459
1487
|
async cloneRepo() {
|
|
1460
1488
|
const configDir = getCannbotConfigDir();
|
|
1461
|
-
if (!
|
|
1489
|
+
if (!existsSync8(configDir)) {
|
|
1462
1490
|
mkdirSync3(configDir, { recursive: true });
|
|
1463
1491
|
}
|
|
1464
1492
|
const targetPath = getCannbotRepoPath();
|
|
1465
|
-
if (
|
|
1493
|
+
if (existsSync8(targetPath)) {
|
|
1466
1494
|
if (this.isValidRepo(targetPath)) {
|
|
1467
1495
|
this.repoPath = targetPath;
|
|
1468
1496
|
return targetPath;
|
|
@@ -1488,102 +1516,102 @@ function createRepositoryManager(customPath) {
|
|
|
1488
1516
|
}
|
|
1489
1517
|
|
|
1490
1518
|
// src/core/installer.ts
|
|
1491
|
-
import { existsSync as
|
|
1492
|
-
import { join as
|
|
1493
|
-
import { execa as
|
|
1519
|
+
import { existsSync as existsSync12 } from "fs";
|
|
1520
|
+
import { join as join12 } from "path";
|
|
1521
|
+
import { execa as execa4 } from "execa";
|
|
1494
1522
|
|
|
1495
1523
|
// src/core/record.ts
|
|
1496
|
-
import { existsSync as
|
|
1497
|
-
import { join as
|
|
1524
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
1525
|
+
import { join as join9 } from "path";
|
|
1498
1526
|
function getInstallsDir() {
|
|
1499
|
-
return
|
|
1527
|
+
return join9(getCannbotConfigDir(), "installs");
|
|
1500
1528
|
}
|
|
1501
1529
|
function getRecordPath(pluginId) {
|
|
1502
|
-
return
|
|
1530
|
+
return join9(getInstallsDir(), `${pluginId}.json`);
|
|
1503
1531
|
}
|
|
1504
1532
|
function readRecord(pluginId) {
|
|
1505
1533
|
const recordPath = getRecordPath(pluginId);
|
|
1506
|
-
if (!
|
|
1534
|
+
if (!existsSync9(recordPath)) {
|
|
1507
1535
|
return null;
|
|
1508
1536
|
}
|
|
1509
1537
|
try {
|
|
1510
|
-
const content =
|
|
1538
|
+
const content = readFileSync7(recordPath, "utf-8");
|
|
1511
1539
|
return JSON.parse(content);
|
|
1512
1540
|
} catch {
|
|
1541
|
+
logger.warn(t("record_corrupted").replace("{file}", recordPath));
|
|
1513
1542
|
return null;
|
|
1514
1543
|
}
|
|
1515
1544
|
}
|
|
1516
1545
|
function writeRecord(record) {
|
|
1517
1546
|
const installsDir = getInstallsDir();
|
|
1518
|
-
if (!
|
|
1547
|
+
if (!existsSync9(installsDir)) {
|
|
1519
1548
|
mkdirSync4(installsDir, { recursive: true });
|
|
1520
1549
|
}
|
|
1521
1550
|
const recordPath = getRecordPath(record.pluginId);
|
|
1522
|
-
|
|
1551
|
+
atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
|
|
1523
1552
|
}
|
|
1524
1553
|
function deleteRecord(pluginId) {
|
|
1525
1554
|
const recordPath = getRecordPath(pluginId);
|
|
1526
|
-
if (
|
|
1527
|
-
|
|
1555
|
+
if (existsSync9(recordPath)) {
|
|
1556
|
+
unlinkSync2(recordPath);
|
|
1528
1557
|
}
|
|
1529
1558
|
}
|
|
1530
1559
|
function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest) {
|
|
1531
1560
|
const files = [];
|
|
1532
1561
|
const directories = [];
|
|
1533
1562
|
if (manifest) {
|
|
1534
|
-
const skillsDir =
|
|
1535
|
-
if (
|
|
1563
|
+
const skillsDir = join9(configRoot, "skills");
|
|
1564
|
+
if (existsSync9(skillsDir)) {
|
|
1536
1565
|
directories.push(skillsDir);
|
|
1537
1566
|
for (const skillName of manifest.installed_skills || []) {
|
|
1538
|
-
const skillPath =
|
|
1539
|
-
if (
|
|
1567
|
+
const skillPath = join9(skillsDir, skillName);
|
|
1568
|
+
if (existsSync9(skillPath) || isSymlink(skillPath)) {
|
|
1540
1569
|
files.push(skillPath);
|
|
1541
1570
|
}
|
|
1542
1571
|
}
|
|
1543
1572
|
}
|
|
1544
|
-
const agentsDir =
|
|
1545
|
-
if (
|
|
1573
|
+
const agentsDir = join9(configRoot, "agents");
|
|
1574
|
+
if (existsSync9(agentsDir)) {
|
|
1546
1575
|
directories.push(agentsDir);
|
|
1547
1576
|
for (const agentName of manifest.installed_agents || []) {
|
|
1548
|
-
const agentPath =
|
|
1549
|
-
const agentPathMd =
|
|
1550
|
-
if (
|
|
1577
|
+
const agentPath = join9(agentsDir, agentName);
|
|
1578
|
+
const agentPathMd = join9(agentsDir, agentName + ".md");
|
|
1579
|
+
if (existsSync9(agentPath) || isSymlink(agentPath)) {
|
|
1551
1580
|
files.push(agentPath);
|
|
1552
|
-
} else if (
|
|
1581
|
+
} else if (existsSync9(agentPathMd) || isSymlink(agentPathMd)) {
|
|
1553
1582
|
files.push(agentPathMd);
|
|
1554
1583
|
}
|
|
1555
1584
|
}
|
|
1556
1585
|
}
|
|
1557
1586
|
}
|
|
1558
|
-
const workflowsLink =
|
|
1587
|
+
const workflowsLink = join9(configRoot, "workflows");
|
|
1559
1588
|
if (isSymlink(workflowsLink)) {
|
|
1560
1589
|
files.push(workflowsLink);
|
|
1561
1590
|
}
|
|
1562
|
-
const manifestPath =
|
|
1563
|
-
if (
|
|
1591
|
+
const manifestPath = join9(configRoot, "cannbot-manifest.json");
|
|
1592
|
+
if (existsSync9(manifestPath)) {
|
|
1564
1593
|
files.push(manifestPath);
|
|
1565
1594
|
}
|
|
1566
|
-
const pluginManifestPath =
|
|
1567
|
-
if (
|
|
1595
|
+
const pluginManifestPath = join9(configRoot, `${pluginId}-manifest.json`);
|
|
1596
|
+
if (existsSync9(pluginManifestPath)) {
|
|
1568
1597
|
files.push(pluginManifestPath);
|
|
1569
1598
|
}
|
|
1570
1599
|
const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
1571
|
-
const configFilePath = level === "project" ?
|
|
1572
|
-
if (
|
|
1600
|
+
const configFilePath = level === "project" ? join9(installPath, configFileName) : join9(configRoot, configFileName);
|
|
1601
|
+
if (existsSync9(configFilePath)) {
|
|
1573
1602
|
files.push(configFilePath);
|
|
1574
1603
|
}
|
|
1575
1604
|
const repoLinks = ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
|
|
1576
1605
|
for (const repoName of repoLinks) {
|
|
1577
|
-
const repoLinkPath =
|
|
1606
|
+
const repoLinkPath = join9(installPath, repoName);
|
|
1578
1607
|
if (isSymlink(repoLinkPath)) {
|
|
1579
1608
|
files.push(repoLinkPath);
|
|
1580
1609
|
}
|
|
1581
|
-
const repoLinkInConfig =
|
|
1610
|
+
const repoLinkInConfig = join9(configRoot, repoName);
|
|
1582
1611
|
if (isSymlink(repoLinkInConfig)) {
|
|
1583
1612
|
files.push(repoLinkInConfig);
|
|
1584
1613
|
}
|
|
1585
1614
|
}
|
|
1586
|
-
directories.push(configRoot);
|
|
1587
1615
|
return {
|
|
1588
1616
|
pluginId,
|
|
1589
1617
|
displayName,
|
|
@@ -1596,71 +1624,418 @@ function scanInstalledFiles(pluginId, displayName, tool, level, installPath, con
|
|
|
1596
1624
|
directories
|
|
1597
1625
|
};
|
|
1598
1626
|
}
|
|
1599
|
-
function
|
|
1627
|
+
function getSkillRecordPath() {
|
|
1628
|
+
return join9(getInstallsDir(), "skills.json");
|
|
1629
|
+
}
|
|
1630
|
+
function readSkillRecord() {
|
|
1631
|
+
const recordPath = getSkillRecordPath();
|
|
1632
|
+
if (!existsSync9(recordPath)) {
|
|
1633
|
+
return {};
|
|
1634
|
+
}
|
|
1600
1635
|
try {
|
|
1601
|
-
|
|
1636
|
+
const content = readFileSync7(recordPath, "utf-8");
|
|
1637
|
+
return JSON.parse(content);
|
|
1638
|
+
} catch {
|
|
1639
|
+
logger.warn(t("skill_record_corrupted"));
|
|
1640
|
+
return {};
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
function writeSkillRecord(record) {
|
|
1644
|
+
const installsDir = getInstallsDir();
|
|
1645
|
+
if (!existsSync9(installsDir)) {
|
|
1646
|
+
mkdirSync4(installsDir, { recursive: true });
|
|
1647
|
+
}
|
|
1648
|
+
const recordPath = getSkillRecordPath();
|
|
1649
|
+
atomicWriteFileSync(recordPath, JSON.stringify(record, null, 2));
|
|
1650
|
+
}
|
|
1651
|
+
function addSkillsToRecord(skillIds, tool, level, installPath) {
|
|
1652
|
+
const record = readSkillRecord();
|
|
1653
|
+
if (!record[tool]) record[tool] = {};
|
|
1654
|
+
if (!record[tool][level]) record[tool][level] = {};
|
|
1655
|
+
if (!record[tool][level][installPath]) {
|
|
1656
|
+
record[tool][level][installPath] = { skills: [], installTime: "" };
|
|
1657
|
+
}
|
|
1658
|
+
const entry = record[tool][level][installPath];
|
|
1659
|
+
for (const id of skillIds) {
|
|
1660
|
+
if (!entry.skills.includes(id)) {
|
|
1661
|
+
entry.skills.push(id);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
entry.installTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1665
|
+
writeSkillRecord(record);
|
|
1666
|
+
}
|
|
1667
|
+
function removeSkillsFromRecord(skillIds, tool, level, installPath) {
|
|
1668
|
+
const record = readSkillRecord();
|
|
1669
|
+
if (!record[tool]?.[level]?.[installPath]) return;
|
|
1670
|
+
const entry = record[tool][level][installPath];
|
|
1671
|
+
entry.skills = entry.skills.filter((id) => !skillIds.includes(id));
|
|
1672
|
+
if (entry.skills.length === 0) {
|
|
1673
|
+
delete record[tool][level][installPath];
|
|
1674
|
+
if (Object.keys(record[tool][level]).length === 0) {
|
|
1675
|
+
delete record[tool][level];
|
|
1676
|
+
if (Object.keys(record[tool]).length === 0) {
|
|
1677
|
+
delete record[tool];
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
writeSkillRecord(record);
|
|
1682
|
+
}
|
|
1683
|
+
function getInstalledSkills(tool, level, installPath) {
|
|
1684
|
+
const record = readSkillRecord();
|
|
1685
|
+
const recordedSkills = record[tool]?.[level]?.[installPath]?.skills || [];
|
|
1686
|
+
const configRoot = getConfigRoot(tool, level);
|
|
1687
|
+
const skillsDir = join9(configRoot, "skills");
|
|
1688
|
+
return recordedSkills.filter((skillId) => {
|
|
1689
|
+
const skillPath = join9(skillsDir, skillId);
|
|
1690
|
+
return existsSync9(skillPath) || isSymlink(skillPath);
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// src/core/backup.ts
|
|
1695
|
+
import { existsSync as existsSync10, copyFileSync, readdirSync as readdirSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
1696
|
+
import { join as join10 } from "path";
|
|
1697
|
+
function detectCurrentPlugin(configRoot, tool) {
|
|
1698
|
+
const manifest = readManifest(configRoot);
|
|
1699
|
+
if (manifest?.team) {
|
|
1700
|
+
const plugin = findPlugin(manifest.team);
|
|
1701
|
+
if (plugin) {
|
|
1702
|
+
return {
|
|
1703
|
+
pluginId: plugin.id,
|
|
1704
|
+
pluginName: plugin.displayName
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
const agentsFile = join10(configRoot, getAgentsFileName(tool));
|
|
1709
|
+
if (!existsSync10(agentsFile)) {
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
const allRecords = readAllRecords();
|
|
1713
|
+
for (const record of allRecords) {
|
|
1714
|
+
if (record.configRoot === configRoot && record.tool === tool) {
|
|
1715
|
+
const plugin = findPlugin(record.pluginId);
|
|
1716
|
+
if (plugin) {
|
|
1717
|
+
return {
|
|
1718
|
+
pluginId: plugin.id,
|
|
1719
|
+
pluginName: plugin.displayName
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
return null;
|
|
1725
|
+
}
|
|
1726
|
+
function createBackup(configRoot, tool, fromPluginId, fromPluginName) {
|
|
1727
|
+
const agentsFile = join10(configRoot, getAgentsFileName(tool));
|
|
1728
|
+
if (!existsSync10(agentsFile)) {
|
|
1729
|
+
return null;
|
|
1730
|
+
}
|
|
1731
|
+
const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
|
|
1732
|
+
const backupFileName = `${getAgentsFileName(tool)}.cannbot-backup.${fromPluginId}.${timestamp}`;
|
|
1733
|
+
const backupPath = join10(configRoot, backupFileName);
|
|
1734
|
+
try {
|
|
1735
|
+
copyFileSync(agentsFile, backupPath);
|
|
1736
|
+
return {
|
|
1737
|
+
filePath: backupPath,
|
|
1738
|
+
pluginId: fromPluginId,
|
|
1739
|
+
pluginName: fromPluginName,
|
|
1740
|
+
backupTime: timestamp
|
|
1741
|
+
};
|
|
1742
|
+
} catch {
|
|
1743
|
+
return null;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
function findBackups(configRoot) {
|
|
1747
|
+
if (!existsSync10(configRoot)) {
|
|
1748
|
+
return [];
|
|
1749
|
+
}
|
|
1750
|
+
const files = readdirSync3(configRoot);
|
|
1751
|
+
const backups = [];
|
|
1752
|
+
for (const file of files) {
|
|
1753
|
+
if (file.includes(".cannbot-backup.")) {
|
|
1754
|
+
const parts = file.split(".cannbot-backup.");
|
|
1755
|
+
if (parts.length === 2) {
|
|
1756
|
+
const pluginIdAndTime = parts[1];
|
|
1757
|
+
const lastDotIndex = pluginIdAndTime.lastIndexOf(".");
|
|
1758
|
+
if (lastDotIndex > 0) {
|
|
1759
|
+
const pluginId = pluginIdAndTime.substring(0, lastDotIndex);
|
|
1760
|
+
const timestamp = pluginIdAndTime.substring(lastDotIndex + 1);
|
|
1761
|
+
const plugin = findPlugin(pluginId);
|
|
1762
|
+
backups.push({
|
|
1763
|
+
filePath: join10(configRoot, file),
|
|
1764
|
+
pluginId,
|
|
1765
|
+
pluginName: plugin?.displayName || pluginId,
|
|
1766
|
+
backupTime: timestamp
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
return backups.sort((a, b) => b.backupTime.localeCompare(a.backupTime));
|
|
1773
|
+
}
|
|
1774
|
+
function restoreBackup(backupPath, configRoot, tool) {
|
|
1775
|
+
try {
|
|
1776
|
+
const agentsFile = join10(configRoot, getAgentsFileName(tool));
|
|
1777
|
+
copyFileSync(backupPath, agentsFile);
|
|
1778
|
+
return true;
|
|
1779
|
+
} catch {
|
|
1780
|
+
return false;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
function deleteBackup(backupPath) {
|
|
1784
|
+
try {
|
|
1785
|
+
if (existsSync10(backupPath)) {
|
|
1786
|
+
unlinkSync3(backupPath);
|
|
1787
|
+
return true;
|
|
1788
|
+
}
|
|
1789
|
+
return false;
|
|
1602
1790
|
} catch {
|
|
1603
1791
|
return false;
|
|
1604
1792
|
}
|
|
1605
1793
|
}
|
|
1606
|
-
function
|
|
1607
|
-
|
|
1794
|
+
function formatTimestamp(date) {
|
|
1795
|
+
const year = date.getFullYear();
|
|
1796
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
1797
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
1798
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
1799
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
1800
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
1801
|
+
return `${year}${month}${day}-${hours}${minutes}${seconds}`;
|
|
1608
1802
|
}
|
|
1609
|
-
function
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
|
|
1803
|
+
function readAllRecords() {
|
|
1804
|
+
const records = [];
|
|
1805
|
+
const plugins = getAllPlugins();
|
|
1806
|
+
for (const plugin of plugins) {
|
|
1807
|
+
const record = readRecord(plugin.id);
|
|
1808
|
+
if (record) {
|
|
1809
|
+
records.push(record);
|
|
1810
|
+
}
|
|
1613
1811
|
}
|
|
1812
|
+
return records;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// src/ui/backup-prompts.ts
|
|
1816
|
+
import { select as select2 } from "@inquirer/prompts";
|
|
1817
|
+
import chalk4 from "chalk";
|
|
1818
|
+
async function showOverwriteWarning(currentPluginName, newPluginName) {
|
|
1819
|
+
console.log();
|
|
1820
|
+
console.log(chalk4.yellow(` \u26A0\uFE0F ${t("backup_detected")}`));
|
|
1821
|
+
console.log();
|
|
1822
|
+
console.log(` ${t("backup_current")}: ${chalk4.cyan(currentPluginName)}`);
|
|
1823
|
+
console.log(` ${t("backup_installing")}: ${chalk4.cyan(newPluginName)}`);
|
|
1824
|
+
console.log();
|
|
1825
|
+
console.log(` ${t("backup_overwrite_warning")}`);
|
|
1826
|
+
console.log(` ${t("backup_auto_create")}`);
|
|
1827
|
+
console.log();
|
|
1828
|
+
const choice = await select2({
|
|
1829
|
+
message: t("backup_select_action"),
|
|
1830
|
+
choices: [
|
|
1831
|
+
{ name: t("backup_overwrite"), value: "overwrite" },
|
|
1832
|
+
{ name: t("backup_cancel"), value: "cancel" }
|
|
1833
|
+
]
|
|
1834
|
+
});
|
|
1835
|
+
return choice;
|
|
1836
|
+
}
|
|
1837
|
+
async function showRestorePrompt(backups) {
|
|
1838
|
+
console.log();
|
|
1839
|
+
console.log(chalk4.cyan(` ${t("backup_restore_prompt")}`));
|
|
1840
|
+
console.log();
|
|
1841
|
+
const choices = backups.map((b) => ({
|
|
1842
|
+
name: `${t("backup_restore")} ${b.pluginName} (${b.backupTime})`,
|
|
1843
|
+
value: b.filePath
|
|
1844
|
+
}));
|
|
1845
|
+
choices.push({ name: t("backup_only_uninstall"), value: "none" });
|
|
1846
|
+
const choice = await select2({
|
|
1847
|
+
message: t("backup_select_action"),
|
|
1848
|
+
choices
|
|
1849
|
+
});
|
|
1850
|
+
return choice;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// src/core/plugin-installer.ts
|
|
1854
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync4, realpathSync, copyFileSync as copyFileSync2, cpSync, writeFileSync as writeFileSync3, readFileSync as readFileSync8 } from "fs";
|
|
1855
|
+
import { join as join11 } from "path";
|
|
1856
|
+
import { execa as execa3 } from "execa";
|
|
1857
|
+
function readPluginManifest(pluginDir) {
|
|
1858
|
+
const manifestPath = join11(pluginDir, "plugin-manifest.json");
|
|
1859
|
+
if (!existsSync11(manifestPath)) return null;
|
|
1614
1860
|
try {
|
|
1615
|
-
const content =
|
|
1616
|
-
|
|
1861
|
+
const content = JSON.parse(readFileSync8(manifestPath, "utf-8"));
|
|
1862
|
+
if (!content || !content.id) return null;
|
|
1863
|
+
return content;
|
|
1617
1864
|
} catch {
|
|
1618
|
-
return
|
|
1865
|
+
return null;
|
|
1619
1866
|
}
|
|
1620
1867
|
}
|
|
1621
|
-
function
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1868
|
+
function installLink(source, target) {
|
|
1869
|
+
try {
|
|
1870
|
+
const resolved = realpathSync(source);
|
|
1871
|
+
if (existsSync11(target) || isSymlink(target)) {
|
|
1872
|
+
unlinkSync4(target);
|
|
1873
|
+
}
|
|
1874
|
+
try {
|
|
1875
|
+
symlinkSync(resolved, target);
|
|
1876
|
+
return "symlink";
|
|
1877
|
+
} catch (e) {
|
|
1878
|
+
if (e.code === "EPERM") {
|
|
1879
|
+
cpSync(resolved, target, { recursive: true });
|
|
1880
|
+
return "copy";
|
|
1881
|
+
}
|
|
1882
|
+
throw e;
|
|
1883
|
+
}
|
|
1884
|
+
} catch {
|
|
1885
|
+
return "failed";
|
|
1625
1886
|
}
|
|
1626
|
-
const recordPath = getSkillRecordPath();
|
|
1627
|
-
writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
|
|
1628
1887
|
}
|
|
1629
|
-
function
|
|
1630
|
-
const
|
|
1631
|
-
if (!
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1888
|
+
function installConfigFile(pluginDir, configFile, tool, level, configRoot, installPath) {
|
|
1889
|
+
const sourcePath = join11(pluginDir, configFile);
|
|
1890
|
+
if (!existsSync11(sourcePath)) return;
|
|
1891
|
+
const agentsFileName = getAgentsFileName(tool);
|
|
1892
|
+
const isProjectLevel = level === "project";
|
|
1893
|
+
let primaryTarget;
|
|
1894
|
+
if (isProjectLevel) {
|
|
1895
|
+
primaryTarget = join11(installPath, agentsFileName);
|
|
1896
|
+
} else {
|
|
1897
|
+
primaryTarget = join11(configRoot, agentsFileName);
|
|
1635
1898
|
}
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1899
|
+
if (existsSync11(primaryTarget) || isSymlink(primaryTarget)) {
|
|
1900
|
+
unlinkSync4(primaryTarget);
|
|
1901
|
+
}
|
|
1902
|
+
try {
|
|
1903
|
+
symlinkSync(realpathSync(sourcePath), primaryTarget);
|
|
1904
|
+
} catch (e) {
|
|
1905
|
+
if (e.code === "EPERM") {
|
|
1906
|
+
copyFileSync2(sourcePath, primaryTarget);
|
|
1907
|
+
} else {
|
|
1908
|
+
throw e;
|
|
1640
1909
|
}
|
|
1641
1910
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
if (Object.keys(record[tool][level]).length === 0) {
|
|
1653
|
-
delete record[tool][level];
|
|
1654
|
-
if (Object.keys(record[tool]).length === 0) {
|
|
1655
|
-
delete record[tool];
|
|
1911
|
+
if (isProjectLevel && primaryTarget !== join11(configRoot, agentsFileName)) {
|
|
1912
|
+
const configRootTarget = join11(configRoot, agentsFileName);
|
|
1913
|
+
if (existsSync11(configRootTarget) || isSymlink(configRootTarget)) {
|
|
1914
|
+
unlinkSync4(configRootTarget);
|
|
1915
|
+
}
|
|
1916
|
+
try {
|
|
1917
|
+
symlinkSync(realpathSync(sourcePath), configRootTarget);
|
|
1918
|
+
} catch (e) {
|
|
1919
|
+
if (e.code === "EPERM") {
|
|
1920
|
+
copyFileSync2(sourcePath, configRootTarget);
|
|
1656
1921
|
}
|
|
1657
1922
|
}
|
|
1658
1923
|
}
|
|
1659
|
-
writeSkillRecord(record);
|
|
1660
1924
|
}
|
|
1661
|
-
function
|
|
1662
|
-
|
|
1663
|
-
|
|
1925
|
+
async function installExternalRepos(repos, repoPath) {
|
|
1926
|
+
if (!repos || repos.length === 0) return;
|
|
1927
|
+
for (const repo of repos) {
|
|
1928
|
+
const targetDir = join11(repoPath, repo.dir);
|
|
1929
|
+
if (existsSync11(targetDir)) {
|
|
1930
|
+
logger.info(` ${repo.dir} already exists, skipping clone`);
|
|
1931
|
+
continue;
|
|
1932
|
+
}
|
|
1933
|
+
const args = ["clone"];
|
|
1934
|
+
if (repo.depth) {
|
|
1935
|
+
args.push("--depth", String(repo.depth));
|
|
1936
|
+
}
|
|
1937
|
+
if (repo.recursive) {
|
|
1938
|
+
args.push("--recursive");
|
|
1939
|
+
}
|
|
1940
|
+
args.push(repo.url, targetDir);
|
|
1941
|
+
try {
|
|
1942
|
+
await execa3("git", args, { timeout: 12e4 });
|
|
1943
|
+
} catch {
|
|
1944
|
+
logger.warn(` git clone failed: ${repo.url}, clone manually: git clone ${repo.url} ${repo.dir}`);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
function writeCannbotManifest(configRoot, pluginId, version, level, tool, skills, agents) {
|
|
1949
|
+
const manifest = {
|
|
1950
|
+
brand: "CANNBot",
|
|
1951
|
+
version,
|
|
1952
|
+
team: pluginId,
|
|
1953
|
+
level,
|
|
1954
|
+
tool,
|
|
1955
|
+
installed_skills: skills,
|
|
1956
|
+
installed_agents: agents,
|
|
1957
|
+
brand_dir: configRoot,
|
|
1958
|
+
install_time: (/* @__PURE__ */ new Date()).toISOString()
|
|
1959
|
+
};
|
|
1960
|
+
const manifestPath = join11(configRoot, "cannbot-manifest.json");
|
|
1961
|
+
writeFileSync3(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
1962
|
+
return manifest;
|
|
1963
|
+
}
|
|
1964
|
+
async function installViaManifest(manifest, pluginDir, repoPath, tool, level, installPath) {
|
|
1965
|
+
const cwd = installPath || process.cwd();
|
|
1966
|
+
const configRoot = getConfigRoot(tool, level, installPath);
|
|
1967
|
+
try {
|
|
1968
|
+
const skillsDir = join11(configRoot, "skills");
|
|
1969
|
+
const agentsDir = join11(configRoot, "agents");
|
|
1970
|
+
mkdirSync5(skillsDir, { recursive: true });
|
|
1971
|
+
mkdirSync5(agentsDir, { recursive: true });
|
|
1972
|
+
const installedSkills = [];
|
|
1973
|
+
const installedAgents = [];
|
|
1974
|
+
for (const source of manifest.skills) {
|
|
1975
|
+
const sourceDir = join11(repoPath, source.dir);
|
|
1976
|
+
if (!existsSync11(sourceDir)) {
|
|
1977
|
+
logger.warn(` Skill source not found: ${source.dir}`);
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
for (const skillEntry of source.skills) {
|
|
1981
|
+
const skillName = typeof skillEntry === "string" ? skillEntry : skillEntry.name;
|
|
1982
|
+
const skillAlias = typeof skillEntry === "string" ? skillName : skillEntry.as || skillName;
|
|
1983
|
+
const skillPath = join11(sourceDir, skillName);
|
|
1984
|
+
if (!existsSync11(skillPath)) {
|
|
1985
|
+
logger.warn(` Skill not found: ${skillName} in ${source.dir}`);
|
|
1986
|
+
continue;
|
|
1987
|
+
}
|
|
1988
|
+
const targetPath = join11(skillsDir, skillAlias);
|
|
1989
|
+
const result = installLink(skillPath, targetPath);
|
|
1990
|
+
if (result !== "failed") {
|
|
1991
|
+
installedSkills.push(skillAlias);
|
|
1992
|
+
} else {
|
|
1993
|
+
logger.warn(` Failed to install skill: ${skillAlias}`);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
for (const agentName of manifest.agents) {
|
|
1998
|
+
const agentFile = agentName.endsWith(".md") ? agentName : `${agentName}.md`;
|
|
1999
|
+
const agentPath = join11(pluginDir, "agents", agentFile);
|
|
2000
|
+
if (!existsSync11(agentPath)) {
|
|
2001
|
+
logger.warn(` Agent not found: ${agentFile}`);
|
|
2002
|
+
continue;
|
|
2003
|
+
}
|
|
2004
|
+
const targetPath = join11(agentsDir, agentFile);
|
|
2005
|
+
const result = installLink(agentPath, targetPath);
|
|
2006
|
+
if (result !== "failed") {
|
|
2007
|
+
installedAgents.push(agentFile);
|
|
2008
|
+
} else {
|
|
2009
|
+
logger.warn(` Failed to install agent: ${agentFile}`);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
installConfigFile(pluginDir, manifest.configFile, tool, level, configRoot, cwd);
|
|
2013
|
+
await installExternalRepos(manifest.externalRepos, repoPath);
|
|
2014
|
+
const cannbotManifest = writeCannbotManifest(
|
|
2015
|
+
configRoot,
|
|
2016
|
+
manifest.id,
|
|
2017
|
+
manifest.version || "1.0.0",
|
|
2018
|
+
level,
|
|
2019
|
+
tool,
|
|
2020
|
+
installedSkills,
|
|
2021
|
+
installedAgents
|
|
2022
|
+
);
|
|
2023
|
+
return {
|
|
2024
|
+
success: true,
|
|
2025
|
+
skillsCount: installedSkills.length,
|
|
2026
|
+
agentsCount: installedAgents.length,
|
|
2027
|
+
errors: [],
|
|
2028
|
+
manifest: cannbotManifest
|
|
2029
|
+
};
|
|
2030
|
+
} catch (error) {
|
|
2031
|
+
return {
|
|
2032
|
+
success: false,
|
|
2033
|
+
skillsCount: 0,
|
|
2034
|
+
agentsCount: 0,
|
|
2035
|
+
errors: [error instanceof Error ? error.message : t("error_unknown")],
|
|
2036
|
+
manifest: null
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
1664
2039
|
}
|
|
1665
2040
|
|
|
1666
2041
|
// src/core/installer.ts
|
|
@@ -1672,18 +2047,116 @@ async function installPlugin(opts) {
|
|
|
1672
2047
|
pluginId: opts.pluginId,
|
|
1673
2048
|
skillsCount: 0,
|
|
1674
2049
|
agentsCount: 0,
|
|
1675
|
-
errors: [
|
|
2050
|
+
errors: [t("error_plugin_not_found") + ": " + opts.pluginId],
|
|
1676
2051
|
warnings: []
|
|
1677
2052
|
};
|
|
1678
2053
|
}
|
|
1679
|
-
const
|
|
1680
|
-
|
|
2054
|
+
const pluginDir = join12(opts.repoPath, plugin.dir);
|
|
2055
|
+
const cwd = opts.installPath || process.cwd();
|
|
2056
|
+
const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
|
|
2057
|
+
const agentsFile = join12(configRoot, getAgentsFileName(opts.tool));
|
|
2058
|
+
let backupInfo = null;
|
|
2059
|
+
if (existsSync12(agentsFile)) {
|
|
2060
|
+
const currentPlugin = detectCurrentPlugin(configRoot, opts.tool);
|
|
2061
|
+
if (currentPlugin && currentPlugin.pluginId !== opts.pluginId) {
|
|
2062
|
+
let choice = "overwrite";
|
|
2063
|
+
if (!opts.yes) {
|
|
2064
|
+
choice = await showOverwriteWarning(
|
|
2065
|
+
currentPlugin.pluginName,
|
|
2066
|
+
plugin.displayName
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
if (choice === "cancel") {
|
|
2070
|
+
logger.info(t("backup_cancel"));
|
|
2071
|
+
return {
|
|
2072
|
+
success: false,
|
|
2073
|
+
pluginId: opts.pluginId,
|
|
2074
|
+
skillsCount: 0,
|
|
2075
|
+
agentsCount: 0,
|
|
2076
|
+
errors: [t("backup_cancel")],
|
|
2077
|
+
warnings: []
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
backupInfo = createBackup(
|
|
2081
|
+
configRoot,
|
|
2082
|
+
opts.tool,
|
|
2083
|
+
currentPlugin.pluginId,
|
|
2084
|
+
currentPlugin.pluginName
|
|
2085
|
+
);
|
|
2086
|
+
if (!backupInfo) {
|
|
2087
|
+
logger.error(t("backup_failed"));
|
|
2088
|
+
return {
|
|
2089
|
+
success: false,
|
|
2090
|
+
pluginId: opts.pluginId,
|
|
2091
|
+
skillsCount: 0,
|
|
2092
|
+
agentsCount: 0,
|
|
2093
|
+
errors: [t("backup_failed")],
|
|
2094
|
+
warnings: []
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
logger.success(`${t("backup_created")}: ${backupInfo.filePath}`);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
const pluginManifest = readPluginManifest(pluginDir);
|
|
2101
|
+
if (pluginManifest) {
|
|
2102
|
+
const result = await installViaManifest(
|
|
2103
|
+
pluginManifest,
|
|
2104
|
+
pluginDir,
|
|
2105
|
+
opts.repoPath,
|
|
2106
|
+
opts.tool,
|
|
2107
|
+
opts.level,
|
|
2108
|
+
opts.installPath
|
|
2109
|
+
);
|
|
2110
|
+
if (result.success) {
|
|
2111
|
+
try {
|
|
2112
|
+
const record = scanInstalledFiles(
|
|
2113
|
+
opts.pluginId,
|
|
2114
|
+
plugin.displayName,
|
|
2115
|
+
opts.tool,
|
|
2116
|
+
opts.level,
|
|
2117
|
+
cwd,
|
|
2118
|
+
configRoot,
|
|
2119
|
+
result.manifest
|
|
2120
|
+
);
|
|
2121
|
+
if (backupInfo) {
|
|
2122
|
+
record.backup = {
|
|
2123
|
+
filePath: backupInfo.filePath,
|
|
2124
|
+
fromPluginId: backupInfo.pluginId,
|
|
2125
|
+
fromPluginName: backupInfo.pluginName,
|
|
2126
|
+
backupTime: backupInfo.backupTime
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
writeRecord(record);
|
|
2130
|
+
} catch {
|
|
2131
|
+
logger.warn(t("record_write_failed"));
|
|
2132
|
+
}
|
|
2133
|
+
return {
|
|
2134
|
+
success: true,
|
|
2135
|
+
pluginId: opts.pluginId,
|
|
2136
|
+
skillsCount: result.skillsCount,
|
|
2137
|
+
agentsCount: result.agentsCount,
|
|
2138
|
+
errors: result.errors,
|
|
2139
|
+
warnings: []
|
|
2140
|
+
};
|
|
2141
|
+
} else {
|
|
2142
|
+
return {
|
|
2143
|
+
success: false,
|
|
2144
|
+
pluginId: opts.pluginId,
|
|
2145
|
+
skillsCount: 0,
|
|
2146
|
+
agentsCount: 0,
|
|
2147
|
+
errors: result.errors,
|
|
2148
|
+
warnings: []
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
const scriptPath = join12(pluginDir, plugin.script);
|
|
2153
|
+
if (!existsSync12(scriptPath)) {
|
|
1681
2154
|
return {
|
|
1682
2155
|
success: false,
|
|
1683
2156
|
pluginId: opts.pluginId,
|
|
1684
2157
|
skillsCount: 0,
|
|
1685
2158
|
agentsCount: 0,
|
|
1686
|
-
errors: [
|
|
2159
|
+
errors: [t("error_script_not_found").replace("{path}", scriptPath)],
|
|
1687
2160
|
warnings: []
|
|
1688
2161
|
};
|
|
1689
2162
|
}
|
|
@@ -1691,14 +2164,15 @@ async function installPlugin(opts) {
|
|
|
1691
2164
|
if (opts.installPath) {
|
|
1692
2165
|
args.push(opts.installPath);
|
|
1693
2166
|
}
|
|
1694
|
-
const cwd = opts.installPath || process.cwd();
|
|
1695
2167
|
try {
|
|
1696
|
-
|
|
2168
|
+
const shell = process.platform === "win32" ? "sh" : "bash";
|
|
2169
|
+
const execOptions = {
|
|
1697
2170
|
cwd,
|
|
1698
2171
|
timeout: 3e5,
|
|
1699
|
-
stdio: "pipe"
|
|
1700
|
-
|
|
1701
|
-
|
|
2172
|
+
stdio: "pipe",
|
|
2173
|
+
input: opts.yes ? "y\n" : void 0
|
|
2174
|
+
};
|
|
2175
|
+
await execa4(shell, [scriptPath, ...args], execOptions);
|
|
1702
2176
|
const manifest = readManifest(configRoot);
|
|
1703
2177
|
let skillsCount = 0;
|
|
1704
2178
|
let agentsCount = 0;
|
|
@@ -1719,8 +2193,17 @@ async function installPlugin(opts) {
|
|
|
1719
2193
|
configRoot,
|
|
1720
2194
|
manifest
|
|
1721
2195
|
);
|
|
2196
|
+
if (backupInfo) {
|
|
2197
|
+
record.backup = {
|
|
2198
|
+
filePath: backupInfo.filePath,
|
|
2199
|
+
fromPluginId: backupInfo.pluginId,
|
|
2200
|
+
fromPluginName: backupInfo.pluginName,
|
|
2201
|
+
backupTime: backupInfo.backupTime
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
1722
2204
|
writeRecord(record);
|
|
1723
2205
|
} catch {
|
|
2206
|
+
logger.warn(t("record_write_failed"));
|
|
1724
2207
|
}
|
|
1725
2208
|
return {
|
|
1726
2209
|
success: true,
|
|
@@ -1731,7 +2214,7 @@ async function installPlugin(opts) {
|
|
|
1731
2214
|
warnings: []
|
|
1732
2215
|
};
|
|
1733
2216
|
} catch (error) {
|
|
1734
|
-
const errorMessage = error instanceof Error ? error.message : "
|
|
2217
|
+
const errorMessage = error instanceof Error ? error.message : t("error_unknown");
|
|
1735
2218
|
return {
|
|
1736
2219
|
success: false,
|
|
1737
2220
|
pluginId: opts.pluginId,
|
|
@@ -1744,41 +2227,46 @@ async function installPlugin(opts) {
|
|
|
1744
2227
|
}
|
|
1745
2228
|
|
|
1746
2229
|
// src/core/skill-installer.ts
|
|
1747
|
-
import { existsSync as
|
|
1748
|
-
import { join as
|
|
1749
|
-
import { select as
|
|
1750
|
-
import
|
|
2230
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, symlinkSync as symlinkSync2, unlinkSync as unlinkSync5, readdirSync as readdirSync4, rmdirSync, realpathSync as realpathSync2 } from "fs";
|
|
2231
|
+
import { join as join13, dirname as dirname7 } from "path";
|
|
2232
|
+
import { select as select3, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
|
|
2233
|
+
import chalk5 from "chalk";
|
|
1751
2234
|
import Table from "cli-table3";
|
|
1752
2235
|
async function installSkills(skillIds, tool, level, repoPath) {
|
|
1753
2236
|
const configRoot = getConfigRoot(tool, level);
|
|
1754
|
-
const skillsDir =
|
|
2237
|
+
const skillsDir = join13(configRoot, "skills");
|
|
1755
2238
|
const installPath = level === "project" ? process.cwd() : configRoot;
|
|
1756
|
-
if (!
|
|
1757
|
-
|
|
2239
|
+
if (!existsSync13(skillsDir)) {
|
|
2240
|
+
mkdirSync6(skillsDir, { recursive: true });
|
|
1758
2241
|
}
|
|
1759
2242
|
const results = [];
|
|
1760
2243
|
const installedIds = [];
|
|
1761
2244
|
for (const skillId of skillIds) {
|
|
1762
2245
|
const skill = findSkill(skillId);
|
|
1763
2246
|
if (!skill) {
|
|
1764
|
-
results.push({ skillId, success: false, error: "
|
|
2247
|
+
results.push({ skillId, success: false, error: t("error_skill_not_found") });
|
|
1765
2248
|
continue;
|
|
1766
2249
|
}
|
|
1767
|
-
const sourcePath =
|
|
1768
|
-
const targetPath =
|
|
1769
|
-
if (!
|
|
1770
|
-
results.push({ skillId, success: false, error:
|
|
2250
|
+
const sourcePath = resolveSkillSourcePath(skill, repoPath);
|
|
2251
|
+
const targetPath = join13(skillsDir, skillId);
|
|
2252
|
+
if (!existsSync13(sourcePath)) {
|
|
2253
|
+
results.push({ skillId, success: false, error: t("error_source_not_found").replace("{path}", sourcePath) });
|
|
1771
2254
|
continue;
|
|
1772
2255
|
}
|
|
1773
2256
|
try {
|
|
1774
|
-
|
|
1775
|
-
|
|
2257
|
+
const resolvedSource = realpathSync2(sourcePath);
|
|
2258
|
+
if (existsSync13(targetPath) || isSymlink(targetPath)) {
|
|
2259
|
+
unlinkSync5(targetPath);
|
|
1776
2260
|
}
|
|
1777
|
-
|
|
2261
|
+
symlinkSync2(resolvedSource, targetPath);
|
|
1778
2262
|
results.push({ skillId, success: true });
|
|
1779
2263
|
installedIds.push(skillId);
|
|
1780
2264
|
} catch (error) {
|
|
1781
|
-
|
|
2265
|
+
let msg = error instanceof Error ? error.message : t("error_unknown");
|
|
2266
|
+
if (process.platform === "win32" && msg.includes("EPERM")) {
|
|
2267
|
+
msg = t("error_symlink_windows");
|
|
2268
|
+
}
|
|
2269
|
+
results.push({ skillId, success: false, error: msg });
|
|
1782
2270
|
}
|
|
1783
2271
|
}
|
|
1784
2272
|
if (installedIds.length > 0) {
|
|
@@ -1788,109 +2276,118 @@ async function installSkills(skillIds, tool, level, repoPath) {
|
|
|
1788
2276
|
}
|
|
1789
2277
|
async function uninstallSkills(skillIds, tool, level) {
|
|
1790
2278
|
const configRoot = getConfigRoot(tool, level);
|
|
1791
|
-
const skillsDir =
|
|
2279
|
+
const skillsDir = join13(configRoot, "skills");
|
|
1792
2280
|
const installPath = level === "project" ? process.cwd() : configRoot;
|
|
1793
2281
|
const results = [];
|
|
1794
2282
|
const removedIds = [];
|
|
1795
2283
|
for (const skillId of skillIds) {
|
|
1796
|
-
const targetPath =
|
|
2284
|
+
const targetPath = join13(skillsDir, skillId);
|
|
1797
2285
|
try {
|
|
1798
|
-
if (
|
|
1799
|
-
|
|
2286
|
+
if (existsSync13(targetPath) || isSymlink(targetPath)) {
|
|
2287
|
+
unlinkSync5(targetPath);
|
|
1800
2288
|
results.push({ skillId, success: true });
|
|
1801
2289
|
removedIds.push(skillId);
|
|
1802
|
-
logger.step(`
|
|
2290
|
+
logger.step(` ${t("uninstall_remove_label")}: ${skillId}`);
|
|
1803
2291
|
} else {
|
|
1804
|
-
results.push({ skillId, success: false, error: "
|
|
1805
|
-
logger.warn(`${skillId}
|
|
2292
|
+
results.push({ skillId, success: false, error: t("error_not_installed") });
|
|
2293
|
+
logger.warn(`${skillId} ${t("uninstall_skill_not_installed")}`);
|
|
1806
2294
|
}
|
|
1807
2295
|
} catch (error) {
|
|
1808
|
-
results.push({ skillId, success: false, error: error instanceof Error ? error.message : "
|
|
2296
|
+
results.push({ skillId, success: false, error: error instanceof Error ? error.message : t("error_unknown") });
|
|
1809
2297
|
}
|
|
1810
2298
|
}
|
|
1811
2299
|
if (removedIds.length > 0) {
|
|
1812
2300
|
removeSkillsFromRecord(removedIds, tool, level, installPath);
|
|
1813
2301
|
}
|
|
1814
|
-
if (
|
|
2302
|
+
if (existsSync13(skillsDir)) {
|
|
1815
2303
|
try {
|
|
1816
|
-
const entries =
|
|
2304
|
+
const entries = readdirSync4(skillsDir);
|
|
1817
2305
|
if (entries.length === 0) {
|
|
1818
2306
|
rmdirSync(skillsDir);
|
|
1819
|
-
logger.step(`
|
|
1820
|
-
}
|
|
1821
|
-
} catch {
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
if (existsSync9(configRoot)) {
|
|
1825
|
-
try {
|
|
1826
|
-
const entries = readdirSync3(configRoot);
|
|
1827
|
-
if (entries.length === 0) {
|
|
1828
|
-
rmdirSync(configRoot);
|
|
1829
|
-
logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${configRoot.split("/").pop()}/`);
|
|
2307
|
+
logger.step(` ${t("uninstall_clean_empty_dir")}: skills/`);
|
|
1830
2308
|
}
|
|
1831
2309
|
} catch {
|
|
1832
2310
|
}
|
|
1833
2311
|
}
|
|
1834
2312
|
return results;
|
|
1835
2313
|
}
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
2314
|
+
var SELECT_ALL = "__select_all__";
|
|
2315
|
+
var SELECT_ALL_CATEGORY = "__select_all_category__";
|
|
2316
|
+
var CONTINUE_SELECT = "__continue_select__";
|
|
2317
|
+
async function interactiveSkillSelect(tool, level) {
|
|
1839
2318
|
const categories = getAllCategories();
|
|
2319
|
+
const allSkillIds = getAllSkills().map((s) => s.id);
|
|
2320
|
+
let accumulated = [];
|
|
1840
2321
|
let step = 0;
|
|
1841
2322
|
let selectedCategoryId = "";
|
|
1842
2323
|
while (true) {
|
|
1843
2324
|
switch (step) {
|
|
1844
2325
|
case 0: {
|
|
1845
|
-
|
|
2326
|
+
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");
|
|
2327
|
+
printBoxTitle(titleSuffix);
|
|
1846
2328
|
const categoryChoices = [
|
|
2329
|
+
{
|
|
2330
|
+
name: `>> ${t("skill_select_all").replace("{count}", String(allSkillIds.length))}`,
|
|
2331
|
+
value: SELECT_ALL
|
|
2332
|
+
},
|
|
2333
|
+
new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
1847
2334
|
...categories.map((cat) => ({
|
|
1848
2335
|
name: `> ${cat.name} (${cat.skills.length} skills)`,
|
|
1849
2336
|
value: cat.id
|
|
1850
2337
|
})),
|
|
1851
2338
|
new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
1852
|
-
{ name:
|
|
1853
|
-
{ name: "
|
|
2339
|
+
...accumulated.length > 0 ? [{ name: `>> ${t("skill_confirm_install_count").replace("{count}", String(accumulated.length))}`, value: "install" }] : [],
|
|
2340
|
+
{ name: "<- " + t("wizard_back"), value: BACK },
|
|
2341
|
+
{ name: "x " + t("wizard_cancel"), value: CANCEL }
|
|
1854
2342
|
];
|
|
1855
2343
|
showOperationHints();
|
|
1856
|
-
selectedCategoryId = await
|
|
2344
|
+
selectedCategoryId = await select3({
|
|
1857
2345
|
message: t("skill_select_category"),
|
|
1858
2346
|
choices: categoryChoices,
|
|
1859
2347
|
loop: false,
|
|
1860
2348
|
theme: selectTheme,
|
|
1861
2349
|
pageSize: 15
|
|
1862
2350
|
});
|
|
1863
|
-
if (selectedCategoryId ===
|
|
1864
|
-
if (selectedCategoryId ===
|
|
2351
|
+
if (selectedCategoryId === BACK) return "back";
|
|
2352
|
+
if (selectedCategoryId === CANCEL) return "cancel";
|
|
2353
|
+
if (selectedCategoryId === SELECT_ALL) {
|
|
2354
|
+
return allSkillIds;
|
|
2355
|
+
}
|
|
2356
|
+
if (selectedCategoryId === "install") {
|
|
2357
|
+
return accumulated;
|
|
2358
|
+
}
|
|
1865
2359
|
step = 1;
|
|
1866
2360
|
break;
|
|
1867
2361
|
}
|
|
1868
2362
|
case 1: {
|
|
1869
2363
|
const category = categories.find((c) => c.id === selectedCategoryId);
|
|
1870
2364
|
if (!category) return [];
|
|
1871
|
-
printBoxTitle(
|
|
1872
|
-
const
|
|
1873
|
-
const
|
|
1874
|
-
const installedSkills = [];
|
|
1875
|
-
for (const tk of tools) {
|
|
1876
|
-
for (const l of levels) {
|
|
1877
|
-
const path = l === "project" ? process.cwd() : getConfigRoot(tk, l);
|
|
1878
|
-
installedSkills.push(...getInstalledSkills(tk, l, path));
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
2365
|
+
printBoxTitle(t("wizard_step_4_skill_title").replace("{category}", category.name));
|
|
2366
|
+
const installPath = level === "project" ? process.cwd() : getConfigRoot(tool, level);
|
|
2367
|
+
const installedSkills = getInstalledSkills(tool, level, installPath);
|
|
1881
2368
|
const installedSet = new Set(installedSkills);
|
|
1882
|
-
const
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
name:
|
|
1887
|
-
value:
|
|
1888
|
-
checked:
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2369
|
+
const categorySkillIds = new Set(category.skills.map((s) => s.id));
|
|
2370
|
+
const accumulatedInCategory = new Set(accumulated.filter((id) => categorySkillIds.has(id)));
|
|
2371
|
+
const skillChoices = [
|
|
2372
|
+
{
|
|
2373
|
+
name: `>> ${t("skill_select_all_category")}`,
|
|
2374
|
+
value: SELECT_ALL_CATEGORY,
|
|
2375
|
+
checked: accumulatedInCategory.size === category.skills.length,
|
|
2376
|
+
description: t("skill_select_all_category")
|
|
2377
|
+
},
|
|
2378
|
+
new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
2379
|
+
...category.skills.map((skill) => {
|
|
2380
|
+
const isInstalled = installedSet.has(skill.id);
|
|
2381
|
+
const isAccumulated = accumulatedInCategory.has(skill.id);
|
|
2382
|
+
const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : isAccumulated ? " [+]" : "";
|
|
2383
|
+
return {
|
|
2384
|
+
name: `${chalk5.cyan(skill.id)}${suffix}`,
|
|
2385
|
+
value: skill.id,
|
|
2386
|
+
checked: isAccumulated,
|
|
2387
|
+
description: skill.description
|
|
2388
|
+
};
|
|
2389
|
+
})
|
|
2390
|
+
];
|
|
1894
2391
|
showOperationHints(true);
|
|
1895
2392
|
const selectedSkills = await checkbox2({
|
|
1896
2393
|
message: t("skill_select_items"),
|
|
@@ -1900,16 +2397,21 @@ async function interactiveSkillSelect() {
|
|
|
1900
2397
|
theme: checkboxTheme,
|
|
1901
2398
|
pageSize: 15
|
|
1902
2399
|
});
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
2400
|
+
let skillIds;
|
|
2401
|
+
if (selectedSkills.includes(SELECT_ALL_CATEGORY)) {
|
|
2402
|
+
skillIds = category.skills.map((s) => s.id);
|
|
2403
|
+
} else {
|
|
2404
|
+
skillIds = selectedSkills.filter((id) => id !== SELECT_ALL_CATEGORY);
|
|
1906
2405
|
}
|
|
1907
|
-
if (selectedSkills.includes(CANCEL3)) return "cancel";
|
|
1908
|
-
const skillIds = selectedSkills.filter((v) => v !== BACK3 && v !== CANCEL3);
|
|
1909
2406
|
if (skillIds.length === 0) {
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
2407
|
+
accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
|
|
2408
|
+
const action2 = await select3({
|
|
2409
|
+
message: accumulated.length > 0 ? t("skill_continue_or_install").replace("{count}", String(accumulated.length)) : t("wizard_no_selection"),
|
|
2410
|
+
choices: accumulated.length > 0 ? [
|
|
2411
|
+
{ name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
|
|
2412
|
+
{ name: "<- " + t("wizard_back_to_reselect"), value: "back" },
|
|
2413
|
+
{ name: "x " + t("wizard_cancel"), value: "cancel" }
|
|
2414
|
+
] : [
|
|
1913
2415
|
new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
1914
2416
|
{ name: "<- " + t("wizard_back_to_reselect"), value: "back" },
|
|
1915
2417
|
{ name: "x " + t("wizard_cancel"), value: "cancel" }
|
|
@@ -1917,13 +2419,38 @@ async function interactiveSkillSelect() {
|
|
|
1917
2419
|
loop: false,
|
|
1918
2420
|
theme: selectTheme
|
|
1919
2421
|
});
|
|
1920
|
-
if (
|
|
2422
|
+
if (action2 === "install") return accumulated;
|
|
2423
|
+
if (action2 === "back") {
|
|
1921
2424
|
step = 0;
|
|
1922
2425
|
break;
|
|
1923
2426
|
}
|
|
1924
2427
|
return "cancel";
|
|
1925
2428
|
}
|
|
1926
|
-
|
|
2429
|
+
accumulated = accumulated.filter((id) => !categorySkillIds.has(id));
|
|
2430
|
+
for (const id of skillIds) {
|
|
2431
|
+
if (!accumulated.includes(id)) {
|
|
2432
|
+
accumulated.push(id);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
const action = await select3({
|
|
2436
|
+
message: t("skill_continue_or_install").replace("{count}", String(accumulated.length)),
|
|
2437
|
+
choices: [
|
|
2438
|
+
{ name: ">> " + t("skill_confirm_install_count").replace("{count}", String(accumulated.length)), value: "install" },
|
|
2439
|
+
{ name: "> " + t("skill_continue_select"), value: CONTINUE_SELECT },
|
|
2440
|
+
new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
2441
|
+
{ name: "x " + t("wizard_cancel"), value: "cancel" }
|
|
2442
|
+
],
|
|
2443
|
+
loop: false,
|
|
2444
|
+
theme: selectTheme
|
|
2445
|
+
});
|
|
2446
|
+
if (action === "install") {
|
|
2447
|
+
return accumulated;
|
|
2448
|
+
}
|
|
2449
|
+
if (action === CONTINUE_SELECT) {
|
|
2450
|
+
step = 0;
|
|
2451
|
+
break;
|
|
2452
|
+
}
|
|
2453
|
+
return "cancel";
|
|
1927
2454
|
}
|
|
1928
2455
|
}
|
|
1929
2456
|
}
|
|
@@ -1931,11 +2458,11 @@ async function interactiveSkillSelect() {
|
|
|
1931
2458
|
function listAllSkills() {
|
|
1932
2459
|
const categories = getAllCategories();
|
|
1933
2460
|
console.log();
|
|
1934
|
-
console.log(
|
|
1935
|
-
console.log(
|
|
2461
|
+
console.log(chalk5.bold(` ${t("skill_list_title")}`));
|
|
2462
|
+
console.log(chalk5.dim(" " + "\u2500".repeat(60)));
|
|
1936
2463
|
for (const category of categories) {
|
|
1937
2464
|
console.log();
|
|
1938
|
-
console.log(
|
|
2465
|
+
console.log(chalk5.bold(` ${category.name}`) + chalk5.dim(` (${category.skills.length} skills)`));
|
|
1939
2466
|
const table = new Table({
|
|
1940
2467
|
style: { head: [], border: [] },
|
|
1941
2468
|
colWidths: [35, 45],
|
|
@@ -1943,51 +2470,54 @@ function listAllSkills() {
|
|
|
1943
2470
|
});
|
|
1944
2471
|
for (const skill of category.skills) {
|
|
1945
2472
|
table.push([
|
|
1946
|
-
|
|
2473
|
+
chalk5.cyan(skill.id),
|
|
1947
2474
|
skill.description
|
|
1948
2475
|
]);
|
|
1949
2476
|
}
|
|
1950
2477
|
console.log(table.toString());
|
|
1951
2478
|
}
|
|
1952
2479
|
console.log();
|
|
1953
|
-
console.log(
|
|
2480
|
+
console.log(chalk5.dim(` ${t("skill_total_count").replace("{count}", String(getAllSkills().length))}`));
|
|
1954
2481
|
console.log();
|
|
1955
2482
|
}
|
|
1956
|
-
function
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
2483
|
+
function resolveSkillSourcePath(skill, repoPath) {
|
|
2484
|
+
if (skill.filePath) {
|
|
2485
|
+
const absPath = skill.filePath.startsWith("/") ? skill.filePath : join13(repoPath, skill.filePath);
|
|
2486
|
+
if (existsSync13(absPath)) {
|
|
2487
|
+
return dirname7(absPath);
|
|
2488
|
+
}
|
|
1961
2489
|
}
|
|
2490
|
+
return join13(repoPath, skill.source, skill.id);
|
|
1962
2491
|
}
|
|
1963
2492
|
|
|
1964
2493
|
// src/ui/display.ts
|
|
1965
|
-
import
|
|
2494
|
+
import chalk6 from "chalk";
|
|
1966
2495
|
import Table2 from "cli-table3";
|
|
1967
2496
|
function printPluginList(plugins, installed) {
|
|
1968
2497
|
const table = new Table2({
|
|
1969
2498
|
head: [
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
chalk4.cyan(t("list_agents"))
|
|
2499
|
+
chalk6.cyan(t("list_id")),
|
|
2500
|
+
chalk6.cyan(t("list_name")),
|
|
2501
|
+
chalk6.cyan(t("list_status")),
|
|
2502
|
+
chalk6.cyan(t("list_description"))
|
|
1975
2503
|
],
|
|
1976
|
-
style: { head: [], border: [] }
|
|
2504
|
+
style: { head: [], border: [] },
|
|
2505
|
+
colWidths: [6, 32, 8, 50],
|
|
2506
|
+
wordWrap: true
|
|
1977
2507
|
});
|
|
1978
2508
|
plugins.forEach((plugin, index) => {
|
|
1979
2509
|
const isInstalled = installed.has(plugin.id);
|
|
1980
|
-
const status = isInstalled ?
|
|
2510
|
+
const status = isInstalled ? chalk6.green(`\u2713 ${t("status_installed")}`) : chalk6.dim(`\u2014`);
|
|
1981
2511
|
table.push([
|
|
1982
2512
|
String(index + 1),
|
|
1983
2513
|
plugin.displayName,
|
|
1984
2514
|
status,
|
|
1985
|
-
|
|
1986
|
-
String(plugin.agents)
|
|
2515
|
+
plugin.description || chalk6.dim("\u2014")
|
|
1987
2516
|
]);
|
|
1988
2517
|
});
|
|
1989
2518
|
console.log();
|
|
1990
|
-
|
|
2519
|
+
const countSuffix = t("count_suffix");
|
|
2520
|
+
console.log(chalk6.bold(` ${t("list_title")} (${plugins.length}${countSuffix ? " " + countSuffix : ""})`));
|
|
1991
2521
|
console.log(table.toString());
|
|
1992
2522
|
console.log();
|
|
1993
2523
|
}
|
|
@@ -1996,7 +2526,7 @@ function printInstallSummary(results) {
|
|
|
1996
2526
|
const totalCount = results.length;
|
|
1997
2527
|
console.log();
|
|
1998
2528
|
console.log(
|
|
1999
|
-
|
|
2529
|
+
chalk6.bold(
|
|
2000
2530
|
` ${t("install_done")}! ${successCount}/${totalCount} ${t("install_success")}`
|
|
2001
2531
|
)
|
|
2002
2532
|
);
|
|
@@ -2004,10 +2534,10 @@ function printInstallSummary(results) {
|
|
|
2004
2534
|
for (const result of results) {
|
|
2005
2535
|
if (result.success) {
|
|
2006
2536
|
console.log(
|
|
2007
|
-
|
|
2537
|
+
chalk6.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
|
|
2008
2538
|
);
|
|
2009
2539
|
} else {
|
|
2010
|
-
console.log(
|
|
2540
|
+
console.log(chalk6.red(" \u2717") + ` ${result.displayName}`);
|
|
2011
2541
|
}
|
|
2012
2542
|
}
|
|
2013
2543
|
console.log();
|
|
@@ -2020,22 +2550,25 @@ function printEnhancedSummary(results, tool, configRoot) {
|
|
|
2020
2550
|
return;
|
|
2021
2551
|
}
|
|
2022
2552
|
const toolName = getToolDisplayName(tool);
|
|
2553
|
+
const countSuffix = t("count_suffix");
|
|
2023
2554
|
const lines = [
|
|
2024
|
-
|
|
2555
|
+
chalk6.bold(` ${t("install_done")}!`),
|
|
2025
2556
|
"",
|
|
2026
|
-
` ${
|
|
2027
|
-
` ${
|
|
2557
|
+
` ${chalk6.dim(t("install_installed_to") + ":")} ${configRoot}`,
|
|
2558
|
+
` ${chalk6.dim(t("install_skills_count") + ":")} ${totalSkills}${countSuffix ? " " + countSuffix : ""} ${chalk6.dim("|")} ${chalk6.dim(t("install_agents_count") + ":")} ${totalAgents}${countSuffix ? " " + countSuffix : ""}`,
|
|
2028
2559
|
"",
|
|
2029
|
-
|
|
2030
|
-
` ${
|
|
2031
|
-
` ${
|
|
2032
|
-
` ${
|
|
2033
|
-
` ${
|
|
2560
|
+
chalk6.bold(` ${t("install_enhanced_next_steps")}:`),
|
|
2561
|
+
` ${chalk6.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk6.green(toolName.toLowerCase())}`,
|
|
2562
|
+
` ${chalk6.cyan("2.")} ${t("install_enhanced_try")}: ${chalk6.green(t("try_prompt"))}`,
|
|
2563
|
+
` ${chalk6.cyan("3.")} ${t("install_enhanced_more")}: ${chalk6.green("install-helper list")}`,
|
|
2564
|
+
` ${chalk6.cyan("4.")} ${t("install_enhanced_check")}: ${chalk6.green("install-helper doctor")}`,
|
|
2034
2565
|
""
|
|
2035
2566
|
];
|
|
2036
2567
|
if (successResults.length === 1) {
|
|
2568
|
+
const plugin = findPlugin(successResults[0].pluginId);
|
|
2569
|
+
const docsPath = plugin ? `${plugin.dir}/quickstart.md` : `${successResults[0].pluginId}/quickstart.md`;
|
|
2037
2570
|
lines.push(
|
|
2038
|
-
` ${
|
|
2571
|
+
` ${chalk6.dim(t("install_enhanced_docs"))}: ${chalk6.dim(docsPath)}`
|
|
2039
2572
|
);
|
|
2040
2573
|
}
|
|
2041
2574
|
console.log();
|
|
@@ -2046,34 +2579,32 @@ function printEnhancedSummary(results, tool, configRoot) {
|
|
|
2046
2579
|
}
|
|
2047
2580
|
|
|
2048
2581
|
// src/commands/init.ts
|
|
2049
|
-
import { existsSync as
|
|
2050
|
-
import { join as
|
|
2051
|
-
var BACK2 = "__back__";
|
|
2052
|
-
var CANCEL2 = "__cancel__";
|
|
2582
|
+
import { existsSync as existsSync14, readFileSync as readFileSync9 } from "fs";
|
|
2583
|
+
import { join as join14 } from "path";
|
|
2053
2584
|
async function initCommand() {
|
|
2054
2585
|
const cwd = process.cwd();
|
|
2055
|
-
if (
|
|
2586
|
+
if (existsSync14(join14(cwd, "package.json")) && (() => {
|
|
2056
2587
|
try {
|
|
2057
|
-
const pkg = JSON.parse(
|
|
2588
|
+
const pkg = JSON.parse(readFileSync9(join14(cwd, "package.json"), "utf-8"));
|
|
2058
2589
|
return pkg.name === "@cannbot-ai/install-helper";
|
|
2059
2590
|
} catch {
|
|
2060
2591
|
return false;
|
|
2061
2592
|
}
|
|
2062
2593
|
})()) {
|
|
2063
|
-
logger.warn("
|
|
2064
|
-
logger.info("
|
|
2065
|
-
logger.info("
|
|
2594
|
+
logger.warn(t("init_pkg_dir_warn"));
|
|
2595
|
+
logger.info(t("init_pkg_dir_hint"));
|
|
2596
|
+
logger.info(t("init_pkg_dir_example"));
|
|
2066
2597
|
return;
|
|
2067
2598
|
}
|
|
2068
2599
|
printBanner(t("wizard_title"));
|
|
2069
|
-
const spinner = createSpinner("
|
|
2600
|
+
const spinner = createSpinner(t("loading_skills_list"));
|
|
2070
2601
|
spinner.start();
|
|
2071
2602
|
try {
|
|
2072
2603
|
const repoManager = createRepositoryManager();
|
|
2073
2604
|
await repoManager.ensureRepoAndScan();
|
|
2074
|
-
spinner.succeed("
|
|
2605
|
+
spinner.succeed(t("loading_skills_list_complete"));
|
|
2075
2606
|
} catch {
|
|
2076
|
-
spinner.warn("
|
|
2607
|
+
spinner.warn(t("loading_skills_list_failed"));
|
|
2077
2608
|
}
|
|
2078
2609
|
let step = 0;
|
|
2079
2610
|
let mode = "plugin";
|
|
@@ -2107,8 +2638,7 @@ async function initCommand() {
|
|
|
2107
2638
|
}
|
|
2108
2639
|
}
|
|
2109
2640
|
async function selectMode() {
|
|
2110
|
-
printBoxTitle("
|
|
2111
|
-
const CANCEL3 = "__cancel__";
|
|
2641
|
+
printBoxTitle(t("wizard_select_mode_title"));
|
|
2112
2642
|
const choices = [
|
|
2113
2643
|
{
|
|
2114
2644
|
name: `> ${t("wizard_mode_plugin")} \u2014 ${t("wizard_mode_plugin_desc")}`,
|
|
@@ -2119,30 +2649,30 @@ async function selectMode() {
|
|
|
2119
2649
|
value: "skill"
|
|
2120
2650
|
},
|
|
2121
2651
|
new Separator3("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
2122
|
-
{ name: "x " + t("wizard_cancel"), value:
|
|
2652
|
+
{ name: "x " + t("wizard_cancel"), value: CANCEL }
|
|
2123
2653
|
];
|
|
2124
2654
|
showOperationHints();
|
|
2125
|
-
const result = await
|
|
2655
|
+
const result = await select4({
|
|
2126
2656
|
message: t("wizard_select_mode"),
|
|
2127
2657
|
choices,
|
|
2128
2658
|
loop: false,
|
|
2129
2659
|
theme: selectTheme
|
|
2130
2660
|
});
|
|
2131
|
-
if (result ===
|
|
2661
|
+
if (result === CANCEL) return null;
|
|
2132
2662
|
return result;
|
|
2133
2663
|
}
|
|
2134
2664
|
async function pluginInstallFlow() {
|
|
2135
2665
|
const answers = await runWizard();
|
|
2136
2666
|
if (!answers.confirmed) {
|
|
2137
2667
|
if (answers.back) return "back";
|
|
2138
|
-
logger.info("
|
|
2668
|
+
logger.info(t("init_cancelled"));
|
|
2139
2669
|
return "done";
|
|
2140
2670
|
}
|
|
2141
2671
|
const repoManager = createRepositoryManager();
|
|
2142
|
-
const spinner = createSpinner("
|
|
2672
|
+
const spinner = createSpinner(t("loading_plugin_data"));
|
|
2143
2673
|
spinner.start();
|
|
2144
2674
|
const repoPath = await repoManager.ensureRepo();
|
|
2145
|
-
spinner.succeed("
|
|
2675
|
+
spinner.succeed(t("loading_plugin_data_complete"));
|
|
2146
2676
|
const allPlugins = getAllPlugins();
|
|
2147
2677
|
const selectedPlugins = answers.plugins.map(
|
|
2148
2678
|
(id) => allPlugins.find((p) => p.id === id)
|
|
@@ -2196,7 +2726,7 @@ async function skillInstallFlow() {
|
|
|
2196
2726
|
const result = await selectToolWithDetection();
|
|
2197
2727
|
if (result === "back") return "back";
|
|
2198
2728
|
if (result === "cancel") {
|
|
2199
|
-
logger.info("
|
|
2729
|
+
logger.info(t("init_cancelled"));
|
|
2200
2730
|
return "done";
|
|
2201
2731
|
}
|
|
2202
2732
|
tool = result;
|
|
@@ -2205,12 +2735,12 @@ async function skillInstallFlow() {
|
|
|
2205
2735
|
}
|
|
2206
2736
|
case 1: {
|
|
2207
2737
|
const result = await stepLevel();
|
|
2208
|
-
if (result ===
|
|
2738
|
+
if (result === BACK) {
|
|
2209
2739
|
step = 0;
|
|
2210
2740
|
break;
|
|
2211
2741
|
}
|
|
2212
|
-
if (result ===
|
|
2213
|
-
logger.info("
|
|
2742
|
+
if (result === CANCEL) {
|
|
2743
|
+
logger.info(t("init_cancelled"));
|
|
2214
2744
|
return "done";
|
|
2215
2745
|
}
|
|
2216
2746
|
level = result;
|
|
@@ -2218,25 +2748,25 @@ async function skillInstallFlow() {
|
|
|
2218
2748
|
break;
|
|
2219
2749
|
}
|
|
2220
2750
|
case 2: {
|
|
2221
|
-
const selectedSkills = await interactiveSkillSelect();
|
|
2751
|
+
const selectedSkills = await interactiveSkillSelect(tool, level);
|
|
2222
2752
|
if (selectedSkills === "back") {
|
|
2223
2753
|
step = 1;
|
|
2224
2754
|
break;
|
|
2225
2755
|
}
|
|
2226
2756
|
if (selectedSkills === "cancel") {
|
|
2227
|
-
logger.info("
|
|
2757
|
+
logger.info(t("init_cancelled"));
|
|
2228
2758
|
return "done";
|
|
2229
2759
|
}
|
|
2230
2760
|
if (selectedSkills.length === 0) {
|
|
2231
|
-
logger.info("
|
|
2761
|
+
logger.info(t("install_no_skill_selected"));
|
|
2232
2762
|
return "done";
|
|
2233
2763
|
}
|
|
2234
2764
|
const repoManager = createRepositoryManager();
|
|
2235
|
-
const spinner = createSpinner("
|
|
2765
|
+
const spinner = createSpinner(t("loading_plugin_data"));
|
|
2236
2766
|
spinner.start();
|
|
2237
2767
|
const repoPath = await repoManager.ensureRepo();
|
|
2238
|
-
spinner.succeed("
|
|
2239
|
-
logger.info(`${t("skill_install_progress")} ${
|
|
2768
|
+
spinner.succeed(t("loading_plugin_data_complete"));
|
|
2769
|
+
logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk7.bold(String(selectedSkills.length)))}...`);
|
|
2240
2770
|
const results = await installSkills(selectedSkills, tool, level, repoPath);
|
|
2241
2771
|
let successCount = 0;
|
|
2242
2772
|
let failCount = 0;
|
|
@@ -2254,10 +2784,10 @@ async function skillInstallFlow() {
|
|
|
2254
2784
|
}
|
|
2255
2785
|
const configRoot = getConfigRoot(tool, level);
|
|
2256
2786
|
logger.blank();
|
|
2257
|
-
logger.success(`${t("skill_install_done")}: ${
|
|
2787
|
+
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)))}`);
|
|
2258
2788
|
logger.blank();
|
|
2259
|
-
logger.info(
|
|
2260
|
-
logger.info(
|
|
2789
|
+
logger.info(`${t("install_to")}: ${chalk7.cyan(configRoot + "/skills/")}`);
|
|
2790
|
+
logger.info(`${t("start_to_use").replace("{tool}", chalk7.green(tool))}`);
|
|
2261
2791
|
logger.blank();
|
|
2262
2792
|
return "done";
|
|
2263
2793
|
}
|
|
@@ -2279,32 +2809,31 @@ async function listCommand() {
|
|
|
2279
2809
|
}
|
|
2280
2810
|
|
|
2281
2811
|
// src/commands/doctor.ts
|
|
2282
|
-
import
|
|
2283
|
-
import { existsSync as
|
|
2284
|
-
import { join as
|
|
2812
|
+
import chalk8 from "chalk";
|
|
2813
|
+
import { existsSync as existsSync15, readdirSync as readdirSync5, lstatSync as lstatSync2, unlinkSync as unlinkSync6, mkdirSync as mkdirSync7 } from "fs";
|
|
2814
|
+
import { join as join15 } from "path";
|
|
2285
2815
|
async function doctorCommand(options = {}) {
|
|
2286
2816
|
console.log();
|
|
2287
|
-
console.log(
|
|
2288
|
-
console.log(
|
|
2289
|
-
let errors = 0;
|
|
2817
|
+
console.log(chalk8.bold(` ${t("doctor_title")}`));
|
|
2818
|
+
console.log(chalk8.dim(" " + "\u2500".repeat(46)));
|
|
2290
2819
|
let warnings = 0;
|
|
2291
2820
|
let fixes = 0;
|
|
2292
2821
|
console.log();
|
|
2293
|
-
console.log(
|
|
2822
|
+
console.log(chalk8.bold(` ${t("doctor_tools")}`));
|
|
2294
2823
|
const detectedTools = await detectTools();
|
|
2295
2824
|
const allTools = getAllTools();
|
|
2296
2825
|
for (const tool of allTools) {
|
|
2297
2826
|
const detected = detectedTools.find((d) => d.name === tool);
|
|
2298
2827
|
if (detected) {
|
|
2299
2828
|
console.log(
|
|
2300
|
-
|
|
2829
|
+
chalk8.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
|
|
2301
2830
|
);
|
|
2302
2831
|
} else {
|
|
2303
|
-
console.log(
|
|
2832
|
+
console.log(chalk8.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 ${t("doctor_not_installed")}`);
|
|
2304
2833
|
}
|
|
2305
2834
|
}
|
|
2306
2835
|
console.log();
|
|
2307
|
-
console.log(
|
|
2836
|
+
console.log(chalk8.bold(` ${t("doctor_plugins")}`));
|
|
2308
2837
|
const plugins = getAllPlugins();
|
|
2309
2838
|
const installed = scanInstalled();
|
|
2310
2839
|
const installedMap = new Map(installed.map((p) => [p.id, p]));
|
|
@@ -2312,80 +2841,80 @@ async function doctorCommand(options = {}) {
|
|
|
2312
2841
|
const inst = installedMap.get(plugin.id);
|
|
2313
2842
|
if (inst) {
|
|
2314
2843
|
console.log(
|
|
2315
|
-
|
|
2844
|
+
chalk8.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
|
|
2316
2845
|
);
|
|
2317
2846
|
} else {
|
|
2318
|
-
console.log(
|
|
2847
|
+
console.log(chalk8.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 ${t("doctor_not_installed")}`);
|
|
2319
2848
|
}
|
|
2320
2849
|
}
|
|
2321
2850
|
console.log();
|
|
2322
|
-
console.log(
|
|
2851
|
+
console.log(chalk8.bold(` ${t("doctor_links")}`));
|
|
2323
2852
|
const primaryTool = detectedTools[0]?.name || "opencode";
|
|
2324
2853
|
const configRoot = getConfigRoot(primaryTool, "project");
|
|
2325
2854
|
const skillsDir = getSkillsDir(configRoot);
|
|
2326
2855
|
const agentsDir = getAgentsDir(configRoot);
|
|
2327
|
-
if (
|
|
2856
|
+
if (existsSync15(skillsDir)) {
|
|
2328
2857
|
const brokenLinks = checkBrokenLinks(skillsDir);
|
|
2329
2858
|
if (brokenLinks === 0) {
|
|
2330
|
-
const count =
|
|
2331
|
-
console.log(
|
|
2859
|
+
const count = readdirSync5(skillsDir).length;
|
|
2860
|
+
console.log(chalk8.green(" \u2713") + ` ${skillsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
|
|
2332
2861
|
} else {
|
|
2333
|
-
console.log(
|
|
2862
|
+
console.log(chalk8.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
|
|
2334
2863
|
warnings++;
|
|
2335
2864
|
if (options.fix) {
|
|
2336
2865
|
const fixed = fixBrokenLinks(skillsDir);
|
|
2337
2866
|
fixes += fixed;
|
|
2338
|
-
console.log(
|
|
2867
|
+
console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
|
|
2339
2868
|
}
|
|
2340
2869
|
}
|
|
2341
2870
|
} else {
|
|
2342
|
-
console.log(
|
|
2871
|
+
console.log(chalk8.dim(" \u2014") + ` ${skillsDir} \u2014 ${t("doctor_not_exist")}`);
|
|
2343
2872
|
if (options.fix) {
|
|
2344
|
-
|
|
2873
|
+
mkdirSync7(skillsDir, { recursive: true });
|
|
2345
2874
|
fixes++;
|
|
2346
|
-
console.log(
|
|
2875
|
+
console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", skillsDir)}`);
|
|
2347
2876
|
}
|
|
2348
2877
|
}
|
|
2349
|
-
if (
|
|
2878
|
+
if (existsSync15(agentsDir)) {
|
|
2350
2879
|
const brokenLinks = checkBrokenLinks(agentsDir);
|
|
2351
2880
|
if (brokenLinks === 0) {
|
|
2352
|
-
const count =
|
|
2353
|
-
console.log(
|
|
2881
|
+
const count = readdirSync5(agentsDir).length;
|
|
2882
|
+
console.log(chalk8.green(" \u2713") + ` ${agentsDir} \u2014 ${t("doctor_links_valid").replace("{count}", String(count))}`);
|
|
2354
2883
|
} else {
|
|
2355
|
-
console.log(
|
|
2884
|
+
console.log(chalk8.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${t("doctor_broken_links").replace("{count}", String(brokenLinks))}`);
|
|
2356
2885
|
warnings++;
|
|
2357
2886
|
if (options.fix) {
|
|
2358
2887
|
const fixed = fixBrokenLinks(agentsDir);
|
|
2359
2888
|
fixes += fixed;
|
|
2360
|
-
console.log(
|
|
2889
|
+
console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: ${t("doctor_fixed").replace("{count}", String(fixed))}`);
|
|
2361
2890
|
}
|
|
2362
2891
|
}
|
|
2363
2892
|
} else {
|
|
2364
|
-
console.log(
|
|
2893
|
+
console.log(chalk8.dim(" \u2014") + ` ${agentsDir} \u2014 ${t("doctor_not_exist")}`);
|
|
2365
2894
|
if (options.fix) {
|
|
2366
|
-
|
|
2895
|
+
mkdirSync7(agentsDir, { recursive: true });
|
|
2367
2896
|
fixes++;
|
|
2368
|
-
console.log(
|
|
2897
|
+
console.log(chalk8.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: ${t("doctor_created").replace("{path}", agentsDir)}`);
|
|
2369
2898
|
}
|
|
2370
2899
|
}
|
|
2371
2900
|
console.log();
|
|
2372
|
-
console.log(
|
|
2901
|
+
console.log(chalk8.bold(` ${t("doctor_config")}`));
|
|
2373
2902
|
const configFile = getConfigFileName(primaryTool);
|
|
2374
|
-
const configPath =
|
|
2375
|
-
if (
|
|
2376
|
-
console.log(
|
|
2903
|
+
const configPath = join15(process.cwd(), configFile);
|
|
2904
|
+
if (existsSync15(configPath)) {
|
|
2905
|
+
console.log(chalk8.green(" \u2713") + ` ${configFile} ${t("doctor_config_exists")}`);
|
|
2377
2906
|
} else {
|
|
2378
|
-
console.log(
|
|
2907
|
+
console.log(chalk8.dim(" \u2014") + ` ${configFile} ${t("doctor_config_not_exist")}`);
|
|
2379
2908
|
}
|
|
2380
2909
|
console.log();
|
|
2381
|
-
console.log(
|
|
2910
|
+
console.log(chalk8.dim(" " + "\u2500".repeat(46)));
|
|
2382
2911
|
if (options.fix && fixes > 0) {
|
|
2383
2912
|
console.log(
|
|
2384
|
-
` ${t("doctor_result")}: ${warnings}
|
|
2913
|
+
` ${t("doctor_result")}: ${t("doctor_result_with_fix").replace("{warnings}", String(warnings)).replace("{fixes}", String(fixes))}`
|
|
2385
2914
|
);
|
|
2386
2915
|
} else {
|
|
2387
2916
|
console.log(
|
|
2388
|
-
` ${t("doctor_result")}: ${warnings}
|
|
2917
|
+
` ${t("doctor_result")}: ${t("doctor_result_without_fix").replace("{warnings}", String(warnings))}`
|
|
2389
2918
|
);
|
|
2390
2919
|
}
|
|
2391
2920
|
console.log();
|
|
@@ -2393,13 +2922,13 @@ async function doctorCommand(options = {}) {
|
|
|
2393
2922
|
function checkBrokenLinks(dir) {
|
|
2394
2923
|
let broken = 0;
|
|
2395
2924
|
try {
|
|
2396
|
-
const entries =
|
|
2925
|
+
const entries = readdirSync5(dir);
|
|
2397
2926
|
for (const entry of entries) {
|
|
2398
|
-
const fullPath =
|
|
2927
|
+
const fullPath = join15(dir, entry);
|
|
2399
2928
|
try {
|
|
2400
|
-
const stats =
|
|
2929
|
+
const stats = lstatSync2(fullPath);
|
|
2401
2930
|
if (stats.isSymbolicLink()) {
|
|
2402
|
-
if (!
|
|
2931
|
+
if (!existsSync15(fullPath)) {
|
|
2403
2932
|
broken++;
|
|
2404
2933
|
}
|
|
2405
2934
|
}
|
|
@@ -2414,14 +2943,14 @@ function checkBrokenLinks(dir) {
|
|
|
2414
2943
|
function fixBrokenLinks(dir) {
|
|
2415
2944
|
let fixed = 0;
|
|
2416
2945
|
try {
|
|
2417
|
-
const entries =
|
|
2946
|
+
const entries = readdirSync5(dir);
|
|
2418
2947
|
for (const entry of entries) {
|
|
2419
|
-
const fullPath =
|
|
2948
|
+
const fullPath = join15(dir, entry);
|
|
2420
2949
|
try {
|
|
2421
|
-
const stats =
|
|
2950
|
+
const stats = lstatSync2(fullPath);
|
|
2422
2951
|
if (stats.isSymbolicLink()) {
|
|
2423
|
-
if (!
|
|
2424
|
-
|
|
2952
|
+
if (!existsSync15(fullPath)) {
|
|
2953
|
+
unlinkSync6(fullPath);
|
|
2425
2954
|
fixed++;
|
|
2426
2955
|
}
|
|
2427
2956
|
}
|
|
@@ -2434,36 +2963,35 @@ function fixBrokenLinks(dir) {
|
|
|
2434
2963
|
}
|
|
2435
2964
|
|
|
2436
2965
|
// src/commands/status.ts
|
|
2437
|
-
import
|
|
2966
|
+
import chalk9 from "chalk";
|
|
2438
2967
|
async function statusCommand() {
|
|
2439
|
-
const plugins = getAllPlugins();
|
|
2440
2968
|
const installed = scanInstalled();
|
|
2441
2969
|
const installedMap = new Map(installed.map((p) => [p.id, p]));
|
|
2442
2970
|
console.log();
|
|
2443
|
-
console.log(
|
|
2971
|
+
console.log(chalk9.bold(` ${t("status_title")}`));
|
|
2444
2972
|
console.log();
|
|
2445
2973
|
if (installed.length === 0) {
|
|
2446
|
-
console.log(
|
|
2447
|
-
console.log(
|
|
2974
|
+
console.log(chalk9.dim(` ${t("status_none")}`));
|
|
2975
|
+
console.log(chalk9.dim(` ${t("status_hint")}`));
|
|
2448
2976
|
console.log();
|
|
2449
2977
|
return;
|
|
2450
2978
|
}
|
|
2451
2979
|
for (const inst of installed) {
|
|
2452
2980
|
console.log(
|
|
2453
|
-
|
|
2981
|
+
chalk9.green(" \u2713") + ` ${inst.displayName}` + chalk9.dim(` (${inst.tool}, ${inst.level})`)
|
|
2454
2982
|
);
|
|
2455
2983
|
console.log(
|
|
2456
|
-
|
|
2984
|
+
chalk9.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
|
|
2457
2985
|
);
|
|
2458
|
-
console.log(
|
|
2459
|
-
console.log(
|
|
2986
|
+
console.log(chalk9.dim(` ${inst.configRoot}`));
|
|
2987
|
+
console.log(chalk9.dim(` ${t("status_install_time")}: ${inst.installTime}`));
|
|
2460
2988
|
console.log();
|
|
2461
2989
|
}
|
|
2462
2990
|
}
|
|
2463
2991
|
|
|
2464
2992
|
// src/commands/install.ts
|
|
2465
2993
|
import { confirm } from "@inquirer/prompts";
|
|
2466
|
-
import
|
|
2994
|
+
import chalk10 from "chalk";
|
|
2467
2995
|
async function installCommand(names, options) {
|
|
2468
2996
|
if (options.list) {
|
|
2469
2997
|
try {
|
|
@@ -2474,79 +3002,150 @@ async function installCommand(names, options) {
|
|
|
2474
3002
|
listAllSkills();
|
|
2475
3003
|
return;
|
|
2476
3004
|
}
|
|
3005
|
+
if (options.all) {
|
|
3006
|
+
const scanSpinner2 = createSpinner(t("loading_skills_list"));
|
|
3007
|
+
scanSpinner2.start();
|
|
3008
|
+
try {
|
|
3009
|
+
const scanRepoManager = createRepositoryManager();
|
|
3010
|
+
await scanRepoManager.ensureRepoAndScan();
|
|
3011
|
+
scanSpinner2.succeed(t("loading_skills_list_complete"));
|
|
3012
|
+
} catch {
|
|
3013
|
+
scanSpinner2.warn(t("loading_skills_list_failed"));
|
|
3014
|
+
}
|
|
3015
|
+
const allSkills = getAllSkills();
|
|
3016
|
+
const skillIds = allSkills.map((s) => s.id);
|
|
3017
|
+
if (skillIds.length === 0) {
|
|
3018
|
+
logger.error(t("install_no_skill_selected"));
|
|
3019
|
+
return;
|
|
3020
|
+
}
|
|
3021
|
+
let tool2;
|
|
3022
|
+
if (options.tool) {
|
|
3023
|
+
tool2 = validateTool(options.tool);
|
|
3024
|
+
} else {
|
|
3025
|
+
const detected = await selectToolWithDetection();
|
|
3026
|
+
if (detected === "back" || detected === "cancel") {
|
|
3027
|
+
logger.info(t("init_cancelled"));
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
3030
|
+
tool2 = detected;
|
|
3031
|
+
}
|
|
3032
|
+
const level2 = options.level ? validateLevel(options.level) : "project";
|
|
3033
|
+
const repoManager = createRepositoryManager();
|
|
3034
|
+
const spinner = createSpinner(t("loading_plugin_data"));
|
|
3035
|
+
spinner.start();
|
|
3036
|
+
const repoPath = await repoManager.ensureRepo();
|
|
3037
|
+
spinner.succeed(t("loading_plugin_data_complete"));
|
|
3038
|
+
logger.info(`${t("skill_all_install_progress").replace("{count}", chalk10.bold(String(skillIds.length)))}`);
|
|
3039
|
+
const results = await installSkills(skillIds, tool2, level2, repoPath);
|
|
3040
|
+
let successCount = 0;
|
|
3041
|
+
let failCount = 0;
|
|
3042
|
+
const total = results.length;
|
|
3043
|
+
for (let i = 0; i < results.length; i++) {
|
|
3044
|
+
const result = results[i];
|
|
3045
|
+
const progress = `[${i + 1}/${total}]`;
|
|
3046
|
+
if (result.success) {
|
|
3047
|
+
logger.success(`${progress} ${result.skillId}`);
|
|
3048
|
+
successCount++;
|
|
3049
|
+
} else {
|
|
3050
|
+
logger.error(`${progress} ${result.skillId}: ${result.error}`);
|
|
3051
|
+
failCount++;
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
const configRoot = getConfigRoot(tool2, level2);
|
|
3055
|
+
logger.blank();
|
|
3056
|
+
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)))}`);
|
|
3057
|
+
logger.blank();
|
|
3058
|
+
logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
|
|
3059
|
+
logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
|
|
3060
|
+
logger.blank();
|
|
3061
|
+
return;
|
|
3062
|
+
}
|
|
2477
3063
|
if (names.length === 0) {
|
|
2478
|
-
const scanSpinner2 = createSpinner("
|
|
3064
|
+
const scanSpinner2 = createSpinner(t("loading_skills_list"));
|
|
2479
3065
|
scanSpinner2.start();
|
|
2480
3066
|
try {
|
|
2481
3067
|
const scanRepoManager = createRepositoryManager();
|
|
2482
3068
|
await scanRepoManager.ensureRepoAndScan();
|
|
2483
|
-
scanSpinner2.succeed("
|
|
3069
|
+
scanSpinner2.succeed(t("loading_skills_list_complete"));
|
|
2484
3070
|
} catch {
|
|
2485
|
-
scanSpinner2.warn("
|
|
3071
|
+
scanSpinner2.warn(t("loading_skills_list_failed"));
|
|
2486
3072
|
}
|
|
2487
3073
|
let tool2;
|
|
2488
3074
|
if (options.tool) {
|
|
2489
|
-
tool2 = options.tool;
|
|
3075
|
+
tool2 = validateTool(options.tool);
|
|
2490
3076
|
} else {
|
|
2491
3077
|
const detected = await selectToolWithDetection();
|
|
2492
|
-
if (detected === "back")
|
|
3078
|
+
if (detected === "back" || detected === "cancel") {
|
|
3079
|
+
logger.info(t("init_cancelled"));
|
|
3080
|
+
return;
|
|
3081
|
+
}
|
|
2493
3082
|
tool2 = detected;
|
|
2494
3083
|
}
|
|
3084
|
+
const level2 = options.level ? validateLevel(options.level) : "project";
|
|
2495
3085
|
let step = 1;
|
|
2496
3086
|
while (true) {
|
|
2497
3087
|
switch (step) {
|
|
2498
3088
|
case 1: {
|
|
2499
|
-
const selectedSkills = await interactiveSkillSelect();
|
|
3089
|
+
const selectedSkills = await interactiveSkillSelect(tool2, level2);
|
|
2500
3090
|
if (selectedSkills === "back") {
|
|
2501
3091
|
const redetected = await selectToolWithDetection();
|
|
2502
|
-
if (redetected === "back")
|
|
3092
|
+
if (redetected === "back" || redetected === "cancel") {
|
|
3093
|
+
logger.info(t("init_cancelled"));
|
|
3094
|
+
return;
|
|
3095
|
+
}
|
|
2503
3096
|
tool2 = redetected;
|
|
2504
3097
|
break;
|
|
2505
3098
|
}
|
|
3099
|
+
if (selectedSkills === "cancel") {
|
|
3100
|
+
logger.info(t("init_cancelled"));
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
2506
3103
|
if (selectedSkills.length === 0) {
|
|
2507
|
-
logger.info("
|
|
3104
|
+
logger.info(t("install_no_skill_selected"));
|
|
2508
3105
|
return;
|
|
2509
3106
|
}
|
|
2510
3107
|
names = selectedSkills;
|
|
2511
3108
|
const repoManager = createRepositoryManager();
|
|
2512
|
-
const spinner = createSpinner(t("
|
|
3109
|
+
const spinner = createSpinner(t("loading_plugin_data"));
|
|
2513
3110
|
spinner.start();
|
|
2514
3111
|
const repoPath = await repoManager.ensureRepo();
|
|
2515
|
-
spinner.succeed(t("
|
|
2516
|
-
logger.info(`${t("skill_install_progress")} ${
|
|
2517
|
-
const level2 = options.level || "project";
|
|
3112
|
+
spinner.succeed(t("loading_plugin_data_complete"));
|
|
3113
|
+
logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(names.length)))}...`);
|
|
2518
3114
|
const results = await installSkills(names, tool2, level2, repoPath);
|
|
2519
3115
|
let successCount = 0;
|
|
2520
3116
|
let failCount = 0;
|
|
2521
|
-
|
|
3117
|
+
const total = results.length;
|
|
3118
|
+
for (let i = 0; i < results.length; i++) {
|
|
3119
|
+
const result = results[i];
|
|
3120
|
+
const progress = `[${i + 1}/${total}]`;
|
|
2522
3121
|
if (result.success) {
|
|
2523
|
-
logger.success(`${result.skillId}`);
|
|
3122
|
+
logger.success(`${progress} ${result.skillId}`);
|
|
2524
3123
|
successCount++;
|
|
2525
3124
|
} else {
|
|
2526
|
-
logger.error(`${result.skillId}: ${result.error}`);
|
|
3125
|
+
logger.error(`${progress} ${result.skillId}: ${result.error}`);
|
|
2527
3126
|
failCount++;
|
|
2528
3127
|
}
|
|
2529
3128
|
}
|
|
2530
3129
|
const configRoot = getConfigRoot(tool2, level2);
|
|
2531
3130
|
logger.blank();
|
|
2532
|
-
logger.success(`${t("skill_install_done")}: ${
|
|
3131
|
+
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)))}`);
|
|
2533
3132
|
logger.blank();
|
|
2534
|
-
logger.info(
|
|
2535
|
-
logger.info(
|
|
3133
|
+
logger.info(`${t("install_to")}: ${chalk10.cyan(configRoot + "/skills/")}`);
|
|
3134
|
+
logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool2))}`);
|
|
2536
3135
|
logger.blank();
|
|
2537
3136
|
return;
|
|
2538
3137
|
}
|
|
2539
3138
|
}
|
|
2540
3139
|
}
|
|
2541
3140
|
}
|
|
2542
|
-
const scanSpinner = createSpinner("
|
|
3141
|
+
const scanSpinner = createSpinner(t("loading_skills_list"));
|
|
2543
3142
|
scanSpinner.start();
|
|
2544
3143
|
try {
|
|
2545
3144
|
const scanRepoManager = createRepositoryManager();
|
|
2546
3145
|
await scanRepoManager.ensureRepoAndScan();
|
|
2547
|
-
scanSpinner.succeed("
|
|
3146
|
+
scanSpinner.succeed(t("loading_skills_list_complete"));
|
|
2548
3147
|
} catch {
|
|
2549
|
-
scanSpinner.warn("
|
|
3148
|
+
scanSpinner.warn(t("loading_skills_list_failed"));
|
|
2550
3149
|
}
|
|
2551
3150
|
const plugins = [];
|
|
2552
3151
|
const skills = [];
|
|
@@ -2563,22 +3162,25 @@ async function installCommand(names, options) {
|
|
|
2563
3162
|
}
|
|
2564
3163
|
logger.error(`${t("error_plugin_not_found")}: ${name}`);
|
|
2565
3164
|
const allPlugins = getAllPlugins();
|
|
2566
|
-
logger.info("
|
|
3165
|
+
logger.info(t("available_plugins") + ":");
|
|
2567
3166
|
for (const p of allPlugins) {
|
|
2568
3167
|
logger.step(` ${p.id} (${p.aliases.join(", ")})`);
|
|
2569
3168
|
}
|
|
2570
|
-
logger.info(
|
|
3169
|
+
logger.info(`${t("available_skills")}: ${chalk10.cyan("install-helper install --list")}`);
|
|
2571
3170
|
return;
|
|
2572
3171
|
}
|
|
2573
3172
|
let tool;
|
|
2574
3173
|
if (options.tool) {
|
|
2575
|
-
tool = options.tool;
|
|
3174
|
+
tool = validateTool(options.tool);
|
|
2576
3175
|
} else {
|
|
2577
3176
|
const detected = await selectToolWithDetection();
|
|
2578
|
-
if (detected === "back")
|
|
3177
|
+
if (detected === "back" || detected === "cancel") {
|
|
3178
|
+
logger.info(t("init_cancelled"));
|
|
3179
|
+
return;
|
|
3180
|
+
}
|
|
2579
3181
|
tool = detected;
|
|
2580
3182
|
}
|
|
2581
|
-
const level = options.level
|
|
3183
|
+
const level = options.level ? validateLevel(options.level) : "project";
|
|
2582
3184
|
if (plugins.length > 0) {
|
|
2583
3185
|
const configRoot = getConfigRoot(tool, level);
|
|
2584
3186
|
const manifests = readAllManifests(configRoot);
|
|
@@ -2606,16 +3208,18 @@ async function installCommand(names, options) {
|
|
|
2606
3208
|
}
|
|
2607
3209
|
if (pluginsToInstall.length > 0) {
|
|
2608
3210
|
const repoManager = createRepositoryManager();
|
|
2609
|
-
const spinner = createSpinner(t("
|
|
3211
|
+
const spinner = createSpinner(t("loading_plugin_data"));
|
|
2610
3212
|
spinner.start();
|
|
2611
3213
|
const repoPath = await repoManager.ensureRepo();
|
|
2612
|
-
spinner.succeed(t("
|
|
3214
|
+
spinner.succeed(t("loading_plugin_data_complete"));
|
|
2613
3215
|
const allPlugins = getAllPlugins();
|
|
2614
3216
|
const results = await installPlugins(
|
|
2615
3217
|
pluginsToInstall,
|
|
2616
3218
|
tool,
|
|
2617
3219
|
level,
|
|
2618
|
-
repoPath
|
|
3220
|
+
repoPath,
|
|
3221
|
+
void 0,
|
|
3222
|
+
options.yes
|
|
2619
3223
|
);
|
|
2620
3224
|
const summary = results.map((result) => {
|
|
2621
3225
|
const plugin = allPlugins.find((p) => p.id === result.pluginId);
|
|
@@ -2633,32 +3237,35 @@ async function installCommand(names, options) {
|
|
|
2633
3237
|
}
|
|
2634
3238
|
if (skills.length > 0) {
|
|
2635
3239
|
const repoManager = createRepositoryManager();
|
|
2636
|
-
const spinner = createSpinner(t("
|
|
3240
|
+
const spinner = createSpinner(t("loading_plugin_data"));
|
|
2637
3241
|
spinner.start();
|
|
2638
3242
|
const repoPath = await repoManager.ensureRepo();
|
|
2639
|
-
spinner.succeed(t("
|
|
2640
|
-
logger.info(`${t("skill_install_progress")} ${
|
|
3243
|
+
spinner.succeed(t("loading_plugin_data_complete"));
|
|
3244
|
+
logger.info(`${t("skill_install_progress")} ${t("skill_count_format").replace("{count}", chalk10.bold(String(skills.length)))}...`);
|
|
2641
3245
|
const results = await installSkills(skills, tool, level, repoPath);
|
|
2642
3246
|
let successCount = 0;
|
|
2643
3247
|
let failCount = 0;
|
|
2644
|
-
|
|
3248
|
+
const total = results.length;
|
|
3249
|
+
for (let i = 0; i < results.length; i++) {
|
|
3250
|
+
const result = results[i];
|
|
3251
|
+
const progress = `[${i + 1}/${total}]`;
|
|
2645
3252
|
if (result.success) {
|
|
2646
|
-
logger.success(`${result.skillId}`);
|
|
3253
|
+
logger.success(`${progress} ${result.skillId}`);
|
|
2647
3254
|
successCount++;
|
|
2648
3255
|
} else {
|
|
2649
|
-
logger.error(`${result.skillId}: ${result.error}`);
|
|
3256
|
+
logger.error(`${progress} ${result.skillId}: ${result.error}`);
|
|
2650
3257
|
failCount++;
|
|
2651
3258
|
}
|
|
2652
3259
|
}
|
|
2653
3260
|
logger.blank();
|
|
2654
|
-
logger.success(`${t("skill_install_done")}: ${
|
|
3261
|
+
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)))}`);
|
|
2655
3262
|
logger.blank();
|
|
2656
|
-
logger.info(
|
|
2657
|
-
logger.info(
|
|
3263
|
+
logger.info(`${t("install_to")}: ${chalk10.cyan(getConfigRoot(tool, level) + "/skills/")}`);
|
|
3264
|
+
logger.info(`${t("start_to_use").replace("{tool}", chalk10.green(tool))}`);
|
|
2658
3265
|
logger.blank();
|
|
2659
3266
|
}
|
|
2660
3267
|
}
|
|
2661
|
-
async function installPlugins(pluginIds, tool, level, repoPath) {
|
|
3268
|
+
async function installPlugins(pluginIds, tool, level, repoPath, installPath, yes) {
|
|
2662
3269
|
const results = [];
|
|
2663
3270
|
const total = pluginIds.length;
|
|
2664
3271
|
for (let i = 0; i < pluginIds.length; i++) {
|
|
@@ -2670,7 +3277,9 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
|
|
|
2670
3277
|
pluginId,
|
|
2671
3278
|
tool,
|
|
2672
3279
|
level,
|
|
2673
|
-
repoPath
|
|
3280
|
+
repoPath,
|
|
3281
|
+
installPath,
|
|
3282
|
+
yes
|
|
2674
3283
|
});
|
|
2675
3284
|
if (result.success) {
|
|
2676
3285
|
spinner.succeed(
|
|
@@ -2686,11 +3295,12 @@ async function installPlugins(pluginIds, tool, level, repoPath) {
|
|
|
2686
3295
|
}
|
|
2687
3296
|
|
|
2688
3297
|
// src/commands/uninstall.ts
|
|
2689
|
-
import { existsSync as
|
|
2690
|
-
import
|
|
3298
|
+
import { existsSync as existsSync16, unlinkSync as unlinkSync7, rmdirSync as rmdirSync2, readdirSync as readdirSync6 } from "fs";
|
|
3299
|
+
import { resolve, basename as basename2 } from "path";
|
|
3300
|
+
import chalk11 from "chalk";
|
|
2691
3301
|
async function uninstallCommand(names, options) {
|
|
2692
|
-
const tool = options.tool
|
|
2693
|
-
const level = options.level
|
|
3302
|
+
const tool = options.tool ? validateTool(options.tool) : "opencode";
|
|
3303
|
+
const level = options.level ? validateLevel(options.level) : "project";
|
|
2694
3304
|
const plugins = [];
|
|
2695
3305
|
const skills = [];
|
|
2696
3306
|
for (const name of names) {
|
|
@@ -2704,40 +3314,50 @@ async function uninstallCommand(names, options) {
|
|
|
2704
3314
|
skills.push(skill.id);
|
|
2705
3315
|
continue;
|
|
2706
3316
|
}
|
|
2707
|
-
logger.error(
|
|
3317
|
+
logger.error(`${t("uninstall_not_found")}: ${name}`);
|
|
2708
3318
|
}
|
|
2709
3319
|
for (const pluginId of plugins) {
|
|
2710
3320
|
const plugin = findPlugin(pluginId);
|
|
2711
3321
|
if (!plugin) continue;
|
|
2712
3322
|
const record = readRecord(plugin.id);
|
|
2713
3323
|
if (!record) {
|
|
2714
|
-
logger.warn(`${plugin.displayName}
|
|
2715
|
-
logger.info(
|
|
3324
|
+
logger.warn(`${plugin.displayName} ${t("uninstall_no_record")}`);
|
|
3325
|
+
logger.info(`${t("uninstall_no_record_hint")}`);
|
|
2716
3326
|
continue;
|
|
2717
3327
|
}
|
|
2718
|
-
logger.info(
|
|
3328
|
+
logger.info(`${t("uninstall_in_progress")} ${plugin.displayName}...`);
|
|
3329
|
+
const configRoot = getConfigRoot(record.tool, record.level);
|
|
3330
|
+
const allowedBases = [resolve(configRoot), resolve(record.installPath)];
|
|
3331
|
+
const isSafePath = (p) => {
|
|
3332
|
+
const resolved = resolve(p);
|
|
3333
|
+
return allowedBases.some((base) => resolved === base || resolved.startsWith(base + "/"));
|
|
3334
|
+
};
|
|
2719
3335
|
let removedFiles = 0;
|
|
2720
3336
|
let removedDirs = 0;
|
|
2721
3337
|
for (const filePath of record.files) {
|
|
3338
|
+
if (!isSafePath(filePath)) continue;
|
|
2722
3339
|
try {
|
|
2723
|
-
if (
|
|
2724
|
-
|
|
2725
|
-
const name = filePath
|
|
2726
|
-
logger.step(`
|
|
3340
|
+
if (existsSync16(filePath) || isSymlink(filePath)) {
|
|
3341
|
+
unlinkSync7(filePath);
|
|
3342
|
+
const name = basename2(filePath);
|
|
3343
|
+
logger.step(` ${t("uninstall_remove_label")}: ${name}`);
|
|
2727
3344
|
removedFiles++;
|
|
2728
3345
|
}
|
|
2729
3346
|
} catch {
|
|
2730
3347
|
}
|
|
2731
3348
|
}
|
|
2732
|
-
const sortedDirs = [...record.directories].sort((a, b) => b.length - a.length);
|
|
3349
|
+
const sortedDirs = [...record.directories].filter(isSafePath).sort((a, b) => b.length - a.length);
|
|
2733
3350
|
for (const dirPath of sortedDirs) {
|
|
2734
3351
|
try {
|
|
2735
|
-
if (
|
|
2736
|
-
|
|
3352
|
+
if (resolve(dirPath) === resolve(configRoot)) {
|
|
3353
|
+
continue;
|
|
3354
|
+
}
|
|
3355
|
+
if (existsSync16(dirPath)) {
|
|
3356
|
+
const entries = readdirSync6(dirPath);
|
|
2737
3357
|
if (entries.length === 0) {
|
|
2738
3358
|
rmdirSync2(dirPath);
|
|
2739
|
-
const name = dirPath
|
|
2740
|
-
logger.step(`
|
|
3359
|
+
const name = basename2(dirPath);
|
|
3360
|
+
logger.step(` ${t("uninstall_clean_empty_dir")}: ${name}/`);
|
|
2741
3361
|
removedDirs++;
|
|
2742
3362
|
}
|
|
2743
3363
|
}
|
|
@@ -2746,15 +3366,32 @@ async function uninstallCommand(names, options) {
|
|
|
2746
3366
|
}
|
|
2747
3367
|
deleteRecord(plugin.id);
|
|
2748
3368
|
removeInstalledPlugin(plugin.id);
|
|
3369
|
+
const backups = findBackups(configRoot);
|
|
3370
|
+
const otherBackups = backups.filter((b) => b.pluginId !== pluginId);
|
|
3371
|
+
if (otherBackups.length > 0) {
|
|
3372
|
+
const choice = await showRestorePrompt(otherBackups);
|
|
3373
|
+
if (choice !== "none") {
|
|
3374
|
+
const restored = restoreBackup(choice, configRoot, record.tool);
|
|
3375
|
+
if (restored) {
|
|
3376
|
+
const backupInfo = otherBackups.find((b) => b.filePath === choice);
|
|
3377
|
+
logger.success(`${t("backup_restore_success")}: ${backupInfo?.pluginName || ""}`);
|
|
3378
|
+
deleteBackup(choice);
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
const currentBackups = backups.filter((b) => b.pluginId === pluginId);
|
|
3383
|
+
for (const backup of currentBackups) {
|
|
3384
|
+
deleteBackup(backup.filePath);
|
|
3385
|
+
}
|
|
2749
3386
|
const totalRemoved = removedFiles + removedDirs;
|
|
2750
3387
|
if (totalRemoved > 0) {
|
|
2751
|
-
logger.success(
|
|
3388
|
+
logger.success(t("uninstall_summary_format").replace("{name}", plugin.displayName).replace("{files}", String(removedFiles)).replace("{dirs}", String(removedDirs)));
|
|
2752
3389
|
} else {
|
|
2753
|
-
logger.warn(`${plugin.displayName}
|
|
3390
|
+
logger.warn(`${plugin.displayName}${t("uninstall_files_gone")}`);
|
|
2754
3391
|
}
|
|
2755
3392
|
}
|
|
2756
3393
|
if (skills.length > 0) {
|
|
2757
|
-
logger.info(
|
|
3394
|
+
logger.info(`${t("uninstall_skills_in_progress")} ${t("skill_count_format").replace("{count}", String(skills.length))}...`);
|
|
2758
3395
|
const results = await uninstallSkills(skills, tool, level);
|
|
2759
3396
|
let successCount = 0;
|
|
2760
3397
|
let failCount = 0;
|
|
@@ -2766,30 +3403,26 @@ async function uninstallCommand(names, options) {
|
|
|
2766
3403
|
}
|
|
2767
3404
|
}
|
|
2768
3405
|
logger.blank();
|
|
2769
|
-
logger.success(`${t("skill_uninstall_done")}: ${
|
|
3406
|
+
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)))}`);
|
|
2770
3407
|
logger.blank();
|
|
2771
3408
|
}
|
|
2772
3409
|
}
|
|
2773
|
-
function isSymlink3(path) {
|
|
2774
|
-
try {
|
|
2775
|
-
return lstatSync4(path).isSymbolicLink();
|
|
2776
|
-
} catch {
|
|
2777
|
-
return false;
|
|
2778
|
-
}
|
|
2779
|
-
}
|
|
2780
3410
|
|
|
2781
3411
|
// src/commands/update.ts
|
|
2782
|
-
import
|
|
3412
|
+
import chalk12 from "chalk";
|
|
2783
3413
|
async function updateCommand(pluginNames, options) {
|
|
2784
3414
|
let tool;
|
|
2785
3415
|
if (options.tool) {
|
|
2786
|
-
tool = options.tool;
|
|
3416
|
+
tool = validateTool(options.tool);
|
|
2787
3417
|
} else {
|
|
2788
3418
|
const detected = await selectToolWithDetection();
|
|
2789
|
-
if (detected === "back")
|
|
3419
|
+
if (detected === "back" || detected === "cancel") {
|
|
3420
|
+
logger.info(t("init_cancelled"));
|
|
3421
|
+
return;
|
|
3422
|
+
}
|
|
2790
3423
|
tool = detected;
|
|
2791
3424
|
}
|
|
2792
|
-
const level = options.level
|
|
3425
|
+
const level = options.level ? validateLevel(options.level) : "project";
|
|
2793
3426
|
let pluginsToUpdate = [];
|
|
2794
3427
|
if (pluginNames.length === 0) {
|
|
2795
3428
|
const configRoot = getConfigRoot(tool, level);
|
|
@@ -2797,7 +3430,7 @@ async function updateCommand(pluginNames, options) {
|
|
|
2797
3430
|
pluginsToUpdate = manifests.map((m) => m.team);
|
|
2798
3431
|
if (pluginsToUpdate.length === 0) {
|
|
2799
3432
|
logger.info(t("update_no_plugins"));
|
|
2800
|
-
logger.info(
|
|
3433
|
+
logger.info(t("update_install_hint").replace("{cmd}", chalk12.cyan("install-helper install <plugin>")));
|
|
2801
3434
|
return;
|
|
2802
3435
|
}
|
|
2803
3436
|
} else {
|
|
@@ -2813,9 +3446,7 @@ async function updateCommand(pluginNames, options) {
|
|
|
2813
3446
|
const repoManager = createRepositoryManager();
|
|
2814
3447
|
const updateSpinner = createSpinner(t("update_updating") + "...");
|
|
2815
3448
|
updateSpinner.start();
|
|
2816
|
-
await repoManager.
|
|
2817
|
-
const repoPath = repoManager.getRepoPath();
|
|
2818
|
-
await repoManager.ensureRepoAndScan();
|
|
3449
|
+
const repoPath = await repoManager.ensureRepoAndScan();
|
|
2819
3450
|
updateSpinner.succeed(t("install_repo_ready"));
|
|
2820
3451
|
const allPlugins = getAllPlugins();
|
|
2821
3452
|
const results = [];
|
|
@@ -2859,42 +3490,43 @@ async function updateCommand(pluginNames, options) {
|
|
|
2859
3490
|
}
|
|
2860
3491
|
|
|
2861
3492
|
// src/commands/info.ts
|
|
2862
|
-
import
|
|
2863
|
-
import { existsSync as
|
|
2864
|
-
import { join as
|
|
3493
|
+
import chalk13 from "chalk";
|
|
3494
|
+
import { existsSync as existsSync17, readdirSync as readdirSync7 } from "fs";
|
|
3495
|
+
import { join as join16 } from "path";
|
|
2865
3496
|
async function infoCommand(pluginName) {
|
|
2866
3497
|
const plugin = findPlugin(pluginName);
|
|
2867
3498
|
if (!plugin) {
|
|
2868
|
-
console.log(
|
|
3499
|
+
console.log(chalk13.red(` ${t("info_not_found")}: ${pluginName}`));
|
|
2869
3500
|
return;
|
|
2870
3501
|
}
|
|
2871
3502
|
const installed = scanInstalled();
|
|
2872
|
-
const
|
|
2873
|
-
const
|
|
3503
|
+
const inst = installed.find((p) => p.id === plugin.id);
|
|
3504
|
+
const isInstalled = !!inst;
|
|
3505
|
+
const statusText = isInstalled ? chalk13.green(`\u2713 ${t("status_installed")}`) : chalk13.dim(`\u2014 ${t("status_not_installed")}`);
|
|
2874
3506
|
console.log();
|
|
2875
|
-
console.log(
|
|
2876
|
-
console.log(
|
|
3507
|
+
console.log(chalk13.bold(` ${plugin.displayName}`));
|
|
3508
|
+
console.log(chalk13.dim(" " + "\u2500".repeat(40)));
|
|
2877
3509
|
console.log();
|
|
2878
|
-
console.log(` ${
|
|
2879
|
-
console.log(` ${
|
|
2880
|
-
console.log(` ${
|
|
2881
|
-
console.log(` ${
|
|
2882
|
-
console.log(` ${
|
|
2883
|
-
console.log(` ${
|
|
3510
|
+
console.log(` ${chalk13.dim(t("info_description") + ":")} ${plugin.description}`);
|
|
3511
|
+
console.log(` ${chalk13.dim("ID:")} ${plugin.id}`);
|
|
3512
|
+
console.log(` ${chalk13.dim(t("info_status") + ":")} ${statusText}`);
|
|
3513
|
+
console.log(` ${chalk13.dim(t("info_skills") + ":")} ${inst ? inst.skillsCount : plugin.skills}`);
|
|
3514
|
+
console.log(` ${chalk13.dim(t("info_agents") + ":")} ${inst ? inst.agentsCount : plugin.agents}`);
|
|
3515
|
+
console.log(` ${chalk13.dim(t("info_aliases") + ":")} ${plugin.aliases.join(", ")}`);
|
|
2884
3516
|
console.log();
|
|
2885
3517
|
try {
|
|
2886
3518
|
const repoManager = createRepositoryManager();
|
|
2887
3519
|
const repoPath = await repoManager.ensureRepo();
|
|
2888
|
-
const quickstartPath =
|
|
2889
|
-
if (
|
|
2890
|
-
console.log(` ${
|
|
3520
|
+
const quickstartPath = join16(repoPath, plugin.dir, "quickstart.md");
|
|
3521
|
+
if (existsSync17(quickstartPath)) {
|
|
3522
|
+
console.log(` ${chalk13.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
|
|
2891
3523
|
}
|
|
2892
|
-
const agentsDir =
|
|
2893
|
-
if (
|
|
2894
|
-
const agentFiles =
|
|
3524
|
+
const agentsDir = join16(repoPath, plugin.dir, "agents");
|
|
3525
|
+
if (existsSync17(agentsDir)) {
|
|
3526
|
+
const agentFiles = readdirSync7(agentsDir).filter((f) => f.endsWith(".md"));
|
|
2895
3527
|
if (agentFiles.length > 0) {
|
|
2896
3528
|
console.log();
|
|
2897
|
-
console.log(` ${
|
|
3529
|
+
console.log(` ${chalk13.bold("Agents:")}`);
|
|
2898
3530
|
for (const agent of agentFiles) {
|
|
2899
3531
|
console.log(` \u2022 ${agent.replace(".md", "")}`);
|
|
2900
3532
|
}
|
|
@@ -2906,49 +3538,63 @@ async function infoCommand(pluginName) {
|
|
|
2906
3538
|
}
|
|
2907
3539
|
|
|
2908
3540
|
// src/commands/lang.ts
|
|
2909
|
-
import
|
|
3541
|
+
import chalk14 from "chalk";
|
|
2910
3542
|
async function langCommand(action, value) {
|
|
2911
3543
|
const config = readConfig();
|
|
2912
3544
|
if (!action || action === "show") {
|
|
2913
3545
|
console.log();
|
|
2914
|
-
console.log(
|
|
2915
|
-
console.log(
|
|
3546
|
+
console.log(chalk14.bold(` ${t("lang_title")}`));
|
|
3547
|
+
console.log(chalk14.dim(" " + "\u2500".repeat(40)));
|
|
2916
3548
|
console.log();
|
|
2917
|
-
console.log(` ${
|
|
3549
|
+
console.log(` ${chalk14.dim(t("lang_current") + ":")} ${config.language}`);
|
|
2918
3550
|
console.log();
|
|
2919
|
-
console.log(
|
|
2920
|
-
console.log(
|
|
3551
|
+
console.log(chalk14.dim(` ${t("lang_supported")}`));
|
|
3552
|
+
console.log(chalk14.dim(` ${t("lang_usage")}`));
|
|
2921
3553
|
console.log();
|
|
2922
3554
|
return;
|
|
2923
3555
|
}
|
|
2924
3556
|
if (action === "set") {
|
|
2925
3557
|
if (!value) {
|
|
2926
|
-
console.log(
|
|
3558
|
+
console.log(chalk14.red(` ${t("lang_specify")}`));
|
|
2927
3559
|
return;
|
|
2928
3560
|
}
|
|
2929
3561
|
if (value !== "zh_CN" && value !== "en_US") {
|
|
2930
|
-
console.log(
|
|
3562
|
+
console.log(chalk14.red(` ${t("lang_invalid")}`));
|
|
2931
3563
|
return;
|
|
2932
3564
|
}
|
|
2933
3565
|
updateConfig({ language: value });
|
|
2934
3566
|
setLanguage(value);
|
|
2935
3567
|
console.log();
|
|
2936
|
-
console.log(
|
|
3568
|
+
console.log(chalk14.green(` \u2713 ${t("lang_set")} ${value}`));
|
|
2937
3569
|
console.log();
|
|
2938
3570
|
return;
|
|
2939
3571
|
}
|
|
2940
|
-
console.log(
|
|
2941
|
-
console.log(
|
|
3572
|
+
console.log(chalk14.red(` ${t("lang_unknown_action")}: ${action}`));
|
|
3573
|
+
console.log(chalk14.dim(` ${t("lang_supported_actions")}`));
|
|
2942
3574
|
}
|
|
2943
3575
|
|
|
2944
3576
|
// src/cli.ts
|
|
3577
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
3578
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
3579
|
+
import { dirname as dirname8, join as join17 } from "path";
|
|
3580
|
+
function readVersion() {
|
|
3581
|
+
try {
|
|
3582
|
+
const __filename5 = fileURLToPath5(import.meta.url);
|
|
3583
|
+
const __dirname5 = dirname8(__filename5);
|
|
3584
|
+
const pkgPath = join17(__dirname5, "..", "package.json");
|
|
3585
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
3586
|
+
return pkg.version || "0.0.0";
|
|
3587
|
+
} catch {
|
|
3588
|
+
return "0.0.0";
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
2945
3591
|
function createCLI() {
|
|
2946
3592
|
const config = readConfig();
|
|
2947
3593
|
if (config.language) {
|
|
2948
3594
|
setLanguage(config.language);
|
|
2949
3595
|
}
|
|
2950
3596
|
const program2 = new Command();
|
|
2951
|
-
program2.name("install-helper").description("Install Helper - Interactive installer for CANN operator development skills").version(
|
|
3597
|
+
program2.name("install-helper").description("CANNBot Install Helper - Interactive installer for CANN operator development skills").version(readVersion());
|
|
2952
3598
|
program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
|
|
2953
3599
|
await initCommand();
|
|
2954
3600
|
});
|
|
@@ -2961,7 +3607,7 @@ function createCLI() {
|
|
|
2961
3607
|
program2.command("status").description("Show installed plugins").action(async () => {
|
|
2962
3608
|
await statusCommand();
|
|
2963
3609
|
});
|
|
2964
|
-
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) => {
|
|
3610
|
+
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) => {
|
|
2965
3611
|
await installCommand(names, options);
|
|
2966
3612
|
});
|
|
2967
3613
|
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) => {
|
|
@@ -2985,7 +3631,7 @@ function createCLI() {
|
|
|
2985
3631
|
// src/index.ts
|
|
2986
3632
|
process.on("uncaughtException", (error) => {
|
|
2987
3633
|
if (error.name === "ExitPromptError") {
|
|
2988
|
-
console.log("\n
|
|
3634
|
+
console.log("\n" + t("init_cancelled"));
|
|
2989
3635
|
process.exit(0);
|
|
2990
3636
|
}
|
|
2991
3637
|
throw error;
|