ccjk 2.6.2 → 3.0.2
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/dist/chunks/agent.mjs +1412 -0
- package/dist/chunks/api.mjs +7 -7
- package/dist/chunks/auto-updater.mjs +9 -9
- package/dist/chunks/ccr.mjs +4 -4
- package/dist/chunks/ccu.mjs +1 -1
- package/dist/chunks/claude-code-incremental-manager.mjs +6 -6
- package/dist/chunks/codex.mjs +4 -4
- package/dist/chunks/commands2.mjs +5 -5
- package/dist/chunks/commit.mjs +2 -2
- package/dist/chunks/config-consolidator.mjs +2 -2
- package/dist/chunks/config-switch.mjs +6 -6
- package/dist/chunks/config.mjs +1 -1
- package/dist/chunks/config2.mjs +14 -14
- package/dist/chunks/doctor.mjs +3 -3
- package/dist/chunks/features.mjs +12 -12
- package/dist/chunks/help.mjs +35 -35
- package/dist/chunks/index.mjs +11 -11
- package/dist/chunks/init.mjs +21 -21
- package/dist/chunks/interview.mjs +33 -33
- package/dist/chunks/marketplace.mjs +8 -8
- package/dist/chunks/mcp.mjs +8 -8
- package/dist/chunks/menu.mjs +302 -293
- package/dist/chunks/notification.mjs +5 -5
- package/dist/chunks/onboarding.mjs +7 -7
- package/dist/chunks/package.mjs +1 -1
- package/dist/chunks/permission-manager.mjs +3 -3
- package/dist/chunks/plugin.mjs +24 -24
- package/dist/chunks/prompts.mjs +3 -3
- package/dist/chunks/providers.mjs +13 -13
- package/dist/chunks/session.mjs +17 -17
- package/dist/chunks/skill.mjs +304 -0
- package/dist/chunks/skills-sync.mjs +4 -4
- package/dist/chunks/skills.mjs +2 -2
- package/dist/chunks/team.mjs +1 -1
- package/dist/chunks/uninstall.mjs +8 -8
- package/dist/chunks/update.mjs +4 -4
- package/dist/chunks/upgrade-manager.mjs +3 -3
- package/dist/chunks/version-checker.mjs +6 -6
- package/dist/chunks/workflows.mjs +2 -2
- package/dist/cli.mjs +48 -0
- package/dist/index.d.mts +157 -14
- package/dist/index.d.ts +157 -14
- package/dist/index.mjs +4 -4
- package/dist/shared/{ccjk.rLRHmcqD.mjs → ccjk.BQzWKmC3.mjs} +3 -3
- package/dist/shared/{ccjk.uVUeWAt8.mjs → ccjk.BZT_f2go.mjs} +7 -7
- package/dist/shared/{ccjk.-FoZ3zat.mjs → ccjk.BlPCiSHj.mjs} +10 -10
- package/dist/shared/ccjk.DH6cOJsf.mjs +1674 -0
- package/dist/shared/{ccjk.tB4-Y4Qb.mjs → ccjk.DrMygfCF.mjs} +1 -1
- package/dist/shared/ccjk.tJ08-yZt.mjs +179 -0
- package/package.json +1 -1
- package/dist/shared/ccjk.BhKlRJ0h.mjs +0 -114
|
@@ -0,0 +1,1674 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir, homedir } from 'node:os';
|
|
3
|
+
import { join, dirname, basename } from 'pathe';
|
|
4
|
+
import { x } from 'tinyexec';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_MIN_CONFIDENCE = 0.6;
|
|
8
|
+
const PATTERN_WEIGHT = 0.4;
|
|
9
|
+
const KEYWORD_WEIGHT = 0.3;
|
|
10
|
+
const CONTEXT_WEIGHT = 0.3;
|
|
11
|
+
class IntentEngine {
|
|
12
|
+
rules = /* @__PURE__ */ new Map();
|
|
13
|
+
contextCache = /* @__PURE__ */ new Map();
|
|
14
|
+
cacheTTL = 5e3;
|
|
15
|
+
// 5 seconds
|
|
16
|
+
constructor() {
|
|
17
|
+
}
|
|
18
|
+
// ==========================================================================
|
|
19
|
+
// Rule Management
|
|
20
|
+
// ==========================================================================
|
|
21
|
+
/**
|
|
22
|
+
* Register an intent rule
|
|
23
|
+
*/
|
|
24
|
+
registerRule(rule) {
|
|
25
|
+
this.rules.set(rule.id, rule);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Register multiple intent rules
|
|
29
|
+
*/
|
|
30
|
+
registerRules(rules) {
|
|
31
|
+
for (const rule of rules) {
|
|
32
|
+
this.registerRule(rule);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Unregister an intent rule
|
|
37
|
+
*/
|
|
38
|
+
unregisterRule(ruleId) {
|
|
39
|
+
this.rules.delete(ruleId);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Unregister all rules for a plugin
|
|
43
|
+
*/
|
|
44
|
+
unregisterPluginRules(pluginId) {
|
|
45
|
+
for (const [id, rule] of this.rules) {
|
|
46
|
+
if (rule.pluginId === pluginId) {
|
|
47
|
+
this.rules.delete(id);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get all registered rules
|
|
53
|
+
*/
|
|
54
|
+
getRules() {
|
|
55
|
+
return Array.from(this.rules.values());
|
|
56
|
+
}
|
|
57
|
+
// ==========================================================================
|
|
58
|
+
// Intent Detection
|
|
59
|
+
// ==========================================================================
|
|
60
|
+
/**
|
|
61
|
+
* Detect intent from user input and context
|
|
62
|
+
*
|
|
63
|
+
* @param userInput - User's input text
|
|
64
|
+
* @param cwd - Current working directory
|
|
65
|
+
* @returns Array of intent matches sorted by confidence
|
|
66
|
+
*/
|
|
67
|
+
async detect(userInput, cwd) {
|
|
68
|
+
const context = await this.buildContext(userInput, cwd);
|
|
69
|
+
const matches = [];
|
|
70
|
+
for (const rule of this.rules.values()) {
|
|
71
|
+
const match = this.matchRule(rule, context);
|
|
72
|
+
if (match && match.confidence >= (rule.minConfidence ?? DEFAULT_MIN_CONFIDENCE)) {
|
|
73
|
+
matches.push(match);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
matches.sort((a, b) => {
|
|
77
|
+
const confDiff = b.confidence - a.confidence;
|
|
78
|
+
if (Math.abs(confDiff) > 0.1) {
|
|
79
|
+
return confDiff;
|
|
80
|
+
}
|
|
81
|
+
const ruleA = this.rules.get(a.intentId);
|
|
82
|
+
const ruleB = this.rules.get(b.intentId);
|
|
83
|
+
return (ruleB?.priority ?? 0) - (ruleA?.priority ?? 0);
|
|
84
|
+
});
|
|
85
|
+
return matches;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get the best matching intent
|
|
89
|
+
*/
|
|
90
|
+
async detectBest(userInput, cwd) {
|
|
91
|
+
const matches = await this.detect(userInput, cwd);
|
|
92
|
+
return matches.length > 0 ? matches[0] : null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check if any intent should auto-execute
|
|
96
|
+
*/
|
|
97
|
+
async detectAutoExecute(userInput, cwd) {
|
|
98
|
+
const matches = await this.detect(userInput, cwd);
|
|
99
|
+
return matches.find((m) => m.autoExecute && m.confidence >= 0.8) ?? null;
|
|
100
|
+
}
|
|
101
|
+
// ==========================================================================
|
|
102
|
+
// Context Building
|
|
103
|
+
// ==========================================================================
|
|
104
|
+
/**
|
|
105
|
+
* Build detection context from user input and environment
|
|
106
|
+
*/
|
|
107
|
+
async buildContext(userInput, cwd) {
|
|
108
|
+
const [gitStatus, projectType, activeSignals] = await Promise.all([
|
|
109
|
+
this.detectGitStatus(cwd),
|
|
110
|
+
this.detectProjectType(cwd),
|
|
111
|
+
this.detectActiveSignals(cwd)
|
|
112
|
+
]);
|
|
113
|
+
return {
|
|
114
|
+
userInput,
|
|
115
|
+
cwd,
|
|
116
|
+
gitStatus,
|
|
117
|
+
projectType,
|
|
118
|
+
activeSignals
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Detect git status
|
|
123
|
+
*/
|
|
124
|
+
async detectGitStatus(cwd) {
|
|
125
|
+
const isRepo = existsSync(join(cwd, ".git"));
|
|
126
|
+
if (!isRepo) {
|
|
127
|
+
return { isRepo: false, hasChanges: false, hasStaged: false };
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const statusResult = await x("git", ["status", "--porcelain"], { nodeOptions: { cwd } });
|
|
131
|
+
const hasChanges = statusResult.stdout.trim().length > 0;
|
|
132
|
+
const stagedResult = await x("git", ["diff", "--cached", "--name-only"], { nodeOptions: { cwd } });
|
|
133
|
+
const hasStaged = stagedResult.stdout.trim().length > 0;
|
|
134
|
+
const branchResult = await x("git", ["branch", "--show-current"], { nodeOptions: { cwd } });
|
|
135
|
+
const branch = branchResult.stdout.trim();
|
|
136
|
+
const remoteResult = await x("git", ["remote", "get-url", "origin"], { nodeOptions: { cwd } });
|
|
137
|
+
const remote = remoteResult.stdout.trim() || void 0;
|
|
138
|
+
return { isRepo, hasChanges, hasStaged, branch, remote };
|
|
139
|
+
} catch {
|
|
140
|
+
return { isRepo, hasChanges: false, hasStaged: false };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Detect project type from files
|
|
145
|
+
*/
|
|
146
|
+
async detectProjectType(cwd) {
|
|
147
|
+
if (existsSync(join(cwd, "next.config.js")) || existsSync(join(cwd, "next.config.mjs")) || existsSync(join(cwd, "next.config.ts"))) {
|
|
148
|
+
return "nextjs";
|
|
149
|
+
}
|
|
150
|
+
if (existsSync(join(cwd, "vue.config.js")) || existsSync(join(cwd, "vite.config.ts"))) {
|
|
151
|
+
const pkgPath2 = join(cwd, "package.json");
|
|
152
|
+
if (existsSync(pkgPath2)) {
|
|
153
|
+
try {
|
|
154
|
+
const pkg = await import(pkgPath2);
|
|
155
|
+
if (pkg.dependencies?.vue || pkg.devDependencies?.vue) {
|
|
156
|
+
return "vue";
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const pkgPath = join(cwd, "package.json");
|
|
163
|
+
if (existsSync(pkgPath)) {
|
|
164
|
+
try {
|
|
165
|
+
const pkg = await import(pkgPath);
|
|
166
|
+
if (pkg.dependencies?.react || pkg.devDependencies?.react) {
|
|
167
|
+
return "react";
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (existsSync(join(cwd, "tsconfig.json"))) {
|
|
173
|
+
return "typescript";
|
|
174
|
+
}
|
|
175
|
+
if (existsSync(join(cwd, "package.json"))) {
|
|
176
|
+
return "nodejs";
|
|
177
|
+
}
|
|
178
|
+
if (existsSync(join(cwd, "requirements.txt")) || existsSync(join(cwd, "pyproject.toml"))) {
|
|
179
|
+
return "python";
|
|
180
|
+
}
|
|
181
|
+
if (existsSync(join(cwd, "Cargo.toml"))) {
|
|
182
|
+
return "rust";
|
|
183
|
+
}
|
|
184
|
+
if (existsSync(join(cwd, "go.mod"))) {
|
|
185
|
+
return "go";
|
|
186
|
+
}
|
|
187
|
+
return "unknown";
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Detect active context signals
|
|
191
|
+
*/
|
|
192
|
+
async detectActiveSignals(cwd) {
|
|
193
|
+
const signals = [];
|
|
194
|
+
const gitStatus = await this.detectGitStatus(cwd);
|
|
195
|
+
if (gitStatus.isRepo) {
|
|
196
|
+
signals.push("git_is_repo");
|
|
197
|
+
if (gitStatus.hasChanges)
|
|
198
|
+
signals.push("git_has_changes");
|
|
199
|
+
if (gitStatus.hasStaged)
|
|
200
|
+
signals.push("git_has_staged");
|
|
201
|
+
if (gitStatus.remote)
|
|
202
|
+
signals.push("git_has_remote");
|
|
203
|
+
}
|
|
204
|
+
if (existsSync(join(cwd, "package.json")))
|
|
205
|
+
signals.push("has_package_json");
|
|
206
|
+
if (existsSync(join(cwd, "tsconfig.json")))
|
|
207
|
+
signals.push("has_tsconfig");
|
|
208
|
+
if (existsSync(join(cwd, "Dockerfile")) || existsSync(join(cwd, "docker-compose.yml")))
|
|
209
|
+
signals.push("has_dockerfile");
|
|
210
|
+
if (existsSync(join(cwd, "tests")) || existsSync(join(cwd, "__tests__")) || existsSync(join(cwd, "test"))) {
|
|
211
|
+
signals.push("has_tests");
|
|
212
|
+
}
|
|
213
|
+
if (cwd.includes("/src") || existsSync(join(cwd, "src"))) {
|
|
214
|
+
signals.push("in_src_directory");
|
|
215
|
+
}
|
|
216
|
+
return signals;
|
|
217
|
+
}
|
|
218
|
+
// ==========================================================================
|
|
219
|
+
// Rule Matching
|
|
220
|
+
// ==========================================================================
|
|
221
|
+
/**
|
|
222
|
+
* Match a single rule against context
|
|
223
|
+
*/
|
|
224
|
+
matchRule(rule, context) {
|
|
225
|
+
const matchedPatterns = [];
|
|
226
|
+
const matchedSignals = [];
|
|
227
|
+
let patternScore = 0;
|
|
228
|
+
for (const pattern of rule.patterns) {
|
|
229
|
+
try {
|
|
230
|
+
const regex = new RegExp(pattern, "i");
|
|
231
|
+
if (regex.test(context.userInput)) {
|
|
232
|
+
matchedPatterns.push(pattern);
|
|
233
|
+
patternScore += 1 / rule.patterns.length;
|
|
234
|
+
}
|
|
235
|
+
} catch {
|
|
236
|
+
if (context.userInput.toLowerCase().includes(pattern.toLowerCase())) {
|
|
237
|
+
matchedPatterns.push(pattern);
|
|
238
|
+
patternScore += 1 / rule.patterns.length;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
let keywordScore = 0;
|
|
243
|
+
const inputLower = context.userInput.toLowerCase();
|
|
244
|
+
for (const keyword of rule.keywords) {
|
|
245
|
+
if (inputLower.includes(keyword.toLowerCase())) {
|
|
246
|
+
keywordScore += 1 / rule.keywords.length;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
let contextScore = 0;
|
|
250
|
+
if (rule.contextSignals.length > 0) {
|
|
251
|
+
for (const signal of rule.contextSignals) {
|
|
252
|
+
if (context.activeSignals.includes(signal)) {
|
|
253
|
+
matchedSignals.push(signal);
|
|
254
|
+
contextScore += 1 / rule.contextSignals.length;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
contextScore = 1;
|
|
259
|
+
}
|
|
260
|
+
let fileBonus = 0;
|
|
261
|
+
if (rule.filePatterns && rule.filePatterns.length > 0) {
|
|
262
|
+
for (const pattern of rule.filePatterns) {
|
|
263
|
+
if (existsSync(join(context.cwd, pattern))) {
|
|
264
|
+
fileBonus += 0.1;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const confidence = Math.min(1, patternScore * PATTERN_WEIGHT + keywordScore * KEYWORD_WEIGHT + contextScore * CONTEXT_WEIGHT + fileBonus);
|
|
269
|
+
if (patternScore === 0 && keywordScore === 0) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
pluginId: rule.pluginId,
|
|
274
|
+
intentId: rule.id,
|
|
275
|
+
confidence,
|
|
276
|
+
matchedPatterns,
|
|
277
|
+
matchedSignals,
|
|
278
|
+
suggestedAction: rule.name,
|
|
279
|
+
autoExecute: rule.autoExecute && confidence >= 0.8
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
// ==========================================================================
|
|
283
|
+
// Cache Management
|
|
284
|
+
// ==========================================================================
|
|
285
|
+
/**
|
|
286
|
+
* Clear context cache
|
|
287
|
+
*/
|
|
288
|
+
clearCache() {
|
|
289
|
+
this.contextCache.clear();
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Get cached value or compute
|
|
293
|
+
*/
|
|
294
|
+
async getCachedOrCompute(key, compute) {
|
|
295
|
+
const cached = this.contextCache.get(key);
|
|
296
|
+
if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
|
|
297
|
+
return cached.value;
|
|
298
|
+
}
|
|
299
|
+
const value = await compute();
|
|
300
|
+
this.contextCache.set(key, { value, timestamp: Date.now() });
|
|
301
|
+
return value;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const DEFAULT_INTENT_RULES = [
|
|
305
|
+
// Git Commit Intent
|
|
306
|
+
{
|
|
307
|
+
id: "intent:git-commit",
|
|
308
|
+
name: { "en": "Git Commit", "zh-CN": "Git \u63D0\u4EA4" },
|
|
309
|
+
patterns: [
|
|
310
|
+
"\u63D0\u4EA4.*\u4EE3\u7801",
|
|
311
|
+
"\u63D0\u4EA4.*\u66F4\u6539",
|
|
312
|
+
"\u63D0\u4EA4.*\u4FEE\u6539",
|
|
313
|
+
"commit.*changes",
|
|
314
|
+
"commit.*code",
|
|
315
|
+
"save.*changes",
|
|
316
|
+
"\u4FDD\u5B58.*\u4FEE\u6539"
|
|
317
|
+
],
|
|
318
|
+
keywords: ["commit", "\u63D0\u4EA4", "save", "push", "\u4FDD\u5B58"],
|
|
319
|
+
contextSignals: ["git_is_repo", "git_has_changes"],
|
|
320
|
+
filePatterns: [".git/"],
|
|
321
|
+
priority: 90,
|
|
322
|
+
pluginId: "git-helper",
|
|
323
|
+
skillId: "smart-commit",
|
|
324
|
+
autoExecute: false
|
|
325
|
+
},
|
|
326
|
+
// Code Review Intent
|
|
327
|
+
{
|
|
328
|
+
id: "intent:code-review",
|
|
329
|
+
name: { "en": "Code Review", "zh-CN": "\u4EE3\u7801\u5BA1\u67E5" },
|
|
330
|
+
patterns: [
|
|
331
|
+
"\u5BA1\u67E5.*\u4EE3\u7801",
|
|
332
|
+
"review.*code",
|
|
333
|
+
"\u68C0\u67E5.*\u4EE3\u7801",
|
|
334
|
+
"check.*code",
|
|
335
|
+
"\u4EE3\u7801.*\u95EE\u9898"
|
|
336
|
+
],
|
|
337
|
+
keywords: ["review", "\u5BA1\u67E5", "check", "\u68C0\u67E5", "lint"],
|
|
338
|
+
contextSignals: ["git_has_changes", "in_src_directory"],
|
|
339
|
+
priority: 85,
|
|
340
|
+
pluginId: "code-reviewer",
|
|
341
|
+
autoExecute: false
|
|
342
|
+
},
|
|
343
|
+
// Test Generation Intent
|
|
344
|
+
{
|
|
345
|
+
id: "intent:generate-tests",
|
|
346
|
+
name: { "en": "Generate Tests", "zh-CN": "\u751F\u6210\u6D4B\u8BD5" },
|
|
347
|
+
patterns: [
|
|
348
|
+
"\u5199.*\u6D4B\u8BD5",
|
|
349
|
+
"\u751F\u6210.*\u6D4B\u8BD5",
|
|
350
|
+
"write.*test",
|
|
351
|
+
"generate.*test",
|
|
352
|
+
"create.*test",
|
|
353
|
+
"\u6DFB\u52A0.*\u6D4B\u8BD5"
|
|
354
|
+
],
|
|
355
|
+
keywords: ["test", "\u6D4B\u8BD5", "spec", "jest", "vitest"],
|
|
356
|
+
contextSignals: ["has_package_json", "has_tests"],
|
|
357
|
+
priority: 80,
|
|
358
|
+
pluginId: "test-generator",
|
|
359
|
+
autoExecute: false
|
|
360
|
+
},
|
|
361
|
+
// Documentation Intent
|
|
362
|
+
{
|
|
363
|
+
id: "intent:generate-docs",
|
|
364
|
+
name: { "en": "Generate Documentation", "zh-CN": "\u751F\u6210\u6587\u6863" },
|
|
365
|
+
patterns: [
|
|
366
|
+
"\u5199.*\u6587\u6863",
|
|
367
|
+
"\u751F\u6210.*\u6587\u6863",
|
|
368
|
+
"write.*doc",
|
|
369
|
+
"generate.*doc",
|
|
370
|
+
"\u6DFB\u52A0.*\u6CE8\u91CA",
|
|
371
|
+
"add.*comment"
|
|
372
|
+
],
|
|
373
|
+
keywords: ["doc", "\u6587\u6863", "readme", "comment", "\u6CE8\u91CA"],
|
|
374
|
+
contextSignals: ["in_src_directory"],
|
|
375
|
+
priority: 75,
|
|
376
|
+
pluginId: "doc-generator",
|
|
377
|
+
autoExecute: false
|
|
378
|
+
},
|
|
379
|
+
// Deploy Intent
|
|
380
|
+
{
|
|
381
|
+
id: "intent:deploy",
|
|
382
|
+
name: { "en": "Deploy", "zh-CN": "\u90E8\u7F72" },
|
|
383
|
+
patterns: [
|
|
384
|
+
"\u90E8\u7F72.*\u9879\u76EE",
|
|
385
|
+
"\u90E8\u7F72.*\u5E94\u7528",
|
|
386
|
+
"deploy.*project",
|
|
387
|
+
"deploy.*app",
|
|
388
|
+
"\u53D1\u5E03.*\u7EBF\u4E0A"
|
|
389
|
+
],
|
|
390
|
+
keywords: ["deploy", "\u90E8\u7F72", "publish", "\u53D1\u5E03", "release"],
|
|
391
|
+
contextSignals: ["has_package_json", "git_has_remote"],
|
|
392
|
+
priority: 70,
|
|
393
|
+
pluginId: "vercel-deploy",
|
|
394
|
+
autoExecute: false
|
|
395
|
+
},
|
|
396
|
+
// Docker Intent
|
|
397
|
+
{
|
|
398
|
+
id: "intent:docker",
|
|
399
|
+
name: { "en": "Docker Operations", "zh-CN": "Docker \u64CD\u4F5C" },
|
|
400
|
+
patterns: [
|
|
401
|
+
"\u521B\u5EFA.*dockerfile",
|
|
402
|
+
"\u751F\u6210.*docker",
|
|
403
|
+
"create.*dockerfile",
|
|
404
|
+
"generate.*docker",
|
|
405
|
+
"\u5BB9\u5668\u5316"
|
|
406
|
+
],
|
|
407
|
+
keywords: ["docker", "container", "\u5BB9\u5668", "dockerfile"],
|
|
408
|
+
contextSignals: ["has_package_json"],
|
|
409
|
+
filePatterns: ["Dockerfile", "docker-compose.yml"],
|
|
410
|
+
priority: 65,
|
|
411
|
+
pluginId: "docker-helper",
|
|
412
|
+
autoExecute: false
|
|
413
|
+
},
|
|
414
|
+
// Refactor Intent
|
|
415
|
+
{
|
|
416
|
+
id: "intent:refactor",
|
|
417
|
+
name: { "en": "Refactor Code", "zh-CN": "\u91CD\u6784\u4EE3\u7801" },
|
|
418
|
+
patterns: [
|
|
419
|
+
"\u91CD\u6784.*\u4EE3\u7801",
|
|
420
|
+
"\u4F18\u5316.*\u4EE3\u7801",
|
|
421
|
+
"refactor.*code",
|
|
422
|
+
"optimize.*code",
|
|
423
|
+
"\u7B80\u5316.*\u4EE3\u7801",
|
|
424
|
+
"simplify.*code"
|
|
425
|
+
],
|
|
426
|
+
keywords: ["refactor", "\u91CD\u6784", "optimize", "\u4F18\u5316", "simplify", "\u7B80\u5316"],
|
|
427
|
+
contextSignals: ["in_src_directory"],
|
|
428
|
+
priority: 60,
|
|
429
|
+
pluginId: "code-simplifier",
|
|
430
|
+
autoExecute: false
|
|
431
|
+
}
|
|
432
|
+
];
|
|
433
|
+
let engineInstance = null;
|
|
434
|
+
function getIntentEngine() {
|
|
435
|
+
if (!engineInstance) {
|
|
436
|
+
engineInstance = new IntentEngine();
|
|
437
|
+
engineInstance.registerRules(DEFAULT_INTENT_RULES);
|
|
438
|
+
}
|
|
439
|
+
return engineInstance;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const DEFAULT_TIMEOUT = 3e4;
|
|
443
|
+
const MAX_TIMEOUT = 3e5;
|
|
444
|
+
const MAX_OUTPUT_SIZE = 1024 * 1024;
|
|
445
|
+
const SCRIPT_INTERPRETERS = {
|
|
446
|
+
bash: ["bash", "-e"],
|
|
447
|
+
node: ["node"],
|
|
448
|
+
python: ["python3", "-u"],
|
|
449
|
+
deno: ["deno", "run", "--allow-all"],
|
|
450
|
+
bun: ["bun", "run"]
|
|
451
|
+
};
|
|
452
|
+
const SCRIPT_EXTENSIONS = {
|
|
453
|
+
bash: [".sh", ".bash"],
|
|
454
|
+
node: [".js", ".mjs", ".cjs"],
|
|
455
|
+
python: [".py"],
|
|
456
|
+
deno: [".ts", ".js"],
|
|
457
|
+
bun: [".ts", ".js"]
|
|
458
|
+
};
|
|
459
|
+
class ScriptRunner {
|
|
460
|
+
grantedPermissions = /* @__PURE__ */ new Set();
|
|
461
|
+
runningProcesses = /* @__PURE__ */ new Map();
|
|
462
|
+
constructor() {
|
|
463
|
+
this.grantedPermissions.add("file:read");
|
|
464
|
+
this.grantedPermissions.add("git:read");
|
|
465
|
+
this.grantedPermissions.add("env:read");
|
|
466
|
+
}
|
|
467
|
+
// ==========================================================================
|
|
468
|
+
// Permission Management
|
|
469
|
+
// ==========================================================================
|
|
470
|
+
/**
|
|
471
|
+
* Grant a permission
|
|
472
|
+
*/
|
|
473
|
+
grantPermission(permission) {
|
|
474
|
+
this.grantedPermissions.add(permission);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Grant multiple permissions
|
|
478
|
+
*/
|
|
479
|
+
grantPermissions(permissions) {
|
|
480
|
+
for (const p of permissions) {
|
|
481
|
+
this.grantedPermissions.add(p);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Revoke a permission
|
|
486
|
+
*/
|
|
487
|
+
revokePermission(permission) {
|
|
488
|
+
this.grantedPermissions.delete(permission);
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Check if permission is granted
|
|
492
|
+
*/
|
|
493
|
+
hasPermission(permission) {
|
|
494
|
+
return this.grantedPermissions.has(permission);
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Check if all required permissions are granted
|
|
498
|
+
*/
|
|
499
|
+
hasAllPermissions(permissions) {
|
|
500
|
+
return permissions.every((p) => this.grantedPermissions.has(p));
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Get missing permissions
|
|
504
|
+
*/
|
|
505
|
+
getMissingPermissions(required) {
|
|
506
|
+
return required.filter((p) => !this.grantedPermissions.has(p));
|
|
507
|
+
}
|
|
508
|
+
// ==========================================================================
|
|
509
|
+
// Script Execution
|
|
510
|
+
// ==========================================================================
|
|
511
|
+
/**
|
|
512
|
+
* Execute a script
|
|
513
|
+
*
|
|
514
|
+
* @param script - Script definition
|
|
515
|
+
* @param pluginPath - Path to the plugin directory
|
|
516
|
+
* @param options - Execution options
|
|
517
|
+
* @returns Script execution result
|
|
518
|
+
*/
|
|
519
|
+
async execute(script, pluginPath, options = {}) {
|
|
520
|
+
const startTime = Date.now();
|
|
521
|
+
const missingPermissions = this.getMissingPermissions(script.permissions);
|
|
522
|
+
if (missingPermissions.length > 0) {
|
|
523
|
+
return {
|
|
524
|
+
success: false,
|
|
525
|
+
exitCode: -1,
|
|
526
|
+
stdout: "",
|
|
527
|
+
stderr: `Permission denied. Missing permissions: ${missingPermissions.join(", ")}`,
|
|
528
|
+
duration: 0
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
const scriptPath = join(pluginPath, script.path);
|
|
532
|
+
if (!existsSync(scriptPath)) {
|
|
533
|
+
return {
|
|
534
|
+
success: false,
|
|
535
|
+
exitCode: -1,
|
|
536
|
+
stdout: "",
|
|
537
|
+
stderr: `Script not found: ${scriptPath}`,
|
|
538
|
+
duration: 0
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
const interpreter = this.getInterpreter(script.type);
|
|
542
|
+
if (!interpreter) {
|
|
543
|
+
return {
|
|
544
|
+
success: false,
|
|
545
|
+
exitCode: -1,
|
|
546
|
+
stdout: "",
|
|
547
|
+
stderr: `Unsupported script type: ${script.type}`,
|
|
548
|
+
duration: 0
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
const args = [...interpreter.slice(1), scriptPath, ...options.args ?? script.defaultArgs ?? []];
|
|
552
|
+
const command = interpreter[0];
|
|
553
|
+
const env = this.buildEnvironment(script, options);
|
|
554
|
+
const cwd = options.cwd ?? dirname(scriptPath);
|
|
555
|
+
const timeout = Math.min(options.timeout ?? script.timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT);
|
|
556
|
+
try {
|
|
557
|
+
const result = await this.spawn(command, args, {
|
|
558
|
+
cwd,
|
|
559
|
+
env,
|
|
560
|
+
timeout,
|
|
561
|
+
stdin: options.stdin,
|
|
562
|
+
captureOutput: options.captureOutput ?? true,
|
|
563
|
+
background: options.background,
|
|
564
|
+
scriptId: `${script.name}-${Date.now()}`
|
|
565
|
+
});
|
|
566
|
+
return {
|
|
567
|
+
...result,
|
|
568
|
+
duration: Date.now() - startTime
|
|
569
|
+
};
|
|
570
|
+
} catch (error) {
|
|
571
|
+
return {
|
|
572
|
+
success: false,
|
|
573
|
+
exitCode: -1,
|
|
574
|
+
stdout: "",
|
|
575
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
576
|
+
duration: Date.now() - startTime
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Execute a script from raw content
|
|
582
|
+
*/
|
|
583
|
+
async executeRaw(content, type, options = {}) {
|
|
584
|
+
const startTime = Date.now();
|
|
585
|
+
if (!this.hasPermission("shell:execute")) {
|
|
586
|
+
return {
|
|
587
|
+
success: false,
|
|
588
|
+
exitCode: -1,
|
|
589
|
+
stdout: "",
|
|
590
|
+
stderr: "Permission denied. shell:execute permission required for raw script execution.",
|
|
591
|
+
duration: 0
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
const interpreter = this.getInterpreter(type);
|
|
595
|
+
if (!interpreter) {
|
|
596
|
+
return {
|
|
597
|
+
success: false,
|
|
598
|
+
exitCode: -1,
|
|
599
|
+
stdout: "",
|
|
600
|
+
stderr: `Unsupported script type: ${type}`,
|
|
601
|
+
duration: 0
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
if (type === "bash") {
|
|
605
|
+
const args = ["-c", content, ...options.args ?? []];
|
|
606
|
+
const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT);
|
|
607
|
+
try {
|
|
608
|
+
const result = await this.spawn("bash", args, {
|
|
609
|
+
cwd: options.cwd,
|
|
610
|
+
env: { ...process.env, ...options.env },
|
|
611
|
+
timeout,
|
|
612
|
+
stdin: options.stdin,
|
|
613
|
+
captureOutput: options.captureOutput ?? true
|
|
614
|
+
});
|
|
615
|
+
return {
|
|
616
|
+
...result,
|
|
617
|
+
duration: Date.now() - startTime
|
|
618
|
+
};
|
|
619
|
+
} catch (error) {
|
|
620
|
+
return {
|
|
621
|
+
success: false,
|
|
622
|
+
exitCode: -1,
|
|
623
|
+
stdout: "",
|
|
624
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
625
|
+
duration: Date.now() - startTime
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
const tempPath = join(tmpdir(), `ccjk-script-${Date.now()}${SCRIPT_EXTENSIONS[type][0]}`);
|
|
630
|
+
const { writeFileSync, unlinkSync } = await import('node:fs');
|
|
631
|
+
try {
|
|
632
|
+
writeFileSync(tempPath, content, "utf-8");
|
|
633
|
+
const args = [...interpreter.slice(1), tempPath, ...options.args ?? []];
|
|
634
|
+
const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT);
|
|
635
|
+
const result = await this.spawn(interpreter[0], args, {
|
|
636
|
+
cwd: options.cwd,
|
|
637
|
+
env: { ...process.env, ...options.env },
|
|
638
|
+
timeout,
|
|
639
|
+
stdin: options.stdin,
|
|
640
|
+
captureOutput: options.captureOutput ?? true
|
|
641
|
+
});
|
|
642
|
+
return {
|
|
643
|
+
...result,
|
|
644
|
+
duration: Date.now() - startTime
|
|
645
|
+
};
|
|
646
|
+
} catch (error) {
|
|
647
|
+
return {
|
|
648
|
+
success: false,
|
|
649
|
+
exitCode: -1,
|
|
650
|
+
stdout: "",
|
|
651
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
652
|
+
duration: Date.now() - startTime
|
|
653
|
+
};
|
|
654
|
+
} finally {
|
|
655
|
+
try {
|
|
656
|
+
unlinkSync(tempPath);
|
|
657
|
+
} catch {
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Execute inline bash command
|
|
663
|
+
*/
|
|
664
|
+
async bash(command, options = {}) {
|
|
665
|
+
return this.executeRaw(command, "bash", options);
|
|
666
|
+
}
|
|
667
|
+
// ==========================================================================
|
|
668
|
+
// Process Management
|
|
669
|
+
// ==========================================================================
|
|
670
|
+
/**
|
|
671
|
+
* Kill a running script
|
|
672
|
+
*/
|
|
673
|
+
kill(scriptId) {
|
|
674
|
+
const process2 = this.runningProcesses.get(scriptId);
|
|
675
|
+
if (process2) {
|
|
676
|
+
process2.kill();
|
|
677
|
+
this.runningProcesses.delete(scriptId);
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Kill all running scripts
|
|
684
|
+
*/
|
|
685
|
+
killAll() {
|
|
686
|
+
for (const [id, process2] of this.runningProcesses) {
|
|
687
|
+
process2.kill();
|
|
688
|
+
this.runningProcesses.delete(id);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Get running script IDs
|
|
693
|
+
*/
|
|
694
|
+
getRunningScripts() {
|
|
695
|
+
return Array.from(this.runningProcesses.keys());
|
|
696
|
+
}
|
|
697
|
+
// ==========================================================================
|
|
698
|
+
// Private Helpers
|
|
699
|
+
// ==========================================================================
|
|
700
|
+
/**
|
|
701
|
+
* Get interpreter for script type
|
|
702
|
+
*/
|
|
703
|
+
getInterpreter(type) {
|
|
704
|
+
return SCRIPT_INTERPRETERS[type] ?? null;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Build environment variables
|
|
708
|
+
*/
|
|
709
|
+
buildEnvironment(script, options) {
|
|
710
|
+
const env = {};
|
|
711
|
+
if (this.hasPermission("env:read")) {
|
|
712
|
+
const safeVars = ["PATH", "HOME", "USER", "SHELL", "LANG", "LC_ALL", "TERM", "NODE_ENV"];
|
|
713
|
+
for (const key of safeVars) {
|
|
714
|
+
if (process.env[key]) {
|
|
715
|
+
env[key] = process.env[key];
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (script.env) {
|
|
720
|
+
Object.assign(env, script.env);
|
|
721
|
+
}
|
|
722
|
+
if (options.env) {
|
|
723
|
+
Object.assign(env, options.env);
|
|
724
|
+
}
|
|
725
|
+
env.CCJK_PLUGIN = "true";
|
|
726
|
+
env.CCJK_VERSION = "2.0";
|
|
727
|
+
return env;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Spawn a process
|
|
731
|
+
*/
|
|
732
|
+
spawn(command, args, options) {
|
|
733
|
+
return new Promise((resolve, reject) => {
|
|
734
|
+
let stdout = "";
|
|
735
|
+
let stderr = "";
|
|
736
|
+
let killed = false;
|
|
737
|
+
const proc = spawn(command, args, {
|
|
738
|
+
cwd: options.cwd,
|
|
739
|
+
env: options.env,
|
|
740
|
+
stdio: options.captureOutput ? ["pipe", "pipe", "pipe"] : "inherit",
|
|
741
|
+
shell: false
|
|
742
|
+
});
|
|
743
|
+
if (options.scriptId) {
|
|
744
|
+
this.runningProcesses.set(options.scriptId, {
|
|
745
|
+
pid: proc.pid,
|
|
746
|
+
kill: () => {
|
|
747
|
+
killed = true;
|
|
748
|
+
proc.kill("SIGTERM");
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
let timeoutId;
|
|
753
|
+
if (options.timeout) {
|
|
754
|
+
timeoutId = setTimeout(() => {
|
|
755
|
+
killed = true;
|
|
756
|
+
proc.kill("SIGTERM");
|
|
757
|
+
setTimeout(() => {
|
|
758
|
+
if (!proc.killed) {
|
|
759
|
+
proc.kill("SIGKILL");
|
|
760
|
+
}
|
|
761
|
+
}, 1e3);
|
|
762
|
+
}, options.timeout);
|
|
763
|
+
}
|
|
764
|
+
if (options.stdin && proc.stdin) {
|
|
765
|
+
proc.stdin.write(options.stdin);
|
|
766
|
+
proc.stdin.end();
|
|
767
|
+
}
|
|
768
|
+
if (options.captureOutput) {
|
|
769
|
+
proc.stdout?.on("data", (data) => {
|
|
770
|
+
const chunk = data.toString();
|
|
771
|
+
if (stdout.length + chunk.length <= MAX_OUTPUT_SIZE) {
|
|
772
|
+
stdout += chunk;
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
proc.stderr?.on("data", (data) => {
|
|
776
|
+
const chunk = data.toString();
|
|
777
|
+
if (stderr.length + chunk.length <= MAX_OUTPUT_SIZE) {
|
|
778
|
+
stderr += chunk;
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
proc.on("close", (code) => {
|
|
783
|
+
if (timeoutId) {
|
|
784
|
+
clearTimeout(timeoutId);
|
|
785
|
+
}
|
|
786
|
+
if (options.scriptId) {
|
|
787
|
+
this.runningProcesses.delete(options.scriptId);
|
|
788
|
+
}
|
|
789
|
+
if (killed && code !== 0) {
|
|
790
|
+
resolve({
|
|
791
|
+
success: false,
|
|
792
|
+
exitCode: code ?? -1,
|
|
793
|
+
stdout,
|
|
794
|
+
stderr: stderr || "Script was killed (timeout or manual)"
|
|
795
|
+
});
|
|
796
|
+
} else {
|
|
797
|
+
resolve({
|
|
798
|
+
success: code === 0,
|
|
799
|
+
exitCode: code ?? 0,
|
|
800
|
+
stdout,
|
|
801
|
+
stderr
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
proc.on("error", (error) => {
|
|
806
|
+
if (timeoutId) {
|
|
807
|
+
clearTimeout(timeoutId);
|
|
808
|
+
}
|
|
809
|
+
if (options.scriptId) {
|
|
810
|
+
this.runningProcesses.delete(options.scriptId);
|
|
811
|
+
}
|
|
812
|
+
reject(error);
|
|
813
|
+
});
|
|
814
|
+
if (options.background) {
|
|
815
|
+
resolve({
|
|
816
|
+
success: true,
|
|
817
|
+
exitCode: 0,
|
|
818
|
+
stdout: `Background process started with PID: ${proc.pid}`,
|
|
819
|
+
stderr: ""
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
let runnerInstance = null;
|
|
826
|
+
function getScriptRunner() {
|
|
827
|
+
if (!runnerInstance) {
|
|
828
|
+
runnerInstance = new ScriptRunner();
|
|
829
|
+
}
|
|
830
|
+
return runnerInstance;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const PRIORITY_KEYWORDS = {
|
|
834
|
+
critical: "critical",
|
|
835
|
+
CRITICAL: "critical",
|
|
836
|
+
\u5173\u952E: "critical",
|
|
837
|
+
high: "high",
|
|
838
|
+
HIGH: "high",
|
|
839
|
+
\u9AD8: "high",
|
|
840
|
+
medium: "medium",
|
|
841
|
+
MEDIUM: "medium",
|
|
842
|
+
\u4E2D: "medium",
|
|
843
|
+
low: "low",
|
|
844
|
+
LOW: "low",
|
|
845
|
+
\u4F4E: "low"
|
|
846
|
+
};
|
|
847
|
+
class SkillParser {
|
|
848
|
+
/**
|
|
849
|
+
* Parse a SKILL.md file
|
|
850
|
+
*
|
|
851
|
+
* @param filePath - Path to SKILL.md file
|
|
852
|
+
* @returns Parsed skill document
|
|
853
|
+
*/
|
|
854
|
+
parse(filePath) {
|
|
855
|
+
const content = readFileSync(filePath, "utf-8");
|
|
856
|
+
return this.parseContent(content, dirname(filePath));
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Parse SKILL.md content
|
|
860
|
+
*
|
|
861
|
+
* @param content - Raw markdown content
|
|
862
|
+
* @param basePath - Base path for resolving references
|
|
863
|
+
* @returns Parsed skill document
|
|
864
|
+
*/
|
|
865
|
+
parseContent(content, basePath) {
|
|
866
|
+
const lines = content.split("\n");
|
|
867
|
+
const title = this.extractTitle(lines);
|
|
868
|
+
const description = this.extractDescription(lines);
|
|
869
|
+
const applicability = this.extractApplicability(content);
|
|
870
|
+
const sections = this.extractSections(content);
|
|
871
|
+
const rules = this.extractRules(content, basePath);
|
|
872
|
+
const examples = this.extractExamples(content);
|
|
873
|
+
return {
|
|
874
|
+
title,
|
|
875
|
+
description,
|
|
876
|
+
applicability,
|
|
877
|
+
sections,
|
|
878
|
+
rules,
|
|
879
|
+
examples,
|
|
880
|
+
rawContent: content
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Parse a skill directory (SKILL.md + references/)
|
|
885
|
+
*
|
|
886
|
+
* @param dirPath - Path to skill directory
|
|
887
|
+
* @returns Parsed skill document with references
|
|
888
|
+
*/
|
|
889
|
+
parseDirectory(dirPath) {
|
|
890
|
+
const skillPath = join(dirPath, "SKILL.md");
|
|
891
|
+
if (!existsSync(skillPath)) {
|
|
892
|
+
throw new Error(`SKILL.md not found in ${dirPath}`);
|
|
893
|
+
}
|
|
894
|
+
const skill = this.parse(skillPath);
|
|
895
|
+
const references = this.loadReferences(dirPath);
|
|
896
|
+
return {
|
|
897
|
+
...skill,
|
|
898
|
+
references
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
// ==========================================================================
|
|
902
|
+
// Extraction Methods
|
|
903
|
+
// ==========================================================================
|
|
904
|
+
/**
|
|
905
|
+
* Extract title from markdown
|
|
906
|
+
*/
|
|
907
|
+
extractTitle(lines) {
|
|
908
|
+
for (const line of lines) {
|
|
909
|
+
const match = line.match(/^#\s+(.+)$/);
|
|
910
|
+
if (match) {
|
|
911
|
+
return match[1].trim();
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return "Untitled Skill";
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Extract description from markdown
|
|
918
|
+
*/
|
|
919
|
+
extractDescription(lines) {
|
|
920
|
+
let inDescription = false;
|
|
921
|
+
const descLines = [];
|
|
922
|
+
for (const line of lines) {
|
|
923
|
+
if (line.match(/^#\s+/)) {
|
|
924
|
+
inDescription = true;
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
if (line.match(/^##\s+/)) {
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
if (inDescription && line.trim()) {
|
|
931
|
+
descLines.push(line);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return descLines.join("\n").trim();
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Extract applicability section
|
|
938
|
+
*/
|
|
939
|
+
extractApplicability(content) {
|
|
940
|
+
const applicability = {
|
|
941
|
+
taskTypes: [],
|
|
942
|
+
fileTypes: [],
|
|
943
|
+
contexts: []
|
|
944
|
+
};
|
|
945
|
+
const applicabilityMatch = content.match(
|
|
946
|
+
/##\s*(?:When to Apply|Applicability|适用场景|何时使用)[^\n]*\n([\s\S]*?)(?=\n##|\n$|$)/i
|
|
947
|
+
);
|
|
948
|
+
if (applicabilityMatch) {
|
|
949
|
+
const section = applicabilityMatch[1];
|
|
950
|
+
const taskMatches = section.matchAll(/[-*]\s*(.+)/g);
|
|
951
|
+
for (const match of taskMatches) {
|
|
952
|
+
const task = match[1].trim();
|
|
953
|
+
if (task) {
|
|
954
|
+
applicability.taskTypes.push(task);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
const fileTypeMatch = content.match(
|
|
959
|
+
/(?:file types?|文件类型)[:\s]*([^\n]+)/i
|
|
960
|
+
);
|
|
961
|
+
if (fileTypeMatch) {
|
|
962
|
+
applicability.fileTypes = fileTypeMatch[1].split(/[,,]/).map((t) => t.trim()).filter(Boolean);
|
|
963
|
+
}
|
|
964
|
+
return applicability;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Extract sections from markdown
|
|
968
|
+
*/
|
|
969
|
+
extractSections(content) {
|
|
970
|
+
const sections = [];
|
|
971
|
+
const lines = content.split("\n");
|
|
972
|
+
let currentSection = null;
|
|
973
|
+
let currentContent = [];
|
|
974
|
+
let currentSubsections = [];
|
|
975
|
+
for (const line of lines) {
|
|
976
|
+
const h2Match = line.match(/^##\s+(.+)$/);
|
|
977
|
+
if (h2Match) {
|
|
978
|
+
if (currentSection) {
|
|
979
|
+
currentSection.content = currentContent.join("\n").trim();
|
|
980
|
+
currentSection.subsections = currentSubsections.length > 0 ? currentSubsections : void 0;
|
|
981
|
+
sections.push(currentSection);
|
|
982
|
+
}
|
|
983
|
+
currentSection = {
|
|
984
|
+
title: h2Match[1].trim(),
|
|
985
|
+
content: "",
|
|
986
|
+
priority: this.detectPriority(h2Match[1])
|
|
987
|
+
};
|
|
988
|
+
currentContent = [];
|
|
989
|
+
currentSubsections = [];
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
const h3Match = line.match(/^###\s+(.+)$/);
|
|
993
|
+
if (h3Match && currentSection) {
|
|
994
|
+
currentSubsections.push({
|
|
995
|
+
title: h3Match[1].trim(),
|
|
996
|
+
content: "",
|
|
997
|
+
// Will be filled by subsequent lines
|
|
998
|
+
priority: this.detectPriority(h3Match[1])
|
|
999
|
+
});
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
if (currentSection) {
|
|
1003
|
+
currentContent.push(line);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
if (currentSection) {
|
|
1007
|
+
currentSection.content = currentContent.join("\n").trim();
|
|
1008
|
+
currentSection.subsections = currentSubsections.length > 0 ? currentSubsections : void 0;
|
|
1009
|
+
sections.push(currentSection);
|
|
1010
|
+
}
|
|
1011
|
+
return sections;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Extract rules from markdown
|
|
1015
|
+
*/
|
|
1016
|
+
extractRules(content, basePath) {
|
|
1017
|
+
const rules = [];
|
|
1018
|
+
const rulePattern = /###?\s*(?:`([a-z]+-\d+)`|(\w+-\d+))[:\s]*(.+?)(?=\n###?|\n##|$)/gs;
|
|
1019
|
+
let match;
|
|
1020
|
+
while ((match = rulePattern.exec(content)) !== null) {
|
|
1021
|
+
const id = match[1] || match[2];
|
|
1022
|
+
const titleAndContent = match[3];
|
|
1023
|
+
const titleMatch = titleAndContent.match(/^[:\s]*(.+)(?:\n|$)/);
|
|
1024
|
+
const title = titleMatch ? titleMatch[1].trim() : id;
|
|
1025
|
+
const category = id.split("-")[0];
|
|
1026
|
+
const priority = this.detectPriority(titleAndContent);
|
|
1027
|
+
const description = titleAndContent.replace(/^[:\s]*(.+)(?:\n|$)/, "").trim();
|
|
1028
|
+
let referencePath;
|
|
1029
|
+
if (basePath) {
|
|
1030
|
+
const refPath = join(basePath, "references", "rules", `${id}.md`);
|
|
1031
|
+
if (existsSync(refPath)) {
|
|
1032
|
+
referencePath = refPath;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
rules.push({
|
|
1036
|
+
id,
|
|
1037
|
+
title,
|
|
1038
|
+
category,
|
|
1039
|
+
priority,
|
|
1040
|
+
description,
|
|
1041
|
+
referencePath
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
const numberedPattern = /(?:^|\n)(\d+)\.\s+\*\*(.+?)\*\*[:\s]*(.+?)(?=\n\d+\.|\n##|$)/gs;
|
|
1045
|
+
while ((match = numberedPattern.exec(content)) !== null) {
|
|
1046
|
+
const num = match[1];
|
|
1047
|
+
const title = match[2].trim();
|
|
1048
|
+
const description = match[3].trim();
|
|
1049
|
+
const id = `rule-${num.padStart(3, "0")}`;
|
|
1050
|
+
rules.push({
|
|
1051
|
+
id,
|
|
1052
|
+
title,
|
|
1053
|
+
category: "general",
|
|
1054
|
+
priority: this.detectPriority(description),
|
|
1055
|
+
description
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
return rules;
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Extract code examples from markdown
|
|
1062
|
+
*/
|
|
1063
|
+
extractExamples(content) {
|
|
1064
|
+
const examples = [];
|
|
1065
|
+
const examplePattern = /###?\s*(?:Example|示例|案例)[:\s]*(.+?)(?=\n###?|\n##|$)/gis;
|
|
1066
|
+
let match;
|
|
1067
|
+
while ((match = examplePattern.exec(content)) !== null) {
|
|
1068
|
+
const exampleContent = match[1];
|
|
1069
|
+
const inputMatch = exampleContent.match(/(?:Input|输入)[:\s]*(.+?)(?=Output|输出|$)/is);
|
|
1070
|
+
const outputMatch = exampleContent.match(/(?:Output|输出)[:\s]*(.+)$/is);
|
|
1071
|
+
if (inputMatch || outputMatch) {
|
|
1072
|
+
examples.push({
|
|
1073
|
+
title: "Example",
|
|
1074
|
+
input: inputMatch ? inputMatch[1].trim() : "",
|
|
1075
|
+
output: outputMatch ? outputMatch[1].trim() : ""
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return examples;
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Extract code blocks from content
|
|
1083
|
+
*/
|
|
1084
|
+
extractCodeBlocks(content) {
|
|
1085
|
+
const blocks = [];
|
|
1086
|
+
const codePattern = /```(\w+)?\n([\s\S]*?)```/g;
|
|
1087
|
+
let match;
|
|
1088
|
+
while ((match = codePattern.exec(content)) !== null) {
|
|
1089
|
+
blocks.push({
|
|
1090
|
+
language: match[1] || "text",
|
|
1091
|
+
code: match[2].trim()
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
return blocks;
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Load reference documents from references/ directory
|
|
1098
|
+
*/
|
|
1099
|
+
loadReferences(dirPath) {
|
|
1100
|
+
const references = [];
|
|
1101
|
+
const refsPath = join(dirPath, "references");
|
|
1102
|
+
if (!existsSync(refsPath)) {
|
|
1103
|
+
return references;
|
|
1104
|
+
}
|
|
1105
|
+
const loadDir = (dir, prefix = "") => {
|
|
1106
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
1107
|
+
for (const entry of entries) {
|
|
1108
|
+
const fullPath = join(dir, entry.name);
|
|
1109
|
+
if (entry.isDirectory()) {
|
|
1110
|
+
loadDir(fullPath, `${prefix}${entry.name}/`);
|
|
1111
|
+
} else if (entry.name.endsWith(".md")) {
|
|
1112
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
1113
|
+
const title = this.extractTitle(content.split("\n")) || basename(entry.name, ".md");
|
|
1114
|
+
references.push({
|
|
1115
|
+
path: `${prefix}${entry.name}`,
|
|
1116
|
+
title,
|
|
1117
|
+
content
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
loadDir(refsPath);
|
|
1123
|
+
return references;
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Detect priority from text
|
|
1127
|
+
*/
|
|
1128
|
+
detectPriority(text) {
|
|
1129
|
+
const lowerText = text.toLowerCase();
|
|
1130
|
+
for (const [keyword, priority] of Object.entries(PRIORITY_KEYWORDS)) {
|
|
1131
|
+
if (lowerText.includes(keyword.toLowerCase())) {
|
|
1132
|
+
return priority;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
return "medium";
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
let parserInstance = null;
|
|
1139
|
+
function getSkillParser() {
|
|
1140
|
+
if (!parserInstance) {
|
|
1141
|
+
parserInstance = new SkillParser();
|
|
1142
|
+
}
|
|
1143
|
+
return parserInstance;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
const PLUGINS_DIR = join(homedir(), ".ccjk", "plugins");
|
|
1147
|
+
const SKILLS_DIR = join(homedir(), ".ccjk", "skills");
|
|
1148
|
+
const AGENTS_DIR = join(homedir(), ".ccjk", "agents");
|
|
1149
|
+
const CONFIG_FILE = join(homedir(), ".ccjk", "plugins.json");
|
|
1150
|
+
class PluginManager {
|
|
1151
|
+
plugins = /* @__PURE__ */ new Map();
|
|
1152
|
+
agents = /* @__PURE__ */ new Map();
|
|
1153
|
+
eventHandlers = /* @__PURE__ */ new Set();
|
|
1154
|
+
initialized = false;
|
|
1155
|
+
constructor() {
|
|
1156
|
+
this.ensureDirectories();
|
|
1157
|
+
}
|
|
1158
|
+
// ==========================================================================
|
|
1159
|
+
// Initialization
|
|
1160
|
+
// ==========================================================================
|
|
1161
|
+
/**
|
|
1162
|
+
* Initialize the plugin manager
|
|
1163
|
+
*/
|
|
1164
|
+
async initialize() {
|
|
1165
|
+
if (this.initialized)
|
|
1166
|
+
return;
|
|
1167
|
+
await this.loadInstalledPlugins();
|
|
1168
|
+
await this.loadInstalledAgents();
|
|
1169
|
+
this.registerAllIntents();
|
|
1170
|
+
this.initialized = true;
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Ensure required directories exist
|
|
1174
|
+
*/
|
|
1175
|
+
ensureDirectories() {
|
|
1176
|
+
for (const dir of [PLUGINS_DIR, SKILLS_DIR, AGENTS_DIR]) {
|
|
1177
|
+
if (!existsSync(dir)) {
|
|
1178
|
+
mkdirSync(dir, { recursive: true });
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
// ==========================================================================
|
|
1183
|
+
// Plugin Installation
|
|
1184
|
+
// ==========================================================================
|
|
1185
|
+
/**
|
|
1186
|
+
* Install a plugin from various sources
|
|
1187
|
+
*
|
|
1188
|
+
* @param source - Plugin source (cloud, github, local, npm)
|
|
1189
|
+
* @param options - Installation options
|
|
1190
|
+
*/
|
|
1191
|
+
async install(source, options = {}) {
|
|
1192
|
+
const resolvedSource = typeof source === "string" ? this.resolveSource(source) : source;
|
|
1193
|
+
try {
|
|
1194
|
+
let result;
|
|
1195
|
+
switch (resolvedSource.type) {
|
|
1196
|
+
case "cloud":
|
|
1197
|
+
result = await this.installFromCloud(resolvedSource.url, options);
|
|
1198
|
+
break;
|
|
1199
|
+
case "github":
|
|
1200
|
+
result = await this.installFromGitHub(resolvedSource.repo, resolvedSource.ref, options);
|
|
1201
|
+
break;
|
|
1202
|
+
case "local":
|
|
1203
|
+
result = await this.installFromLocal(resolvedSource.path, options);
|
|
1204
|
+
break;
|
|
1205
|
+
case "npm":
|
|
1206
|
+
result = await this.installFromNpm(resolvedSource.package, options);
|
|
1207
|
+
break;
|
|
1208
|
+
default:
|
|
1209
|
+
return { success: false, pluginId: "", error: "Unknown source type" };
|
|
1210
|
+
}
|
|
1211
|
+
if (result.success) {
|
|
1212
|
+
this.emit({ type: "plugin:installed", pluginId: result.pluginId, version: result.version });
|
|
1213
|
+
}
|
|
1214
|
+
return result;
|
|
1215
|
+
} catch (error) {
|
|
1216
|
+
return {
|
|
1217
|
+
success: false,
|
|
1218
|
+
pluginId: "",
|
|
1219
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Install from GitHub (supports Vercel Agent Skills format)
|
|
1225
|
+
*/
|
|
1226
|
+
async installFromGitHub(repo, ref, options = {}) {
|
|
1227
|
+
const parts = repo.split("/");
|
|
1228
|
+
if (parts.length < 2) {
|
|
1229
|
+
return { success: false, pluginId: "", error: "Invalid GitHub repo format" };
|
|
1230
|
+
}
|
|
1231
|
+
const owner = parts[0];
|
|
1232
|
+
const repoName = parts[1];
|
|
1233
|
+
const subPath = parts.slice(2).join("/");
|
|
1234
|
+
const targetDir = join(SKILLS_DIR, `${owner}-${repoName}${subPath ? `-${subPath.replace(/\//g, "-")}` : ""}`);
|
|
1235
|
+
if (existsSync(targetDir)) {
|
|
1236
|
+
if (options.force) {
|
|
1237
|
+
rmSync(targetDir, { recursive: true });
|
|
1238
|
+
} else {
|
|
1239
|
+
return { success: false, pluginId: "", error: "Plugin already installed. Use --force to reinstall." };
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
try {
|
|
1243
|
+
const cloneUrl = `https://github.com/${owner}/${repoName}.git`;
|
|
1244
|
+
await x("git", ["clone", "--depth", "1", ...ref ? ["--branch", ref] : [], cloneUrl, targetDir]);
|
|
1245
|
+
if (subPath) {
|
|
1246
|
+
const subDir = join(targetDir, subPath);
|
|
1247
|
+
if (!existsSync(subDir)) {
|
|
1248
|
+
rmSync(targetDir, { recursive: true });
|
|
1249
|
+
return { success: false, pluginId: "", error: `Path ${subPath} not found in repo` };
|
|
1250
|
+
}
|
|
1251
|
+
const tempDir = `${targetDir}-temp`;
|
|
1252
|
+
await x("mv", [subDir, tempDir]);
|
|
1253
|
+
rmSync(targetDir, { recursive: true });
|
|
1254
|
+
await x("mv", [tempDir, targetDir]);
|
|
1255
|
+
}
|
|
1256
|
+
const plugin = await this.loadPluginFromDirectory(targetDir);
|
|
1257
|
+
if (!plugin) {
|
|
1258
|
+
rmSync(targetDir, { recursive: true });
|
|
1259
|
+
return { success: false, pluginId: "", error: "Invalid plugin format" };
|
|
1260
|
+
}
|
|
1261
|
+
this.plugins.set(plugin.manifest.id, plugin);
|
|
1262
|
+
this.registerPluginIntents(plugin);
|
|
1263
|
+
this.saveConfig();
|
|
1264
|
+
return {
|
|
1265
|
+
success: true,
|
|
1266
|
+
pluginId: plugin.manifest.id,
|
|
1267
|
+
version: plugin.manifest.version,
|
|
1268
|
+
path: targetDir
|
|
1269
|
+
};
|
|
1270
|
+
} catch (error) {
|
|
1271
|
+
if (existsSync(targetDir)) {
|
|
1272
|
+
rmSync(targetDir, { recursive: true });
|
|
1273
|
+
}
|
|
1274
|
+
return {
|
|
1275
|
+
success: false,
|
|
1276
|
+
pluginId: "",
|
|
1277
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Install from local directory
|
|
1283
|
+
*/
|
|
1284
|
+
async installFromLocal(path, options = {}) {
|
|
1285
|
+
if (!existsSync(path)) {
|
|
1286
|
+
return { success: false, pluginId: "", error: `Path not found: ${path}` };
|
|
1287
|
+
}
|
|
1288
|
+
const plugin = await this.loadPluginFromDirectory(path);
|
|
1289
|
+
if (!plugin) {
|
|
1290
|
+
return { success: false, pluginId: "", error: "Invalid plugin format" };
|
|
1291
|
+
}
|
|
1292
|
+
const targetDir = join(PLUGINS_DIR, plugin.manifest.id);
|
|
1293
|
+
if (existsSync(targetDir) && !options.force) {
|
|
1294
|
+
return { success: false, pluginId: "", error: "Plugin already installed. Use --force to reinstall." };
|
|
1295
|
+
}
|
|
1296
|
+
await x("cp", ["-r", path, targetDir]);
|
|
1297
|
+
this.plugins.set(plugin.manifest.id, { ...plugin, source: { type: "local", path: targetDir } });
|
|
1298
|
+
this.registerPluginIntents(plugin);
|
|
1299
|
+
this.saveConfig();
|
|
1300
|
+
return {
|
|
1301
|
+
success: true,
|
|
1302
|
+
pluginId: plugin.manifest.id,
|
|
1303
|
+
version: plugin.manifest.version,
|
|
1304
|
+
path: targetDir
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Install from cloud registry
|
|
1309
|
+
*/
|
|
1310
|
+
async installFromCloud(url, options = {}) {
|
|
1311
|
+
return { success: false, pluginId: "", error: "Cloud installation not yet implemented in v2" };
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Install from NPM
|
|
1315
|
+
*/
|
|
1316
|
+
async installFromNpm(packageName, options = {}) {
|
|
1317
|
+
const targetDir = join(PLUGINS_DIR, packageName.replace(/\//g, "-"));
|
|
1318
|
+
try {
|
|
1319
|
+
await x("npm", ["pack", packageName, "--pack-destination", targetDir]);
|
|
1320
|
+
return { success: false, pluginId: "", error: "NPM installation not yet fully implemented" };
|
|
1321
|
+
} catch (error) {
|
|
1322
|
+
return {
|
|
1323
|
+
success: false,
|
|
1324
|
+
pluginId: "",
|
|
1325
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Resolve source string to PluginSource
|
|
1331
|
+
*/
|
|
1332
|
+
resolveSource(source) {
|
|
1333
|
+
if (source.startsWith("github:") || source.match(/^[\w-]+\/[\w-]+/)) {
|
|
1334
|
+
const repo = source.replace("github:", "");
|
|
1335
|
+
return { type: "github", repo };
|
|
1336
|
+
}
|
|
1337
|
+
if (source.startsWith("npm:") || source.startsWith("@")) {
|
|
1338
|
+
const pkg = source.replace("npm:", "");
|
|
1339
|
+
return { type: "npm", package: pkg };
|
|
1340
|
+
}
|
|
1341
|
+
if (source.startsWith("/") || source.startsWith("./") || source.startsWith("~")) {
|
|
1342
|
+
const path = source.startsWith("~") ? source.replace("~", homedir()) : source;
|
|
1343
|
+
return { type: "local", path };
|
|
1344
|
+
}
|
|
1345
|
+
return { type: "cloud", url: source };
|
|
1346
|
+
}
|
|
1347
|
+
// ==========================================================================
|
|
1348
|
+
// Plugin Loading
|
|
1349
|
+
// ==========================================================================
|
|
1350
|
+
/**
|
|
1351
|
+
* Load plugin from directory
|
|
1352
|
+
*/
|
|
1353
|
+
async loadPluginFromDirectory(dirPath) {
|
|
1354
|
+
const parser = getSkillParser();
|
|
1355
|
+
const manifestPath = join(dirPath, "plugin.json");
|
|
1356
|
+
const skillPath = join(dirPath, "SKILL.md");
|
|
1357
|
+
let manifest;
|
|
1358
|
+
if (existsSync(manifestPath)) {
|
|
1359
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
1360
|
+
} else if (existsSync(skillPath)) {
|
|
1361
|
+
const skill2 = parser.parse(skillPath);
|
|
1362
|
+
manifest = this.generateManifestFromSkill(skill2, dirPath);
|
|
1363
|
+
} else {
|
|
1364
|
+
return null;
|
|
1365
|
+
}
|
|
1366
|
+
const skill = existsSync(skillPath) ? parser.parse(skillPath) : void 0;
|
|
1367
|
+
const scripts = this.loadScripts(dirPath);
|
|
1368
|
+
const intents = this.loadIntents(dirPath, manifest.id);
|
|
1369
|
+
return {
|
|
1370
|
+
manifest,
|
|
1371
|
+
skill,
|
|
1372
|
+
scripts,
|
|
1373
|
+
intents,
|
|
1374
|
+
source: { type: "local", path: dirPath }
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Generate manifest from SKILL.md
|
|
1379
|
+
*/
|
|
1380
|
+
generateManifestFromSkill(skill, dirPath) {
|
|
1381
|
+
const dirName = dirPath.split("/").pop() || "unknown";
|
|
1382
|
+
return {
|
|
1383
|
+
id: dirName,
|
|
1384
|
+
name: { "en": skill.title, "zh-CN": skill.title },
|
|
1385
|
+
description: { "en": skill.description, "zh-CN": skill.description },
|
|
1386
|
+
version: "1.0.0",
|
|
1387
|
+
author: { name: "Unknown" },
|
|
1388
|
+
category: "other",
|
|
1389
|
+
tags: [],
|
|
1390
|
+
permissions: ["file:read"],
|
|
1391
|
+
formatVersion: "2.0"
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Load scripts from directory
|
|
1396
|
+
*/
|
|
1397
|
+
loadScripts(dirPath) {
|
|
1398
|
+
const scripts = [];
|
|
1399
|
+
const scriptsDir = join(dirPath, "scripts");
|
|
1400
|
+
if (!existsSync(scriptsDir))
|
|
1401
|
+
return scripts;
|
|
1402
|
+
const entries = readdirSync(scriptsDir, { withFileTypes: true });
|
|
1403
|
+
for (const entry of entries) {
|
|
1404
|
+
if (entry.isFile()) {
|
|
1405
|
+
const ext = entry.name.substring(entry.name.lastIndexOf("."));
|
|
1406
|
+
let type = null;
|
|
1407
|
+
if ([".sh", ".bash"].includes(ext))
|
|
1408
|
+
type = "bash";
|
|
1409
|
+
else if ([".js", ".mjs"].includes(ext))
|
|
1410
|
+
type = "node";
|
|
1411
|
+
else if (ext === ".py")
|
|
1412
|
+
type = "python";
|
|
1413
|
+
if (type) {
|
|
1414
|
+
scripts.push({
|
|
1415
|
+
name: entry.name.replace(/\.[^.]+$/, ""),
|
|
1416
|
+
path: `scripts/${entry.name}`,
|
|
1417
|
+
type,
|
|
1418
|
+
permissions: ["shell:execute"]
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
return scripts;
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Load intents from directory
|
|
1427
|
+
*/
|
|
1428
|
+
loadIntents(dirPath, pluginId) {
|
|
1429
|
+
const intentsPath = join(dirPath, "intents", "intents.yaml");
|
|
1430
|
+
if (!existsSync(intentsPath))
|
|
1431
|
+
return void 0;
|
|
1432
|
+
return void 0;
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Load all installed plugins
|
|
1436
|
+
*/
|
|
1437
|
+
async loadInstalledPlugins() {
|
|
1438
|
+
if (existsSync(PLUGINS_DIR)) {
|
|
1439
|
+
const entries = readdirSync(PLUGINS_DIR, { withFileTypes: true });
|
|
1440
|
+
for (const entry of entries) {
|
|
1441
|
+
if (entry.isDirectory()) {
|
|
1442
|
+
const plugin = await this.loadPluginFromDirectory(join(PLUGINS_DIR, entry.name));
|
|
1443
|
+
if (plugin) {
|
|
1444
|
+
this.plugins.set(plugin.manifest.id, plugin);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
if (existsSync(SKILLS_DIR)) {
|
|
1450
|
+
const entries = readdirSync(SKILLS_DIR, { withFileTypes: true });
|
|
1451
|
+
for (const entry of entries) {
|
|
1452
|
+
if (entry.isDirectory()) {
|
|
1453
|
+
const plugin = await this.loadPluginFromDirectory(join(SKILLS_DIR, entry.name));
|
|
1454
|
+
if (plugin) {
|
|
1455
|
+
this.plugins.set(plugin.manifest.id, plugin);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
// ==========================================================================
|
|
1462
|
+
// Intent Management
|
|
1463
|
+
// ==========================================================================
|
|
1464
|
+
/**
|
|
1465
|
+
* Register intents from all plugins
|
|
1466
|
+
*/
|
|
1467
|
+
registerAllIntents() {
|
|
1468
|
+
const engine = getIntentEngine();
|
|
1469
|
+
for (const plugin of this.plugins.values()) {
|
|
1470
|
+
if (plugin.intents) {
|
|
1471
|
+
engine.registerRules(plugin.intents);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Register intents from a single plugin
|
|
1477
|
+
*/
|
|
1478
|
+
registerPluginIntents(plugin) {
|
|
1479
|
+
if (plugin.intents) {
|
|
1480
|
+
const engine = getIntentEngine();
|
|
1481
|
+
engine.registerRules(plugin.intents);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Detect intent and get matching plugins
|
|
1486
|
+
*/
|
|
1487
|
+
async detectIntent(userInput, cwd) {
|
|
1488
|
+
const engine = getIntentEngine();
|
|
1489
|
+
return engine.detect(userInput, cwd);
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Execute based on detected intent
|
|
1493
|
+
*/
|
|
1494
|
+
async executeIntent(match, cwd) {
|
|
1495
|
+
const plugin = this.plugins.get(match.pluginId);
|
|
1496
|
+
if (!plugin) {
|
|
1497
|
+
throw new Error(`Plugin not found: ${match.pluginId}`);
|
|
1498
|
+
}
|
|
1499
|
+
this.emit({ type: "plugin:activated", pluginId: match.pluginId, trigger: "intent" });
|
|
1500
|
+
if (plugin.scripts && plugin.scripts.length > 0) {
|
|
1501
|
+
const mainScript = plugin.scripts.find((s) => s.name === "main") || plugin.scripts[0];
|
|
1502
|
+
const runner = getScriptRunner();
|
|
1503
|
+
const pluginPath = plugin.source.type === "local" ? plugin.source.path : "";
|
|
1504
|
+
const result = await runner.execute(mainScript, pluginPath, { cwd });
|
|
1505
|
+
this.emit({ type: "intent:executed", match, result });
|
|
1506
|
+
return result;
|
|
1507
|
+
}
|
|
1508
|
+
if (plugin.skill) {
|
|
1509
|
+
this.emit({ type: "intent:executed", match, result: plugin.skill });
|
|
1510
|
+
return plugin.skill;
|
|
1511
|
+
}
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1514
|
+
// ==========================================================================
|
|
1515
|
+
// Agent Management
|
|
1516
|
+
// ==========================================================================
|
|
1517
|
+
/**
|
|
1518
|
+
* Create an agent from skills and MCP servers
|
|
1519
|
+
*/
|
|
1520
|
+
async createAgent(definition) {
|
|
1521
|
+
for (const skillRef of definition.skills) {
|
|
1522
|
+
if (!this.plugins.has(skillRef.pluginId)) {
|
|
1523
|
+
throw new Error(`Skill not found: ${skillRef.pluginId}`);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
const agentPath = join(AGENTS_DIR, `${definition.id}.json`);
|
|
1527
|
+
writeFileSync(agentPath, JSON.stringify(definition, null, 2));
|
|
1528
|
+
this.agents.set(definition.id, definition);
|
|
1529
|
+
this.emit({ type: "agent:created", agentId: definition.id });
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Get an agent by ID
|
|
1533
|
+
*/
|
|
1534
|
+
getAgent(agentId) {
|
|
1535
|
+
return this.agents.get(agentId);
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* List all agents
|
|
1539
|
+
*/
|
|
1540
|
+
listAgents() {
|
|
1541
|
+
return Array.from(this.agents.values());
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Load installed agents
|
|
1545
|
+
*/
|
|
1546
|
+
async loadInstalledAgents() {
|
|
1547
|
+
if (!existsSync(AGENTS_DIR))
|
|
1548
|
+
return;
|
|
1549
|
+
const entries = readdirSync(AGENTS_DIR, { withFileTypes: true });
|
|
1550
|
+
for (const entry of entries) {
|
|
1551
|
+
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
1552
|
+
const content = readFileSync(join(AGENTS_DIR, entry.name), "utf-8");
|
|
1553
|
+
const agent = JSON.parse(content);
|
|
1554
|
+
this.agents.set(agent.id, agent);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
// ==========================================================================
|
|
1559
|
+
// Plugin Management
|
|
1560
|
+
// ==========================================================================
|
|
1561
|
+
/**
|
|
1562
|
+
* Uninstall a plugin
|
|
1563
|
+
*/
|
|
1564
|
+
async uninstall(pluginId) {
|
|
1565
|
+
const plugin = this.plugins.get(pluginId);
|
|
1566
|
+
if (!plugin)
|
|
1567
|
+
return false;
|
|
1568
|
+
if (plugin.source.type === "local") {
|
|
1569
|
+
rmSync(plugin.source.path, { recursive: true });
|
|
1570
|
+
}
|
|
1571
|
+
const engine = getIntentEngine();
|
|
1572
|
+
engine.unregisterPluginRules(pluginId);
|
|
1573
|
+
this.plugins.delete(pluginId);
|
|
1574
|
+
this.saveConfig();
|
|
1575
|
+
this.emit({ type: "plugin:uninstalled", pluginId });
|
|
1576
|
+
return true;
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Get a plugin by ID
|
|
1580
|
+
*/
|
|
1581
|
+
getPlugin(pluginId) {
|
|
1582
|
+
return this.plugins.get(pluginId);
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* List all installed plugins
|
|
1586
|
+
*/
|
|
1587
|
+
listPlugins() {
|
|
1588
|
+
return Array.from(this.plugins.values());
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* Check for updates
|
|
1592
|
+
*/
|
|
1593
|
+
async checkUpdates() {
|
|
1594
|
+
return [];
|
|
1595
|
+
}
|
|
1596
|
+
// ==========================================================================
|
|
1597
|
+
// Script Execution
|
|
1598
|
+
// ==========================================================================
|
|
1599
|
+
/**
|
|
1600
|
+
* Execute a plugin script
|
|
1601
|
+
*/
|
|
1602
|
+
async executeScript(pluginId, scriptName, options = {}) {
|
|
1603
|
+
const plugin = this.plugins.get(pluginId);
|
|
1604
|
+
if (!plugin) {
|
|
1605
|
+
throw new Error(`Plugin not found: ${pluginId}`);
|
|
1606
|
+
}
|
|
1607
|
+
const script = plugin.scripts?.find((s) => s.name === scriptName);
|
|
1608
|
+
if (!script) {
|
|
1609
|
+
throw new Error(`Script not found: ${scriptName}`);
|
|
1610
|
+
}
|
|
1611
|
+
const runner = getScriptRunner();
|
|
1612
|
+
const pluginPath = plugin.source.type === "local" ? plugin.source.path : "";
|
|
1613
|
+
this.emit({ type: "script:started", pluginId, scriptName });
|
|
1614
|
+
const result = await runner.execute(script, pluginPath, options);
|
|
1615
|
+
this.emit({ type: "script:completed", pluginId, scriptName, result });
|
|
1616
|
+
return result;
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Grant permission to script runner
|
|
1620
|
+
*/
|
|
1621
|
+
grantPermission(permission) {
|
|
1622
|
+
const runner = getScriptRunner();
|
|
1623
|
+
runner.grantPermission(permission);
|
|
1624
|
+
}
|
|
1625
|
+
// ==========================================================================
|
|
1626
|
+
// Event System
|
|
1627
|
+
// ==========================================================================
|
|
1628
|
+
/**
|
|
1629
|
+
* Subscribe to plugin events
|
|
1630
|
+
*/
|
|
1631
|
+
on(handler) {
|
|
1632
|
+
this.eventHandlers.add(handler);
|
|
1633
|
+
return () => this.eventHandlers.delete(handler);
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Emit an event
|
|
1637
|
+
*/
|
|
1638
|
+
emit(event) {
|
|
1639
|
+
for (const handler of this.eventHandlers) {
|
|
1640
|
+
try {
|
|
1641
|
+
handler(event);
|
|
1642
|
+
} catch (error) {
|
|
1643
|
+
console.error("Event handler error:", error);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
// ==========================================================================
|
|
1648
|
+
// Configuration
|
|
1649
|
+
// ==========================================================================
|
|
1650
|
+
/**
|
|
1651
|
+
* Save configuration
|
|
1652
|
+
*/
|
|
1653
|
+
saveConfig() {
|
|
1654
|
+
const config = {
|
|
1655
|
+
plugins: Array.from(this.plugins.entries()).map(([id, p]) => ({
|
|
1656
|
+
id,
|
|
1657
|
+
source: p.source,
|
|
1658
|
+
version: p.manifest.version
|
|
1659
|
+
})),
|
|
1660
|
+
agents: Array.from(this.agents.keys())
|
|
1661
|
+
};
|
|
1662
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
let managerInstance = null;
|
|
1666
|
+
async function getPluginManager() {
|
|
1667
|
+
if (!managerInstance) {
|
|
1668
|
+
managerInstance = new PluginManager();
|
|
1669
|
+
await managerInstance.initialize();
|
|
1670
|
+
}
|
|
1671
|
+
return managerInstance;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
export { getPluginManager as g };
|