@kwonye/mcpx 0.1.99 → 0.1.101

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 (40) hide show
  1. package/dist/{cli-28g7x9rn.js → cli-6wh8qx2y.js} +9 -3
  2. package/dist/{cli-dy7x0zrg.js → cli-6zq2fd6h.js} +1 -1
  3. package/dist/{cli-b0xrqsjc.js → cli-9k3wmag6.js} +286 -363
  4. package/dist/{cli-ct7asw78.js → cli-aawpvhhe.js} +13 -10
  5. package/dist/{cli-1ewrsfj4.js → cli-hh7rthhx.js} +4 -1
  6. package/dist/{cli-7x2n23em.js → cli-qzzjkt0q.js} +1132 -723
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +352 -88
  9. package/dist/{config-yx5m60fv.js → config-fbgyqmv1.js} +2 -2
  10. package/dist/core/auth-probe.d.ts +6 -1
  11. package/dist/core/auth-probe.d.ts.map +1 -1
  12. package/dist/core/daemon.d.ts +1 -0
  13. package/dist/core/daemon.d.ts.map +1 -1
  14. package/dist/core/identifiers.d.ts +3 -0
  15. package/dist/core/identifiers.d.ts.map +1 -0
  16. package/dist/core/index.d.ts +2 -2
  17. package/dist/core/index.d.ts.map +1 -1
  18. package/dist/core/index.js +22 -6
  19. package/dist/core/marketplace.d.ts.map +1 -1
  20. package/dist/core/oauth.d.ts +77 -2
  21. package/dist/core/oauth.d.ts.map +1 -1
  22. package/dist/core/paths.d.ts +1 -0
  23. package/dist/core/paths.d.ts.map +1 -1
  24. package/dist/core/plugin-cache.d.ts.map +1 -1
  25. package/dist/core/plugin-host.d.ts.map +1 -1
  26. package/dist/core/plugin-lifecycle.d.ts.map +1 -1
  27. package/dist/core/plugin-manager.d.ts.map +1 -1
  28. package/dist/core/plugin-parse.d.ts.map +1 -1
  29. package/dist/core/plugin-projections.d.ts.map +1 -1
  30. package/dist/core/plugin-source.d.ts.map +1 -1
  31. package/dist/core/secrets.d.ts.map +1 -1
  32. package/dist/core/skill-projections.d.ts.map +1 -1
  33. package/dist/core/skills.d.ts.map +1 -1
  34. package/dist/core/update-manager.d.ts.map +1 -1
  35. package/dist/gateway/server.d.ts.map +1 -1
  36. package/dist/{marketplace-pwabb3ea.js → marketplace-zem0m4mw.js} +5 -5
  37. package/dist/{plugin-host-9xcpnehq.js → plugin-host-5jmyt0td.js} +13 -39
  38. package/dist/{plugin-manager-19q55n69.js → plugin-manager-rw05c2x3.js} +4 -4
  39. package/dist/{registry-yvbxcaq6.js → registry-04kfpjh0.js} +2 -2
  40. package/package.json +1 -1
