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.
Files changed (51) hide show
  1. package/dist/chunks/agent.mjs +1412 -0
  2. package/dist/chunks/api.mjs +7 -7
  3. package/dist/chunks/auto-updater.mjs +9 -9
  4. package/dist/chunks/ccr.mjs +4 -4
  5. package/dist/chunks/ccu.mjs +1 -1
  6. package/dist/chunks/claude-code-incremental-manager.mjs +6 -6
  7. package/dist/chunks/codex.mjs +4 -4
  8. package/dist/chunks/commands2.mjs +5 -5
  9. package/dist/chunks/commit.mjs +2 -2
  10. package/dist/chunks/config-consolidator.mjs +2 -2
  11. package/dist/chunks/config-switch.mjs +6 -6
  12. package/dist/chunks/config.mjs +1 -1
  13. package/dist/chunks/config2.mjs +14 -14
  14. package/dist/chunks/doctor.mjs +3 -3
  15. package/dist/chunks/features.mjs +12 -12
  16. package/dist/chunks/help.mjs +35 -35
  17. package/dist/chunks/index.mjs +11 -11
  18. package/dist/chunks/init.mjs +21 -21
  19. package/dist/chunks/interview.mjs +33 -33
  20. package/dist/chunks/marketplace.mjs +8 -8
  21. package/dist/chunks/mcp.mjs +8 -8
  22. package/dist/chunks/menu.mjs +302 -293
  23. package/dist/chunks/notification.mjs +5 -5
  24. package/dist/chunks/onboarding.mjs +7 -7
  25. package/dist/chunks/package.mjs +1 -1
  26. package/dist/chunks/permission-manager.mjs +3 -3
  27. package/dist/chunks/plugin.mjs +24 -24
  28. package/dist/chunks/prompts.mjs +3 -3
  29. package/dist/chunks/providers.mjs +13 -13
  30. package/dist/chunks/session.mjs +17 -17
  31. package/dist/chunks/skill.mjs +304 -0
  32. package/dist/chunks/skills-sync.mjs +4 -4
  33. package/dist/chunks/skills.mjs +2 -2
  34. package/dist/chunks/team.mjs +1 -1
  35. package/dist/chunks/uninstall.mjs +8 -8
  36. package/dist/chunks/update.mjs +4 -4
  37. package/dist/chunks/upgrade-manager.mjs +3 -3
  38. package/dist/chunks/version-checker.mjs +6 -6
  39. package/dist/chunks/workflows.mjs +2 -2
  40. package/dist/cli.mjs +48 -0
  41. package/dist/index.d.mts +157 -14
  42. package/dist/index.d.ts +157 -14
  43. package/dist/index.mjs +4 -4
  44. package/dist/shared/{ccjk.rLRHmcqD.mjs → ccjk.BQzWKmC3.mjs} +3 -3
  45. package/dist/shared/{ccjk.uVUeWAt8.mjs → ccjk.BZT_f2go.mjs} +7 -7
  46. package/dist/shared/{ccjk.-FoZ3zat.mjs → ccjk.BlPCiSHj.mjs} +10 -10
  47. package/dist/shared/ccjk.DH6cOJsf.mjs +1674 -0
  48. package/dist/shared/{ccjk.tB4-Y4Qb.mjs → ccjk.DrMygfCF.mjs} +1 -1
  49. package/dist/shared/ccjk.tJ08-yZt.mjs +179 -0
  50. package/package.json +1 -1
  51. 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 };