@kwonye/mcpx 0.1.98 → 0.1.99

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 (34) hide show
  1. package/dist/{cli-remv48t3.js → cli-1ewrsfj4.js} +1 -1
  2. package/dist/{cli-zjepj3sj.js → cli-28g7x9rn.js} +10 -1
  3. package/dist/{cli-gf70a85c.js → cli-7x2n23em.js} +112 -24
  4. package/dist/{cli-vy81xjm0.js → cli-b0xrqsjc.js} +1612 -488
  5. package/dist/cli-ct7asw78.js +598 -0
  6. package/dist/{cli-g4w6k655.js → cli-dy7x0zrg.js} +70 -5
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +137 -25
  9. package/dist/{config-2s2aqkqp.js → config-yx5m60fv.js} +2 -2
  10. package/dist/core/config.d.ts.map +1 -1
  11. package/dist/core/daemon.d.ts.map +1 -1
  12. package/dist/core/index.d.ts +3 -1
  13. package/dist/core/index.d.ts.map +1 -1
  14. package/dist/core/index.js +45 -21
  15. package/dist/core/marketplace-defaults.d.ts +4 -0
  16. package/dist/core/marketplace-defaults.d.ts.map +1 -0
  17. package/dist/core/marketplace-updater.d.ts +10 -0
  18. package/dist/core/marketplace-updater.d.ts.map +1 -0
  19. package/dist/core/marketplace.d.ts +67 -0
  20. package/dist/core/marketplace.d.ts.map +1 -0
  21. package/dist/core/paths.d.ts +3 -0
  22. package/dist/core/paths.d.ts.map +1 -1
  23. package/dist/core/plugin-cache.d.ts.map +1 -1
  24. package/dist/core/plugin-manager.d.ts +20 -7
  25. package/dist/core/plugin-manager.d.ts.map +1 -1
  26. package/dist/core/plugin-parse.d.ts.map +1 -1
  27. package/dist/marketplace-pwabb3ea.js +33 -0
  28. package/dist/{plugin-host-wr17xed6.js → plugin-host-9xcpnehq.js} +4 -5
  29. package/dist/{plugin-manager-7c9nk34n.js → plugin-manager-19q55n69.js} +4 -5
  30. package/dist/{registry-bnjb2345.js → registry-yvbxcaq6.js} +2 -2
  31. package/dist/types.d.ts +69 -0
  32. package/dist/types.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/dist/cli-er8me5qr.js +0 -1052
@@ -3,28 +3,733 @@ import {
3
3
  loadProjectConfig,
4
4
  saveConfig,
5
5
  saveProjectConfig
6
- } from "./cli-g4w6k655.js";
6
+ } from "./cli-dy7x0zrg.js";
7
7
  import {
8
+ SecretsManager,
8
9
  ensureGatewayToken
9
- } from "./cli-remv48t3.js";
10
+ } from "./cli-1ewrsfj4.js";
10
11
  import {
11
12
  ensureDir,
12
13
  findProjectConfigPath,
13
14
  getConfigPath,
14
15
  getManagedIndexPath,
16
+ getPluginCacheRoot,
17
+ getPluginDataRoot,
15
18
  getSkillsDir,
16
19
  homeDir,
17
20
  readJsonFile,
18
21
  sha256,
19
22
  writeJsonAtomic
20
- } from "./cli-zjepj3sj.js";
23
+ } from "./cli-28g7x9rn.js";
21
24
  import {
25
+ __require,
22
26
  isServerEnabled
23
27
  } from "./cli-s4gspqdf.js";
24
28
 
25
- // src/core/config-store.ts
29
+ // src/core/plugin-manager.ts
30
+ import fs15 from "node:fs";
31
+ import path21 from "node:path";
32
+
33
+ // src/core/plugin-cache.ts
26
34
  import fs from "node:fs";
27
35
  import path from "node:path";
