@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
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PluginCache,
|
|
3
|
+
PluginManager,
|
|
4
|
+
discoverComponents,
|
|
5
|
+
hasManifest,
|
|
6
|
+
mutateConfig,
|
|
7
|
+
readManifest,
|
|
8
|
+
uninstallPlugin
|
|
9
|
+
} from "./cli-b0xrqsjc.js";
|
|
10
|
+
import {
|
|
11
|
+
loadConfig
|
|
12
|
+
} from "./cli-dy7x0zrg.js";
|
|
13
|
+
import {
|
|
14
|
+
ensureDir,
|
|
15
|
+
getMarketplaceCacheRoot
|
|
16
|
+
} from "./cli-28g7x9rn.js";
|
|
17
|
+
import {
|
|
18
|
+
__require
|
|
19
|
+
} from "./cli-s4gspqdf.js";
|
|
20
|
+
|
|
21
|
+
// src/core/marketplace.ts
|
|
22
|
+
import crypto from "node:crypto";
|
|
23
|
+
import fs from "node:fs";
|
|
24
|
+
import os from "node:os";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
import { execFileSync } from "node:child_process";
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
var CLAUDE_MANIFEST = ".claude-plugin/marketplace.json";
|
|
29
|
+
var CODEX_MANIFEST = ".agents/plugins/marketplace.json";
|
|
30
|
+
var SUPPORTED = new Set(["skills", "commands", "agents", "hooks", "mcpServers"]);
|
|
31
|
+
var UNSUPPORTED = new Set(["apps", "lspServers", "settings", "outputStyles", "dependencies"]);
|
|
32
|
+
var authorSchema = z.object({
|
|
33
|
+
name: z.string(),
|
|
34
|
+
email: z.string().optional(),
|
|
35
|
+
url: z.string().optional()
|
|
36
|
+
}).passthrough();
|
|
37
|
+
var claudeEntrySchema = z.object({
|
|
38
|
+
name: z.string().min(1),
|
|
39
|
+
displayName: z.string().optional(),
|
|
40
|
+
description: z.string().optional(),
|
|
41
|
+
version: z.string().optional(),
|
|
42
|
+
author: authorSchema.optional(),
|
|
43
|
+
homepage: z.string().optional(),
|
|
44
|
+
category: z.string().optional(),
|
|
45
|
+
tags: z.array(z.string()).optional(),
|
|
46
|
+
keywords: z.array(z.string()).optional(),
|
|
47
|
+
source: z.unknown(),
|
|
48
|
+
skills: z.unknown().optional(),
|
|
49
|
+
commands: z.unknown().optional(),
|
|
50
|
+
agents: z.unknown().optional(),
|
|
51
|
+
hooks: z.unknown().optional(),
|
|
52
|
+
mcpServers: z.unknown().optional(),
|
|
53
|
+
lspServers: z.unknown().optional(),
|
|
54
|
+
apps: z.unknown().optional(),
|
|
55
|
+
settings: z.unknown().optional(),
|
|
56
|
+
outputStyles: z.unknown().optional(),
|
|
57
|
+
dependencies: z.unknown().optional()
|
|
58
|
+
}).passthrough();
|
|
59
|
+
var claudeCatalogSchema = z.object({
|
|
60
|
+
name: z.string().min(1),
|
|
61
|
+
owner: authorSchema,
|
|
62
|
+
plugins: z.array(claudeEntrySchema)
|
|
63
|
+
}).passthrough();
|
|
64
|
+
var codexEntrySchema = z.object({
|
|
65
|
+
name: z.string().min(1),
|
|
66
|
+
source: z.unknown(),
|
|
67
|
+
category: z.string().optional(),
|
|
68
|
+
policy: z.object({
|
|
69
|
+
installation: z.string().optional(),
|
|
70
|
+
authentication: z.string().optional()
|
|
71
|
+
}).passthrough().optional()
|
|
72
|
+
}).passthrough();
|
|
73
|
+
var codexCatalogSchema = z.object({
|
|
74
|
+
name: z.string().min(1),
|
|
75
|
+
interface: z.object({ displayName: z.string().optional() }).passthrough().optional(),
|
|
76
|
+
plugins: z.array(codexEntrySchema)
|
|
77
|
+
}).passthrough();
|
|
78
|
+
function sourceKey(source) {
|
|
79
|
+
return crypto.createHash("sha256").update(source).digest("hex").slice(0, 20);
|
|
80
|
+
}
|
|
81
|
+
function snapshotRoot(source, revision) {
|
|
82
|
+
return path.join(getMarketplaceCacheRoot(), sourceKey(source), revision);
|
|
83
|
+
}
|
|
84
|
+
function copyDirectory(source, destination) {
|
|
85
|
+
fs.cpSync(source, destination, { recursive: true, dereference: false });
|
|
86
|
+
}
|
|
87
|
+
function hashPath(target) {
|
|
88
|
+
const hash = crypto.createHash("sha256");
|
|
89
|
+
const visit = (current, relative) => {
|
|
90
|
+
const stat = fs.lstatSync(current);
|
|
91
|
+
if (stat.isSymbolicLink()) {
|
|
92
|
+
hash.update(relative).update(fs.readlinkSync(current));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (stat.isDirectory()) {
|
|
96
|
+
for (const name of fs.readdirSync(current).sort()) {
|
|
97
|
+
if (name === ".git" || name === ".DS_Store")
|
|
98
|
+
continue;
|
|
99
|
+
visit(path.join(current, name), path.join(relative, name));
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
hash.update(relative).update(fs.readFileSync(current));
|
|
104
|
+
};
|
|
105
|
+
visit(target, "");
|
|
106
|
+
return hash.digest("hex");
|
|
107
|
+
}
|
|
108
|
+
function parseGitSource(source) {
|
|
109
|
+
const hashIndex = source.lastIndexOf("#");
|
|
110
|
+
const withoutRef = hashIndex > source.indexOf("//") ? source.slice(0, hashIndex) : source;
|
|
111
|
+
const ref = hashIndex > source.indexOf("//") ? source.slice(hashIndex + 1) : "HEAD";
|
|
112
|
+
if (/^[\w.-]+\/[\w.-]+$/.test(withoutRef)) {
|
|
113
|
+
return { remote: `https://github.com/${withoutRef}.git`, ref };
|
|
114
|
+
}
|
|
115
|
+
if (/^(?:https?:\/\/|ssh:\/\/|git@).+/.test(withoutRef) && !withoutRef.endsWith(".json")) {
|
|
116
|
+
return { remote: withoutRef, ref };
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
function marketplaceSourceType(source) {
|
|
121
|
+
if (/^[\w.-]+\/[\w.-]+(?:#.+)?$/.test(source))
|
|
122
|
+
return "github";
|
|
123
|
+
if (/^https?:\/\/.+\.json(?:[?#].*)?$/.test(source))
|
|
124
|
+
return "hosted-json";
|
|
125
|
+
if (/^(?:https?:\/\/|ssh:\/\/|git@)/.test(source))
|
|
126
|
+
return "git";
|
|
127
|
+
return "local";
|
|
128
|
+
}
|
|
129
|
+
async function acquireSource(source) {
|
|
130
|
+
ensureDir(getMarketplaceCacheRoot());
|
|
131
|
+
const git = parseGitSource(source);
|
|
132
|
+
if (git) {
|
|
133
|
+
const revision2 = execFileSync("git", ["ls-remote", git.remote, git.ref], { encoding: "utf8", timeout: 30000 }).split("\t")[0]?.trim();
|
|
134
|
+
if (!revision2)
|
|
135
|
+
throw new Error(`Could not resolve ${git.ref} for ${source}`);
|
|
136
|
+
const destination2 = snapshotRoot(source, revision2);
|
|
137
|
+
if (fs.existsSync(destination2))
|
|
138
|
+
return { root: destination2, revision: revision2 };
|
|
139
|
+
ensureDir(path.dirname(destination2));
|
|
140
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), "mcpx-marketplace-"));
|
|
141
|
+
try {
|
|
142
|
+
execFileSync("git", ["init"], { cwd: temp, stdio: "pipe", timeout: 1e4 });
|
|
143
|
+
execFileSync("git", ["remote", "add", "origin", git.remote], { cwd: temp, stdio: "pipe", timeout: 1e4 });
|
|
144
|
+
execFileSync("git", ["fetch", "--depth", "1", "origin", revision2], { cwd: temp, stdio: "pipe", timeout: 60000 });
|
|
145
|
+
execFileSync("git", ["checkout", "--detach", "FETCH_HEAD"], { cwd: temp, stdio: "pipe", timeout: 1e4 });
|
|
146
|
+
fs.rmSync(path.join(temp, ".git"), { recursive: true, force: true });
|
|
147
|
+
fs.renameSync(temp, destination2);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
fs.rmSync(temp, { recursive: true, force: true });
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
return { root: destination2, revision: revision2 };
|
|
153
|
+
}
|
|
154
|
+
if (/^https?:\/\//.test(source)) {
|
|
155
|
+
const response = await fetch(source);
|
|
156
|
+
if (!response.ok)
|
|
157
|
+
throw new Error(`Marketplace request failed (${response.status})`);
|
|
158
|
+
const body = await response.text();
|
|
159
|
+
const revision2 = crypto.createHash("sha256").update(body).digest("hex");
|
|
160
|
+
const destination2 = snapshotRoot(source, revision2);
|
|
161
|
+
if (!fs.existsSync(destination2)) {
|
|
162
|
+
ensureDir(destination2);
|
|
163
|
+
fs.writeFileSync(path.join(destination2, "marketplace.json"), body, { mode: 384 });
|
|
164
|
+
}
|
|
165
|
+
return { root: destination2, revision: revision2 };
|
|
166
|
+
}
|
|
167
|
+
const resolved = path.resolve(source);
|
|
168
|
+
if (!fs.existsSync(resolved))
|
|
169
|
+
throw new Error(`Marketplace path not found: ${resolved}`);
|
|
170
|
+
const sourceRoot = fs.statSync(resolved).isDirectory() ? resolved : path.dirname(resolved);
|
|
171
|
+
const revision = hashPath(resolved);
|
|
172
|
+
const destination = snapshotRoot(source, revision);
|
|
173
|
+
if (!fs.existsSync(destination)) {
|
|
174
|
+
ensureDir(path.dirname(destination));
|
|
175
|
+
copyDirectory(sourceRoot, destination);
|
|
176
|
+
}
|
|
177
|
+
return { root: destination, revision };
|
|
178
|
+
}
|
|
179
|
+
function locateManifest(root, requested) {
|
|
180
|
+
const candidates = requested ? [requested] : [CLAUDE_MANIFEST, CODEX_MANIFEST, "marketplace.json"];
|
|
181
|
+
for (const candidate of candidates) {
|
|
182
|
+
const resolved = path.resolve(root, candidate);
|
|
183
|
+
if ((resolved === root || resolved.startsWith(path.resolve(root) + path.sep)) && fs.existsSync(resolved)) {
|
|
184
|
+
return resolved;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
throw new Error(`Marketplace manifest not found. Expected ${candidates.join(" or ")}.`);
|
|
188
|
+
}
|
|
189
|
+
function loadCatalog(root, requested, expectedFormat) {
|
|
190
|
+
const manifest = locateManifest(root, requested);
|
|
191
|
+
const raw = JSON.parse(fs.readFileSync(manifest, "utf8"));
|
|
192
|
+
const format = expectedFormat ?? (manifest.includes(`${path.sep}.agents${path.sep}`) || raw.interface ? "codex" : "claude");
|
|
193
|
+
if (format === "codex") {
|
|
194
|
+
const parsed2 = codexCatalogSchema.parse(raw);
|
|
195
|
+
return {
|
|
196
|
+
name: parsed2.name,
|
|
197
|
+
displayName: parsed2.interface?.displayName ?? parsed2.name,
|
|
198
|
+
format,
|
|
199
|
+
entries: parsed2.plugins,
|
|
200
|
+
root,
|
|
201
|
+
manifestPath: path.relative(root, manifest)
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const parsed = claudeCatalogSchema.parse(raw);
|
|
205
|
+
return { name: parsed.name, displayName: parsed.name, format, entries: parsed.plugins, root, manifestPath: path.relative(root, manifest) };
|
|
206
|
+
}
|
|
207
|
+
function marketplaceSnapshot(marketplace) {
|
|
208
|
+
if (!marketplace.resolvedRevision)
|
|
209
|
+
throw new Error(`Marketplace ${marketplace.name} has not been downloaded yet`);
|
|
210
|
+
const root = snapshotRoot(marketplace.source, marketplace.resolvedRevision);
|
|
211
|
+
if (!fs.existsSync(root))
|
|
212
|
+
throw new Error(`Cached marketplace snapshot is missing for ${marketplace.name}`);
|
|
213
|
+
return loadCatalog(root, marketplace.manifestPath, marketplace.format);
|
|
214
|
+
}
|
|
215
|
+
function resolveContained(root, relative) {
|
|
216
|
+
const resolved = path.resolve(root, relative);
|
|
217
|
+
const realRoot = fs.realpathSync(root);
|
|
218
|
+
if (!fs.existsSync(resolved))
|
|
219
|
+
throw new Error(`Marketplace plugin path not found: ${relative}`);
|
|
220
|
+
const realResolved = fs.realpathSync(resolved);
|
|
221
|
+
if (realResolved !== realRoot && !realResolved.startsWith(realRoot + path.sep)) {
|
|
222
|
+
throw new Error(`Marketplace plugin path escapes its snapshot: ${relative}`);
|
|
223
|
+
}
|
|
224
|
+
return realResolved;
|
|
225
|
+
}
|
|
226
|
+
function resolveMarketplacePluginSource(catalog, entry) {
|
|
227
|
+
const value = entry.source;
|
|
228
|
+
if (typeof value === "string") {
|
|
229
|
+
if (!value.startsWith("./"))
|
|
230
|
+
throw new Error(`Unsupported marketplace plugin source: ${value}`);
|
|
231
|
+
return { type: "local", original: resolveContained(catalog.root, value) };
|
|
232
|
+
}
|
|
233
|
+
const source = z.object({
|
|
234
|
+
source: z.string(),
|
|
235
|
+
repo: z.string().optional(),
|
|
236
|
+
url: z.string().optional(),
|
|
237
|
+
path: z.string().optional(),
|
|
238
|
+
ref: z.string().optional(),
|
|
239
|
+
sha: z.string().optional(),
|
|
240
|
+
package: z.string().optional()
|
|
241
|
+
}).passthrough().parse(value);
|
|
242
|
+
if (source.source === "local") {
|
|
243
|
+
if (!source.path)
|
|
244
|
+
throw new Error("Local marketplace plugin source is missing path");
|
|
245
|
+
return { type: "local", original: resolveContained(catalog.root, source.path) };
|
|
246
|
+
}
|
|
247
|
+
if (source.source === "github") {
|
|
248
|
+
if (!source.repo)
|
|
249
|
+
throw new Error("GitHub plugin source is missing repo");
|
|
250
|
+
return { type: "github", original: source.repo, ref: source.ref, resolvedSha: source.sha };
|
|
251
|
+
}
|
|
252
|
+
if (source.source === "url") {
|
|
253
|
+
if (!source.url)
|
|
254
|
+
throw new Error("Git plugin source is missing URL");
|
|
255
|
+
return { type: "git", original: source.url, ref: source.ref, resolvedSha: source.sha };
|
|
256
|
+
}
|
|
257
|
+
if (source.source === "git-subdir") {
|
|
258
|
+
if (!source.url || !source.path)
|
|
259
|
+
throw new Error("git-subdir plugin source is missing URL or path");
|
|
260
|
+
return { type: "git-subdir", original: source.url, path: source.path, ref: source.ref, resolvedSha: source.sha };
|
|
261
|
+
}
|
|
262
|
+
if (source.source === "npm") {
|
|
263
|
+
return { type: "npm", original: `npm:${source.package ?? entry.name}` };
|
|
264
|
+
}
|
|
265
|
+
throw new Error(`Unsupported marketplace plugin source type: ${source.source}`);
|
|
266
|
+
}
|
|
267
|
+
function findPluginRoot(root) {
|
|
268
|
+
if (hasManifest(root))
|
|
269
|
+
return root;
|
|
270
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
271
|
+
if (entry.isDirectory() && hasManifest(path.join(root, entry.name)))
|
|
272
|
+
return path.join(root, entry.name);
|
|
273
|
+
}
|
|
274
|
+
return root;
|
|
275
|
+
}
|
|
276
|
+
function capabilities(root, entry) {
|
|
277
|
+
const supported = new Set;
|
|
278
|
+
const unsupported = new Set;
|
|
279
|
+
for (const key of [...SUPPORTED, ...UNSUPPORTED]) {
|
|
280
|
+
if (entry[key] !== undefined)
|
|
281
|
+
(SUPPORTED.has(key) ? supported : unsupported).add(key);
|
|
282
|
+
}
|
|
283
|
+
const sourceObject = typeof entry.source === "object" && entry.source ? entry.source : null;
|
|
284
|
+
if (sourceObject?.source === "npm")
|
|
285
|
+
unsupported.add("npm");
|
|
286
|
+
if (root) {
|
|
287
|
+
const manifest = readManifest(root);
|
|
288
|
+
for (const key of manifest?.declaredCapabilities ?? [])
|
|
289
|
+
(SUPPORTED.has(key) ? supported : unsupported).add(key);
|
|
290
|
+
const discovered = discoverComponents(root);
|
|
291
|
+
if (discovered.skills.length)
|
|
292
|
+
supported.add("skills");
|
|
293
|
+
if (discovered.commands.length)
|
|
294
|
+
supported.add("commands");
|
|
295
|
+
if (discovered.agents.length)
|
|
296
|
+
supported.add("agents");
|
|
297
|
+
if (discovered.hooks.length)
|
|
298
|
+
supported.add("hooks");
|
|
299
|
+
if (discovered.mcpServers.length)
|
|
300
|
+
supported.add("mcpServers");
|
|
301
|
+
}
|
|
302
|
+
return { supported: [...supported].sort(), unsupported: [...unsupported].sort() };
|
|
303
|
+
}
|
|
304
|
+
function relativePluginRoot(catalog, entry) {
|
|
305
|
+
try {
|
|
306
|
+
const source = resolveMarketplacePluginSource(catalog, entry);
|
|
307
|
+
return source.type === "local" ? findPluginRoot(source.original) : null;
|
|
308
|
+
} catch {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function listingFromEntry(config, marketplace, catalog, entry) {
|
|
313
|
+
const root = relativePluginRoot(catalog, entry);
|
|
314
|
+
const manifest = root ? readManifest(root) : null;
|
|
315
|
+
const caps = capabilities(root, entry);
|
|
316
|
+
const raw = entry;
|
|
317
|
+
const installationAvailable = raw.policy?.installation === undefined || raw.policy.installation === "AVAILABLE";
|
|
318
|
+
return {
|
|
319
|
+
id: `${entry.name}@${marketplace.name}`,
|
|
320
|
+
name: entry.name,
|
|
321
|
+
displayName: raw.displayName ?? manifest?.displayName ?? entry.name,
|
|
322
|
+
marketplace: marketplace.name,
|
|
323
|
+
description: raw.description ?? manifest?.description,
|
|
324
|
+
author: raw.author ?? manifest?.author,
|
|
325
|
+
homepage: raw.homepage ?? manifest?.homepage,
|
|
326
|
+
version: raw.version ?? manifest?.version,
|
|
327
|
+
category: raw.category ?? manifest?.category,
|
|
328
|
+
tags: [...raw.tags ?? [], ...raw.keywords ?? manifest?.keywords ?? []],
|
|
329
|
+
source: entry.source,
|
|
330
|
+
supportedCapabilities: caps.supported,
|
|
331
|
+
unsupportedCapabilities: caps.unsupported,
|
|
332
|
+
compatible: installationAvailable && (caps.supported.length > 0 || !root && caps.unsupported.length === 0),
|
|
333
|
+
installed: Object.values(config.plugins ?? {}).some((plugin) => plugin.marketplace?.name === marketplace.name && plugin.marketplace.pluginName === entry.name),
|
|
334
|
+
authentication: raw.policy?.authentication
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function parseQualified(id) {
|
|
338
|
+
const at = id.lastIndexOf("@");
|
|
339
|
+
if (at <= 0 || at === id.length - 1)
|
|
340
|
+
throw new Error(`Expected plugin@marketplace, received "${id}"`);
|
|
341
|
+
return { pluginName: id.slice(0, at), marketplaceName: id.slice(at + 1) };
|
|
342
|
+
}
|
|
343
|
+
async function ensureReady(marketplace) {
|
|
344
|
+
if (marketplace.resolvedRevision)
|
|
345
|
+
return marketplace;
|
|
346
|
+
if (process.env.MCPX_NO_UPDATE === "1")
|
|
347
|
+
return marketplace;
|
|
348
|
+
try {
|
|
349
|
+
return await refreshMarketplace(marketplace.name);
|
|
350
|
+
} catch {
|
|
351
|
+
return loadConfig().marketplaces?.[marketplace.name] ?? marketplace;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async function listMarketplaces() {
|
|
355
|
+
return Object.values(loadConfig().marketplaces ?? {}).sort((a, b) => Number(b.builtIn) - Number(a.builtIn) || a.displayName.localeCompare(b.displayName));
|
|
356
|
+
}
|
|
357
|
+
async function addMarketplace(source, manifestPath) {
|
|
358
|
+
const acquired = await acquireSource(source);
|
|
359
|
+
const catalog = loadCatalog(acquired.root, manifestPath);
|
|
360
|
+
const config = loadConfig();
|
|
361
|
+
const existing = config.marketplaces?.[catalog.name];
|
|
362
|
+
if (existing?.builtIn)
|
|
363
|
+
throw new Error(`Built-in marketplace ${catalog.name} cannot be replaced`);
|
|
364
|
+
const now = new Date().toISOString();
|
|
365
|
+
const marketplace = {
|
|
366
|
+
name: catalog.name,
|
|
367
|
+
displayName: catalog.displayName,
|
|
368
|
+
source,
|
|
369
|
+
sourceType: marketplaceSourceType(source),
|
|
370
|
+
manifestPath: catalog.manifestPath,
|
|
371
|
+
format: catalog.format,
|
|
372
|
+
builtIn: false,
|
|
373
|
+
autoUpdate: false,
|
|
374
|
+
addedAt: existing?.addedAt ?? now,
|
|
375
|
+
lastCheckedAt: now,
|
|
376
|
+
lastUpdatedAt: now,
|
|
377
|
+
resolvedRevision: acquired.revision,
|
|
378
|
+
status: "ready"
|
|
379
|
+
};
|
|
380
|
+
await mutateConfig((fresh) => {
|
|
381
|
+
if (!fresh.marketplaces)
|
|
382
|
+
fresh.marketplaces = {};
|
|
383
|
+
if (fresh.marketplaces[catalog.name]?.builtIn)
|
|
384
|
+
throw new Error(`Built-in marketplace ${catalog.name} cannot be replaced`);
|
|
385
|
+
fresh.marketplaces[catalog.name] = marketplace;
|
|
386
|
+
});
|
|
387
|
+
return marketplace;
|
|
388
|
+
}
|
|
389
|
+
async function refreshMarketplace(name) {
|
|
390
|
+
const current = loadConfig().marketplaces?.[name];
|
|
391
|
+
if (!current)
|
|
392
|
+
throw new Error(`Marketplace ${name} not found`);
|
|
393
|
+
const checkedAt = new Date().toISOString();
|
|
394
|
+
try {
|
|
395
|
+
const acquired = await acquireSource(current.source);
|
|
396
|
+
const catalog = loadCatalog(acquired.root, current.manifestPath, current.format);
|
|
397
|
+
if (catalog.name !== current.name)
|
|
398
|
+
throw new Error(`Marketplace name changed from ${current.name} to ${catalog.name}`);
|
|
399
|
+
let updated;
|
|
400
|
+
await mutateConfig((config) => {
|
|
401
|
+
const fresh = config.marketplaces?.[name];
|
|
402
|
+
if (!fresh)
|
|
403
|
+
throw new Error(`Marketplace ${name} not found`);
|
|
404
|
+
fresh.displayName = catalog.displayName;
|
|
405
|
+
fresh.manifestPath = catalog.manifestPath;
|
|
406
|
+
fresh.format = catalog.format;
|
|
407
|
+
fresh.lastCheckedAt = checkedAt;
|
|
408
|
+
fresh.lastUpdatedAt = acquired.revision === fresh.resolvedRevision ? fresh.lastUpdatedAt : checkedAt;
|
|
409
|
+
fresh.resolvedRevision = acquired.revision;
|
|
410
|
+
fresh.status = "ready";
|
|
411
|
+
delete fresh.error;
|
|
412
|
+
updated = { ...fresh };
|
|
413
|
+
});
|
|
414
|
+
return updated;
|
|
415
|
+
} catch (error) {
|
|
416
|
+
await mutateConfig((config) => {
|
|
417
|
+
const fresh = config.marketplaces?.[name];
|
|
418
|
+
if (!fresh)
|
|
419
|
+
return;
|
|
420
|
+
fresh.lastCheckedAt = checkedAt;
|
|
421
|
+
fresh.status = fresh.resolvedRevision ? "stale" : "error";
|
|
422
|
+
fresh.error = error instanceof Error ? error.message : String(error);
|
|
423
|
+
});
|
|
424
|
+
throw error;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async function setMarketplaceAutoUpdate(name, enabled) {
|
|
428
|
+
let updated;
|
|
429
|
+
await mutateConfig((config) => {
|
|
430
|
+
const marketplace = config.marketplaces?.[name];
|
|
431
|
+
if (!marketplace)
|
|
432
|
+
throw new Error(`Marketplace ${name} not found`);
|
|
433
|
+
marketplace.autoUpdate = enabled;
|
|
434
|
+
updated = { ...marketplace };
|
|
435
|
+
});
|
|
436
|
+
return updated;
|
|
437
|
+
}
|
|
438
|
+
async function removeMarketplace(name) {
|
|
439
|
+
const config = loadConfig();
|
|
440
|
+
const marketplace = config.marketplaces?.[name];
|
|
441
|
+
if (!marketplace)
|
|
442
|
+
throw new Error(`Marketplace ${name} not found`);
|
|
443
|
+
if (marketplace.builtIn)
|
|
444
|
+
throw new Error(`Built-in marketplace ${name} cannot be removed`);
|
|
445
|
+
const installed = Object.values(config.plugins ?? {}).filter((plugin) => plugin.marketplace?.name === name);
|
|
446
|
+
for (const plugin of installed)
|
|
447
|
+
await uninstallPlugin(plugin.id, true);
|
|
448
|
+
await mutateConfig((fresh) => {
|
|
449
|
+
delete fresh.marketplaces?.[name];
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
async function listMarketplacePlugins(query) {
|
|
453
|
+
const initial = await listMarketplaces();
|
|
454
|
+
for (const marketplace of initial)
|
|
455
|
+
await ensureReady(marketplace);
|
|
456
|
+
const config = loadConfig();
|
|
457
|
+
const needle = query?.trim().toLowerCase();
|
|
458
|
+
const listings = [];
|
|
459
|
+
for (const marketplace of Object.values(config.marketplaces ?? {})) {
|
|
460
|
+
if (!marketplace.resolvedRevision)
|
|
461
|
+
continue;
|
|
462
|
+
try {
|
|
463
|
+
const catalog = marketplaceSnapshot(marketplace);
|
|
464
|
+
for (const entry of catalog.entries) {
|
|
465
|
+
const listing = listingFromEntry(config, marketplace, catalog, entry);
|
|
466
|
+
if (!needle || [listing.name, listing.displayName, listing.description, listing.category, ...listing.tags].some((value) => value?.toLowerCase().includes(needle))) {
|
|
467
|
+
listings.push(listing);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
} catch {}
|
|
471
|
+
}
|
|
472
|
+
return listings.sort((a, b) => a.displayName.localeCompare(b.displayName) || a.marketplace.localeCompare(b.marketplace));
|
|
473
|
+
}
|
|
474
|
+
async function inspectMarketplacePlugin(id) {
|
|
475
|
+
const { pluginName, marketplaceName } = parseQualified(id);
|
|
476
|
+
let marketplace = loadConfig().marketplaces?.[marketplaceName];
|
|
477
|
+
if (!marketplace)
|
|
478
|
+
throw new Error(`Marketplace ${marketplaceName} not found`);
|
|
479
|
+
marketplace = await ensureReady(marketplace);
|
|
480
|
+
const catalog = marketplaceSnapshot(marketplace);
|
|
481
|
+
const entry = catalog.entries.find((candidate) => candidate.name === pluginName);
|
|
482
|
+
if (!entry)
|
|
483
|
+
throw new Error(`Plugin ${pluginName} not found in ${marketplaceName}`);
|
|
484
|
+
const source = resolveMarketplacePluginSource(catalog, entry);
|
|
485
|
+
if (source.type === "npm") {
|
|
486
|
+
const listing = listingFromEntry(loadConfig(), marketplace, catalog, entry);
|
|
487
|
+
return { ...listing, compatible: false, unsupportedCapabilities: [...new Set([...listing.unsupportedCapabilities, "npm"])], manifest: null, discovered: { skills: [], commands: [], agents: [], hooks: [], mcpServers: [] } };
|
|
488
|
+
}
|
|
489
|
+
const cached = await new PluginCache().fetch(source, pluginName);
|
|
490
|
+
const root = findPluginRoot(cached.root);
|
|
491
|
+
const manifest = readManifest(root);
|
|
492
|
+
const discovered = discoverComponents(root);
|
|
493
|
+
const base = listingFromEntry(loadConfig(), marketplace, catalog, entry);
|
|
494
|
+
const caps = capabilities(root, entry);
|
|
495
|
+
return {
|
|
496
|
+
...base,
|
|
497
|
+
description: base.description ?? manifest?.description,
|
|
498
|
+
author: base.author ?? manifest?.author,
|
|
499
|
+
homepage: base.homepage ?? manifest?.homepage,
|
|
500
|
+
version: base.version ?? manifest?.version,
|
|
501
|
+
supportedCapabilities: caps.supported,
|
|
502
|
+
unsupportedCapabilities: caps.unsupported,
|
|
503
|
+
compatible: caps.supported.length > 0,
|
|
504
|
+
manifest,
|
|
505
|
+
discovered,
|
|
506
|
+
resolvedSource: source.original,
|
|
507
|
+
sourceFingerprint: `${marketplace.resolvedRevision}:${cached.sha}:${JSON.stringify(entry.source)}`
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
async function installMarketplacePlugin(id) {
|
|
511
|
+
const detail = await inspectMarketplacePlugin(id);
|
|
512
|
+
if (!detail.compatible || !detail.sourceFingerprint)
|
|
513
|
+
throw new Error(`Plugin ${id} has no mcpx-compatible capabilities`);
|
|
514
|
+
const { marketplaceName, pluginName } = parseQualified(id);
|
|
515
|
+
const marketplace = loadConfig().marketplaces?.[marketplaceName];
|
|
516
|
+
if (!marketplace)
|
|
517
|
+
throw new Error(`Marketplace ${marketplaceName} not found`);
|
|
518
|
+
const catalog = marketplaceSnapshot(marketplace);
|
|
519
|
+
const entry = catalog.entries.find((candidate) => candidate.name === pluginName);
|
|
520
|
+
const source = resolveMarketplacePluginSource(catalog, entry);
|
|
521
|
+
const approvals = {};
|
|
522
|
+
for (const capability of detail.supportedCapabilities) {
|
|
523
|
+
if (SUPPORTED.has(capability))
|
|
524
|
+
approvals[capability] = true;
|
|
525
|
+
}
|
|
526
|
+
const manager = new PluginManager;
|
|
527
|
+
return manager.installResolvedPlugin(source, {
|
|
528
|
+
name: pluginName,
|
|
529
|
+
approvals,
|
|
530
|
+
marketplace: { name: marketplaceName, pluginName, sourceFingerprint: detail.sourceFingerprint }
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
async function updateMarketplaceInstalledPlugin(pluginId, sync = true) {
|
|
534
|
+
const plugin = loadConfig().plugins?.[pluginId];
|
|
535
|
+
if (!plugin?.marketplace)
|
|
536
|
+
throw new Error(`Plugin ${pluginId} is not marketplace-managed`);
|
|
537
|
+
const qualified = `${plugin.marketplace.pluginName}@${plugin.marketplace.name}`;
|
|
538
|
+
try {
|
|
539
|
+
const detail = await inspectMarketplacePlugin(qualified);
|
|
540
|
+
if (!detail.compatible || !detail.sourceFingerprint)
|
|
541
|
+
throw new Error(`Plugin ${qualified} has no mcpx-compatible capabilities`);
|
|
542
|
+
if (detail.sourceFingerprint === plugin.marketplace.sourceFingerprint)
|
|
543
|
+
return plugin;
|
|
544
|
+
const marketplace = loadConfig().marketplaces?.[plugin.marketplace.name];
|
|
545
|
+
if (!marketplace)
|
|
546
|
+
throw new Error(`Marketplace ${plugin.marketplace.name} not found`);
|
|
547
|
+
const catalog = marketplaceSnapshot(marketplace);
|
|
548
|
+
const entry = catalog.entries.find((candidate) => candidate.name === plugin.marketplace.pluginName);
|
|
549
|
+
if (!entry)
|
|
550
|
+
throw new Error(`Plugin ${qualified} is no longer listed`);
|
|
551
|
+
const source = resolveMarketplacePluginSource(catalog, entry);
|
|
552
|
+
return await new PluginManager().updateResolvedPlugin(pluginId, source, detail.sourceFingerprint, sync);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
await mutateConfig((config) => {
|
|
555
|
+
const current = config.plugins?.[pluginId];
|
|
556
|
+
if (current)
|
|
557
|
+
current.updateError = error instanceof Error ? error.message : String(error);
|
|
558
|
+
});
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
async function refreshMarketplaceWithPlugins(name) {
|
|
563
|
+
const marketplace = await refreshMarketplace(name);
|
|
564
|
+
const plugins = Object.values(loadConfig().plugins ?? {}).filter((plugin) => plugin.marketplace?.name === name);
|
|
565
|
+
const updated = [];
|
|
566
|
+
const errors = [];
|
|
567
|
+
for (const plugin of plugins) {
|
|
568
|
+
try {
|
|
569
|
+
const before = plugin.marketplace.sourceFingerprint;
|
|
570
|
+
const result = await updateMarketplaceInstalledPlugin(plugin.id, false);
|
|
571
|
+
if (result.marketplace?.sourceFingerprint !== before)
|
|
572
|
+
updated.push(result.id);
|
|
573
|
+
} catch (error) {
|
|
574
|
+
errors.push(`${plugin.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (updated.length > 0) {
|
|
578
|
+
const { pluginSync } = await import("./plugin-manager-19q55n69.js");
|
|
579
|
+
await pluginSync();
|
|
580
|
+
}
|
|
581
|
+
return { marketplace, updated, errors };
|
|
582
|
+
}
|
|
583
|
+
async function refreshAutoUpdateMarketplaces() {
|
|
584
|
+
const enabled = Object.values(loadConfig().marketplaces ?? {}).filter((marketplace) => marketplace.autoUpdate);
|
|
585
|
+
const checked = [];
|
|
586
|
+
const errors = [];
|
|
587
|
+
for (const marketplace of enabled) {
|
|
588
|
+
try {
|
|
589
|
+
await refreshMarketplaceWithPlugins(marketplace.name);
|
|
590
|
+
checked.push(marketplace.name);
|
|
591
|
+
} catch (error) {
|
|
592
|
+
errors.push(`${marketplace.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return { checked, errors };
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
export { resolveMarketplacePluginSource, listMarketplaces, addMarketplace, refreshMarketplace, setMarketplaceAutoUpdate, removeMarketplace, listMarketplacePlugins, inspectMarketplacePlugin, installMarketplacePlugin, updateMarketplaceInstalledPlugin, refreshMarketplaceWithPlugins, refreshAutoUpdateMarketplaces };
|