@@ -3,11 +3,11 @@ import {
3
3
  loadProjectConfig,
4
4
  saveConfig,
5
5
  saveProjectConfig
6
- } from "./cli-dy7x0zrg.js";
6
+ } from "./cli-6zq2fd6h.js";
7
7
  import {
8
8
  SecretsManager,
9
9
  ensureGatewayToken
10
- } from "./cli-1ewrsfj4.js";
10
+ } from "./cli-hh7rthhx.js";
11
11
  import {
12
12
  ensureDir,
13
13
  findProjectConfigPath,
@@ -20,7 +20,7 @@ import {
20
20
  readJsonFile,
21
21
  sha256,
22
22
  writeJsonAtomic
23
- } from "./cli-28g7x9rn.js";
23
+ } from "./cli-6wh8qx2y.js";
24
24
  import {
25
25
  __require,
26
26
  isServerEnabled
@@ -33,8 +33,10 @@ import path21 from "node:path";
33
33
  // src/core/plugin-cache.ts
34
34
  import fs from "node:fs";
35
35
  import path from "node:path";
36
- import { execFileSync } from "node:child_process";
36
+ import { execFile } from "node:child_process";
37
+ import { promisify } from "node:util";
37
38
  var TMP_PREFIX = "mcpx-acquire-";
39
+ var execFileAsync = promisify(execFile);
38
40
 
39
41
  class PluginCache {
40
42
  cacheRoot;
@@ -55,24 +57,18 @@ class PluginCache {
55
57
  async resolveSha(source) {
56
58
  const srcDir = this.sourceDir(source);
57
59
  if (source.type === "github") {
58
- const repo = source.original.split("@")[0];
60
+ const repo = source.original.replace(/^github\.com\//, "").split("@")[0];
59
61
  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();
62
+ const { stdout } = await execFileAsync("git", ["ls-remote", `https://github.com/${repo}.git`, ref], { timeout: 30000 });
63
+ const sha = stdout.split("\t")[0]?.trim();
65
64
  if (!sha)
66
65
  throw new Error(`Could not resolve ref ${ref} for ${repo}`);
67
66
  return sha;
68
67
  }
69
68
  if (source.type === "git" || source.type === "git-subdir") {
70
69
  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();
70
+ const { stdout } = await execFileAsync("git", ["ls-remote", source.original, ref], { timeout: 30000 });
71
+ const sha = stdout.split("\t")[0]?.trim();
76
72
  if (!sha)
77
73
  throw new Error(`Could not resolve ref ${ref} for ${source.original}`);
78
74
  return sha;
@@ -99,23 +95,22 @@ class PluginCache {
99
95
  }
100
96
  ensureDir(srcDir);
101
97
  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;
98
+ const remote = source.type === "github" ? `https://github.com/${source.original.replace(/^github\.com\//, "").split("@")[0]}.git` : source.original;
103
99
  const target = source.resolvedSha || source.ref || "HEAD";
104
100
  const tmpDir = fs.mkdtempSync(path.join(this.cacheRoot, TMP_PREFIX));
105
101
  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], {
102
+ await execFileAsync("git", ["init"], { cwd: tmpDir, timeout: 1e4 });
103
+ await execFileAsync("git", ["remote", "add", "origin", remote], { cwd: tmpDir, timeout: 1e4 });
104
+ await execFileAsync("git", ["fetch", "--depth", "1", "origin", target], {
109
105
  cwd: tmpDir,
110
- stdio: "pipe",
111
106
  timeout: 60000
112
107
  });
113
- execFileSync("git", ["checkout", "--detach", "FETCH_HEAD"], { cwd: tmpDir, stdio: "pipe", timeout: 1e4 });
114
- const actualSha = execFileSync("git", ["rev-parse", "HEAD"], {
108
+ await execFileAsync("git", ["checkout", "--detach", "FETCH_HEAD"], { cwd: tmpDir, timeout: 1e4 });
109
+ const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"], {
115
110
  cwd: tmpDir,
116
- encoding: "utf8",
117
111
  timeout: 1e4
118
- }).trim();
112
+ });
113
+ const actualSha = stdout.trim();
119
114
  fs.rmSync(path.join(tmpDir, ".git"), { recursive: true, force: true });
120
115
  const finalDest = this.shaDir(srcDir, actualSha);
121
116
  if (fs.existsSync(finalDest)) {
@@ -130,10 +125,21 @@ class PluginCache {
130
125
  if (!subdir.startsWith(path.resolve(tmpDir) + path.sep) || !fs.existsSync(subdir)) {
131
126
  throw new Error(`Plugin subdirectory not found: ${source.path}`);
132
127
  }
133
- fs.renameSync(subdir, finalDest);
128
+ try {
129
+ fs.renameSync(subdir, finalDest);
130
+ } catch (error) {
131
+ if (!fs.existsSync(finalDest))
132
+ throw error;
133
+ }
134
134
  fs.rmSync(tmpDir, { recursive: true, force: true });
135
135
  } else {
136
- fs.renameSync(tmpDir, finalDest);
136
+ try {
137
+ fs.renameSync(tmpDir, finalDest);
138
+ } catch (error) {
139
+ if (!fs.existsSync(finalDest))
140
+ throw error;
141
+ return { source: source.original, name: pluginName, sha: actualSha, root: finalDest };
142
+ }
137
143
  }
138
144
  this.updateRef(srcDir, actualSha);
139
145
  return { source: source.original, name: pluginName, sha: actualSha, root: finalDest };
@@ -357,7 +363,10 @@ class PluginDataManager {
357
363
  // src/core/plugin-lifecycle.ts
358
364
  import fs3 from "node:fs";
359
365
  import path3 from "node:path";
360
- import { execFileSync as execFileSync2 } from "node:child_process";
366
+ import { execFile as execFile2 } from "node:child_process";
367
+ import { promisify as promisify2 } from "node:util";
368
+ var execFileAsync2 = promisify2(execFile2);
369
+
361
370
  class PluginLifecycle {
362
371
  dataManager;
363
372
  constructor() {
@@ -373,9 +382,8 @@ class PluginLifecycle {
373
382
  const pkg = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf8"));
374
383
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
375
384
  if (Object.keys(deps).length > 0) {
376
- execFileSync2("npm", ["install", "--production"], {
385
+ await execFileAsync2("npm", ["install", "--production", "--ignore-scripts"], {
377
386
  cwd: pluginRoot,
378
- stdio: "pipe",
379
387
  timeout: 120000
380
388
  });
381
389
  }
@@ -384,8 +392,7 @@ class PluginLifecycle {
384
392
  const reqPath = path3.join(pluginRoot, "requirements.txt");
385
393
  if (fs3.existsSync(reqPath)) {
386
394
  try {
387
- execFileSync2("pip", ["install", "-r", reqPath], {
388
- stdio: "pipe",
395
+ await execFileAsync2("pip", ["install", "-r", reqPath], {
389
396
  timeout: 120000
390
397
  });
391
398
  } catch {}
@@ -450,10 +457,7 @@ function parseSource(source) {
450
457
  original: source
451
458
  };
452
459
  }
453
- return {
454
- type: "git",
455
- original: source
456
- };
460
+ throw new Error(`Unrecognized plugin source: ${source}`);
457
461
  }
458
462
 
459
463
  // src/core/plugin-parse.ts
@@ -462,7 +466,8 @@ import path4 from "node:path";
462
466
  import { z } from "zod";
463
467
  var CLAUDE_PLUGIN_JSON_REL = path4.join(".claude-plugin", "plugin.json");
464
468
  var CODEX_PLUGIN_JSON_REL = path4.join(".codex-plugin", "plugin.json");
465
- var MCP_JSON_REL = ".mcp.json";
469
+ var AGENT_PLUGIN_JSON_REL = "plugin.json";
470
+ var MCP_JSON_RELS = ["mcp.json", ".mcp.json"];
466
471
  var HOOKS_JSON_REL = path4.join("hooks", "hooks.json");
467
472
  var SKILLS_DIR = "skills";
468
473
  var COMMANDS_DIR = "commands";
@@ -498,7 +503,7 @@ var manifestSchema = z.object({
498
503
  }).passthrough().optional()
499
504
  }).passthrough();
500
505
  function manifestPath(pluginRoot) {
501
- for (const rel of [CLAUDE_PLUGIN_JSON_REL, CODEX_PLUGIN_JSON_REL]) {
506
+ for (const rel of [AGENT_PLUGIN_JSON_REL, CLAUDE_PLUGIN_JSON_REL, CODEX_PLUGIN_JSON_REL]) {
502
507
  const candidate = path4.join(pluginRoot, rel);
503
508
  if (fs4.existsSync(candidate))
504
509
  return candidate;
@@ -687,7 +692,7 @@ function discoverHooks(pluginRoot, raw) {
687
692
  }
688
693
  function discoverMcpServers(pluginRoot, raw) {
689
694
  const declared = typeof raw?.mcpServers === "string" ? raw.mcpServers : undefined;
690
- const mcpPath = declared ? resolveWithinRoot(pluginRoot, declared) : path4.join(pluginRoot, MCP_JSON_REL);
695
+ const mcpPath = declared ? resolveWithinRoot(pluginRoot, declared) : MCP_JSON_RELS.map((rel) => path4.join(pluginRoot, rel)).find(fs4.existsSync) ?? null;
691
696
  if (!mcpPath || !fs4.existsSync(mcpPath))
692
697
  return [];
693
698
  try {
@@ -724,7 +729,7 @@ function hasManifest(pluginRoot) {
724
729
  return manifestPath(pluginRoot) !== null;
725
730
  }
726
731
  function resolvePluginVars(value, pluginRoot, dataDir) {
727
- return value.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginRoot).replace(/\$\{DATA\}/g, dataDir).replace(/\$\{CLAUDE_PROJECT_DIR\}/g, pluginRoot);
732
+ return value.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginRoot).replace(/\$\{CLAUDE_PLUGIN_DATA\}/g, dataDir).replace(/\$\{MCPX_PLUGIN_ROOT\}/g, pluginRoot).replace(/\$\{MCPX_PLUGIN_DATA\}/g, dataDir).replace(/\$\{DATA\}/g, dataDir).replace(/\$\{CLAUDE_PROJECT_DIR\}/g, pluginRoot);
728
733
  }
729
734
 
730
735
  // src/core/config-store.ts
@@ -997,7 +1002,7 @@ function detectManagedEntryDrift(managedIndex, clientId, entryName, existingValu
997
1002
  // src/adapters/claude.ts
998
1003
  import fs8 from "node:fs";
999
1004
  import path8 from "node:path";
1000
- import { z as z2 } from "zod";
1005
+ import { z as z4 } from "zod";
1001
1006
 
1002
1007
  // src/core/plugin-projections.ts
1003
1008
  import fs6 from "node:fs";
@@ -1021,6 +1026,17 @@ function saveOwnership(targetDir, manifest) {
1021
1026
  }
1022
1027
  function recordOwned(targetDir, pluginId, paths) {
1023
1028
  const manifest = loadOwnership(targetDir);
1029
+ const previous = manifest.plugins[pluginId]?.paths ?? [];
1030
+ const next = new Set(paths);
1031
+ for (const stale of previous) {
1032
+ if (next.has(stale))
1033
+ continue;
1034
+ const stalePath = path6.join(targetDir, stale);
1035
+ try {
1036
+ assertWithinBase(targetDir, stalePath);
1037
+ fs6.rmSync(stalePath, { recursive: true, force: true });
1038
+ } catch {}
1039
+ }
1024
1040
  manifest.plugins[pluginId] = {
1025
1041
  pluginId,
1026
1042
  paths,
@@ -1043,6 +1059,7 @@ function prunePluginProjections(targetDir, pluginId) {
1043
1059
  for (const p of paths) {
1044
1060
  const fullPath = path6.join(targetDir, p);
1045
1061
  try {
1062
+ assertWithinBase(targetDir, fullPath);
1046
1063
  if (fs6.existsSync(fullPath)) {
1047
1064
  fs6.rmSync(fullPath, { recursive: true, force: true });
1048
1065
  }
@@ -1099,6 +1116,7 @@ function syncClaude(plugins) {
1099
1116
  continue;
1100
1117
  }
1101
1118
  const targetDir = path6.join(targetBase, plugin.pluginName);
1119
+ assertWithinBase(targetBase, targetDir);
1102
1120
  ensureDir(targetDir);
1103
1121
  const mcpJsonSrc = path6.join(plugin.pluginRoot, ".mcp.json");
1104
1122
  const mcpJsonDest = path6.join(targetDir, ".mcp.json");
@@ -1125,226 +1143,70 @@ function syncClaudeDesktop(plugins) {
1125
1143
  unsupported
1126
1144
  };
1127
1145
  }
1128
- function syncCodex(plugins) {
1129
- const targetBase = path6.join(homeDir(), ".codex", "skills");
1146
+ function syncNamespacedComponents(config, plugins) {
1130
1147
  const projectedDirs = [];
1131
- const unsupported = ["hooks", "agents"];
1132
1148
  const errors = [];
1133
- for (const plugin of plugins) {
1134
- if (!plugin.enabled)
1135
- continue;
1136
- if (plugin.components.skills && isComponentApproved(plugin, "skills")) {
1137
- ensureDir(targetBase);
1138
- const owned = [];
1139
- for (const skill of plugin.skills) {
1140
- try {
1141
- const targetDir = path6.join(targetBase, nsName(plugin.pluginName, skill.id));
1142
- assertWithinBase(targetBase, targetDir);
1143
- ensureDir(targetDir);
1144
- copyFileOrDir(skill.path, path6.join(targetDir, "SKILL.md"));
1145
- owned.push(nsName(plugin.pluginName, skill.id));
1146
- projectedDirs.push(targetDir);
1147
- } catch (error) {
1148
- errors.push(`${plugin.pluginName}/${skill.id}: ${error.message}`);
1149
- }
1150
- }
1151
- recordOwned(targetBase, plugin.pluginId, owned);
1152
- }
1153
- }
1154
- return {
1155
- clientId: "codex",
1156
- status: "SYNCED",
1157
- projectedDirs,
1158
- unsupported,
1159
- ...errors.length > 0 ? { error: errors.join("; ") } : {}
1160
- };
1161
- }
1162
- function syncCursor(plugins) {
1163
- const skillsBase = path6.join(homeDir(), ".cursor", "skills");
1164
- const commandsBase = path6.join(homeDir(), ".cursor", "commands");
1165
- const projectedDirs = [];
1166
- const unsupported = ["hooks", "agents"];
1167
- const errors = [];
1168
- for (const plugin of plugins) {
1169
- if (!plugin.enabled)
1170
- continue;
1171
- const ownedSkills = [];
1172
- const ownedCommands = [];
1173
- if (plugin.components.skills && isComponentApproved(plugin, "skills")) {
1174
- ensureDir(skillsBase);
1175
- for (const skill of plugin.skills) {
1176
- try {
1177
- const targetDir = path6.join(skillsBase, nsName(plugin.pluginName, skill.id));
1178
- assertWithinBase(skillsBase, targetDir);
1179
- ensureDir(targetDir);
1180
- copyFileOrDir(skill.path, path6.join(targetDir, "SKILL.md"));
1181
- ownedSkills.push(nsName(plugin.pluginName, skill.id));
1182
- projectedDirs.push(targetDir);
1183
- } catch (error) {
1184
- errors.push(`${plugin.pluginName}/${skill.id}: ${error.message}`);
1185
- }
1186
- }
1187
- }
1188
- if (plugin.components.commands && isComponentApproved(plugin, "commands")) {
1189
- ensureDir(commandsBase);
1190
- for (const cmd of plugin.commands) {
1191
- try {
1192
- const targetPath = path6.join(commandsBase, `${nsName(plugin.pluginName, cmd.id)}.md`);
1193
- assertWithinBase(commandsBase, targetPath);
1194
- copyFileOrDir(cmd.path, targetPath);
1195
- ownedCommands.push(`${nsName(plugin.pluginName, cmd.id)}.md`);
1196
- projectedDirs.push(targetPath);
1197
- } catch (error) {
1198
- errors.push(`${plugin.pluginName}/${cmd.id}: ${error.message}`);
1199
- }
1149
+ const syncSkills = (plugin) => {
1150
+ if (!config.skillsBase)
1151
+ return;
1152
+ if (!plugin.components.skills || !isComponentApproved(plugin, "skills")) {
1153
+ if (fs6.existsSync(config.skillsBase)) {
1154
+ recordOwned(config.skillsBase, plugin.pluginId, []);
1200
1155
  }
1156
+ return;
1201
1157
  }
1202
- if (ownedSkills.length > 0)
1203
- recordOwned(skillsBase, plugin.pluginId, ownedSkills);
1204
- if (ownedCommands.length > 0)
1205
- recordOwned(commandsBase, plugin.pluginId, ownedCommands);
1206
- }
1207
- return {
1208
- clientId: "cursor",
1209
- status: "SYNCED",
1210
- projectedDirs,
1211
- unsupported,
1212
- ...errors.length > 0 ? { error: errors.join("; ") } : {}
1213
- };
1214
- }
1215
- function syncVsCode(plugins) {
1216
- const skillsBase = path6.join(homeDir(), ".copilot", "skills");
1217
- const projectedDirs = [];
1218
- const unsupported = ["hooks", "agents"];
1219
- const errors = [];
1220
- for (const plugin of plugins) {
1221
- if (!plugin.enabled)
1222
- continue;
1223
- if (plugin.components.skills && isComponentApproved(plugin, "skills")) {
1224
- ensureDir(skillsBase);
1225
- const owned = [];
1226
- for (const skill of plugin.skills) {
1227
- try {
1228
- const targetDir = path6.join(skillsBase, nsName(plugin.pluginName, skill.id));
1229
- assertWithinBase(skillsBase, targetDir);
1230
- ensureDir(targetDir);
1231
- copyFileOrDir(skill.path, path6.join(targetDir, "SKILL.md"));
1232
- owned.push(nsName(plugin.pluginName, skill.id));
1233
- projectedDirs.push(targetDir);
1234
- } catch (error) {
1235
- errors.push(`${plugin.pluginName}/${skill.id}: ${error.message}`);
1236
- }
1158
+ ensureDir(config.skillsBase);
1159
+ const owned = [];
1160
+ for (const skill of plugin.skills) {
1161
+ try {
1162
+ const name = nsName(plugin.pluginName, skill.id);
1163
+ const targetDir = path6.join(config.skillsBase, name);
1164
+ assertWithinBase(config.skillsBase, targetDir);
1165
+ ensureDir(targetDir);
1166
+ copyFileOrDir(skill.path, path6.join(targetDir, "SKILL.md"));
1167
+ owned.push(name);
1168
+ projectedDirs.push(targetDir);
1169
+ } catch (error) {
1170
+ errors.push(`${plugin.pluginName}/${skill.id}: ${error.message}`);
1237
1171
  }
1238
- recordOwned(skillsBase, plugin.pluginId, owned);
1239
1172
  }
1240
- }
1241
- return {
1242
- clientId: "vscode",
1243
- status: "SYNCED",
1244
- projectedDirs,
1245
- unsupported,
1246
- ...errors.length > 0 ? { error: errors.join("; ") } : {}
1173
+ recordOwned(config.skillsBase, plugin.pluginId, owned);
1247
1174
  };
1248
- }
1249
- function syncQwen(plugins) {
1250
- const targetBase = path6.join(homeDir(), ".qwen", "skills");
1251
- const projectedDirs = [];
1252
- const unsupported = ["hooks", "agents", "commands"];
1253
- const errors = [];
1254
- for (const plugin of plugins) {
1255
- if (!plugin.enabled)
1256
- continue;
1257
- if (plugin.components.skills && isComponentApproved(plugin, "skills")) {
1258
- ensureDir(targetBase);
1259
- const owned = [];
1260
- for (const skill of plugin.skills) {
1261
- try {
1262
- const targetDir = path6.join(targetBase, nsName(plugin.pluginName, skill.id));
1263
- assertWithinBase(targetBase, targetDir);
1264
- ensureDir(targetDir);
1265
- copyFileOrDir(skill.path, path6.join(targetDir, "SKILL.md"));
1266
- owned.push(nsName(plugin.pluginName, skill.id));
1267
- projectedDirs.push(targetDir);
1268
- } catch (error) {
1269
- errors.push(`${plugin.pluginName}/${skill.id}: ${error.message}`);
1270
- }
1175
+ const syncCommands = (plugin) => {
1176
+ if (!config.commandsBase)
1177
+ return;
1178
+ if (!plugin.components.commands || !isComponentApproved(plugin, "commands")) {
1179
+ if (fs6.existsSync(config.commandsBase)) {
1180
+ recordOwned(config.commandsBase, plugin.pluginId, []);
1271
1181
  }
1272
- recordOwned(targetBase, plugin.pluginId, owned);
1182
+ return;
1273
1183
  }
1274
- }
1275
- return {
1276
- clientId: "qwen",
1277
- status: "SYNCED",
1278
- projectedDirs,
1279
- unsupported,
1280
- ...errors.length > 0 ? { error: errors.join("; ") } : {}
1281
- };
1282
- }
1283
- function syncCline(plugins) {
1284
- const targetBase = path6.join(homeDir(), ".config", "cline", "skills");
1285
- const projectedDirs = [];
1286
- const unsupported = ["hooks", "agents", "commands"];
1287
- const errors = [];
1288
- for (const plugin of plugins) {
1289
- if (!plugin.enabled)
1290
- continue;
1291
- if (plugin.components.skills && isComponentApproved(plugin, "skills")) {
1292
- ensureDir(targetBase);
1293
- const owned = [];
1294
- for (const skill of plugin.skills) {
1295
- try {
1296
- const targetDir = path6.join(targetBase, nsName(plugin.pluginName, skill.id));
1297
- assertWithinBase(targetBase, targetDir);
1298
- ensureDir(targetDir);
1299
- copyFileOrDir(skill.path, path6.join(targetDir, "SKILL.md"));
1300
- owned.push(nsName(plugin.pluginName, skill.id));
1301
- projectedDirs.push(targetDir);
1302
- } catch (error) {
1303
- errors.push(`${plugin.pluginName}/${skill.id}: ${error.message}`);
1304
- }
1184
+ ensureDir(config.commandsBase);
1185
+ const owned = [];
1186
+ for (const command of plugin.commands) {
1187
+ try {
1188
+ const targetPath = path6.join(config.commandsBase, `${nsName(plugin.pluginName, command.id)}.md`);
1189
+ assertWithinBase(config.commandsBase, targetPath);
1190
+ copyFileOrDir(command.path, targetPath);
1191
+ owned.push(path6.basename(targetPath));
1192
+ projectedDirs.push(targetPath);
1193
+ } catch (error) {
1194
+ errors.push(`${plugin.pluginName}/${command.id}: ${error.message}`);
1305
1195
  }
1306
- recordOwned(targetBase, plugin.pluginId, owned);
1307
1196
  }
1308
- }
1309
- return {
1310
- clientId: "cline",
1311
- status: "SYNCED",
1312
- projectedDirs,
1313
- unsupported,
1314
- ...errors.length > 0 ? { error: errors.join("; ") } : {}
1197
+ recordOwned(config.commandsBase, plugin.pluginId, owned);
1315
1198
  };
1316
- }
1317
- function syncKiro(plugins) {
1318
- const targetBase = path6.join(homeDir(), ".kiro", "skills");
1319
- const projectedDirs = [];
1320
- const unsupported = ["hooks", "agents", "commands"];
1321
- const errors = [];
1322
1199
  for (const plugin of plugins) {
1323
1200
  if (!plugin.enabled)
1324
1201
  continue;
1325
- if (plugin.components.skills && isComponentApproved(plugin, "skills")) {
1326
- ensureDir(targetBase);
1327
- const owned = [];
1328
- for (const skill of plugin.skills) {
1329
- try {
1330
- const targetDir = path6.join(targetBase, nsName(plugin.pluginName, skill.id));
1331
- assertWithinBase(targetBase, targetDir);
1332
- ensureDir(targetDir);
1333
- copyFileOrDir(skill.path, path6.join(targetDir, "SKILL.md"));
1334
- owned.push(nsName(plugin.pluginName, skill.id));
1335
- projectedDirs.push(targetDir);
1336
- } catch (error) {
1337
- errors.push(`${plugin.pluginName}/${skill.id}: ${error.message}`);
1338
- }
1339
- }
1340
- recordOwned(targetBase, plugin.pluginId, owned);
1341
- }
1202
+ syncSkills(plugin);
1203
+ syncCommands(plugin);
1342
1204
  }
1343
1205
  return {
1344
- clientId: "kiro",
1206
+ clientId: config.clientId,
1345
1207
  status: "SYNCED",
1346
1208
  projectedDirs,
1347
- unsupported,
1209
+ unsupported: config.unsupported,
1348
1210
  ...errors.length > 0 ? { error: errors.join("; ") } : {}
1349
1211
  };
1350
1212
  }
@@ -1361,12 +1223,12 @@ function syncPluginsToClient(clientId, plugins) {
1361
1223
  const syncMap = {
1362
1224
  claude: syncClaude,
1363
1225
  "claude-desktop": syncClaudeDesktop,
1364
- codex: syncCodex,
1365
- cursor: syncCursor,
1366
- vscode: syncVsCode,
1367
- qwen: syncQwen,
1368
- cline: syncCline,
1369
- kiro: syncKiro,
1226
+ codex: (plugins2) => syncNamespacedComponents({ clientId: "codex", skillsBase: path6.join(homeDir(), ".codex", "skills"), unsupported: ["hooks", "agents"] }, plugins2),
1227
+ cursor: (plugins2) => syncNamespacedComponents({ clientId: "cursor", skillsBase: path6.join(homeDir(), ".cursor", "skills"), commandsBase: path6.join(homeDir(), ".cursor", "commands"), unsupported: ["hooks", "agents"] }, plugins2),
1228
+ vscode: (plugins2) => syncNamespacedComponents({ clientId: "vscode", skillsBase: path6.join(homeDir(), ".copilot", "skills"), unsupported: ["hooks", "agents"] }, plugins2),
1229
+ qwen: (plugins2) => syncNamespacedComponents({ clientId: "qwen", skillsBase: path6.join(homeDir(), ".qwen", "skills"), unsupported: ["hooks", "agents", "commands"] }, plugins2),
1230
+ cline: (plugins2) => syncNamespacedComponents({ clientId: "cline", skillsBase: path6.join(homeDir(), ".config", "cline", "skills"), unsupported: ["hooks", "agents", "commands"] }, plugins2),
1231
+ kiro: (plugins2) => syncNamespacedComponents({ clientId: "kiro", skillsBase: path6.join(homeDir(), ".kiro", "skills"), unsupported: ["hooks", "agents", "commands"] }, plugins2),
1370
1232
  opencode: syncOpenCode
1371
1233
  };
1372
1234
  const syncFn = syncMap[clientId];
@@ -1377,7 +1239,7 @@ function syncPluginsToClient(clientId, plugins) {
1377
1239
  claude: path6.join(homeDir(), ".claude", "skills"),
1378
1240
  "claude-desktop": "",
1379
1241
  codex: path6.join(homeDir(), ".codex", "skills"),
1380
- cursor: path6.join(homeDir(), ".cursor"),
1242
+ cursor: path6.join(homeDir(), ".cursor", "skills"),
1381
1243
  vscode: path6.join(homeDir(), ".copilot", "skills"),
1382
1244
  qwen: path6.join(homeDir(), ".qwen", "skills"),
1383
1245
  cline: path6.join(homeDir(), ".config", "cline", "skills"),
@@ -1388,6 +1250,9 @@ function syncPluginsToClient(clientId, plugins) {
1388
1250
  if (target) {
1389
1251
  const enabledIds = plugins.filter((p) => p.enabled).map((p) => p.pluginId);
1390
1252
  pruneUnlistedPlugins(target, enabledIds);
1253
+ if (clientId === "cursor") {
1254
+ pruneUnlistedPlugins(path6.join(homeDir(), ".cursor", "commands"), enabledIds);
1255
+ }
1391
1256
  }
1392
1257
  return syncFn(plugins);
1393
1258
  }
@@ -1427,18 +1292,53 @@ function copyFileOrDir(src, dest, errors) {
1427
1292
  // src/core/skill-projections.ts
1428
1293
  import fs7 from "node:fs";
1429
1294
  import path7 from "node:path";
1295
+ import { z as z3 } from "zod";
1296
+
1297
+ // src/core/identifiers.ts
1298
+ import { z as z2 } from "zod";
1299
+ var skillIdSchema = z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/);
1300
+ function validateSkillId(id) {
1301
+ const result = skillIdSchema.safeParse(id);
1302
+ if (!result.success) {
1303
+ throw new Error(`Invalid skill id "${id}". Use letters, numbers, dots, underscores, and hyphens.`);
1304
+ }
1305
+ return id;
1306
+ }
1307
+ function normalizePluginName(name) {
1308
+ const normalized = name.replace(/[\\/]/g, "_").replace(/^\.+/, "").replace(/[^a-zA-Z0-9._-]/g, "_");
1309
+ if (!normalized) {
1310
+ throw new Error(`Invalid plugin name "${name}"`);
1311
+ }
1312
+ return normalized;
1313
+ }
1314
+
1315
+ // src/core/skill-projections.ts
1430
1316
  var OWNERSHIP_MANIFEST2 = "mcpx-skills.json";
1317
+ var ownershipSchema = z3.object({
1318
+ version: z3.literal(1),
1319
+ paths: z3.array(z3.string())
1320
+ });
1321
+ function assertWithinBase2(base, target) {
1322
+ const resolvedBase = path7.resolve(base);
1323
+ const resolvedTarget = path7.resolve(target);
1324
+ if (resolvedTarget !== resolvedBase && !resolvedTarget.startsWith(resolvedBase + path7.sep)) {
1325
+ throw new Error(`Refusing to access path outside projection base: ${target}`);
1326
+ }
1327
+ }
1431
1328
  function loadOwnership2(targetDir) {
1432
1329
  const manifestPath2 = path7.join(targetDir, OWNERSHIP_MANIFEST2);
1433
1330
  if (!fs7.existsSync(manifestPath2)) {
1434
1331
  return { version: 1, paths: [] };
1435
1332
  }
1436
1333
  try {
1437
- return JSON.parse(fs7.readFileSync(manifestPath2, "utf8"));
1334
+ const parsed = ownershipSchema.safeParse(JSON.parse(fs7.readFileSync(manifestPath2, "utf8")));
1335
+ if (parsed.success)
1336
+ return parsed.data;
1438
1337
  } catch {
1439
1338
  console.error(`[mcpx] Warning: corrupt ownership manifest at ${manifestPath2}, resetting`);
1440
1339
  return { version: 1, paths: [] };
1441
1340
  }
1341
+ return { version: 1, paths: [] };
1442
1342
  }
1443
1343
  function saveOwnership2(targetDir, manifest) {
1444
1344
  ensureDir(targetDir);
@@ -1451,11 +1351,13 @@ function projectSkillsToDir(targetDir, skills, layout) {
1451
1351
  if (!newPaths.has(ownedPath)) {
1452
1352
  const fullPath = path7.join(targetDir, ownedPath);
1453
1353
  try {
1354
+ assertWithinBase2(targetDir, fullPath);
1454
1355
  if (fs7.existsSync(fullPath)) {
1455
1356
  fs7.rmSync(fullPath, { recursive: true, force: true });
1456
1357
  }
1457
1358
  if (layout === "dir") {
1458
1359
  const dirPath = path7.join(targetDir, path7.dirname(ownedPath));
1360
+ assertWithinBase2(targetDir, dirPath);
1459
1361
  try {
1460
1362
  const remaining = fs7.readdirSync(dirPath);
1461
1363
  if (remaining.length === 0) {
@@ -1469,7 +1371,9 @@ function projectSkillsToDir(targetDir, skills, layout) {
1469
1371
  }
1470
1372
  }
1471
1373
  for (const skill of skills) {
1374
+ validateSkillId(skill.id);
1472
1375
  const filePath = layout === "dir" ? path7.join(targetDir, skill.id, "SKILL.md") : path7.join(targetDir, `${skill.id}.md`);
1376
+ assertWithinBase2(targetDir, filePath);
1473
1377
  ensureDir(path7.dirname(filePath));
1474
1378
  fs7.writeFileSync(filePath, skill.content, "utf-8");
1475
1379
  }
@@ -1493,16 +1397,16 @@ function syncProjectDisabledArray(project, managedNames, disabledForThisProject)
1493
1397
  delete project.disabledMcpServers;
1494
1398
  }
1495
1399
  }
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(),
1400
+ var stringMapSchema = z4.record(z4.string(), z4.string());
1401
+ var claudeEntrySchema = z4.object({
1402
+ type: z4.string().optional(),
1403
+ url: z4.string().min(1).optional(),
1500
1404
  headers: stringMapSchema.optional(),
1501
- command: z2.string().min(1).optional(),
1502
- args: z2.array(z2.string()).optional(),
1405
+ command: z4.string().min(1).optional(),
1406
+ args: z4.array(z4.string()).optional(),
1503
1407
  env: stringMapSchema.optional(),
1504
- cwd: z2.string().min(1).optional(),
1505
- disabled: z2.boolean().optional()
1408
+ cwd: z4.string().min(1).optional(),
1409
+ disabled: z4.boolean().optional()
1506
1410
  }).passthrough();
1507
1411
 
1508
1412
  class ClaudeAdapter {
@@ -1643,29 +1547,29 @@ class ClaudeAdapter {
1643
1547
  }
1644
1548
 
1645
1549
  // src/adapters/claude-desktop.ts
1646
- import { execFileSync as execFileSync3 } from "node:child_process";
1550
+ import { execFileSync } from "node:child_process";
1647
1551
  import path9 from "node:path";
1648
- import { z as z3 } from "zod";
1552
+ import { z as z5 } from "zod";
1649
1553
 
1650
1554
  // src/version.ts
1651
1555
  var APP_VERSION = "0.1.3";
1652
1556
 
1653
1557
  // 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(),
1558
+ var stringMapSchema2 = z5.record(z5.string(), z5.string());
1559
+ var claudeDesktopEntrySchema = z5.object({
1560
+ type: z5.string().optional(),
1561
+ url: z5.string().min(1).optional(),
1658
1562
  headers: stringMapSchema2.optional(),
1659
- command: z3.string().min(1).optional(),
1660
- args: z3.array(z3.string()).optional(),
1563
+ command: z5.string().min(1).optional(),
1564
+ args: z5.array(z5.string()).optional(),
1661
1565
  env: stringMapSchema2.optional(),
1662
- cwd: z3.string().min(1).optional(),
1663
- disabled: z3.boolean().optional()
1566
+ cwd: z5.string().min(1).optional(),
1567
+ disabled: z5.boolean().optional()
1664
1568
  }).passthrough();
1665
1569
  function buildProxyEntry(entry) {
1666
1570
  const upstreamName = entry.name.replace(/ \(mcpx\)$/, "");
1667
1571
  try {
1668
- const mcpxPath = execFileSync3("which", ["mcpx"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
1572
+ const mcpxPath = execFileSync("which", ["mcpx"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
1669
1573
  if (mcpxPath)
1670
1574
  return { command: mcpxPath, args: ["proxy", upstreamName] };
1671
1575
  } catch {}
@@ -1793,22 +1697,22 @@ class ClaudeDesktopAdapter {
1793
1697
  import path10 from "node:path";
1794
1698
  import fs9 from "node:fs";
1795
1699
  import { parse, stringify } from "@iarna/toml";
1796
- import { z as z4 } from "zod";
1797
- var stringMapSchema3 = z4.preprocess((value) => {
1700
+ import { z as z6 } from "zod";
1701
+ var stringMapSchema3 = z6.preprocess((value) => {
1798
1702
  if (!value || typeof value !== "object" || Array.isArray(value)) {
1799
1703
  return value;
1800
1704
  }
1801
1705
  return Object.fromEntries(Object.entries(value));
1802
- }, z4.record(z4.string(), z4.string()));
1803
- var codexEntrySchema = z4.object({
1804
- enabled: z4.boolean().optional(),
1805
- url: z4.string().min(1).optional(),
1706
+ }, z6.record(z6.string(), z6.string()));
1707
+ var codexEntrySchema = z6.object({
1708
+ enabled: z6.boolean().optional(),
1709
+ url: z6.string().min(1).optional(),
1806
1710
  headers: stringMapSchema3.optional(),
1807
1711
  http_headers: stringMapSchema3.optional(),
1808
- command: z4.string().min(1).optional(),
1809
- args: z4.array(z4.string()).optional(),
1712
+ command: z6.string().min(1).optional(),
1713
+ args: z6.array(z6.string()).optional(),
1810
1714
  env: stringMapSchema3.optional(),
1811
- cwd: z4.string().min(1).optional()
1715
+ cwd: z6.string().min(1).optional()
1812
1716
  }).passthrough();
1813
1717
 
1814
1718
  class CodexAdapter {
@@ -1923,17 +1827,17 @@ class CodexAdapter {
1923
1827
 
1924
1828
  // src/adapters/cursor.ts
1925
1829
  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(),
1830
+ import { z as z7 } from "zod";
1831
+ var stringMapSchema4 = z7.record(z7.string(), z7.string());
1832
+ var cursorEntrySchema = z7.object({
1833
+ type: z7.string().optional(),
1834
+ url: z7.string().min(1).optional(),
1931
1835
  headers: stringMapSchema4.optional(),
1932
- command: z5.string().min(1).optional(),
1933
- args: z5.array(z5.string()).optional(),
1836
+ command: z7.string().min(1).optional(),
1837
+ args: z7.array(z7.string()).optional(),
1934
1838
  env: stringMapSchema4.optional(),
1935
- cwd: z5.string().min(1).optional(),
1936
- disabled: z5.boolean().optional()
1839
+ cwd: z7.string().min(1).optional(),
1840
+ disabled: z7.boolean().optional()
1937
1841
  }).passthrough();
1938
1842
 
1939
1843
  class CursorAdapter {
@@ -2054,19 +1958,19 @@ class CursorAdapter {
2054
1958
  // src/adapters/cline.ts
2055
1959
  import path12 from "node:path";
2056
1960
  import fs10 from "node:fs";
2057
- import { z as z6 } from "zod";
1961
+ import { z as z8 } from "zod";
2058
1962
  var HTTP_TRANSPORT_TYPES = new Set(["http", "sse", "streamableHttp"]);
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(),
1963
+ var stringMapSchema5 = z8.record(z8.string(), z8.string());
1964
+ var clineEntrySchema = z8.object({
1965
+ type: z8.string().optional(),
1966
+ transportType: z8.string().optional(),
1967
+ url: z8.string().min(1).optional(),
2064
1968
  headers: stringMapSchema5.optional(),
2065
- command: z6.string().min(1).optional(),
2066
- args: z6.array(z6.string()).optional(),
1969
+ command: z8.string().min(1).optional(),
1970
+ args: z8.array(z8.string()).optional(),
2067
1971
  env: stringMapSchema5.optional(),
2068
- cwd: z6.string().min(1).optional(),
2069
- disabled: z6.boolean().optional()
1972
+ cwd: z8.string().min(1).optional(),
1973
+ disabled: z8.boolean().optional()
2070
1974
  }).passthrough();
2071
1975
  function getCandidatePaths() {
2072
1976
  if (process.platform === "linux") {
@@ -2215,17 +2119,17 @@ class ClineAdapter {
2215
2119
 
2216
2120
  // src/adapters/kiro.ts
2217
2121
  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(),
2122
+ import { z as z9 } from "zod";
2123
+ var stringMapSchema6 = z9.record(z9.string(), z9.string());
2124
+ var kiroEntrySchema = z9.object({
2125
+ type: z9.string().optional(),
2126
+ url: z9.string().min(1).optional(),
2223
2127
  headers: stringMapSchema6.optional(),
2224
- command: z7.string().min(1).optional(),
2225
- args: z7.array(z7.string()).optional(),
2128
+ command: z9.string().min(1).optional(),
2129
+ args: z9.array(z9.string()).optional(),
2226
2130
  env: stringMapSchema6.optional(),
2227
- cwd: z7.string().min(1).optional(),
2228
- disabled: z7.boolean().optional()
2131
+ cwd: z9.string().min(1).optional(),
2132
+ disabled: z9.boolean().optional()
2229
2133
  }).passthrough();
2230
2134
 
2231
2135
  class KiroAdapter {
@@ -2342,18 +2246,18 @@ class KiroAdapter {
2342
2246
 
2343
2247
  // src/adapters/opencode.ts
2344
2248
  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(),
2249
+ import { z as z10 } from "zod";
2250
+ var stringMapSchema7 = z10.record(z10.string(), z10.string());
2251
+ var openCodeEntrySchema = z10.object({
2252
+ type: z10.string().optional(),
2253
+ url: z10.string().min(1).optional(),
2350
2254
  headers: stringMapSchema7.optional(),
2351
- command: z8.union([z8.string().min(1), z8.array(z8.string())]).optional(),
2352
- args: z8.array(z8.string()).optional(),
2255
+ command: z10.union([z10.string().min(1), z10.array(z10.string())]).optional(),
2256
+ args: z10.array(z10.string()).optional(),
2353
2257
  env: stringMapSchema7.optional(),
2354
2258
  environment: stringMapSchema7.optional(),
2355
- cwd: z8.string().min(1).optional(),
2356
- enabled: z8.boolean().optional()
2259
+ cwd: z10.string().min(1).optional(),
2260
+ enabled: z10.boolean().optional()
2357
2261
  }).passthrough();
2358
2262
 
2359
2263
  class OpenCodeAdapter {
@@ -2480,17 +2384,17 @@ class OpenCodeAdapter {
2480
2384
 
2481
2385
  // src/adapters/vscode.ts
2482
2386
  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(),
2387
+ import { z as z11 } from "zod";
2388
+ var stringMapSchema8 = z11.record(z11.string(), z11.string());
2389
+ var vscodeEntrySchema = z11.object({
2390
+ type: z11.string().optional(),
2391
+ url: z11.string().min(1).optional(),
2488
2392
  headers: stringMapSchema8.optional(),
2489
- command: z9.string().min(1).optional(),
2490
- args: z9.array(z9.string()).optional(),
2393
+ command: z11.string().min(1).optional(),
2394
+ args: z11.array(z11.string()).optional(),
2491
2395
  env: stringMapSchema8.optional(),
2492
- cwd: z9.string().min(1).optional(),
2493
- disabled: z9.boolean().optional()
2396
+ cwd: z11.string().min(1).optional(),
2397
+ disabled: z11.boolean().optional()
2494
2398
  }).passthrough();
2495
2399
 
2496
2400
  class VsCodeAdapter {
@@ -2616,17 +2520,17 @@ class VsCodeAdapter {
2616
2520
 
2617
2521
  // src/adapters/qwen.ts
2618
2522
  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(),
2523
+ import { z as z12 } from "zod";
2524
+ var stringMapSchema9 = z12.record(z12.string(), z12.string());
2525
+ var qwenEntrySchema = z12.object({
2526
+ httpUrl: z12.string().min(1).optional(),
2527
+ url: z12.string().min(1).optional(),
2624
2528
  headers: stringMapSchema9.optional(),
2625
- command: z10.string().min(1).optional(),
2626
- args: z10.array(z10.string()).optional(),
2529
+ command: z12.string().min(1).optional(),
2530
+ args: z12.array(z12.string()).optional(),
2627
2531
  env: stringMapSchema9.optional(),
2628
- cwd: z10.string().min(1).optional(),
2629
- disabled: z10.boolean().optional()
2532
+ cwd: z12.string().min(1).optional(),
2533
+ disabled: z12.boolean().optional()
2630
2534
  }).passthrough();
2631
2535
 
2632
2536
  class QwenAdapter {
@@ -2757,18 +2661,18 @@ class QwenAdapter {
2757
2661
  // src/adapters/openclaw.ts
2758
2662
  import fs11 from "node:fs";
2759
2663
  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(),
2664
+ import { z as z13 } from "zod";
2665
+ var stringMapSchema10 = z13.record(z13.string(), z13.string());
2666
+ var openclawEntrySchema = z13.object({
2667
+ url: z13.string().min(1).optional(),
2668
+ transport: z13.string().optional(),
2765
2669
  headers: stringMapSchema10.optional(),
2766
- command: z11.string().min(1).optional(),
2767
- args: z11.array(z11.string()).optional(),
2670
+ command: z13.string().min(1).optional(),
2671
+ args: z13.array(z13.string()).optional(),
2768
2672
  env: stringMapSchema10.optional(),
2769
- cwd: z11.string().min(1).optional(),
2770
- workingDirectory: z11.string().min(1).optional(),
2771
- auth: z11.unknown().optional()
2673
+ cwd: z13.string().min(1).optional(),
2674
+ workingDirectory: z13.string().min(1).optional(),
2675
+ auth: z13.unknown().optional()
2772
2676
  }).passthrough();
2773
2677
 
2774
2678
  class OpenClawAdapter {
@@ -2908,18 +2812,18 @@ class OpenClawAdapter {
2908
2812
  import fs12 from "node:fs";
2909
2813
  import path18 from "node:path";
2910
2814
  import YAML from "yaml";
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(),
2815
+ import { z as z14 } from "zod";
2816
+ var stringMapSchema11 = z14.record(z14.string(), z14.string());
2817
+ var hermesEntrySchema = z14.object({
2818
+ url: z14.string().min(1).optional(),
2915
2819
  headers: stringMapSchema11.optional(),
2916
- command: z12.string().min(1).optional(),
2917
- args: z12.array(z12.string()).optional(),
2820
+ command: z14.string().min(1).optional(),
2821
+ args: z14.array(z14.string()).optional(),
2918
2822
  env: stringMapSchema11.optional(),
2919
- auth: z12.unknown().optional(),
2920
- enabled: z12.boolean().optional(),
2921
- timeout: z12.number().optional(),
2922
- tools: z12.array(z12.string()).optional()
2823
+ auth: z14.unknown().optional(),
2824
+ enabled: z14.boolean().optional(),
2825
+ timeout: z14.number().optional(),
2826
+ tools: z14.array(z14.string()).optional()
2923
2827
  }).passthrough();
2924
2828
 
2925
2829
  class HermesAdapter {
@@ -3084,13 +2988,18 @@ function listSkills() {
3084
2988
  for (const file of files) {
3085
2989
  if (file.endsWith(".md")) {
3086
2990
  const id = path19.basename(file, ".md");
3087
- const content = fs13.readFileSync(path19.join(skillsDir, file), "utf-8");
3088
- skills.push({ id, content });
2991
+ try {
2992
+ const content = fs13.readFileSync(path19.join(skillsDir, file), "utf-8");
2993
+ skills.push({ id, content });
2994
+ } catch {
2995
+ console.error(`[mcpx] Warning: could not read skill ${file}`);
2996
+ }
3089
2997
  }
3090
2998
  }
3091
2999
  return skills;
3092
3000
  }
3093
3001
  function getSkill(id) {
3002
+ validateSkillId(id);
3094
3003
  const skillsDir = getSkillsDir();
3095
3004
  const filePath = path19.join(skillsDir, `${id}.md`);
3096
3005
  if (!fs13.existsSync(filePath)) {
@@ -3100,12 +3009,14 @@ function getSkill(id) {
3100
3009
  return { id, content };
3101
3010
  }
3102
3011
  function saveSkill(id, content) {
3012
+ validateSkillId(id);
3103
3013
  const skillsDir = getSkillsDir();
3104
3014
  ensureDir(skillsDir);
3105
3015
  const filePath = path19.join(skillsDir, `${id}.md`);
3106
3016
  fs13.writeFileSync(filePath, content, "utf-8");
3107
3017
  }
3108
3018
  function deleteSkill(id) {
3019
+ validateSkillId(id);
3109
3020
  const skillsDir = getSkillsDir();
3110
3021
  const filePath = path19.join(skillsDir, `${id}.md`);
3111
3022
  if (fs13.existsSync(filePath)) {
@@ -3464,7 +3375,7 @@ class PluginManager {
3464
3375
  }
3465
3376
  async installResolvedPlugin(source, options, sourceLabel = source.original) {
3466
3377
  const info = await this.inspectResolvedSource(source, sourceLabel);
3467
- const pluginName = options?.name || info.manifest?.name || path21.basename(info.root);
3378
+ const pluginName = normalizePluginName(options?.name || info.manifest?.name || path21.basename(info.root));
3468
3379
  const pluginId = `${pluginName}@${info.sha.slice(0, 8)}`;
3469
3380
  const dataDir = path21.join(getPluginDataRoot(), pluginId);
3470
3381
  ensureDir(dataDir);
@@ -3518,7 +3429,7 @@ class PluginManager {
3518
3429
  for (const mcp of discovered.mcpServers) {
3519
3430
  const nsName2 = `${pluginName}__${mcp.id}`;
3520
3431
  const approved = plugin.approvals?.mcpServers !== false;
3521
- config.servers[nsName2] = mcp.transport === "http" && mcp.url ? { transport: "http", url: mcp.url, enabled: enabled && approved } : {
3432
+ config.servers[nsName2] = mcp.transport === "http" && mcp.url ? { transport: "http", url: new URL(mcp.url).toString(), enabled: enabled && approved } : {
3522
3433
  transport: "stdio",
3523
3434
  command: "npx",
3524
3435
  args: ["-y", "@kwonye/mcpx@latest", "plugin-host", pluginId, mcp.id],
@@ -3549,7 +3460,7 @@ class PluginManager {
3549
3460
  const currentSnapshot = loadConfig(this.configPath).plugins?.[pluginId];
3550
3461
  if (!currentSnapshot)
3551
3462
  throw new Error(`Plugin ${pluginId} not found`);
3552
- const pluginName = info.manifest?.name || currentSnapshot.name;
3463
+ const pluginName = normalizePluginName(info.manifest?.name || currentSnapshot.name);
3553
3464
  let updatedPlugin;
3554
3465
  await mutateConfig((config) => {
3555
3466
  if (!config.plugins)
@@ -3565,17 +3476,18 @@ class PluginManager {
3565
3476
  current.root = info.root;
3566
3477
  current.discovered = info.components;
3567
3478
  current.serverNames = info.components.mcpServers.map((s) => `${pluginName}__${s.id}`);
3568
- const approved = current.approvals?.mcpServers !== false;
3479
+ const requiresApproval = !current.marketplace && info.components.mcpServers.length > 0;
3480
+ const approved = !requiresApproval && current.approvals?.mcpServers !== false;
3569
3481
  for (const mcp of info.components.mcpServers) {
3570
3482
  const nsName2 = `${pluginName}__${mcp.id}`;
3571
- config.servers[nsName2] = mcp.transport === "http" && mcp.url ? { transport: "http", url: mcp.url, enabled: current.enabled && approved } : {
3483
+ config.servers[nsName2] = mcp.transport === "http" && mcp.url ? { transport: "http", url: new URL(mcp.url).toString(), enabled: current.enabled && approved } : {
3572
3484
  transport: "stdio",
3573
3485
  command: "npx",
3574
3486
  args: ["-y", "@kwonye/mcpx@latest", "plugin-host", pluginId, mcp.id],
3575
3487
  enabled: current.enabled && approved
3576
3488
  };
3577
3489
  }
3578
- current.approvals = {};
3490
+ current.approvals = requiresApproval ? { mcpServers: false } : {};
3579
3491
  if (sourceFingerprint && current.marketplace)
3580
3492
  current.marketplace.sourceFingerprint = sourceFingerprint;
3581
3493
  delete current.updateError;
@@ -3620,6 +3532,11 @@ class PluginManager {
3620
3532
  if (!plugin)
3621
3533
  throw new Error(`Plugin ${pluginId} not found`);
3622
3534
  plugin.enabled = true;
3535
+ for (const name of plugin.serverNames) {
3536
+ const server = config.servers[name];
3537
+ if (server)
3538
+ server.enabled = plugin.approvals?.mcpServers !== false;
3539
+ }
3623
3540
  }, this.configPath);
3624
3541
  const syncConfig = loadConfig(this.configPath);
3625
3542
  const summary = syncAllClients(syncConfig, this.secrets);
@@ -3635,6 +3552,11 @@ class PluginManager {
3635
3552
  if (!plugin)
3636
3553
  throw new Error(`Plugin ${pluginId} not found`);
3637
3554
  plugin.enabled = false;
3555
+ for (const name of plugin.serverNames) {
3556
+ const server = config.servers[name];
3557
+ if (server)
3558
+ server.enabled = false;
3559
+ }
3638
3560
  }, this.configPath);
3639
3561
  const syncConfig = loadConfig(this.configPath);
3640
3562
  const summary = syncAllClients(syncConfig, this.secrets);
@@ -3678,10 +3600,11 @@ class PluginManager {
3678
3600
  const config = loadConfig(this.configPath);
3679
3601
  if (!config.plugins)
3680
3602
  throw new Error(`Plugin ${pluginId} not found`);
3681
- const plugin = config.plugins[pluginId];
3603
+ const resolvedId = resolvePluginId(config, pluginId);
3604
+ const plugin = config.plugins[resolvedId];
3682
3605
  if (!plugin)
3683
3606
  throw new Error(`Plugin ${pluginId} not found`);
3684
- await this.lifecycle.prepareDependencies(pluginId, plugin.root);
3607
+ await this.lifecycle.prepareDependencies(resolvedId, plugin.root);
3685
3608
  }
3686
3609
  }
3687
3610
  async function inspectPlugin(source) {
@@ -3697,7 +3620,7 @@ async function updatePlugin(nameOrId) {
3697
3620
  const config = loadConfig(manager["configPath"]);
3698
3621
  const id = resolvePluginId(config, nameOrId);
3699
3622
  if (config.plugins?.[id]?.marketplace) {
3700
- const { updateMarketplaceInstalledPlugin } = await import("./marketplace-pwabb3ea.js");
3623
+ const { updateMarketplaceInstalledPlugin } = await import("./marketplace-zem0m4mw.js");
3701
3624
  return updateMarketplaceInstalledPlugin(id);
3702
3625
  }
3703
3626
  return manager.updatePlugin(id);