@kwonye/mcpx 0.1.104 → 0.1.106
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/adapters/codex.d.ts +2 -0
- package/dist/adapters/index.d.ts +2 -1
- package/dist/adapters/json.d.ts +26 -0
- package/dist/adapters/pi.d.ts +9 -0
- package/dist/chunk-0xt563m1.js +126 -0
- package/dist/{chunk-s9v1epnk.js → chunk-1mrkz29t.js} +71 -56
- package/dist/{chunk-31brdz5s.js → chunk-1v66g9d6.js} +4 -3
- package/dist/chunk-1yq3yb66.js +126 -0
- package/dist/chunk-27mq4agf.js +33 -0
- package/dist/{chunk-941dqsx2.js → chunk-4aafp444.js} +8175 -8066
- package/dist/chunk-4xym942z.js +36 -0
- package/dist/{chunk-tnhekcpv.js → chunk-56z18csx.js} +1 -1
- package/dist/{chunk-bnbz55g6.js → chunk-65kshh9w.js} +1 -1
- package/dist/{chunk-xydajvzg.js → chunk-7ky3rsw9.js} +1 -1
- package/dist/chunk-7rq88hb3.js +563 -0
- package/dist/chunk-8v13hqrp.js +825 -0
- package/dist/chunk-a2gen8bn.js +730 -0
- package/dist/{chunk-wq4zd3t7.js → chunk-brxwa30j.js} +130 -111
- package/dist/chunk-chaj8a8b.js +45 -0
- package/dist/chunk-e96643mj.js +26 -0
- package/dist/{chunk-02r7k4ax.js → chunk-ew06e41w.js} +12 -6
- package/dist/chunk-ezk8txcy.js +23 -0
- package/dist/{chunk-cqenqnxk.js → chunk-grkz21sq.js} +35 -3
- package/dist/{chunk-12vgqm89.js → chunk-gvjxpk98.js} +1 -1
- package/dist/chunk-h9kp47rp.js +18 -0
- package/dist/chunk-hahs24dv.js +127 -0
- package/dist/{chunk-z34m83m4.js → chunk-jv0s0cp6.js} +5 -3
- package/dist/{chunk-enzmq965.js → chunk-n0462d52.js} +1 -1
- package/dist/chunk-nh6hhv29.js +138 -0
- package/dist/chunk-p7yzyt87.js +19426 -0
- package/dist/chunk-q86c5s6r.js +7 -0
- package/dist/chunk-qf8bj9y7.js +254 -0
- package/dist/chunk-t20f49g3.js +30 -0
- package/dist/{chunk-b58zsdp4.js → chunk-v041118a.js} +17 -5
- package/dist/{chunk-91vcwk5m.js → chunk-v14bg5yp.js} +1 -1
- package/dist/chunk-wvzap0na.js +18166 -0
- package/dist/chunk-wztmp5b4.js +30479 -0
- package/dist/chunk-xvqr1q9b.js +14 -0
- package/dist/{chunk-0xhdyt4z.js → chunk-yj7mpczw.js} +17 -5
- package/dist/chunk-yr0txkw0.js +31 -0
- package/dist/cli.js +3977 -1045
- package/dist/core/adoption.d.ts +62 -0
- package/dist/core/analytics.d.ts +17 -0
- package/dist/core/consumers.d.ts +8 -0
- package/dist/core/control.d.ts +12 -0
- package/dist/core/daemon.d.ts +2 -2
- package/dist/core/file-state.d.ts +5 -0
- package/dist/core/harnesses.d.ts +25 -0
- package/dist/core/index.d.ts +17 -7
- package/dist/core/index.js +128 -62
- package/dist/core/managed-index-lock.d.ts +1 -1
- package/dist/core/management.d.ts +32 -0
- package/dist/core/native-document.d.ts +4 -0
- package/dist/core/operations.d.ts +52 -0
- package/dist/core/paths.d.ts +2 -0
- package/dist/core/plugin-manager.d.ts +4 -1
- package/dist/core/plugin-projections.d.ts +1 -0
- package/dist/core/runtime-entry.d.ts +6 -0
- package/dist/core/runtime-snapshot.d.ts +6 -0
- package/dist/core/secret-keychain.d.ts +31 -0
- package/dist/core/secrets.d.ts +12 -2
- package/dist/core/served-skills.d.ts +62 -0
- package/dist/core/share.d.ts +71 -0
- package/dist/core/skills.d.ts +7 -0
- package/dist/core/status.d.ts +1 -0
- package/dist/core/sync-plan.d.ts +14 -0
- package/dist/core/sync.d.ts +3 -0
- package/dist/core/telemetry.d.ts +2 -2
- package/dist/core/tui.d.ts +20 -0
- package/dist/core/verify-upstreams.d.ts +4 -0
- package/dist/core/work-limiter.d.ts +10 -0
- package/dist/gateway/server.d.ts +2 -0
- package/dist/types.d.ts +72 -1
- package/dist/util/fs.d.ts +2 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/dist/adapters/opencode.d.ts +0 -11
- package/dist/chunk-cz8cdmw1.js +0 -344
- package/dist/chunk-z1k63fa3.js +0 -3760
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildDaemonChildEnv,
|
|
3
|
+
getDaemonStatus,
|
|
4
|
+
readDaemonLogs,
|
|
5
|
+
resolveGatewayPort,
|
|
6
|
+
restartDaemon,
|
|
7
|
+
runDaemonForeground,
|
|
8
|
+
startDaemon,
|
|
9
|
+
stopDaemon
|
|
10
|
+
} from "./chunk-wvzap0na.js";
|
|
11
|
+
import"./chunk-1mrkz29t.js";
|
|
12
|
+
import"./chunk-4aafp444.js";
|
|
13
|
+
import"./chunk-a2gen8bn.js";
|
|
14
|
+
import"./chunk-wztmp5b4.js";
|
|
15
|
+
import"./chunk-8v13hqrp.js";
|
|
16
|
+
import"./chunk-t20f49g3.js";
|
|
17
|
+
import"./chunk-hahs24dv.js";
|
|
18
|
+
import"./chunk-chaj8a8b.js";
|
|
19
|
+
import"./chunk-7rq88hb3.js";
|
|
20
|
+
import"./chunk-brxwa30j.js";
|
|
21
|
+
import"./chunk-p7yzyt87.js";
|
|
22
|
+
import"./chunk-grkz21sq.js";
|
|
23
|
+
import"./chunk-bjw71y54.js";
|
|
24
|
+
import"./chunk-wj4tjacg.js";
|
|
25
|
+
export {
|
|
26
|
+
buildDaemonChildEnv,
|
|
27
|
+
getDaemonStatus,
|
|
28
|
+
readDaemonLogs,
|
|
29
|
+
resolveGatewayPort,
|
|
30
|
+
restartDaemon,
|
|
31
|
+
runDaemonForeground,
|
|
32
|
+
startDaemon,
|
|
33
|
+
stopDaemon
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
//# debugId=7E85224A0D63747064756E2164756E21
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadConfig
|
|
3
|
+
} from "./chunk-brxwa30j.js";
|
|
4
|
+
import {
|
|
5
|
+
exports_external
|
|
6
|
+
} from "./chunk-p7yzyt87.js";
|
|
7
|
+
import {
|
|
8
|
+
ensureDir,
|
|
9
|
+
getPluginCacheRoot,
|
|
10
|
+
getSkillPackagesRoot,
|
|
11
|
+
getSkillStatePath,
|
|
12
|
+
getSkillsDir
|
|
13
|
+
} from "./chunk-grkz21sq.js";
|
|
14
|
+
|
|
15
|
+
// src/core/skills.ts
|
|
16
|
+
import fs2 from "node:fs";
|
|
17
|
+
import path2 from "node:path";
|
|
18
|
+
|
|
19
|
+
// src/core/identifiers.ts
|
|
20
|
+
var skillIdSchema = exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/);
|
|
21
|
+
function validateSkillId(id) {
|
|
22
|
+
const result = skillIdSchema.safeParse(id);
|
|
23
|
+
if (!result.success) {
|
|
24
|
+
throw new Error(`Invalid skill id "${id}". Use letters, numbers, dots, underscores, and hyphens.`);
|
|
25
|
+
}
|
|
26
|
+
return id;
|
|
27
|
+
}
|
|
28
|
+
function normalizePluginName(name) {
|
|
29
|
+
const normalized = name.replace(/[\\/]/g, "_").replace(/^\.+/, "").replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
30
|
+
if (!normalized) {
|
|
31
|
+
throw new Error(`Invalid plugin name "${name}"`);
|
|
32
|
+
}
|
|
33
|
+
return normalized;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/core/plugin-source.ts
|
|
37
|
+
var GITHUB_RE = /^(?:github\.com\/)?([a-zA-Z0-9][\w.-]*\/[a-zA-Z0-9][\w.-]*?)(?:@([\w./-]+))?$/;
|
|
38
|
+
var GIT_URL_RE = /^https?:\/\/.*\.git(?:\?.*)?(?:\/\/.*)?$/;
|
|
39
|
+
var SCP_GIT_RE = /^[\w.-]+@[\w.-]+:[\w./-]+$/;
|
|
40
|
+
var NPM_RE = /^npm:(@?[\w.-]+(?:\/[\w.-]+)?)(?:@([\w.-]+))?$/;
|
|
41
|
+
var MARKETPLACE_RE = /^marketplace:([\w.-]+)$/;
|
|
42
|
+
function parseSource(source) {
|
|
43
|
+
const github = source.match(GITHUB_RE);
|
|
44
|
+
if (github) {
|
|
45
|
+
return {
|
|
46
|
+
type: "github",
|
|
47
|
+
original: source,
|
|
48
|
+
ref: github[2] || undefined
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (NPM_RE.test(source)) {
|
|
52
|
+
const npm = source.match(NPM_RE);
|
|
53
|
+
return {
|
|
54
|
+
type: "npm",
|
|
55
|
+
original: source,
|
|
56
|
+
ref: npm[2] || undefined
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (MARKETPLACE_RE.test(source)) {
|
|
60
|
+
return {
|
|
61
|
+
type: "marketplace",
|
|
62
|
+
original: source
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../") || source === "." || source === "..") {
|
|
66
|
+
return {
|
|
67
|
+
type: "local",
|
|
68
|
+
original: source
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (GIT_URL_RE.test(source) || SCP_GIT_RE.test(source)) {
|
|
72
|
+
return {
|
|
73
|
+
type: "git",
|
|
74
|
+
original: source
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
throw new Error(`Unrecognized plugin source: ${source}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/core/plugin-cache.ts
|
|
81
|
+
import fs from "node:fs";
|
|
82
|
+
import path from "node:path";
|
|
83
|
+
import crypto from "node:crypto";
|
|
84
|
+
import { execFile } from "node:child_process";
|
|
85
|
+
import { promisify } from "node:util";
|
|
86
|
+
var TMP_PREFIX = "mcpx-acquire-";
|
|
87
|
+
var execFileAsync = promisify(execFile);
|
|
88
|
+
|
|
89
|
+
class PluginCache {
|
|
90
|
+
cacheRoot;
|
|
91
|
+
constructor() {
|
|
92
|
+
this.cacheRoot = getPluginCacheRoot();
|
|
93
|
+
ensureDir(this.cacheRoot);
|
|
94
|
+
}
|
|
95
|
+
sourceDir(source) {
|
|
96
|
+
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, "_")}`;
|
|
97
|
+
return path.join(this.cacheRoot, key);
|
|
98
|
+
}
|
|
99
|
+
refPath(srcDir) {
|
|
100
|
+
return path.join(srcDir, "ref.json");
|
|
101
|
+
}
|
|
102
|
+
shaDir(srcDir, sha) {
|
|
103
|
+
return path.join(srcDir, sha);
|
|
104
|
+
}
|
|
105
|
+
async resolveSha(source) {
|
|
106
|
+
const srcDir = this.sourceDir(source);
|
|
107
|
+
if (source.type === "github") {
|
|
108
|
+
const repo = source.original.replace(/^github\.com\//, "").split("@")[0];
|
|
109
|
+
const ref = source.ref || "HEAD";
|
|
110
|
+
const { stdout } = await execFileAsync("git", ["ls-remote", `https://github.com/${repo}.git`, ref], { timeout: 30000 });
|
|
111
|
+
const sha = stdout.split("\t")[0]?.trim();
|
|
112
|
+
if (!sha)
|
|
113
|
+
throw new Error(`Could not resolve ref ${ref} for ${repo}`);
|
|
114
|
+
return sha;
|
|
115
|
+
}
|
|
116
|
+
if (source.type === "git" || source.type === "git-subdir") {
|
|
117
|
+
const ref = source.ref || "HEAD";
|
|
118
|
+
const { stdout } = await execFileAsync("git", ["ls-remote", source.original, ref], { timeout: 30000 });
|
|
119
|
+
const sha = stdout.split("\t")[0]?.trim();
|
|
120
|
+
if (!sha)
|
|
121
|
+
throw new Error(`Could not resolve ref ${ref} for ${source.original}`);
|
|
122
|
+
return sha;
|
|
123
|
+
}
|
|
124
|
+
if (source.type === "local") {
|
|
125
|
+
const resolved = path.resolve(source.original);
|
|
126
|
+
if (!fs.existsSync(resolved)) {
|
|
127
|
+
throw new Error(`Local path not found: ${resolved}`);
|
|
128
|
+
}
|
|
129
|
+
return computeTreeHash(resolved);
|
|
130
|
+
}
|
|
131
|
+
if (source.type === "npm") {
|
|
132
|
+
const pkg = source.original.replace(/^npm:/, "");
|
|
133
|
+
const spec = source.ref ? `${pkg}@${source.ref}` : pkg;
|
|
134
|
+
const { stdout } = await execFileAsync("npm", ["view", spec, "version", "dist.integrity", "--json"], { timeout: 30000 });
|
|
135
|
+
const metadata = JSON.parse(stdout);
|
|
136
|
+
if (!metadata.version)
|
|
137
|
+
throw new Error(`Could not resolve npm package ${spec}`);
|
|
138
|
+
return `npm-${metadata.version}-${metadata.dist?.integrity ?? metadata["dist.integrity"] ?? "unverified"}`;
|
|
139
|
+
}
|
|
140
|
+
throw new Error(`Cannot resolve SHA for source type: ${source.type}`);
|
|
141
|
+
}
|
|
142
|
+
async fetch(source, pluginName) {
|
|
143
|
+
const sha = source.resolvedSha || await this.resolveSha(source);
|
|
144
|
+
const srcDir = this.sourceDir(source);
|
|
145
|
+
const destDir = this.shaDir(srcDir, sha);
|
|
146
|
+
if (fs.existsSync(destDir)) {
|
|
147
|
+
this.updateRef(srcDir, sha);
|
|
148
|
+
return { source: source.original, name: pluginName, sha, root: destDir };
|
|
149
|
+
}
|
|
150
|
+
ensureDir(srcDir);
|
|
151
|
+
if (source.type === "github" || source.type === "git" || source.type === "git-subdir") {
|
|
152
|
+
const remote = source.type === "github" ? `https://github.com/${source.original.replace(/^github\.com\//, "").split("@")[0]}.git` : source.original;
|
|
153
|
+
const target = source.resolvedSha || source.ref || "HEAD";
|
|
154
|
+
const tmpDir = fs.mkdtempSync(path.join(this.cacheRoot, TMP_PREFIX));
|
|
155
|
+
try {
|
|
156
|
+
await execFileAsync("git", ["init"], { cwd: tmpDir, timeout: 1e4 });
|
|
157
|
+
await execFileAsync("git", ["remote", "add", "origin", remote], { cwd: tmpDir, timeout: 1e4 });
|
|
158
|
+
await execFileAsync("git", ["fetch", "--depth", "1", "origin", target], {
|
|
159
|
+
cwd: tmpDir,
|
|
160
|
+
timeout: 60000
|
|
161
|
+
});
|
|
162
|
+
await execFileAsync("git", ["checkout", "--detach", "FETCH_HEAD"], { cwd: tmpDir, timeout: 1e4 });
|
|
163
|
+
const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"], {
|
|
164
|
+
cwd: tmpDir,
|
|
165
|
+
timeout: 1e4
|
|
166
|
+
});
|
|
167
|
+
const actualSha = stdout.trim();
|
|
168
|
+
fs.rmSync(path.join(tmpDir, ".git"), { recursive: true, force: true });
|
|
169
|
+
const finalDest = this.shaDir(srcDir, actualSha);
|
|
170
|
+
if (fs.existsSync(finalDest)) {
|
|
171
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
172
|
+
this.updateRef(srcDir, actualSha);
|
|
173
|
+
return { source: source.original, name: pluginName, sha: actualSha, root: finalDest };
|
|
174
|
+
}
|
|
175
|
+
if (source.type === "git-subdir") {
|
|
176
|
+
if (!source.path)
|
|
177
|
+
throw new Error("git-subdir source is missing a path");
|
|
178
|
+
const subdir = path.resolve(tmpDir, source.path);
|
|
179
|
+
if (!subdir.startsWith(path.resolve(tmpDir) + path.sep) || !fs.existsSync(subdir)) {
|
|
180
|
+
throw new Error(`Plugin subdirectory not found: ${source.path}`);
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
fs.renameSync(subdir, finalDest);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (!fs.existsSync(finalDest))
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
189
|
+
} else {
|
|
190
|
+
try {
|
|
191
|
+
fs.renameSync(tmpDir, finalDest);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (!fs.existsSync(finalDest))
|
|
194
|
+
throw error;
|
|
195
|
+
return { source: source.original, name: pluginName, sha: actualSha, root: finalDest };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
this.updateRef(srcDir, actualSha);
|
|
199
|
+
return { source: source.original, name: pluginName, sha: actualSha, root: finalDest };
|
|
200
|
+
} catch (e) {
|
|
201
|
+
if (fs.existsSync(tmpDir))
|
|
202
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
203
|
+
throw e;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (source.type === "local") {
|
|
207
|
+
const resolved = path.resolve(source.original);
|
|
208
|
+
if (!fs.existsSync(resolved)) {
|
|
209
|
+
throw new Error(`Local path not found: ${resolved}`);
|
|
210
|
+
}
|
|
211
|
+
copyDirSync(resolved, destDir);
|
|
212
|
+
this.updateRef(srcDir, sha);
|
|
213
|
+
return { source: source.original, name: pluginName, sha, root: destDir };
|
|
214
|
+
}
|
|
215
|
+
if (source.type === "npm") {
|
|
216
|
+
const pkg = source.original.replace(/^npm:/, "");
|
|
217
|
+
const spec = source.ref ? `${pkg}@${source.ref}` : pkg;
|
|
218
|
+
const tmpDir = fs.mkdtempSync(path.join(this.cacheRoot, TMP_PREFIX));
|
|
219
|
+
try {
|
|
220
|
+
const { stdout } = await execFileAsync("npm", ["pack", spec, "--ignore-scripts", "--json", "--pack-destination", tmpDir], { timeout: 120000 });
|
|
221
|
+
const packed = JSON.parse(stdout);
|
|
222
|
+
const filename = packed[0]?.filename;
|
|
223
|
+
if (!filename)
|
|
224
|
+
throw new Error(`npm did not produce an archive for ${spec}`);
|
|
225
|
+
const archive = path.join(tmpDir, filename);
|
|
226
|
+
const actualSha = `npm-${packed[0]?.version ?? source.ref ?? "latest"}-${crypto.createHash("sha256").update(fs.readFileSync(archive)).digest("hex")}`;
|
|
227
|
+
const finalDest = this.shaDir(srcDir, actualSha);
|
|
228
|
+
if (!fs.existsSync(finalDest)) {
|
|
229
|
+
ensureDir(finalDest);
|
|
230
|
+
await execFileAsync("tar", ["-xzf", archive, "--strip-components=1", "-C", finalDest], { timeout: 30000 });
|
|
231
|
+
}
|
|
232
|
+
this.updateRef(srcDir, actualSha);
|
|
233
|
+
return { source: source.original, name: pluginName, sha: actualSha, root: finalDest };
|
|
234
|
+
} finally {
|
|
235
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
throw new Error(`Cannot fetch source type: ${source.type}`);
|
|
239
|
+
}
|
|
240
|
+
updateRef(srcDir, sha) {
|
|
241
|
+
const refs = {};
|
|
242
|
+
if (fs.existsSync(this.refPath(srcDir))) {
|
|
243
|
+
try {
|
|
244
|
+
const existing = JSON.parse(fs.readFileSync(this.refPath(srcDir), "utf8"));
|
|
245
|
+
Object.assign(refs, existing);
|
|
246
|
+
} catch {}
|
|
247
|
+
}
|
|
248
|
+
refs.latest = sha;
|
|
249
|
+
fs.writeFileSync(this.refPath(srcDir), JSON.stringify(refs, null, 2));
|
|
250
|
+
}
|
|
251
|
+
listCached() {
|
|
252
|
+
const results = [];
|
|
253
|
+
try {
|
|
254
|
+
const sourceDirs = fs.readdirSync(this.cacheRoot);
|
|
255
|
+
for (const dir of sourceDirs) {
|
|
256
|
+
const srcDir = path.join(this.cacheRoot, dir);
|
|
257
|
+
if (!fs.statSync(srcDir).isDirectory())
|
|
258
|
+
continue;
|
|
259
|
+
const entries = fs.readdirSync(srcDir);
|
|
260
|
+
for (const entry of entries) {
|
|
261
|
+
if (entry === "ref.json")
|
|
262
|
+
continue;
|
|
263
|
+
const shaDir = path.join(srcDir, entry);
|
|
264
|
+
if (!fs.statSync(shaDir).isDirectory())
|
|
265
|
+
continue;
|
|
266
|
+
const refs = {};
|
|
267
|
+
if (fs.existsSync(this.refPath(srcDir))) {
|
|
268
|
+
try {
|
|
269
|
+
Object.assign(refs, JSON.parse(fs.readFileSync(this.refPath(srcDir), "utf8")));
|
|
270
|
+
} catch {}
|
|
271
|
+
}
|
|
272
|
+
results.push({
|
|
273
|
+
source: dir,
|
|
274
|
+
name: entry,
|
|
275
|
+
sha: entry,
|
|
276
|
+
root: shaDir
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
} catch {}
|
|
281
|
+
return results;
|
|
282
|
+
}
|
|
283
|
+
remove(source, sha) {
|
|
284
|
+
const srcDir = this.sourceDir(source);
|
|
285
|
+
const target = this.shaDir(srcDir, sha);
|
|
286
|
+
if (fs.existsSync(target)) {
|
|
287
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
removeAll(source) {
|
|
291
|
+
const srcDir = this.sourceDir(source);
|
|
292
|
+
if (fs.existsSync(srcDir)) {
|
|
293
|
+
fs.rmSync(srcDir, { recursive: true, force: true });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function computeTreeHash(dir) {
|
|
298
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
299
|
+
const hash = crypto.createHash("sha256");
|
|
300
|
+
for (const entry of entries) {
|
|
301
|
+
if (entry.name.startsWith(".git"))
|
|
302
|
+
continue;
|
|
303
|
+
if (entry.name === ".DS_Store")
|
|
304
|
+
continue;
|
|
305
|
+
if (entry.isDirectory()) {
|
|
306
|
+
hash.update(entry.name);
|
|
307
|
+
hash.update(computeTreeHash(path.join(dir, entry.name)));
|
|
308
|
+
} else if (entry.isFile()) {
|
|
309
|
+
hash.update(entry.name);
|
|
310
|
+
hash.update(fs.readFileSync(path.join(dir, entry.name)));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return hash.digest("hex");
|
|
314
|
+
}
|
|
315
|
+
function copyDirSync(src, dest) {
|
|
316
|
+
ensureDir(dest);
|
|
317
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
318
|
+
for (const entry of entries) {
|
|
319
|
+
if (entry.name.startsWith(".git"))
|
|
320
|
+
continue;
|
|
321
|
+
if (entry.name === ".DS_Store")
|
|
322
|
+
continue;
|
|
323
|
+
const srcPath = path.join(src, entry.name);
|
|
324
|
+
const destPath = path.join(dest, entry.name);
|
|
325
|
+
if (entry.isDirectory()) {
|
|
326
|
+
copyDirSync(srcPath, destPath);
|
|
327
|
+
} else if (entry.isFile()) {
|
|
328
|
+
fs.copyFileSync(srcPath, destPath);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/core/skills.ts
|
|
334
|
+
var skillStateSchema = exports_external.record(exports_external.string(), exports_external.object({ source: exports_external.string(), skillName: exports_external.string().optional(), pinned: exports_external.boolean().default(false), previousPath: exports_external.string().optional() }));
|
|
335
|
+
function readSkillState() {
|
|
336
|
+
const statePath = getSkillStatePath();
|
|
337
|
+
if (!fs2.existsSync(statePath))
|
|
338
|
+
return {};
|
|
339
|
+
try {
|
|
340
|
+
return skillStateSchema.parse(JSON.parse(fs2.readFileSync(statePath, "utf8")));
|
|
341
|
+
} catch {
|
|
342
|
+
return {};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function writeSkillState(state) {
|
|
346
|
+
ensureDir(path2.dirname(getSkillStatePath()));
|
|
347
|
+
fs2.writeFileSync(getSkillStatePath(), JSON.stringify(state, null, 2) + `
|
|
348
|
+
`, "utf8");
|
|
349
|
+
}
|
|
350
|
+
function validateSkillPackage(root) {
|
|
351
|
+
const skillPath = path2.join(root, "SKILL.md");
|
|
352
|
+
if (!fs2.existsSync(skillPath))
|
|
353
|
+
throw new Error(`Agent Skill is missing SKILL.md: ${root}`);
|
|
354
|
+
const content = fs2.readFileSync(skillPath, "utf8");
|
|
355
|
+
const match = content.match(/^---\s*[\s\S]*?^name:\s*([^\r\n]+)[\s\S]*?^description:\s*([^\r\n]+)[\s\S]*?^---/m);
|
|
356
|
+
if (!match || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(match[1].trim()) || !match[2].trim()) {
|
|
357
|
+
throw new Error(`Invalid Agent Skill frontmatter in ${skillPath}; name and description are required`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function installedSkills() {
|
|
361
|
+
const root = getSkillPackagesRoot();
|
|
362
|
+
if (!fs2.existsSync(root))
|
|
363
|
+
return [];
|
|
364
|
+
return fs2.readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
|
|
365
|
+
if (!entry.isDirectory())
|
|
366
|
+
return [];
|
|
367
|
+
const packageRoot = path2.join(root, entry.name);
|
|
368
|
+
const skillFile = path2.join(packageRoot, "SKILL.md");
|
|
369
|
+
if (!fs2.existsSync(skillFile))
|
|
370
|
+
return [];
|
|
371
|
+
return [{ id: entry.name, content: fs2.readFileSync(skillFile, "utf8"), root: packageRoot, source: "installed", readOnly: true }];
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
function listSkills() {
|
|
375
|
+
const skillsDir = getSkillsDir();
|
|
376
|
+
const skills = [];
|
|
377
|
+
const files = fs2.existsSync(skillsDir) ? fs2.readdirSync(skillsDir, { withFileTypes: true }) : [];
|
|
378
|
+
const authoredDirectories = new Set(files.filter((file) => file.isDirectory()).map((file) => file.name));
|
|
379
|
+
for (const file of files) {
|
|
380
|
+
const id = file.isDirectory() ? file.name : file.name.endsWith(".md") ? path2.basename(file.name, ".md") : null;
|
|
381
|
+
if (id && (file.isDirectory() || !authoredDirectories.has(id))) {
|
|
382
|
+
try {
|
|
383
|
+
const root = file.isDirectory() ? path2.join(skillsDir, id) : skillsDir;
|
|
384
|
+
const filePath = file.isDirectory() ? path2.join(root, "SKILL.md") : path2.join(skillsDir, file.name);
|
|
385
|
+
if (!fs2.existsSync(filePath))
|
|
386
|
+
continue;
|
|
387
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
388
|
+
skills.push({ id, content, root: file.isDirectory() ? root : undefined, source: "authored" });
|
|
389
|
+
} catch {
|
|
390
|
+
console.error(`[mcpx] Warning: could not read skill ${file.name}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const authoredIds = new Set(skills.map((skill) => skill.id));
|
|
395
|
+
const native = [];
|
|
396
|
+
for (const pkg of Object.values(loadConfig().packages ?? {}))
|
|
397
|
+
for (const skill of pkg.skills ?? []) {
|
|
398
|
+
const root = path2.resolve(pkg.root, skill.path);
|
|
399
|
+
if (!root.startsWith(path2.resolve(pkg.root) + path2.sep))
|
|
400
|
+
continue;
|
|
401
|
+
const file = path2.join(root, "SKILL.md");
|
|
402
|
+
if (fs2.existsSync(file))
|
|
403
|
+
native.push({ id: skill.id, root, content: fs2.readFileSync(file, "utf8"), projectionPath: skill.projectionPath, scope: pkg.scope, ownerHarness: pkg.harness, source: "installed", readOnly: true });
|
|
404
|
+
}
|
|
405
|
+
return [...skills, ...installedSkills().filter((skill) => !authoredIds.has(skill.id)), ...native];
|
|
406
|
+
}
|
|
407
|
+
function getSkill(id) {
|
|
408
|
+
validateSkillId(id);
|
|
409
|
+
const skillsDir = getSkillsDir();
|
|
410
|
+
const directory = path2.join(skillsDir, id);
|
|
411
|
+
const packagePath = path2.join(directory, "SKILL.md");
|
|
412
|
+
const legacyPath = path2.join(skillsDir, `${id}.md`);
|
|
413
|
+
const filePath = fs2.existsSync(packagePath) ? packagePath : legacyPath;
|
|
414
|
+
if (!fs2.existsSync(filePath)) {
|
|
415
|
+
const installedPath = path2.join(getSkillPackagesRoot(), id, "SKILL.md");
|
|
416
|
+
if (!fs2.existsSync(installedPath))
|
|
417
|
+
return listSkills().find((skill) => skill.id === id) ?? null;
|
|
418
|
+
const installedRoot = path2.dirname(installedPath);
|
|
419
|
+
return { id, content: fs2.readFileSync(installedPath, "utf8"), root: installedRoot, source: "installed", readOnly: true };
|
|
420
|
+
}
|
|
421
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
422
|
+
return { id, content, root: fs2.existsSync(packagePath) ? directory : undefined, source: "authored" };
|
|
423
|
+
}
|
|
424
|
+
function saveSkill(id, content) {
|
|
425
|
+
validateSkillId(id);
|
|
426
|
+
const skillsDir = getSkillsDir();
|
|
427
|
+
ensureDir(skillsDir);
|
|
428
|
+
const directory = path2.join(skillsDir, id);
|
|
429
|
+
const filePath = path2.join(directory, "SKILL.md");
|
|
430
|
+
ensureDir(directory);
|
|
431
|
+
fs2.writeFileSync(filePath, content, "utf-8");
|
|
432
|
+
const legacyPath = path2.join(skillsDir, `${id}.md`);
|
|
433
|
+
if (fs2.existsSync(legacyPath)) {
|
|
434
|
+
if (fs2.readFileSync(filePath, "utf-8") === fs2.readFileSync(legacyPath, "utf-8"))
|
|
435
|
+
fs2.unlinkSync(legacyPath);
|
|
436
|
+
else
|
|
437
|
+
console.error(`[mcpx] Warning: preserving conflicting legacy skill file: ${legacyPath}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function deleteSkill(id) {
|
|
441
|
+
validateSkillId(id);
|
|
442
|
+
const skillsDir = getSkillsDir();
|
|
443
|
+
const directory = path2.join(skillsDir, id);
|
|
444
|
+
const filePath = path2.join(directory, "SKILL.md");
|
|
445
|
+
const legacyPath = path2.join(skillsDir, `${id}.md`);
|
|
446
|
+
const hadAuthored = fs2.existsSync(directory) || fs2.existsSync(legacyPath);
|
|
447
|
+
if (fs2.existsSync(directory))
|
|
448
|
+
fs2.rmSync(directory, { recursive: true, force: true });
|
|
449
|
+
if (fs2.existsSync(legacyPath))
|
|
450
|
+
fs2.unlinkSync(legacyPath);
|
|
451
|
+
if (!hadAuthored) {
|
|
452
|
+
const installedPath = path2.join(getSkillPackagesRoot(), id);
|
|
453
|
+
if (fs2.existsSync(installedPath))
|
|
454
|
+
fs2.rmSync(installedPath, { recursive: true, force: true });
|
|
455
|
+
const state = readSkillState();
|
|
456
|
+
delete state[id];
|
|
457
|
+
writeSkillState(state);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function customizeSkill(id, targetId = id) {
|
|
461
|
+
validateSkillId(id);
|
|
462
|
+
validateSkillId(targetId);
|
|
463
|
+
const existing = getSkill(id);
|
|
464
|
+
if (!existing)
|
|
465
|
+
throw new Error(`Skill "${id}" not found`);
|
|
466
|
+
saveSkill(targetId, existing.content);
|
|
467
|
+
return getSkill(targetId);
|
|
468
|
+
}
|
|
469
|
+
async function installSkillFromSource(source, skillName) {
|
|
470
|
+
const parsed = parseSource(source);
|
|
471
|
+
const cache = new PluginCache;
|
|
472
|
+
const cached = await cache.fetch(parsed, skillName ?? "skill");
|
|
473
|
+
const candidates = [];
|
|
474
|
+
const declaredName = (root) => {
|
|
475
|
+
try {
|
|
476
|
+
const content = fs2.readFileSync(path2.join(root, "SKILL.md"), "utf8");
|
|
477
|
+
const frontmatter = content.match(/^---\s*[\s\S]*?^name:\s*([^\r\n]+)[\s\S]*?^---/m)?.[1]?.trim();
|
|
478
|
+
return frontmatter && /^[a-z0-9][a-z0-9-]*$/.test(frontmatter) ? frontmatter : undefined;
|
|
479
|
+
} catch {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
const addCandidate = (root, id = path2.basename(root)) => {
|
|
484
|
+
if (fs2.existsSync(path2.join(root, "SKILL.md"))) {
|
|
485
|
+
validateSkillPackage(root);
|
|
486
|
+
candidates.push({ id: declaredName(root) ?? id, root });
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
addCandidate(cached.root);
|
|
490
|
+
const skillsRoot = path2.join(cached.root, "skills");
|
|
491
|
+
if (fs2.existsSync(skillsRoot)) {
|
|
492
|
+
for (const entry of fs2.readdirSync(skillsRoot, { withFileTypes: true })) {
|
|
493
|
+
if (entry.isDirectory())
|
|
494
|
+
addCandidate(path2.join(skillsRoot, entry.name), entry.name);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (candidates.length === 0)
|
|
498
|
+
throw new Error(`No Agent Skill package found in ${source}`);
|
|
499
|
+
const selected = skillName ? candidates.find((candidate) => candidate.id === skillName) : candidates[0];
|
|
500
|
+
if (!selected)
|
|
501
|
+
throw new Error(`Skill "${skillName}" was not found in ${source}`);
|
|
502
|
+
validateSkillId(selected.id);
|
|
503
|
+
const destination = path2.join(getSkillPackagesRoot(), selected.id);
|
|
504
|
+
ensureDir(path2.dirname(destination));
|
|
505
|
+
const state = readSkillState();
|
|
506
|
+
const stateSource = parsed.type === "local" ? path2.resolve(source) : source;
|
|
507
|
+
if (fs2.existsSync(destination)) {
|
|
508
|
+
if (state[selected.id]?.pinned)
|
|
509
|
+
throw new Error(`Skill "${selected.id}" is pinned; unpin it before updating`);
|
|
510
|
+
const previousPath = `${destination}.previous`;
|
|
511
|
+
if (fs2.existsSync(previousPath))
|
|
512
|
+
fs2.rmSync(previousPath, { recursive: true, force: true });
|
|
513
|
+
fs2.renameSync(destination, previousPath);
|
|
514
|
+
state[selected.id] = { ...state[selected.id], source: stateSource, skillName: selected.id, pinned: false, previousPath };
|
|
515
|
+
} else {
|
|
516
|
+
state[selected.id] = { source: stateSource, skillName: selected.id, pinned: false };
|
|
517
|
+
}
|
|
518
|
+
fs2.cpSync(selected.root, destination, { recursive: true, dereference: true });
|
|
519
|
+
writeSkillState(state);
|
|
520
|
+
return { id: selected.id, content: fs2.readFileSync(path2.join(destination, "SKILL.md"), "utf8"), root: destination, source: "installed", readOnly: true };
|
|
521
|
+
}
|
|
522
|
+
function pinSkill(id) {
|
|
523
|
+
const state = readSkillState();
|
|
524
|
+
if (!state[id])
|
|
525
|
+
throw new Error(`Skill "${id}" is not installed`);
|
|
526
|
+
state[id].pinned = true;
|
|
527
|
+
writeSkillState(state);
|
|
528
|
+
}
|
|
529
|
+
function unpinSkill(id) {
|
|
530
|
+
const state = readSkillState();
|
|
531
|
+
if (!state[id])
|
|
532
|
+
throw new Error(`Skill "${id}" is not installed`);
|
|
533
|
+
state[id].pinned = false;
|
|
534
|
+
writeSkillState(state);
|
|
535
|
+
}
|
|
536
|
+
async function updateSkill(id) {
|
|
537
|
+
const state = readSkillState();
|
|
538
|
+
const entry = state[id];
|
|
539
|
+
if (!entry)
|
|
540
|
+
throw new Error(`Skill "${id}" is not installed`);
|
|
541
|
+
if (entry.pinned)
|
|
542
|
+
throw new Error(`Skill "${id}" is pinned; unpin it before updating`);
|
|
543
|
+
return installSkillFromSource(entry.source, entry.skillName ?? id);
|
|
544
|
+
}
|
|
545
|
+
function rollbackSkill(id) {
|
|
546
|
+
const state = readSkillState();
|
|
547
|
+
const entry = state[id];
|
|
548
|
+
if (!entry?.previousPath || !fs2.existsSync(entry.previousPath))
|
|
549
|
+
throw new Error(`Skill "${id}" has no previous revision`);
|
|
550
|
+
const destination = path2.join(getSkillPackagesRoot(), id);
|
|
551
|
+
const failedPath = `${destination}.failed`;
|
|
552
|
+
if (fs2.existsSync(failedPath))
|
|
553
|
+
fs2.rmSync(failedPath, { recursive: true, force: true });
|
|
554
|
+
fs2.renameSync(destination, failedPath);
|
|
555
|
+
fs2.renameSync(entry.previousPath, destination);
|
|
556
|
+
entry.previousPath = failedPath;
|
|
557
|
+
writeSkillState(state);
|
|
558
|
+
return { id, content: fs2.readFileSync(path2.join(destination, "SKILL.md"), "utf8"), root: destination, source: "installed", readOnly: true };
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export { validateSkillId, normalizePluginName, parseSource, PluginCache, validateSkillPackage, listSkills, getSkill, saveSkill, deleteSkill, customizeSkill, installSkillFromSource, pinSkill, unpinSkill, updateSkill, rollbackSkill };
|
|
562
|
+
|
|
563
|
+
//# debugId=43A9FAB87FA4420064756E2164756E21
|