@cannbot-ai/install-helper 0.0.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +227 -0
- package/dist/index.js +2900 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2900 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import { select as select3, Separator as Separator3 } from "@inquirer/prompts";
|
|
8
|
+
import chalk5 from "chalk";
|
|
9
|
+
|
|
10
|
+
// src/ui/wizard.ts
|
|
11
|
+
import { select, checkbox, Separator } from "@inquirer/prompts";
|
|
12
|
+
import chalk2 from "chalk";
|
|
13
|
+
|
|
14
|
+
// src/core/registry.ts
|
|
15
|
+
var PLUGIN_REGISTRY = [
|
|
16
|
+
{
|
|
17
|
+
id: "ops-direct-invoke",
|
|
18
|
+
dir: "plugins-official/ops-direct-invoke",
|
|
19
|
+
displayName: "AscendC Kernel \u76F4\u8C03",
|
|
20
|
+
script: "init.sh",
|
|
21
|
+
aliases: ["ops-direct", "ascendc-direct", "direct", "kernel"],
|
|
22
|
+
skills: 16,
|
|
23
|
+
agents: 3,
|
|
24
|
+
description: "Ascend C Kernel <<<>>> \u76F4\u8C03\u7B97\u5B50\u5F00\u53D1\u5168\u6D41\u7A0B"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "ops-direct-invoke-flash",
|
|
28
|
+
dir: "plugins-official/ops-direct-invoke-flash",
|
|
29
|
+
displayName: "AscendC Kernel \u4ECE\u96F6\u6784\u5EFA",
|
|
30
|
+
script: "init.sh",
|
|
31
|
+
aliases: ["flash", "kernel-flash"],
|
|
32
|
+
skills: 1,
|
|
33
|
+
agents: 1,
|
|
34
|
+
description: "Ascend C / Ascend950 Reg API \u6838\u51FD\u6570\u4ECE\u96F6\u6784\u5EFA"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "ops-registry-invoke",
|
|
38
|
+
dir: "plugins-official/ops-registry-invoke",
|
|
39
|
+
displayName: "AscendC \u7B97\u5B50\u6CE8\u518C\u8C03\u7528",
|
|
40
|
+
script: "init.sh",
|
|
41
|
+
aliases: ["ops-registry", "ascendc-registry", "registry"],
|
|
42
|
+
skills: 12,
|
|
43
|
+
agents: 4,
|
|
44
|
+
description: "Ascend C \u7B97\u5B50\u6CE8\u518C\u8C03\u7528\u5F00\u53D1\u6D41\u7A0B"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "pypto-op-orchestrator",
|
|
48
|
+
dir: "plugins-official/pypto-op-orchestrator",
|
|
49
|
+
displayName: "PyPTO \u7B97\u5B50",
|
|
50
|
+
script: "init.sh",
|
|
51
|
+
aliases: ["pypto", "pytorch"],
|
|
52
|
+
skills: 8,
|
|
53
|
+
agents: 3,
|
|
54
|
+
description: "PyPTO \u7B97\u5B50\u7AEF\u5230\u7AEF\u5F00\u53D1\u7F16\u6392"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "triton-op-generator",
|
|
58
|
+
dir: "plugins-official/triton-op-generator",
|
|
59
|
+
displayName: "Triton \u7B97\u5B50\u751F\u6210",
|
|
60
|
+
script: "install.sh",
|
|
61
|
+
aliases: ["triton"],
|
|
62
|
+
skills: 6,
|
|
63
|
+
agents: 0,
|
|
64
|
+
description: "Triton-Ascend \u7B97\u5B50\u4EE3\u7801\u751F\u6210\u4E0E\u4F18\u5316"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "tilelang-op-orchestrator",
|
|
68
|
+
dir: "plugins-official/tilelang-op-orchestrator",
|
|
69
|
+
displayName: "TileLang \u7B97\u5B50",
|
|
70
|
+
script: "init.sh",
|
|
71
|
+
aliases: ["tilelang"],
|
|
72
|
+
skills: 9,
|
|
73
|
+
agents: 3,
|
|
74
|
+
description: "TileLang \u7B97\u5B50\u5F00\u53D1\u6D41\u7A0B"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "model-infer-optimize",
|
|
78
|
+
dir: "plugins-official/model-infer-optimize",
|
|
79
|
+
displayName: "NPU \u63A8\u7406\u4F18\u5316",
|
|
80
|
+
script: "init.sh",
|
|
81
|
+
aliases: ["model-infer", "infer", "inference"],
|
|
82
|
+
skills: 11,
|
|
83
|
+
agents: 3,
|
|
84
|
+
description: "NPU \u6A21\u578B\u63A8\u7406\u7AEF\u5230\u7AEF\u4F18\u5316"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "catlass-op-generator",
|
|
88
|
+
dir: "plugins-official/catlass-op-generator",
|
|
89
|
+
displayName: "Catlass \u7B97\u5B50\u76F4\u8C03",
|
|
90
|
+
script: "init.sh",
|
|
91
|
+
aliases: ["catlass"],
|
|
92
|
+
skills: 10,
|
|
93
|
+
agents: 3,
|
|
94
|
+
description: "Catlass \u7B97\u5B50\u76F4\u8C03\u5F00\u53D1"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "ops-code-reviewer",
|
|
98
|
+
dir: "plugins-official/ops-code-reviewer",
|
|
99
|
+
displayName: "\u4EE3\u7801\u68C0\u89C6",
|
|
100
|
+
script: "init.sh",
|
|
101
|
+
aliases: ["code-review", "reviewer", "review"],
|
|
102
|
+
skills: 5,
|
|
103
|
+
agents: 1,
|
|
104
|
+
description: "\u4EE3\u7801\u68C0\u89C6\u4E0E\u89C4\u8303\u68C0\u67E5"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "torch-compile",
|
|
108
|
+
dir: "plugins-official/torch-compile",
|
|
109
|
+
displayName: "torch.compile \u56FE\u6A21\u5F0F",
|
|
110
|
+
script: "init.sh",
|
|
111
|
+
aliases: ["torch", "compile", "graph"],
|
|
112
|
+
skills: 6,
|
|
113
|
+
agents: 1,
|
|
114
|
+
description: "PyTorch torch.compile \u56FE\u6A21\u5F0F\u9002\u914D"
|
|
115
|
+
}
|
|
116
|
+
];
|
|
117
|
+
function findPlugin(query) {
|
|
118
|
+
const normalized = query.toLowerCase().trim();
|
|
119
|
+
const exactMatch = PLUGIN_REGISTRY.find(
|
|
120
|
+
(p) => p.id === normalized || p.id === query.trim()
|
|
121
|
+
);
|
|
122
|
+
if (exactMatch) return exactMatch;
|
|
123
|
+
const aliasMatch = PLUGIN_REGISTRY.find(
|
|
124
|
+
(p) => p.aliases.some((a) => a === normalized)
|
|
125
|
+
);
|
|
126
|
+
if (aliasMatch) return aliasMatch;
|
|
127
|
+
const prefixMatch = PLUGIN_REGISTRY.find((p) => p.id.startsWith(normalized));
|
|
128
|
+
if (prefixMatch) return prefixMatch;
|
|
129
|
+
return void 0;
|
|
130
|
+
}
|
|
131
|
+
function getAllPlugins() {
|
|
132
|
+
return PLUGIN_REGISTRY;
|
|
133
|
+
}
|
|
134
|
+
function getPluginById(id) {
|
|
135
|
+
return PLUGIN_REGISTRY.find((p) => p.id === id);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/core/detector.ts
|
|
139
|
+
import { execa } from "execa";
|
|
140
|
+
import { existsSync as existsSync2 } from "fs";
|
|
141
|
+
import { homedir as homedir2 } from "os";
|
|
142
|
+
import { join as join2 } from "path";
|
|
143
|
+
|
|
144
|
+
// src/utils/paths.ts
|
|
145
|
+
import { homedir } from "os";
|
|
146
|
+
import { join } from "path";
|
|
147
|
+
import { existsSync } from "fs";
|
|
148
|
+
function detectTraeVariant() {
|
|
149
|
+
const home = homedir();
|
|
150
|
+
if (existsSync(join(home, ".trae-cn"))) return "ide";
|
|
151
|
+
if (existsSync(join(home, ".marscode"))) return "plugin";
|
|
152
|
+
if (existsSync(join(home, ".traecli"))) return "cli";
|
|
153
|
+
return "unknown";
|
|
154
|
+
}
|
|
155
|
+
function getConfigRoot(tool, level, base) {
|
|
156
|
+
const home = homedir();
|
|
157
|
+
if (level === "global") {
|
|
158
|
+
switch (tool) {
|
|
159
|
+
case "opencode":
|
|
160
|
+
return join(home, ".config", "opencode");
|
|
161
|
+
case "claude":
|
|
162
|
+
return join(home, ".claude");
|
|
163
|
+
case "trae": {
|
|
164
|
+
const variant = detectTraeVariant();
|
|
165
|
+
switch (variant) {
|
|
166
|
+
case "plugin":
|
|
167
|
+
return join(home, ".marscode");
|
|
168
|
+
case "cli":
|
|
169
|
+
return join(home, ".traecli");
|
|
170
|
+
default:
|
|
171
|
+
return join(home, ".trae-cn");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
case "cursor":
|
|
175
|
+
return join(home, ".cursor");
|
|
176
|
+
case "copilot":
|
|
177
|
+
return join(home, ".copilot");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const baseDir = base || process.cwd();
|
|
181
|
+
switch (tool) {
|
|
182
|
+
case "opencode":
|
|
183
|
+
return join(baseDir, ".opencode");
|
|
184
|
+
case "claude":
|
|
185
|
+
return join(baseDir, ".claude");
|
|
186
|
+
case "trae": {
|
|
187
|
+
const variant = detectTraeVariant();
|
|
188
|
+
switch (variant) {
|
|
189
|
+
case "plugin":
|
|
190
|
+
return join(baseDir, ".marscode");
|
|
191
|
+
case "cli":
|
|
192
|
+
return join(baseDir, ".traecli");
|
|
193
|
+
default:
|
|
194
|
+
return join(baseDir, ".trae");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
case "cursor":
|
|
198
|
+
return join(baseDir, ".cursor");
|
|
199
|
+
case "copilot":
|
|
200
|
+
return join(baseDir, ".github");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function getConfigFileName(tool) {
|
|
204
|
+
return tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
205
|
+
}
|
|
206
|
+
function getSkillsDir(configRoot) {
|
|
207
|
+
return join(configRoot, "skills");
|
|
208
|
+
}
|
|
209
|
+
function getAgentsDir(configRoot) {
|
|
210
|
+
return join(configRoot, "agents");
|
|
211
|
+
}
|
|
212
|
+
function getManifestPath(configRoot) {
|
|
213
|
+
return join(configRoot, "cannbot-manifest.json");
|
|
214
|
+
}
|
|
215
|
+
function getCannbotConfigDir() {
|
|
216
|
+
return join(homedir(), ".cannbot");
|
|
217
|
+
}
|
|
218
|
+
function getCannbotRepoPath() {
|
|
219
|
+
return join(getCannbotConfigDir(), "repo");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/core/detector.ts
|
|
223
|
+
async function getCommandVersion(cmd, args = ["--version"]) {
|
|
224
|
+
try {
|
|
225
|
+
const result = await execa(cmd, args, { timeout: 5e3 });
|
|
226
|
+
const output = result.stdout.trim();
|
|
227
|
+
const match = output.match(/(\d+\.\d+\.\d+)/);
|
|
228
|
+
return match ? match[1] : output.split("\n")[0];
|
|
229
|
+
} catch {
|
|
230
|
+
return void 0;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async function getCommandPath(cmd) {
|
|
234
|
+
try {
|
|
235
|
+
const result = await execa("which", [cmd], { timeout: 5e3 });
|
|
236
|
+
return result.stdout.trim();
|
|
237
|
+
} catch {
|
|
238
|
+
return void 0;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function detectOpenCode() {
|
|
242
|
+
const path = await getCommandPath("opencode");
|
|
243
|
+
if (!path) return void 0;
|
|
244
|
+
const version = await getCommandVersion("opencode");
|
|
245
|
+
return { name: "opencode", version, path };
|
|
246
|
+
}
|
|
247
|
+
async function detectClaude() {
|
|
248
|
+
const path = await getCommandPath("claude");
|
|
249
|
+
if (!path) return void 0;
|
|
250
|
+
const version = await getCommandVersion("claude");
|
|
251
|
+
return { name: "claude", version, path };
|
|
252
|
+
}
|
|
253
|
+
async function detectTrae() {
|
|
254
|
+
const home = homedir2();
|
|
255
|
+
const variant = detectTraeVariant();
|
|
256
|
+
let path;
|
|
257
|
+
if (variant === "ide" && existsSync2(join2(home, ".trae-cn"))) {
|
|
258
|
+
path = join2(home, ".trae-cn");
|
|
259
|
+
} else if (variant === "plugin" && existsSync2(join2(home, ".marscode"))) {
|
|
260
|
+
path = join2(home, ".marscode");
|
|
261
|
+
} else if (variant === "cli" && existsSync2(join2(home, ".traecli"))) {
|
|
262
|
+
path = join2(home, ".traecli");
|
|
263
|
+
} else if (variant === "unknown") {
|
|
264
|
+
const cmdPath = await getCommandPath("trae");
|
|
265
|
+
if (cmdPath) path = cmdPath;
|
|
266
|
+
}
|
|
267
|
+
if (!path) return void 0;
|
|
268
|
+
return { name: "trae", version: variant, path };
|
|
269
|
+
}
|
|
270
|
+
async function detectCursor() {
|
|
271
|
+
const path = await getCommandPath("cursor");
|
|
272
|
+
if (path) {
|
|
273
|
+
const version = await getCommandVersion("cursor");
|
|
274
|
+
return { name: "cursor", version, path };
|
|
275
|
+
}
|
|
276
|
+
if (process.platform === "darwin") {
|
|
277
|
+
const appPath = "/Applications/Cursor.app";
|
|
278
|
+
if (existsSync2(appPath)) {
|
|
279
|
+
return { name: "cursor", path: appPath };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return void 0;
|
|
283
|
+
}
|
|
284
|
+
async function detectCopilot() {
|
|
285
|
+
const path = await getCommandPath("gh");
|
|
286
|
+
if (!path) return void 0;
|
|
287
|
+
try {
|
|
288
|
+
await execa("gh", ["extension", "list"], { timeout: 5e3 });
|
|
289
|
+
return { name: "copilot", path };
|
|
290
|
+
} catch {
|
|
291
|
+
return void 0;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async function detectTools() {
|
|
295
|
+
const detectors = [
|
|
296
|
+
detectOpenCode,
|
|
297
|
+
detectClaude,
|
|
298
|
+
detectTrae,
|
|
299
|
+
detectCursor,
|
|
300
|
+
detectCopilot
|
|
301
|
+
];
|
|
302
|
+
const results = await Promise.allSettled(detectors.map((d) => d()));
|
|
303
|
+
const tools = [];
|
|
304
|
+
for (const result of results) {
|
|
305
|
+
if (result.status === "fulfilled" && result.value) {
|
|
306
|
+
tools.push(result.value);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return tools;
|
|
310
|
+
}
|
|
311
|
+
function getToolDisplayName(tool) {
|
|
312
|
+
const names = {
|
|
313
|
+
opencode: "OpenCode",
|
|
314
|
+
claude: "Claude Code",
|
|
315
|
+
trae: "Trae",
|
|
316
|
+
cursor: "Cursor",
|
|
317
|
+
copilot: "GitHub Copilot"
|
|
318
|
+
};
|
|
319
|
+
return names[tool];
|
|
320
|
+
}
|
|
321
|
+
function getAllTools() {
|
|
322
|
+
return ["opencode", "claude", "trae", "cursor", "copilot"];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/core/manifest.ts
|
|
326
|
+
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
327
|
+
import { join as join3 } from "path";
|
|
328
|
+
function readManifest(configRoot) {
|
|
329
|
+
const manifestPath = getManifestPath(configRoot);
|
|
330
|
+
if (existsSync3(manifestPath)) {
|
|
331
|
+
try {
|
|
332
|
+
const content = readFileSync(manifestPath, "utf-8");
|
|
333
|
+
return JSON.parse(content);
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
function readAllManifests(configRoot) {
|
|
340
|
+
const manifests = [];
|
|
341
|
+
const plugins = getAllPlugins();
|
|
342
|
+
const standardPath = getManifestPath(configRoot);
|
|
343
|
+
if (existsSync3(standardPath)) {
|
|
344
|
+
try {
|
|
345
|
+
const content = readFileSync(standardPath, "utf-8");
|
|
346
|
+
manifests.push(JSON.parse(content));
|
|
347
|
+
} catch {
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
for (const plugin of plugins) {
|
|
351
|
+
const pluginManifestPath = join3(configRoot, `${plugin.id}-manifest.json`);
|
|
352
|
+
if (existsSync3(pluginManifestPath)) {
|
|
353
|
+
try {
|
|
354
|
+
const content = readFileSync(pluginManifestPath, "utf-8");
|
|
355
|
+
manifests.push(JSON.parse(content));
|
|
356
|
+
} catch {
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return manifests;
|
|
361
|
+
}
|
|
362
|
+
function scanInstalled() {
|
|
363
|
+
const plugins = getAllPlugins();
|
|
364
|
+
const installed = [];
|
|
365
|
+
const tools = ["opencode", "claude", "trae", "cursor", "copilot"];
|
|
366
|
+
const levels = ["project", "global"];
|
|
367
|
+
for (const tool of tools) {
|
|
368
|
+
for (const level of levels) {
|
|
369
|
+
const configRoot = getConfigRoot(tool, level);
|
|
370
|
+
const manifests = readAllManifests(configRoot);
|
|
371
|
+
for (const manifest of manifests) {
|
|
372
|
+
const plugin = plugins.find((p) => p.id === manifest.team);
|
|
373
|
+
if (!plugin) continue;
|
|
374
|
+
const alreadyAdded = installed.some(
|
|
375
|
+
(p) => p.id === plugin.id && p.tool === tool && p.level === level
|
|
376
|
+
);
|
|
377
|
+
if (alreadyAdded) continue;
|
|
378
|
+
installed.push({
|
|
379
|
+
id: plugin.id,
|
|
380
|
+
displayName: plugin.displayName,
|
|
381
|
+
tool,
|
|
382
|
+
level,
|
|
383
|
+
skillsCount: manifest.installed_skills?.length || 0,
|
|
384
|
+
agentsCount: manifest.installed_agents?.length || 0,
|
|
385
|
+
installTime: manifest.install_time,
|
|
386
|
+
configRoot
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return installed;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// src/utils/i18n.ts
|
|
395
|
+
var zh_CN = {
|
|
396
|
+
wizard_title: "CANNBot \u4EA4\u4E92\u5F0F\u5B89\u88C5\u52A9\u624B",
|
|
397
|
+
wizard_detect: "\u6B63\u5728\u68C0\u6D4B\u5DF2\u5B89\u88C5\u7684 AI \u7F16\u7A0B\u5DE5\u5177...",
|
|
398
|
+
wizard_select_tool: "\u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177",
|
|
399
|
+
wizard_select_level: "\u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E",
|
|
400
|
+
wizard_select_plugins: "\u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6",
|
|
401
|
+
wizard_confirm: "\u786E\u8BA4\u5B89\u88C5",
|
|
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
|
+
};
|
|
476
|
+
var en_US = {
|
|
477
|
+
wizard_title: "CANNBot Skills Setup Wizard",
|
|
478
|
+
wizard_detect: "Detecting installed AI coding tools...",
|
|
479
|
+
wizard_select_tool: "Select AI coding tool",
|
|
480
|
+
wizard_select_level: "Select install level",
|
|
481
|
+
wizard_select_plugins: "Select scenarios to install",
|
|
482
|
+
wizard_confirm: "Confirm installation",
|
|
483
|
+
wizard_detected_tool: "Detected",
|
|
484
|
+
wizard_confirm_tool: "Confirm using",
|
|
485
|
+
wizard_last_used: "last used",
|
|
486
|
+
wizard_already_installed: "installed",
|
|
487
|
+
install_cloning: "Cloning cannbot-skills repository...",
|
|
488
|
+
install_repo_ready: "Repository ready",
|
|
489
|
+
install_progress: "Installing",
|
|
490
|
+
install_done: "Installation complete",
|
|
491
|
+
install_success: "success",
|
|
492
|
+
install_failed: "failed",
|
|
493
|
+
install_already_installed: "already installed",
|
|
494
|
+
install_reinstall_confirm: "Reinstall (overwrite)",
|
|
495
|
+
install_skip: "skipped",
|
|
496
|
+
install_enhanced_next_steps: "Next steps",
|
|
497
|
+
install_enhanced_launch: "Launch",
|
|
498
|
+
install_enhanced_try: "Try",
|
|
499
|
+
install_enhanced_more: "More",
|
|
500
|
+
install_enhanced_check: "Check",
|
|
501
|
+
install_enhanced_docs: "Docs",
|
|
502
|
+
update_title: "Update installed Skills",
|
|
503
|
+
update_updating: "Updating",
|
|
504
|
+
update_done: "Update complete",
|
|
505
|
+
update_no_plugins: "No installed plugins",
|
|
506
|
+
doctor_title: "CANNBot Doctor",
|
|
507
|
+
doctor_tools: "AI Tools",
|
|
508
|
+
doctor_plugins: "Installed Plugins",
|
|
509
|
+
doctor_links: "Link Integrity",
|
|
510
|
+
doctor_config: "Configuration",
|
|
511
|
+
doctor_result: "Result",
|
|
512
|
+
doctor_fix_title: "Auto-fix",
|
|
513
|
+
doctor_fix_cleaning: "Cleaning broken links",
|
|
514
|
+
doctor_fix_rebuilding: "Rebuilding directories",
|
|
515
|
+
doctor_fix_done: "Fix complete",
|
|
516
|
+
info_title: "Plugin Details",
|
|
517
|
+
info_description: "Description",
|
|
518
|
+
info_skills: "Skills",
|
|
519
|
+
info_agents: "Agents",
|
|
520
|
+
info_status: "Status",
|
|
521
|
+
info_quickstart: "Quick Start",
|
|
522
|
+
info_not_found: "Plugin not found",
|
|
523
|
+
lang_title: "Language Settings",
|
|
524
|
+
lang_current: "Current language",
|
|
525
|
+
lang_set: "Language set to",
|
|
526
|
+
lang_invalid: "Invalid language, supported: zh_CN, en_US",
|
|
527
|
+
list_title: "Available Scenarios",
|
|
528
|
+
list_id: "#",
|
|
529
|
+
list_name: "Name",
|
|
530
|
+
list_status: "Status",
|
|
531
|
+
list_skills: "Skills",
|
|
532
|
+
list_agents: "Agents",
|
|
533
|
+
status_installed: "installed",
|
|
534
|
+
status_not_installed: "not installed",
|
|
535
|
+
error_tool_not_found: "No AI coding tool detected",
|
|
536
|
+
error_plugin_not_found: "Plugin not found",
|
|
537
|
+
error_repo_not_found: "Repository not found",
|
|
538
|
+
quick_start: "Launch command",
|
|
539
|
+
quick_start_hint: "Try: help me develop an Abs operator",
|
|
540
|
+
skill_select_category: "Select Skill category",
|
|
541
|
+
skill_select_items: "Select Skills to install (space to toggle, enter to confirm)",
|
|
542
|
+
skill_already_installed: "installed",
|
|
543
|
+
skill_install_progress: "Installing",
|
|
544
|
+
skill_install_done: "Skill installation complete",
|
|
545
|
+
skill_uninstall_done: "Skill uninstallation complete",
|
|
546
|
+
skill_list_title: "Available Skills",
|
|
547
|
+
wizard_select_mode: "Select installation mode",
|
|
548
|
+
wizard_mode_plugin: "Install Plugin",
|
|
549
|
+
wizard_mode_plugin_desc: "Full development workflow (Skills + Agents + config)",
|
|
550
|
+
wizard_mode_skill: "Install Skill",
|
|
551
|
+
wizard_mode_skill_desc: "Domain knowledge only, give AI specific capabilities",
|
|
552
|
+
wizard_back: "Go back",
|
|
553
|
+
wizard_cancel: "Cancel installation",
|
|
554
|
+
wizard_no_selection: "No items selected, please choose an action",
|
|
555
|
+
wizard_back_to_reselect: "Go back"
|
|
556
|
+
};
|
|
557
|
+
var currentLang = "zh_CN";
|
|
558
|
+
function setLanguage(lang) {
|
|
559
|
+
currentLang = lang;
|
|
560
|
+
}
|
|
561
|
+
function t(key) {
|
|
562
|
+
return currentLang === "zh_CN" ? zh_CN[key] : en_US[key];
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/utils/logger.ts
|
|
566
|
+
import chalk from "chalk";
|
|
567
|
+
import ora from "ora";
|
|
568
|
+
var logger = {
|
|
569
|
+
success: (msg) => console.log(chalk.green("\u2713") + " " + msg),
|
|
570
|
+
error: (msg) => console.log(chalk.red("\u2717") + " " + msg),
|
|
571
|
+
warn: (msg) => console.log(chalk.yellow("\u26A0") + " " + msg),
|
|
572
|
+
info: (msg) => console.log(chalk.cyan("\u2192") + " " + msg),
|
|
573
|
+
step: (msg) => console.log(chalk.dim(msg)),
|
|
574
|
+
blank: () => console.log()
|
|
575
|
+
};
|
|
576
|
+
function createSpinner(text) {
|
|
577
|
+
return ora({ text, color: "cyan" });
|
|
578
|
+
}
|
|
579
|
+
function printBanner(subtitle) {
|
|
580
|
+
console.log(chalk.cyan(`
|
|
581
|
+
____ _ _ _ _ _ ____ _
|
|
582
|
+
/ ___| / \\ | \\ | | \\ | | __ ) ___ | |_
|
|
583
|
+
| | / _ \\ | \\| | \\| | _ \\ / _ \\| __|
|
|
584
|
+
| |___ / ___ \\| |\\ | |\\ | |_) | (_) | |_
|
|
585
|
+
\\____/_/ \\_\\_| \\_|_| \\_|____/ \\___/ \\__|
|
|
586
|
+
`));
|
|
587
|
+
if (subtitle) {
|
|
588
|
+
console.log(chalk.bold(` ${subtitle}`));
|
|
589
|
+
console.log();
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function getDisplayWidth(str) {
|
|
593
|
+
let width = 0;
|
|
594
|
+
for (const ch of str) {
|
|
595
|
+
const code = ch.codePointAt(0);
|
|
596
|
+
if (code >= 11904 && code <= 40959 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65103 || code >= 65280 && code <= 65519) {
|
|
597
|
+
width += 2;
|
|
598
|
+
} else {
|
|
599
|
+
width += 1;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return width;
|
|
603
|
+
}
|
|
604
|
+
function printBoxTitle(title, width = 65) {
|
|
605
|
+
const border = "\u2550".repeat(width);
|
|
606
|
+
const displayWidth = getDisplayWidth(title);
|
|
607
|
+
const totalPadding = width - displayWidth;
|
|
608
|
+
const leftPadding = Math.floor(totalPadding / 2);
|
|
609
|
+
const rightPadding = totalPadding - leftPadding;
|
|
610
|
+
const paddedTitle = " ".repeat(Math.max(0, leftPadding)) + title + " ".repeat(Math.max(0, rightPadding));
|
|
611
|
+
console.log();
|
|
612
|
+
console.log(chalk.cyan.bold(` \u2554${border}\u2557`));
|
|
613
|
+
console.log(chalk.cyan.bold(` \u2551`) + chalk.cyan.bold(paddedTitle) + chalk.cyan.bold(`\u2551`));
|
|
614
|
+
console.log(chalk.cyan.bold(` \u255A${border}\u255D`));
|
|
615
|
+
console.log();
|
|
616
|
+
}
|
|
617
|
+
function showOperationHints(isCheckbox = false) {
|
|
618
|
+
if (isCheckbox) {
|
|
619
|
+
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
|
|
620
|
+
`);
|
|
621
|
+
} else {
|
|
622
|
+
console.log(` \u{1F4A1} \x1B[36m\u2191\u2193\x1B[0m \u79FB\u52A8 | \x1B[36m\u23CE\x1B[0m \u786E\u8BA4
|
|
623
|
+
`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// src/utils/config.ts
|
|
628
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
629
|
+
import { join as join4 } from "path";
|
|
630
|
+
import { homedir as homedir3 } from "os";
|
|
631
|
+
import { parse, stringify } from "yaml";
|
|
632
|
+
function getConfigDir() {
|
|
633
|
+
return join4(homedir3(), ".cannbot");
|
|
634
|
+
}
|
|
635
|
+
function getConfigPath() {
|
|
636
|
+
return join4(getConfigDir(), "config.yaml");
|
|
637
|
+
}
|
|
638
|
+
function readConfig() {
|
|
639
|
+
const configPath = getConfigPath();
|
|
640
|
+
if (!existsSync4(configPath)) {
|
|
641
|
+
return {
|
|
642
|
+
language: "zh_CN",
|
|
643
|
+
installedPlugins: []
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
const content = readFileSync2(configPath, "utf-8");
|
|
648
|
+
const parsed = parse(content);
|
|
649
|
+
return {
|
|
650
|
+
language: parsed.language || "zh_CN",
|
|
651
|
+
lastTool: parsed.lastTool,
|
|
652
|
+
lastLevel: parsed.lastLevel,
|
|
653
|
+
repoPath: parsed.repoPath,
|
|
654
|
+
installedPlugins: parsed.installedPlugins || []
|
|
655
|
+
};
|
|
656
|
+
} catch {
|
|
657
|
+
return {
|
|
658
|
+
language: "zh_CN",
|
|
659
|
+
installedPlugins: []
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
function writeConfig(config) {
|
|
664
|
+
const configDir = getConfigDir();
|
|
665
|
+
if (!existsSync4(configDir)) {
|
|
666
|
+
mkdirSync(configDir, { recursive: true });
|
|
667
|
+
}
|
|
668
|
+
const configPath = getConfigPath();
|
|
669
|
+
const content = stringify(config);
|
|
670
|
+
writeFileSync(configPath, content, "utf-8");
|
|
671
|
+
}
|
|
672
|
+
function updateConfig(updates) {
|
|
673
|
+
const config = readConfig();
|
|
674
|
+
const updated = { ...config, ...updates };
|
|
675
|
+
writeConfig(updated);
|
|
676
|
+
return updated;
|
|
677
|
+
}
|
|
678
|
+
function addInstalledPlugin(pluginId) {
|
|
679
|
+
const config = readConfig();
|
|
680
|
+
if (!config.installedPlugins.includes(pluginId)) {
|
|
681
|
+
config.installedPlugins.push(pluginId);
|
|
682
|
+
writeConfig(config);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function removeInstalledPlugin(pluginId) {
|
|
686
|
+
const config = readConfig();
|
|
687
|
+
config.installedPlugins = config.installedPlugins.filter(
|
|
688
|
+
(id) => id !== pluginId
|
|
689
|
+
);
|
|
690
|
+
writeConfig(config);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/ui/theme.ts
|
|
694
|
+
var selectTheme = {
|
|
695
|
+
helpMode: "never"
|
|
696
|
+
};
|
|
697
|
+
var checkboxTheme = {
|
|
698
|
+
icon: {
|
|
699
|
+
checked: "\x1B[32m\u2611\x1B[22m",
|
|
700
|
+
unchecked: "\u2610",
|
|
701
|
+
cursor: "\u276F"
|
|
702
|
+
},
|
|
703
|
+
helpMode: "never"
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
// src/ui/wizard.ts
|
|
707
|
+
var BACK = "__back__";
|
|
708
|
+
var CANCEL = "__cancel__";
|
|
709
|
+
async function selectToolWithDetection() {
|
|
710
|
+
const config = readConfig();
|
|
711
|
+
logger.blank();
|
|
712
|
+
const spinner = createSpinner(t("wizard_detect"));
|
|
713
|
+
spinner.start();
|
|
714
|
+
const detectedTools = await detectTools();
|
|
715
|
+
spinner.stop();
|
|
716
|
+
const result = await stepTool(detectedTools, config.lastTool);
|
|
717
|
+
if (result === BACK) return "back";
|
|
718
|
+
if (result === CANCEL) return "cancel";
|
|
719
|
+
return result;
|
|
720
|
+
}
|
|
721
|
+
async function runWizard() {
|
|
722
|
+
const config = readConfig();
|
|
723
|
+
logger.blank();
|
|
724
|
+
const spinner = createSpinner(t("wizard_detect"));
|
|
725
|
+
spinner.start();
|
|
726
|
+
const detectedTools = await detectTools();
|
|
727
|
+
spinner.stop();
|
|
728
|
+
let selectedTool;
|
|
729
|
+
let level = "project";
|
|
730
|
+
let plugins = [];
|
|
731
|
+
let step = 0;
|
|
732
|
+
while (true) {
|
|
733
|
+
switch (step) {
|
|
734
|
+
case 0: {
|
|
735
|
+
const result = await stepTool(detectedTools, config.lastTool);
|
|
736
|
+
if (result === BACK) {
|
|
737
|
+
return { language: config.language || "zh_CN", tool: "opencode", level: "project", plugins: [], confirmed: false, back: true };
|
|
738
|
+
}
|
|
739
|
+
if (result === CANCEL) {
|
|
740
|
+
return { language: config.language || "zh_CN", tool: "opencode", level: "project", plugins: [], confirmed: false };
|
|
741
|
+
}
|
|
742
|
+
selectedTool = result;
|
|
743
|
+
step = 1;
|
|
744
|
+
break;
|
|
745
|
+
}
|
|
746
|
+
case 1: {
|
|
747
|
+
const result = await stepLevel(config.lastLevel);
|
|
748
|
+
if (result === BACK) {
|
|
749
|
+
step = 0;
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
if (result === CANCEL) {
|
|
753
|
+
return { language: config.language || "zh_CN", tool: selectedTool, level: "project", plugins: [], confirmed: false };
|
|
754
|
+
}
|
|
755
|
+
level = result;
|
|
756
|
+
step = 2;
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
case 2: {
|
|
760
|
+
const result = await stepPlugins(selectedTool, level);
|
|
761
|
+
if (result === BACK) {
|
|
762
|
+
step = 1;
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
765
|
+
if (result === CANCEL) {
|
|
766
|
+
return { language: config.language || "zh_CN", tool: selectedTool, level, plugins: [], confirmed: false };
|
|
767
|
+
}
|
|
768
|
+
plugins = result;
|
|
769
|
+
step = 3;
|
|
770
|
+
break;
|
|
771
|
+
}
|
|
772
|
+
case 3: {
|
|
773
|
+
const result = await stepConfirm(selectedTool, level, plugins);
|
|
774
|
+
if (result === BACK) {
|
|
775
|
+
step = 2;
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
if (result === CANCEL || result === false) {
|
|
779
|
+
return { language: config.language || "zh_CN", tool: selectedTool, level, plugins: [], confirmed: false };
|
|
780
|
+
}
|
|
781
|
+
updateConfig({ lastTool: selectedTool, lastLevel: level });
|
|
782
|
+
return { language: config.language || "zh_CN", tool: selectedTool, level, plugins, confirmed: true };
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
async function stepTool(detectedTools, lastTool) {
|
|
788
|
+
printBoxTitle("\u9009\u62E9 AI \u7F16\u7A0B\u5DE5\u5177");
|
|
789
|
+
if (detectedTools.length === 0) {
|
|
790
|
+
logger.error(t("error_tool_not_found"));
|
|
791
|
+
const choices2 = [
|
|
792
|
+
...["opencode", "claude", "trae", "cursor", "copilot"].map((tool) => ({
|
|
793
|
+
name: `> ${getToolDisplayName(tool)}`,
|
|
794
|
+
value: tool
|
|
795
|
+
})),
|
|
796
|
+
new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
797
|
+
{ name: "<- " + t("wizard_back"), value: BACK },
|
|
798
|
+
{ name: "x " + t("wizard_cancel"), value: CANCEL }
|
|
799
|
+
];
|
|
800
|
+
showOperationHints();
|
|
801
|
+
const result2 = await select({ message: t("wizard_select_tool"), choices: choices2, default: lastTool, loop: false, theme: selectTheme });
|
|
802
|
+
if (result2 === BACK) return BACK;
|
|
803
|
+
if (result2 === CANCEL) return CANCEL;
|
|
804
|
+
return result2;
|
|
805
|
+
}
|
|
806
|
+
if (detectedTools.length === 1) {
|
|
807
|
+
const tool = detectedTools[0];
|
|
808
|
+
const isLastUsed = lastTool === tool.name;
|
|
809
|
+
const suffix = isLastUsed ? ` [${t("wizard_last_used")}]` : "";
|
|
810
|
+
logger.success(
|
|
811
|
+
`${t("wizard_detected_tool")}: ${getToolDisplayName(tool.name)}${tool.version ? ` (v${tool.version})` : ""}${suffix}`
|
|
812
|
+
);
|
|
813
|
+
const choices2 = [
|
|
814
|
+
{ name: `> ${t("wizard_confirm_tool")} ${getToolDisplayName(tool.name)}`, value: tool.name },
|
|
815
|
+
{ name: `> ${t("wizard_select_tool")}`, value: "manual" },
|
|
816
|
+
new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
817
|
+
{ name: "<- " + t("wizard_back"), value: BACK },
|
|
818
|
+
{ name: "x " + t("wizard_cancel"), value: CANCEL }
|
|
819
|
+
];
|
|
820
|
+
showOperationHints();
|
|
821
|
+
const result2 = await select({ message: t("wizard_select_tool"), choices: choices2, loop: false, theme: selectTheme });
|
|
822
|
+
if (result2 === BACK) return BACK;
|
|
823
|
+
if (result2 === CANCEL) return CANCEL;
|
|
824
|
+
if (result2 === "manual") {
|
|
825
|
+
return stepToolManual(lastTool);
|
|
826
|
+
}
|
|
827
|
+
return tool.name;
|
|
828
|
+
}
|
|
829
|
+
const choices = detectedTools.map((tool) => {
|
|
830
|
+
const isLastUsed = lastTool === tool.name;
|
|
831
|
+
const suffix = isLastUsed ? ` [${t("wizard_last_used")}]` : "";
|
|
832
|
+
return {
|
|
833
|
+
name: `> ${getToolDisplayName(tool.name)}${tool.version ? ` (v${tool.version})` : ""}${suffix}`,
|
|
834
|
+
value: tool.name
|
|
835
|
+
};
|
|
836
|
+
});
|
|
837
|
+
choices.push(new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
838
|
+
choices.push({ name: "<- " + t("wizard_back"), value: BACK });
|
|
839
|
+
choices.push({ name: "x " + t("wizard_cancel"), value: CANCEL });
|
|
840
|
+
showOperationHints();
|
|
841
|
+
const result = await select({ message: t("wizard_select_tool"), choices, default: lastTool, loop: false, theme: selectTheme });
|
|
842
|
+
if (result === BACK) return BACK;
|
|
843
|
+
if (result === CANCEL) return CANCEL;
|
|
844
|
+
return result;
|
|
845
|
+
}
|
|
846
|
+
async function stepToolManual(lastTool) {
|
|
847
|
+
const choices = [
|
|
848
|
+
...["opencode", "claude", "trae", "cursor", "copilot"].map((tool) => ({
|
|
849
|
+
name: `> ${getToolDisplayName(tool)}`,
|
|
850
|
+
value: tool
|
|
851
|
+
})),
|
|
852
|
+
new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
853
|
+
{ name: "<- " + t("wizard_back"), value: BACK },
|
|
854
|
+
{ name: "x " + t("wizard_cancel"), value: CANCEL }
|
|
855
|
+
];
|
|
856
|
+
showOperationHints();
|
|
857
|
+
const result = await select({ message: t("wizard_select_tool"), choices, default: lastTool, loop: false, theme: selectTheme });
|
|
858
|
+
if (result === BACK) return BACK;
|
|
859
|
+
if (result === CANCEL) return CANCEL;
|
|
860
|
+
return result;
|
|
861
|
+
}
|
|
862
|
+
async function stepLevel(lastLevel) {
|
|
863
|
+
printBoxTitle("\u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E");
|
|
864
|
+
const choices = [
|
|
865
|
+
{ name: "> project \u2014 \u5B89\u88C5\u5230\u5F53\u524D\u76EE\u5F55\uFF08\u63A8\u8350\uFF0C\u4EC5\u5F53\u524D\u9879\u76EE\u53EF\u7528\uFF09", value: "project" },
|
|
866
|
+
{ name: "> global \u2014 \u5B89\u88C5\u5230\u5168\u5C40\uFF08\u6240\u6709\u9879\u76EE\u5171\u4EAB\uFF09", value: "global" },
|
|
867
|
+
new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
868
|
+
{ name: "<- " + t("wizard_back"), value: BACK },
|
|
869
|
+
{ name: "x " + t("wizard_cancel"), value: CANCEL }
|
|
870
|
+
];
|
|
871
|
+
showOperationHints();
|
|
872
|
+
const result = await select({
|
|
873
|
+
message: t("wizard_select_level"),
|
|
874
|
+
choices,
|
|
875
|
+
default: lastLevel || "project",
|
|
876
|
+
loop: false,
|
|
877
|
+
theme: selectTheme
|
|
878
|
+
});
|
|
879
|
+
if (result === BACK) return BACK;
|
|
880
|
+
if (result === CANCEL) return CANCEL;
|
|
881
|
+
return result;
|
|
882
|
+
}
|
|
883
|
+
async function stepPlugins(tool, level) {
|
|
884
|
+
printBoxTitle("\u9009\u62E9\u8981\u5B89\u88C5\u7684\u63D2\u4EF6");
|
|
885
|
+
const plugins = getAllPlugins();
|
|
886
|
+
const configRoot = getConfigRoot(tool, level);
|
|
887
|
+
const manifests = readAllManifests(configRoot);
|
|
888
|
+
const installedSet = new Set(manifests.map((m) => m.team));
|
|
889
|
+
const choices = plugins.map((p) => {
|
|
890
|
+
const isInstalled = installedSet.has(p.id);
|
|
891
|
+
const suffix = isInstalled ? ` [${t("wizard_already_installed")}]` : "";
|
|
892
|
+
return {
|
|
893
|
+
name: `${p.displayName}${suffix} (${p.skills} skills, ${p.agents} agents)`,
|
|
894
|
+
value: p.id,
|
|
895
|
+
checked: false
|
|
896
|
+
};
|
|
897
|
+
});
|
|
898
|
+
choices.push(new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
899
|
+
choices.push({ name: "<- " + t("wizard_back"), value: BACK, checked: false });
|
|
900
|
+
choices.push({ name: "x " + t("wizard_cancel"), value: CANCEL, checked: false });
|
|
901
|
+
showOperationHints(true);
|
|
902
|
+
const selected = await checkbox({
|
|
903
|
+
message: t("wizard_select_plugins"),
|
|
904
|
+
choices,
|
|
905
|
+
loop: false,
|
|
906
|
+
instructions: false,
|
|
907
|
+
theme: checkboxTheme
|
|
908
|
+
});
|
|
909
|
+
if (selected.includes(BACK)) return BACK;
|
|
910
|
+
if (selected.includes(CANCEL)) return CANCEL;
|
|
911
|
+
const pluginIds = selected.filter((v) => v !== BACK && v !== CANCEL);
|
|
912
|
+
if (pluginIds.length === 0) {
|
|
913
|
+
const action = await select({
|
|
914
|
+
message: t("wizard_no_selection"),
|
|
915
|
+
choices: [
|
|
916
|
+
new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
917
|
+
{ name: "<- " + t("wizard_back_to_reselect"), value: "back" },
|
|
918
|
+
{ name: "x " + t("wizard_cancel"), value: "cancel" }
|
|
919
|
+
],
|
|
920
|
+
loop: false,
|
|
921
|
+
theme: selectTheme
|
|
922
|
+
});
|
|
923
|
+
if (action === "back") return BACK;
|
|
924
|
+
return CANCEL;
|
|
925
|
+
}
|
|
926
|
+
return pluginIds;
|
|
927
|
+
}
|
|
928
|
+
async function stepConfirm(tool, level, plugins) {
|
|
929
|
+
printBoxTitle("\u786E\u8BA4\u5B89\u88C5");
|
|
930
|
+
const allPlugins = getAllPlugins();
|
|
931
|
+
const selectedPlugins = plugins.map((id) => allPlugins.find((p) => p.id === id));
|
|
932
|
+
logger.info(`\u5373\u5C06\u5B89\u88C5 ${chalk2.bold(plugins.length)} \u4E2A\u573A\u666F\u5230 ${chalk2.cyan("." + tool + "/")}\uFF1A`);
|
|
933
|
+
for (const plugin of selectedPlugins) {
|
|
934
|
+
if (plugin) {
|
|
935
|
+
logger.step(` \u2022 ${chalk2.bold(plugin.displayName)} ${chalk2.dim(`(${plugin.skills} skills, ${plugin.agents} agents)`)}`);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
logger.blank();
|
|
939
|
+
const choices = [
|
|
940
|
+
{ name: "> " + t("wizard_confirm"), value: "confirm" },
|
|
941
|
+
new Separator("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
942
|
+
{ name: "<- " + t("wizard_back"), value: BACK },
|
|
943
|
+
{ name: "x " + t("wizard_cancel"), value: CANCEL }
|
|
944
|
+
];
|
|
945
|
+
showOperationHints();
|
|
946
|
+
const result = await select({ message: t("wizard_confirm"), choices, loop: false, theme: selectTheme });
|
|
947
|
+
if (result === BACK) return BACK;
|
|
948
|
+
if (result === CANCEL) return CANCEL;
|
|
949
|
+
return true;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// src/core/repository.ts
|
|
953
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
954
|
+
import { join as join6 } from "path";
|
|
955
|
+
import { execa as execa2 } from "execa";
|
|
956
|
+
|
|
957
|
+
// src/core/scanner.ts
|
|
958
|
+
import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
959
|
+
import { join as join5, dirname } from "path";
|
|
960
|
+
import { parse as parseYaml } from "yaml";
|
|
961
|
+
var EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
962
|
+
"node_modules",
|
|
963
|
+
".git",
|
|
964
|
+
"asc-devkit",
|
|
965
|
+
"cann-samples",
|
|
966
|
+
"tilelang-ascend",
|
|
967
|
+
".agents",
|
|
968
|
+
".opencode",
|
|
969
|
+
".claude",
|
|
970
|
+
"dist",
|
|
971
|
+
"build"
|
|
972
|
+
]);
|
|
973
|
+
var SKILL_SCAN_DIRS = [
|
|
974
|
+
"ops",
|
|
975
|
+
"model",
|
|
976
|
+
"graph",
|
|
977
|
+
"infra",
|
|
978
|
+
"ops-lab"
|
|
979
|
+
];
|
|
980
|
+
var PLUGIN_SCAN_DIRS = [
|
|
981
|
+
"plugins-official",
|
|
982
|
+
"plugins-community"
|
|
983
|
+
];
|
|
984
|
+
function scanSkills(repoPath) {
|
|
985
|
+
const skills = [];
|
|
986
|
+
const seen = /* @__PURE__ */ new Set();
|
|
987
|
+
for (const dir of SKILL_SCAN_DIRS) {
|
|
988
|
+
const fullPath = join5(repoPath, dir);
|
|
989
|
+
if (!existsSync5(fullPath)) continue;
|
|
990
|
+
scanDirectory(fullPath, repoPath, dir, skills, seen);
|
|
991
|
+
}
|
|
992
|
+
for (const pluginDir of PLUGIN_SCAN_DIRS) {
|
|
993
|
+
const pluginParent = join5(repoPath, pluginDir);
|
|
994
|
+
if (!existsSync5(pluginParent)) continue;
|
|
995
|
+
for (const plugin of safeReaddir(pluginParent)) {
|
|
996
|
+
const pluginPath = join5(pluginParent, plugin);
|
|
997
|
+
if (!isDirectory(pluginPath)) continue;
|
|
998
|
+
const skillsDir = join5(pluginPath, "skills");
|
|
999
|
+
if (existsSync5(skillsDir)) {
|
|
1000
|
+
scanDirectory(skillsDir, repoPath, `${pluginDir}/${plugin}/skills`, skills, seen);
|
|
1001
|
+
}
|
|
1002
|
+
const skillDir = join5(pluginPath, "skill");
|
|
1003
|
+
if (existsSync5(skillDir)) {
|
|
1004
|
+
scanDirectory(skillDir, repoPath, `${pluginDir}/${plugin}/skill`, skills, seen);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return skills;
|
|
1009
|
+
}
|
|
1010
|
+
function parseFrontmatter(filePath) {
|
|
1011
|
+
try {
|
|
1012
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
1013
|
+
const lines = content.split("\n");
|
|
1014
|
+
if (lines.length < 3) return null;
|
|
1015
|
+
if (lines[0].trim() !== "---") return null;
|
|
1016
|
+
let endIndex = -1;
|
|
1017
|
+
for (let i = 1; i < lines.length; i++) {
|
|
1018
|
+
if (lines[i].trim() === "---") {
|
|
1019
|
+
endIndex = i;
|
|
1020
|
+
break;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (endIndex === -1) return null;
|
|
1024
|
+
const yamlBlock = lines.slice(1, endIndex).join("\n").replace(/\r$/, "");
|
|
1025
|
+
const parsed = parseYaml(yamlBlock);
|
|
1026
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
1027
|
+
if (!parsed.name || typeof parsed.name !== "string") return null;
|
|
1028
|
+
return {
|
|
1029
|
+
name: parsed.name.trim(),
|
|
1030
|
+
description: (parsed.description || "").trim()
|
|
1031
|
+
};
|
|
1032
|
+
} catch {
|
|
1033
|
+
return null;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
function getCurrentCommit(repoPath) {
|
|
1037
|
+
try {
|
|
1038
|
+
const headFile = join5(repoPath, ".git", "HEAD");
|
|
1039
|
+
if (!existsSync5(headFile)) return "unknown";
|
|
1040
|
+
const headContent = readFileSync3(headFile, "utf-8").trim();
|
|
1041
|
+
if (headContent.startsWith("ref: ")) {
|
|
1042
|
+
const refPath = join5(repoPath, ".git", headContent.slice(5));
|
|
1043
|
+
if (existsSync5(refPath)) {
|
|
1044
|
+
return readFileSync3(refPath, "utf-8").trim();
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return headContent;
|
|
1048
|
+
} catch {
|
|
1049
|
+
return "unknown";
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
function readScanCache() {
|
|
1053
|
+
try {
|
|
1054
|
+
const cachePath = getCachePath();
|
|
1055
|
+
if (!existsSync5(cachePath)) return null;
|
|
1056
|
+
const content = readFileSync3(cachePath, "utf-8");
|
|
1057
|
+
const cache = JSON.parse(content);
|
|
1058
|
+
if (!cache.skills || !cache.timestamp) return null;
|
|
1059
|
+
const age = Date.now() - cache.timestamp;
|
|
1060
|
+
if (age > 24 * 60 * 60 * 1e3) return null;
|
|
1061
|
+
return cache;
|
|
1062
|
+
} catch {
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
function writeScanCache(cache) {
|
|
1067
|
+
try {
|
|
1068
|
+
const cachePath = getCachePath();
|
|
1069
|
+
const cacheDir = dirname(cachePath);
|
|
1070
|
+
if (!existsSync5(cacheDir)) {
|
|
1071
|
+
mkdirSync2(cacheDir, { recursive: true });
|
|
1072
|
+
}
|
|
1073
|
+
writeFileSync2(cachePath, JSON.stringify(cache, null, 2));
|
|
1074
|
+
} catch {
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
function getCachePath() {
|
|
1078
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
1079
|
+
return join5(homeDir, ".cannbot", "scan-cache.json");
|
|
1080
|
+
}
|
|
1081
|
+
function scanDirectory(dirPath, repoPath, sourcePrefix, skills, seen) {
|
|
1082
|
+
for (const entry of safeReaddir(dirPath)) {
|
|
1083
|
+
const fullPath = join5(dirPath, entry);
|
|
1084
|
+
if (EXCLUDE_DIRS.has(entry)) continue;
|
|
1085
|
+
if (!isDirectory(fullPath)) continue;
|
|
1086
|
+
const skillMd = join5(fullPath, "SKILL.md");
|
|
1087
|
+
if (existsSync5(skillMd)) {
|
|
1088
|
+
const frontmatter = parseFrontmatter(skillMd);
|
|
1089
|
+
if (frontmatter && !seen.has(frontmatter.name)) {
|
|
1090
|
+
seen.add(frontmatter.name);
|
|
1091
|
+
skills.push({
|
|
1092
|
+
id: frontmatter.name,
|
|
1093
|
+
description: frontmatter.description,
|
|
1094
|
+
source: sourcePrefix,
|
|
1095
|
+
filePath: skillMd
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
scanDirectory(fullPath, repoPath, sourcePrefix, skills, seen);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
function safeReaddir(dir) {
|
|
1103
|
+
try {
|
|
1104
|
+
return readdirSync(dir);
|
|
1105
|
+
} catch {
|
|
1106
|
+
return [];
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
function isDirectory(path) {
|
|
1110
|
+
try {
|
|
1111
|
+
return statSync(path).isDirectory();
|
|
1112
|
+
} catch {
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// src/core/skill-registry.ts
|
|
1118
|
+
var CATEGORY_DEFS = [
|
|
1119
|
+
{ id: "knowledge", name: "\u77E5\u8BC6\u4E0E\u53C2\u8003" },
|
|
1120
|
+
{ id: "env-tools", name: "\u73AF\u5883\u4E0E\u5DE5\u5177" },
|
|
1121
|
+
{ id: "debug", name: "\u8C03\u8BD5\u4E0E\u8BCA\u65AD" },
|
|
1122
|
+
{ id: "testing", name: "\u6D4B\u8BD5\u4E0E\u8D28\u91CF" },
|
|
1123
|
+
{ id: "ascendc", name: "AscendC \u5F00\u53D1" },
|
|
1124
|
+
{ id: "pypto", name: "PyPTO \u5F00\u53D1" },
|
|
1125
|
+
{ id: "tilelang", name: "TileLang \u5F00\u53D1" },
|
|
1126
|
+
{ id: "triton", name: "Triton \u5F00\u53D1" },
|
|
1127
|
+
{ id: "model", name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316" },
|
|
1128
|
+
{ id: "graph", name: "\u56FE\u6A21\u5F0F" },
|
|
1129
|
+
{ id: "platform", name: "\u5E73\u53F0\u5DE5\u5177" },
|
|
1130
|
+
{ id: "other", name: "\u5176\u4ED6 Skills" }
|
|
1131
|
+
];
|
|
1132
|
+
var dynamicSkills = [];
|
|
1133
|
+
var initialized = false;
|
|
1134
|
+
function initFromScan(scanned) {
|
|
1135
|
+
dynamicSkills = scanned.map((s) => ({
|
|
1136
|
+
id: s.id,
|
|
1137
|
+
description: s.description,
|
|
1138
|
+
source: s.source
|
|
1139
|
+
}));
|
|
1140
|
+
initialized = true;
|
|
1141
|
+
}
|
|
1142
|
+
var STATIC_SKILL_CATEGORIES = [
|
|
1143
|
+
{
|
|
1144
|
+
id: "knowledge",
|
|
1145
|
+
name: "\u77E5\u8BC6\u4E0E\u53C2\u8003",
|
|
1146
|
+
skills: [
|
|
1147
|
+
{ id: "npu-arch", description: "NPU \u67B6\u6784\u77E5\u8BC6\u3001\u82AF\u7247\u578B\u53F7\u6620\u5C04", source: "ops" },
|
|
1148
|
+
{ id: "ascendc-api-best-practices", description: "API \u4F7F\u7528\u6700\u4F73\u5B9E\u8DF5", source: "ops" },
|
|
1149
|
+
{ id: "ascendc-tiling-design", description: "Tiling \u548C Kernel \u8BBE\u8BA1\u65B9\u6CD5\u8BBA", source: "ops" },
|
|
1150
|
+
{ id: "ascendc-docs-search", description: "API \u6587\u6863\u7D22\u5F15 + \u5728\u7EBF\u641C\u7D22", source: "ops" },
|
|
1151
|
+
{ id: "ascendc-docs-gen", description: "\u7B97\u5B50\u6587\u6863\u5199\u4F5C\u53C2\u8003", source: "ops" },
|
|
1152
|
+
{ id: "ascendc-performance-best-practices", description: "\u6027\u80FD\u4F18\u5316\u7ECF\u9A8C\u603B\u7ED3", source: "ops" },
|
|
1153
|
+
{ id: "ascendc-regbase-best-practice", description: "RegBase \u7B97\u5B50 API \u7EA6\u675F", source: "ops" },
|
|
1154
|
+
{ id: "ops-precision-standard", description: "\u7B97\u5B50\u7CBE\u5EA6\u6807\u51C6\uFF08atol/rtol\uFF09", source: "ops" },
|
|
1155
|
+
{ id: "ops-spec-gen", description: "\u7B97\u5B50 spec.yaml \u751F\u6210\u4E0E\u6821\u9A8C", source: "ops" }
|
|
1156
|
+
]
|
|
1157
|
+
},
|
|
1158
|
+
{
|
|
1159
|
+
id: "env-tools",
|
|
1160
|
+
name: "\u73AF\u5883\u4E0E\u5DE5\u5177",
|
|
1161
|
+
skills: [
|
|
1162
|
+
{ id: "ascendc-env-check", description: "NPU \u8BBE\u5907\u67E5\u8BE2\u3001CANN \u73AF\u5883\u9A8C\u8BC1", source: "ops" },
|
|
1163
|
+
{ id: "cann-env-setup", description: "CANN \u5B89\u88C5\u4E0E\u73AF\u5883\u914D\u7F6E\u6307\u5BFC", source: "ops" },
|
|
1164
|
+
{ id: "ops-profiling", description: "NPU \u6027\u80FD\u91C7\u96C6\u4E0E\u5206\u6790", source: "ops" },
|
|
1165
|
+
{ id: "ops-simulator", description: "CANN Simulator \u7CBE\u5EA6/\u6027\u80FD\u4EFF\u771F", source: "ops" },
|
|
1166
|
+
{ id: "torch-ops-profiler", description: "torch_npu.profiler \u6027\u80FD\u62A5\u544A", source: "ops" },
|
|
1167
|
+
{ id: "aiss-tiling-solver", description: "AISS-TilingSolver \u81EA\u52A8\u6C42\u89E3 Tiling \u53C2\u6570", source: "ops" }
|
|
1168
|
+
]
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
id: "debug",
|
|
1172
|
+
name: "\u8C03\u8BD5\u4E0E\u8BCA\u65AD",
|
|
1173
|
+
skills: [
|
|
1174
|
+
{ id: "ascendc-precision-debug", description: "\u7CBE\u5EA6\u8C03\u8BD5\u3001\u75C7\u72B6-\u539F\u56E0\u901F\u67E5", source: "ops" },
|
|
1175
|
+
{ id: "ascendc-runtime-debug", description: "\u8FD0\u884C\u65F6\u9519\u8BEF\u7801\u89E3\u6790", source: "ops" },
|
|
1176
|
+
{ id: "ascendc-crash-debug", description: "\u5361\u6B7B/\u5D29\u6E83\u8C03\u8BD5\u3001Coredump \u5206\u6790", source: "ops" },
|
|
1177
|
+
{ id: "ascendc-perf-optimize", description: "\u6027\u80FD\u4F18\u5316\u7B56\u7565\u5236\u5B9A", source: "ops" },
|
|
1178
|
+
{ id: "model-infer-precision-debug", description: "NPU \u63A8\u7406\u7CBE\u5EA6\u8BCA\u65AD", source: "model" },
|
|
1179
|
+
{ id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" },
|
|
1180
|
+
{ id: "torch-npugraph-ex-dfx-triage", description: "npugraph_ex DFX \u95EE\u9898\u5206\u8BCA", source: "graph" },
|
|
1181
|
+
{ id: "torch-npugraph-ex-compile-error-diagnosis", description: "npugraph_ex \u7F16\u8BD1\u671F\u62A5\u9519\u8BCA\u65AD", source: "graph" }
|
|
1182
|
+
]
|
|
1183
|
+
},
|
|
1184
|
+
{
|
|
1185
|
+
id: "testing",
|
|
1186
|
+
name: "\u6D4B\u8BD5\u4E0E\u8D28\u91CF",
|
|
1187
|
+
skills: [
|
|
1188
|
+
{ id: "ascendc-code-review", description: "\u4EE3\u7801\u68C0\u89C6\u65B9\u6CD5\u8BBA", source: "ops" },
|
|
1189
|
+
{ id: "ascendc-ut-develop", description: "UT \u5F00\u53D1\u4E0E\u8986\u76D6\u7387\u589E\u5F3A", source: "ops" },
|
|
1190
|
+
{ id: "ascendc-st-design", description: "ST \u6D4B\u8BD5\u7528\u4F8B\u8BBE\u8BA1", source: "ops" },
|
|
1191
|
+
{ id: "ascendc-whitebox-design", description: "\u767D\u76D2\u6D4B\u8BD5\u7528\u4F8B\u751F\u6210", source: "ops" },
|
|
1192
|
+
{ id: "ascendc-task-focus", description: "\u957F\u4EFB\u52A1\u805A\u7126\u9632\u8FF7\u5931", source: "ops" },
|
|
1193
|
+
{ id: "tilelang-op-test-design", description: "TileLang \u6D4B\u8BD5\u8BBE\u8BA1", source: "ops" },
|
|
1194
|
+
{ id: "tilelang-review", description: "TileLang \u4EE3\u7801\u683C\u5F0F\u68C0\u67E5", source: "ops" },
|
|
1195
|
+
{ id: "triton-op-verifier", description: "Triton \u7B97\u5B50\u9A8C\u8BC1", source: "ops" },
|
|
1196
|
+
{ id: "cannbot-skill-reviewer", description: "Skill \u5165\u5E93\u8D28\u91CF\u5BA1\u67E5", source: "infra" }
|
|
1197
|
+
]
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
id: "ascendc",
|
|
1201
|
+
name: "AscendC \u5F00\u53D1",
|
|
1202
|
+
skills: [
|
|
1203
|
+
{ id: "ascendc-direct-invoke-template", description: "Kernel \u76F4\u8C03\u5DE5\u7A0B\u6A21\u677F", source: "ops" },
|
|
1204
|
+
{ id: "ascendc-registry-invoke-template", description: "\u81EA\u5B9A\u4E49\u7B97\u5B50\u5DE5\u7A0B\u6A21\u677F", source: "ops" },
|
|
1205
|
+
{ id: "ascendc-direct-invoke-to-registry-invoke", description: "\u76F4\u8C03\u8F6C\u6CE8\u518C\u8C03\u7528", source: "ops" },
|
|
1206
|
+
{ id: "ascendc-registry-invoke-to-direct-invoke", description: "\u6CE8\u518C\u8C03\u7528\u8F6C\u76F4\u8C03", source: "ops" },
|
|
1207
|
+
{ id: "ascendc-blaze-best-practice", description: "Matmul/GEMM Blaze \u76F4\u8C03\u751F\u6210", source: "ops" },
|
|
1208
|
+
{ id: "ascendc-simt-best-practices", description: "SIMT \u6700\u4F73\u5B9E\u8DF5\u4E0E API \u5BFC\u822A", source: "ops" },
|
|
1209
|
+
{ id: "ascendc-simt-tiling-design", description: "SIMT \u7B97\u5B50\u5207\u5206\u8BBE\u8BA1", source: "ops" },
|
|
1210
|
+
{ id: "torch-ascendc-op-extension", description: "Ascend C \u5BF9\u63A5 PyTorch", source: "ops" },
|
|
1211
|
+
{ id: "catlass-op-design", description: "Catlass \u7B97\u5B50\u8BBE\u8BA1", source: "ops" },
|
|
1212
|
+
{ id: "catlass-op-develop", description: "Catlass \u7B97\u5B50\u5F00\u53D1", source: "ops" },
|
|
1213
|
+
{ id: "catlass-op-perf-tune", description: "Catlass \u6027\u80FD\u8C03\u4F18", source: "ops" },
|
|
1214
|
+
{ id: "cuda2ascend-simt", description: "CUDA \u8FC1\u79FB\u5230 Ascend C SIMT", source: "ops-lab" },
|
|
1215
|
+
{ id: "ops-direct-invoke-flash", description: "\u4ECE\u96F6\u6784\u5EFA Ascend C \u6838\u51FD\u6570", source: "plugins-official/ops-direct-invoke-flash/skills" },
|
|
1216
|
+
{ id: "ops-registry-invoke-workflow", description: "\u6CE8\u518C\u8C03\u7528\u5DE5\u4F5C\u6D41", source: "plugins-official/ops-registry-invoke" },
|
|
1217
|
+
{ id: "ops-easyasc-dsl", description: "EasyASC DSL \u7B97\u5B50\u5F00\u53D1", source: "plugins-community/ops-easyasc-dsl" }
|
|
1218
|
+
]
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
id: "pypto",
|
|
1222
|
+
name: "PyPTO \u5F00\u53D1",
|
|
1223
|
+
skills: [
|
|
1224
|
+
{ id: "pypto-intent-understand", description: "\u9700\u6C42\u610F\u56FE\u7406\u89E3\u4E0E\u89C4\u683C\u751F\u6210", source: "ops" },
|
|
1225
|
+
{ id: "pypto-api-explore", description: "API \u53EF\u884C\u6027\u63A2\u7D22\u4E0E\u5206\u6790", source: "ops" },
|
|
1226
|
+
{ id: "pypto-op-design", description: "\u7B97\u5B50\u65B9\u6848\u8BBE\u8BA1\u751F\u6210", source: "ops" },
|
|
1227
|
+
{ id: "pypto-golden-generate", description: "Golden \u53C2\u8003\u5B9E\u73B0\u751F\u6210", source: "ops" },
|
|
1228
|
+
{ id: "pypto-op-develop", description: "\u7B97\u5B50\u4EE3\u7801\u5B9E\u73B0\u4E0E\u6D4B\u8BD5", source: "ops" },
|
|
1229
|
+
{ id: "pypto-precision-debug", description: "\u7CBE\u5EA6\u95EE\u9898\u6392\u67E5", source: "ops" },
|
|
1230
|
+
{ id: "pypto-precision-compare", description: "\u7CBE\u5EA6\u5BF9\u6BD4\u5206\u6790", source: "ops" },
|
|
1231
|
+
{ id: "pypto-op-perf-tune", description: "\u6027\u80FD\u5206\u6790\u4E0E\u8C03\u4F18", source: "ops" }
|
|
1232
|
+
]
|
|
1233
|
+
},
|
|
1234
|
+
{
|
|
1235
|
+
id: "tilelang",
|
|
1236
|
+
name: "TileLang \u5F00\u53D1",
|
|
1237
|
+
skills: [
|
|
1238
|
+
{ id: "tilelang-env-check", description: "\u73AF\u5883\u68C0\u67E5\u4E0E\u914D\u7F6E\u9A8C\u8BC1", source: "ops" },
|
|
1239
|
+
{ id: "tilelang-submodule-pull", description: "\u4E09\u65B9\u5E93\u4E0E\u5B50\u6A21\u5757\u62C9\u53D6", source: "ops" },
|
|
1240
|
+
{ id: "tilelang-api-best-practices", description: "TileLang API \u6700\u4F73\u5B9E\u8DF5", source: "ops" },
|
|
1241
|
+
{ id: "tilelang-programming-model-guide", description: "Developer/Expert \u6A21\u5F0F\u9009\u62E9", source: "ops" },
|
|
1242
|
+
{ id: "tilelang-op-design", description: "\u7B97\u5B50\u8BBE\u8BA1\u6587\u6863\u751F\u6210", source: "ops" },
|
|
1243
|
+
{ id: "tilelang-op-develop", description: "\u7B97\u5B50\u4EE3\u7801\u5B9E\u73B0\u4E0E\u6D4B\u8BD5", source: "ops" },
|
|
1244
|
+
{ id: "tilelang-op-test-design", description: "\u6D4B\u8BD5\u8BBE\u8BA1\u4E0E\u8986\u76D6\u7387\u5206\u6790", source: "ops" },
|
|
1245
|
+
{ id: "tilelang-perf-optimization", description: "\u6027\u80FD\u8C03\u4F18\u4E0E\u52A3\u5316\u68C0\u67E5", source: "ops" },
|
|
1246
|
+
{ id: "tilelang-review", description: "\u4EE3\u7801\u683C\u5F0F\u68C0\u67E5\u4E0E\u4FEE\u590D", source: "ops" }
|
|
1247
|
+
]
|
|
1248
|
+
},
|
|
1249
|
+
{
|
|
1250
|
+
id: "triton",
|
|
1251
|
+
name: "Triton \u5F00\u53D1",
|
|
1252
|
+
skills: [
|
|
1253
|
+
{ id: "triton-task-extractor", description: "\u7B97\u5B50\u4EFB\u52A1\u63D0\u53D6\u4E0E\u6784\u5EFA", source: "ops" },
|
|
1254
|
+
{ id: "triton-op-designer", description: "\u7B97\u6CD5\u8349\u56FE\u8BBE\u8BA1", source: "ops" },
|
|
1255
|
+
{ id: "triton-op-coding", description: "Triton \u5185\u6838\u4EE3\u7801\u751F\u6210", source: "ops" },
|
|
1256
|
+
{ id: "triton-op-verifier", description: "\u7B97\u5B50\u7CBE\u5EA6\u548C\u6027\u80FD\u9A8C\u8BC1", source: "ops" },
|
|
1257
|
+
{ id: "triton-latency-optimizer", description: "Triton \u4EE3\u7801\u6027\u80FD\u4F18\u5316", source: "ops" }
|
|
1258
|
+
]
|
|
1259
|
+
},
|
|
1260
|
+
{
|
|
1261
|
+
id: "model",
|
|
1262
|
+
name: "\u6A21\u578B\u63A8\u7406\u4F18\u5316",
|
|
1263
|
+
skills: [
|
|
1264
|
+
{ id: "model-infer-migrator", description: "\u6846\u67B6\u9002\u914D\u4E0E\u90E8\u7F72\u57FA\u7EBF", source: "model" },
|
|
1265
|
+
{ id: "model-infer-parallel-analysis", description: "\u5E76\u884C\u7B56\u7565\u5206\u6790\uFF08TP/EP/DP\uFF09", source: "model" },
|
|
1266
|
+
{ id: "model-infer-parallel-impl", description: "\u5E76\u884C\u5207\u5206\u5B9E\u65BD", source: "model" },
|
|
1267
|
+
{ id: "model-infer-kvcache", description: "KVCache \u4F18\u5316 + FA \u66FF\u6362", source: "model" },
|
|
1268
|
+
{ id: "model-infer-fusion", description: "torch_npu \u878D\u5408\u7B97\u5B50\u66FF\u6362", source: "model" },
|
|
1269
|
+
{ id: "model-infer-graph-mode", description: "torch.compile \u56FE\u6A21\u5F0F\u9002\u914D", source: "model" },
|
|
1270
|
+
{ id: "model-infer-multi-stream", description: "\u591A\u6D41\u5E76\u884C\u4F18\u5316", source: "model" },
|
|
1271
|
+
{ id: "model-infer-prefetch", description: "\u6743\u91CD\u9884\u53D6\u9002\u914D", source: "model" },
|
|
1272
|
+
{ id: "model-infer-superkernel", description: "SuperKernel \u9002\u914D", source: "model" },
|
|
1273
|
+
{ id: "model-infer-precision-debug", description: "NPU \u63A8\u7406\u7CBE\u5EA6\u8BCA\u65AD", source: "model" },
|
|
1274
|
+
{ id: "model-infer-runtime-debug", description: "NPU \u63A8\u7406\u8FD0\u884C\u65F6\u9519\u8BEF\u8BCA\u65AD", source: "model" }
|
|
1275
|
+
]
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
id: "graph",
|
|
1279
|
+
name: "\u56FE\u6A21\u5F0F",
|
|
1280
|
+
skills: [
|
|
1281
|
+
{ id: "torch-npugraph-ex-knowledge", description: "npugraph_ex \u4F7F\u7528\u6307\u5357", source: "graph" },
|
|
1282
|
+
{ id: "torch-npugraph-ex-template", description: "npugraph_ex MRE \u4EE3\u7801\u6A21\u677F", source: "graph" },
|
|
1283
|
+
{ id: "torch-npugraph-ex-dfx-triage", description: "DFX \u95EE\u9898\u5206\u8BCA", source: "graph" },
|
|
1284
|
+
{ id: "torch-npugraph-ex-compile-error-diagnosis", description: "\u7F16\u8BD1\u671F\u62A5\u9519\u8BCA\u65AD", source: "graph" },
|
|
1285
|
+
{ id: "torch-npugraph-ex-runtime-error-diagnosis", description: "\u8FD0\u884C\u65F6\u62A5\u9519\u8BCA\u65AD", source: "graph" },
|
|
1286
|
+
{ id: "torch-npugraph-ex-performance-diagnosis", description: "\u6027\u80FD\u8BCA\u65AD", source: "graph" },
|
|
1287
|
+
{ id: "torch-custom-ops-guide", description: "\u81EA\u5B9A\u4E49\u7B97\u5B50\u5165\u56FE\u6307\u5357", source: "graph" }
|
|
1288
|
+
]
|
|
1289
|
+
},
|
|
1290
|
+
{
|
|
1291
|
+
id: "platform",
|
|
1292
|
+
name: "\u5E73\u53F0\u5DE5\u5177",
|
|
1293
|
+
skills: [
|
|
1294
|
+
{ id: "gitcode-pr-handler", description: "GitCode PR \u6807\u9898/\u63CF\u8FF0\u751F\u6210", source: "infra" },
|
|
1295
|
+
{ id: "gitcode-issue-gen", description: "GitCode Issue \u751F\u6210\u4E0E\u5173\u8054", source: "infra" },
|
|
1296
|
+
{ id: "gitcode-issue-handler", description: "GitCode Issue \u7AEF\u5230\u7AEF\u5904\u7F6E", source: "infra" },
|
|
1297
|
+
{ id: "gitcode-toolkit", description: "GitCode \u534F\u4F5C\u901A\u7528\u53C2\u8003", source: "infra" },
|
|
1298
|
+
{ id: "cannbot-skill-reviewer", description: "Skill \u5165\u5E93\u8D28\u91CF\u5BA1\u67E5", source: "infra" }
|
|
1299
|
+
]
|
|
1300
|
+
}
|
|
1301
|
+
];
|
|
1302
|
+
var CATEGORY_MAP = {};
|
|
1303
|
+
for (const cat of STATIC_SKILL_CATEGORIES) {
|
|
1304
|
+
for (const skill of cat.skills) {
|
|
1305
|
+
CATEGORY_MAP[skill.id] = cat.id;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
function getActiveSkills() {
|
|
1309
|
+
if (initialized) return deduplicate(dynamicSkills);
|
|
1310
|
+
return deduplicate(STATIC_SKILL_CATEGORIES.flatMap((c) => c.skills));
|
|
1311
|
+
}
|
|
1312
|
+
function deduplicate(skills) {
|
|
1313
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1314
|
+
const result = [];
|
|
1315
|
+
for (const skill of skills) {
|
|
1316
|
+
if (!seen.has(skill.id)) {
|
|
1317
|
+
result.push(skill);
|
|
1318
|
+
seen.add(skill.id);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
return result;
|
|
1322
|
+
}
|
|
1323
|
+
function findSkill(query) {
|
|
1324
|
+
const normalized = query.toLowerCase().trim();
|
|
1325
|
+
const pool = getActiveSkills();
|
|
1326
|
+
return pool.find((s) => s.id === normalized);
|
|
1327
|
+
}
|
|
1328
|
+
function getAllSkills() {
|
|
1329
|
+
return getActiveSkills();
|
|
1330
|
+
}
|
|
1331
|
+
function getAllCategories() {
|
|
1332
|
+
const skills = getActiveSkills();
|
|
1333
|
+
return CATEGORY_DEFS.map((def) => ({
|
|
1334
|
+
id: def.id,
|
|
1335
|
+
name: def.name,
|
|
1336
|
+
skills: skills.filter((s) => {
|
|
1337
|
+
const catId = CATEGORY_MAP[s.id];
|
|
1338
|
+
if (catId) return catId === def.id;
|
|
1339
|
+
return def.id === "other";
|
|
1340
|
+
})
|
|
1341
|
+
})).filter((cat) => cat.skills.length > 0);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// src/core/repository.ts
|
|
1345
|
+
var REPO_URL = "https://gitcode.com/cann/cannbot-skills.git";
|
|
1346
|
+
var RepositoryManager = class {
|
|
1347
|
+
repoPath;
|
|
1348
|
+
constructor(customPath) {
|
|
1349
|
+
this.repoPath = customPath;
|
|
1350
|
+
}
|
|
1351
|
+
async ensureRepo() {
|
|
1352
|
+
if (this.repoPath && this.isValidRepo(this.repoPath)) {
|
|
1353
|
+
return this.repoPath;
|
|
1354
|
+
}
|
|
1355
|
+
const cwd = process.cwd();
|
|
1356
|
+
if (this.isValidRepo(cwd)) {
|
|
1357
|
+
this.repoPath = cwd;
|
|
1358
|
+
return cwd;
|
|
1359
|
+
}
|
|
1360
|
+
const envPath = process.env.CANNBOT_REPO_PATH;
|
|
1361
|
+
if (envPath && this.isValidRepo(envPath)) {
|
|
1362
|
+
this.repoPath = envPath;
|
|
1363
|
+
return envPath;
|
|
1364
|
+
}
|
|
1365
|
+
const cachedPath = getCannbotRepoPath();
|
|
1366
|
+
if (this.isValidRepo(cachedPath)) {
|
|
1367
|
+
this.repoPath = cachedPath;
|
|
1368
|
+
return cachedPath;
|
|
1369
|
+
}
|
|
1370
|
+
return await this.cloneRepo();
|
|
1371
|
+
}
|
|
1372
|
+
async ensureRepoAndScan() {
|
|
1373
|
+
const repoPath = await this.ensureRepo();
|
|
1374
|
+
const cache = readScanCache();
|
|
1375
|
+
if (cache && cache.repoCommit === getCurrentCommit(repoPath)) {
|
|
1376
|
+
initFromScan(cache.skills);
|
|
1377
|
+
return repoPath;
|
|
1378
|
+
}
|
|
1379
|
+
const skills = scanSkills(repoPath);
|
|
1380
|
+
initFromScan(skills);
|
|
1381
|
+
writeScanCache({
|
|
1382
|
+
skills,
|
|
1383
|
+
repoCommit: getCurrentCommit(repoPath),
|
|
1384
|
+
timestamp: Date.now()
|
|
1385
|
+
});
|
|
1386
|
+
return repoPath;
|
|
1387
|
+
}
|
|
1388
|
+
async updateRepo() {
|
|
1389
|
+
const repoPath = await this.ensureRepo();
|
|
1390
|
+
try {
|
|
1391
|
+
await execa2("git", ["pull", "--quiet"], { cwd: repoPath, timeout: 3e4 });
|
|
1392
|
+
} catch {
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
getRepoPath() {
|
|
1396
|
+
if (!this.repoPath) {
|
|
1397
|
+
throw new Error("Repository not initialized. Call ensureRepo() first.");
|
|
1398
|
+
}
|
|
1399
|
+
return this.repoPath;
|
|
1400
|
+
}
|
|
1401
|
+
isValidRepo(path) {
|
|
1402
|
+
if (!existsSync6(path)) return false;
|
|
1403
|
+
const gitDir = join6(path, ".git");
|
|
1404
|
+
const pluginsDir = join6(path, "plugins-official");
|
|
1405
|
+
return existsSync6(gitDir) && existsSync6(pluginsDir);
|
|
1406
|
+
}
|
|
1407
|
+
async cloneRepo() {
|
|
1408
|
+
const configDir = getCannbotConfigDir();
|
|
1409
|
+
if (!existsSync6(configDir)) {
|
|
1410
|
+
mkdirSync3(configDir, { recursive: true });
|
|
1411
|
+
}
|
|
1412
|
+
const targetPath = getCannbotRepoPath();
|
|
1413
|
+
if (existsSync6(targetPath)) {
|
|
1414
|
+
if (this.isValidRepo(targetPath)) {
|
|
1415
|
+
this.repoPath = targetPath;
|
|
1416
|
+
return targetPath;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
try {
|
|
1420
|
+
await execa2(
|
|
1421
|
+
"git",
|
|
1422
|
+
["clone", "--depth", "1", REPO_URL, targetPath],
|
|
1423
|
+
{ timeout: 12e4 }
|
|
1424
|
+
);
|
|
1425
|
+
this.repoPath = targetPath;
|
|
1426
|
+
return targetPath;
|
|
1427
|
+
} catch (error) {
|
|
1428
|
+
throw new Error(
|
|
1429
|
+
`Failed to clone repository: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1430
|
+
);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
1434
|
+
function createRepositoryManager(customPath) {
|
|
1435
|
+
return new RepositoryManager(customPath);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
// src/core/installer.ts
|
|
1439
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1440
|
+
import { join as join8 } from "path";
|
|
1441
|
+
import { execa as execa3 } from "execa";
|
|
1442
|
+
|
|
1443
|
+
// src/core/record.ts
|
|
1444
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, lstatSync, unlinkSync } from "fs";
|
|
1445
|
+
import { join as join7 } from "path";
|
|
1446
|
+
function getInstallsDir() {
|
|
1447
|
+
return join7(getCannbotConfigDir(), "installs");
|
|
1448
|
+
}
|
|
1449
|
+
function getRecordPath(pluginId) {
|
|
1450
|
+
return join7(getInstallsDir(), `${pluginId}.json`);
|
|
1451
|
+
}
|
|
1452
|
+
function readRecord(pluginId) {
|
|
1453
|
+
const recordPath = getRecordPath(pluginId);
|
|
1454
|
+
if (!existsSync7(recordPath)) {
|
|
1455
|
+
return null;
|
|
1456
|
+
}
|
|
1457
|
+
try {
|
|
1458
|
+
const content = readFileSync4(recordPath, "utf-8");
|
|
1459
|
+
return JSON.parse(content);
|
|
1460
|
+
} catch {
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
function writeRecord(record) {
|
|
1465
|
+
const installsDir = getInstallsDir();
|
|
1466
|
+
if (!existsSync7(installsDir)) {
|
|
1467
|
+
mkdirSync4(installsDir, { recursive: true });
|
|
1468
|
+
}
|
|
1469
|
+
const recordPath = getRecordPath(record.pluginId);
|
|
1470
|
+
writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
|
|
1471
|
+
}
|
|
1472
|
+
function deleteRecord(pluginId) {
|
|
1473
|
+
const recordPath = getRecordPath(pluginId);
|
|
1474
|
+
if (existsSync7(recordPath)) {
|
|
1475
|
+
unlinkSync(recordPath);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
function scanInstalledFiles(pluginId, displayName, tool, level, installPath, configRoot, manifest) {
|
|
1479
|
+
const files = [];
|
|
1480
|
+
const directories = [];
|
|
1481
|
+
if (manifest) {
|
|
1482
|
+
const skillsDir = join7(configRoot, "skills");
|
|
1483
|
+
if (existsSync7(skillsDir)) {
|
|
1484
|
+
directories.push(skillsDir);
|
|
1485
|
+
for (const skillName of manifest.installed_skills || []) {
|
|
1486
|
+
const skillPath = join7(skillsDir, skillName);
|
|
1487
|
+
if (existsSync7(skillPath) || isSymlink(skillPath)) {
|
|
1488
|
+
files.push(skillPath);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
const agentsDir = join7(configRoot, "agents");
|
|
1493
|
+
if (existsSync7(agentsDir)) {
|
|
1494
|
+
directories.push(agentsDir);
|
|
1495
|
+
for (const agentName of manifest.installed_agents || []) {
|
|
1496
|
+
const agentPath = join7(agentsDir, agentName);
|
|
1497
|
+
const agentPathMd = join7(agentsDir, agentName + ".md");
|
|
1498
|
+
if (existsSync7(agentPath) || isSymlink(agentPath)) {
|
|
1499
|
+
files.push(agentPath);
|
|
1500
|
+
} else if (existsSync7(agentPathMd) || isSymlink(agentPathMd)) {
|
|
1501
|
+
files.push(agentPathMd);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
const workflowsLink = join7(configRoot, "workflows");
|
|
1507
|
+
if (isSymlink(workflowsLink)) {
|
|
1508
|
+
files.push(workflowsLink);
|
|
1509
|
+
}
|
|
1510
|
+
const manifestPath = join7(configRoot, "cannbot-manifest.json");
|
|
1511
|
+
if (existsSync7(manifestPath)) {
|
|
1512
|
+
files.push(manifestPath);
|
|
1513
|
+
}
|
|
1514
|
+
const pluginManifestPath = join7(configRoot, `${pluginId}-manifest.json`);
|
|
1515
|
+
if (existsSync7(pluginManifestPath)) {
|
|
1516
|
+
files.push(pluginManifestPath);
|
|
1517
|
+
}
|
|
1518
|
+
const configFileName = tool === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
1519
|
+
const configFilePath = level === "project" ? join7(installPath, configFileName) : join7(configRoot, configFileName);
|
|
1520
|
+
if (existsSync7(configFilePath)) {
|
|
1521
|
+
files.push(configFilePath);
|
|
1522
|
+
}
|
|
1523
|
+
const repoLinks = ["asc-devkit", "pypto", "tilelang-ascend", "cann-recipes-infer", "cann-samples"];
|
|
1524
|
+
for (const repoName of repoLinks) {
|
|
1525
|
+
const repoLinkPath = join7(installPath, repoName);
|
|
1526
|
+
if (isSymlink(repoLinkPath)) {
|
|
1527
|
+
files.push(repoLinkPath);
|
|
1528
|
+
}
|
|
1529
|
+
const repoLinkInConfig = join7(configRoot, repoName);
|
|
1530
|
+
if (isSymlink(repoLinkInConfig)) {
|
|
1531
|
+
files.push(repoLinkInConfig);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
directories.push(configRoot);
|
|
1535
|
+
return {
|
|
1536
|
+
pluginId,
|
|
1537
|
+
displayName,
|
|
1538
|
+
tool,
|
|
1539
|
+
level,
|
|
1540
|
+
installPath,
|
|
1541
|
+
configRoot,
|
|
1542
|
+
installTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1543
|
+
files,
|
|
1544
|
+
directories
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
function isSymlink(path) {
|
|
1548
|
+
try {
|
|
1549
|
+
return lstatSync(path).isSymbolicLink();
|
|
1550
|
+
} catch {
|
|
1551
|
+
return false;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
function getSkillRecordPath() {
|
|
1555
|
+
return join7(getInstallsDir(), "skills.json");
|
|
1556
|
+
}
|
|
1557
|
+
function readSkillRecord() {
|
|
1558
|
+
const recordPath = getSkillRecordPath();
|
|
1559
|
+
if (!existsSync7(recordPath)) {
|
|
1560
|
+
return {};
|
|
1561
|
+
}
|
|
1562
|
+
try {
|
|
1563
|
+
const content = readFileSync4(recordPath, "utf-8");
|
|
1564
|
+
return JSON.parse(content);
|
|
1565
|
+
} catch {
|
|
1566
|
+
return {};
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
function writeSkillRecord(record) {
|
|
1570
|
+
const installsDir = getInstallsDir();
|
|
1571
|
+
if (!existsSync7(installsDir)) {
|
|
1572
|
+
mkdirSync4(installsDir, { recursive: true });
|
|
1573
|
+
}
|
|
1574
|
+
const recordPath = getSkillRecordPath();
|
|
1575
|
+
writeFileSync4(recordPath, JSON.stringify(record, null, 2), "utf-8");
|
|
1576
|
+
}
|
|
1577
|
+
function addSkillsToRecord(skillIds, tool, level, installPath) {
|
|
1578
|
+
const record = readSkillRecord();
|
|
1579
|
+
if (!record[tool]) record[tool] = {};
|
|
1580
|
+
if (!record[tool][level]) record[tool][level] = {};
|
|
1581
|
+
if (!record[tool][level][installPath]) {
|
|
1582
|
+
record[tool][level][installPath] = { skills: [], installTime: "" };
|
|
1583
|
+
}
|
|
1584
|
+
const entry = record[tool][level][installPath];
|
|
1585
|
+
for (const id of skillIds) {
|
|
1586
|
+
if (!entry.skills.includes(id)) {
|
|
1587
|
+
entry.skills.push(id);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
entry.installTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1591
|
+
writeSkillRecord(record);
|
|
1592
|
+
}
|
|
1593
|
+
function removeSkillsFromRecord(skillIds, tool, level, installPath) {
|
|
1594
|
+
const record = readSkillRecord();
|
|
1595
|
+
if (!record[tool]?.[level]?.[installPath]) return;
|
|
1596
|
+
const entry = record[tool][level][installPath];
|
|
1597
|
+
entry.skills = entry.skills.filter((id) => !skillIds.includes(id));
|
|
1598
|
+
if (entry.skills.length === 0) {
|
|
1599
|
+
delete record[tool][level][installPath];
|
|
1600
|
+
if (Object.keys(record[tool][level]).length === 0) {
|
|
1601
|
+
delete record[tool][level];
|
|
1602
|
+
if (Object.keys(record[tool]).length === 0) {
|
|
1603
|
+
delete record[tool];
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
writeSkillRecord(record);
|
|
1608
|
+
}
|
|
1609
|
+
function getInstalledSkills(tool, level, installPath) {
|
|
1610
|
+
const record = readSkillRecord();
|
|
1611
|
+
return record[tool]?.[level]?.[installPath]?.skills || [];
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// src/core/installer.ts
|
|
1615
|
+
async function installPlugin(opts) {
|
|
1616
|
+
const plugin = getPluginById(opts.pluginId);
|
|
1617
|
+
if (!plugin) {
|
|
1618
|
+
return {
|
|
1619
|
+
success: false,
|
|
1620
|
+
pluginId: opts.pluginId,
|
|
1621
|
+
skillsCount: 0,
|
|
1622
|
+
agentsCount: 0,
|
|
1623
|
+
errors: [`Plugin not found: ${opts.pluginId}`],
|
|
1624
|
+
warnings: []
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
const scriptPath = join8(opts.repoPath, plugin.dir, plugin.script);
|
|
1628
|
+
if (!existsSync8(scriptPath)) {
|
|
1629
|
+
return {
|
|
1630
|
+
success: false,
|
|
1631
|
+
pluginId: opts.pluginId,
|
|
1632
|
+
skillsCount: 0,
|
|
1633
|
+
agentsCount: 0,
|
|
1634
|
+
errors: [`Script not found: ${scriptPath}`],
|
|
1635
|
+
warnings: []
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
const args = [opts.level, opts.tool];
|
|
1639
|
+
if (opts.installPath) {
|
|
1640
|
+
args.push(opts.installPath);
|
|
1641
|
+
}
|
|
1642
|
+
const cwd = opts.installPath || process.cwd();
|
|
1643
|
+
try {
|
|
1644
|
+
await execa3("bash", [scriptPath, ...args], {
|
|
1645
|
+
cwd,
|
|
1646
|
+
timeout: 3e5,
|
|
1647
|
+
stdio: "pipe"
|
|
1648
|
+
});
|
|
1649
|
+
const configRoot = getConfigRoot(opts.tool, opts.level, opts.installPath);
|
|
1650
|
+
const manifest = readManifest(configRoot);
|
|
1651
|
+
let skillsCount = 0;
|
|
1652
|
+
let agentsCount = 0;
|
|
1653
|
+
if (manifest) {
|
|
1654
|
+
skillsCount = manifest.installed_skills?.length || 0;
|
|
1655
|
+
agentsCount = manifest.installed_agents?.length || 0;
|
|
1656
|
+
} else {
|
|
1657
|
+
skillsCount = plugin.skills;
|
|
1658
|
+
agentsCount = plugin.agents;
|
|
1659
|
+
}
|
|
1660
|
+
try {
|
|
1661
|
+
const record = scanInstalledFiles(
|
|
1662
|
+
opts.pluginId,
|
|
1663
|
+
plugin.displayName,
|
|
1664
|
+
opts.tool,
|
|
1665
|
+
opts.level,
|
|
1666
|
+
cwd,
|
|
1667
|
+
configRoot,
|
|
1668
|
+
manifest
|
|
1669
|
+
);
|
|
1670
|
+
writeRecord(record);
|
|
1671
|
+
} catch {
|
|
1672
|
+
}
|
|
1673
|
+
return {
|
|
1674
|
+
success: true,
|
|
1675
|
+
pluginId: opts.pluginId,
|
|
1676
|
+
skillsCount,
|
|
1677
|
+
agentsCount,
|
|
1678
|
+
errors: [],
|
|
1679
|
+
warnings: []
|
|
1680
|
+
};
|
|
1681
|
+
} catch (error) {
|
|
1682
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1683
|
+
return {
|
|
1684
|
+
success: false,
|
|
1685
|
+
pluginId: opts.pluginId,
|
|
1686
|
+
skillsCount: 0,
|
|
1687
|
+
agentsCount: 0,
|
|
1688
|
+
errors: [errorMessage],
|
|
1689
|
+
warnings: []
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// src/core/skill-installer.ts
|
|
1695
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, symlinkSync, unlinkSync as unlinkSync2, readdirSync as readdirSync3, rmdirSync, realpathSync, lstatSync as lstatSync2 } from "fs";
|
|
1696
|
+
import { join as join9 } from "path";
|
|
1697
|
+
import { select as select2, checkbox as checkbox2, Separator as Separator2 } from "@inquirer/prompts";
|
|
1698
|
+
import chalk3 from "chalk";
|
|
1699
|
+
import Table from "cli-table3";
|
|
1700
|
+
async function installSkills(skillIds, tool, level, repoPath) {
|
|
1701
|
+
const configRoot = getConfigRoot(tool, level);
|
|
1702
|
+
const skillsDir = join9(configRoot, "skills");
|
|
1703
|
+
const installPath = level === "project" ? process.cwd() : configRoot;
|
|
1704
|
+
if (!existsSync9(skillsDir)) {
|
|
1705
|
+
mkdirSync5(skillsDir, { recursive: true });
|
|
1706
|
+
}
|
|
1707
|
+
const results = [];
|
|
1708
|
+
const installedIds = [];
|
|
1709
|
+
for (const skillId of skillIds) {
|
|
1710
|
+
const skill = findSkill(skillId);
|
|
1711
|
+
if (!skill) {
|
|
1712
|
+
results.push({ skillId, success: false, error: "Skill not found in registry" });
|
|
1713
|
+
continue;
|
|
1714
|
+
}
|
|
1715
|
+
const sourcePath = join9(repoPath, skill.source, skill.id);
|
|
1716
|
+
const targetPath = join9(skillsDir, skillId);
|
|
1717
|
+
if (!existsSync9(sourcePath)) {
|
|
1718
|
+
results.push({ skillId, success: false, error: `Source not found: ${sourcePath}` });
|
|
1719
|
+
continue;
|
|
1720
|
+
}
|
|
1721
|
+
try {
|
|
1722
|
+
if (existsSync9(targetPath) || isSymlink2(targetPath)) {
|
|
1723
|
+
unlinkSync2(targetPath);
|
|
1724
|
+
}
|
|
1725
|
+
symlinkSync(realpathSync(sourcePath), targetPath);
|
|
1726
|
+
results.push({ skillId, success: true });
|
|
1727
|
+
installedIds.push(skillId);
|
|
1728
|
+
} catch (error) {
|
|
1729
|
+
results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
if (installedIds.length > 0) {
|
|
1733
|
+
addSkillsToRecord(installedIds, tool, level, installPath);
|
|
1734
|
+
}
|
|
1735
|
+
return results;
|
|
1736
|
+
}
|
|
1737
|
+
async function uninstallSkills(skillIds, tool, level) {
|
|
1738
|
+
const configRoot = getConfigRoot(tool, level);
|
|
1739
|
+
const skillsDir = join9(configRoot, "skills");
|
|
1740
|
+
const installPath = level === "project" ? process.cwd() : configRoot;
|
|
1741
|
+
const results = [];
|
|
1742
|
+
const removedIds = [];
|
|
1743
|
+
for (const skillId of skillIds) {
|
|
1744
|
+
const targetPath = join9(skillsDir, skillId);
|
|
1745
|
+
try {
|
|
1746
|
+
if (existsSync9(targetPath) || isSymlink2(targetPath)) {
|
|
1747
|
+
unlinkSync2(targetPath);
|
|
1748
|
+
results.push({ skillId, success: true });
|
|
1749
|
+
removedIds.push(skillId);
|
|
1750
|
+
logger.step(` \u79FB\u9664: ${skillId}`);
|
|
1751
|
+
} else {
|
|
1752
|
+
results.push({ skillId, success: false, error: "Not installed" });
|
|
1753
|
+
logger.warn(`${skillId} \u672A\u5B89\u88C5`);
|
|
1754
|
+
}
|
|
1755
|
+
} catch (error) {
|
|
1756
|
+
results.push({ skillId, success: false, error: error instanceof Error ? error.message : "Unknown error" });
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
if (removedIds.length > 0) {
|
|
1760
|
+
removeSkillsFromRecord(removedIds, tool, level, installPath);
|
|
1761
|
+
}
|
|
1762
|
+
if (existsSync9(skillsDir)) {
|
|
1763
|
+
try {
|
|
1764
|
+
const entries = readdirSync3(skillsDir);
|
|
1765
|
+
if (entries.length === 0) {
|
|
1766
|
+
rmdirSync(skillsDir);
|
|
1767
|
+
logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: skills/`);
|
|
1768
|
+
}
|
|
1769
|
+
} catch {
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
if (existsSync9(configRoot)) {
|
|
1773
|
+
try {
|
|
1774
|
+
const entries = readdirSync3(configRoot);
|
|
1775
|
+
if (entries.length === 0) {
|
|
1776
|
+
rmdirSync(configRoot);
|
|
1777
|
+
logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${configRoot.split("/").pop()}/`);
|
|
1778
|
+
}
|
|
1779
|
+
} catch {
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
return results;
|
|
1783
|
+
}
|
|
1784
|
+
async function interactiveSkillSelect() {
|
|
1785
|
+
const BACK2 = "__back__";
|
|
1786
|
+
const CANCEL2 = "__cancel__";
|
|
1787
|
+
const categories = getAllCategories();
|
|
1788
|
+
let step = 0;
|
|
1789
|
+
let selectedCategoryId = "";
|
|
1790
|
+
while (true) {
|
|
1791
|
+
switch (step) {
|
|
1792
|
+
case 0: {
|
|
1793
|
+
printBoxTitle("\u9009\u62E9 Skill \u7C7B\u522B");
|
|
1794
|
+
const categoryChoices = [
|
|
1795
|
+
...categories.map((cat) => ({
|
|
1796
|
+
name: `> ${cat.name} (${cat.skills.length} skills)`,
|
|
1797
|
+
value: cat.id
|
|
1798
|
+
})),
|
|
1799
|
+
new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
1800
|
+
{ name: "<- " + t("wizard_back"), value: BACK2 },
|
|
1801
|
+
{ name: "x " + t("wizard_cancel"), value: CANCEL2 }
|
|
1802
|
+
];
|
|
1803
|
+
showOperationHints();
|
|
1804
|
+
selectedCategoryId = await select2({
|
|
1805
|
+
message: t("skill_select_category"),
|
|
1806
|
+
choices: categoryChoices,
|
|
1807
|
+
loop: false,
|
|
1808
|
+
theme: selectTheme
|
|
1809
|
+
});
|
|
1810
|
+
if (selectedCategoryId === BACK2) return "back";
|
|
1811
|
+
if (selectedCategoryId === CANCEL2) return "cancel";
|
|
1812
|
+
step = 1;
|
|
1813
|
+
break;
|
|
1814
|
+
}
|
|
1815
|
+
case 1: {
|
|
1816
|
+
const category = categories.find((c) => c.id === selectedCategoryId);
|
|
1817
|
+
if (!category) return [];
|
|
1818
|
+
printBoxTitle(`\u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills \u2014 ${category.name}`);
|
|
1819
|
+
const installedSkills = getInstalledSkills(
|
|
1820
|
+
"opencode",
|
|
1821
|
+
"project",
|
|
1822
|
+
process.cwd()
|
|
1823
|
+
);
|
|
1824
|
+
const installedSet = new Set(installedSkills);
|
|
1825
|
+
const skillChoices = category.skills.map((skill) => {
|
|
1826
|
+
const isInstalled = installedSet.has(skill.id);
|
|
1827
|
+
const suffix = isInstalled ? ` [${t("skill_already_installed")}]` : "";
|
|
1828
|
+
return {
|
|
1829
|
+
name: `${skill.id}${suffix} \u2014 ${skill.description}`,
|
|
1830
|
+
value: skill.id,
|
|
1831
|
+
checked: false
|
|
1832
|
+
};
|
|
1833
|
+
});
|
|
1834
|
+
skillChoices.push(new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1835
|
+
skillChoices.push({ name: "<- " + t("wizard_back"), value: BACK2, checked: false });
|
|
1836
|
+
skillChoices.push({ name: "x " + t("wizard_cancel"), value: CANCEL2, checked: false });
|
|
1837
|
+
showOperationHints(true);
|
|
1838
|
+
const selectedSkills = await checkbox2({
|
|
1839
|
+
message: t("skill_select_items"),
|
|
1840
|
+
choices: skillChoices,
|
|
1841
|
+
loop: false,
|
|
1842
|
+
instructions: false,
|
|
1843
|
+
theme: checkboxTheme
|
|
1844
|
+
});
|
|
1845
|
+
if (selectedSkills.includes(BACK2)) {
|
|
1846
|
+
step = 0;
|
|
1847
|
+
break;
|
|
1848
|
+
}
|
|
1849
|
+
if (selectedSkills.includes(CANCEL2)) return "cancel";
|
|
1850
|
+
const skillIds = selectedSkills.filter((v) => v !== BACK2 && v !== CANCEL2);
|
|
1851
|
+
if (skillIds.length === 0) {
|
|
1852
|
+
const action = await select2({
|
|
1853
|
+
message: t("wizard_no_selection"),
|
|
1854
|
+
choices: [
|
|
1855
|
+
new Separator2("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
1856
|
+
{ name: "<- " + t("wizard_back_to_reselect"), value: "back" },
|
|
1857
|
+
{ name: "x " + t("wizard_cancel"), value: "cancel" }
|
|
1858
|
+
],
|
|
1859
|
+
loop: false,
|
|
1860
|
+
theme: selectTheme
|
|
1861
|
+
});
|
|
1862
|
+
if (action === "back") {
|
|
1863
|
+
step = 0;
|
|
1864
|
+
break;
|
|
1865
|
+
}
|
|
1866
|
+
return "cancel";
|
|
1867
|
+
}
|
|
1868
|
+
return skillIds;
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
function listAllSkills() {
|
|
1874
|
+
const categories = getAllCategories();
|
|
1875
|
+
console.log();
|
|
1876
|
+
console.log(chalk3.bold(` ${t("skill_list_title")}`));
|
|
1877
|
+
console.log(chalk3.dim(" " + "\u2500".repeat(60)));
|
|
1878
|
+
for (const category of categories) {
|
|
1879
|
+
console.log();
|
|
1880
|
+
console.log(chalk3.bold(` ${category.name}`) + chalk3.dim(` (${category.skills.length} skills)`));
|
|
1881
|
+
const table = new Table({
|
|
1882
|
+
style: { head: [], border: [] },
|
|
1883
|
+
colWidths: [35, 45],
|
|
1884
|
+
wordWrap: true
|
|
1885
|
+
});
|
|
1886
|
+
for (const skill of category.skills) {
|
|
1887
|
+
table.push([
|
|
1888
|
+
chalk3.cyan(skill.id),
|
|
1889
|
+
skill.description
|
|
1890
|
+
]);
|
|
1891
|
+
}
|
|
1892
|
+
console.log(table.toString());
|
|
1893
|
+
}
|
|
1894
|
+
console.log();
|
|
1895
|
+
console.log(chalk3.dim(` \u5171 ${getAllSkills().length} \u4E2A Skills`));
|
|
1896
|
+
console.log();
|
|
1897
|
+
}
|
|
1898
|
+
function isSymlink2(path) {
|
|
1899
|
+
try {
|
|
1900
|
+
return lstatSync2(path).isSymbolicLink();
|
|
1901
|
+
} catch {
|
|
1902
|
+
return false;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
// src/ui/display.ts
|
|
1907
|
+
import chalk4 from "chalk";
|
|
1908
|
+
import Table2 from "cli-table3";
|
|
1909
|
+
function printPluginList(plugins, installed) {
|
|
1910
|
+
const table = new Table2({
|
|
1911
|
+
head: [
|
|
1912
|
+
chalk4.cyan(t("list_id")),
|
|
1913
|
+
chalk4.cyan(t("list_name")),
|
|
1914
|
+
chalk4.cyan(t("list_status")),
|
|
1915
|
+
chalk4.cyan(t("list_skills")),
|
|
1916
|
+
chalk4.cyan(t("list_agents"))
|
|
1917
|
+
],
|
|
1918
|
+
style: { head: [], border: [] }
|
|
1919
|
+
});
|
|
1920
|
+
plugins.forEach((plugin, index) => {
|
|
1921
|
+
const isInstalled = installed.has(plugin.id);
|
|
1922
|
+
const status = isInstalled ? chalk4.green(`\u2713 ${t("status_installed")}`) : chalk4.dim(`\u2014`);
|
|
1923
|
+
table.push([
|
|
1924
|
+
String(index + 1),
|
|
1925
|
+
plugin.displayName,
|
|
1926
|
+
status,
|
|
1927
|
+
String(plugin.skills),
|
|
1928
|
+
String(plugin.agents)
|
|
1929
|
+
]);
|
|
1930
|
+
});
|
|
1931
|
+
console.log();
|
|
1932
|
+
console.log(chalk4.bold(` ${t("list_title")} (${plugins.length} \u4E2A)`));
|
|
1933
|
+
console.log(table.toString());
|
|
1934
|
+
console.log();
|
|
1935
|
+
}
|
|
1936
|
+
function printInstallSummary(results) {
|
|
1937
|
+
const successCount = results.filter((r) => r.success).length;
|
|
1938
|
+
const totalCount = results.length;
|
|
1939
|
+
console.log();
|
|
1940
|
+
console.log(
|
|
1941
|
+
chalk4.bold(
|
|
1942
|
+
` ${t("install_done")}! ${successCount}/${totalCount} ${t("install_success")}`
|
|
1943
|
+
)
|
|
1944
|
+
);
|
|
1945
|
+
console.log();
|
|
1946
|
+
for (const result of results) {
|
|
1947
|
+
if (result.success) {
|
|
1948
|
+
console.log(
|
|
1949
|
+
chalk4.green(" \u2713") + ` ${result.displayName} (${result.skillsCount} skills, ${result.agentsCount} agents)`
|
|
1950
|
+
);
|
|
1951
|
+
} else {
|
|
1952
|
+
console.log(chalk4.red(" \u2717") + ` ${result.displayName}`);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
console.log();
|
|
1956
|
+
}
|
|
1957
|
+
function printEnhancedSummary(results, tool, configRoot) {
|
|
1958
|
+
const successResults = results.filter((r) => r.success);
|
|
1959
|
+
const totalSkills = successResults.reduce((sum, r) => sum + r.skillsCount, 0);
|
|
1960
|
+
const totalAgents = successResults.reduce((sum, r) => sum + r.agentsCount, 0);
|
|
1961
|
+
if (successResults.length === 0) {
|
|
1962
|
+
return;
|
|
1963
|
+
}
|
|
1964
|
+
const toolName = getToolDisplayName(tool);
|
|
1965
|
+
const lines = [
|
|
1966
|
+
chalk4.bold(` ${t("install_done")}!`),
|
|
1967
|
+
"",
|
|
1968
|
+
` ${chalk4.dim("\u5B89\u88C5\u5230:")} ${configRoot}`,
|
|
1969
|
+
` ${chalk4.dim("Skills:")} ${totalSkills} \u4E2A ${chalk4.dim("|")} ${chalk4.dim("Agents:")} ${totalAgents} \u4E2A`,
|
|
1970
|
+
"",
|
|
1971
|
+
chalk4.bold(` ${t("install_enhanced_next_steps")}:`),
|
|
1972
|
+
` ${chalk4.cyan("1.")} ${t("install_enhanced_launch")}: ${chalk4.green(toolName.toLowerCase())}`,
|
|
1973
|
+
` ${chalk4.cyan("2.")} ${t("install_enhanced_try")}: ${chalk4.green("\u5E2E\u6211\u5F00\u53D1\u4E00\u4E2A Abs \u7B97\u5B50")}`,
|
|
1974
|
+
` ${chalk4.cyan("3.")} ${t("install_enhanced_more")}: ${chalk4.green("install-helper list")}`,
|
|
1975
|
+
` ${chalk4.cyan("4.")} ${t("install_enhanced_check")}: ${chalk4.green("install-helper doctor")}`,
|
|
1976
|
+
""
|
|
1977
|
+
];
|
|
1978
|
+
if (successResults.length === 1) {
|
|
1979
|
+
lines.push(
|
|
1980
|
+
` ${chalk4.dim(t("install_enhanced_docs"))}: ${chalk4.dim(`plugins-official/${successResults[0].pluginId}/quickstart.md`)}`
|
|
1981
|
+
);
|
|
1982
|
+
}
|
|
1983
|
+
console.log();
|
|
1984
|
+
for (const line of lines) {
|
|
1985
|
+
console.log(line);
|
|
1986
|
+
}
|
|
1987
|
+
console.log();
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
// src/commands/init.ts
|
|
1991
|
+
import { existsSync as existsSync10 } from "fs";
|
|
1992
|
+
import { join as join10 } from "path";
|
|
1993
|
+
async function initCommand() {
|
|
1994
|
+
const cwd = process.cwd();
|
|
1995
|
+
if (existsSync10(join10(cwd, "package.json")) && cwd.includes("install-helper")) {
|
|
1996
|
+
logger.warn("\u5F53\u524D\u76EE\u5F55\u770B\u8D77\u6765\u662F install-helper \u5305\u76EE\u5F55");
|
|
1997
|
+
logger.info("\u8BF7\u5728\u4F60\u7684\u9879\u76EE\u76EE\u5F55\u4E2D\u8FD0\u884C\u6B64\u547D\u4EE4\uFF0C\u800C\u4E0D\u662F\u5728 install-helper \u5B89\u88C5\u76EE\u5F55\u4E2D");
|
|
1998
|
+
logger.info("\u793A\u4F8B\uFF1Acd ~/my-project && install-helper");
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
printBanner(t("wizard_title"));
|
|
2002
|
+
const spinner = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
|
|
2003
|
+
spinner.start();
|
|
2004
|
+
try {
|
|
2005
|
+
const repoManager = createRepositoryManager();
|
|
2006
|
+
await repoManager.ensureRepoAndScan();
|
|
2007
|
+
spinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
|
|
2008
|
+
} catch {
|
|
2009
|
+
spinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
|
|
2010
|
+
}
|
|
2011
|
+
let step = 0;
|
|
2012
|
+
let mode = "plugin";
|
|
2013
|
+
while (true) {
|
|
2014
|
+
switch (step) {
|
|
2015
|
+
case 0: {
|
|
2016
|
+
const result = await selectMode();
|
|
2017
|
+
if (result === null) return;
|
|
2018
|
+
mode = result;
|
|
2019
|
+
step = 1;
|
|
2020
|
+
break;
|
|
2021
|
+
}
|
|
2022
|
+
case 1: {
|
|
2023
|
+
if (mode === "plugin") {
|
|
2024
|
+
const result = await pluginInstallFlow();
|
|
2025
|
+
if (result === "back") {
|
|
2026
|
+
step = 0;
|
|
2027
|
+
break;
|
|
2028
|
+
}
|
|
2029
|
+
return;
|
|
2030
|
+
} else {
|
|
2031
|
+
const result = await skillInstallFlow();
|
|
2032
|
+
if (result === "back") {
|
|
2033
|
+
step = 0;
|
|
2034
|
+
break;
|
|
2035
|
+
}
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
async function selectMode() {
|
|
2043
|
+
printBoxTitle("\u9009\u62E9\u5B89\u88C5\u7C7B\u578B");
|
|
2044
|
+
const CANCEL2 = "__cancel__";
|
|
2045
|
+
const choices = [
|
|
2046
|
+
{
|
|
2047
|
+
name: `> ${t("wizard_mode_plugin")} \u2014 ${t("wizard_mode_plugin_desc")}`,
|
|
2048
|
+
value: "plugin"
|
|
2049
|
+
},
|
|
2050
|
+
{
|
|
2051
|
+
name: `> ${t("wizard_mode_skill")} \u2014 ${t("wizard_mode_skill_desc")}`,
|
|
2052
|
+
value: "skill"
|
|
2053
|
+
},
|
|
2054
|
+
new Separator3("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
|
|
2055
|
+
{ name: "x " + t("wizard_cancel"), value: CANCEL2 }
|
|
2056
|
+
];
|
|
2057
|
+
showOperationHints();
|
|
2058
|
+
const result = await select3({
|
|
2059
|
+
message: t("wizard_select_mode"),
|
|
2060
|
+
choices,
|
|
2061
|
+
loop: false,
|
|
2062
|
+
theme: selectTheme
|
|
2063
|
+
});
|
|
2064
|
+
if (result === CANCEL2) return null;
|
|
2065
|
+
return result;
|
|
2066
|
+
}
|
|
2067
|
+
async function pluginInstallFlow() {
|
|
2068
|
+
const answers = await runWizard();
|
|
2069
|
+
if (!answers.confirmed) {
|
|
2070
|
+
if (answers.back) return "back";
|
|
2071
|
+
logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
|
|
2072
|
+
return "done";
|
|
2073
|
+
}
|
|
2074
|
+
const repoManager = createRepositoryManager();
|
|
2075
|
+
const spinner = createSpinner(t("install_cloning"));
|
|
2076
|
+
spinner.start();
|
|
2077
|
+
const repoPath = await repoManager.ensureRepo();
|
|
2078
|
+
spinner.succeed(t("install_repo_ready"));
|
|
2079
|
+
const allPlugins = getAllPlugins();
|
|
2080
|
+
const selectedPlugins = answers.plugins.map(
|
|
2081
|
+
(id) => allPlugins.find((p) => p.id === id)
|
|
2082
|
+
);
|
|
2083
|
+
const total = answers.plugins.length;
|
|
2084
|
+
const results = [];
|
|
2085
|
+
for (let i = 0; i < answers.plugins.length; i++) {
|
|
2086
|
+
const pluginId = answers.plugins[i];
|
|
2087
|
+
const plugin = selectedPlugins[i];
|
|
2088
|
+
const displayName = plugin?.displayName || pluginId;
|
|
2089
|
+
const progress = `[${i + 1}/${total}]`;
|
|
2090
|
+
const pluginSpinner = createSpinner(`${progress} ${t("install_progress")} ${displayName}...`);
|
|
2091
|
+
pluginSpinner.start();
|
|
2092
|
+
const result = await installPlugin({
|
|
2093
|
+
pluginId,
|
|
2094
|
+
tool: answers.tool,
|
|
2095
|
+
level: answers.level,
|
|
2096
|
+
repoPath
|
|
2097
|
+
});
|
|
2098
|
+
if (result.success) {
|
|
2099
|
+
pluginSpinner.succeed(
|
|
2100
|
+
`${progress} ${displayName} \u2014 ${result.skillsCount} skills, ${result.agentsCount} agents`
|
|
2101
|
+
);
|
|
2102
|
+
addInstalledPlugin(pluginId);
|
|
2103
|
+
} else {
|
|
2104
|
+
pluginSpinner.fail(
|
|
2105
|
+
`${progress} ${displayName} \u2014 ${result.errors.join(", ")}`
|
|
2106
|
+
);
|
|
2107
|
+
}
|
|
2108
|
+
results.push(result);
|
|
2109
|
+
}
|
|
2110
|
+
const summary = results.map((result, index) => ({
|
|
2111
|
+
pluginId: result.pluginId,
|
|
2112
|
+
displayName: selectedPlugins[index]?.displayName || result.pluginId,
|
|
2113
|
+
success: result.success,
|
|
2114
|
+
skillsCount: result.skillsCount,
|
|
2115
|
+
agentsCount: result.agentsCount
|
|
2116
|
+
}));
|
|
2117
|
+
printInstallSummary(summary);
|
|
2118
|
+
const configRoot = getConfigRoot(answers.tool, answers.level);
|
|
2119
|
+
printEnhancedSummary(summary, answers.tool, configRoot);
|
|
2120
|
+
return "done";
|
|
2121
|
+
}
|
|
2122
|
+
async function skillInstallFlow() {
|
|
2123
|
+
let step = 0;
|
|
2124
|
+
let tool = "opencode";
|
|
2125
|
+
while (true) {
|
|
2126
|
+
switch (step) {
|
|
2127
|
+
case 0: {
|
|
2128
|
+
const result = await selectToolWithDetection();
|
|
2129
|
+
if (result === "back") return "back";
|
|
2130
|
+
if (result === "cancel") {
|
|
2131
|
+
logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
|
|
2132
|
+
return "done";
|
|
2133
|
+
}
|
|
2134
|
+
tool = result;
|
|
2135
|
+
step = 1;
|
|
2136
|
+
break;
|
|
2137
|
+
}
|
|
2138
|
+
case 1: {
|
|
2139
|
+
const selectedSkills = await interactiveSkillSelect();
|
|
2140
|
+
if (selectedSkills === "back") {
|
|
2141
|
+
step = 0;
|
|
2142
|
+
break;
|
|
2143
|
+
}
|
|
2144
|
+
if (selectedSkills === "cancel") {
|
|
2145
|
+
logger.info("\u5DF2\u53D6\u6D88\u5B89\u88C5");
|
|
2146
|
+
return "done";
|
|
2147
|
+
}
|
|
2148
|
+
if (selectedSkills.length === 0) {
|
|
2149
|
+
logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Skill");
|
|
2150
|
+
return "done";
|
|
2151
|
+
}
|
|
2152
|
+
const repoManager = createRepositoryManager();
|
|
2153
|
+
const spinner = createSpinner(t("install_cloning"));
|
|
2154
|
+
spinner.start();
|
|
2155
|
+
const repoPath = await repoManager.ensureRepo();
|
|
2156
|
+
spinner.succeed(t("install_repo_ready"));
|
|
2157
|
+
logger.info(`${t("skill_install_progress")} ${chalk5.bold(selectedSkills.length)} \u4E2A Skills...`);
|
|
2158
|
+
const results = await installSkills(selectedSkills, tool, "project", repoPath);
|
|
2159
|
+
let successCount = 0;
|
|
2160
|
+
let failCount = 0;
|
|
2161
|
+
for (const result of results) {
|
|
2162
|
+
if (result.success) {
|
|
2163
|
+
logger.success(`${result.skillId}`);
|
|
2164
|
+
successCount++;
|
|
2165
|
+
} else {
|
|
2166
|
+
logger.error(`${result.skillId}: ${result.error}`);
|
|
2167
|
+
failCount++;
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
const configRoot = getConfigRoot(tool, "project");
|
|
2171
|
+
logger.blank();
|
|
2172
|
+
logger.success(`${t("skill_install_done")}: ${chalk5.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk5.red(failCount + " \u5931\u8D25") : chalk5.dim(failCount + " \u5931\u8D25")}`);
|
|
2173
|
+
logger.blank();
|
|
2174
|
+
logger.info(`\u5B89\u88C5\u5230: ${chalk5.cyan(configRoot + "/skills/")}`);
|
|
2175
|
+
logger.info(`\u542F\u52A8 ${chalk5.green(tool)} \u5373\u53EF\u4F7F\u7528`);
|
|
2176
|
+
logger.blank();
|
|
2177
|
+
return "done";
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
// src/commands/list.ts
|
|
2184
|
+
async function listCommand() {
|
|
2185
|
+
try {
|
|
2186
|
+
const repoManager = createRepositoryManager();
|
|
2187
|
+
await repoManager.ensureRepoAndScan();
|
|
2188
|
+
} catch {
|
|
2189
|
+
}
|
|
2190
|
+
const plugins = getAllPlugins();
|
|
2191
|
+
const installed = scanInstalled();
|
|
2192
|
+
const installedMap = new Map(installed.map((p) => [p.id, p]));
|
|
2193
|
+
printPluginList(plugins, installedMap);
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
// src/commands/doctor.ts
|
|
2197
|
+
import chalk6 from "chalk";
|
|
2198
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4, lstatSync as lstatSync3, unlinkSync as unlinkSync3, mkdirSync as mkdirSync6 } from "fs";
|
|
2199
|
+
import { join as join11 } from "path";
|
|
2200
|
+
async function doctorCommand(options = {}) {
|
|
2201
|
+
console.log();
|
|
2202
|
+
console.log(chalk6.bold(` ${t("doctor_title")}`));
|
|
2203
|
+
console.log(chalk6.dim(" " + "\u2500".repeat(46)));
|
|
2204
|
+
let errors = 0;
|
|
2205
|
+
let warnings = 0;
|
|
2206
|
+
let fixes = 0;
|
|
2207
|
+
console.log();
|
|
2208
|
+
console.log(chalk6.bold(` ${t("doctor_tools")}`));
|
|
2209
|
+
const detectedTools = await detectTools();
|
|
2210
|
+
const allTools = getAllTools();
|
|
2211
|
+
for (const tool of allTools) {
|
|
2212
|
+
const detected = detectedTools.find((d) => d.name === tool);
|
|
2213
|
+
if (detected) {
|
|
2214
|
+
console.log(
|
|
2215
|
+
chalk6.green(" \u2713") + ` ${getToolDisplayName(tool)}${detected.version ? ` v${detected.version}` : ""}`
|
|
2216
|
+
);
|
|
2217
|
+
} else {
|
|
2218
|
+
console.log(chalk6.dim(" \u2014") + ` ${getToolDisplayName(tool)} \u2014 \u672A\u5B89\u88C5`);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
console.log();
|
|
2222
|
+
console.log(chalk6.bold(` ${t("doctor_plugins")}`));
|
|
2223
|
+
const plugins = getAllPlugins();
|
|
2224
|
+
const installed = scanInstalled();
|
|
2225
|
+
const installedMap = new Map(installed.map((p) => [p.id, p]));
|
|
2226
|
+
for (const plugin of plugins) {
|
|
2227
|
+
const inst = installedMap.get(plugin.id);
|
|
2228
|
+
if (inst) {
|
|
2229
|
+
console.log(
|
|
2230
|
+
chalk6.green(" \u2713") + ` ${plugin.id.padEnd(30)} ${inst.skillsCount} skills, ${inst.agentsCount} agents`
|
|
2231
|
+
);
|
|
2232
|
+
} else {
|
|
2233
|
+
console.log(chalk6.dim(" \u2014") + ` ${plugin.id.padEnd(30)} \u2014 \u672A\u5B89\u88C5`);
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
console.log();
|
|
2237
|
+
console.log(chalk6.bold(` ${t("doctor_links")}`));
|
|
2238
|
+
const primaryTool = detectedTools[0]?.name || "opencode";
|
|
2239
|
+
const configRoot = getConfigRoot(primaryTool, "project");
|
|
2240
|
+
const skillsDir = getSkillsDir(configRoot);
|
|
2241
|
+
const agentsDir = getAgentsDir(configRoot);
|
|
2242
|
+
if (existsSync11(skillsDir)) {
|
|
2243
|
+
const brokenLinks = checkBrokenLinks(skillsDir);
|
|
2244
|
+
if (brokenLinks === 0) {
|
|
2245
|
+
const count = readdirSync4(skillsDir).length;
|
|
2246
|
+
console.log(chalk6.green(" \u2713") + ` ${skillsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
|
|
2247
|
+
} else {
|
|
2248
|
+
console.log(chalk6.yellow(" \u26A0") + ` ${skillsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
|
|
2249
|
+
warnings++;
|
|
2250
|
+
if (options.fix) {
|
|
2251
|
+
const fixed = fixBrokenLinks(skillsDir);
|
|
2252
|
+
fixes += fixed;
|
|
2253
|
+
console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
} else {
|
|
2257
|
+
console.log(chalk6.dim(" \u2014") + ` ${skillsDir} \u2014 \u4E0D\u5B58\u5728`);
|
|
2258
|
+
if (options.fix) {
|
|
2259
|
+
mkdirSync6(skillsDir, { recursive: true });
|
|
2260
|
+
fixes++;
|
|
2261
|
+
console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${skillsDir}`);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
if (existsSync11(agentsDir)) {
|
|
2265
|
+
const brokenLinks = checkBrokenLinks(agentsDir);
|
|
2266
|
+
if (brokenLinks === 0) {
|
|
2267
|
+
const count = readdirSync4(agentsDir).length;
|
|
2268
|
+
console.log(chalk6.green(" \u2713") + ` ${agentsDir} \u2014 ${count} \u4E2A\u8F6F\u94FE\u63A5\u5747\u6709\u6548`);
|
|
2269
|
+
} else {
|
|
2270
|
+
console.log(chalk6.yellow(" \u26A0") + ` ${agentsDir} \u2014 ${brokenLinks} \u4E2A\u5931\u6548\u94FE\u63A5`);
|
|
2271
|
+
warnings++;
|
|
2272
|
+
if (options.fix) {
|
|
2273
|
+
const fixed = fixBrokenLinks(agentsDir);
|
|
2274
|
+
fixes += fixed;
|
|
2275
|
+
console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_cleaning")}: \u5DF2\u6E05\u7406 ${fixed} \u4E2A\u5931\u6548\u94FE\u63A5`);
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
} else {
|
|
2279
|
+
console.log(chalk6.dim(" \u2014") + ` ${agentsDir} \u2014 \u4E0D\u5B58\u5728`);
|
|
2280
|
+
if (options.fix) {
|
|
2281
|
+
mkdirSync6(agentsDir, { recursive: true });
|
|
2282
|
+
fixes++;
|
|
2283
|
+
console.log(chalk6.green(" \u2713") + ` ${t("doctor_fix_rebuilding")}: \u5DF2\u521B\u5EFA ${agentsDir}`);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
console.log();
|
|
2287
|
+
console.log(chalk6.bold(` ${t("doctor_config")}`));
|
|
2288
|
+
const configFile = getConfigFileName(primaryTool);
|
|
2289
|
+
const configPath = join11(process.cwd(), configFile);
|
|
2290
|
+
if (existsSync11(configPath)) {
|
|
2291
|
+
console.log(chalk6.green(" \u2713") + ` ${configFile} \u5B58\u5728`);
|
|
2292
|
+
} else {
|
|
2293
|
+
console.log(chalk6.dim(" \u2014") + ` ${configFile} \u4E0D\u5B58\u5728`);
|
|
2294
|
+
}
|
|
2295
|
+
console.log();
|
|
2296
|
+
console.log(chalk6.dim(" " + "\u2500".repeat(46)));
|
|
2297
|
+
if (options.fix && fixes > 0) {
|
|
2298
|
+
console.log(
|
|
2299
|
+
` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF, ${fixes} \u9879\u5DF2\u4FEE\u590D`
|
|
2300
|
+
);
|
|
2301
|
+
} else {
|
|
2302
|
+
console.log(
|
|
2303
|
+
` ${t("doctor_result")}: ${warnings} \u4E2A\u8B66\u544A, ${errors} \u4E2A\u9519\u8BEF`
|
|
2304
|
+
);
|
|
2305
|
+
}
|
|
2306
|
+
console.log();
|
|
2307
|
+
}
|
|
2308
|
+
function checkBrokenLinks(dir) {
|
|
2309
|
+
let broken = 0;
|
|
2310
|
+
try {
|
|
2311
|
+
const entries = readdirSync4(dir);
|
|
2312
|
+
for (const entry of entries) {
|
|
2313
|
+
const fullPath = join11(dir, entry);
|
|
2314
|
+
try {
|
|
2315
|
+
const stats = lstatSync3(fullPath);
|
|
2316
|
+
if (stats.isSymbolicLink()) {
|
|
2317
|
+
if (!existsSync11(fullPath)) {
|
|
2318
|
+
broken++;
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
} catch {
|
|
2322
|
+
broken++;
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
} catch {
|
|
2326
|
+
}
|
|
2327
|
+
return broken;
|
|
2328
|
+
}
|
|
2329
|
+
function fixBrokenLinks(dir) {
|
|
2330
|
+
let fixed = 0;
|
|
2331
|
+
try {
|
|
2332
|
+
const entries = readdirSync4(dir);
|
|
2333
|
+
for (const entry of entries) {
|
|
2334
|
+
const fullPath = join11(dir, entry);
|
|
2335
|
+
try {
|
|
2336
|
+
const stats = lstatSync3(fullPath);
|
|
2337
|
+
if (stats.isSymbolicLink()) {
|
|
2338
|
+
if (!existsSync11(fullPath)) {
|
|
2339
|
+
unlinkSync3(fullPath);
|
|
2340
|
+
fixed++;
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
} catch {
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
} catch {
|
|
2347
|
+
}
|
|
2348
|
+
return fixed;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
// src/commands/status.ts
|
|
2352
|
+
import chalk7 from "chalk";
|
|
2353
|
+
async function statusCommand() {
|
|
2354
|
+
const plugins = getAllPlugins();
|
|
2355
|
+
const installed = scanInstalled();
|
|
2356
|
+
const installedMap = new Map(installed.map((p) => [p.id, p]));
|
|
2357
|
+
console.log();
|
|
2358
|
+
console.log(chalk7.bold(" \u5DF2\u5B89\u88C5\u63D2\u4EF6"));
|
|
2359
|
+
console.log();
|
|
2360
|
+
if (installed.length === 0) {
|
|
2361
|
+
console.log(chalk7.dim(" \u6682\u65E0\u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6"));
|
|
2362
|
+
console.log(chalk7.dim(" \u8FD0\u884C install-helper init \u5F00\u59CB\u5B89\u88C5"));
|
|
2363
|
+
console.log();
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
for (const inst of installed) {
|
|
2367
|
+
console.log(
|
|
2368
|
+
chalk7.green(" \u2713") + ` ${inst.displayName}` + chalk7.dim(` (${inst.tool}, ${inst.level})`)
|
|
2369
|
+
);
|
|
2370
|
+
console.log(
|
|
2371
|
+
chalk7.dim(` ${inst.skillsCount} skills, ${inst.agentsCount} agents`)
|
|
2372
|
+
);
|
|
2373
|
+
console.log(chalk7.dim(` ${inst.configRoot}`));
|
|
2374
|
+
console.log(chalk7.dim(` \u5B89\u88C5\u65F6\u95F4: ${inst.installTime}`));
|
|
2375
|
+
console.log();
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
// src/commands/install.ts
|
|
2380
|
+
import { confirm } from "@inquirer/prompts";
|
|
2381
|
+
import chalk8 from "chalk";
|
|
2382
|
+
async function installCommand(names, options) {
|
|
2383
|
+
if (options.list) {
|
|
2384
|
+
try {
|
|
2385
|
+
const repoManager = createRepositoryManager();
|
|
2386
|
+
await repoManager.ensureRepoAndScan();
|
|
2387
|
+
} catch {
|
|
2388
|
+
}
|
|
2389
|
+
listAllSkills();
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
if (names.length === 0) {
|
|
2393
|
+
let tool2;
|
|
2394
|
+
if (options.tool) {
|
|
2395
|
+
tool2 = options.tool;
|
|
2396
|
+
} else {
|
|
2397
|
+
const detected = await selectToolWithDetection();
|
|
2398
|
+
if (detected === "back") return;
|
|
2399
|
+
tool2 = detected;
|
|
2400
|
+
}
|
|
2401
|
+
let step = 1;
|
|
2402
|
+
while (true) {
|
|
2403
|
+
switch (step) {
|
|
2404
|
+
case 1: {
|
|
2405
|
+
const selectedSkills = await interactiveSkillSelect();
|
|
2406
|
+
if (selectedSkills === "back") {
|
|
2407
|
+
const redetected = await selectToolWithDetection();
|
|
2408
|
+
if (redetected === "back") return;
|
|
2409
|
+
tool2 = redetected;
|
|
2410
|
+
break;
|
|
2411
|
+
}
|
|
2412
|
+
if (selectedSkills.length === 0) {
|
|
2413
|
+
logger.info("\u672A\u9009\u62E9\u4EFB\u4F55 Skill");
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
names = selectedSkills;
|
|
2417
|
+
const repoManager = createRepositoryManager();
|
|
2418
|
+
const spinner = createSpinner(t("install_cloning"));
|
|
2419
|
+
spinner.start();
|
|
2420
|
+
const repoPath = await repoManager.ensureRepo();
|
|
2421
|
+
spinner.succeed(t("install_repo_ready"));
|
|
2422
|
+
logger.info(`${t("skill_install_progress")} ${chalk8.bold(names.length)} \u4E2A Skills...`);
|
|
2423
|
+
const results = await installSkills(names, tool2, "project", repoPath);
|
|
2424
|
+
let successCount = 0;
|
|
2425
|
+
let failCount = 0;
|
|
2426
|
+
for (const result of results) {
|
|
2427
|
+
if (result.success) {
|
|
2428
|
+
logger.success(`${result.skillId}`);
|
|
2429
|
+
successCount++;
|
|
2430
|
+
} else {
|
|
2431
|
+
logger.error(`${result.skillId}: ${result.error}`);
|
|
2432
|
+
failCount++;
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
const configRoot = getConfigRoot(tool2, "project");
|
|
2436
|
+
logger.blank();
|
|
2437
|
+
logger.success(`${t("skill_install_done")}: ${chalk8.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk8.red(failCount + " \u5931\u8D25") : chalk8.dim(failCount + " \u5931\u8D25")}`);
|
|
2438
|
+
logger.blank();
|
|
2439
|
+
logger.info(`\u5B89\u88C5\u5230: ${chalk8.cyan(configRoot + "/skills/")}`);
|
|
2440
|
+
logger.info(`\u542F\u52A8 ${chalk8.green(tool2)} \u5373\u53EF\u4F7F\u7528`);
|
|
2441
|
+
logger.blank();
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
const scanSpinner = createSpinner("\u6B63\u5728\u52A0\u8F7D Skills \u5217\u8868...");
|
|
2448
|
+
scanSpinner.start();
|
|
2449
|
+
try {
|
|
2450
|
+
const scanRepoManager = createRepositoryManager();
|
|
2451
|
+
await scanRepoManager.ensureRepoAndScan();
|
|
2452
|
+
scanSpinner.succeed("Skills \u5217\u8868\u52A0\u8F7D\u5B8C\u6210");
|
|
2453
|
+
} catch {
|
|
2454
|
+
scanSpinner.warn("Skills \u5217\u8868\u52A0\u8F7D\u5931\u8D25\uFF0C\u5C06\u4F7F\u7528\u5185\u7F6E\u6570\u636E");
|
|
2455
|
+
}
|
|
2456
|
+
const plugins = [];
|
|
2457
|
+
const skills = [];
|
|
2458
|
+
for (const name of names) {
|
|
2459
|
+
const plugin = findPlugin(name);
|
|
2460
|
+
if (plugin) {
|
|
2461
|
+
plugins.push(plugin.id);
|
|
2462
|
+
continue;
|
|
2463
|
+
}
|
|
2464
|
+
const skill = findSkill(name);
|
|
2465
|
+
if (skill) {
|
|
2466
|
+
skills.push(skill.id);
|
|
2467
|
+
continue;
|
|
2468
|
+
}
|
|
2469
|
+
logger.error(`${t("error_plugin_not_found")}: ${name}`);
|
|
2470
|
+
const allPlugins = getAllPlugins();
|
|
2471
|
+
logger.info("\u53EF\u7528\u63D2\u4EF6:");
|
|
2472
|
+
for (const p of allPlugins) {
|
|
2473
|
+
logger.step(` ${p.id} (${p.aliases.join(", ")})`);
|
|
2474
|
+
}
|
|
2475
|
+
logger.info(`\u53EF\u7528 Skills: ${chalk8.cyan("install-helper install --list")}`);
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
let tool;
|
|
2479
|
+
if (options.tool) {
|
|
2480
|
+
tool = options.tool;
|
|
2481
|
+
} else {
|
|
2482
|
+
const detected = await selectToolWithDetection();
|
|
2483
|
+
if (detected === "back") return;
|
|
2484
|
+
tool = detected;
|
|
2485
|
+
}
|
|
2486
|
+
const level = options.level || "project";
|
|
2487
|
+
if (plugins.length > 0) {
|
|
2488
|
+
const configRoot = getConfigRoot(tool, level);
|
|
2489
|
+
const manifests = readAllManifests(configRoot);
|
|
2490
|
+
const installedSet = new Set(manifests.map((m) => m.team));
|
|
2491
|
+
const pluginsToInstall = [];
|
|
2492
|
+
const skippedPlugins = [];
|
|
2493
|
+
for (const pluginId of plugins) {
|
|
2494
|
+
if (installedSet.has(pluginId) && !options.yes) {
|
|
2495
|
+
const plugin = findPlugin(pluginId);
|
|
2496
|
+
const displayName = plugin?.displayName || pluginId;
|
|
2497
|
+
logger.warn(`${displayName} [${t("install_already_installed")}]`);
|
|
2498
|
+
const shouldReinstall = await confirm({
|
|
2499
|
+
message: `${t("install_reinstall_confirm")}?`,
|
|
2500
|
+
default: false
|
|
2501
|
+
});
|
|
2502
|
+
if (shouldReinstall) {
|
|
2503
|
+
pluginsToInstall.push(pluginId);
|
|
2504
|
+
} else {
|
|
2505
|
+
skippedPlugins.push(pluginId);
|
|
2506
|
+
logger.info(`${displayName} ${t("install_skip")}`);
|
|
2507
|
+
}
|
|
2508
|
+
} else {
|
|
2509
|
+
pluginsToInstall.push(pluginId);
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
if (pluginsToInstall.length > 0) {
|
|
2513
|
+
const repoManager = createRepositoryManager();
|
|
2514
|
+
const spinner = createSpinner(t("install_cloning"));
|
|
2515
|
+
spinner.start();
|
|
2516
|
+
const repoPath = await repoManager.ensureRepo();
|
|
2517
|
+
spinner.succeed(t("install_repo_ready"));
|
|
2518
|
+
const allPlugins = getAllPlugins();
|
|
2519
|
+
const results = await installPlugins(
|
|
2520
|
+
pluginsToInstall,
|
|
2521
|
+
tool,
|
|
2522
|
+
level,
|
|
2523
|
+
repoPath
|
|
2524
|
+
);
|
|
2525
|
+
const summary = results.map((result) => {
|
|
2526
|
+
const plugin = allPlugins.find((p) => p.id === result.pluginId);
|
|
2527
|
+
return {
|
|
2528
|
+
pluginId: result.pluginId,
|
|
2529
|
+
displayName: plugin?.displayName || result.pluginId,
|
|
2530
|
+
success: result.success,
|
|
2531
|
+
skillsCount: result.skillsCount,
|
|
2532
|
+
agentsCount: result.agentsCount
|
|
2533
|
+
};
|
|
2534
|
+
});
|
|
2535
|
+
printInstallSummary(summary);
|
|
2536
|
+
printEnhancedSummary(summary, tool, configRoot);
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
if (skills.length > 0) {
|
|
2540
|
+
const repoManager = createRepositoryManager();
|
|
2541
|
+
const spinner = createSpinner(t("install_cloning"));
|
|
2542
|
+
spinner.start();
|
|
2543
|
+
const repoPath = await repoManager.ensureRepo();
|
|
2544
|
+
spinner.succeed(t("install_repo_ready"));
|
|
2545
|
+
logger.info(`${t("skill_install_progress")} ${chalk8.bold(skills.length)} \u4E2A Skills...`);
|
|
2546
|
+
const results = await installSkills(skills, tool, level, repoPath);
|
|
2547
|
+
let successCount = 0;
|
|
2548
|
+
let failCount = 0;
|
|
2549
|
+
for (const result of results) {
|
|
2550
|
+
if (result.success) {
|
|
2551
|
+
logger.success(`${result.skillId}`);
|
|
2552
|
+
successCount++;
|
|
2553
|
+
} else {
|
|
2554
|
+
logger.error(`${result.skillId}: ${result.error}`);
|
|
2555
|
+
failCount++;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
logger.blank();
|
|
2559
|
+
logger.success(`${t("skill_install_done")}: ${chalk8.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk8.red(failCount + " \u5931\u8D25") : chalk8.dim(failCount + " \u5931\u8D25")}`);
|
|
2560
|
+
logger.blank();
|
|
2561
|
+
logger.info(`\u5B89\u88C5\u5230: ${chalk8.cyan(getConfigRoot(tool, level) + "/skills/")}`);
|
|
2562
|
+
logger.info(`\u542F\u52A8 ${chalk8.green(tool)} \u5373\u53EF\u4F7F\u7528`);
|
|
2563
|
+
logger.blank();
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
async function installPlugins(pluginIds, tool, level, repoPath) {
|
|
2567
|
+
const results = [];
|
|
2568
|
+
const total = pluginIds.length;
|
|
2569
|
+
for (let i = 0; i < pluginIds.length; i++) {
|
|
2570
|
+
const pluginId = pluginIds[i];
|
|
2571
|
+
const progress = `[${i + 1}/${total}]`;
|
|
2572
|
+
const spinner = createSpinner(`${progress} ${t("install_progress")} ${pluginId}...`);
|
|
2573
|
+
spinner.start();
|
|
2574
|
+
const result = await installPlugin({
|
|
2575
|
+
pluginId,
|
|
2576
|
+
tool,
|
|
2577
|
+
level,
|
|
2578
|
+
repoPath
|
|
2579
|
+
});
|
|
2580
|
+
if (result.success) {
|
|
2581
|
+
spinner.succeed(
|
|
2582
|
+
`${progress} ${pluginId} \u2014 ${result.skillsCount} skills, ${result.agentsCount} agents`
|
|
2583
|
+
);
|
|
2584
|
+
addInstalledPlugin(pluginId);
|
|
2585
|
+
} else {
|
|
2586
|
+
spinner.fail(`${progress} ${pluginId} \u2014 ${result.errors.join(", ")}`);
|
|
2587
|
+
}
|
|
2588
|
+
results.push(result);
|
|
2589
|
+
}
|
|
2590
|
+
return results;
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
// src/commands/uninstall.ts
|
|
2594
|
+
import { existsSync as existsSync12, unlinkSync as unlinkSync4, rmdirSync as rmdirSync2, readdirSync as readdirSync5, lstatSync as lstatSync4 } from "fs";
|
|
2595
|
+
import chalk9 from "chalk";
|
|
2596
|
+
async function uninstallCommand(names, options) {
|
|
2597
|
+
const tool = options.tool || "opencode";
|
|
2598
|
+
const level = options.level || "project";
|
|
2599
|
+
const plugins = [];
|
|
2600
|
+
const skills = [];
|
|
2601
|
+
for (const name of names) {
|
|
2602
|
+
const plugin = findPlugin(name);
|
|
2603
|
+
if (plugin) {
|
|
2604
|
+
plugins.push(plugin.id);
|
|
2605
|
+
continue;
|
|
2606
|
+
}
|
|
2607
|
+
const skill = findSkill(name);
|
|
2608
|
+
if (skill) {
|
|
2609
|
+
skills.push(skill.id);
|
|
2610
|
+
continue;
|
|
2611
|
+
}
|
|
2612
|
+
logger.error(`\u672A\u627E\u5230: ${name}`);
|
|
2613
|
+
}
|
|
2614
|
+
for (const pluginId of plugins) {
|
|
2615
|
+
const plugin = findPlugin(pluginId);
|
|
2616
|
+
if (!plugin) continue;
|
|
2617
|
+
const record = readRecord(plugin.id);
|
|
2618
|
+
if (!record) {
|
|
2619
|
+
logger.warn(`${plugin.displayName} \u672A\u627E\u5230\u5B89\u88C5\u8BB0\u5F55\uFF0C\u53EF\u80FD\u672A\u5B89\u88C5\u6216\u5B89\u88C5\u8BB0\u5F55\u5DF2\u635F\u574F`);
|
|
2620
|
+
logger.info(`\u5982\u9700\u5F3A\u5236\u6E05\u7406\uFF0C\u8BF7\u624B\u52A8\u5220\u9664 .opencode/ \u6216 .claude/ \u76EE\u5F55\u4E2D\u7684\u76F8\u5173\u6587\u4EF6`);
|
|
2621
|
+
continue;
|
|
2622
|
+
}
|
|
2623
|
+
logger.info(`\u6B63\u5728\u5378\u8F7D ${plugin.displayName}...`);
|
|
2624
|
+
let removedFiles = 0;
|
|
2625
|
+
let removedDirs = 0;
|
|
2626
|
+
for (const filePath of record.files) {
|
|
2627
|
+
try {
|
|
2628
|
+
if (existsSync12(filePath) || isSymlink3(filePath)) {
|
|
2629
|
+
unlinkSync4(filePath);
|
|
2630
|
+
const name = filePath.split("/").pop() || filePath;
|
|
2631
|
+
logger.step(` \u79FB\u9664: ${name}`);
|
|
2632
|
+
removedFiles++;
|
|
2633
|
+
}
|
|
2634
|
+
} catch {
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
const sortedDirs = [...record.directories].sort((a, b) => b.length - a.length);
|
|
2638
|
+
for (const dirPath of sortedDirs) {
|
|
2639
|
+
try {
|
|
2640
|
+
if (existsSync12(dirPath)) {
|
|
2641
|
+
const entries = readdirSync5(dirPath);
|
|
2642
|
+
if (entries.length === 0) {
|
|
2643
|
+
rmdirSync2(dirPath);
|
|
2644
|
+
const name = dirPath.split("/").pop() || dirPath;
|
|
2645
|
+
logger.step(` \u6E05\u7406\u7A7A\u76EE\u5F55: ${name}/`);
|
|
2646
|
+
removedDirs++;
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
} catch {
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
deleteRecord(plugin.id);
|
|
2653
|
+
removeInstalledPlugin(plugin.id);
|
|
2654
|
+
const totalRemoved = removedFiles + removedDirs;
|
|
2655
|
+
if (totalRemoved > 0) {
|
|
2656
|
+
logger.success(`\u5DF2\u5378\u8F7D ${plugin.displayName}\uFF08\u79FB\u9664 ${removedFiles} \u4E2A\u6587\u4EF6\uFF0C${removedDirs} \u4E2A\u76EE\u5F55\uFF09`);
|
|
2657
|
+
} else {
|
|
2658
|
+
logger.warn(`${plugin.displayName} \u7684\u6587\u4EF6\u5DF2\u4E0D\u5B58\u5728\uFF0C\u5DF2\u6E05\u7406\u5B89\u88C5\u8BB0\u5F55`);
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
if (skills.length > 0) {
|
|
2662
|
+
logger.info(`\u6B63\u5728\u5378\u8F7D ${skills.length} \u4E2A Skills...`);
|
|
2663
|
+
const results = await uninstallSkills(skills, tool, level);
|
|
2664
|
+
let successCount = 0;
|
|
2665
|
+
let failCount = 0;
|
|
2666
|
+
for (const result of results) {
|
|
2667
|
+
if (result.success) {
|
|
2668
|
+
successCount++;
|
|
2669
|
+
} else {
|
|
2670
|
+
failCount++;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
logger.blank();
|
|
2674
|
+
logger.success(`${t("skill_uninstall_done")}: ${chalk9.green(successCount + " \u6210\u529F")}, ${failCount > 0 ? chalk9.red(failCount + " \u5931\u8D25") : chalk9.dim(failCount + " \u5931\u8D25")}`);
|
|
2675
|
+
logger.blank();
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
function isSymlink3(path) {
|
|
2679
|
+
try {
|
|
2680
|
+
return lstatSync4(path).isSymbolicLink();
|
|
2681
|
+
} catch {
|
|
2682
|
+
return false;
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
// src/commands/update.ts
|
|
2687
|
+
import chalk10 from "chalk";
|
|
2688
|
+
async function updateCommand(pluginNames, options) {
|
|
2689
|
+
let tool;
|
|
2690
|
+
if (options.tool) {
|
|
2691
|
+
tool = options.tool;
|
|
2692
|
+
} else {
|
|
2693
|
+
const detected = await selectToolWithDetection();
|
|
2694
|
+
if (detected === "back") return;
|
|
2695
|
+
tool = detected;
|
|
2696
|
+
}
|
|
2697
|
+
const level = options.level || "project";
|
|
2698
|
+
let pluginsToUpdate = [];
|
|
2699
|
+
if (pluginNames.length === 0) {
|
|
2700
|
+
const configRoot = getConfigRoot(tool, level);
|
|
2701
|
+
const manifests = readAllManifests(configRoot);
|
|
2702
|
+
pluginsToUpdate = manifests.map((m) => m.team);
|
|
2703
|
+
if (pluginsToUpdate.length === 0) {
|
|
2704
|
+
logger.info(t("update_no_plugins"));
|
|
2705
|
+
logger.info(`\u4F7F\u7528 ${chalk10.cyan("install-helper install <plugin>")} \u5B89\u88C5\u63D2\u4EF6`);
|
|
2706
|
+
return;
|
|
2707
|
+
}
|
|
2708
|
+
} else {
|
|
2709
|
+
for (const name of pluginNames) {
|
|
2710
|
+
const plugin = findPlugin(name);
|
|
2711
|
+
if (!plugin) {
|
|
2712
|
+
logger.error(`${t("error_plugin_not_found")}: ${name}`);
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
pluginsToUpdate.push(plugin.id);
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
const repoManager = createRepositoryManager();
|
|
2719
|
+
const updateSpinner = createSpinner(t("update_updating") + " cannbot-skills...");
|
|
2720
|
+
updateSpinner.start();
|
|
2721
|
+
await repoManager.updateRepo();
|
|
2722
|
+
const repoPath = repoManager.getRepoPath();
|
|
2723
|
+
updateSpinner.succeed(t("install_repo_ready"));
|
|
2724
|
+
const allPlugins = getAllPlugins();
|
|
2725
|
+
const results = [];
|
|
2726
|
+
const total = pluginsToUpdate.length;
|
|
2727
|
+
for (let i = 0; i < pluginsToUpdate.length; i++) {
|
|
2728
|
+
const pluginId = pluginsToUpdate[i];
|
|
2729
|
+
const plugin = allPlugins.find((p) => p.id === pluginId);
|
|
2730
|
+
const displayName = plugin?.displayName || pluginId;
|
|
2731
|
+
const progress = `[${i + 1}/${total}]`;
|
|
2732
|
+
const pluginSpinner = createSpinner(`${progress} ${t("update_updating")} ${displayName}...`);
|
|
2733
|
+
pluginSpinner.start();
|
|
2734
|
+
const result = await installPlugin({
|
|
2735
|
+
pluginId,
|
|
2736
|
+
tool,
|
|
2737
|
+
level,
|
|
2738
|
+
repoPath
|
|
2739
|
+
});
|
|
2740
|
+
if (result.success) {
|
|
2741
|
+
pluginSpinner.succeed(
|
|
2742
|
+
`${progress} ${displayName} \u2014 ${result.skillsCount} skills, ${result.agentsCount} agents`
|
|
2743
|
+
);
|
|
2744
|
+
} else {
|
|
2745
|
+
pluginSpinner.fail(
|
|
2746
|
+
`${progress} ${displayName} \u2014 ${result.errors.join(", ")}`
|
|
2747
|
+
);
|
|
2748
|
+
}
|
|
2749
|
+
results.push(result);
|
|
2750
|
+
}
|
|
2751
|
+
const summary = results.map((result) => {
|
|
2752
|
+
const plugin = allPlugins.find((p) => p.id === result.pluginId);
|
|
2753
|
+
return {
|
|
2754
|
+
pluginId: result.pluginId,
|
|
2755
|
+
displayName: plugin?.displayName || result.pluginId,
|
|
2756
|
+
success: result.success,
|
|
2757
|
+
skillsCount: result.skillsCount,
|
|
2758
|
+
agentsCount: result.agentsCount
|
|
2759
|
+
};
|
|
2760
|
+
});
|
|
2761
|
+
printInstallSummary(summary);
|
|
2762
|
+
logger.success(t("update_done"));
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
// src/commands/info.ts
|
|
2766
|
+
import chalk11 from "chalk";
|
|
2767
|
+
import { existsSync as existsSync13 } from "fs";
|
|
2768
|
+
import { join as join12 } from "path";
|
|
2769
|
+
async function infoCommand(pluginName) {
|
|
2770
|
+
const plugin = findPlugin(pluginName);
|
|
2771
|
+
if (!plugin) {
|
|
2772
|
+
console.log(chalk11.red(` ${t("info_not_found")}: ${pluginName}`));
|
|
2773
|
+
return;
|
|
2774
|
+
}
|
|
2775
|
+
const installed = scanInstalled();
|
|
2776
|
+
const isInstalled = installed.some((p) => p.id === plugin.id);
|
|
2777
|
+
const statusText = isInstalled ? chalk11.green(`\u2713 ${t("status_installed")}`) : chalk11.dim(`\u2014 ${t("status_not_installed")}`);
|
|
2778
|
+
console.log();
|
|
2779
|
+
console.log(chalk11.bold(` ${plugin.displayName}`));
|
|
2780
|
+
console.log(chalk11.dim(" " + "\u2500".repeat(40)));
|
|
2781
|
+
console.log();
|
|
2782
|
+
console.log(` ${chalk11.dim(t("info_description") + ":")} ${plugin.description}`);
|
|
2783
|
+
console.log(` ${chalk11.dim("ID:")} ${plugin.id}`);
|
|
2784
|
+
console.log(` ${chalk11.dim(t("info_status") + ":")} ${statusText}`);
|
|
2785
|
+
console.log(` ${chalk11.dim(t("info_skills") + ":")} ${plugin.skills}`);
|
|
2786
|
+
console.log(` ${chalk11.dim(t("info_agents") + ":")} ${plugin.agents}`);
|
|
2787
|
+
console.log(` ${chalk11.dim("\u522B\u540D:")} ${plugin.aliases.join(", ")}`);
|
|
2788
|
+
console.log();
|
|
2789
|
+
try {
|
|
2790
|
+
const repoManager = createRepositoryManager();
|
|
2791
|
+
const repoPath = await repoManager.ensureRepo();
|
|
2792
|
+
const quickstartPath = join12(repoPath, plugin.dir, "quickstart.md");
|
|
2793
|
+
if (existsSync13(quickstartPath)) {
|
|
2794
|
+
console.log(` ${chalk11.dim(t("info_quickstart") + ":")} ${quickstartPath}`);
|
|
2795
|
+
}
|
|
2796
|
+
const agentsDir = join12(repoPath, plugin.dir, "agents");
|
|
2797
|
+
if (existsSync13(agentsDir)) {
|
|
2798
|
+
const { readdirSync: readdirSync6 } = await import("fs");
|
|
2799
|
+
const agentFiles = readdirSync6(agentsDir).filter((f) => f.endsWith(".md"));
|
|
2800
|
+
if (agentFiles.length > 0) {
|
|
2801
|
+
console.log();
|
|
2802
|
+
console.log(` ${chalk11.bold("Agents:")}`);
|
|
2803
|
+
for (const agent of agentFiles) {
|
|
2804
|
+
console.log(` \u2022 ${agent.replace(".md", "")}`);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
} catch {
|
|
2809
|
+
}
|
|
2810
|
+
console.log();
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
// src/commands/lang.ts
|
|
2814
|
+
import chalk12 from "chalk";
|
|
2815
|
+
async function langCommand(action, value) {
|
|
2816
|
+
const config = readConfig();
|
|
2817
|
+
if (!action || action === "show") {
|
|
2818
|
+
console.log();
|
|
2819
|
+
console.log(chalk12.bold(` ${t("lang_title")}`));
|
|
2820
|
+
console.log(chalk12.dim(" " + "\u2500".repeat(40)));
|
|
2821
|
+
console.log();
|
|
2822
|
+
console.log(` ${chalk12.dim(t("lang_current") + ":")} ${config.language}`);
|
|
2823
|
+
console.log();
|
|
2824
|
+
console.log(chalk12.dim(" \u652F\u6301\u7684\u8BED\u8A00: zh_CN, en_US"));
|
|
2825
|
+
console.log(chalk12.dim(" \u7528\u6CD5: install-helper lang set <language>"));
|
|
2826
|
+
console.log();
|
|
2827
|
+
return;
|
|
2828
|
+
}
|
|
2829
|
+
if (action === "set") {
|
|
2830
|
+
if (!value) {
|
|
2831
|
+
console.log(chalk12.red(" \u8BF7\u6307\u5B9A\u8BED\u8A00: install-helper lang set zh_CN"));
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
if (value !== "zh_CN" && value !== "en_US") {
|
|
2835
|
+
console.log(chalk12.red(` ${t("lang_invalid")}`));
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2838
|
+
updateConfig({ language: value });
|
|
2839
|
+
setLanguage(value);
|
|
2840
|
+
console.log();
|
|
2841
|
+
console.log(chalk12.green(` \u2713 ${t("lang_set")} ${value}`));
|
|
2842
|
+
console.log();
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
console.log(chalk12.red(` \u672A\u77E5\u64CD\u4F5C: ${action}`));
|
|
2846
|
+
console.log(chalk12.dim(" \u652F\u6301\u7684\u64CD\u4F5C: show, set"));
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
// src/cli.ts
|
|
2850
|
+
function createCLI() {
|
|
2851
|
+
const config = readConfig();
|
|
2852
|
+
if (config.language) {
|
|
2853
|
+
setLanguage(config.language);
|
|
2854
|
+
}
|
|
2855
|
+
const program2 = new Command();
|
|
2856
|
+
program2.name("install-helper").description("CANNBot Helper - Interactive installer for CANN operator development skills").version("0.0.1-beta.0");
|
|
2857
|
+
program2.command("init", { isDefault: false }).description("Run interactive installation wizard").action(async () => {
|
|
2858
|
+
await initCommand();
|
|
2859
|
+
});
|
|
2860
|
+
program2.command("list").description("List available plugins").action(async () => {
|
|
2861
|
+
await listCommand();
|
|
2862
|
+
});
|
|
2863
|
+
program2.command("doctor").description("Run health check").option("--fix", "Auto-fix detected issues").action(async (options) => {
|
|
2864
|
+
await doctorCommand(options);
|
|
2865
|
+
});
|
|
2866
|
+
program2.command("status").description("Show installed plugins").action(async () => {
|
|
2867
|
+
await statusCommand();
|
|
2868
|
+
});
|
|
2869
|
+
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) => {
|
|
2870
|
+
await installCommand(names, options);
|
|
2871
|
+
});
|
|
2872
|
+
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) => {
|
|
2873
|
+
await uninstallCommand(names, options);
|
|
2874
|
+
});
|
|
2875
|
+
program2.command("update [plugins...]").description("Update installed plugins (git pull + reinstall)").option("-t, --tool <tool>", "AI tool (opencode, claude, trae, cursor, copilot)").option("-l, --level <level>", "Install level (project, global)", "project").action(async (plugins, options) => {
|
|
2876
|
+
await updateCommand(plugins, options);
|
|
2877
|
+
});
|
|
2878
|
+
program2.command("info <plugin>").description("Show plugin details").action(async (plugin) => {
|
|
2879
|
+
await infoCommand(plugin);
|
|
2880
|
+
});
|
|
2881
|
+
program2.command("lang [action] [value]").description("Manage language settings (show/set)").action(async (action, value) => {
|
|
2882
|
+
await langCommand(action, value);
|
|
2883
|
+
});
|
|
2884
|
+
program2.action(async () => {
|
|
2885
|
+
await initCommand();
|
|
2886
|
+
});
|
|
2887
|
+
return program2;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
// src/index.ts
|
|
2891
|
+
process.on("uncaughtException", (error) => {
|
|
2892
|
+
if (error.name === "ExitPromptError") {
|
|
2893
|
+
console.log("\n\u5DF2\u53D6\u6D88\u5B89\u88C5");
|
|
2894
|
+
process.exit(0);
|
|
2895
|
+
}
|
|
2896
|
+
throw error;
|
|
2897
|
+
});
|
|
2898
|
+
var program = createCLI();
|
|
2899
|
+
program.parse();
|
|
2900
|
+
//# sourceMappingURL=index.js.map
|