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