@kwonye/mcpx 0.1.91 → 0.1.94

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 (73) hide show
  1. package/dist/adapters/claude-desktop.d.ts +2 -1
  2. package/dist/adapters/claude-desktop.d.ts.map +1 -1
  3. package/dist/adapters/claude.d.ts +2 -1
  4. package/dist/adapters/claude.d.ts.map +1 -1
  5. package/dist/adapters/cline.d.ts.map +1 -1
  6. package/dist/adapters/codex.d.ts +2 -1
  7. package/dist/adapters/codex.d.ts.map +1 -1
  8. package/dist/adapters/cursor.d.ts +2 -1
  9. package/dist/adapters/cursor.d.ts.map +1 -1
  10. package/dist/adapters/kiro.d.ts +2 -1
  11. package/dist/adapters/kiro.d.ts.map +1 -1
  12. package/dist/adapters/opencode.d.ts.map +1 -1
  13. package/dist/adapters/qwen.d.ts +2 -1
  14. package/dist/adapters/qwen.d.ts.map +1 -1
  15. package/dist/adapters/utils/index.d.ts +11 -3
  16. package/dist/adapters/utils/index.d.ts.map +1 -1
  17. package/dist/adapters/vscode.d.ts +2 -1
  18. package/dist/adapters/vscode.d.ts.map +1 -1
  19. package/dist/{cli-b83jnbz7.js → cli-154shqmt.js} +180 -1717
  20. package/dist/cli-9fmn9y72.js +923 -0
  21. package/dist/{cli-btx8jwra.js → cli-csfsg1yf.js} +136 -8
  22. package/dist/{cli-grafmdtc.js → cli-mxmgxdm2.js} +181 -1718
  23. package/dist/cli-n8vt375e.js +2082 -0
  24. package/dist/{cli-qfvawh8e.js → cli-tk8bggg5.js} +82 -9
  25. package/dist/{cli-zbbeadz8.js → cli-zjepj3sj.js} +7 -1
  26. package/dist/cli.d.ts.map +1 -1
  27. package/dist/cli.js +172 -20
  28. package/dist/{config-nkgc4c4n.js → config-dtc3c79c.js} +7 -3
  29. package/dist/core/config-repair.d.ts.map +1 -1
  30. package/dist/core/config-store.d.ts +3 -0
  31. package/dist/core/config-store.d.ts.map +1 -0
  32. package/dist/core/config.d.ts +13 -1
  33. package/dist/core/config.d.ts.map +1 -1
  34. package/dist/core/daemon.d.ts +1 -1
  35. package/dist/core/daemon.d.ts.map +1 -1
  36. package/dist/core/errors.d.ts +18 -0
  37. package/dist/core/errors.d.ts.map +1 -0
  38. package/dist/core/index.d.ts +14 -3
  39. package/dist/core/index.d.ts.map +1 -1
  40. package/dist/core/index.js +133 -12
  41. package/dist/core/oauth.d.ts.map +1 -1
  42. package/dist/core/paths.d.ts +4 -0
  43. package/dist/core/paths.d.ts.map +1 -1
  44. package/dist/core/plugin-cache.d.ts +21 -0
  45. package/dist/core/plugin-cache.d.ts.map +1 -0
  46. package/dist/core/plugin-data.d.ts +29 -0
  47. package/dist/core/plugin-data.d.ts.map +1 -0
  48. package/dist/core/plugin-lifecycle.d.ts +8 -0
  49. package/dist/core/plugin-lifecycle.d.ts.map +1 -0
  50. package/dist/core/plugin-manager.d.ts +52 -0
  51. package/dist/core/plugin-manager.d.ts.map +1 -0
  52. package/dist/core/plugin-parse.d.ts +6 -0
  53. package/dist/core/plugin-parse.d.ts.map +1 -0
  54. package/dist/core/plugin-projections.d.ts +9 -0
  55. package/dist/core/plugin-projections.d.ts.map +1 -0
  56. package/dist/core/plugin-source.d.ts +5 -0
  57. package/dist/core/plugin-source.d.ts.map +1 -0
  58. package/dist/core/registry.d.ts +4 -1
  59. package/dist/core/registry.d.ts.map +1 -1
  60. package/dist/core/secrets.d.ts.map +1 -1
  61. package/dist/core/server-auth.d.ts.map +1 -1
  62. package/dist/core/status.d.ts +1 -0
  63. package/dist/core/status.d.ts.map +1 -1
  64. package/dist/core/sync.d.ts +19 -2
  65. package/dist/core/sync.d.ts.map +1 -1
  66. package/dist/gateway/server.d.ts +4 -1
  67. package/dist/gateway/server.d.ts.map +1 -1
  68. package/dist/plugin-manager-1sjwfnah.js +31 -0
  69. package/dist/{registry-vykv33et.js → registry-n12thgf2.js} +2 -2
  70. package/dist/types.d.ts +85 -0
  71. package/dist/types.d.ts.map +1 -1
  72. package/dist/version.d.ts +1 -1
  73. package/package.json +3 -2