36
+ import { execFileSync } from "node:child_process";
37
+ var TMP_PREFIX = "mcpx-acquire-";
38
+
39
+ class PluginCache {
40
+ cacheRoot;
41
+ constructor() {
42
+ this.cacheRoot = getPluginCacheRoot();
43
+ ensureDir(this.cacheRoot);
44
+ }
45
+ sourceDir(source) {
46
+ 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, "_")}` : source.type === "git-subdir" ? `git_subdir_${`${source.original}/${source.path ?? ""}`.replace(/[^a-zA-Z0-9_./@-]/g, "_")}` : `git_${source.original.replace(/[^a-zA-Z0-9_./@-]/g, "_")}`;
47
+ return path.join(this.cacheRoot, key);
48
+ }
49
+ refPath(srcDir) {
50
+ return path.join(srcDir, "ref.json");
51
+ }
52
+ shaDir(srcDir, sha) {
53
+ return path.join(srcDir, sha);
54
+ }
55
+ async resolveSha(source) {
56
+ const srcDir = this.sourceDir(source);
57
+ if (source.type === "github") {
58
+ const repo = source.original.split("@")[0];
59
+ const ref = source.ref || "HEAD";
60
+ const output = execFileSync("git", ["ls-remote", `https://github.com/${repo}.git`, ref], {
61
+ encoding: "utf8",
62
+ timeout: 30000
63
+ });
64
+ const sha = output.split("\t")[0]?.trim();
65
+ if (!sha)
66
+ throw new Error(`Could not resolve ref ${ref} for ${repo}`);
67
+ return sha;
68
+ }
69
+ if (source.type === "git" || source.type === "git-subdir") {
70
+ const ref = source.ref || "HEAD";
71
+ const output = execFileSync("git", ["ls-remote", source.original, ref], {
72
+ encoding: "utf8",
73
+ timeout: 30000
74
+ });
75
+ const sha = output.split("\t")[0]?.trim();
76
+ if (!sha)
77
+ throw new Error(`Could not resolve ref ${ref} for ${source.original}`);
78
+ return sha;
79
+ }
80
+ if (source.type === "local") {
81
+ const resolved = path.resolve(source.original);
82
+ if (!fs.existsSync(resolved)) {
83
+ throw new Error(`Local path not found: ${resolved}`);
84
+ }
85
+ return computeTreeHash(resolved);
86
+ }
87
+ if (source.type === "npm") {
88
+ throw new Error("npm source SHA resolution not yet implemented");
89
+ }
90
+ throw new Error(`Cannot resolve SHA for source type: ${source.type}`);
91
+ }
92
+ async fetch(source, pluginName) {
93
+ const sha = source.resolvedSha || await this.resolveSha(source);
94
+ const srcDir = this.sourceDir(source);
95
+ const destDir = this.shaDir(srcDir, sha);
96
+ if (fs.existsSync(destDir)) {
97
+ this.updateRef(srcDir, sha);
98
+ return { source: source.original, name: pluginName, sha, root: destDir };
99
+ }
100
+ ensureDir(srcDir);
101
+ if (source.type === "github" || source.type === "git" || source.type === "git-subdir") {
102
+ const remote = source.type === "github" ? `https://github.com/${source.original.split("@")[0]}.git` : source.original;
103
+ const target = source.resolvedSha || source.ref || "HEAD";
104
+ const tmpDir = fs.mkdtempSync(path.join(this.cacheRoot, TMP_PREFIX));
105
+ try {
106
+ execFileSync("git", ["init"], { cwd: tmpDir, stdio: "pipe", timeout: 1e4 });
107
+ execFileSync("git", ["remote", "add", "origin", remote], { cwd: tmpDir, stdio: "pipe", timeout: 1e4 });
108
+ execFileSync("git", ["fetch", "--depth", "1", "origin", target], {
109
+ cwd: tmpDir,
110
+ stdio: "pipe",
111
+ timeout: 60000
112
+ });
113
+ execFileSync("git", ["checkout", "--detach", "FETCH_HEAD"], { cwd: tmpDir, stdio: "pipe", timeout: 1e4 });
114
+ const actualSha = execFileSync("git", ["rev-parse", "HEAD"], {
115
+ cwd: tmpDir,
116
+ encoding: "utf8",
117
+ timeout: 1e4
118
+ }).trim();
119
+ fs.rmSync(path.join(tmpDir, ".git"), { recursive: true, force: true });
120
+ const finalDest = this.shaDir(srcDir, actualSha);
121
+ if (fs.existsSync(finalDest)) {
122
+ fs.rmSync(tmpDir, { recursive: true, force: true });
123
+ this.updateRef(srcDir, actualSha);
124
+ return { source: source.original, name: pluginName, sha: actualSha, root: finalDest };
125
+ }
126
+ if (source.type === "git-subdir") {
127
+ if (!source.path)
128
+ throw new Error("git-subdir source is missing a path");
129
+ const subdir = path.resolve(tmpDir, source.path);
130
+ if (!subdir.startsWith(path.resolve(tmpDir) + path.sep) || !fs.existsSync(subdir)) {
131
+ throw new Error(`Plugin subdirectory not found: ${source.path}`);
132
+ }
133
+ fs.renameSync(subdir, finalDest);
134
+ fs.rmSync(tmpDir, { recursive: true, force: true });
135
+ } else {
136
+ fs.renameSync(tmpDir, finalDest);
137
+ }
138
+ this.updateRef(srcDir, actualSha);
139
+ return { source: source.original, name: pluginName, sha: actualSha, root: finalDest };
140
+ } catch (e) {
141
+ if (fs.existsSync(tmpDir))
142
+ fs.rmSync(tmpDir, { recursive: true, force: true });
143
+ throw e;
144
+ }
145
+ }
146
+ if (source.type === "local") {
147
+ const resolved = path.resolve(source.original);
148
+ if (!fs.existsSync(resolved)) {
149
+ throw new Error(`Local path not found: ${resolved}`);
150
+ }
151
+ copyDirSync(resolved, destDir);
152
+ this.updateRef(srcDir, sha);
153
+ return { source: source.original, name: pluginName, sha, root: destDir };
154
+ }
155
+ if (source.type === "npm") {
156
+ throw new Error("npm source fetch not yet implemented");
157
+ }
158
+ throw new Error(`Cannot fetch source type: ${source.type}`);
159
+ }
160
+ updateRef(srcDir, sha) {
161
+ const refs = {};
162
+ if (fs.existsSync(this.refPath(srcDir))) {
163
+ try {
164
+ const existing = JSON.parse(fs.readFileSync(this.refPath(srcDir), "utf8"));
165
+ Object.assign(refs, existing);
166
+ } catch {}
167
+ }
168
+ refs.latest = sha;
169
+ fs.writeFileSync(this.refPath(srcDir), JSON.stringify(refs, null, 2));
170
+ }
171
+ listCached() {
172
+ const results = [];
173
+ try {
174
+ const sourceDirs = fs.readdirSync(this.cacheRoot);
175
+ for (const dir of sourceDirs) {
176
+ const srcDir = path.join(this.cacheRoot, dir);
177
+ if (!fs.statSync(srcDir).isDirectory())
178
+ continue;
179
+ const entries = fs.readdirSync(srcDir);
180
+ for (const entry of entries) {
181
+ if (entry === "ref.json")
182
+ continue;
183
+ const shaDir = path.join(srcDir, entry);
184
+ if (!fs.statSync(shaDir).isDirectory())
185
+ continue;
186
+ const refs = {};
187
+ if (fs.existsSync(this.refPath(srcDir))) {
188
+ try {
189
+ Object.assign(refs, JSON.parse(fs.readFileSync(this.refPath(srcDir), "utf8")));
190
+ } catch {}
191
+ }
192
+ results.push({
193
+ source: dir,
194
+ name: entry,
195
+ sha: entry,
196
+ root: shaDir
197
+ });
198
+ }
199
+ }
200
+ } catch {}
201
+ return results;
202
+ }
203
+ remove(source, sha) {
204
+ const srcDir = this.sourceDir(source);
205
+ const target = this.shaDir(srcDir, sha);
206
+ if (fs.existsSync(target)) {
207
+ fs.rmSync(target, { recursive: true, force: true });
208
+ }
209
+ }
210
+ removeAll(source) {
211
+ const srcDir = this.sourceDir(source);
212
+ if (fs.existsSync(srcDir)) {
213
+ fs.rmSync(srcDir, { recursive: true, force: true });
214
+ }
215
+ }
216
+ }
217
+ function computeTreeHash(dir) {
218
+ const entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
219
+ const crypto = __require("node:crypto");
220
+ const hash = crypto.createHash("sha256");
221
+ for (const entry of entries) {
222
+ if (entry.name.startsWith(".git"))
223
+ continue;
224
+ if (entry.name === ".DS_Store")
225
+ continue;
226
+ if (entry.isDirectory()) {
227
+ hash.update(entry.name);
228
+ hash.update(computeTreeHash(path.join(dir, entry.name)));
229
+ } else if (entry.isFile()) {
230
+ hash.update(entry.name);
231
+ hash.update(fs.readFileSync(path.join(dir, entry.name)));
232
+ }
233
+ }
234
+ return hash.digest("hex");
235
+ }
236
+ function copyDirSync(src, dest) {
237
+ ensureDir(dest);
238
+ const entries = fs.readdirSync(src, { withFileTypes: true });
239
+ for (const entry of entries) {
240
+ if (entry.name.startsWith(".git"))
241
+ continue;
242
+ if (entry.name === ".DS_Store")
243
+ continue;
244
+ const srcPath = path.join(src, entry.name);
245
+ const destPath = path.join(dest, entry.name);
246
+ if (entry.isDirectory()) {
247
+ copyDirSync(srcPath, destPath);
248
+ } else if (entry.isFile()) {
249
+ fs.copyFileSync(srcPath, destPath);
250
+ }
251
+ }
252
+ }
253
+
254
+ // src/core/plugin-data.ts
255
+ import fs2 from "node:fs";
256
+ import path2 from "node:path";
257
+ class PluginDataManager {
258
+ dataRoot;
259
+ constructor() {
260
+ this.dataRoot = getPluginDataRoot();
261
+ ensureDir(this.dataRoot);
262
+ }
263
+ getPluginDataDir(pluginId) {
264
+ return path2.join(this.dataRoot, pluginId);
265
+ }
266
+ getPluginDataPath(pluginId) {
267
+ return path2.join(this.getPluginDataDir(pluginId), "data.json");
268
+ }
269
+ async loadData(pluginId) {
270
+ const dataPath = this.getPluginDataPath(pluginId);
271
+ if (!fs2.existsSync(dataPath)) {
272
+ return null;
273
+ }
274
+ try {
275
+ const data = readJsonFile(dataPath, null);
276
+ return data;
277
+ } catch {
278
+ return null;
279
+ }
280
+ }
281
+ async saveData(pluginId, data) {
282
+ const existing = await this.loadData(pluginId) || {};
283
+ const merged = { ...existing, ...data };
284
+ const dataPath = this.getPluginDataPath(pluginId);
285
+ ensureDir(path2.dirname(dataPath));
286
+ writeJsonAtomic(dataPath, merged);
287
+ }
288
+ async updateDependency(pluginId, packageName, version) {
289
+ const data = await this.loadData(pluginId);
290
+ if (!data) {
291
+ return;
292
+ }
293
+ if (!data.dependencies) {
294
+ data.dependencies = {};
295
+ }
296
+ data.dependencies[packageName] = version;
297
+ await this.saveData(pluginId, data);
298
+ }
299
+ async getDependencies(pluginId) {
300
+ const data = await this.loadData(pluginId);
301
+ return data?.dependencies || {};
302
+ }
303
+ async addLog(pluginId, level, message) {
304
+ const data = await this.loadData(pluginId);
305
+ if (!data) {
306
+ return;
307
+ }
308
+ if (!data.logs) {
309
+ data.logs = [];
310
+ }
311
+ data.logs.push({
312
+ timestamp: new Date().toISOString(),
313
+ level,
314
+ message
315
+ });
316
+ if (data.logs.length > 1000) {
317
+ data.logs = data.logs.slice(-1000);
318
+ }
319
+ await this.saveData(pluginId, data);
320
+ }
321
+ async updateStatus(pluginId, status, error) {
322
+ const data = await this.loadData(pluginId);
323
+ if (!data) {
324
+ return;
325
+ }
326
+ data.status = status;
327
+ if (error) {
328
+ data.error = error;
329
+ }
330
+ data.lastHealthCheck = new Date().toISOString();
331
+ await this.saveData(pluginId, data);
332
+ }
333
+ async removeData(pluginId) {
334
+ const dataDir = this.getPluginDataDir(pluginId);
335
+ if (fs2.existsSync(dataDir)) {
336
+ fs2.rmSync(dataDir, { recursive: true, force: true });
337
+ }
338
+ }
339
+ async cleanupOrphanedData() {
340
+ if (!fs2.existsSync(this.dataRoot)) {
341
+ return;
342
+ }
343
+ const pluginIds = fs2.readdirSync(this.dataRoot);
344
+ for (const pluginId of pluginIds) {
345
+ const pluginDir = path2.join(this.dataRoot, pluginId);
346
+ if (!fs2.statSync(pluginDir).isDirectory()) {
347
+ continue;
348
+ }
349
+ const dataPath = path2.join(pluginDir, "data.json");
350
+ if (!fs2.existsSync(dataPath)) {
351
+ fs2.rmSync(pluginDir, { recursive: true, force: true });
352
+ }
353
+ }
354
+ }
355
+ }
356
+
357
+ // src/core/plugin-lifecycle.ts
358
+ import fs3 from "node:fs";
359
+ import path3 from "node:path";
360
+ import { execFileSync as execFileSync2 } from "node:child_process";
361
+ class PluginLifecycle {
362
+ dataManager;
363
+ constructor() {
364
+ this.dataManager = new PluginDataManager;
365
+ }
366
+ async prepareDependencies(pluginId, pluginRoot) {
367
+ await this.dataManager.updateStatus(pluginId, "preparing");
368
+ const dataDir = path3.join(getPluginDataRoot(), pluginId);
369
+ ensureDir(dataDir);
370
+ const pkgJsonPath = path3.join(pluginRoot, "package.json");
371
+ if (fs3.existsSync(pkgJsonPath)) {
372
+ try {
373
+ const pkg = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf8"));
374
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
375
+ if (Object.keys(deps).length > 0) {
376
+ execFileSync2("npm", ["install", "--production"], {
377
+ cwd: pluginRoot,
378
+ stdio: "pipe",
379
+ timeout: 120000
380
+ });
381
+ }
382
+ } catch {}
383
+ }
384
+ const reqPath = path3.join(pluginRoot, "requirements.txt");
385
+ if (fs3.existsSync(reqPath)) {
386
+ try {
387
+ execFileSync2("pip", ["install", "-r", reqPath], {
388
+ stdio: "pipe",
389
+ timeout: 120000
390
+ });
391
+ } catch {}
392
+ }
393
+ await this.dataManager.updateStatus(pluginId, "healthy");
394
+ }
395
+ async removeData(pluginId) {
396
+ await this.dataManager.removeData(pluginId);
397
+ }
398
+ initData(pluginId) {
399
+ const dataDir = path3.join(getPluginDataRoot(), pluginId);
400
+ ensureDir(dataDir);
401
+ this.dataManager.saveData(pluginId, {
402
+ id: pluginId,
403
+ dependencies: {},
404
+ settings: {},
405
+ logs: [],
406
+ status: "healthy",
407
+ installedAt: new Date().toISOString()
408
+ });
409
+ }
410
+ }
411
+
412
+ // src/core/plugin-source.ts
413
+ var GITHUB_RE = /^(?:github\.com\/)?([a-zA-Z0-9][\w.-]*\/[a-zA-Z0-9][\w.-]*?)(?:@([\w./-]+))?$/;
414
+ var GIT_URL_RE = /^https?:\/\/.*\.git(?:\?.*)?(?:\/\/.*)?$/;
415
+ var SCP_GIT_RE = /^[\w.-]+@[\w.-]+:[\w./-]+$/;
416
+ var NPM_RE = /^npm:(@?[\w.-]+(?:\/[\w.-]+)?)(?:@([\w.-]+))?$/;
417
+ var MARKETPLACE_RE = /^marketplace:([\w.-]+)$/;
418
+ function parseSource(source) {
419
+ const github = source.match(GITHUB_RE);
420
+ if (github) {
421
+ return {
422
+ type: "github",
423
+ original: source,
424
+ ref: github[2] || undefined
425
+ };
426
+ }
427
+ if (NPM_RE.test(source)) {
428
+ const npm = source.match(NPM_RE);
429
+ return {
430
+ type: "npm",
431
+ original: source,
432
+ ref: npm[2] || undefined
433
+ };
434
+ }
435
+ if (MARKETPLACE_RE.test(source)) {
436
+ return {
437
+ type: "marketplace",
438
+ original: source
439
+ };
440
+ }
441
+ if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../") || source === "." || source === "..") {
442
+ return {
443
+ type: "local",
444
+ original: source
445
+ };
446
+ }
447
+ if (GIT_URL_RE.test(source) || SCP_GIT_RE.test(source)) {
448
+ return {
449
+ type: "git",
450
+ original: source
451
+ };
452
+ }
453
+ return {
454
+ type: "git",
455
+ original: source
456
+ };
457
+ }
458
+
459
+ // src/core/plugin-parse.ts
460
+ import fs4 from "node:fs";
461
+ import path4 from "node:path";
462
+ import { z } from "zod";
463
+ var CLAUDE_PLUGIN_JSON_REL = path4.join(".claude-plugin", "plugin.json");
464
+ var CODEX_PLUGIN_JSON_REL = path4.join(".codex-plugin", "plugin.json");
465
+ var MCP_JSON_REL = ".mcp.json";
466
+ var HOOKS_JSON_REL = path4.join("hooks", "hooks.json");
467
+ var SKILLS_DIR = "skills";
468
+ var COMMANDS_DIR = "commands";
469
+ var AGENTS_DIR = "agents";
470
+ var manifestSchema = z.object({
471
+ name: z.string().optional(),
472
+ version: z.string().optional(),
473
+ description: z.string().optional(),
474
+ source: z.string().optional(),
475
+ icon: z.string().optional(),
476
+ assets: z.array(z.string()).optional(),
477
+ entry: z.string().optional(),
478
+ author: z.object({ name: z.string(), email: z.string().optional(), url: z.string().optional() }).optional(),
479
+ homepage: z.string().optional(),
480
+ repository: z.string().optional(),
481
+ license: z.string().optional(),
482
+ keywords: z.array(z.string()).optional(),
483
+ category: z.string().optional(),
484
+ skills: z.union([z.string(), z.array(z.string())]).optional(),
485
+ commands: z.union([z.string(), z.array(z.string())]).optional(),
486
+ agents: z.union([z.string(), z.array(z.string())]).optional(),
487
+ hooks: z.unknown().optional(),
488
+ mcpServers: z.unknown().optional(),
489
+ lspServers: z.unknown().optional(),
490
+ apps: z.unknown().optional(),
491
+ settings: z.unknown().optional(),
492
+ outputStyles: z.unknown().optional(),
493
+ dependencies: z.unknown().optional(),
494
+ interface: z.object({
495
+ displayName: z.string().optional(),
496
+ shortDescription: z.string().optional(),
497
+ category: z.string().optional()
498
+ }).passthrough().optional()
499
+ }).passthrough();
500
+ function manifestPath(pluginRoot) {
501
+ for (const rel of [CLAUDE_PLUGIN_JSON_REL, CODEX_PLUGIN_JSON_REL]) {
502
+ const candidate = path4.join(pluginRoot, rel);
503
+ if (fs4.existsSync(candidate))
504
+ return candidate;
505
+ }
506
+ return null;
507
+ }
508
+ function readRawManifest(pluginRoot) {
509
+ const filePath = manifestPath(pluginRoot);
510
+ if (!filePath)
511
+ return null;
512
+ try {
513
+ return manifestSchema.parse(JSON.parse(fs4.readFileSync(filePath, "utf8")));
514
+ } catch {
515
+ return null;
516
+ }
517
+ }
518
+ function sanitizeComponentId(id) {
519
+ const stripped = id.replace(/[\/\\]/g, "_").replace(/^\.+/, "");
520
+ return stripped.length > 0 ? stripped : "unnamed";
521
+ }
522
+ function readManifest(pluginRoot) {
523
+ const raw = readRawManifest(pluginRoot);
524
+ if (!raw)
525
+ return null;
526
+ const declaredCapabilities = ["skills", "commands", "agents", "hooks", "mcpServers", "lspServers", "apps", "settings", "outputStyles", "dependencies"].filter((key) => raw[key] !== undefined);
527
+ return {
528
+ name: raw.name ?? path4.basename(pluginRoot),
529
+ version: raw.version ?? "0.0.0",
530
+ description: raw.description ?? raw.interface?.shortDescription,
531
+ source: raw.source,
532
+ icon: raw.icon,
533
+ assets: raw.assets,
534
+ entry: raw.entry,
535
+ author: raw.author,
536
+ homepage: raw.homepage,
537
+ repository: raw.repository,
538
+ license: raw.license,
539
+ keywords: raw.keywords,
540
+ displayName: raw.interface?.displayName,
541
+ category: raw.category ?? raw.interface?.category,
542
+ declaredCapabilities
543
+ };
544
+ }
545
+ function declaredPaths(raw, key, fallback) {
546
+ const value = raw?.[key];
547
+ if (typeof value === "string")
548
+ return [value];
549
+ if (Array.isArray(value))
550
+ return value;
551
+ return [fallback];
552
+ }
553
+ function resolveWithinRoot(pluginRoot, declared) {
554
+ const resolved = path4.resolve(pluginRoot, declared);
555
+ const root = path4.resolve(pluginRoot);
556
+ return resolved === root || resolved.startsWith(root + path4.sep) ? resolved : null;
557
+ }
558
+ function parseFrontmatter(content) {
559
+ const trimmed = content.trimStart();
560
+ if (!trimmed.startsWith("---")) {
561
+ return { frontmatter: {}, body: content };
562
+ }
563
+ const end = trimmed.indexOf("---", 3);
564
+ if (end === -1) {
565
+ return { frontmatter: {}, body: content };
566
+ }
567
+ const raw = trimmed.slice(3, end).trim();
568
+ const body = trimmed.slice(end + 3).trimStart();
569
+ const frontmatter = {};
570
+ for (const line of raw.split(`
571
+ `)) {
572
+ const sep = line.indexOf(":");
573
+ if (sep !== -1) {
574
+ const key = line.slice(0, sep).trim();
575
+ const val = line.slice(sep + 1).trim();
576
+ if (key === "name")
577
+ frontmatter.name = val;
578
+ if (key === "description")
579
+ frontmatter.description = val;
580
+ }
581
+ }
582
+ return { frontmatter, body };
583
+ }
584
+ function discoverSkills(pluginRoot, raw) {
585
+ const results = [];
586
+ for (const declared of declaredPaths(raw, "skills", SKILLS_DIR))
587
+ try {
588
+ const skillsDir = resolveWithinRoot(pluginRoot, declared);
589
+ if (!skillsDir || !fs4.existsSync(skillsDir))
590
+ continue;
591
+ const entries = fs4.readdirSync(skillsDir, { withFileTypes: true });
592
+ for (const entry of entries) {
593
+ if (!entry.isDirectory()) {
594
+ if (entry.name.endsWith(".md")) {
595
+ const skillPath = path4.join(skillsDir, entry.name);
596
+ const content = fs4.readFileSync(skillPath, "utf8");
597
+ const { frontmatter } = parseFrontmatter(content);
598
+ results.push({
599
+ id: sanitizeComponentId(frontmatter.name ?? path4.basename(entry.name, ".md")),
600
+ type: "skills",
601
+ path: skillPath,
602
+ description: frontmatter.description
603
+ });
604
+ }
605
+ continue;
606
+ }
607
+ const skillMdPath = path4.join(skillsDir, entry.name, "SKILL.md");
608
+ if (fs4.existsSync(skillMdPath)) {
609
+ const content = fs4.readFileSync(skillMdPath, "utf8");
610
+ const { frontmatter } = parseFrontmatter(content);
611
+ results.push({
612
+ id: sanitizeComponentId(frontmatter.name ?? entry.name),
613
+ type: "skills",
614
+ path: skillMdPath,
615
+ description: frontmatter.description
616
+ });
617
+ }
618
+ }
619
+ } catch {}
620
+ return results;
621
+ }
622
+ function discoverCommands(pluginRoot, raw) {
623
+ const results = [];
624
+ for (const declared of declaredPaths(raw, "commands", COMMANDS_DIR))
625
+ try {
626
+ const commandsDir = resolveWithinRoot(pluginRoot, declared);
627
+ if (!commandsDir || !fs4.existsSync(commandsDir))
628
+ continue;
629
+ const entries = fs4.readdirSync(commandsDir, { withFileTypes: true });
630
+ for (const entry of entries) {
631
+ if (entry.isFile() && entry.name.endsWith(".md")) {
632
+ const cmdPath = path4.join(commandsDir, entry.name);
633
+ const content = fs4.readFileSync(cmdPath, "utf8");
634
+ const { frontmatter } = parseFrontmatter(content);
635
+ results.push({
636
+ id: sanitizeComponentId(frontmatter.name ?? path4.basename(entry.name, ".md")),
637
+ type: "commands",
638
+ path: cmdPath,
639
+ description: frontmatter.description
640
+ });
641
+ }
642
+ }
643
+ } catch {}
644
+ return results;
645
+ }
646
+ function discoverAgents(pluginRoot, raw) {
647
+ const results = [];
648
+ for (const declared of declaredPaths(raw, "agents", AGENTS_DIR))
649
+ try {
650
+ const agentsDir = resolveWithinRoot(pluginRoot, declared);
651
+ if (!agentsDir || !fs4.existsSync(agentsDir))
652
+ continue;
653
+ const entries = fs4.readdirSync(agentsDir, { withFileTypes: true });
654
+ for (const entry of entries) {
655
+ if (entry.isFile() && entry.name.endsWith(".md")) {
656
+ const agentPath = path4.join(agentsDir, entry.name);
657
+ const content = fs4.readFileSync(agentPath, "utf8");
658
+ const { frontmatter } = parseFrontmatter(content);
659
+ results.push({
660
+ id: sanitizeComponentId(frontmatter.name ?? path4.basename(entry.name, ".md")),
661
+ type: "agents",
662
+ path: agentPath,
663
+ description: frontmatter.description
664
+ });
665
+ }
666
+ }
667
+ } catch {}
668
+ return results;
669
+ }
670
+ function discoverHooks(pluginRoot, raw) {
671
+ const declared = typeof raw?.hooks === "string" ? raw.hooks : undefined;
672
+ const hooksPath = declared ? resolveWithinRoot(pluginRoot, declared) : [path4.join(pluginRoot, HOOKS_JSON_REL), path4.join(pluginRoot, "hooks.json")].find(fs4.existsSync) ?? null;
673
+ if (!hooksPath || !fs4.existsSync(hooksPath))
674
+ return [];
675
+ try {
676
+ const raw2 = JSON.parse(fs4.readFileSync(hooksPath, "utf8"));
677
+ const hooks = Array.isArray(raw2) ? raw2 : raw2.hooks ?? [];
678
+ return hooks.map((h, i) => ({
679
+ id: h.id ?? `hook-${i}`,
680
+ type: "hooks",
681
+ path: hooksPath,
682
+ description: h.description
683
+ }));
684
+ } catch {
685
+ return [];
686
+ }
687
+ }
688
+ function discoverMcpServers(pluginRoot, raw) {
689
+ const declared = typeof raw?.mcpServers === "string" ? raw.mcpServers : undefined;
690
+ const mcpPath = declared ? resolveWithinRoot(pluginRoot, declared) : path4.join(pluginRoot, MCP_JSON_REL);
691
+ if (!mcpPath || !fs4.existsSync(mcpPath))
692
+ return [];
693
+ try {
694
+ const raw2 = JSON.parse(fs4.readFileSync(mcpPath, "utf8"));
695
+ const servers = raw2.mcpServers ?? {};
696
+ return Object.entries(servers).map(([id, s]) => {
697
+ const isHttp = s.type === "http" || typeof s.url === "string";
698
+ return {
699
+ id,
700
+ transport: isHttp ? "http" : "stdio",
701
+ command: s.command ?? "",
702
+ args: s.args,
703
+ env: s.env,
704
+ cwd: s.cwd,
705
+ url: isHttp ? s.url : undefined,
706
+ oauthResource: s.oauth_resource
707
+ };
708
+ });
709
+ } catch {
710
+ return [];
711
+ }
712
+ }
713
+ function discoverComponents(pluginRoot) {
714
+ const raw = readRawManifest(pluginRoot);
715
+ return {
716
+ skills: discoverSkills(pluginRoot, raw),
717
+ commands: discoverCommands(pluginRoot, raw),
718
+ agents: discoverAgents(pluginRoot, raw),
719
+ hooks: discoverHooks(pluginRoot, raw),
720
+ mcpServers: discoverMcpServers(pluginRoot, raw)
721
+ };
722
+ }
723
+ function hasManifest(pluginRoot) {
724
+ return manifestPath(pluginRoot) !== null;
725
+ }
726
+ function resolvePluginVars(value, pluginRoot, dataDir) {
727
+ return value.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginRoot).replace(/\$\{DATA\}/g, dataDir).replace(/\$\{CLAUDE_PROJECT_DIR\}/g, pluginRoot);
728
+ }
729
+
730
+ // src/core/config-store.ts
731
+ import fs5 from "node:fs";
732
+ import path5 from "node:path";
28
733
  var LOCK_STALE_MS = 5000;
