@kwonye/mcpx 0.1.97 → 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.
- package/dist/{cli-remv48t3.js → cli-1ewrsfj4.js} +1 -1
- package/dist/{cli-zjepj3sj.js → cli-28g7x9rn.js} +10 -1
- package/dist/{cli-gf70a85c.js → cli-7x2n23em.js} +112 -24
- package/dist/{cli-vy81xjm0.js → cli-b0xrqsjc.js} +1612 -488
- package/dist/cli-ct7asw78.js +598 -0
- package/dist/{cli-g4w6k655.js → cli-dy7x0zrg.js} +70 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +137 -25
- package/dist/{config-2s2aqkqp.js → config-yx5m60fv.js} +2 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/daemon.d.ts.map +1 -1
- package/dist/core/index.d.ts +4 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +46 -20
- package/dist/core/marketplace-defaults.d.ts +4 -0
- package/dist/core/marketplace-defaults.d.ts.map +1 -0
- package/dist/core/marketplace-updater.d.ts +10 -0
- package/dist/core/marketplace-updater.d.ts.map +1 -0
- package/dist/core/marketplace.d.ts +67 -0
- package/dist/core/marketplace.d.ts.map +1 -0
- package/dist/core/paths.d.ts +3 -0
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/plugin-cache.d.ts.map +1 -1
- package/dist/core/plugin-manager.d.ts +21 -7
- package/dist/core/plugin-manager.d.ts.map +1 -1
- package/dist/core/plugin-parse.d.ts.map +1 -1
- package/dist/marketplace-pwabb3ea.js +33 -0
- package/dist/{plugin-host-d9wkemnr.js → plugin-host-9xcpnehq.js} +4 -5
- package/dist/{plugin-manager-0ygz96m9.js → plugin-manager-19q55n69.js} +6 -5
- package/dist/{registry-bnjb2345.js → registry-yvbxcaq6.js} +2 -2
- package/dist/types.d.ts +69 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/cli-tdsve98z.js +0 -1029
|
@@ -3,28 +3,733 @@ import {
|
|
|
3
3
|
loadProjectConfig,
|
|
4
4
|
saveConfig,
|
|
5
5
|
saveProjectConfig
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-dy7x0zrg.js";
|
|
7
7
|
import {
|
|
8
|
+
SecretsManager,
|
|
8
9
|
ensureGatewayToken
|
|
9
|
-
} from "./cli-
|
|
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-
|
|
23
|
+
} from "./cli-28g7x9rn.js";
|
|
21
24
|
import {
|
|
25
|
+
__require,
|
|
22
26
|
isServerEnabled
|
|
23
27
|
} from "./cli-s4gspqdf.js";
|
|
24
28
|
|
|
25
|
-
// src/core/
|
|
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
|
-
|
|
39
|
-
const fd =
|
|
40
|
-
|
|
41
|
-
|
|
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 =
|
|
753
|
+
const stat = fs5.statSync(lockPath);
|
|
49
754
|
if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
|
|
50
|
-
|
|
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
|
-
|
|
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 ?
|
|
104
|
-
const localPath =
|
|
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 =
|
|
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/
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
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
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
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
|
|
177
|
-
import
|
|
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
|
|
181
|
-
if (!
|
|
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(
|
|
1012
|
+
return JSON.parse(fs6.readFileSync(manifestPath2, "utf8"));
|
|
186
1013
|
} catch {
|
|
187
|
-
console.error(`[mcpx] Warning: corrupt plugin ownership manifest at ${
|
|
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
|
-
|
|
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 =
|
|
1044
|
+
const fullPath = path6.join(targetDir, p);
|
|
218
1045
|
try {
|
|
219
|
-
if (
|
|
220
|
-
|
|
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 =
|
|
245
|
-
const resolvedTarget =
|
|
246
|
-
if (resolvedTarget !== resolvedBase && !resolvedTarget.startsWith(resolvedBase +
|
|
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 =
|
|
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 =
|
|
1101
|
+
const targetDir = path6.join(targetBase, plugin.pluginName);
|
|
275
1102
|
ensureDir(targetDir);
|
|
276
|
-
const mcpJsonSrc =
|
|
277
|
-
const mcpJsonDest =
|
|
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 (
|
|
280
|
-
|
|
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 =
|
|
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 =
|
|
1141
|
+
const targetDir = path6.join(targetBase, nsName(plugin.pluginName, skill.id));
|
|
315
1142
|
assertWithinBase(targetBase, targetDir);
|
|
316
1143
|
ensureDir(targetDir);
|
|
317
|
-
copyFileOrDir(skill.path,
|
|
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 =
|
|
337
|
-
const commandsBase =
|
|
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 =
|
|
1177
|
+
const targetDir = path6.join(skillsBase, nsName(plugin.pluginName, skill.id));
|
|
351
1178
|
assertWithinBase(skillsBase, targetDir);
|
|
352
1179
|
ensureDir(targetDir);
|
|
353
|
-
copyFileOrDir(skill.path,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1228
|
+
const targetDir = path6.join(skillsBase, nsName(plugin.pluginName, skill.id));
|
|
402
1229
|
assertWithinBase(skillsBase, targetDir);
|
|
403
1230
|
ensureDir(targetDir);
|
|
404
|
-
copyFileOrDir(skill.path,
|
|
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 =
|
|
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 =
|
|
1262
|
+
const targetDir = path6.join(targetBase, nsName(plugin.pluginName, skill.id));
|
|
436
1263
|
assertWithinBase(targetBase, targetDir);
|
|
437
1264
|
ensureDir(targetDir);
|
|
438
|
-
copyFileOrDir(skill.path,
|
|
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 =
|
|
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 =
|
|
1296
|
+
const targetDir = path6.join(targetBase, nsName(plugin.pluginName, skill.id));
|
|
470
1297
|
assertWithinBase(targetBase, targetDir);
|
|
471
1298
|
ensureDir(targetDir);
|
|
472
|
-
copyFileOrDir(skill.path,
|
|
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 =
|
|
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 =
|
|
1330
|
+
const targetDir = path6.join(targetBase, nsName(plugin.pluginName, skill.id));
|
|
504
1331
|
assertWithinBase(targetBase, targetDir);
|
|
505
1332
|
ensureDir(targetDir);
|
|
506
|
-
copyFileOrDir(skill.path,
|
|
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:
|
|
1377
|
+
claude: path6.join(homeDir(), ".claude", "skills"),
|
|
551
1378
|
"claude-desktop": "",
|
|
552
|
-
codex:
|
|
553
|
-
cursor:
|
|
554
|
-
vscode:
|
|
555
|
-
qwen:
|
|
556
|
-
cline:
|
|
557
|
-
kiro:
|
|
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 =
|
|
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 =
|
|
575
|
-
const destPath =
|
|
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
|
-
|
|
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(
|
|
590
|
-
if (
|
|
1416
|
+
ensureDir(path6.dirname(dest));
|
|
1417
|
+
if (fs6.statSync(src).isDirectory()) {
|
|
591
1418
|
copyPluginDir(src, dest, [], errors);
|
|
592
1419
|
} else {
|
|
593
|
-
|
|
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
|
|
610
|
-
import
|
|
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
|
|
614
|
-
if (!
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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
|
|
803
|
-
|
|
804
|
-
|
|
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
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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
|
|
1482
|
+
return fs8.realpathSync(path8.resolve(p));
|
|
823
1483
|
} catch {
|
|
824
|
-
return
|
|
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 =
|
|
837
|
-
var claudeEntrySchema =
|
|
838
|
-
type:
|
|
839
|
-
url:
|
|
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:
|
|
842
|
-
args:
|
|
1501
|
+
command: z2.string().min(1).optional(),
|
|
1502
|
+
args: z2.array(z2.string()).optional(),
|
|
843
1503
|
env: stringMapSchema.optional(),
|
|
844
|
-
cwd:
|
|
845
|
-
disabled:
|
|
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
|
|
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(
|
|
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
|
|
988
|
-
import { z as
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
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:
|
|
995
|
-
args:
|
|
1659
|
+
command: z3.string().min(1).optional(),
|
|
1660
|
+
args: z3.array(z3.string()).optional(),
|
|
996
1661
|
env: stringMapSchema2.optional(),
|
|
997
|
-
cwd:
|
|
998
|
-
disabled:
|
|
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 =
|
|
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
|
|
1679
|
+
return path9.join(homeDir(), ".config", "Claude", "claude_desktop_config.json");
|
|
1015
1680
|
}
|
|
1016
1681
|
if (process.platform === "win32") {
|
|
1017
|
-
return
|
|
1682
|
+
return path9.join(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json");
|
|
1018
1683
|
}
|
|
1019
|
-
return
|
|
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(
|
|
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
|
|
1129
|
-
import
|
|
1793
|
+
import path10 from "node:path";
|
|
1794
|
+
import fs9 from "node:fs";
|
|
1130
1795
|
import { parse, stringify } from "@iarna/toml";
|
|
1131
|
-
import { z as
|
|
1132
|
-
var stringMapSchema3 =
|
|
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
|
-
},
|
|
1138
|
-
var codexEntrySchema =
|
|
1139
|
-
enabled:
|
|
1140
|
-
url:
|
|
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:
|
|
1144
|
-
args:
|
|
1808
|
+
command: z4.string().min(1).optional(),
|
|
1809
|
+
args: z4.array(z4.string()).optional(),
|
|
1145
1810
|
env: stringMapSchema3.optional(),
|
|
1146
|
-
cwd:
|
|
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
|
|
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(
|
|
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 || !
|
|
1831
|
+
if (!configPath || !fs9.existsSync(configPath)) {
|
|
1167
1832
|
return result;
|
|
1168
1833
|
}
|
|
1169
|
-
const existing =
|
|
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 =
|
|
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
|
-
|
|
1912
|
+
fs9.mkdirSync(path10.dirname(configPath), { recursive: true });
|
|
1248
1913
|
const tmpPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
|
|
1249
|
-
|
|
1250
|
-
|
|
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
|
|
1261
|
-
import { z as
|
|
1262
|
-
var stringMapSchema4 =
|
|
1263
|
-
var cursorEntrySchema =
|
|
1264
|
-
type:
|
|
1265
|
-
url:
|
|
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:
|
|
1268
|
-
args:
|
|
1932
|
+
command: z5.string().min(1).optional(),
|
|
1933
|
+
args: z5.array(z5.string()).optional(),
|
|
1269
1934
|
env: stringMapSchema4.optional(),
|
|
1270
|
-
cwd:
|
|
1271
|
-
disabled:
|
|
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
|
|
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(
|
|
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
|
|
1391
|
-
import
|
|
1392
|
-
import { z as
|
|
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 =
|
|
1395
|
-
var clineEntrySchema =
|
|
1396
|
-
type:
|
|
1397
|
-
transportType:
|
|
1398
|
-
url:
|
|
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:
|
|
1401
|
-
args:
|
|
2065
|
+
command: z6.string().min(1).optional(),
|
|
2066
|
+
args: z6.array(z6.string()).optional(),
|
|
1402
2067
|
env: stringMapSchema5.optional(),
|
|
1403
|
-
cwd:
|
|
1404
|
-
disabled:
|
|
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
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
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
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
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
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
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 (
|
|
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(
|
|
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 || !
|
|
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 (!
|
|
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
|
|
1553
|
-
import { z as
|
|
1554
|
-
var stringMapSchema6 =
|
|
1555
|
-
var kiroEntrySchema =
|
|
1556
|
-
type:
|
|
1557
|
-
url:
|
|
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:
|
|
1560
|
-
args:
|
|
2224
|
+
command: z7.string().min(1).optional(),
|
|
2225
|
+
args: z7.array(z7.string()).optional(),
|
|
1561
2226
|
env: stringMapSchema6.optional(),
|
|
1562
|
-
cwd:
|
|
1563
|
-
disabled:
|
|
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
|
|
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(
|
|
2339
|
+
projectSkillsToDir(path13.join(homeDir(), ".kiro", "skills"), skills, "flat");
|
|
1675
2340
|
}
|
|
1676
2341
|
}
|
|
1677
2342
|
|
|
1678
2343
|
// src/adapters/opencode.ts
|
|
1679
|
-
import
|
|
1680
|
-
import { z as
|
|
1681
|
-
var stringMapSchema7 =
|
|
1682
|
-
var openCodeEntrySchema =
|
|
1683
|
-
type:
|
|
1684
|
-
url:
|
|
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:
|
|
1687
|
-
args:
|
|
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:
|
|
1691
|
-
enabled:
|
|
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
|
|
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(
|
|
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
|
|
1818
|
-
import { z as
|
|
1819
|
-
var stringMapSchema8 =
|
|
1820
|
-
var vscodeEntrySchema =
|
|
1821
|
-
type:
|
|
1822
|
-
url:
|
|
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:
|
|
1825
|
-
args:
|
|
2489
|
+
command: z9.string().min(1).optional(),
|
|
2490
|
+
args: z9.array(z9.string()).optional(),
|
|
1826
2491
|
env: stringMapSchema8.optional(),
|
|
1827
|
-
cwd:
|
|
1828
|
-
disabled:
|
|
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
|
|
2500
|
+
return path15.join(homeDir(), ".config", "Code", "User", "mcp.json");
|
|
1836
2501
|
}
|
|
1837
2502
|
if (process.platform === "win32") {
|
|
1838
|
-
return
|
|
2503
|
+
return path15.join(process.env.APPDATA ?? "", "Code", "User", "mcp.json");
|
|
1839
2504
|
}
|
|
1840
|
-
return
|
|
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(
|
|
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
|
|
1954
|
-
import { z as
|
|
1955
|
-
var stringMapSchema9 =
|
|
1956
|
-
var qwenEntrySchema =
|
|
1957
|
-
httpUrl:
|
|
1958
|
-
url:
|
|
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:
|
|
1961
|
-
args:
|
|
2625
|
+
command: z10.string().min(1).optional(),
|
|
2626
|
+
args: z10.array(z10.string()).optional(),
|
|
1962
2627
|
env: stringMapSchema9.optional(),
|
|
1963
|
-
cwd:
|
|
1964
|
-
disabled:
|
|
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
|
|
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(
|
|
2753
|
+
projectSkillsToDir(path16.join(homeDir(), ".qwen", "skills"), skills, "flat");
|
|
2089
2754
|
}
|
|
2090
2755
|
}
|
|
2091
2756
|
|
|
2092
2757
|
// src/adapters/openclaw.ts
|
|
2093
|
-
import
|
|
2094
|
-
import
|
|
2095
|
-
import { z as
|
|
2096
|
-
var stringMapSchema10 =
|
|
2097
|
-
var openclawEntrySchema =
|
|
2098
|
-
url:
|
|
2099
|
-
transport:
|
|
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:
|
|
2102
|
-
args:
|
|
2766
|
+
command: z11.string().min(1).optional(),
|
|
2767
|
+
args: z11.array(z11.string()).optional(),
|
|
2103
2768
|
env: stringMapSchema10.optional(),
|
|
2104
|
-
cwd:
|
|
2105
|
-
workingDirectory:
|
|
2106
|
-
auth:
|
|
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 ??
|
|
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(
|
|
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(
|
|
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 (
|
|
2863
|
+
if (fs11.existsSync(configPath)) {
|
|
2199
2864
|
try {
|
|
2200
|
-
raw = JSON.parse(
|
|
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
|
|
2244
|
-
import
|
|
2908
|
+
import fs12 from "node:fs";
|
|
2909
|
+
import path18 from "node:path";
|
|
2245
2910
|
import YAML from "yaml";
|
|
2246
|
-
import { z as
|
|
2247
|
-
var stringMapSchema11 =
|
|
2248
|
-
var hermesEntrySchema =
|
|
2249
|
-
url:
|
|
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:
|
|
2252
|
-
args:
|
|
2916
|
+
command: z12.string().min(1).optional(),
|
|
2917
|
+
args: z12.array(z12.string()).optional(),
|
|
2253
2918
|
env: stringMapSchema11.optional(),
|
|
2254
|
-
auth:
|
|
2255
|
-
enabled:
|
|
2256
|
-
timeout:
|
|
2257
|
-
tools:
|
|
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
|
|
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(
|
|
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 || !
|
|
2942
|
+
if (!configPath || !fs12.existsSync(configPath)) {
|
|
2278
2943
|
return result;
|
|
2279
2944
|
}
|
|
2280
|
-
const existing =
|
|
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 =
|
|
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
|
-
|
|
3024
|
+
fs12.mkdirSync(path18.dirname(configPath), { recursive: true });
|
|
2360
3025
|
const tmpPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
|
|
2361
|
-
|
|
2362
|
-
|
|
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
|
|
2411
|
-
import
|
|
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 =
|
|
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
|
-
|
|
3128
|
+
fs14.mkdirSync(path20.dirname(lockPath), { recursive: true });
|
|
2422
3129
|
const deadline = Date.now() + 5000;
|
|
2423
3130
|
while (true) {
|
|
2424
3131
|
try {
|
|
2425
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|