@@ -0,0 +1,923 @@
1
+ import {
2
+ persistSyncState,
3
+ syncAllClients
4
+ } from "./cli-n8vt375e.js";
5
+ import {
6
+ loadConfig,
7
+ saveConfig
8
+ } from "./cli-csfsg1yf.js";
9
+ import {
10
+ SecretsManager
11
+ } from "./cli-tk8bggg5.js";
12
+ import {
13
+ ensureDir,
14
+ getConfigPath,
15
+ getPluginCacheRoot,
16
+ getPluginDataRoot,
17
+ readJsonFile,
18
+ writeJsonAtomic
19
+ } from "./cli-zjepj3sj.js";
20
+ import {
21
+ __require
22
+ } from "./cli-s4gspqdf.js";
23
+
24
+ // src/core/plugin-manager.ts
25
+ import fs5 from "node:fs";
26
+ import path5 from "node:path";
27
+
28
+ // src/core/plugin-cache.ts
29
+ import fs from "node:fs";
30
+ import path from "node:path";
31
+ import { execFileSync } from "node:child_process";
32
+ var TMP_PREFIX = "mcpx-acquire-";
33
+
34
+ class PluginCache {
35
+ cacheRoot;
36
+ constructor() {
37
+ this.cacheRoot = getPluginCacheRoot();
38
+ ensureDir(this.cacheRoot);
39
+ }
40
+ sourceDir(source) {
41
+ const key = source.type === "github" ? `github_${source.original.split("@")[0].replace(/[^a-zA-Z0-9_/-]/g, "_")}` : source.type === "npm" ? `npm_${source.original.replace(/^npm:/, "").split("@")[0].replace(/[^a-zA-Z0-9_/-]/g, "_")}` : source.type === "local" ? `local_${source.original.replace(/[^a-zA-Z0-9_/-]/g, "_")}` : `git_${source.original.replace(/[^a-zA-Z0-9_./@-]/g, "_")}`;
42
+ return path.join(this.cacheRoot, key);
43
+ }
44
+ refPath(srcDir) {
45
+ return path.join(srcDir, "ref.json");
46
+ }
47
+ shaDir(srcDir, sha) {
48
+ return path.join(srcDir, sha);
49
+ }
50
+ async resolveSha(source) {
51
+ const srcDir = this.sourceDir(source);
52
+ if (source.type === "github") {
53
+ const repo = source.original.split("@")[0];
54
+ const ref = source.ref || "HEAD";
55
+ const output = execFileSync("git", ["ls-remote", `https://github.com/${repo}.git`, ref], {
56
+ encoding: "utf8",
57
+ timeout: 30000
58
+ });
59
+ const sha = output.split("\t")[0]?.trim();
60
+ if (!sha)
61
+ throw new Error(`Could not resolve ref ${ref} for ${repo}`);
62
+ return sha;
63
+ }
64
+ if (source.type === "git") {
65
+ const ref = source.ref || "HEAD";
66
+ const output = execFileSync("git", ["ls-remote", source.original, ref], {
67
+ encoding: "utf8",
68
+ timeout: 30000
69
+ });
70
+ const sha = output.split("\t")[0]?.trim();
71
+ if (!sha)
72
+ throw new Error(`Could not resolve ref ${ref} for ${source.original}`);
73
+ return sha;
74
+ }
75
+ if (source.type === "local") {
76
+ const resolved = path.resolve(source.original);
77
+ if (!fs.existsSync(resolved)) {
78
+ throw new Error(`Local path not found: ${resolved}`);
79
+ }
80
+ return computeTreeHash(resolved);
81
+ }
82
+ if (source.type === "npm") {
83
+ throw new Error("npm source SHA resolution not yet implemented");
84
+ }
85
+ throw new Error(`Cannot resolve SHA for source type: ${source.type}`);
86
+ }
87
+ async fetch(source, pluginName) {
88
+ const sha = source.resolvedSha || await this.resolveSha(source);
89
+ const srcDir = this.sourceDir(source);
90
+ const destDir = this.shaDir(srcDir, sha);
91
+ if (fs.existsSync(destDir)) {
92
+ this.updateRef(srcDir, sha);
93
+ return { source: source.original, name: pluginName, sha, root: destDir };
94
+ }
95
+ ensureDir(srcDir);
96
+ if (source.type === "github") {
97
+ const repo = source.original.split("@")[0];
98
+ const tmpDir = fs.mkdtempSync(path.join(this.cacheRoot, TMP_PREFIX));
99
+ try {
100
+ execFileSync("git", ["clone", "--depth", "1", `https://github.com/${repo}.git`, tmpDir], {
101
+ stdio: "pipe",
102
+ timeout: 60000
103
+ });
104
+ const actualSha = execFileSync("git", ["rev-parse", "HEAD"], {
105
+ cwd: tmpDir,
106
+ encoding: "utf8",
107
+ timeout: 1e4
108
+ }).trim();
109
+ fs.rmSync(path.join(tmpDir, ".git"), { recursive: true, force: true });
110
+ const finalDest = this.shaDir(srcDir, actualSha);
111
+ if (fs.existsSync(finalDest)) {
112
+ fs.rmSync(tmpDir, { recursive: true, force: true });
113
+ this.updateRef(srcDir, actualSha);
114
+ return { source: source.original, name: pluginName, sha: actualSha, root: finalDest };
115
+ }
116
+ fs.renameSync(tmpDir, finalDest);
117
+ this.updateRef(srcDir, actualSha);
118
+ return { source: source.original, name: pluginName, sha: actualSha, root: finalDest };
119
+ } catch (e) {
120
+ if (fs.existsSync(tmpDir))
121
+ fs.rmSync(tmpDir, { recursive: true, force: true });
122
+ throw e;
123
+ }
124
+ }
125
+ if (source.type === "git") {
126
+ const tmpDir = fs.mkdtempSync(path.join(this.cacheRoot, TMP_PREFIX));
127
+ try {
128
+ execFileSync("git", ["clone", "--depth", "1", source.original, tmpDir], {
129
+ stdio: "pipe",
130
+ timeout: 60000
131
+ });
132
+ const actualSha = execFileSync("git", ["rev-parse", "HEAD"], {
133
+ cwd: tmpDir,
134
+ encoding: "utf8",
135
+ timeout: 1e4
136
+ }).trim();
137
+ fs.rmSync(path.join(tmpDir, ".git"), { recursive: true, force: true });
138
+ const finalDest = this.shaDir(srcDir, actualSha);
139
+ if (fs.existsSync(finalDest)) {
140
+ fs.rmSync(tmpDir, { recursive: true, force: true });
141
+ this.updateRef(srcDir, actualSha);
142
+ return { source: source.original, name: pluginName, sha: actualSha, root: finalDest };
143
+ }
144
+ fs.renameSync(tmpDir, finalDest);
145
+ this.updateRef(srcDir, actualSha);
146
+ return { source: source.original, name: pluginName, sha: actualSha, root: finalDest };
147
+ } catch (e) {
148
+ if (fs.existsSync(tmpDir))
149
+ fs.rmSync(tmpDir, { recursive: true, force: true });
150
+ throw e;
151
+ }
152
+ }
153
+ if (source.type === "local") {
154
+ const resolved = path.resolve(source.original);
155
+ if (!fs.existsSync(resolved)) {
156
+ throw new Error(`Local path not found: ${resolved}`);
157
+ }
158
+ copyDirSync(resolved, destDir);
159
+ this.updateRef(srcDir, sha);
160
+ return { source: source.original, name: pluginName, sha, root: destDir };
161
+ }
162
+ if (source.type === "npm") {
163
+ throw new Error("npm source fetch not yet implemented");
164
+ }
165
+ throw new Error(`Cannot fetch source type: ${source.type}`);
166
+ }
167
+ updateRef(srcDir, sha) {
168
+ const refs = {};
169
+ if (fs.existsSync(this.refPath(srcDir))) {
170
+ try {
171
+ const existing = JSON.parse(fs.readFileSync(this.refPath(srcDir), "utf8"));
172
+ Object.assign(refs, existing);
173
+ } catch {}
174
+ }
175
+ refs.latest = sha;
176
+ fs.writeFileSync(this.refPath(srcDir), JSON.stringify(refs, null, 2));
177
+ }
178
+ listCached() {
179
+ const results = [];
180
+ try {
181
+ const sourceDirs = fs.readdirSync(this.cacheRoot);
182
+ for (const dir of sourceDirs) {
183
+ const srcDir = path.join(this.cacheRoot, dir);
184
+ if (!fs.statSync(srcDir).isDirectory())
185
+ continue;
186
+ const entries = fs.readdirSync(srcDir);
187
+ for (const entry of entries) {
188
+ if (entry === "ref.json")
189
+ continue;
190
+ const shaDir = path.join(srcDir, entry);
191
+ if (!fs.statSync(shaDir).isDirectory())
192
+ continue;
193
+ const refs = {};
194
+ if (fs.existsSync(this.refPath(srcDir))) {
195
+ try {
196
+ Object.assign(refs, JSON.parse(fs.readFileSync(this.refPath(srcDir), "utf8")));
197
+ } catch {}
198
+ }
199
+ results.push({
200
+ source: dir,
201
+ name: entry,
202
+ sha: entry,
203
+ root: shaDir
204
+ });
205
+ }
206
+ }
207
+ } catch {}
208
+ return results;
209
+ }
210
+ remove(source, sha) {
211
+ const srcDir = this.sourceDir(source);
212
+ const target = this.shaDir(srcDir, sha);
213
+ if (fs.existsSync(target)) {
214
+ fs.rmSync(target, { recursive: true, force: true });
215
+ }
216
+ }
217
+ removeAll(source) {
218
+ const srcDir = this.sourceDir(source);
219
+ if (fs.existsSync(srcDir)) {
220
+ fs.rmSync(srcDir, { recursive: true, force: true });
221
+ }
222
+ }
223
+ }
224
+ function computeTreeHash(dir) {
225
+ const entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
226
+ const crypto = __require("node:crypto");
227
+ const hash = crypto.createHash("sha256");
228
+ for (const entry of entries) {
229
+ if (entry.name.startsWith("."))
230
+ continue;
231
+ if (entry.isDirectory()) {
232
+ hash.update(entry.name);
233
+ hash.update(computeTreeHash(path.join(dir, entry.name)));
234
+ } else if (entry.isFile()) {
235
+ hash.update(entry.name);
236
+ hash.update(fs.readFileSync(path.join(dir, entry.name)));
237
+ }
238
+ }
239
+ return hash.digest("hex");
240
+ }
241
+ function copyDirSync(src, dest) {
242
+ ensureDir(dest);
243
+ const entries = fs.readdirSync(src, { withFileTypes: true });
244
+ for (const entry of entries) {
245
+ if (entry.name.startsWith(".git"))
246
+ continue;
247
+ const srcPath = path.join(src, entry.name);
248
+ const destPath = path.join(dest, entry.name);
249
+ if (entry.isDirectory()) {
250
+ copyDirSync(srcPath, destPath);
251
+ } else if (entry.isFile()) {
252
+ fs.copyFileSync(srcPath, destPath);
253
+ }
254
+ }
255
+ }
256
+
257
+ // src/core/plugin-data.ts
258
+ import fs2 from "node:fs";
259
+ import path2 from "node:path";
260
+ class PluginDataManager {
261
+ dataRoot;
262
+ constructor() {
263
+ this.dataRoot = getPluginDataRoot();
264
+ ensureDir(this.dataRoot);
265
+ }
266
+ getPluginDataDir(pluginId) {
267
+ return path2.join(this.dataRoot, pluginId);
268
+ }
269
+ getPluginDataPath(pluginId) {
270
+ return path2.join(this.getPluginDataDir(pluginId), "data.json");
271
+ }
272
+ async loadData(pluginId) {
273
+ const dataPath = this.getPluginDataPath(pluginId);
274
+ if (!fs2.existsSync(dataPath)) {
275
+ return null;
276
+ }
277
+ try {
278
+ const data = readJsonFile(dataPath, null);
279
+ return data;
280
+ } catch {
281
+ return null;
282
+ }
283
+ }
284
+ async saveData(pluginId, data) {
285
+ const existing = await this.loadData(pluginId) || {};
286
+ const merged = { ...existing, ...data };
287
+ const dataPath = this.getPluginDataPath(pluginId);
288
+ ensureDir(path2.dirname(dataPath));
289
+ writeJsonAtomic(dataPath, merged);
290
+ }
291
+ async updateDependency(pluginId, packageName, version) {
292
+ const data = await this.loadData(pluginId);
293
+ if (!data) {
294
+ return;
295
+ }
296
+ if (!data.dependencies) {
297
+ data.dependencies = {};
298
+ }
299
+ data.dependencies[packageName] = version;
300
+ await this.saveData(pluginId, data);
301
+ }
302
+ async getDependencies(pluginId) {
303
+ const data = await this.loadData(pluginId);
304
+ return data?.dependencies || {};
305
+ }
306
+ async addLog(pluginId, level, message) {
307
+ const data = await this.loadData(pluginId);
308
+ if (!data) {
309
+ return;
310
+ }
311
+ if (!data.logs) {
312
+ data.logs = [];
313
+ }
314
+ data.logs.push({
315
+ timestamp: new Date().toISOString(),
316
+ level,
317
+ message
318
+ });
319
+ if (data.logs.length > 1000) {
320
+ data.logs = data.logs.slice(-1000);
321
+ }
322
+ await this.saveData(pluginId, data);
323
+ }
324
+ async updateStatus(pluginId, status, error) {
325
+ const data = await this.loadData(pluginId);
326
+ if (!data) {
327
+ return;
328
+ }
329
+ data.status = status;
330
+ if (error) {
331
+ data.error = error;
332
+ }
333
+ data.lastHealthCheck = new Date().toISOString();
334
+ await this.saveData(pluginId, data);
335
+ }
336
+ async removeData(pluginId) {
337
+ const dataDir = this.getPluginDataDir(pluginId);
338
+ if (fs2.existsSync(dataDir)) {
339
+ fs2.rmSync(dataDir, { recursive: true, force: true });
340
+ }
341
+ }
342
+ async cleanupOrphanedData() {
343
+ if (!fs2.existsSync(this.dataRoot)) {
344
+ return;
345
+ }
346
+ const pluginIds = fs2.readdirSync(this.dataRoot);
347
+ for (const pluginId of pluginIds) {
348
+ const pluginDir = path2.join(this.dataRoot, pluginId);
349
+ if (!fs2.statSync(pluginDir).isDirectory()) {
350
+ continue;
351
+ }
352
+ const dataPath = path2.join(pluginDir, "data.json");
353
+ if (!fs2.existsSync(dataPath)) {
354
+ fs2.rmSync(pluginDir, { recursive: true, force: true });
355
+ }
356
+ }
357
+ }
358
+ }
359
+
360
+ // src/core/plugin-lifecycle.ts
361
+ import fs3 from "node:fs";
362
+ import path3 from "node:path";
363
+ import { execFileSync as execFileSync2 } from "node:child_process";
364
+ class PluginLifecycle {
365
+ dataManager;
366
+ constructor() {
367
+ this.dataManager = new PluginDataManager;
368
+ }
369
+ async prepareDependencies(pluginId, pluginRoot) {
370
+ await this.dataManager.updateStatus(pluginId, "preparing");
371
+ const dataDir = path3.join(getPluginDataRoot(), pluginId);
372
+ ensureDir(dataDir);
373
+ const pkgJsonPath = path3.join(pluginRoot, "package.json");
374
+ if (fs3.existsSync(pkgJsonPath)) {
375
+ try {
376
+ const pkg = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf8"));
377
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
378
+ if (Object.keys(deps).length > 0) {
379
+ execFileSync2("npm", ["install", "--production"], {
380
+ cwd: pluginRoot,
381
+ stdio: "pipe",
382
+ timeout: 120000
383
+ });
384
+ }
385
+ } catch {}
386
+ }
387
+ const reqPath = path3.join(pluginRoot, "requirements.txt");
388
+ if (fs3.existsSync(reqPath)) {
389
+ try {
390
+ execFileSync2("pip", ["install", "-r", reqPath], {
391
+ stdio: "pipe",
392
+ timeout: 120000
393
+ });
394
+ } catch {}
395
+ }
396
+ await this.dataManager.updateStatus(pluginId, "healthy");
397
+ }
398
+ async removeData(pluginId) {
399
+ await this.dataManager.removeData(pluginId);
400
+ }
401
+ initData(pluginId) {
402
+ const dataDir = path3.join(getPluginDataRoot(), pluginId);
403
+ ensureDir(dataDir);
404
+ this.dataManager.saveData(pluginId, {
405
+ id: pluginId,
406
+ dependencies: {},
407
+ settings: {},
408
+ logs: [],
409
+ status: "healthy",
410
+ installedAt: new Date().toISOString()
411
+ });
412
+ }
413
+ }
414
+
415
+ // src/core/plugin-source.ts
416
+ var GITHUB_RE = /^(?:github\.com\/)?([a-zA-Z0-9][\w.-]*\/[a-zA-Z0-9][\w.-]*?)(?:@([\w./-]+))?$/;
417
+ var GIT_URL_RE = /^https?:\/\/.*\.git(?:\?.*)?(?:\/\/.*)?$/;
418
+ var SCP_GIT_RE = /^[\w.-]+@[\w.-]+:[\w./-]+$/;
419
+ var NPM_RE = /^npm:(@?[\w.-]+(?:\/[\w.-]+)?)(?:@([\w.-]+))?$/;
420
+ var MARKETPLACE_RE = /^marketplace:([\w.-]+)$/;
421
+ function parseSource(source) {
422
+ const github = source.match(GITHUB_RE);
423
+ if (github) {
424
+ return {
425
+ type: "github",
426
+ original: source,
427
+ ref: github[2] || undefined
428
+ };
429
+ }
430
+ if (NPM_RE.test(source)) {
431
+ const npm = source.match(NPM_RE);
432
+ return {
433
+ type: "npm",
434
+ original: source,
435
+ ref: npm[2] || undefined
436
+ };
437
+ }
438
+ if (MARKETPLACE_RE.test(source)) {
439
+ return {
440
+ type: "marketplace",
441
+ original: source
442
+ };
443
+ }
444
+ if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../") || source === "." || source === "..") {
445
+ return {
446
+ type: "local",
447
+ original: source
448
+ };
449
+ }
450
+ if (GIT_URL_RE.test(source) || SCP_GIT_RE.test(source)) {
451
+ return {
452
+ type: "git",
453
+ original: source
454
+ };
455
+ }
456
+ return {
457
+ type: "git",
458
+ original: source
459
+ };
460
+ }
461
+
462
+ // src/core/plugin-parse.ts
463
+ import fs4 from "node:fs";
464
+ import path4 from "node:path";
465
+ var PLUGIN_JSON_REL = path4.join(".claude-plugin", "plugin.json");
466
+ var MCP_JSON_REL = ".mcp.json";
467
+ var HOOKS_JSON_REL = path4.join("hooks", "hooks.json");
468
+ var SKILLS_DIR = "skills";
469
+ var COMMANDS_DIR = "commands";
470
+ var AGENTS_DIR = "agents";
471
+ function readManifest(pluginRoot) {
472
+ const manifestPath = path4.join(pluginRoot, PLUGIN_JSON_REL);
473
+ if (!fs4.existsSync(manifestPath)) {
474
+ return null;
475
+ }
476
+ try {
477
+ const raw = JSON.parse(fs4.readFileSync(manifestPath, "utf8"));
478
+ return {
479
+ name: raw.name ?? path4.basename(pluginRoot),
480
+ version: raw.version ?? "0.0.0",
481
+ description: raw.description,
482
+ source: raw.source,
483
+ icon: raw.icon,
484
+ assets: raw.assets,
485
+ entry: raw.entry
486
+ };
487
+ } catch {
488
+ return null;
489
+ }
490
+ }
491
+ function parseFrontmatter(content) {
492
+ const trimmed = content.trimStart();
493
+ if (!trimmed.startsWith("---")) {
494
+ return { frontmatter: {}, body: content };
495
+ }
496
+ const end = trimmed.indexOf("---", 3);
497
+ if (end === -1) {
498
+ return { frontmatter: {}, body: content };
499
+ }
500
+ const raw = trimmed.slice(3, end).trim();
501
+ const body = trimmed.slice(end + 3).trimStart();
502
+ const frontmatter = {};
503
+ for (const line of raw.split(`
504
+ `)) {
505
+ const sep = line.indexOf(":");
506
+ if (sep !== -1) {
507
+ const key = line.slice(0, sep).trim();
508
+ const val = line.slice(sep + 1).trim();
509
+ if (key === "name")
510
+ frontmatter.name = val;
511
+ if (key === "description")
512
+ frontmatter.description = val;
513
+ }
514
+ }
515
+ return { frontmatter, body };
516
+ }
517
+ function discoverSkills(pluginRoot) {
518
+ const skillsDir = path4.join(pluginRoot, SKILLS_DIR);
519
+ if (!fs4.existsSync(skillsDir))
520
+ return [];
521
+ const results = [];
522
+ try {
523
+ const entries = fs4.readdirSync(skillsDir, { withFileTypes: true });
524
+ for (const entry of entries) {
525
+ if (!entry.isDirectory()) {
526
+ if (entry.name.endsWith(".md")) {
527
+ const skillPath = path4.join(skillsDir, entry.name);
528
+ const content = fs4.readFileSync(skillPath, "utf8");
529
+ const { frontmatter } = parseFrontmatter(content);
530
+ results.push({
531
+ id: frontmatter.name ?? path4.basename(entry.name, ".md"),
532
+ type: "skills",
533
+ path: skillPath,
534
+ description: frontmatter.description
535
+ });
536
+ }
537
+ continue;
538
+ }
539
+ const skillMdPath = path4.join(skillsDir, entry.name, "SKILL.md");
540
+ if (fs4.existsSync(skillMdPath)) {
541
+ const content = fs4.readFileSync(skillMdPath, "utf8");
542
+ const { frontmatter } = parseFrontmatter(content);
543
+ results.push({
544
+ id: frontmatter.name ?? entry.name,
545
+ type: "skills",
546
+ path: skillMdPath,
547
+ description: frontmatter.description
548
+ });
549
+ }
550
+ }
551
+ } catch {}
552
+ return results;
553
+ }
554
+ function discoverCommands(pluginRoot) {
555
+ const commandsDir = path4.join(pluginRoot, COMMANDS_DIR);
556
+ if (!fs4.existsSync(commandsDir))
557
+ return [];
558
+ const results = [];
559
+ try {
560
+ const entries = fs4.readdirSync(commandsDir, { withFileTypes: true });
561
+ for (const entry of entries) {
562
+ if (entry.isFile() && entry.name.endsWith(".md")) {
563
+ const cmdPath = path4.join(commandsDir, entry.name);
564
+ const content = fs4.readFileSync(cmdPath, "utf8");
565
+ const { frontmatter } = parseFrontmatter(content);
566
+ results.push({
567
+ id: frontmatter.name ?? path4.basename(entry.name, ".md"),
568
+ type: "commands",
569
+ path: cmdPath,
570
+ description: frontmatter.description
571
+ });
572
+ }
573
+ }
574
+ } catch {}
575
+ return results;
576
+ }
577
+ function discoverAgents(pluginRoot) {
578
+ const agentsDir = path4.join(pluginRoot, AGENTS_DIR);
579
+ if (!fs4.existsSync(agentsDir))
580
+ return [];
581
+ const results = [];
582
+ try {
583
+ const entries = fs4.readdirSync(agentsDir, { withFileTypes: true });
584
+ for (const entry of entries) {
585
+ if (entry.isFile() && entry.name.endsWith(".md")) {
586
+ const agentPath = path4.join(agentsDir, entry.name);
587
+ const content = fs4.readFileSync(agentPath, "utf8");
588
+ const { frontmatter } = parseFrontmatter(content);
589
+ results.push({
590
+ id: frontmatter.name ?? path4.basename(entry.name, ".md"),
591
+ type: "agents",
592
+ path: agentPath,
593
+ description: frontmatter.description
594
+ });
595
+ }
596
+ }
597
+ } catch {}
598
+ return results;
599
+ }
600
+ function discoverHooks(pluginRoot) {
601
+ const hooksPath = path4.join(pluginRoot, HOOKS_JSON_REL);
602
+ if (!fs4.existsSync(hooksPath))
603
+ return [];
604
+ try {
605
+ const raw = JSON.parse(fs4.readFileSync(hooksPath, "utf8"));
606
+ const hooks = Array.isArray(raw) ? raw : raw.hooks ?? [];
607
+ return hooks.map((h, i) => ({
608
+ id: h.id ?? `hook-${i}`,
609
+ type: "hooks",
610
+ path: hooksPath,
611
+ description: h.description
612
+ }));
613
+ } catch {
614
+ return [];
615
+ }
616
+ }
617
+ function discoverMcpServers(pluginRoot) {
618
+ const mcpPath = path4.join(pluginRoot, MCP_JSON_REL);
619
+ if (!fs4.existsSync(mcpPath))
620
+ return [];
621
+ try {
622
+ const raw = JSON.parse(fs4.readFileSync(mcpPath, "utf8"));
623
+ const servers = raw.mcpServers ?? {};
624
+ return Object.entries(servers).map(([id, s]) => ({
625
+ id,
626
+ command: s.command ?? "",
627
+ args: s.args,
628
+ env: s.env,
629
+ cwd: s.cwd
630
+ }));
631
+ } catch {
632
+ return [];
633
+ }
634
+ }
635
+ function discoverComponents(pluginRoot) {
636
+ return {
637
+ skills: discoverSkills(pluginRoot),
638
+ commands: discoverCommands(pluginRoot),
639
+ agents: discoverAgents(pluginRoot),
640
+ hooks: discoverHooks(pluginRoot),
641
+ mcpServers: discoverMcpServers(pluginRoot)
642
+ };
643
+ }
644
+ function hasManifest(pluginRoot) {
645
+ return fs4.existsSync(path4.join(pluginRoot, PLUGIN_JSON_REL));
646
+ }
647
+
648
+ // src/core/plugin-manager.ts
649
+ class PluginManager {
650
+ configPath;
651
+ cache;
652
+ dataManager;
653
+ lifecycle;
654
+ secrets;
655
+ constructor(configPath) {
656
+ this.configPath = configPath || getConfigPath();
657
+ this.cache = new PluginCache;
658
+ this.dataManager = new PluginDataManager;
659
+ this.lifecycle = new PluginLifecycle;
660
+ this.secrets = new SecretsManager;
661
+ }
662
+ async inspectSource(sourceStr) {
663
+ const parsed = parseSource(sourceStr);
664
+ const sha = await this.cache.resolveSha(parsed);
665
+ parsed.resolvedSha = sha;
666
+ const cached = await this.cache.fetch(parsed, "");
667
+ let pluginRoot = cached.root;
668
+ if (!hasManifest(pluginRoot)) {
669
+ const entries = fs5.readdirSync(pluginRoot, { withFileTypes: true });
670
+ for (const entry of entries) {
671
+ if (entry.isDirectory()) {
672
+ const candidate = path5.join(pluginRoot, entry.name);
673
+ if (hasManifest(candidate)) {
674
+ pluginRoot = candidate;
675
+ break;
676
+ }
677
+ }
678
+ }
679
+ }
680
+ const manifest = readManifest(pluginRoot);
681
+ const components = discoverComponents(pluginRoot);
682
+ return { manifest, components, root: pluginRoot, source: sourceStr, sha };
683
+ }
684
+ async installPlugin(sourceStr, options) {
685
+ const info = await this.inspectSource(sourceStr);
686
+ const pluginName = options?.name || info.manifest?.name || path5.basename(info.root);
687
+ const pluginId = `${pluginName}@${info.sha.slice(0, 8)}`;
688
+ const config = loadConfig(this.configPath);
689
+ if (!config.plugins)
690
+ config.plugins = {};
691
+ if (config.plugins[pluginId]) {
692
+ throw new Error(`Plugin ${pluginId} is already installed`);
693
+ }
694
+ const serverNames = info.components.mcpServers.map((s) => `${pluginName}__${s.id}`);
695
+ for (const existing of Object.values(config.plugins)) {
696
+ for (const sn of serverNames) {
697
+ if (existing.serverNames.includes(sn)) {
698
+ throw new Error(`Server name collision: "${sn}" is already claimed by plugin "${existing.name}". Use --name to specify a different plugin name.`);
699
+ }
700
+ }
701
+ }
702
+ const dataDir = path5.join(getPluginDataRoot(), pluginId);
703
+ ensureDir(dataDir);
704
+ const enabled = options?.enabled ?? true;
705
+ const discovered = info.components;
706
+ const plugin = {
707
+ id: pluginId,
708
+ name: pluginName,
709
+ source: sourceStr,
710
+ version: info.manifest?.version || "0.0.0",
711
+ ref: sourceStr.includes("@") ? sourceStr.split("@")[1] || "latest" : "latest",
712
+ resolvedSha: info.sha,
713
+ installedAt: new Date().toISOString(),
714
+ root: info.root,
715
+ dataDir,
716
+ components: {
717
+ mcpServers: options?.components?.mcpServers ?? discovered.mcpServers.length > 0,
718
+ skills: options?.components?.skills ?? discovered.skills.length > 0,
719
+ hooks: options?.components?.hooks ?? discovered.hooks.length > 0,
720
+ agents: options?.components?.agents ?? discovered.agents.length > 0,
721
+ commands: options?.components?.commands ?? discovered.commands.length > 0
722
+ },
723
+ discovered,
724
+ enabled,
725
+ status: "healthy",
726
+ serverNames,
727
+ projectedClients: [],
728
+ approvals: options?.approvals || {}
729
+ };
730
+ config.plugins[pluginId] = plugin;
731
+ saveConfig(config, this.configPath);
732
+ for (const mcp of discovered.mcpServers) {
733
+ const nsName = `${pluginName}__${mcp.id}`;
734
+ const approved = plugin.approvals?.mcpServers !== false;
735
+ config.servers[nsName] = {
736
+ transport: "stdio",
737
+ command: mcp.command,
738
+ args: mcp.args,
739
+ env: mcp.env,
740
+ cwd: mcp.cwd || plugin.root,
741
+ enabled: enabled && approved
742
+ };
743
+ }
744
+ saveConfig(config, this.configPath);
745
+ this.lifecycle.initData(pluginId);
746
+ const syncConfig = loadConfig(this.configPath);
747
+ const summary = syncAllClients(syncConfig, this.secrets);
748
+ persistSyncState(summary, syncConfig);
749
+ saveConfig(syncConfig, this.configPath);
750
+ return plugin;
751
+ }
752
+ async updatePlugin(pluginId) {
753
+ const config = loadConfig(this.configPath);
754
+ if (!config.plugins)
755
+ throw new Error(`Plugin ${pluginId} not found`);
756
+ const current = config.plugins[pluginId];
757
+ if (!current)
758
+ throw new Error(`Plugin ${pluginId} not found`);
759
+ const info = await this.inspectSource(current.source);
760
+ const pluginName = info.manifest?.name || current.name;
761
+ for (const oldName of current.serverNames) {
762
+ delete config.servers[oldName];
763
+ }
764
+ current.version = info.manifest?.version || current.version;
765
+ current.resolvedSha = info.sha;
766
+ current.root = info.root;
767
+ current.discovered = info.components;
768
+ current.serverNames = info.components.mcpServers.map((s) => `${pluginName}__${s.id}`);
769
+ const approved = current.approvals?.mcpServers !== false;
770
+ for (const mcp of info.components.mcpServers) {
771
+ const nsName = `${pluginName}__${mcp.id}`;
772
+ config.servers[nsName] = {
773
+ transport: "stdio",
774
+ command: mcp.command,
775
+ args: mcp.args,
776
+ env: mcp.env,
777
+ cwd: mcp.cwd || current.root,
778
+ enabled: current.enabled && approved
779
+ };
780
+ }
781
+ current.approvals = {};
782
+ config.plugins[pluginId] = current;
783
+ saveConfig(config, this.configPath);
784
+ const syncConfig = loadConfig(this.configPath);
785
+ const summary = syncAllClients(syncConfig, this.secrets);
786
+ persistSyncState(summary, syncConfig);
787
+ saveConfig(syncConfig, this.configPath);
788
+ return current;
789
+ }
790
+ async uninstallPlugin(pluginId, keepData = false) {
791
+ const config = loadConfig(this.configPath);
792
+ if (!config.plugins)
793
+ throw new Error(`Plugin ${pluginId} not found`);
794
+ const plugin = config.plugins[pluginId];
795
+ if (!plugin)
796
+ throw new Error(`Plugin ${pluginId} not found`);
797
+ for (const name of plugin.serverNames) {
798
+ delete config.servers[name];
799
+ }
800
+ delete config.plugins[pluginId];
801
+ saveConfig(config, this.configPath);
802
+ if (!keepData) {
803
+ await this.lifecycle.removeData(pluginId);
804
+ }
805
+ const syncConfig = loadConfig(this.configPath);
806
+ const summary = syncAllClients(syncConfig, this.secrets);
807
+ persistSyncState(summary, syncConfig);
808
+ saveConfig(syncConfig, this.configPath);
809
+ }
810
+ async enablePlugin(pluginId) {
811
+ const config = loadConfig(this.configPath);
812
+ if (!config.plugins)
813
+ throw new Error(`Plugin ${pluginId} not found`);
814
+ const plugin = config.plugins[pluginId];
815
+ if (!plugin)
816
+ throw new Error(`Plugin ${pluginId} not found`);
817
+ plugin.enabled = true;
818
+ saveConfig(config, this.configPath);
819
+ const syncConfig = loadConfig(this.configPath);
820
+ const summary = syncAllClients(syncConfig, this.secrets);
821
+ persistSyncState(summary, syncConfig);
822
+ saveConfig(syncConfig, this.configPath);
823
+ }
824
+ async disablePlugin(pluginId) {
825
+ const config = loadConfig(this.configPath);
826
+ if (!config.plugins)
827
+ throw new Error(`Plugin ${pluginId} not found`);
828
+ const plugin = config.plugins[pluginId];
829
+ if (!plugin)
830
+ throw new Error(`Plugin ${pluginId} not found`);
831
+ plugin.enabled = false;
832
+ saveConfig(config, this.configPath);
833
+ const syncConfig = loadConfig(this.configPath);
834
+ const summary = syncAllClients(syncConfig, this.secrets);
835
+ persistSyncState(summary, syncConfig);
836
+ saveConfig(syncConfig, this.configPath);
837
+ }
838
+ async approveComponent(pluginId, component) {
839
+ const config = loadConfig(this.configPath);
840
+ if (!config.plugins)
841
+ throw new Error(`Plugin ${pluginId} not found`);
842
+ const plugin = config.plugins[pluginId];
843
+ if (!plugin)
844
+ throw new Error(`Plugin ${pluginId} not found`);
845
+ if (!plugin.approvals)
846
+ plugin.approvals = {};
847
+ plugin.approvals[component] = true;
848
+ if (component === "mcpServers") {
849
+ for (const name of plugin.serverNames) {
850
+ if (config.servers[name]) {
851
+ config.servers[name].enabled = plugin.enabled;
852
+ }
853
+ }
854
+ }
855
+ saveConfig(config, this.configPath);
856
+ const syncConfig = loadConfig(this.configPath);
857
+ const summary = syncAllClients(syncConfig, this.secrets);
858
+ persistSyncState(summary, syncConfig);
859
+ saveConfig(syncConfig, this.configPath);
860
+ }
861
+ async getPluginStatus(pluginId) {
862
+ const config = loadConfig(this.configPath);
863
+ return config.plugins?.[pluginId] || null;
864
+ }
865
+ async listPlugins() {
866
+ const config = loadConfig(this.configPath);
867
+ return Object.values(config.plugins || {});
868
+ }
869
+ async preparePlugin(pluginId) {
870
+ const config = loadConfig(this.configPath);
871
+ if (!config.plugins)
872
+ throw new Error(`Plugin ${pluginId} not found`);
873
+ const plugin = config.plugins[pluginId];
874
+ if (!plugin)
875
+ throw new Error(`Plugin ${pluginId} not found`);
876
+ await this.lifecycle.prepareDependencies(pluginId, plugin.root);
877
+ }
878
+ }
879
+ async function inspectPlugin(source) {
880
+ const manager = new PluginManager;
881
+ return manager.inspectSource(source);
882
+ }
883
+ async function installPlugin(source, options) {
884
+ const manager = new PluginManager;
885
+ return manager.installPlugin(source, options);
886
+ }
887
+ async function updatePlugin(name) {
888
+ const manager = new PluginManager;
889
+ return manager.updatePlugin(name);
890
+ }
891
+ async function uninstallPlugin(name, keepData) {
892
+ const manager = new PluginManager;
893
+ await manager.uninstallPlugin(name, keepData);
894
+ }
895
+ async function enablePlugin(name) {
896
+ const manager = new PluginManager;
897
+ await manager.enablePlugin(name);
898
+ }
899
+ async function disablePlugin(name) {
900
+ const manager = new PluginManager;
901
+ await manager.disablePlugin(name);
902
+ }
903
+ async function approvePluginComponent(name, component) {
904
+ const manager = new PluginManager;
905
+ await manager.approveComponent(name, component);
906
+ }
907
+ async function getPluginStatus(name) {
908
+ const manager = new PluginManager;
909
+ if (name)
910
+ return manager.getPluginStatus(name);
911
+ const plugins = await manager.listPlugins();
912
+ return plugins[0] || null;
913
+ }
914
+ async function listPlugins() {
915
+ const manager = new PluginManager;
916
+ return manager.listPlugins();
917
+ }
918
+ async function preparePlugin(name) {
919
+ const manager = new PluginManager;
920
+ await manager.preparePlugin(name);
921
+ }
922
+
923
+ export { PluginCache, PluginDataManager, PluginLifecycle, parseSource, readManifest, discoverComponents, hasManifest, PluginManager, inspectPlugin, installPlugin, updatePlugin, uninstallPlugin, enablePlugin, disablePlugin, approvePluginComponent, getPluginStatus, listPlugins, preparePlugin };