29
734
  var LOCK_RETRY_MS = 50;
30
735
  var LOCK_MAX_RETRIES = 40;
@@ -35,19 +740,19 @@ function getLockPath(filePath) {
35
740
  function acquireLock(filePath) {
36
741
  const lockPath = getLockPath(filePath);
37
742
  try {
38
- fs.mkdirSync(path.dirname(lockPath), { recursive: true });
39
- const fd = fs.openSync(lockPath, "wx");
40
- fs.writeSync(fd, String(process.pid));
41
- fs.closeSync(fd);
743
+ fs5.mkdirSync(path5.dirname(lockPath), { recursive: true });
744
+ const fd = fs5.openSync(lockPath, "wx");
745
+ fs5.writeSync(fd, String(process.pid));
746
+ fs5.closeSync(fd);
42
747
  return true;
43
748
  } catch (err) {
44
749
  if (err.code !== "EEXIST") {
45
750
  return false;
46
751
  }
47
752
  try {
48
- const stat = fs.statSync(lockPath);
753
+ const stat = fs5.statSync(lockPath);
49
754
  if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
50
- fs.unlinkSync(lockPath);
755
+ fs5.unlinkSync(lockPath);
51
756
  return acquireLock(filePath);
52
757
  }
53
758
  } catch {
@@ -58,7 +763,7 @@ function acquireLock(filePath) {
58
763
  }
59
764
  function releaseLock(filePath) {
60
765
  try {
61
- fs.unlinkSync(getLockPath(filePath));
766
+ fs5.unlinkSync(getLockPath(filePath));
62
767
  } catch {}
63
768
  }
64
769
  function mutateFile(filePath, load, save, fn) {
@@ -100,8 +805,8 @@ async function mutateActiveConfig(options, mutator) {
100
805
  if (!options.global) {
101
806
  const projectConfigPath = findProjectConfigPath();
102
807
  if (options.local) {
103
- const projectPath = projectConfigPath ? path.dirname(projectConfigPath) : process.cwd();
104
- const localPath = path.join(projectPath, ".mcpx.json");
808
+ const projectPath = projectConfigPath ? path5.dirname(projectConfigPath) : process.cwd();
809
+ const localPath = path5.join(projectPath, ".mcpx.json");
105
810
  const globalConfig = loadConfig(globalPath);
106
811
  const result2 = await mutateProjectConfig(localPath, async (projectConfig) => {
107
812
  const mergedConfig = {
@@ -121,7 +826,7 @@ async function mutateActiveConfig(options, mutator) {
121
826
  return { type: "project", configPath: localPath, projectPath, result: result2 };
122
827
  }
123
828
  if (projectConfigPath) {
124
- const projectPath = path.dirname(projectConfigPath);
829
+ const projectPath = path5.dirname(projectConfigPath);
125
830
  const result2 = await mutateConfig((config) => mutator(config, { type: "project", projectPath }), globalPath);
126
831
  return { type: "project", configPath: globalPath, projectPath, result: result2 };
127
832
  }
@@ -130,67 +835,189 @@ async function mutateActiveConfig(options, mutator) {
130
835
  return { type: "global", configPath: globalPath, result };
131
836
  }
132
837
 
133
- // src/core/skills.ts
134
- import fs2 from "node:fs";
135
- import path2 from "node:path";
136
- function listSkills() {
137
- const skillsDir = getSkillsDir();
138
- if (!fs2.existsSync(skillsDir)) {
139
- return [];
838
+ // src/adapters/utils/index.ts
839
+ function getManagedEntryNames(managedIndex, clientId) {
840
+ return Object.keys(managedIndex.managed[clientId]?.entries ?? {});
841
+ }
842
+ function isManagedEntry(managedIndex, clientId, entryName) {
843
+ return Boolean(managedIndex.managed[clientId]?.entries?.[entryName]);
844
+ }
845
+ function pruneStaleManagedEntries(managedIndex, clientId, servers, keepEntryNames) {
846
+ const keep = new Set(keepEntryNames);
847
+ const managedNames = getManagedEntryNames(managedIndex, clientId);
848
+ for (const name of managedNames) {
849
+ if (!keep.has(name)) {
850
+ delete servers[name];
851
+ }
140
852
  }
141
- const files = fs2.readdirSync(skillsDir);
142
- const skills = [];
143
- for (const file of files) {
144
- if (file.endsWith(".md")) {
145
- const id = path2.basename(file, ".md");
146
- const content = fs2.readFileSync(path2.join(skillsDir, file), "utf-8");
147
- skills.push({ id, content });
853
+ for (const name of Object.keys(servers)) {
854
+ if (!keep.has(name) && name.endsWith(" (mcpx)")) {
855
+ delete servers[name];
856
+ }
857
+ }
858
+ }
859
+ function ensureManagedEntryWritable(managedIndex, clientId, entryName, existingValue, expectedEntry) {
860
+ if (existingValue === undefined || existingValue === null) {
861
+ return null;
862
+ }
863
+ if (isManagedEntry(managedIndex, clientId, entryName)) {
864
+ return null;
865
+ }
866
+ if (expectedEntry && isManagedGatewayProjection(entryName) && matchesExpectedShape(existingValue, expectedEntry)) {
867
+ if (!managedIndex.managed[clientId]) {
868
+ managedIndex.managed[clientId] = { configPath: "", entries: {} };
148
869
  }
870
+ managedIndex.managed[clientId].entries[entryName] = {
871
+ fingerprint: sha256(JSON.stringify(existingValue)),
872
+ lastSyncedAt: new Date().toISOString()
873
+ };
874
+ return null;
875
+ }
876
+ return `Cannot sync managed entry "${entryName}" because an unmanaged entry already exists.`;
877
+ }
878
+ function matchesExpectedShape(value, expected) {
879
+ if (!value || typeof value !== "object")
880
+ return false;
881
+ const v = value;
882
+ if (expected.url && v.url !== expected.url)
883
+ return false;
884
+ if (expected.command && v.command !== expected.command)
885
+ return false;
886
+ return true;
887
+ }
888
+ function setManagedEntries(managedIndex, clientId, configPath, entries) {
889
+ if (!managedIndex.managed[clientId]) {
890
+ managedIndex.managed[clientId] = {
891
+ configPath,
892
+ entries: {}
893
+ };
894
+ }
895
+ managedIndex.managed[clientId].configPath = configPath;
896
+ managedIndex.managed[clientId].entries = Object.fromEntries(Object.entries(entries).map(([entryName, serializedEntry]) => [entryName, {
897
+ fingerprint: sha256(serializedEntry),
898
+ lastSyncedAt: new Date().toISOString()
899
+ }]));
900
+ }
901
+ function okResult(clientId, configPath, message) {
902
+ return {
903
+ clientId,
904
+ status: "SYNCED",
905
+ configPath,
906
+ message
907
+ };
908
+ }
909
+ function errorResult(clientId, configPath, message) {
910
+ return {
911
+ clientId,
912
+ status: "ERROR",
913
+ configPath,
914
+ message
915
+ };
916
+ }
917
+ function managedGatewayEntryName(serverName) {
918
+ return `${serverName} (mcpx)`;
919
+ }
920
+ function isManagedGatewayProjection(entryName) {
921
+ return entryName.endsWith(" (mcpx)");
922
+ }
923
+ function sortRecord(record) {
924
+ if (!record || Object.keys(record).length === 0) {
925
+ return;
926
+ }
927
+ return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
928
+ }
929
+ function normalizeServerSpec(spec) {
930
+ if (spec.transport === "http") {
931
+ return {
932
+ transport: "http",
933
+ url: spec.url,
934
+ headers: sortRecord(spec.headers)
935
+ };
149
936
  }
150
- return skills;
937
+ return {
938
+ transport: "stdio",
939
+ command: spec.command,
940
+ args: spec.args && spec.args.length > 0 ? [...spec.args] : undefined,
941
+ env: sortRecord(spec.env),
942
+ cwd: spec.cwd
943
+ };
151
944
  }
152
- function getSkill(id) {
153
- const skillsDir = getSkillsDir();
154
- const filePath = path2.join(skillsDir, `${id}.md`);
155
- if (!fs2.existsSync(filePath)) {
156
- return null;
945
+ function serializeServerSpec(spec) {
946
+ return JSON.stringify(normalizeServerSpec(spec));
947
+ }
948
+ function serverSpecsEqual(left, right) {
949
+ return serializeServerSpec(left) === serializeServerSpec(right);
950
+ }
951
+ function emptyImportScan(clientId, configPath) {
952
+ return {
953
+ clientId,
954
+ configPath,
955
+ candidates: [],
956
+ skipped: []
957
+ };
958
+ }
959
+ function buildImportSkip(clientId, sourceEntryName, reason, configPath, serverName = sourceEntryName) {
960
+ return {
961
+ clientId,
962
+ configPath,
963
+ sourceEntryName,
964
+ serverName,
965
+ reason
966
+ };
967
+ }
968
+ function removeSourceEntries(entries, names) {
969
+ for (const name of names ?? []) {
970
+ delete entries[name];
157
971
  }
158
- const content = fs2.readFileSync(filePath, "utf-8");
159
- return { id, content };
160
972
  }
161
- function saveSkill(id, content) {
162
- const skillsDir = getSkillsDir();
163
- ensureDir(skillsDir);
164
- const filePath = path2.join(skillsDir, `${id}.md`);
165
- fs2.writeFileSync(filePath, content, "utf-8");
973
+ function purgeManagedFromDisabledArray(raw, managedNames) {
974
+ const existingDisabled = raw.disabledMcpServers ?? [];
975
+ raw.disabledMcpServers = existingDisabled.filter((name) => !managedNames.includes(name));
976
+ if (Array.isArray(raw.disabledMcpServers) && raw.disabledMcpServers.length === 0) {
977
+ delete raw.disabledMcpServers;
978
+ }
166
979
  }
167
- function deleteSkill(id) {
168
- const skillsDir = getSkillsDir();
169
- const filePath = path2.join(skillsDir, `${id}.md`);
170
- if (fs2.existsSync(filePath)) {
171
- fs2.unlinkSync(filePath);
980
+ function purgeManagedFromExcludedArray(raw, managedNames) {
981
+ const mcp = raw.mcp ?? {};
982
+ const existingExcluded = mcp.excluded ?? [];
983
+ mcp.excluded = existingExcluded.filter((name) => !managedNames.includes(name));
984
+ if (Array.isArray(mcp.excluded) && mcp.excluded.length === 0) {
985
+ delete mcp.excluded;
172
986
  }
987
+ raw.mcp = Object.keys(mcp).length > 0 ? mcp : undefined;
988
+ }
989
+ function detectManagedEntryDrift(managedIndex, clientId, entryName, existingValue) {
990
+ const recorded = managedIndex.managed[clientId]?.entries?.[entryName];
991
+ if (!recorded || existingValue === undefined)
992
+ return false;
993
+ const liveFingerprint = sha256(JSON.stringify(existingValue));
994
+ return liveFingerprint !== recorded.fingerprint;
173
995
  }
174
996
 
997
+ // src/adapters/claude.ts
998
+ import fs8 from "node:fs";
999
+ import path8 from "node:path";
1000
+ import { z as z2 } from "zod";
1001
+
175
1002
  // src/core/plugin-projections.ts
176
- import fs3 from "node:fs";
177
- import path3 from "node:path";
1003
+ import fs6 from "node:fs";
1004
+ import path6 from "node:path";
178
1005
  var OWNERSHIP_MANIFEST = "mcpx-plugins.json";
179
1006
  function loadOwnership(targetDir) {
180
- const manifestPath = path3.join(targetDir, OWNERSHIP_MANIFEST);
181
- if (!fs3.existsSync(manifestPath)) {
1007
+ const manifestPath2 = path6.join(targetDir, OWNERSHIP_MANIFEST);
1008
+ if (!fs6.existsSync(manifestPath2)) {
182
1009
  return { version: 1, plugins: {} };
183
1010
  }
184
1011
  try {
185
- return JSON.parse(fs3.readFileSync(manifestPath, "utf8"));
1012
+ return JSON.parse(fs6.readFileSync(manifestPath2, "utf8"));
186
1013
  } catch {
187
- console.error(`[mcpx] Warning: corrupt plugin ownership manifest at ${manifestPath}, resetting`);
1014
+ console.error(`[mcpx] Warning: corrupt plugin ownership manifest at ${manifestPath2}, resetting`);
188
1015
  return { version: 1, plugins: {} };
189
1016
  }
190
1017
  }
191
1018
  function saveOwnership(targetDir, manifest) {
192
1019
  ensureDir(targetDir);
193
- fs3.writeFileSync(path3.join(targetDir, OWNERSHIP_MANIFEST), JSON.stringify(manifest, null, 2));
1020
+ fs6.writeFileSync(path6.join(targetDir, OWNERSHIP_MANIFEST), JSON.stringify(manifest, null, 2));
194
1021
  }
195
1022
  function recordOwned(targetDir, pluginId, paths) {
196
1023
  const manifest = loadOwnership(targetDir);
@@ -214,10 +1041,10 @@ function removeOwned(targetDir, pluginId) {
214
1041
  function prunePluginProjections(targetDir, pluginId) {
215
1042
  const paths = removeOwned(targetDir, pluginId);
216
1043
  for (const p of paths) {
217
- const fullPath = path3.join(targetDir, p);
1044
+ const fullPath = path6.join(targetDir, p);
218
1045
  try {
219
- if (fs3.existsSync(fullPath)) {
220
- fs3.rmSync(fullPath, { recursive: true, force: true });
1046
+ if (fs6.existsSync(fullPath)) {
1047
+ fs6.rmSync(fullPath, { recursive: true, force: true });
221
1048
  }
222
1049
  } catch {}
223
1050
  }
@@ -241,9 +1068,9 @@ function nsName(pluginName, id) {
241
1068
  return `${pluginName}__${id}`;
242
1069
  }
243
1070
  function assertWithinBase(base, target) {
244
- const resolvedBase = path3.resolve(base);
245
- const resolvedTarget = path3.resolve(target);
246
- if (resolvedTarget !== resolvedBase && !resolvedTarget.startsWith(resolvedBase + path3.sep)) {
1071
+ const resolvedBase = path6.resolve(base);
1072
+ const resolvedTarget = path6.resolve(target);
1073
+ if (resolvedTarget !== resolvedBase && !resolvedTarget.startsWith(resolvedBase + path6.sep)) {
247
1074
  throw new Error(`Refusing to write outside projection base: ${target} (base: ${base})`);
248
1075
  }
249
1076
  }
@@ -260,7 +1087,7 @@ function isComponentApproved(plugin, component) {
260
1087
  return plugin.components[component] ?? false;
261
1088
  }
262
1089
  function syncClaude(plugins) {
263
- const targetBase = path3.join(homeDir(), ".claude", "skills");
1090
+ const targetBase = path6.join(homeDir(), ".claude", "skills");
264
1091
  const projectedDirs = [];
265
1092
  const unsupported = [];
266
1093
  const skippedUnapproved = [];
@@ -271,13 +1098,13 @@ function syncClaude(plugins) {
271
1098
  skippedUnapproved.push(`${plugin.pluginName}:skills`);
272
1099
  continue;
273
1100
  }
274
- const targetDir = path3.join(targetBase, plugin.pluginName);
1101
+ const targetDir = path6.join(targetBase, plugin.pluginName);
275
1102
  ensureDir(targetDir);
276
- const mcpJsonSrc = path3.join(plugin.pluginRoot, ".mcp.json");
277
- const mcpJsonDest = path3.join(targetDir, ".mcp.json");
1103
+ const mcpJsonSrc = path6.join(plugin.pluginRoot, ".mcp.json");
1104
+ const mcpJsonDest = path6.join(targetDir, ".mcp.json");
278
1105
  copyPluginDir(plugin.pluginRoot, targetDir, [".mcp.json", ".git"]);
279
- if (fs3.existsSync(mcpJsonDest)) {
280
- fs3.rmSync(mcpJsonDest, { recursive: true, force: true });
1106
+ if (fs6.existsSync(mcpJsonDest)) {
1107
+ fs6.rmSync(mcpJsonDest, { recursive: true, force: true });
281
1108
  }
282
1109
  projectedDirs.push(targetDir);
283
1110
  recordOwned(targetBase, plugin.pluginId, [plugin.pluginName]);
@@ -299,7 +1126,7 @@ function syncClaudeDesktop(plugins) {
299
1126
  };
300
1127
  }
301
1128
  function syncCodex(plugins) {
302
- const targetBase = path3.join(homeDir(), ".codex", "skills");
1129
+ const targetBase = path6.join(homeDir(), ".codex", "skills");
303
1130
  const projectedDirs = [];
304
1131
  const unsupported = ["hooks", "agents"];
305
1132
  const errors = [];
@@ -311,10 +1138,10 @@ function syncCodex(plugins) {
311
1138
  const owned = [];
312
1139
  for (const skill of plugin.skills) {
313
1140
  try {
314
- const targetDir = path3.join(targetBase, nsName(plugin.pluginName, skill.id));
1141
+ const targetDir = path6.join(targetBase, nsName(plugin.pluginName, skill.id));
315
1142
  assertWithinBase(targetBase, targetDir);
316
1143
  ensureDir(targetDir);
317
- copyFileOrDir(skill.path, path3.join(targetDir, "SKILL.md"));
1144
+ copyFileOrDir(skill.path, path6.join(targetDir, "SKILL.md"));
318
1145
  owned.push(nsName(plugin.pluginName, skill.id));
319
1146
  projectedDirs.push(targetDir);
320
1147
  } catch (error) {
@@ -333,8 +1160,8 @@ function syncCodex(plugins) {
333
1160
  };
334
1161
  }
335
1162
  function syncCursor(plugins) {
336
- const skillsBase = path3.join(homeDir(), ".cursor", "skills");
337
- const commandsBase = path3.join(homeDir(), ".cursor", "commands");
1163
+ const skillsBase = path6.join(homeDir(), ".cursor", "skills");
1164
+ const commandsBase = path6.join(homeDir(), ".cursor", "commands");
338
1165
  const projectedDirs = [];
339
1166
  const unsupported = ["hooks", "agents"];
340
1167
  const errors = [];
@@ -347,10 +1174,10 @@ function syncCursor(plugins) {
347
1174
  ensureDir(skillsBase);
348
1175
  for (const skill of plugin.skills) {
349
1176
  try {
350
- const targetDir = path3.join(skillsBase, nsName(plugin.pluginName, skill.id));
1177
+ const targetDir = path6.join(skillsBase, nsName(plugin.pluginName, skill.id));
351
1178
  assertWithinBase(skillsBase, targetDir);
352
1179
  ensureDir(targetDir);
353
- copyFileOrDir(skill.path, path3.join(targetDir, "SKILL.md"));
1180
+ copyFileOrDir(skill.path, path6.join(targetDir, "SKILL.md"));
354
1181
  ownedSkills.push(nsName(plugin.pluginName, skill.id));
355
1182
  projectedDirs.push(targetDir);
356
1183
  } catch (error) {
@@ -362,7 +1189,7 @@ function syncCursor(plugins) {
362
1189
  ensureDir(commandsBase);
363
1190
  for (const cmd of plugin.commands) {
364
1191
  try {
365
- const targetPath = path3.join(commandsBase, `${nsName(plugin.pluginName, cmd.id)}.md`);
1192
+ const targetPath = path6.join(commandsBase, `${nsName(plugin.pluginName, cmd.id)}.md`);
366
1193
  assertWithinBase(commandsBase, targetPath);
367
1194
  copyFileOrDir(cmd.path, targetPath);
368
1195
  ownedCommands.push(`${nsName(plugin.pluginName, cmd.id)}.md`);
@@ -386,7 +1213,7 @@ function syncCursor(plugins) {
386
1213
  };
387
1214
  }
388
1215
  function syncVsCode(plugins) {
389
- const skillsBase = path3.join(homeDir(), ".copilot", "skills");
1216
+ const skillsBase = path6.join(homeDir(), ".copilot", "skills");
390
1217
  const projectedDirs = [];
391
1218
  const unsupported = ["hooks", "agents"];
392
1219
  const errors = [];
@@ -398,10 +1225,10 @@ function syncVsCode(plugins) {
398
1225
  const owned = [];
399
1226
  for (const skill of plugin.skills) {
400
1227
  try {
401
- const targetDir = path3.join(skillsBase, nsName(plugin.pluginName, skill.id));
1228
+ const targetDir = path6.join(skillsBase, nsName(plugin.pluginName, skill.id));
402
1229
  assertWithinBase(skillsBase, targetDir);
403
1230
  ensureDir(targetDir);
404
- copyFileOrDir(skill.path, path3.join(targetDir, "SKILL.md"));
1231
+ copyFileOrDir(skill.path, path6.join(targetDir, "SKILL.md"));
405
1232
  owned.push(nsName(plugin.pluginName, skill.id));
406
1233
  projectedDirs.push(targetDir);
407
1234
  } catch (error) {
@@ -420,7 +1247,7 @@ function syncVsCode(plugins) {
420
1247
  };
421
1248
  }
422
1249
  function syncQwen(plugins) {
423
- const targetBase = path3.join(homeDir(), ".qwen", "skills");
1250
+ const targetBase = path6.join(homeDir(), ".qwen", "skills");
424
1251
  const projectedDirs = [];
425
1252
  const unsupported = ["hooks", "agents", "commands"];
426
1253
  const errors = [];
@@ -432,10 +1259,10 @@ function syncQwen(plugins) {
432
1259
  const owned = [];
433
1260
  for (const skill of plugin.skills) {
434
1261
  try {
435
- const targetDir = path3.join(targetBase, nsName(plugin.pluginName, skill.id));
1262
+ const targetDir = path6.join(targetBase, nsName(plugin.pluginName, skill.id));
436
1263
  assertWithinBase(targetBase, targetDir);
437
1264
  ensureDir(targetDir);
438
- copyFileOrDir(skill.path, path3.join(targetDir, "SKILL.md"));
1265
+ copyFileOrDir(skill.path, path6.join(targetDir, "SKILL.md"));
439
1266
  owned.push(nsName(plugin.pluginName, skill.id));
440
1267
  projectedDirs.push(targetDir);
441
1268
  } catch (error) {
@@ -454,7 +1281,7 @@ function syncQwen(plugins) {
454
1281
  };
455
1282
  }
456
1283
  function syncCline(plugins) {
457
- const targetBase = path3.join(homeDir(), ".config", "cline", "skills");
1284
+ const targetBase = path6.join(homeDir(), ".config", "cline", "skills");
458
1285
  const projectedDirs = [];
459
1286
  const unsupported = ["hooks", "agents", "commands"];
460
1287
  const errors = [];
@@ -466,10 +1293,10 @@ function syncCline(plugins) {
466
1293
  const owned = [];
467
1294
  for (const skill of plugin.skills) {
468
1295
  try {
469
- const targetDir = path3.join(targetBase, nsName(plugin.pluginName, skill.id));
1296
+ const targetDir = path6.join(targetBase, nsName(plugin.pluginName, skill.id));
470
1297
  assertWithinBase(targetBase, targetDir);
471
1298
  ensureDir(targetDir);
472
- copyFileOrDir(skill.path, path3.join(targetDir, "SKILL.md"));
1299
+ copyFileOrDir(skill.path, path6.join(targetDir, "SKILL.md"));
473
1300
  owned.push(nsName(plugin.pluginName, skill.id));
474
1301
  projectedDirs.push(targetDir);
475
1302
  } catch (error) {
@@ -488,7 +1315,7 @@ function syncCline(plugins) {
488
1315
  };
489
1316
  }
490
1317
  function syncKiro(plugins) {
491
- const targetBase = path3.join(homeDir(), ".kiro", "skills");
1318
+ const targetBase = path6.join(homeDir(), ".kiro", "skills");
492
1319
  const projectedDirs = [];
493
1320
  const unsupported = ["hooks", "agents", "commands"];
494
1321
  const errors = [];
@@ -500,10 +1327,10 @@ function syncKiro(plugins) {
500
1327
  const owned = [];
501
1328
  for (const skill of plugin.skills) {
502
1329
  try {
503
- const targetDir = path3.join(targetBase, nsName(plugin.pluginName, skill.id));
1330
+ const targetDir = path6.join(targetBase, nsName(plugin.pluginName, skill.id));
504
1331
  assertWithinBase(targetBase, targetDir);
505
1332
  ensureDir(targetDir);
506
- copyFileOrDir(skill.path, path3.join(targetDir, "SKILL.md"));
1333
+ copyFileOrDir(skill.path, path6.join(targetDir, "SKILL.md"));
507
1334
  owned.push(nsName(plugin.pluginName, skill.id));
508
1335
  projectedDirs.push(targetDir);
509
1336
  } catch (error) {
@@ -547,14 +1374,14 @@ function syncPluginsToClient(clientId, plugins) {
547
1374
  return { clientId, status: "SKIPPED", projectedDirs: [], unsupported: [] };
548
1375
  }
549
1376
  const targetBaseMap = {
550
- claude: path3.join(homeDir(), ".claude", "skills"),
1377
+ claude: path6.join(homeDir(), ".claude", "skills"),
551
1378
  "claude-desktop": "",
552
- codex: path3.join(homeDir(), ".codex", "skills"),
553
- cursor: path3.join(homeDir(), ".cursor"),
554
- vscode: path3.join(homeDir(), ".copilot", "skills"),
555
- qwen: path3.join(homeDir(), ".qwen", "skills"),
556
- cline: path3.join(homeDir(), ".config", "cline", "skills"),
557
- kiro: path3.join(homeDir(), ".kiro", "skills"),
1379
+ codex: path6.join(homeDir(), ".codex", "skills"),
1380
+ cursor: path6.join(homeDir(), ".cursor"),
1381
+ vscode: path6.join(homeDir(), ".copilot", "skills"),
1382
+ qwen: path6.join(homeDir(), ".qwen", "skills"),
1383
+ cline: path6.join(homeDir(), ".config", "cline", "skills"),
1384
+ kiro: path6.join(homeDir(), ".kiro", "skills"),
558
1385
  opencode: ""
559
1386
  };
560
1387
  const target = targetBaseMap[clientId];
@@ -567,17 +1394,17 @@ function syncPluginsToClient(clientId, plugins) {
567
1394
  function copyPluginDir(src, dest, exclude = [], errors) {
568
1395
  const excludeSet = new Set(exclude);
569
1396
  ensureDir(dest);
570
- const entries = fs3.readdirSync(src, { withFileTypes: true });
1397
+ const entries = fs6.readdirSync(src, { withFileTypes: true });
571
1398
  for (const entry of entries) {
572
1399
  if (excludeSet.has(entry.name))
573
1400
  continue;
574
- const srcPath = path3.join(src, entry.name);
575
- const destPath = path3.join(dest, entry.name);
1401
+ const srcPath = path6.join(src, entry.name);
1402
+ const destPath = path6.join(dest, entry.name);
576
1403
  if (entry.isDirectory()) {
577
1404
  copyPluginDir(srcPath, destPath, exclude, errors);
578
1405
  } else if (entry.isFile()) {
579
1406
  try {
580
- fs3.copyFileSync(srcPath, destPath);
1407
+ fs6.copyFileSync(srcPath, destPath);
581
1408
  } catch (e) {
582
1409
  errors?.push(`Failed to copy ${srcPath}: ${e.message}`);
583
1410
  }
@@ -586,242 +1413,75 @@ function copyPluginDir(src, dest, exclude = [], errors) {
586
1413
  }
587
1414
  function copyFileOrDir(src, dest, errors) {
588
1415
  try {
589
- ensureDir(path3.dirname(dest));
590
- if (fs3.statSync(src).isDirectory()) {
1416
+ ensureDir(path6.dirname(dest));
1417
+ if (fs6.statSync(src).isDirectory()) {
591
1418
  copyPluginDir(src, dest, [], errors);
592
1419
  } else {
593
- fs3.copyFileSync(src, dest);
1420
+ fs6.copyFileSync(src, dest);
594
1421
  }
595
1422
  } catch (e) {
596
1423
  errors?.push(`Failed to copy ${src} -> ${dest}: ${e.message}`);
597
1424
  }
598
1425
  }
599
1426
 
600
- // src/version.ts
601
- var APP_VERSION = "0.1.3";
602
-
603
- // src/adapters/claude.ts
604
- import fs5 from "node:fs";
605
- import path5 from "node:path";
606
- import { z } from "zod";
607
-
608
1427
  // src/core/skill-projections.ts
609
- import fs4 from "node:fs";
610
- import path4 from "node:path";
1428
+ import fs7 from "node:fs";
1429
+ import path7 from "node:path";
611
1430
  var OWNERSHIP_MANIFEST2 = "mcpx-skills.json";
612
1431
  function loadOwnership2(targetDir) {
613
- const manifestPath = path4.join(targetDir, OWNERSHIP_MANIFEST2);
614
- if (!fs4.existsSync(manifestPath)) {
615
- return { version: 1, paths: [] };
616
- }
617
- try {
618
- return JSON.parse(fs4.readFileSync(manifestPath, "utf8"));
619
- } catch {
620
- console.error(`[mcpx] Warning: corrupt ownership manifest at ${manifestPath}, resetting`);
621
- return { version: 1, paths: [] };
622
- }
623
- }
624
- function saveOwnership2(targetDir, manifest) {
625
- ensureDir(targetDir);
626
- fs4.writeFileSync(path4.join(targetDir, OWNERSHIP_MANIFEST2), JSON.stringify(manifest, null, 2));
627
- }
628
- function projectSkillsToDir(targetDir, skills, layout) {
629
- const owned = loadOwnership2(targetDir);
630
- const newPaths = new Set(skills.map((s) => layout === "dir" ? `${s.id}/SKILL.md` : `${s.id}.md`));
631
- for (const ownedPath of owned.paths) {
632
- if (!newPaths.has(ownedPath)) {
633
- const fullPath = path4.join(targetDir, ownedPath);
634
- try {
635
- if (fs4.existsSync(fullPath)) {
636
- fs4.rmSync(fullPath, { recursive: true, force: true });
637
- }
638
- if (layout === "dir") {
639
- const dirPath = path4.join(targetDir, path4.dirname(ownedPath));
640
- try {
641
- const remaining = fs4.readdirSync(dirPath);
642
- if (remaining.length === 0) {
643
- fs4.rmSync(dirPath, { recursive: true, force: true });
644
- }
645
- } catch {}
646
- }
647
- } catch {
648
- console.error(`[mcpx] Warning: could not remove stale skill projection: ${fullPath}`);
649
- }
650
- }
651
- }
652
- for (const skill of skills) {
653
- const filePath = layout === "dir" ? path4.join(targetDir, skill.id, "SKILL.md") : path4.join(targetDir, `${skill.id}.md`);
654
- ensureDir(path4.dirname(filePath));
655
- fs4.writeFileSync(filePath, skill.content, "utf-8");
656
- }
657
- saveOwnership2(targetDir, { version: 1, paths: Array.from(newPaths) });
658
- }
659
-
660
- // src/adapters/utils/index.ts
661
- function getManagedEntryNames(managedIndex, clientId) {
662
- return Object.keys(managedIndex.managed[clientId]?.entries ?? {});
663
- }
664
- function isManagedEntry(managedIndex, clientId, entryName) {
665
- return Boolean(managedIndex.managed[clientId]?.entries?.[entryName]);
666
- }
667
- function pruneStaleManagedEntries(managedIndex, clientId, servers, keepEntryNames) {
668
- const keep = new Set(keepEntryNames);
669
- const managedNames = getManagedEntryNames(managedIndex, clientId);
670
- for (const name of managedNames) {
671
- if (!keep.has(name)) {
672
- delete servers[name];
673
- }
674
- }
675
- for (const name of Object.keys(servers)) {
676
- if (!keep.has(name) && name.endsWith(" (mcpx)")) {
677
- delete servers[name];
678
- }
679
- }
680
- }
681
- function ensureManagedEntryWritable(managedIndex, clientId, entryName, existingValue, expectedEntry) {
682
- if (existingValue === undefined || existingValue === null) {
683
- return null;
684
- }
685
- if (isManagedEntry(managedIndex, clientId, entryName)) {
686
- return null;
687
- }
688
- if (expectedEntry && isManagedGatewayProjection(entryName) && matchesExpectedShape(existingValue, expectedEntry)) {
689
- if (!managedIndex.managed[clientId]) {
690
- managedIndex.managed[clientId] = { configPath: "", entries: {} };
691
- }
692
- managedIndex.managed[clientId].entries[entryName] = {
693
- fingerprint: sha256(JSON.stringify(existingValue)),
694
- lastSyncedAt: new Date().toISOString()
695
- };
696
- return null;
697
- }
698
- return `Cannot sync managed entry "${entryName}" because an unmanaged entry already exists.`;
699
- }
700
- function matchesExpectedShape(value, expected) {
701
- if (!value || typeof value !== "object")
702
- return false;
703
- const v = value;
704
- if (expected.url && v.url !== expected.url)
705
- return false;
706
- if (expected.command && v.command !== expected.command)
707
- return false;
708
- return true;
709
- }
710
- function setManagedEntries(managedIndex, clientId, configPath, entries) {
711
- if (!managedIndex.managed[clientId]) {
712
- managedIndex.managed[clientId] = {
713
- configPath,
714
- entries: {}
715
- };
716
- }
717
- managedIndex.managed[clientId].configPath = configPath;
718
- managedIndex.managed[clientId].entries = Object.fromEntries(Object.entries(entries).map(([entryName, serializedEntry]) => [entryName, {
719
- fingerprint: sha256(serializedEntry),
720
- lastSyncedAt: new Date().toISOString()
721
- }]));
722
- }
723
- function okResult(clientId, configPath, message) {
724
- return {
725
- clientId,
726
- status: "SYNCED",
727
- configPath,
728
- message
729
- };
730
- }
731
- function errorResult(clientId, configPath, message) {
732
- return {
733
- clientId,
734
- status: "ERROR",
735
- configPath,
736
- message
737
- };
738
- }
739
- function managedGatewayEntryName(serverName) {
740
- return `${serverName} (mcpx)`;
741
- }
742
- function isManagedGatewayProjection(entryName) {
743
- return entryName.endsWith(" (mcpx)");
744
- }
745
- function sortRecord(record) {
746
- if (!record || Object.keys(record).length === 0) {
747
- return;
748
- }
749
- return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
750
- }
751
- function normalizeServerSpec(spec) {
752
- if (spec.transport === "http") {
753
- return {
754
- transport: "http",
755
- url: spec.url,
756
- headers: sortRecord(spec.headers)
757
- };
758
- }
759
- return {
760
- transport: "stdio",
761
- command: spec.command,
762
- args: spec.args && spec.args.length > 0 ? [...spec.args] : undefined,
763
- env: sortRecord(spec.env),
764
- cwd: spec.cwd
765
- };
766
- }
767
- function serializeServerSpec(spec) {
768
- return JSON.stringify(normalizeServerSpec(spec));
769
- }
770
- function serverSpecsEqual(left, right) {
771
- return serializeServerSpec(left) === serializeServerSpec(right);
772
- }
773
- function emptyImportScan(clientId, configPath) {
774
- return {
775
- clientId,
776
- configPath,
777
- candidates: [],
778
- skipped: []
779
- };
780
- }
781
- function buildImportSkip(clientId, sourceEntryName, reason, configPath, serverName = sourceEntryName) {
782
- return {
783
- clientId,
784
- configPath,
785
- sourceEntryName,
786
- serverName,
787
- reason
788
- };
789
- }
790
- function removeSourceEntries(entries, names) {
791
- for (const name of names ?? []) {
792
- delete entries[name];
1432
+ const manifestPath2 = path7.join(targetDir, OWNERSHIP_MANIFEST2);
1433
+ if (!fs7.existsSync(manifestPath2)) {
1434
+ return { version: 1, paths: [] };
793
1435
  }
794
- }
795
- function purgeManagedFromDisabledArray(raw, managedNames) {
796
- const existingDisabled = raw.disabledMcpServers ?? [];
797
- raw.disabledMcpServers = existingDisabled.filter((name) => !managedNames.includes(name));
798
- if (Array.isArray(raw.disabledMcpServers) && raw.disabledMcpServers.length === 0) {
799
- delete raw.disabledMcpServers;
1436
+ try {
1437
+ return JSON.parse(fs7.readFileSync(manifestPath2, "utf8"));
1438
+ } catch {
1439
+ console.error(`[mcpx] Warning: corrupt ownership manifest at ${manifestPath2}, resetting`);
1440
+ return { version: 1, paths: [] };
800
1441
  }
801
1442
  }
802
- function purgeManagedFromExcludedArray(raw, managedNames) {
803
- const mcp = raw.mcp ?? {};
804
- const existingExcluded = mcp.excluded ?? [];
805
- mcp.excluded = existingExcluded.filter((name) => !managedNames.includes(name));
806
- if (Array.isArray(mcp.excluded) && mcp.excluded.length === 0) {
807
- delete mcp.excluded;
808
- }
809
- raw.mcp = Object.keys(mcp).length > 0 ? mcp : undefined;
1443
+ function saveOwnership2(targetDir, manifest) {
1444
+ ensureDir(targetDir);
1445
+ fs7.writeFileSync(path7.join(targetDir, OWNERSHIP_MANIFEST2), JSON.stringify(manifest, null, 2));
810
1446
  }
811
- function detectManagedEntryDrift(managedIndex, clientId, entryName, existingValue) {
812
- const recorded = managedIndex.managed[clientId]?.entries?.[entryName];
813
- if (!recorded || existingValue === undefined)
814
- return false;
815
- const liveFingerprint = sha256(JSON.stringify(existingValue));
816
- return liveFingerprint !== recorded.fingerprint;
1447
+ function projectSkillsToDir(targetDir, skills, layout) {
1448
+ const owned = loadOwnership2(targetDir);
1449
+ const newPaths = new Set(skills.map((s) => layout === "dir" ? `${s.id}/SKILL.md` : `${s.id}.md`));
1450
+ for (const ownedPath of owned.paths) {
1451
+ if (!newPaths.has(ownedPath)) {
1452
+ const fullPath = path7.join(targetDir, ownedPath);
1453
+ try {
1454
+ if (fs7.existsSync(fullPath)) {
1455
+ fs7.rmSync(fullPath, { recursive: true, force: true });
1456
+ }
1457
+ if (layout === "dir") {
1458
+ const dirPath = path7.join(targetDir, path7.dirname(ownedPath));
1459
+ try {
1460
+ const remaining = fs7.readdirSync(dirPath);
1461
+ if (remaining.length === 0) {
1462
+ fs7.rmSync(dirPath, { recursive: true, force: true });
1463
+ }
1464
+ } catch {}
1465
+ }
1466
+ } catch {
1467
+ console.error(`[mcpx] Warning: could not remove stale skill projection: ${fullPath}`);
1468
+ }
1469
+ }
1470
+ }
1471
+ for (const skill of skills) {
1472
+ const filePath = layout === "dir" ? path7.join(targetDir, skill.id, "SKILL.md") : path7.join(targetDir, `${skill.id}.md`);
1473
+ ensureDir(path7.dirname(filePath));
1474
+ fs7.writeFileSync(filePath, skill.content, "utf-8");
1475
+ }
1476
+ saveOwnership2(targetDir, { version: 1, paths: Array.from(newPaths) });
817
1477
  }
818
1478
 
819
1479
  // src/adapters/claude.ts
820
1480
  function normalizeProjectKey(p) {
821
1481
  try {
822
- return fs5.realpathSync(path5.resolve(p));
1482
+ return fs8.realpathSync(path8.resolve(p));
823
1483
  } catch {
824
- return path5.resolve(p);
1484
+ return path8.resolve(p);
825
1485
  }
826
1486
  }
827
1487
  function syncProjectDisabledArray(project, managedNames, disabledForThisProject) {
@@ -833,22 +1493,22 @@ function syncProjectDisabledArray(project, managedNames, disabledForThisProject)
833
1493
  delete project.disabledMcpServers;
834
1494
  }
835
1495
  }
836
- var stringMapSchema = z.record(z.string(), z.string());
837
- var claudeEntrySchema = z.object({
838
- type: z.string().optional(),
839
- url: z.string().min(1).optional(),
1496
+ var stringMapSchema = z2.record(z2.string(), z2.string());
1497
+ var claudeEntrySchema = z2.object({
1498
+ type: z2.string().optional(),
1499
+ url: z2.string().min(1).optional(),
840
1500
  headers: stringMapSchema.optional(),
841
- command: z.string().min(1).optional(),
842
- args: z.array(z.string()).optional(),
1501
+ command: z2.string().min(1).optional(),
1502
+ args: z2.array(z2.string()).optional(),
843
1503
  env: stringMapSchema.optional(),
844
- cwd: z.string().min(1).optional(),
845
- disabled: z.boolean().optional()
1504
+ cwd: z2.string().min(1).optional(),
1505
+ disabled: z2.boolean().optional()
846
1506
  }).passthrough();
847
1507
 
848
1508
  class ClaudeAdapter {
849
1509
  id = "claude";
850
1510
  detectConfigPath() {
851
- return path5.join(homeDir(), ".claude.json");
1511
+ return path8.join(homeDir(), ".claude.json");
852
1512
  }
853
1513
  supportsHttp() {
854
1514
  return true;
@@ -857,7 +1517,7 @@ class ClaudeAdapter {
857
1517
  return syncPluginsToClient(this.id, plugins);
858
1518
  }
859
1519
  syncSkills(skills) {
860
- projectSkillsToDir(path5.join(homeDir(), ".claude", "skills"), skills, "dir");
1520
+ projectSkillsToDir(path8.join(homeDir(), ".claude", "skills"), skills, "dir");
861
1521
  }
862
1522
  scanForImports(_config, managedIndex) {
863
1523
  const configPath = this.detectConfigPath();
@@ -983,24 +1643,29 @@ class ClaudeAdapter {
983
1643
  }
984
1644
 
985
1645
  // src/adapters/claude-desktop.ts
986
- import { execFileSync } from "node:child_process";
987
- import path6 from "node:path";
988
- import { z as z2 } from "zod";
989
- var stringMapSchema2 = z2.record(z2.string(), z2.string());
990
- var claudeDesktopEntrySchema = z2.object({
991
- type: z2.string().optional(),
992
- url: z2.string().min(1).optional(),
1646
+ import { execFileSync as execFileSync3 } from "node:child_process";
1647
+ import path9 from "node:path";
1648
+ import { z as z3 } from "zod";
1649
+
1650
+ // src/version.ts
1651
+ var APP_VERSION = "0.1.3";
1652
+
1653
+ // src/adapters/claude-desktop.ts
1654
+ var stringMapSchema2 = z3.record(z3.string(), z3.string());
1655
+ var claudeDesktopEntrySchema = z3.object({
1656
+ type: z3.string().optional(),
1657
+ url: z3.string().min(1).optional(),
993
1658
  headers: stringMapSchema2.optional(),
994
- command: z2.string().min(1).optional(),
995
- args: z2.array(z2.string()).optional(),
1659
+ command: z3.string().min(1).optional(),
1660
+ args: z3.array(z3.string()).optional(),
996
1661
  env: stringMapSchema2.optional(),
997
- cwd: z2.string().min(1).optional(),
998
- disabled: z2.boolean().optional()
1662
+ cwd: z3.string().min(1).optional(),
1663
+ disabled: z3.boolean().optional()
999
1664
  }).passthrough();
1000
1665
  function buildProxyEntry(entry) {
1001
1666
  const upstreamName = entry.name.replace(/ \(mcpx\)$/, "");
1002
1667
  try {
1003
- const mcpxPath = execFileSync("which", ["mcpx"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
1668
+ const mcpxPath = execFileSync3("which", ["mcpx"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
1004
1669
  if (mcpxPath)
1005
1670
  return { command: mcpxPath, args: ["proxy", upstreamName] };
1006
1671
  } catch {}
@@ -1011,12 +1676,12 @@ class ClaudeDesktopAdapter {
1011
1676
  id = "claude-desktop";
1012
1677
  detectConfigPath() {
1013
1678
  if (process.platform === "linux") {
1014
- return path6.join(homeDir(), ".config", "Claude", "claude_desktop_config.json");
1679
+ return path9.join(homeDir(), ".config", "Claude", "claude_desktop_config.json");
1015
1680
  }
1016
1681
  if (process.platform === "win32") {
1017
- return path6.join(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json");
1682
+ return path9.join(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json");
1018
1683
  }
1019
- return path6.join(homeDir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
1684
+ return path9.join(homeDir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
1020
1685
  }
1021
1686
  supportsHttp() {
1022
1687
  return true;
@@ -1029,7 +1694,7 @@ class ClaudeDesktopAdapter {
1029
1694
  if (!configPath) {
1030
1695
  return;
1031
1696
  }
1032
- projectSkillsToDir(path6.join(path6.dirname(configPath), "skills"), skills, "dir");
1697
+ projectSkillsToDir(path9.join(path9.dirname(configPath), "skills"), skills, "dir");
1033
1698
  }
1034
1699
  scanForImports(_config, managedIndex) {
1035
1700
  const configPath = this.detectConfigPath();
@@ -1125,31 +1790,31 @@ class ClaudeDesktopAdapter {
1125
1790
  }
1126
1791
 
1127
1792
  // src/adapters/codex.ts
1128
- import path7 from "node:path";
1129
- import fs6 from "node:fs";
1793
+ import path10 from "node:path";
1794
+ import fs9 from "node:fs";
1130
1795
  import { parse, stringify } from "@iarna/toml";
1131
- import { z as z3 } from "zod";
1132
- var stringMapSchema3 = z3.preprocess((value) => {
1796
+ import { z as z4 } from "zod";
1797
+ var stringMapSchema3 = z4.preprocess((value) => {
1133
1798
  if (!value || typeof value !== "object" || Array.isArray(value)) {
1134
1799
  return value;
1135
1800
  }
1136
1801
  return Object.fromEntries(Object.entries(value));
1137
- }, z3.record(z3.string(), z3.string()));
1138
- var codexEntrySchema = z3.object({
1139
- enabled: z3.boolean().optional(),
1140
- url: z3.string().min(1).optional(),
1802
+ }, z4.record(z4.string(), z4.string()));
1803
+ var codexEntrySchema = z4.object({
1804
+ enabled: z4.boolean().optional(),
1805
+ url: z4.string().min(1).optional(),
1141
1806
  headers: stringMapSchema3.optional(),
1142
1807
  http_headers: stringMapSchema3.optional(),
1143
- command: z3.string().min(1).optional(),
1144
- args: z3.array(z3.string()).optional(),
1808
+ command: z4.string().min(1).optional(),
1809
+ args: z4.array(z4.string()).optional(),
1145
1810
  env: stringMapSchema3.optional(),
1146
- cwd: z3.string().min(1).optional()
1811
+ cwd: z4.string().min(1).optional()
1147
1812
  }).passthrough();
1148
1813
 
1149
1814
  class CodexAdapter {
1150
1815
  id = "codex";
1151
1816
  detectConfigPath() {
1152
- return path7.join(homeDir(), ".codex", "config.toml");
1817
+ return path10.join(homeDir(), ".codex", "config.toml");
1153
1818
  }
1154
1819
  supportsHttp() {
1155
1820
  return true;
@@ -1158,15 +1823,15 @@ class CodexAdapter {
1158
1823
  return syncPluginsToClient(this.id, plugins);
1159
1824
  }
1160
1825
  syncSkills(skills) {
1161
- projectSkillsToDir(path7.join(homeDir(), ".codex", "skills"), skills, "dir");
1826
+ projectSkillsToDir(path10.join(homeDir(), ".codex", "skills"), skills, "dir");
1162
1827
  }
1163
1828
  scanForImports(_config, managedIndex) {
1164
1829
  const configPath = this.detectConfigPath();
1165
1830
  const result = emptyImportScan(this.id, configPath ?? undefined);
1166
- if (!configPath || !fs6.existsSync(configPath)) {
1831
+ if (!configPath || !fs9.existsSync(configPath)) {
1167
1832
  return result;
1168
1833
  }
1169
- const existing = fs6.readFileSync(configPath, "utf8");
1834
+ const existing = fs9.readFileSync(configPath, "utf8");
1170
1835
  const doc = existing.trim().length > 0 ? parse(existing) : {};
1171
1836
  const mcpServers = doc.mcp_servers ?? {};
1172
1837
  for (const [name, rawEntry] of Object.entries(mcpServers)) {
@@ -1221,7 +1886,7 @@ class CodexAdapter {
1221
1886
  return errorResult(this.id, undefined, "Unable to resolve Codex config.toml path.");
1222
1887
  }
1223
1888
  try {
1224
- const existing = fs6.existsSync(configPath) ? fs6.readFileSync(configPath, "utf8") : "";
1889
+ const existing = fs9.existsSync(configPath) ? fs9.readFileSync(configPath, "utf8") : "";
1225
1890
  const doc = existing.trim().length > 0 ? parse(existing) : {};
1226
1891
  const mcpServers = doc.mcp_servers ?? {};
1227
1892
  removeSourceEntries(mcpServers, options.sourceEntriesToRemove);
@@ -1244,10 +1909,10 @@ class CodexAdapter {
1244
1909
  mcpServers[name] = entry;
1245
1910
  }
1246
1911
  doc.mcp_servers = mcpServers;
1247
- fs6.mkdirSync(path7.dirname(configPath), { recursive: true });
1912
+ fs9.mkdirSync(path10.dirname(configPath), { recursive: true });
1248
1913
  const tmpPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
1249
- fs6.writeFileSync(tmpPath, stringify(doc), { mode: 384 });
1250
- fs6.renameSync(tmpPath, configPath);
1914
+ fs9.writeFileSync(tmpPath, stringify(doc), { mode: 384 });
1915
+ fs9.renameSync(tmpPath, configPath);
1251
1916
  setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
1252
1917
  return { ...okResult(this.id, configPath), driftedEntries: driftedNames.length > 0 ? driftedNames : undefined };
1253
1918
  } catch (error) {
@@ -1257,24 +1922,24 @@ class CodexAdapter {
1257
1922
  }
1258
1923
 
1259
1924
  // src/adapters/cursor.ts
1260
- import path8 from "node:path";
1261
- import { z as z4 } from "zod";
1262
- var stringMapSchema4 = z4.record(z4.string(), z4.string());
1263
- var cursorEntrySchema = z4.object({
1264
- type: z4.string().optional(),
1265
- url: z4.string().min(1).optional(),
1925
+ import path11 from "node:path";
1926
+ import { z as z5 } from "zod";
1927
+ var stringMapSchema4 = z5.record(z5.string(), z5.string());
1928
+ var cursorEntrySchema = z5.object({
1929
+ type: z5.string().optional(),
1930
+ url: z5.string().min(1).optional(),
1266
1931
  headers: stringMapSchema4.optional(),
1267
- command: z4.string().min(1).optional(),
1268
- args: z4.array(z4.string()).optional(),
1932
+ command: z5.string().min(1).optional(),
1933
+ args: z5.array(z5.string()).optional(),
1269
1934
  env: stringMapSchema4.optional(),
1270
- cwd: z4.string().min(1).optional(),
1271
- disabled: z4.boolean().optional()
1935
+ cwd: z5.string().min(1).optional(),
1936
+ disabled: z5.boolean().optional()
1272
1937
  }).passthrough();
1273
1938
 
1274
1939
  class CursorAdapter {
1275
1940
  id = "cursor";
1276
1941
  detectConfigPath() {
1277
- return path8.join(homeDir(), ".cursor", "mcp.json");
1942
+ return path11.join(homeDir(), ".cursor", "mcp.json");
1278
1943
  }
1279
1944
  supportsHttp() {
1280
1945
  return true;
@@ -1283,7 +1948,7 @@ class CursorAdapter {
1283
1948
  return syncPluginsToClient(this.id, plugins);
1284
1949
  }
1285
1950
  syncSkills(skills) {
1286
- projectSkillsToDir(path8.join(homeDir(), ".cursor", "skills"), skills, "dir");
1951
+ projectSkillsToDir(path11.join(homeDir(), ".cursor", "skills"), skills, "dir");
1287
1952
  }
1288
1953
  scanForImports(_config, managedIndex) {
1289
1954
  const configPath = this.detectConfigPath();
@@ -1387,41 +2052,41 @@ class CursorAdapter {
1387
2052
  }
1388
2053
 
1389
2054
  // src/adapters/cline.ts
1390
- import path9 from "node:path";
1391
- import fs7 from "node:fs";
1392
- import { z as z5 } from "zod";
2055
+ import path12 from "node:path";
2056
+ import fs10 from "node:fs";
2057
+ import { z as z6 } from "zod";
1393
2058
  var HTTP_TRANSPORT_TYPES = new Set(["http", "sse", "streamableHttp"]);
1394
- var stringMapSchema5 = z5.record(z5.string(), z5.string());
1395
- var clineEntrySchema = z5.object({
1396
- type: z5.string().optional(),
1397
- transportType: z5.string().optional(),
1398
- url: z5.string().min(1).optional(),
2059
+ var stringMapSchema5 = z6.record(z6.string(), z6.string());
2060
+ var clineEntrySchema = z6.object({
2061
+ type: z6.string().optional(),
2062
+ transportType: z6.string().optional(),
2063
+ url: z6.string().min(1).optional(),
1399
2064
  headers: stringMapSchema5.optional(),
1400
- command: z5.string().min(1).optional(),
1401
- args: z5.array(z5.string()).optional(),
2065
+ command: z6.string().min(1).optional(),
2066
+ args: z6.array(z6.string()).optional(),
1402
2067
  env: stringMapSchema5.optional(),
1403
- cwd: z5.string().min(1).optional(),
1404
- disabled: z5.boolean().optional()
2068
+ cwd: z6.string().min(1).optional(),
2069
+ disabled: z6.boolean().optional()
1405
2070
  }).passthrough();
1406
2071
  function getCandidatePaths() {
1407
2072
  if (process.platform === "linux") {
1408
2073
  return [
1409
- path9.join(homeDir(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
1410
- path9.join(homeDir(), ".config", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
1411
- path9.join(homeDir(), ".cline", "settings", "cline_mcp_settings.json")
2074
+ path12.join(homeDir(), ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
2075
+ path12.join(homeDir(), ".config", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
2076
+ path12.join(homeDir(), ".cline", "settings", "cline_mcp_settings.json")
1412
2077
  ];
1413
2078
  }
1414
2079
  if (process.platform === "win32") {
1415
2080
  return [
1416
- path9.join(process.env.APPDATA ?? "", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
1417
- path9.join(process.env.APPDATA ?? "", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
1418
- path9.join(homeDir(), ".cline", "settings", "cline_mcp_settings.json")
2081
+ path12.join(process.env.APPDATA ?? "", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
2082
+ path12.join(process.env.APPDATA ?? "", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
2083
+ path12.join(homeDir(), ".cline", "settings", "cline_mcp_settings.json")
1419
2084
  ];
1420
2085
  }
1421
2086
  return [
1422
- path9.join(homeDir(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
1423
- path9.join(homeDir(), "Library", "Application Support", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
1424
- path9.join(homeDir(), ".cline", "settings", "cline_mcp_settings.json")
2087
+ path12.join(homeDir(), "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
2088
+ path12.join(homeDir(), "Library", "Application Support", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
2089
+ path12.join(homeDir(), ".cline", "settings", "cline_mcp_settings.json")
1425
2090
  ];
1426
2091
  }
1427
2092
 
@@ -1430,7 +2095,7 @@ class ClineAdapter {
1430
2095
  detectConfigPath() {
1431
2096
  const candidates = getCandidatePaths();
1432
2097
  for (const candidate of candidates) {
1433
- if (fs7.existsSync(candidate)) {
2098
+ if (fs10.existsSync(candidate)) {
1434
2099
  return candidate;
1435
2100
  }
1436
2101
  }
@@ -1443,12 +2108,12 @@ class ClineAdapter {
1443
2108
  return syncPluginsToClient(this.id, plugins);
1444
2109
  }
1445
2110
  syncSkills(skills) {
1446
- projectSkillsToDir(path9.join(homeDir(), ".config", "cline", "skills"), skills, "dir");
2111
+ projectSkillsToDir(path12.join(homeDir(), ".config", "cline", "skills"), skills, "dir");
1447
2112
  }
1448
2113
  scanForImports(_config, managedIndex) {
1449
2114
  const configPath = this.detectConfigPath();
1450
2115
  const result = emptyImportScan(this.id, configPath ?? undefined);
1451
- if (!configPath || !fs7.existsSync(configPath)) {
2116
+ if (!configPath || !fs10.existsSync(configPath)) {
1452
2117
  return result;
1453
2118
  }
1454
2119
  const raw = readJsonFile(configPath, {});
@@ -1507,7 +2172,7 @@ class ClineAdapter {
1507
2172
  if (!configPath) {
1508
2173
  return errorResult(this.id, undefined, "Unable to resolve Cline MCP config path.");
1509
2174
  }
1510
- if (!fs7.existsSync(configPath)) {
2175
+ if (!fs10.existsSync(configPath)) {
1511
2176
  return okResult(this.id, configPath, "Cline config not found; skipping sync.");
1512
2177
  }
1513
2178
  try {
@@ -1549,24 +2214,24 @@ class ClineAdapter {
1549
2214
  }
1550
2215
 
1551
2216
  // src/adapters/kiro.ts
1552
- import path10 from "node:path";
1553
- import { z as z6 } from "zod";
1554
- var stringMapSchema6 = z6.record(z6.string(), z6.string());
1555
- var kiroEntrySchema = z6.object({
1556
- type: z6.string().optional(),
1557
- url: z6.string().min(1).optional(),
2217
+ import path13 from "node:path";
2218
+ import { z as z7 } from "zod";
2219
+ var stringMapSchema6 = z7.record(z7.string(), z7.string());
2220
+ var kiroEntrySchema = z7.object({
2221
+ type: z7.string().optional(),
2222
+ url: z7.string().min(1).optional(),
1558
2223
  headers: stringMapSchema6.optional(),
1559
- command: z6.string().min(1).optional(),
1560
- args: z6.array(z6.string()).optional(),
2224
+ command: z7.string().min(1).optional(),
2225
+ args: z7.array(z7.string()).optional(),
1561
2226
  env: stringMapSchema6.optional(),
1562
- cwd: z6.string().min(1).optional(),
1563
- disabled: z6.boolean().optional()
2227
+ cwd: z7.string().min(1).optional(),
2228
+ disabled: z7.boolean().optional()
1564
2229
  }).passthrough();
1565
2230
 
1566
2231
  class KiroAdapter {
1567
2232
  id = "kiro";
1568
2233
  detectConfigPath() {
1569
- return path10.join(homeDir(), ".kiro", "settings", "mcp.json");
2234
+ return path13.join(homeDir(), ".kiro", "settings", "mcp.json");
1570
2235
  }
1571
2236
  supportsHttp() {
1572
2237
  return true;
@@ -1671,30 +2336,30 @@ class KiroAdapter {
1671
2336
  }
1672
2337
  }
1673
2338
  syncSkills(skills) {
1674
- projectSkillsToDir(path10.join(homeDir(), ".kiro", "skills"), skills, "flat");
2339
+ projectSkillsToDir(path13.join(homeDir(), ".kiro", "skills"), skills, "flat");
1675
2340
  }
1676
2341
  }
1677
2342
 
1678
2343
  // src/adapters/opencode.ts
1679
- import path11 from "node:path";
1680
- import { z as z7 } from "zod";
1681
- var stringMapSchema7 = z7.record(z7.string(), z7.string());
1682
- var openCodeEntrySchema = z7.object({
1683
- type: z7.string().optional(),
1684
- url: z7.string().min(1).optional(),
2344
+ import path14 from "node:path";
2345
+ import { z as z8 } from "zod";
2346
+ var stringMapSchema7 = z8.record(z8.string(), z8.string());
2347
+ var openCodeEntrySchema = z8.object({
2348
+ type: z8.string().optional(),
2349
+ url: z8.string().min(1).optional(),
1685
2350
  headers: stringMapSchema7.optional(),
1686
- command: z7.union([z7.string().min(1), z7.array(z7.string())]).optional(),
1687
- args: z7.array(z7.string()).optional(),
2351
+ command: z8.union([z8.string().min(1), z8.array(z8.string())]).optional(),
2352
+ args: z8.array(z8.string()).optional(),
1688
2353
  env: stringMapSchema7.optional(),
1689
2354
  environment: stringMapSchema7.optional(),
1690
- cwd: z7.string().min(1).optional(),
1691
- enabled: z7.boolean().optional()
2355
+ cwd: z8.string().min(1).optional(),
2356
+ enabled: z8.boolean().optional()
1692
2357
  }).passthrough();
1693
2358
 
1694
2359
  class OpenCodeAdapter {
1695
2360
  id = "opencode";
1696
2361
  detectConfigPath() {
1697
- return path11.join(homeDir(), ".config", "opencode", "opencode.json");
2362
+ return path14.join(homeDir(), ".config", "opencode", "opencode.json");
1698
2363
  }
1699
2364
  supportsHttp() {
1700
2365
  return true;
@@ -1707,7 +2372,7 @@ class OpenCodeAdapter {
1707
2372
  if (!configPath) {
1708
2373
  return;
1709
2374
  }
1710
- projectSkillsToDir(path11.join(path11.dirname(configPath), "skills"), skills, "dir");
2375
+ projectSkillsToDir(path14.join(path14.dirname(configPath), "skills"), skills, "dir");
1711
2376
  }
1712
2377
  scanForImports(_config, managedIndex) {
1713
2378
  const configPath = this.detectConfigPath();
@@ -1814,30 +2479,30 @@ class OpenCodeAdapter {
1814
2479
  }
1815
2480
 
1816
2481
  // src/adapters/vscode.ts
1817
- import path12 from "node:path";
1818
- import { z as z8 } from "zod";
1819
- var stringMapSchema8 = z8.record(z8.string(), z8.string());
1820
- var vscodeEntrySchema = z8.object({
1821
- type: z8.string().optional(),
1822
- url: z8.string().min(1).optional(),
2482
+ import path15 from "node:path";
2483
+ import { z as z9 } from "zod";
2484
+ var stringMapSchema8 = z9.record(z9.string(), z9.string());
2485
+ var vscodeEntrySchema = z9.object({
2486
+ type: z9.string().optional(),
2487
+ url: z9.string().min(1).optional(),
1823
2488
  headers: stringMapSchema8.optional(),
1824
- command: z8.string().min(1).optional(),
1825
- args: z8.array(z8.string()).optional(),
2489
+ command: z9.string().min(1).optional(),
2490
+ args: z9.array(z9.string()).optional(),
1826
2491
  env: stringMapSchema8.optional(),
1827
- cwd: z8.string().min(1).optional(),
1828
- disabled: z8.boolean().optional()
2492
+ cwd: z9.string().min(1).optional(),
2493
+ disabled: z9.boolean().optional()
1829
2494
  }).passthrough();
1830
2495
 
1831
2496
  class VsCodeAdapter {
1832
2497
  id = "vscode";
1833
2498
  detectConfigPath() {
1834
2499
  if (process.platform === "linux") {
1835
- return path12.join(homeDir(), ".config", "Code", "User", "mcp.json");
2500
+ return path15.join(homeDir(), ".config", "Code", "User", "mcp.json");
1836
2501
  }
1837
2502
  if (process.platform === "win32") {
1838
- return path12.join(process.env.APPDATA ?? "", "Code", "User", "mcp.json");
2503
+ return path15.join(process.env.APPDATA ?? "", "Code", "User", "mcp.json");
1839
2504
  }
1840
- return path12.join(homeDir(), "Library", "Application Support", "Code", "User", "mcp.json");
2505
+ return path15.join(homeDir(), "Library", "Application Support", "Code", "User", "mcp.json");
1841
2506
  }
1842
2507
  supportsHttp() {
1843
2508
  return true;
@@ -1846,7 +2511,7 @@ class VsCodeAdapter {
1846
2511
  return syncPluginsToClient(this.id, plugins);
1847
2512
  }
1848
2513
  syncSkills(skills) {
1849
- projectSkillsToDir(path12.join(homeDir(), ".copilot", "skills"), skills, "dir");
2514
+ projectSkillsToDir(path15.join(homeDir(), ".copilot", "skills"), skills, "dir");
1850
2515
  }
1851
2516
  scanForImports(_config, managedIndex) {
1852
2517
  const configPath = this.detectConfigPath();
@@ -1950,24 +2615,24 @@ class VsCodeAdapter {
1950
2615
  }
1951
2616
 
1952
2617
  // src/adapters/qwen.ts
1953
- import path13 from "node:path";
1954
- import { z as z9 } from "zod";
1955
- var stringMapSchema9 = z9.record(z9.string(), z9.string());
1956
- var qwenEntrySchema = z9.object({
1957
- httpUrl: z9.string().min(1).optional(),
1958
- url: z9.string().min(1).optional(),
2618
+ import path16 from "node:path";
2619
+ import { z as z10 } from "zod";
2620
+ var stringMapSchema9 = z10.record(z10.string(), z10.string());
2621
+ var qwenEntrySchema = z10.object({
2622
+ httpUrl: z10.string().min(1).optional(),
2623
+ url: z10.string().min(1).optional(),
1959
2624
  headers: stringMapSchema9.optional(),
1960
- command: z9.string().min(1).optional(),
1961
- args: z9.array(z9.string()).optional(),
2625
+ command: z10.string().min(1).optional(),
2626
+ args: z10.array(z10.string()).optional(),
1962
2627
  env: stringMapSchema9.optional(),
1963
- cwd: z9.string().min(1).optional(),
1964
- disabled: z9.boolean().optional()
2628
+ cwd: z10.string().min(1).optional(),
2629
+ disabled: z10.boolean().optional()
1965
2630
  }).passthrough();
1966
2631
 
1967
2632
  class QwenAdapter {
1968
2633
  id = "qwen";
1969
2634
  detectConfigPath() {
1970
- return path13.join(homeDir(), ".qwen", "settings.json");
2635
+ return path16.join(homeDir(), ".qwen", "settings.json");
1971
2636
  }
1972
2637
  supportsHttp() {
1973
2638
  return true;
@@ -2085,31 +2750,31 @@ class QwenAdapter {
2085
2750
  }
2086
2751
  }
2087
2752
  syncSkills(skills) {
2088
- projectSkillsToDir(path13.join(homeDir(), ".qwen", "skills"), skills, "flat");
2753
+ projectSkillsToDir(path16.join(homeDir(), ".qwen", "skills"), skills, "flat");
2089
2754
  }
2090
2755
  }
2091
2756
 
2092
2757
  // src/adapters/openclaw.ts
2093
- import fs8 from "node:fs";
2094
- import path14 from "node:path";
2095
- import { z as z10 } from "zod";
2096
- var stringMapSchema10 = z10.record(z10.string(), z10.string());
2097
- var openclawEntrySchema = z10.object({
2098
- url: z10.string().min(1).optional(),
2099
- transport: z10.string().optional(),
2758
+ import fs11 from "node:fs";
2759
+ import path17 from "node:path";
2760
+ import { z as z11 } from "zod";
2761
+ var stringMapSchema10 = z11.record(z11.string(), z11.string());
2762
+ var openclawEntrySchema = z11.object({
2763
+ url: z11.string().min(1).optional(),
2764
+ transport: z11.string().optional(),
2100
2765
  headers: stringMapSchema10.optional(),
2101
- command: z10.string().min(1).optional(),
2102
- args: z10.array(z10.string()).optional(),
2766
+ command: z11.string().min(1).optional(),
2767
+ args: z11.array(z11.string()).optional(),
2103
2768
  env: stringMapSchema10.optional(),
2104
- cwd: z10.string().min(1).optional(),
2105
- workingDirectory: z10.string().min(1).optional(),
2106
- auth: z10.unknown().optional()
2769
+ cwd: z11.string().min(1).optional(),
2770
+ workingDirectory: z11.string().min(1).optional(),
2771
+ auth: z11.unknown().optional()
2107
2772
  }).passthrough();
2108
2773
 
2109
2774
  class OpenClawAdapter {
2110
2775
  id = "openclaw";
2111
2776
  detectConfigPath() {
2112
- return process.env.OPENCLAW_CONFIG_PATH ?? path14.join(homeDir(), ".openclaw", "openclaw.json");
2777
+ return process.env.OPENCLAW_CONFIG_PATH ?? path17.join(homeDir(), ".openclaw", "openclaw.json");
2113
2778
  }
2114
2779
  supportsHttp() {
2115
2780
  return true;
@@ -2118,7 +2783,7 @@ class OpenClawAdapter {
2118
2783
  return syncPluginsToClient(this.id, plugins);
2119
2784
  }
2120
2785
  syncSkills(skills) {
2121
- projectSkillsToDir(path14.join(homeDir(), ".openclaw", "skills"), skills, "dir");
2786
+ projectSkillsToDir(path17.join(homeDir(), ".openclaw", "skills"), skills, "dir");
2122
2787
  }
2123
2788
  scanForImports(_config, managedIndex) {
2124
2789
  const configPath = this.detectConfigPath();
@@ -2128,7 +2793,7 @@ class OpenClawAdapter {
2128
2793
  }
2129
2794
  let raw;
2130
2795
  try {
2131
- raw = JSON.parse(fs8.readFileSync(configPath, "utf8"));
2796
+ raw = JSON.parse(fs11.readFileSync(configPath, "utf8"));
2132
2797
  } catch {
2133
2798
  return result;
2134
2799
  }
@@ -2195,9 +2860,9 @@ class OpenClawAdapter {
2195
2860
  }
2196
2861
  try {
2197
2862
  let raw;
2198
- if (fs8.existsSync(configPath)) {
2863
+ if (fs11.existsSync(configPath)) {
2199
2864
  try {
2200
- raw = JSON.parse(fs8.readFileSync(configPath, "utf8"));
2865
+ raw = JSON.parse(fs11.readFileSync(configPath, "utf8"));
2201
2866
  } catch {
2202
2867
  return errorResult(this.id, configPath, "OpenClaw config contains invalid JSON (possibly JSON5). Cannot safely write.");
2203
2868
  }
@@ -2240,27 +2905,27 @@ class OpenClawAdapter {
2240
2905
  }
2241
2906
 
2242
2907
  // src/adapters/hermes.ts
2243
- import fs9 from "node:fs";
2244
- import path15 from "node:path";
2908
+ import fs12 from "node:fs";
2909
+ import path18 from "node:path";
2245
2910
  import YAML from "yaml";
2246
- import { z as z11 } from "zod";
2247
- var stringMapSchema11 = z11.record(z11.string(), z11.string());
2248
- var hermesEntrySchema = z11.object({
2249
- url: z11.string().min(1).optional(),
2911
+ import { z as z12 } from "zod";
2912
+ var stringMapSchema11 = z12.record(z12.string(), z12.string());
2913
+ var hermesEntrySchema = z12.object({
2914
+ url: z12.string().min(1).optional(),
2250
2915
  headers: stringMapSchema11.optional(),
2251
- command: z11.string().min(1).optional(),
2252
- args: z11.array(z11.string()).optional(),
2916
+ command: z12.string().min(1).optional(),
2917
+ args: z12.array(z12.string()).optional(),
2253
2918
  env: stringMapSchema11.optional(),
2254
- auth: z11.unknown().optional(),
2255
- enabled: z11.boolean().optional(),
2256
- timeout: z11.number().optional(),
2257
- tools: z11.array(z11.string()).optional()
2919
+ auth: z12.unknown().optional(),
2920
+ enabled: z12.boolean().optional(),
2921
+ timeout: z12.number().optional(),
2922
+ tools: z12.array(z12.string()).optional()
2258
2923
  }).passthrough();
2259
2924
 
2260
2925
  class HermesAdapter {
2261
2926
  id = "hermes";
2262
2927
  detectConfigPath() {
2263
- return path15.join(homeDir(), ".hermes", "config.yaml");
2928
+ return path18.join(homeDir(), ".hermes", "config.yaml");
2264
2929
  }
2265
2930
  supportsHttp() {
2266
2931
  return true;
@@ -2269,15 +2934,15 @@ class HermesAdapter {
2269
2934
  return syncPluginsToClient(this.id, plugins);
2270
2935
  }
2271
2936
  syncSkills(skills) {
2272
- projectSkillsToDir(path15.join(homeDir(), ".hermes", "skills"), skills, "dir");
2937
+ projectSkillsToDir(path18.join(homeDir(), ".hermes", "skills"), skills, "dir");
2273
2938
  }
2274
2939
  scanForImports(_config, managedIndex) {
2275
2940
  const configPath = this.detectConfigPath();
2276
2941
  const result = emptyImportScan(this.id, configPath ?? undefined);
2277
- if (!configPath || !fs9.existsSync(configPath)) {
2942
+ if (!configPath || !fs12.existsSync(configPath)) {
2278
2943
  return result;
2279
2944
  }
2280
- const existing = fs9.readFileSync(configPath, "utf8");
2945
+ const existing = fs12.readFileSync(configPath, "utf8");
2281
2946
  const doc = YAML.parse(existing) ?? {};
2282
2947
  const mcpServers = doc.mcp_servers ?? {};
2283
2948
  for (const [name, rawEntry] of Object.entries(mcpServers)) {
@@ -2335,7 +3000,7 @@ class HermesAdapter {
2335
3000
  return errorResult(this.id, undefined, "Unable to resolve Hermes config.yaml path.");
2336
3001
  }
2337
3002
  try {
2338
- const existing = fs9.existsSync(configPath) ? fs9.readFileSync(configPath, "utf8") : "";
3003
+ const existing = fs12.existsSync(configPath) ? fs12.readFileSync(configPath, "utf8") : "";
2339
3004
  const doc = existing.trim().length > 0 ? YAML.parse(existing) ?? {} : {};
2340
3005
  const mcpServers = doc.mcp_servers ?? {};
2341
3006
  removeSourceEntries(mcpServers, options.sourceEntriesToRemove);
@@ -2356,10 +3021,10 @@ class HermesAdapter {
2356
3021
  mcpServers[name] = entry;
2357
3022
  }
2358
3023
  doc.mcp_servers = mcpServers;
2359
- fs9.mkdirSync(path15.dirname(configPath), { recursive: true });
3024
+ fs12.mkdirSync(path18.dirname(configPath), { recursive: true });
2360
3025
  const tmpPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
2361
- fs9.writeFileSync(tmpPath, YAML.stringify(doc), { mode: 384 });
2362
- fs9.renameSync(tmpPath, configPath);
3026
+ fs12.writeFileSync(tmpPath, YAML.stringify(doc), { mode: 384 });
3027
+ fs12.renameSync(tmpPath, configPath);
2363
3028
  setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
2364
3029
  return okResult(this.id, configPath);
2365
3030
  } catch (error) {
@@ -2406,29 +3071,71 @@ function saveManagedIndex(index, filePath = getManagedIndexPath()) {
2406
3071
  writeJsonAtomic(filePath, index);
2407
3072
  }
2408
3073
 
3074
+ // src/core/skills.ts
3075
+ import fs13 from "node:fs";
3076
+ import path19 from "node:path";
3077
+ function listSkills() {
3078
+ const skillsDir = getSkillsDir();
3079
+ if (!fs13.existsSync(skillsDir)) {
3080
+ return [];
3081
+ }
3082
+ const files = fs13.readdirSync(skillsDir);
3083
+ const skills = [];
3084
+ for (const file of files) {
3085
+ if (file.endsWith(".md")) {
3086
+ const id = path19.basename(file, ".md");
3087
+ const content = fs13.readFileSync(path19.join(skillsDir, file), "utf-8");
3088
+ skills.push({ id, content });
3089
+ }
3090
+ }
3091
+ return skills;
3092
+ }
3093
+ function getSkill(id) {
3094
+ const skillsDir = getSkillsDir();
3095
+ const filePath = path19.join(skillsDir, `${id}.md`);
3096
+ if (!fs13.existsSync(filePath)) {
3097
+ return null;
3098
+ }
3099
+ const content = fs13.readFileSync(filePath, "utf-8");
3100
+ return { id, content };
3101
+ }
3102
+ function saveSkill(id, content) {
3103
+ const skillsDir = getSkillsDir();
3104
+ ensureDir(skillsDir);
3105
+ const filePath = path19.join(skillsDir, `${id}.md`);
3106
+ fs13.writeFileSync(filePath, content, "utf-8");
3107
+ }
3108
+ function deleteSkill(id) {
3109
+ const skillsDir = getSkillsDir();
3110
+ const filePath = path19.join(skillsDir, `${id}.md`);
3111
+ if (fs13.existsSync(filePath)) {
3112
+ fs13.unlinkSync(filePath);
3113
+ }
3114
+ }
3115
+
2409
3116
  // src/core/managed-index-lock.ts
2410
- import fs10 from "node:fs";
2411
- import path16 from "node:path";
3117
+ import fs14 from "node:fs";
3118
+ import path20 from "node:path";
2412
3119
  function isStale(lockPath, maxAgeMs = 5000) {
2413
3120
  try {
2414
- const stat = fs10.statSync(lockPath);
3121
+ const stat = fs14.statSync(lockPath);
2415
3122
  return Date.now() - stat.mtimeMs > maxAgeMs;
2416
3123
  } catch {
2417
3124
  return true;
2418
3125
  }
2419
3126
  }
2420
3127
  function withManagedIndexLock(lockPath, fn) {
2421
- fs10.mkdirSync(path16.dirname(lockPath), { recursive: true });
3128
+ fs14.mkdirSync(path20.dirname(lockPath), { recursive: true });
2422
3129
  const deadline = Date.now() + 5000;
2423
3130
  while (true) {
2424
3131
  try {
2425
- fs10.writeFileSync(lockPath, `${process.pid}
3132
+ fs14.writeFileSync(lockPath, `${process.pid}
2426
3133
  `, { flag: "wx" });
2427
3134
  break;
2428
3135
  } catch {
2429
3136
  if (isStale(lockPath)) {
2430
3137
  try {
2431
- fs10.unlinkSync(lockPath);
3138
+ fs14.unlinkSync(lockPath);
2432
3139
  } catch {}
2433
3140
  continue;
2434
3141
  }
@@ -2441,7 +3148,7 @@ function withManagedIndexLock(lockPath, fn) {
2441
3148
  return fn();
2442
3149
  } finally {
2443
3150
  try {
2444
- fs10.unlinkSync(lockPath);
3151
+ fs14.unlinkSync(lockPath);
2445
3152
  } catch {}
2446
3153
  }
2447
3154
  }
@@ -2713,4 +3420,421 @@ function persistSyncState(summary, config) {
2713
3420
  }
2714
3421
  }
2715
3422
 
2716
- export { mutateConfig, mutateProjectConfig, mutateActiveConfig, listSkills, getSkill, saveSkill, deleteSkill, prunePluginProjections, pruneAllPluginProjections, syncPluginsToClient, APP_VERSION, getAdapters, loadManagedIndex, getGatewayUrl, syncAllClients, persistSyncState };
3423
+ // src/core/plugin-manager.ts
3424
+ class PluginManager {
3425
+ configPath;
3426
+ cache;
3427
+ dataManager;
3428
+ lifecycle;
3429
+ secrets;
3430
+ constructor(configPath) {
3431
+ this.configPath = configPath || getConfigPath();
3432
+ this.cache = new PluginCache;
3433
+ this.dataManager = new PluginDataManager;
3434
+ this.lifecycle = new PluginLifecycle;
3435
+ this.secrets = new SecretsManager;
3436
+ }
3437
+ async inspectSource(sourceStr) {
3438
+ const parsed = parseSource(sourceStr);
3439
+ return this.inspectResolvedSource(parsed, sourceStr);
3440
+ }
3441
+ async inspectResolvedSource(parsed, sourceLabel = parsed.original) {
3442
+ const sha = parsed.resolvedSha || await this.cache.resolveSha(parsed);
3443
+ parsed.resolvedSha = sha;
3444
+ const cached = await this.cache.fetch(parsed, "");
3445
+ let pluginRoot = cached.root;
3446
+ if (!hasManifest(pluginRoot)) {
3447
+ const entries = fs15.readdirSync(pluginRoot, { withFileTypes: true });
3448
+ for (const entry of entries) {
3449
+ if (entry.isDirectory()) {
3450
+ const candidate = path21.join(pluginRoot, entry.name);
3451
+ if (hasManifest(candidate)) {
3452
+ pluginRoot = candidate;
3453
+ break;
3454
+ }
3455
+ }
3456
+ }
3457
+ }
3458
+ const manifest = readManifest(pluginRoot);
3459
+ const components = discoverComponents(pluginRoot);
3460
+ return { manifest, components, root: pluginRoot, source: sourceLabel, sha: cached.sha };
3461
+ }
3462
+ async installPlugin(sourceStr, options) {
3463
+ return this.installResolvedPlugin(parseSource(sourceStr), options, sourceStr);
3464
+ }
3465
+ async installResolvedPlugin(source, options, sourceLabel = source.original) {
3466
+ const info = await this.inspectResolvedSource(source, sourceLabel);
3467
+ const pluginName = options?.name || info.manifest?.name || path21.basename(info.root);
3468
+ const pluginId = `${pluginName}@${info.sha.slice(0, 8)}`;
3469
+ const dataDir = path21.join(getPluginDataRoot(), pluginId);
3470
+ ensureDir(dataDir);
3471
+ const enabled = options?.enabled ?? true;
3472
+ const discovered = info.components;
3473
+ const serverNames = discovered.mcpServers.map((s) => `${pluginName}__${s.id}`);
3474
+ const plugin = {
3475
+ id: pluginId,
3476
+ name: pluginName,
3477
+ source: sourceLabel,
3478
+ version: info.manifest?.version || "0.0.0",
3479
+ ref: source.ref || "latest",
3480
+ resolvedSha: info.sha,
3481
+ installedAt: new Date().toISOString(),
3482
+ root: info.root,
3483
+ dataDir,
3484
+ components: {
3485
+ mcpServers: options?.components?.mcpServers ?? discovered.mcpServers.length > 0,
3486
+ skills: options?.components?.skills ?? discovered.skills.length > 0,
3487
+ hooks: options?.components?.hooks ?? discovered.hooks.length > 0,
3488
+ agents: options?.components?.agents ?? discovered.agents.length > 0,
3489
+ commands: options?.components?.commands ?? discovered.commands.length > 0
3490
+ },
3491
+ discovered,
3492
+ enabled,
3493
+ status: "healthy",
3494
+ serverNames,
3495
+ projectedClients: [],
3496
+ approvals: options?.approvals || {},
3497
+ marketplace: options?.marketplace
3498
+ };
3499
+ await mutateConfig((config) => {
3500
+ if (!config.plugins)
3501
+ config.plugins = {};
3502
+ if (config.plugins[pluginId]) {
3503
+ throw new Error(`Plugin ${pluginId} is already installed`);
3504
+ }
3505
+ for (const existing of Object.values(config.plugins)) {
3506
+ for (const sn of serverNames) {
3507
+ if (existing.serverNames.includes(sn)) {
3508
+ throw new Error(`Server name collision: "${sn}" is already claimed by plugin "${existing.name}". Use --name to specify a different plugin name.`);
3509
+ }
3510
+ }
3511
+ }
3512
+ for (const sn of serverNames) {
3513
+ if (config.servers?.[sn]) {
3514
+ throw new Error(`Server name collision: "${sn}" is already registered as a server. Remove that server or use --name to choose a different plugin name.`);
3515
+ }
3516
+ }
3517
+ config.plugins[pluginId] = plugin;
3518
+ for (const mcp of discovered.mcpServers) {
3519
+ const nsName2 = `${pluginName}__${mcp.id}`;
3520
+ const approved = plugin.approvals?.mcpServers !== false;
3521
+ config.servers[nsName2] = mcp.transport === "http" && mcp.url ? { transport: "http", url: mcp.url, enabled: enabled && approved } : {
3522
+ transport: "stdio",
3523
+ command: "npx",
3524
+ args: ["-y", "@kwonye/mcpx@latest", "plugin-host", pluginId, mcp.id],
3525
+ enabled: enabled && approved
3526
+ };
3527
+ }
3528
+ }, this.configPath);
3529
+ this.lifecycle.initData(pluginId);
3530
+ const syncConfig = loadConfig(this.configPath);
3531
+ const summary = syncAllClients(syncConfig, this.secrets);
3532
+ await mutateConfig((freshConfig) => {
3533
+ persistSyncState(summary, freshConfig);
3534
+ }, this.configPath);
3535
+ return plugin;
3536
+ }
3537
+ async updatePlugin(pluginId) {
3538
+ const current = loadConfig(this.configPath).plugins?.[pluginId];
3539
+ if (!current)
3540
+ throw new Error(`Plugin ${pluginId} not found`);
3541
+ const info = await this.inspectSource(current.source);
3542
+ return this.applyPluginUpdate(pluginId, info, undefined, true);
3543
+ }
3544
+ async updateResolvedPlugin(pluginId, source, sourceFingerprint, sync = true) {
3545
+ const info = await this.inspectResolvedSource(source);
3546
+ return this.applyPluginUpdate(pluginId, info, sourceFingerprint, sync);
3547
+ }
3548
+ async applyPluginUpdate(pluginId, info, sourceFingerprint, sync = true) {
3549
+ const currentSnapshot = loadConfig(this.configPath).plugins?.[pluginId];
3550
+ if (!currentSnapshot)
3551
+ throw new Error(`Plugin ${pluginId} not found`);
3552
+ const pluginName = info.manifest?.name || currentSnapshot.name;
3553
+ let updatedPlugin;
3554
+ await mutateConfig((config) => {
3555
+ if (!config.plugins)
3556
+ throw new Error(`Plugin ${pluginId} not found`);
3557
+ const current = config.plugins[pluginId];
3558
+ if (!current)
3559
+ throw new Error(`Plugin ${pluginId} not found`);
3560
+ for (const oldName of current.serverNames) {
3561
+ delete config.servers[oldName];
3562
+ }
3563
+ current.version = info.manifest?.version || current.version;
3564
+ current.resolvedSha = info.sha;
3565
+ current.root = info.root;
3566
+ current.discovered = info.components;
3567
+ current.serverNames = info.components.mcpServers.map((s) => `${pluginName}__${s.id}`);
3568
+ const approved = current.approvals?.mcpServers !== false;
3569
+ for (const mcp of info.components.mcpServers) {
3570
+ const nsName2 = `${pluginName}__${mcp.id}`;
3571
+ config.servers[nsName2] = mcp.transport === "http" && mcp.url ? { transport: "http", url: mcp.url, enabled: current.enabled && approved } : {
3572
+ transport: "stdio",
3573
+ command: "npx",
3574
+ args: ["-y", "@kwonye/mcpx@latest", "plugin-host", pluginId, mcp.id],
3575
+ enabled: current.enabled && approved
3576
+ };
3577
+ }
3578
+ current.approvals = {};
3579
+ if (sourceFingerprint && current.marketplace)
3580
+ current.marketplace.sourceFingerprint = sourceFingerprint;
3581
+ delete current.updateError;
3582
+ config.plugins[pluginId] = current;
3583
+ updatedPlugin = current;
3584
+ }, this.configPath);
3585
+ if (sync) {
3586
+ const syncConfig = loadConfig(this.configPath);
3587
+ const summary = syncAllClients(syncConfig, this.secrets);
3588
+ await mutateConfig((freshConfig) => {
3589
+ persistSyncState(summary, freshConfig);
3590
+ }, this.configPath);
3591
+ }
3592
+ return updatedPlugin;
3593
+ }
3594
+ async uninstallPlugin(pluginId, keepData = false) {
3595
+ await mutateConfig((config) => {
3596
+ if (!config.plugins)
3597
+ throw new Error(`Plugin ${pluginId} not found`);
3598
+ const plugin = config.plugins[pluginId];
3599
+ if (!plugin)
3600
+ throw new Error(`Plugin ${pluginId} not found`);
3601
+ for (const name of plugin.serverNames) {
3602
+ delete config.servers[name];
3603
+ }
3604
+ delete config.plugins[pluginId];
3605
+ }, this.configPath);
3606
+ if (!keepData) {
3607
+ await this.lifecycle.removeData(pluginId);
3608
+ }
3609
+ const syncConfig = loadConfig(this.configPath);
3610
+ const summary = syncAllClients(syncConfig, this.secrets);
3611
+ await mutateConfig((freshConfig) => {
3612
+ persistSyncState(summary, freshConfig);
3613
+ }, this.configPath);
3614
+ }
3615
+ async enablePlugin(pluginId) {
3616
+ await mutateConfig((config) => {
3617
+ if (!config.plugins)
3618
+ throw new Error(`Plugin ${pluginId} not found`);
3619
+ const plugin = config.plugins[pluginId];
3620
+ if (!plugin)
3621
+ throw new Error(`Plugin ${pluginId} not found`);
3622
+ plugin.enabled = true;
3623
+ }, this.configPath);
3624
+ const syncConfig = loadConfig(this.configPath);
3625
+ const summary = syncAllClients(syncConfig, this.secrets);
3626
+ await mutateConfig((freshConfig) => {
3627
+ persistSyncState(summary, freshConfig);
3628
+ }, this.configPath);
3629
+ }
3630
+ async disablePlugin(pluginId) {
3631
+ await mutateConfig((config) => {
3632
+ if (!config.plugins)
3633
+ throw new Error(`Plugin ${pluginId} not found`);
3634
+ const plugin = config.plugins[pluginId];
3635
+ if (!plugin)
3636
+ throw new Error(`Plugin ${pluginId} not found`);
3637
+ plugin.enabled = false;
3638
+ }, this.configPath);
3639
+ const syncConfig = loadConfig(this.configPath);
3640
+ const summary = syncAllClients(syncConfig, this.secrets);
3641
+ await mutateConfig((freshConfig) => {
3642
+ persistSyncState(summary, freshConfig);
3643
+ }, this.configPath);
3644
+ }
3645
+ async approveComponent(pluginId, component) {
3646
+ await mutateConfig((config) => {
3647
+ if (!config.plugins)
3648
+ throw new Error(`Plugin ${pluginId} not found`);
3649
+ const plugin = config.plugins[pluginId];
3650
+ if (!plugin)
3651
+ throw new Error(`Plugin ${pluginId} not found`);
3652
+ if (!plugin.approvals)
3653
+ plugin.approvals = {};
3654
+ plugin.approvals[component] = true;
3655
+ if (component === "mcpServers") {
3656
+ for (const name of plugin.serverNames) {
3657
+ if (config.servers[name]) {
3658
+ config.servers[name].enabled = plugin.enabled;
3659
+ }
3660
+ }
3661
+ }
3662
+ }, this.configPath);
3663
+ const syncConfig = loadConfig(this.configPath);
3664
+ const summary = syncAllClients(syncConfig, this.secrets);
3665
+ await mutateConfig((freshConfig) => {
3666
+ persistSyncState(summary, freshConfig);
3667
+ }, this.configPath);
3668
+ }
3669
+ async getPluginStatus(pluginId) {
3670
+ const config = loadConfig(this.configPath);
3671
+ return config.plugins?.[pluginId] || null;
3672
+ }
3673
+ async listPlugins() {
3674
+ const config = loadConfig(this.configPath);
3675
+ return Object.values(config.plugins || {});
3676
+ }
3677
+ async preparePlugin(pluginId) {
3678
+ const config = loadConfig(this.configPath);
3679
+ if (!config.plugins)
3680
+ throw new Error(`Plugin ${pluginId} not found`);
3681
+ const plugin = config.plugins[pluginId];
3682
+ if (!plugin)
3683
+ throw new Error(`Plugin ${pluginId} not found`);
3684
+ await this.lifecycle.prepareDependencies(pluginId, plugin.root);
3685
+ }
3686
+ }
3687
+ async function inspectPlugin(source) {
3688
+ const manager = new PluginManager;
3689
+ return manager.inspectSource(source);
3690
+ }
3691
+ async function installPlugin(source, options) {
3692
+ const manager = new PluginManager;
3693
+ return manager.installPlugin(source, options);
3694
+ }
3695
+ async function updatePlugin(nameOrId) {
3696
+ const manager = new PluginManager;
3697
+ const config = loadConfig(manager["configPath"]);
3698
+ const id = resolvePluginId(config, nameOrId);
3699
+ if (config.plugins?.[id]?.marketplace) {
3700
+ const { updateMarketplaceInstalledPlugin } = await import("./marketplace-pwabb3ea.js");
3701
+ return updateMarketplaceInstalledPlugin(id);
3702
+ }
3703
+ return manager.updatePlugin(id);
3704
+ }
3705
+ async function uninstallPlugin(nameOrId, keepData) {
3706
+ const manager = new PluginManager;
3707
+ const config = loadConfig(manager["configPath"]);
3708
+ const id = resolvePluginId(config, nameOrId);
3709
+ await manager.uninstallPlugin(id, keepData);
3710
+ }
3711
+ async function enablePlugin(nameOrId) {
3712
+ const manager = new PluginManager;
3713
+ const config = loadConfig(manager["configPath"]);
3714
+ const id = resolvePluginId(config, nameOrId);
3715
+ await manager.enablePlugin(id);
3716
+ }
3717
+ async function disablePlugin(nameOrId) {
3718
+ const manager = new PluginManager;
3719
+ const config = loadConfig(manager["configPath"]);
3720
+ const id = resolvePluginId(config, nameOrId);
3721
+ await manager.disablePlugin(id);
3722
+ }
3723
+ async function setPluginProjectOverride(nameOrId, projectPath, override) {
3724
+ await mutateConfig((config) => {
3725
+ const id = resolvePluginId(config, nameOrId);
3726
+ const plugin = config.plugins?.[id];
3727
+ if (!plugin)
3728
+ throw new Error(`Plugin ${id} not found`);
3729
+ if (!config.projects?.[projectPath]) {
3730
+ throw new Error(`Project "${projectPath}" is not registered. Run "mcpx project init" there first.`);
3731
+ }
3732
+ if (!plugin.projectOverrides)
3733
+ plugin.projectOverrides = {};
3734
+ plugin.projectOverrides[projectPath] = { ...plugin.projectOverrides[projectPath], ...override };
3735
+ });
3736
+ const secrets = new SecretsManager;
3737
+ const syncConfig = loadConfig();
3738
+ const summary = syncAllClients(syncConfig, secrets);
3739
+ await mutateConfig((freshConfig) => {
3740
+ persistSyncState(summary, freshConfig);
3741
+ });
3742
+ }
3743
+ async function resetPluginProjectOverride(nameOrId, projectPath) {
3744
+ await mutateConfig((config) => {
3745
+ const id = resolvePluginId(config, nameOrId);
3746
+ const plugin = config.plugins?.[id];
3747
+ if (!plugin)
3748
+ throw new Error(`Plugin ${id} not found`);
3749
+ if (!config.projects?.[projectPath]) {
3750
+ throw new Error(`Project "${projectPath}" is not registered. Run "mcpx project init" there first.`);
3751
+ }
3752
+ if (!plugin.projectOverrides?.[projectPath])
3753
+ return;
3754
+ delete plugin.projectOverrides[projectPath];
3755
+ if (Object.keys(plugin.projectOverrides).length === 0) {
3756
+ delete plugin.projectOverrides;
3757
+ }
3758
+ });
3759
+ const secrets = new SecretsManager;
3760
+ const syncConfig = loadConfig();
3761
+ const summary = syncAllClients(syncConfig, secrets);
3762
+ await mutateConfig((freshConfig) => {
3763
+ persistSyncState(summary, freshConfig);
3764
+ });
3765
+ }
3766
+ async function approvePluginComponent(nameOrId, component) {
3767
+ const manager = new PluginManager;
3768
+ const config = loadConfig(manager["configPath"]);
3769
+ const id = resolvePluginId(config, nameOrId);
3770
+ await manager.approveComponent(id, component);
3771
+ }
3772
+ async function getPluginStatus(nameOrId) {
3773
+ const manager = new PluginManager;
3774
+ if (nameOrId) {
3775
+ const config = loadConfig(manager["configPath"]);
3776
+ const id = resolvePluginId(config, nameOrId);
3777
+ return manager.getPluginStatus(id);
3778
+ }
3779
+ const plugins = await manager.listPlugins();
3780
+ return plugins[0] || null;
3781
+ }
3782
+ async function listPlugins() {
3783
+ const manager = new PluginManager;
3784
+ return manager.listPlugins();
3785
+ }
3786
+ function resolvePluginId(config, nameOrId) {
3787
+ const plugins = config.plugins ?? {};
3788
+ if (plugins[nameOrId])
3789
+ return nameOrId;
3790
+ const qualifiedMatches = Object.keys(plugins).filter((id) => {
3791
+ const provenance = plugins[id].marketplace;
3792
+ return provenance && `${provenance.pluginName}@${provenance.name}` === nameOrId;
3793
+ });
3794
+ if (qualifiedMatches.length === 1)
3795
+ return qualifiedMatches[0];
3796
+ const matches = Object.keys(plugins).filter((id) => plugins[id].name === nameOrId);
3797
+ if (matches.length === 1)
3798
+ return matches[0];
3799
+ if (matches.length === 0) {
3800
+ throw new Error(`Plugin "${nameOrId}" not found. Available: ${Object.values(plugins).map((p) => `${p.name} (${p.id})`).join(", ") || "none"}`);
3801
+ }
3802
+ throw new Error(`Multiple plugins match "${nameOrId}": ${matches.join(", ")}. Use the full id (<name>@<sha8>).`);
3803
+ }
3804
+ async function pluginConfigSet(nameOrId, key, value, projectPath) {
3805
+ const manager = new PluginManager;
3806
+ const configPath = manager["configPath"];
3807
+ await mutateConfig((config) => {
3808
+ const id = resolvePluginId(config, nameOrId);
3809
+ const plugin = config.plugins?.[id];
3810
+ if (!plugin)
3811
+ throw new Error(`Plugin ${id} not found`);
3812
+ if (projectPath) {
3813
+ if (!plugin.projectOverrides)
3814
+ plugin.projectOverrides = {};
3815
+ if (!plugin.projectOverrides[projectPath])
3816
+ plugin.projectOverrides[projectPath] = {};
3817
+ if (!plugin.projectOverrides[projectPath].config)
3818
+ plugin.projectOverrides[projectPath].config = {};
3819
+ plugin.projectOverrides[projectPath].config[key] = value;
3820
+ } else {
3821
+ if (!plugin.config)
3822
+ plugin.config = {};
3823
+ plugin.config[key] = value;
3824
+ }
3825
+ }, configPath);
3826
+ }
3827
+ async function pluginSync() {
3828
+ const config = loadConfig();
3829
+ const secrets = new SecretsManager;
3830
+ const summary = syncAllClients(config, secrets);
3831
+ await mutateConfig((freshConfig) => {
3832
+ persistSyncState(summary, freshConfig);
3833
+ });
3834
+ }
3835
+ async function preparePlugin(name) {
3836
+ const manager = new PluginManager;
3837
+ await manager.preparePlugin(name);
3838
+ }
3839
+
3840
+ export { mutateConfig, mutateProjectConfig, mutateActiveConfig, listSkills, getSkill, saveSkill, deleteSkill, prunePluginProjections, pruneAllPluginProjections, syncPluginsToClient, APP_VERSION, getAdapters, loadManagedIndex, getGatewayUrl, syncAllClients, persistSyncState, PluginCache, readManifest, discoverComponents, hasManifest, resolvePluginVars, PluginDataManager, PluginLifecycle, parseSource, PluginManager, inspectPlugin, installPlugin, updatePlugin, uninstallPlugin, enablePlugin, disablePlugin, setPluginProjectOverride, resetPluginProjectOverride, approvePluginComponent, getPluginStatus, listPlugins, resolvePluginId, pluginConfigSet, pluginSync, preparePlugin };