@modootoday/pmcp 0.16.1 → 0.17.1
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/.agents/plugins/marketplace.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +7 -1
- package/dist/cli.js +535 -320
- package/dist/http.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +269 -107
- package/dist/stdio.js +1 -1
- package/docs/project-configuration.md +35 -0
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/plugins/pmcp/.claude-plugin/plugin.json +1 -1
- package/plugins/pmcp/gemini-extension.json +1 -1
- package/plugins/pmcp/plugin.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1548,7 +1548,8 @@ function nativeOptions(path, where, value) {
|
|
|
1548
1548
|
codex: ["startup_timeout_sec", "tool_timeout_sec"],
|
|
1549
1549
|
gemini: ["timeout"],
|
|
1550
1550
|
grok: ["startup_timeout_sec", "tool_timeout_sec"],
|
|
1551
|
-
antigravity: []
|
|
1551
|
+
antigravity: [],
|
|
1552
|
+
opencode: ["timeout"]
|
|
1552
1553
|
};
|
|
1553
1554
|
const result = {};
|
|
1554
1555
|
for (const [tool, raw] of Object.entries(
|
|
@@ -1980,7 +1981,8 @@ var TOOLS = [
|
|
|
1980
1981
|
"codex",
|
|
1981
1982
|
"gemini",
|
|
1982
1983
|
"grok",
|
|
1983
|
-
"antigravity"
|
|
1984
|
+
"antigravity",
|
|
1985
|
+
"opencode"
|
|
1984
1986
|
];
|
|
1985
1987
|
var NAME = /^[a-z0-9][a-z0-9-]*$/u;
|
|
1986
1988
|
function table(path, where, value) {
|
|
@@ -2079,8 +2081,8 @@ function readSpec(config) {
|
|
|
2079
2081
|
}
|
|
2080
2082
|
|
|
2081
2083
|
// src/project/plan.ts
|
|
2082
|
-
import { existsSync as
|
|
2083
|
-
import { join as
|
|
2084
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "fs";
|
|
2085
|
+
import { join as join8 } from "path";
|
|
2084
2086
|
|
|
2085
2087
|
// src/project/frontmatter.ts
|
|
2086
2088
|
import { parse as parse3 } from "yaml";
|
|
@@ -2228,6 +2230,56 @@ function packageRuleOutputs(root, spec, owned) {
|
|
|
2228
2230
|
|
|
2229
2231
|
// src/project/mcp.ts
|
|
2230
2232
|
import { isAbsolute as isAbsolute4, relative as relative4 } from "path";
|
|
2233
|
+
|
|
2234
|
+
// src/project/adapters/opencode.ts
|
|
2235
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2236
|
+
import { join as join6 } from "path";
|
|
2237
|
+
var OPENCODE_CONFIG = "opencode.json";
|
|
2238
|
+
function checkOpenCodeConfig(root) {
|
|
2239
|
+
if (existsSync8(join6(root, "opencode.jsonc"))) {
|
|
2240
|
+
throw new ProjectError(
|
|
2241
|
+
"OpenCode JSONC projection is not supported; reconcile opencode.jsonc before projecting opencode.json"
|
|
2242
|
+
);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
function opencodeMcpEntry(server, started) {
|
|
2246
|
+
if (server.tools !== void 0) {
|
|
2247
|
+
throw new ProjectError(
|
|
2248
|
+
"OpenCode per-server tool filtering is not qualified; limit this server's targets"
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
const timeout = server.native?.opencode;
|
|
2252
|
+
if (server.url !== void 0) {
|
|
2253
|
+
const headers = { ...server.headers };
|
|
2254
|
+
for (const [key, name] of Object.entries(server.headerEnv ?? {})) {
|
|
2255
|
+
headers[key] = `{env:${name}}`;
|
|
2256
|
+
}
|
|
2257
|
+
if (server.bearerTokenEnvVar) {
|
|
2258
|
+
headers["Authorization"] = `Bearer {env:${server.bearerTokenEnvVar}}`;
|
|
2259
|
+
}
|
|
2260
|
+
return {
|
|
2261
|
+
type: "remote",
|
|
2262
|
+
url: server.url,
|
|
2263
|
+
enabled: true,
|
|
2264
|
+
...Object.keys(headers).length > 0 ? { headers } : {},
|
|
2265
|
+
...timeout
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
if (!started) {
|
|
2269
|
+
throw new ProjectError(
|
|
2270
|
+
"OpenCode local MCP projection requires a launch specification"
|
|
2271
|
+
);
|
|
2272
|
+
}
|
|
2273
|
+
return {
|
|
2274
|
+
type: "local",
|
|
2275
|
+
command: [started.command, ...started.args],
|
|
2276
|
+
enabled: true,
|
|
2277
|
+
...Object.keys(started.env).length > 0 ? { environment: { ...started.env } } : {},
|
|
2278
|
+
...timeout
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// src/project/mcp.ts
|
|
2231
2283
|
var runtimeByTool = {
|
|
2232
2284
|
claude: "claude-code",
|
|
2233
2285
|
codex: "codex-cli",
|
|
@@ -2235,6 +2287,15 @@ var runtimeByTool = {
|
|
|
2235
2287
|
grok: "grok-cli",
|
|
2236
2288
|
antigravity: "antigravity"
|
|
2237
2289
|
};
|
|
2290
|
+
function mailboxRuntime(tool) {
|
|
2291
|
+
const runtime = runtimeByTool[tool];
|
|
2292
|
+
if (!runtime) {
|
|
2293
|
+
throw new ProjectError(
|
|
2294
|
+
"OpenCode mailbox runtime binding is not qualified; limit this server's targets"
|
|
2295
|
+
);
|
|
2296
|
+
}
|
|
2297
|
+
return runtime;
|
|
2298
|
+
}
|
|
2238
2299
|
var ROOT_VAR = "${PROJECT_ROOT}";
|
|
2239
2300
|
var ANTIGRAVITY_MCP_PLUGIN = ".agents/plugins/pmcp-mcp";
|
|
2240
2301
|
var VARIABLE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/gu;
|
|
@@ -2370,18 +2431,19 @@ function httpEntry(server, tool) {
|
|
|
2370
2431
|
function nativeEntry(spec, alias, server, tool) {
|
|
2371
2432
|
let result;
|
|
2372
2433
|
if (server.url !== void 0) {
|
|
2434
|
+
if (tool === "opencode") return opencodeMcpEntry(server);
|
|
2373
2435
|
result = httpEntry(server, tool);
|
|
2374
2436
|
} else {
|
|
2375
2437
|
const env = { ...server.env };
|
|
2376
2438
|
if (server.mailbox) {
|
|
2377
|
-
env["PMCP_MAILBOX_RUNTIME"] =
|
|
2439
|
+
env["PMCP_MAILBOX_RUNTIME"] = mailboxRuntime(tool);
|
|
2378
2440
|
}
|
|
2379
2441
|
let started;
|
|
2380
2442
|
if (Object.keys(server.envFrom ?? {}).length > 0) {
|
|
2381
2443
|
const entry = relative4(spec.root, process.argv[1] ?? "");
|
|
2382
2444
|
const driverEnv = {};
|
|
2383
2445
|
if (server.mailbox) {
|
|
2384
|
-
driverEnv["PMCP_MAILBOX_RUNTIME"] =
|
|
2446
|
+
driverEnv["PMCP_MAILBOX_RUNTIME"] = mailboxRuntime(tool);
|
|
2385
2447
|
}
|
|
2386
2448
|
if (!isAbsolute4(entry) && !entry.startsWith("..")) {
|
|
2387
2449
|
started = launch({
|
|
@@ -2399,6 +2461,7 @@ function nativeEntry(spec, alias, server, tool) {
|
|
|
2399
2461
|
} else {
|
|
2400
2462
|
started = launch({ ...server, env });
|
|
2401
2463
|
}
|
|
2464
|
+
if (tool === "opencode") return opencodeMcpEntry(server, started);
|
|
2402
2465
|
result = { command: started.command, args: [...started.args] };
|
|
2403
2466
|
if (Object.keys(started.env).length > 0) {
|
|
2404
2467
|
result["env"] = { ...started.env };
|
|
@@ -2455,9 +2518,11 @@ function mcpOutputs(spec) {
|
|
|
2455
2518
|
const jsonFiles = {
|
|
2456
2519
|
claude: ".mcp.json",
|
|
2457
2520
|
gemini: ".gemini/settings.json",
|
|
2458
|
-
antigravity: `${ANTIGRAVITY_MCP_PLUGIN}/mcp_config.json
|
|
2521
|
+
antigravity: `${ANTIGRAVITY_MCP_PLUGIN}/mcp_config.json`,
|
|
2522
|
+
opencode: OPENCODE_CONFIG
|
|
2459
2523
|
};
|
|
2460
2524
|
for (const tool of spec.tools) {
|
|
2525
|
+
if (tool === "opencode") checkOpenCodeConfig(spec.root);
|
|
2461
2526
|
const servers = serversForTool(spec, tool);
|
|
2462
2527
|
if (servers.length === 0) {
|
|
2463
2528
|
continue;
|
|
@@ -2476,7 +2541,7 @@ function mcpOutputs(spec) {
|
|
|
2476
2541
|
outputs.push(
|
|
2477
2542
|
jsonOutput(
|
|
2478
2543
|
file,
|
|
2479
|
-
["mcpServers", alias],
|
|
2544
|
+
[tool === "opencode" ? "mcp" : "mcpServers", alias],
|
|
2480
2545
|
nativeEntry(spec, alias, server, tool)
|
|
2481
2546
|
)
|
|
2482
2547
|
);
|
|
@@ -2492,8 +2557,8 @@ function mcpOutputs(spec) {
|
|
|
2492
2557
|
|
|
2493
2558
|
// src/project/hooks.ts
|
|
2494
2559
|
import { createHash as createHash4 } from "crypto";
|
|
2495
|
-
import { existsSync as
|
|
2496
|
-
import { join as
|
|
2560
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
2561
|
+
import { join as join7 } from "path";
|
|
2497
2562
|
import { fileURLToPath } from "url";
|
|
2498
2563
|
var HOOK_DIRECTORY = ".agents/pmcp-hooks";
|
|
2499
2564
|
var ADAPTER = `${HOOK_DIRECTORY}/adapter.mjs`;
|
|
@@ -2523,8 +2588,8 @@ function object4(value) {
|
|
|
2523
2588
|
return value;
|
|
2524
2589
|
}
|
|
2525
2590
|
function groups(root, file, key) {
|
|
2526
|
-
const absolute =
|
|
2527
|
-
if (!
|
|
2591
|
+
const absolute = join7(root, file);
|
|
2592
|
+
if (!existsSync9(absolute)) {
|
|
2528
2593
|
return [];
|
|
2529
2594
|
}
|
|
2530
2595
|
let value;
|
|
@@ -2553,15 +2618,15 @@ function adapterSource() {
|
|
|
2553
2618
|
const sourceBuild = fileURLToPath(
|
|
2554
2619
|
new URL("../../dist/project/hook-adapter.js", import.meta.url)
|
|
2555
2620
|
);
|
|
2556
|
-
const file =
|
|
2557
|
-
if (!
|
|
2621
|
+
const file = existsSync9(published) ? published : sourceBuild;
|
|
2622
|
+
if (!existsSync9(file)) {
|
|
2558
2623
|
throw new ProjectError("Build pmcp before projecting shell hook adapters");
|
|
2559
2624
|
}
|
|
2560
2625
|
return readFileSync6(file, "utf8");
|
|
2561
2626
|
}
|
|
2562
2627
|
function hookOutputs(spec) {
|
|
2563
|
-
const lockFile =
|
|
2564
|
-
const lock =
|
|
2628
|
+
const lockFile = join7(spec.root, "pmcp.lock");
|
|
2629
|
+
const lock = existsSync9(lockFile) ? JSON.parse(readFileSync6(lockFile, "utf8")) : {};
|
|
2565
2630
|
const previous = new Map(
|
|
2566
2631
|
(lock.outputs ?? []).filter((entry) => entry.managed !== void 0).map((entry) => [entry.path, entry.managed])
|
|
2567
2632
|
);
|
|
@@ -2641,6 +2706,14 @@ function hookOutputs(spec) {
|
|
|
2641
2706
|
return outputs;
|
|
2642
2707
|
}
|
|
2643
2708
|
|
|
2709
|
+
// src/project/adapters/opencode-skills.ts
|
|
2710
|
+
function opencodeSkillOutputs(spec, names) {
|
|
2711
|
+
if (!spec.tools.has("opencode")) return [];
|
|
2712
|
+
return names.filter((name) => spec.skills[name]?.invocation === "user").map(
|
|
2713
|
+
(name) => jsonOutput(OPENCODE_CONFIG, ["permission", "skill", name], "deny")
|
|
2714
|
+
);
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2644
2717
|
// src/project/plan.ts
|
|
2645
2718
|
var AGENT_FIELDS = {
|
|
2646
2719
|
claude: ["name", "description", "tools", "model", "effort"],
|
|
@@ -2648,25 +2721,26 @@ var AGENT_FIELDS = {
|
|
|
2648
2721
|
};
|
|
2649
2722
|
var SETTINGS = ".claude/settings.json";
|
|
2650
2723
|
function listDirs(dir) {
|
|
2651
|
-
if (!
|
|
2724
|
+
if (!existsSync10(dir)) return [];
|
|
2652
2725
|
return readdirSync4(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
2653
2726
|
}
|
|
2654
2727
|
var tomlString = (value) => JSON.stringify(typeof value === "string" ? value : String(value ?? ""));
|
|
2655
2728
|
function skillNames(root) {
|
|
2656
|
-
const dir =
|
|
2729
|
+
const dir = join8(root, ".agents/skills");
|
|
2657
2730
|
return listDirs(dir).filter(
|
|
2658
|
-
(name) =>
|
|
2731
|
+
(name) => existsSync10(join8(dir, name, "SKILL.md"))
|
|
2659
2732
|
);
|
|
2660
2733
|
}
|
|
2661
2734
|
function agentNames(root) {
|
|
2662
|
-
const dir =
|
|
2735
|
+
const dir = join8(root, ".agents/agents");
|
|
2663
2736
|
return listDirs(dir).filter(
|
|
2664
|
-
(name) =>
|
|
2737
|
+
(name) => existsSync10(join8(dir, name, "agent.md"))
|
|
2665
2738
|
);
|
|
2666
2739
|
}
|
|
2667
2740
|
function skillOutputs(spec) {
|
|
2668
|
-
const
|
|
2669
|
-
|
|
2741
|
+
const names = skillNames(spec.root);
|
|
2742
|
+
const outputs = opencodeSkillOutputs(spec, names);
|
|
2743
|
+
for (const name of names) {
|
|
2670
2744
|
const target = `../../.agents/skills/${name}`;
|
|
2671
2745
|
if (spec.tools.has("claude")) {
|
|
2672
2746
|
outputs.push({ kind: "link", path: `.claude/skills/${name}`, target });
|
|
@@ -2695,13 +2769,13 @@ policy:
|
|
|
2695
2769
|
}
|
|
2696
2770
|
function ruleOutputs(spec) {
|
|
2697
2771
|
if (!spec.tools.has("claude")) return [];
|
|
2698
|
-
const dir =
|
|
2699
|
-
if (!
|
|
2772
|
+
const dir = join8(spec.root, ".agents/rules");
|
|
2773
|
+
if (!existsSync10(dir)) return [];
|
|
2700
2774
|
const outputs = [];
|
|
2701
2775
|
for (const file of readdirSync4(dir).filter((f) => f.endsWith(".md")).sort()) {
|
|
2702
2776
|
const rel = `.agents/rules/${file}`;
|
|
2703
2777
|
const { meta, body } = splitFrontmatter(
|
|
2704
|
-
readFileSync7(
|
|
2778
|
+
readFileSync7(join8(spec.root, rel), "utf8")
|
|
2705
2779
|
);
|
|
2706
2780
|
const globs = [meta["globs"] ?? []].flat().filter((g) => typeof g === "string" && g !== "");
|
|
2707
2781
|
const head = globs.length > 0 ? renderFrontmatter({ paths: globs }) : "";
|
|
@@ -2720,7 +2794,7 @@ function agentOutputs(spec) {
|
|
|
2720
2794
|
for (const name of agentNames(spec.root)) {
|
|
2721
2795
|
const rel = `.agents/agents/${name}/agent.md`;
|
|
2722
2796
|
const { meta, body } = splitFrontmatter(
|
|
2723
|
-
readFileSync7(
|
|
2797
|
+
readFileSync7(join8(spec.root, rel), "utf8")
|
|
2724
2798
|
);
|
|
2725
2799
|
const overrides = spec.agents[name] ?? {};
|
|
2726
2800
|
const note = `<!-- ${MARK} from ${rel} -->
|
|
@@ -2792,7 +2866,7 @@ function censusDirs(spec) {
|
|
|
2792
2866
|
// src/project/apply.ts
|
|
2793
2867
|
import { createHash as createHash6 } from "crypto";
|
|
2794
2868
|
import {
|
|
2795
|
-
existsSync as
|
|
2869
|
+
existsSync as existsSync12,
|
|
2796
2870
|
lstatSync as lstatSync4,
|
|
2797
2871
|
mkdirSync as mkdirSync2,
|
|
2798
2872
|
readFileSync as readFileSync9,
|
|
@@ -2800,13 +2874,13 @@ import {
|
|
|
2800
2874
|
readlinkSync as readlinkSync2,
|
|
2801
2875
|
rmSync as rmSync2
|
|
2802
2876
|
} from "fs";
|
|
2803
|
-
import { dirname as dirname8, join as
|
|
2877
|
+
import { dirname as dirname8, join as join10, posix as posix2 } from "path";
|
|
2804
2878
|
import { parse as parse4 } from "smol-toml";
|
|
2805
2879
|
|
|
2806
2880
|
// src/project/backup.ts
|
|
2807
2881
|
import { createHash as createHash5 } from "crypto";
|
|
2808
2882
|
import {
|
|
2809
|
-
existsSync as
|
|
2883
|
+
existsSync as existsSync11,
|
|
2810
2884
|
lstatSync as lstatSync2,
|
|
2811
2885
|
mkdirSync,
|
|
2812
2886
|
mkdtempSync,
|
|
@@ -2814,9 +2888,9 @@ import {
|
|
|
2814
2888
|
writeFileSync
|
|
2815
2889
|
} from "fs";
|
|
2816
2890
|
import { homedir } from "os";
|
|
2817
|
-
import { dirname as dirname6, isAbsolute as isAbsolute5, join as
|
|
2891
|
+
import { dirname as dirname6, isAbsolute as isAbsolute5, join as join9, relative as relative5, resolve as resolve6 } from "path";
|
|
2818
2892
|
function backupFiles(root, paths) {
|
|
2819
|
-
const base =
|
|
2893
|
+
const base = join9(homedir(), ".cache/pmcp/project-backups");
|
|
2820
2894
|
const inside = relative5(resolve6(root), resolve6(base));
|
|
2821
2895
|
if (!isAbsolute5(inside) && inside !== ".." && !inside.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
|
|
2822
2896
|
throw new ProjectError("Project backups must be outside the project");
|
|
@@ -2837,7 +2911,7 @@ function backupFiles(root, paths) {
|
|
|
2837
2911
|
if (isAbsolute5(within) || within === ".." || within.startsWith("../") || within.startsWith("..\\")) {
|
|
2838
2912
|
throw new ProjectError("Backup paths must stay inside the project");
|
|
2839
2913
|
}
|
|
2840
|
-
if (!
|
|
2914
|
+
if (!existsSync11(absolute)) {
|
|
2841
2915
|
return { path, contents: null };
|
|
2842
2916
|
}
|
|
2843
2917
|
const stat = lstatSync2(absolute);
|
|
@@ -2848,12 +2922,12 @@ function backupFiles(root, paths) {
|
|
|
2848
2922
|
}
|
|
2849
2923
|
return { path, contents: readFileSync8(absolute) };
|
|
2850
2924
|
});
|
|
2851
|
-
const destination = mkdtempSync(
|
|
2925
|
+
const destination = mkdtempSync(join9(base, "project-"));
|
|
2852
2926
|
const entries = snapshots.map(({ path, contents }) => {
|
|
2853
2927
|
if (contents === null) {
|
|
2854
2928
|
return { path, present: false };
|
|
2855
2929
|
}
|
|
2856
|
-
const output =
|
|
2930
|
+
const output = join9(destination, "files", path);
|
|
2857
2931
|
mkdirSync(dirname6(output), { recursive: true, mode: 448 });
|
|
2858
2932
|
writeFileSync(output, contents, { mode: 384 });
|
|
2859
2933
|
return {
|
|
@@ -2863,7 +2937,7 @@ function backupFiles(root, paths) {
|
|
|
2863
2937
|
};
|
|
2864
2938
|
});
|
|
2865
2939
|
writeFileSync(
|
|
2866
|
-
|
|
2940
|
+
join9(destination, "receipt.json"),
|
|
2867
2941
|
JSON.stringify(
|
|
2868
2942
|
{
|
|
2869
2943
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2949,8 +3023,8 @@ var PREDECESSORS = [
|
|
|
2949
3023
|
];
|
|
2950
3024
|
var sha = (text) => createHash6("sha256").update(text).digest("hex");
|
|
2951
3025
|
function readLock(root) {
|
|
2952
|
-
const path =
|
|
2953
|
-
if (!
|
|
3026
|
+
const path = join10(root, LOCK_FILE);
|
|
3027
|
+
if (!existsSync12(path)) return [];
|
|
2954
3028
|
const lock = JSON.parse(readFileSync9(path, "utf8"));
|
|
2955
3029
|
return lock.outputs ?? [];
|
|
2956
3030
|
}
|
|
@@ -2961,9 +3035,9 @@ function linkOf(abs) {
|
|
|
2961
3035
|
return null;
|
|
2962
3036
|
}
|
|
2963
3037
|
}
|
|
2964
|
-
var exists = (abs) =>
|
|
3038
|
+
var exists = (abs) => existsSync12(abs) || linkOf(abs) !== null;
|
|
2965
3039
|
function readJson2(abs) {
|
|
2966
|
-
if (!
|
|
3040
|
+
if (!existsSync12(abs)) return {};
|
|
2967
3041
|
let value;
|
|
2968
3042
|
try {
|
|
2969
3043
|
value = JSON.parse(readFileSync9(abs, "utf8"));
|
|
@@ -3018,32 +3092,44 @@ function withoutBlock(text) {
|
|
|
3018
3092
|
return `${text.slice(0, start).replace(/\n+$/u, "\n")}${after}`;
|
|
3019
3093
|
}
|
|
3020
3094
|
function present(root, output) {
|
|
3021
|
-
const abs =
|
|
3095
|
+
const abs = join10(root, "file" in output ? output.file : output.path);
|
|
3022
3096
|
switch (output.kind) {
|
|
3023
3097
|
case "link":
|
|
3024
3098
|
return linkOf(abs) === output.target;
|
|
3025
3099
|
case "file":
|
|
3026
|
-
return
|
|
3100
|
+
return existsSync12(abs) && linkOf(abs) === null && readFileSync9(abs, "utf8") === output.content;
|
|
3027
3101
|
case "json":
|
|
3028
3102
|
if (output.managed && Array.isArray(output.value) && output.value.length === 0) {
|
|
3029
3103
|
return getKey(readJson2(abs), output.key) === void 0;
|
|
3030
3104
|
}
|
|
3031
3105
|
return same(getKey(readJson2(abs), output.key), output.value);
|
|
3032
3106
|
case "block":
|
|
3033
|
-
return
|
|
3107
|
+
return existsSync12(abs) && readFileSync9(abs, "utf8").includes(block(output.content));
|
|
3034
3108
|
}
|
|
3035
3109
|
}
|
|
3036
3110
|
function replaceable(root, output, owned) {
|
|
3037
3111
|
if (output.kind === "json") {
|
|
3038
|
-
if (output.
|
|
3112
|
+
if (output.file === "opencode.json") {
|
|
3113
|
+
let parent = readJson2(join10(root, output.file));
|
|
3114
|
+
for (const part of output.key.slice(0, -1)) {
|
|
3115
|
+
if (parent === void 0) break;
|
|
3116
|
+
if (parent === null || typeof parent !== "object" || Array.isArray(parent))
|
|
3117
|
+
return false;
|
|
3118
|
+
parent = parent[part];
|
|
3119
|
+
}
|
|
3120
|
+
if (parent !== void 0 && (parent === null || typeof parent !== "object" || Array.isArray(parent)))
|
|
3121
|
+
return false;
|
|
3122
|
+
}
|
|
3123
|
+
const guarded = output.key[0] === "mcpServers" || output.file === "opencode.json";
|
|
3124
|
+
if (!guarded || owned.has(output.path)) {
|
|
3039
3125
|
return true;
|
|
3040
3126
|
}
|
|
3041
|
-
const existing = getKey(readJson2(
|
|
3127
|
+
const existing = getKey(readJson2(join10(root, output.file)), output.key);
|
|
3042
3128
|
return existing === void 0 || same(existing, output.value);
|
|
3043
3129
|
}
|
|
3044
3130
|
if (output.kind === "block") {
|
|
3045
|
-
const absolute =
|
|
3046
|
-
const text2 =
|
|
3131
|
+
const absolute = join10(root, output.file);
|
|
3132
|
+
const text2 = existsSync12(absolute) ? readFileSync9(absolute, "utf8") : "";
|
|
3047
3133
|
const outside = parse4(withoutBlock(text2));
|
|
3048
3134
|
const proposed = parse4(output.content);
|
|
3049
3135
|
const existing = objectKeys(outside["mcp_servers"]);
|
|
@@ -3052,7 +3138,7 @@ function replaceable(root, output, owned) {
|
|
|
3052
3138
|
);
|
|
3053
3139
|
}
|
|
3054
3140
|
if (owned.has(output.path)) return true;
|
|
3055
|
-
const abs =
|
|
3141
|
+
const abs = join10(root, output.path);
|
|
3056
3142
|
if (!exists(abs)) return true;
|
|
3057
3143
|
const target = linkOf(abs);
|
|
3058
3144
|
if (target !== null) {
|
|
@@ -3073,15 +3159,15 @@ function undeclared(spec, outputs) {
|
|
|
3073
3159
|
const declared = new Set(outputs.map((o) => o.path));
|
|
3074
3160
|
const found = [];
|
|
3075
3161
|
for (const dir of censusDirs(spec)) {
|
|
3076
|
-
const abs =
|
|
3077
|
-
if (!
|
|
3162
|
+
const abs = join10(spec.root, dir);
|
|
3163
|
+
if (!existsSync12(abs)) continue;
|
|
3078
3164
|
for (const entry of readdirSync5(abs)) {
|
|
3079
3165
|
const path = `${dir}/${entry}`;
|
|
3080
3166
|
if (!declared.has(path)) found.push(path);
|
|
3081
3167
|
}
|
|
3082
3168
|
}
|
|
3083
|
-
const rules =
|
|
3084
|
-
if (spec.tools.has("grok") &&
|
|
3169
|
+
const rules = join10(spec.root, RULES_DIR);
|
|
3170
|
+
if (spec.tools.has("grok") && existsSync12(rules)) {
|
|
3085
3171
|
for (const entry of readdirSync5(rules, { withFileTypes: true })) {
|
|
3086
3172
|
if (entry.isDirectory()) continue;
|
|
3087
3173
|
found.push(`${RULES_DIR}/${entry.name}`);
|
|
@@ -3108,8 +3194,8 @@ function deadGlobs(root, outputs) {
|
|
|
3108
3194
|
if (rule.kind === "file") {
|
|
3109
3195
|
text = rule.content;
|
|
3110
3196
|
} else if (rule.kind === "link") {
|
|
3111
|
-
const abs =
|
|
3112
|
-
if (!
|
|
3197
|
+
const abs = join10(root, posix2.join(posix2.dirname(rule.path), rule.target));
|
|
3198
|
+
if (!existsSync12(abs)) continue;
|
|
3113
3199
|
text = readFileSync9(abs, "utf8");
|
|
3114
3200
|
} else {
|
|
3115
3201
|
continue;
|
|
@@ -3160,7 +3246,7 @@ function project(spec, options) {
|
|
|
3160
3246
|
}
|
|
3161
3247
|
}
|
|
3162
3248
|
for (const path of jsonPaths) {
|
|
3163
|
-
readJson2(
|
|
3249
|
+
readJson2(join10(spec.root, path));
|
|
3164
3250
|
}
|
|
3165
3251
|
if (options.check) {
|
|
3166
3252
|
const problems = [
|
|
@@ -3181,6 +3267,7 @@ function project(spec, options) {
|
|
|
3181
3267
|
return { code: 3, problems: refused, outputs: outputs.length, pruned: 0 };
|
|
3182
3268
|
}
|
|
3183
3269
|
const mcpPaths = [
|
|
3270
|
+
"opencode.json",
|
|
3184
3271
|
".mcp.json",
|
|
3185
3272
|
".gemini/settings.json",
|
|
3186
3273
|
".agents/mcp_config.json",
|
|
@@ -3222,7 +3309,7 @@ function project(spec, options) {
|
|
|
3222
3309
|
for (const output of outputs) {
|
|
3223
3310
|
if (output.kind !== "link" && output.kind !== "file") continue;
|
|
3224
3311
|
if (present(spec.root, output)) continue;
|
|
3225
|
-
const abs =
|
|
3312
|
+
const abs = join10(spec.root, output.path);
|
|
3226
3313
|
mkdirSync2(dirname8(abs), { recursive: true });
|
|
3227
3314
|
if (output.kind === "link") {
|
|
3228
3315
|
replaceLink(abs, output.target);
|
|
@@ -3250,12 +3337,12 @@ function project(spec, options) {
|
|
|
3250
3337
|
group.drops.push(keyed.key);
|
|
3251
3338
|
jsonFiles.set(keyed.file, group);
|
|
3252
3339
|
} else {
|
|
3253
|
-
rmSync2(
|
|
3340
|
+
rmSync2(join10(spec.root, entry.path), { force: true, recursive: true });
|
|
3254
3341
|
}
|
|
3255
3342
|
pruned += 1;
|
|
3256
3343
|
}
|
|
3257
3344
|
for (const [file, { sets, drops }] of jsonFiles) {
|
|
3258
|
-
const abs =
|
|
3345
|
+
const abs = join10(spec.root, file);
|
|
3259
3346
|
const before = readJson2(abs);
|
|
3260
3347
|
const after = structuredClone(before);
|
|
3261
3348
|
for (const key of drops) deleteKey(after, key);
|
|
@@ -3270,14 +3357,14 @@ function project(spec, options) {
|
|
|
3270
3357
|
}
|
|
3271
3358
|
}
|
|
3272
3359
|
if (same(before, after)) continue;
|
|
3273
|
-
if (!
|
|
3360
|
+
if (!existsSync12(abs) && Object.keys(after).length === 0) continue;
|
|
3274
3361
|
mkdirSync2(dirname8(abs), { recursive: true });
|
|
3275
3362
|
replaceFile(abs, `${JSON.stringify(after, null, 2)}
|
|
3276
3363
|
`);
|
|
3277
3364
|
}
|
|
3278
3365
|
for (const [file, content] of blockFiles) {
|
|
3279
|
-
const abs =
|
|
3280
|
-
const before =
|
|
3366
|
+
const abs = join10(spec.root, file);
|
|
3367
|
+
const before = existsSync12(abs) ? readFileSync9(abs, "utf8") : "";
|
|
3281
3368
|
let after = withoutBlock(before);
|
|
3282
3369
|
if (content !== null) {
|
|
3283
3370
|
const sep5 = after === "" || after.endsWith("\n\n") ? "" : after.endsWith("\n") ? "\n" : "\n\n";
|
|
@@ -3296,15 +3383,15 @@ function project(spec, options) {
|
|
|
3296
3383
|
)
|
|
3297
3384
|
);
|
|
3298
3385
|
for (const [name, target] of legacyLinks(spec.root)) {
|
|
3299
|
-
const abs =
|
|
3300
|
-
const broken = !
|
|
3386
|
+
const abs = join10(spec.root, RULES_DIR, `${name}.md`);
|
|
3387
|
+
const broken = !existsSync12(abs);
|
|
3301
3388
|
if (!broken && !linkedDirs.has(posix2.dirname(target))) continue;
|
|
3302
3389
|
rmSync2(abs, { force: true });
|
|
3303
3390
|
pruned += 1;
|
|
3304
3391
|
}
|
|
3305
3392
|
}
|
|
3306
3393
|
replaceFile(
|
|
3307
|
-
|
|
3394
|
+
join10(spec.root, LOCK_FILE),
|
|
3308
3395
|
`${JSON.stringify({ version: 1, outputs: outputs.filter((output) => output.kind !== "json" || output.managed === void 0 || output.managed.length > 0).map(lockEntry) }, null, 2)}
|
|
3309
3396
|
`
|
|
3310
3397
|
);
|
|
@@ -3318,10 +3405,83 @@ function project(spec, options) {
|
|
|
3318
3405
|
}
|
|
3319
3406
|
|
|
3320
3407
|
// src/project/doctor.ts
|
|
3321
|
-
import { spawnSync as
|
|
3322
|
-
import { existsSync as
|
|
3323
|
-
import { join as
|
|
3408
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
3409
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
3410
|
+
import { join as join13 } from "path";
|
|
3324
3411
|
import { parse as parse5 } from "smol-toml";
|
|
3412
|
+
|
|
3413
|
+
// src/project/adapters/opencode-doctor.ts
|
|
3414
|
+
import { realpathSync as realpathSync3 } from "fs";
|
|
3415
|
+
import { join as join11 } from "path";
|
|
3416
|
+
function connected(out, alias) {
|
|
3417
|
+
const ansi = new RegExp(
|
|
3418
|
+
`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`,
|
|
3419
|
+
"gu"
|
|
3420
|
+
);
|
|
3421
|
+
const pattern = new RegExp(
|
|
3422
|
+
`^[^a-zA-Z0-9]*${alias}\\s+connected(?:\\s|$)`,
|
|
3423
|
+
"u"
|
|
3424
|
+
);
|
|
3425
|
+
return out.replace(ansi, "").split("\n").some((line) => pattern.test(line.trim()));
|
|
3426
|
+
}
|
|
3427
|
+
function projectSkill(out, root, name) {
|
|
3428
|
+
try {
|
|
3429
|
+
const entries = JSON.parse(out);
|
|
3430
|
+
if (!Array.isArray(entries)) return false;
|
|
3431
|
+
const expected = realpathSync3(
|
|
3432
|
+
join11(root, ".agents/skills", name, "SKILL.md")
|
|
3433
|
+
);
|
|
3434
|
+
return entries.some(
|
|
3435
|
+
(entry) => entry?.name === name && typeof entry.location === "string" && realpathSync3(entry.location) === expected
|
|
3436
|
+
);
|
|
3437
|
+
} catch {
|
|
3438
|
+
return false;
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
function opencodeDoctor(spec, options, listed2) {
|
|
3442
|
+
const findings = [];
|
|
3443
|
+
const names = skillNames(spec.root);
|
|
3444
|
+
if (names.length > 0) {
|
|
3445
|
+
findings.push(
|
|
3446
|
+
listed2(
|
|
3447
|
+
"opencode",
|
|
3448
|
+
"project skills discovered",
|
|
3449
|
+
options.run("opencode", ["debug", "skill"], spec.root, options.env),
|
|
3450
|
+
names,
|
|
3451
|
+
(out, name) => projectSkill(out, spec.root, name)
|
|
3452
|
+
)
|
|
3453
|
+
);
|
|
3454
|
+
}
|
|
3455
|
+
const aliases = serversForTool(spec, "opencode").map(([alias]) => alias);
|
|
3456
|
+
if (aliases.length > 0) {
|
|
3457
|
+
findings.push(
|
|
3458
|
+
listed2(
|
|
3459
|
+
"opencode",
|
|
3460
|
+
"mcp servers connected",
|
|
3461
|
+
options.run("opencode", ["mcp", "list"], spec.root, options.env),
|
|
3462
|
+
aliases,
|
|
3463
|
+
connected
|
|
3464
|
+
)
|
|
3465
|
+
);
|
|
3466
|
+
}
|
|
3467
|
+
return findings;
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3470
|
+
// src/project/diagnostics/native-json.ts
|
|
3471
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
3472
|
+
import {
|
|
3473
|
+
closeSync as closeSync2,
|
|
3474
|
+
mkdtempSync as mkdtempSync2,
|
|
3475
|
+
openSync as openSync2,
|
|
3476
|
+
readFileSync as readFileSync10,
|
|
3477
|
+
rmSync as rmSync3,
|
|
3478
|
+
statSync as statSync3
|
|
3479
|
+
} from "fs";
|
|
3480
|
+
import { tmpdir } from "os";
|
|
3481
|
+
import { join as join12 } from "path";
|
|
3482
|
+
var MAX_OUTPUT = 1 << 26;
|
|
3483
|
+
|
|
3484
|
+
// src/project/doctor.ts
|
|
3325
3485
|
var missingFrom = (names, present2) => names.filter((name) => !present2(name));
|
|
3326
3486
|
function verdict(tool, check, missing) {
|
|
3327
3487
|
if (missing.length === 0) return { tool, check, status: "ok" };
|
|
@@ -3382,10 +3542,10 @@ function claude(spec, o) {
|
|
|
3382
3542
|
}
|
|
3383
3543
|
function codex(spec, o) {
|
|
3384
3544
|
const findings = [];
|
|
3385
|
-
const config =
|
|
3545
|
+
const config = join13(o.home, ".codex/config.toml");
|
|
3386
3546
|
let trusted = false;
|
|
3387
|
-
if (
|
|
3388
|
-
const projects = parse5(
|
|
3547
|
+
if (existsSync13(config)) {
|
|
3548
|
+
const projects = parse5(readFileSync11(config, "utf8")).projects ?? {};
|
|
3389
3549
|
const root = spec.root.replace(/\/$/u, "");
|
|
3390
3550
|
trusted = projects[root]?.trust_level === "trusted" || projects[`${root}/`]?.trust_level === "trusted";
|
|
3391
3551
|
}
|
|
@@ -3511,8 +3671,8 @@ function grok(spec, o) {
|
|
|
3511
3671
|
}
|
|
3512
3672
|
function antigravity(spec, options) {
|
|
3513
3673
|
const foreign = agentNames(spec.root).filter((name) => {
|
|
3514
|
-
const text =
|
|
3515
|
-
|
|
3674
|
+
const text = readFileSync11(
|
|
3675
|
+
join13(spec.root, ".agents/agents", name, "agent.md"),
|
|
3516
3676
|
"utf8"
|
|
3517
3677
|
);
|
|
3518
3678
|
return splitFrontmatter(text).meta["tools"] !== void 0;
|
|
@@ -3532,7 +3692,7 @@ function antigravity(spec, options) {
|
|
|
3532
3692
|
if (serversForTool(spec, "antigravity").length > 0) {
|
|
3533
3693
|
const result = options.run(
|
|
3534
3694
|
"agy",
|
|
3535
|
-
["plugin", "validate",
|
|
3695
|
+
["plugin", "validate", join13(spec.root, ANTIGRAVITY_MCP_PLUGIN)],
|
|
3536
3696
|
spec.root
|
|
3537
3697
|
);
|
|
3538
3698
|
findings.push(
|
|
@@ -3556,6 +3716,8 @@ function doctor(spec, options) {
|
|
|
3556
3716
|
if (wanted("codex")) findings.push(...codex(spec, options));
|
|
3557
3717
|
if (wanted("gemini")) findings.push(...gemini(spec, options));
|
|
3558
3718
|
if (wanted("grok")) findings.push(...grok(spec, options));
|
|
3719
|
+
if (wanted("opencode"))
|
|
3720
|
+
findings.push(...opencodeDoctor(spec, options, listed));
|
|
3559
3721
|
if (wanted("antigravity")) {
|
|
3560
3722
|
findings.push(...antigravity(spec, options));
|
|
3561
3723
|
}
|
|
@@ -3563,11 +3725,11 @@ function doctor(spec, options) {
|
|
|
3563
3725
|
}
|
|
3564
3726
|
|
|
3565
3727
|
// src/server.ts
|
|
3566
|
-
import { readFileSync as
|
|
3728
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
3567
3729
|
|
|
3568
3730
|
// src/files/bundle.ts
|
|
3569
|
-
import { readFileSync as
|
|
3570
|
-
import { dirname as dirname9, join as
|
|
3731
|
+
import { readFileSync as readFileSync13, statSync as statSync5 } from "fs";
|
|
3732
|
+
import { dirname as dirname9, join as join15 } from "path";
|
|
3571
3733
|
|
|
3572
3734
|
// src/mime.ts
|
|
3573
3735
|
var TYPES = {
|
|
@@ -3674,8 +3836,8 @@ function classify(path, bytes) {
|
|
|
3674
3836
|
|
|
3675
3837
|
// src/files/list.ts
|
|
3676
3838
|
import { createHash as createHash7 } from "crypto";
|
|
3677
|
-
import { readFileSync as
|
|
3678
|
-
import { basename as basename4, join as
|
|
3839
|
+
import { readFileSync as readFileSync12, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
3840
|
+
import { basename as basename4, join as join14, relative as relative6, sep as sep2 } from "path";
|
|
3679
3841
|
|
|
3680
3842
|
// src/files/redact.ts
|
|
3681
3843
|
import { basename as basename3 } from "path";
|
|
@@ -3712,11 +3874,11 @@ function servedBytes(path, raw) {
|
|
|
3712
3874
|
var FILE_LIST_LIMIT = 200;
|
|
3713
3875
|
var sha256 = (bytes) => createHash7("sha256").update(bytes).digest("hex");
|
|
3714
3876
|
function describe(path, full) {
|
|
3715
|
-
const bytes = servedBytes(full,
|
|
3877
|
+
const bytes = servedBytes(full, readFileSync12(full));
|
|
3716
3878
|
return { path, bytes: bytes.length, sha256: sha256(bytes) };
|
|
3717
3879
|
}
|
|
3718
3880
|
function listSkillFiles(root, limit = FILE_LIST_LIMIT) {
|
|
3719
|
-
if (
|
|
3881
|
+
if (statSync4(root).isFile()) {
|
|
3720
3882
|
return { files: [describe(basename4(root), root)], truncated: false };
|
|
3721
3883
|
}
|
|
3722
3884
|
const files = [];
|
|
@@ -3725,7 +3887,7 @@ function listSkillFiles(root, limit = FILE_LIST_LIMIT) {
|
|
|
3725
3887
|
const dir = stack.pop();
|
|
3726
3888
|
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
3727
3889
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
3728
|
-
const full =
|
|
3890
|
+
const full = join14(dir, entry.name);
|
|
3729
3891
|
if (entry.isDirectory()) {
|
|
3730
3892
|
stack.push(full);
|
|
3731
3893
|
continue;
|
|
@@ -3759,7 +3921,7 @@ function resolveSkillFileUri(uri, entries) {
|
|
|
3759
3921
|
// src/files/bundle.ts
|
|
3760
3922
|
function bundleSkill(entry) {
|
|
3761
3923
|
const root = assetRoot(entry);
|
|
3762
|
-
const base =
|
|
3924
|
+
const base = statSync5(root).isFile() ? dirname9(root) : root;
|
|
3763
3925
|
const listing2 = listSkillFiles(root);
|
|
3764
3926
|
return {
|
|
3765
3927
|
name: entry.name,
|
|
@@ -3767,28 +3929,28 @@ function bundleSkill(entry) {
|
|
|
3767
3929
|
files: listing2.files.map((file) => ({
|
|
3768
3930
|
...file,
|
|
3769
3931
|
uri: skillFileUri(entry, file.path),
|
|
3770
|
-
mimeType: classify(file.path,
|
|
3932
|
+
mimeType: classify(file.path, readFileSync13(join15(base, file.path))).mimeType
|
|
3771
3933
|
}))
|
|
3772
3934
|
};
|
|
3773
3935
|
}
|
|
3774
3936
|
|
|
3775
3937
|
// src/files/read.ts
|
|
3776
|
-
import { readFileSync as
|
|
3938
|
+
import { readFileSync as readFileSync14, statSync as statSync7 } from "fs";
|
|
3777
3939
|
|
|
3778
3940
|
// src/files/resolve.ts
|
|
3779
|
-
import { realpathSync as
|
|
3941
|
+
import { realpathSync as realpathSync4, statSync as statSync6 } from "fs";
|
|
3780
3942
|
import { dirname as dirname10, isAbsolute as isAbsolute6, relative as relative7, resolve as resolve7, sep as sep3 } from "path";
|
|
3781
3943
|
var ownFile = (entry) => relative7(assetRoot(entry), entry.path).split(sep3).join("/");
|
|
3782
3944
|
function resolveSkillFile(entry, wanted = ownFile(entry)) {
|
|
3783
3945
|
if (isAbsolute6(wanted) || wanted.split(/[\\/]/u).includes("..")) {
|
|
3784
3946
|
return { ok: false, error: "path_outside_skill", detail: wanted };
|
|
3785
3947
|
}
|
|
3786
|
-
const scope =
|
|
3787
|
-
const single =
|
|
3948
|
+
const scope = realpathSync4(assetRoot(entry));
|
|
3949
|
+
const single = statSync6(scope).isFile();
|
|
3788
3950
|
const root = single ? dirname10(scope) : scope;
|
|
3789
3951
|
let target;
|
|
3790
3952
|
try {
|
|
3791
|
-
target =
|
|
3953
|
+
target = realpathSync4(
|
|
3792
3954
|
single && wanted === "" ? scope : resolve7(root, wanted)
|
|
3793
3955
|
);
|
|
3794
3956
|
} catch {
|
|
@@ -3809,7 +3971,7 @@ var READ_BUDGET_BYTES = 256 * 1024;
|
|
|
3809
3971
|
function readSkillFile(entry, wanted, budget = READ_BUDGET_BYTES) {
|
|
3810
3972
|
const resolved = resolveSkillFile(entry, wanted);
|
|
3811
3973
|
if (!resolved.ok) return resolved;
|
|
3812
|
-
const size =
|
|
3974
|
+
const size = statSync7(resolved.target).size;
|
|
3813
3975
|
if (size > budget) {
|
|
3814
3976
|
return {
|
|
3815
3977
|
ok: false,
|
|
@@ -3817,7 +3979,7 @@ function readSkillFile(entry, wanted, budget = READ_BUDGET_BYTES) {
|
|
|
3817
3979
|
detail: `${size} bytes > ${budget}`
|
|
3818
3980
|
};
|
|
3819
3981
|
}
|
|
3820
|
-
const bytes = servedBytes(resolved.target,
|
|
3982
|
+
const bytes = servedBytes(resolved.target, readFileSync14(resolved.target));
|
|
3821
3983
|
const { kind: kind2, mimeType } = classify(resolved.path, bytes);
|
|
3822
3984
|
return {
|
|
3823
3985
|
ok: true,
|
|
@@ -3993,7 +4155,7 @@ function createSkillTools(options) {
|
|
|
3993
4155
|
describe(name) {
|
|
3994
4156
|
const entry = byName(name);
|
|
3995
4157
|
if (!entry) return null;
|
|
3996
|
-
const source =
|
|
4158
|
+
const source = readFileSync15(entry.path, "utf8");
|
|
3997
4159
|
const listing2 = listSkillFiles(assetRoot(entry));
|
|
3998
4160
|
return {
|
|
3999
4161
|
name: entry.name,
|
|
@@ -4030,7 +4192,7 @@ function createSkillTools(options) {
|
|
|
4030
4192
|
return {
|
|
4031
4193
|
name: entry.name,
|
|
4032
4194
|
body: readBody(
|
|
4033
|
-
servedBytes(entry.path,
|
|
4195
|
+
servedBytes(entry.path, readFileSync15(entry.path)).toString("utf8")
|
|
4034
4196
|
),
|
|
4035
4197
|
files: bundle.files.filter((file) => file.path !== own),
|
|
4036
4198
|
filesTruncated: bundle.truncated
|
|
@@ -4099,7 +4261,7 @@ function registerSkillPrompts(server, tools) {
|
|
|
4099
4261
|
}
|
|
4100
4262
|
|
|
4101
4263
|
// src/resources.ts
|
|
4102
|
-
import { statSync as
|
|
4264
|
+
import { statSync as statSync8 } from "fs";
|
|
4103
4265
|
import {
|
|
4104
4266
|
ResourceNotFoundError,
|
|
4105
4267
|
ResourceTemplate
|
|
@@ -4112,7 +4274,7 @@ function listing(entries) {
|
|
|
4112
4274
|
name: entry.name,
|
|
4113
4275
|
description: entry.description,
|
|
4114
4276
|
mimeType: "text/markdown",
|
|
4115
|
-
size:
|
|
4277
|
+
size: statSync8(entry.path).size
|
|
4116
4278
|
}));
|
|
4117
4279
|
}
|
|
4118
4280
|
function complete(value, entries) {
|
|
@@ -4150,17 +4312,17 @@ function registerSkillResources(server, skills) {
|
|
|
4150
4312
|
|
|
4151
4313
|
// src/extension/entry.ts
|
|
4152
4314
|
import { createHash as createHash8 } from "crypto";
|
|
4153
|
-
import { readFileSync as
|
|
4154
|
-
import { basename as basename5, dirname as dirname11, join as
|
|
4315
|
+
import { readFileSync as readFileSync16, readdirSync as readdirSync7, statSync as statSync9 } from "fs";
|
|
4316
|
+
import { basename as basename5, dirname as dirname11, join as join16, relative as relative8, sep as sep4 } from "path";
|
|
4155
4317
|
var MAX_RESOURCES = 512;
|
|
4156
4318
|
var MAX_SKILL_BYTES = 16 * 1024 * 1024;
|
|
4157
4319
|
var digests = /* @__PURE__ */ new Map();
|
|
4158
4320
|
function facts(path) {
|
|
4159
|
-
const stat =
|
|
4321
|
+
const stat = statSync9(path);
|
|
4160
4322
|
const key = `${stat.mtimeMs}:${stat.size}`;
|
|
4161
4323
|
const cached = digests.get(path);
|
|
4162
4324
|
if (cached?.key === key) return cached;
|
|
4163
|
-
const bytes = servedBytes(path,
|
|
4325
|
+
const bytes = servedBytes(path, readFileSync16(path));
|
|
4164
4326
|
const digest = `sha256:${createHash8("sha256").update(bytes).digest("hex")}`;
|
|
4165
4327
|
const fresh = { key, digest, size: bytes.length };
|
|
4166
4328
|
digests.set(path, fresh);
|
|
@@ -4173,7 +4335,7 @@ function filesUnder(root) {
|
|
|
4173
4335
|
const dir = stack.pop();
|
|
4174
4336
|
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
4175
4337
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
4176
|
-
const full =
|
|
4338
|
+
const full = join16(dir, entry.name);
|
|
4177
4339
|
if (entry.isDirectory()) stack.push(full);
|
|
4178
4340
|
else if (entry.isFile()) found.push(full);
|
|
4179
4341
|
if (found.length > MAX_RESOURCES) return null;
|
|
@@ -4184,7 +4346,7 @@ function filesUnder(root) {
|
|
|
4184
4346
|
function extensionEntry(entry) {
|
|
4185
4347
|
if (kindOf(entry) !== "skill" || basename5(entry.path) !== "SKILL.md")
|
|
4186
4348
|
return null;
|
|
4187
|
-
const frontmatter = frontmatterObject(
|
|
4349
|
+
const frontmatter = frontmatterObject(readFileSync16(entry.path, "utf8"));
|
|
4188
4350
|
const root = dirname11(entry.path);
|
|
4189
4351
|
if (specProblems(frontmatter, basename5(root)).length > 0) return null;
|
|
4190
4352
|
if (frontmatter?.["name"] !== entry.slug) return null;
|
|
@@ -4679,7 +4841,7 @@ function registerMailboxTools(server, service) {
|
|
|
4679
4841
|
|
|
4680
4842
|
// src/mcp.ts
|
|
4681
4843
|
var SERVER_NAME = "pmcp";
|
|
4682
|
-
var SERVER_VERSION = "0.
|
|
4844
|
+
var SERVER_VERSION = "0.17.1";
|
|
4683
4845
|
function registerSkillTools(server, tools) {
|
|
4684
4846
|
registerCatalogTool(server, tools);
|
|
4685
4847
|
registerFindTool(server, tools);
|
|
@@ -4700,16 +4862,16 @@ function createSkillServer(options) {
|
|
|
4700
4862
|
}
|
|
4701
4863
|
|
|
4702
4864
|
// src/validate.ts
|
|
4703
|
-
import { existsSync as
|
|
4704
|
-
import { basename as basename6, join as
|
|
4865
|
+
import { existsSync as existsSync14, readFileSync as readFileSync17, statSync as statSync10 } from "fs";
|
|
4866
|
+
import { basename as basename6, join as join17, resolve as resolve8 } from "path";
|
|
4705
4867
|
function validateSkillDir(dir) {
|
|
4706
4868
|
const root = resolve8(dir);
|
|
4707
|
-
const skillMd = basename6(root) === "SKILL.md" ? root :
|
|
4708
|
-
if (!
|
|
4869
|
+
const skillMd = basename6(root) === "SKILL.md" ? root : join17(root, "SKILL.md");
|
|
4870
|
+
if (!existsSync14(skillMd) || !statSync10(skillMd).isFile()) {
|
|
4709
4871
|
return [{ path: root, reason: "no SKILL.md", level: "error" }];
|
|
4710
4872
|
}
|
|
4711
|
-
const front = frontmatterObject(
|
|
4712
|
-
const directory = basename6(skillMd === root ?
|
|
4873
|
+
const front = frontmatterObject(readFileSync17(skillMd, "utf8"));
|
|
4874
|
+
const directory = basename6(skillMd === root ? join17(root, "..") : root);
|
|
4713
4875
|
return specProblems(front, directory).map((reason) => ({
|
|
4714
4876
|
path: skillMd,
|
|
4715
4877
|
reason,
|