@modootoday/pmcp 0.14.1 → 0.15.0
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 +46 -8
- package/dist/cli.js +1073 -530
- package/dist/http.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +87 -25
- package/dist/stdio.js +1 -1
- package/dist/tui-worker.js +314 -13
- package/docs/skills.md +3 -3
- package/docs/terminal-workspace.md +63 -0
- package/gemini-extension.json +1 -1
- package/package.json +3 -2
- 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/cli.js
CHANGED
|
@@ -9,6 +9,23 @@ var __export = (target2, all) => {
|
|
|
9
9
|
__defProp(target2, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/cli/entry.ts
|
|
13
|
+
function interactiveEntry(terminal) {
|
|
14
|
+
if (!terminal.stdin || !terminal.stdout) return false;
|
|
15
|
+
if (!terminal.term || terminal.term === "dumb") return false;
|
|
16
|
+
return !terminal.ci || terminal.ci === "false" || terminal.ci === "0";
|
|
17
|
+
}
|
|
18
|
+
function entryMode(terminal) {
|
|
19
|
+
if (!terminal.stdin || !terminal.stdout) return "server";
|
|
20
|
+
if (!interactiveEntry(terminal)) return "help";
|
|
21
|
+
return "terminal";
|
|
22
|
+
}
|
|
23
|
+
var init_entry = __esm({
|
|
24
|
+
"src/cli/entry.ts"() {
|
|
25
|
+
"use strict";
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
12
29
|
// src/storage/sqlite.ts
|
|
13
30
|
async function openSqlite(path, options2 = {}) {
|
|
14
31
|
const provider = process.versions.bun ? "bun:sqlite" : "node:sqlite";
|
|
@@ -245,6 +262,7 @@ function findConfig(start2) {
|
|
|
245
262
|
for (; ; ) {
|
|
246
263
|
const candidate2 = join4(dir, CONFIG_FILE);
|
|
247
264
|
if (existsSync4(candidate2)) return candidate2;
|
|
265
|
+
if (existsSync4(join4(dir, ".git"))) return null;
|
|
248
266
|
const parent = dirname3(dir);
|
|
249
267
|
if (parent === dir) return null;
|
|
250
268
|
dir = parent;
|
|
@@ -1920,47 +1938,47 @@ var init_validation2 = __esm({
|
|
|
1920
1938
|
});
|
|
1921
1939
|
|
|
1922
1940
|
// src/harness/groups/store.ts
|
|
1923
|
-
import { randomUUID as
|
|
1941
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
1924
1942
|
import {
|
|
1925
|
-
closeSync as
|
|
1943
|
+
closeSync as closeSync4,
|
|
1926
1944
|
constants as constants3,
|
|
1927
1945
|
fstatSync as fstatSync2,
|
|
1928
|
-
fsyncSync,
|
|
1929
|
-
openSync as
|
|
1930
|
-
readFileSync as
|
|
1931
|
-
realpathSync as
|
|
1932
|
-
renameSync as
|
|
1946
|
+
fsyncSync as fsyncSync2,
|
|
1947
|
+
openSync as openSync4,
|
|
1948
|
+
readFileSync as readFileSync34,
|
|
1949
|
+
realpathSync as realpathSync6,
|
|
1950
|
+
renameSync as renameSync4,
|
|
1933
1951
|
writeFileSync as writeFileSync9
|
|
1934
1952
|
} from "fs";
|
|
1935
|
-
import { dirname as
|
|
1953
|
+
import { dirname as dirname21, join as join29 } from "path";
|
|
1936
1954
|
function privateJson(path, value) {
|
|
1937
|
-
const temporary = `${path}.${
|
|
1938
|
-
const descriptor =
|
|
1955
|
+
const temporary = `${path}.${randomUUID5()}.new`;
|
|
1956
|
+
const descriptor = openSync4(temporary, "wx", 384);
|
|
1939
1957
|
try {
|
|
1940
1958
|
writeFileSync9(descriptor, `${JSON.stringify(value, null, 2)}
|
|
1941
1959
|
`);
|
|
1942
|
-
|
|
1960
|
+
fsyncSync2(descriptor);
|
|
1943
1961
|
} finally {
|
|
1944
|
-
|
|
1962
|
+
closeSync4(descriptor);
|
|
1945
1963
|
}
|
|
1946
|
-
|
|
1947
|
-
const directory =
|
|
1964
|
+
renameSync4(temporary, path);
|
|
1965
|
+
const directory = openSync4(dirname21(path), "r");
|
|
1948
1966
|
try {
|
|
1949
|
-
|
|
1967
|
+
fsyncSync2(directory);
|
|
1950
1968
|
} finally {
|
|
1951
|
-
|
|
1969
|
+
closeSync4(directory);
|
|
1952
1970
|
}
|
|
1953
1971
|
}
|
|
1954
1972
|
function readPrivateJson2(path) {
|
|
1955
|
-
const descriptor =
|
|
1973
|
+
const descriptor = openSync4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
1956
1974
|
try {
|
|
1957
1975
|
const stat = fstatSync2(descriptor);
|
|
1958
1976
|
if (!stat.isFile() || (stat.mode & 63) !== 0 || stat.size > 2097152)
|
|
1959
1977
|
throw new Error("unsafe_state_file");
|
|
1960
1978
|
if (stat.uid !== process.getuid?.()) throw new Error("foreign_state_file");
|
|
1961
|
-
return JSON.parse(
|
|
1979
|
+
return JSON.parse(readFileSync34(descriptor, "utf8"));
|
|
1962
1980
|
} finally {
|
|
1963
|
-
|
|
1981
|
+
closeSync4(descriptor);
|
|
1964
1982
|
}
|
|
1965
1983
|
}
|
|
1966
1984
|
var GroupStore;
|
|
@@ -1973,10 +1991,10 @@ var init_store2 = __esm({
|
|
|
1973
1991
|
path;
|
|
1974
1992
|
state;
|
|
1975
1993
|
constructor(path) {
|
|
1976
|
-
this.path =
|
|
1994
|
+
this.path = realpathSync6(path);
|
|
1977
1995
|
this.state = validateState(readPrivateJson2(path));
|
|
1978
1996
|
const metadata2 = object8(
|
|
1979
|
-
readPrivateJson2(
|
|
1997
|
+
readPrivateJson2(join29(dirname21(this.path), "harness.json"))
|
|
1980
1998
|
);
|
|
1981
1999
|
if (this.state.group.schemaVersion !== 1 || this.state.group.owner !== metadata2.owner) {
|
|
1982
2000
|
throw new Error("foreign_group");
|
|
@@ -1997,13 +2015,13 @@ var init_store2 = __esm({
|
|
|
1997
2015
|
|
|
1998
2016
|
// src/harness/adapters/process/serialized.ts
|
|
1999
2017
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
2000
|
-
import { randomUUID as
|
|
2001
|
-
import { existsSync as
|
|
2002
|
-
import { dirname as
|
|
2018
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
2019
|
+
import { existsSync as existsSync25, unlinkSync as unlinkSync2 } from "fs";
|
|
2020
|
+
import { dirname as dirname22, join as join30 } from "path";
|
|
2003
2021
|
function serialized(operation) {
|
|
2004
2022
|
const store = new GroupStore(text(operation.groupFile, "group_file"));
|
|
2005
|
-
const directory =
|
|
2006
|
-
const operationFile =
|
|
2023
|
+
const directory = dirname22(store.path);
|
|
2024
|
+
const operationFile = join30(directory, `operation-${randomUUID6()}.json`);
|
|
2007
2025
|
privateJson(operationFile, operation);
|
|
2008
2026
|
const worker = artifact("worker");
|
|
2009
2027
|
try {
|
|
@@ -2015,7 +2033,7 @@ function serialized(operation) {
|
|
|
2015
2033
|
"--no-fork",
|
|
2016
2034
|
"--conflict-exit-code",
|
|
2017
2035
|
"73",
|
|
2018
|
-
|
|
2036
|
+
join30(directory, "group-control.lock"),
|
|
2019
2037
|
process.execPath,
|
|
2020
2038
|
worker,
|
|
2021
2039
|
operationFile
|
|
@@ -2031,7 +2049,7 @@ function serialized(operation) {
|
|
|
2031
2049
|
if (!result.stdout.trim()) throw new Error("worker_receipt_missing");
|
|
2032
2050
|
return JSON.parse(result.stdout);
|
|
2033
2051
|
} finally {
|
|
2034
|
-
if (
|
|
2052
|
+
if (existsSync25(operationFile)) unlinkSync2(operationFile);
|
|
2035
2053
|
}
|
|
2036
2054
|
}
|
|
2037
2055
|
var init_serialized = __esm({
|
|
@@ -2111,11 +2129,11 @@ var init_command = __esm({
|
|
|
2111
2129
|
});
|
|
2112
2130
|
|
|
2113
2131
|
// src/harness/adapters/process/identity.ts
|
|
2114
|
-
import { readFileSync as
|
|
2132
|
+
import { readFileSync as readFileSync35 } from "fs";
|
|
2115
2133
|
function processIdentity(pid) {
|
|
2116
2134
|
if (!Number.isSafeInteger(pid) || pid < 1)
|
|
2117
2135
|
throw new Error("invalid_process_id");
|
|
2118
|
-
const stat =
|
|
2136
|
+
const stat = readFileSync35(`/proc/${pid}/stat`, "utf8");
|
|
2119
2137
|
const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
2120
2138
|
if (!/^\d+$/.test(fields[19] ?? ""))
|
|
2121
2139
|
throw new Error("process_identity_unknown");
|
|
@@ -2345,19 +2363,19 @@ var init_connection = __esm({
|
|
|
2345
2363
|
});
|
|
2346
2364
|
|
|
2347
2365
|
// src/harness/adapters/tmux/records.ts
|
|
2348
|
-
import { randomUUID as
|
|
2366
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
2349
2367
|
import {
|
|
2350
|
-
lstatSync as
|
|
2368
|
+
lstatSync as lstatSync6,
|
|
2351
2369
|
mkdirSync as mkdirSync9,
|
|
2352
2370
|
readdirSync as readdirSync10,
|
|
2353
|
-
realpathSync as
|
|
2371
|
+
realpathSync as realpathSync7,
|
|
2354
2372
|
writeFileSync as writeFileSync10
|
|
2355
2373
|
} from "fs";
|
|
2356
2374
|
import { homedir as homedir7 } from "os";
|
|
2357
|
-
import { join as
|
|
2375
|
+
import { join as join31 } from "path";
|
|
2358
2376
|
function stateDirectory() {
|
|
2359
|
-
return
|
|
2360
|
-
process.env.XDG_STATE_HOME ??
|
|
2377
|
+
return join31(
|
|
2378
|
+
process.env.XDG_STATE_HOME ?? join31(homedir7(), ".local/state"),
|
|
2361
2379
|
"pmcp/harness"
|
|
2362
2380
|
);
|
|
2363
2381
|
}
|
|
@@ -2384,15 +2402,15 @@ var init_records = __esm({
|
|
|
2384
2402
|
configPath;
|
|
2385
2403
|
owner;
|
|
2386
2404
|
constructor(directory) {
|
|
2387
|
-
const stat =
|
|
2405
|
+
const stat = lstatSync6(directory);
|
|
2388
2406
|
if (!stat.isDirectory() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
2389
2407
|
throw new Error("unsafe_group_directory");
|
|
2390
|
-
this.directory =
|
|
2391
|
-
this.socket =
|
|
2408
|
+
this.directory = realpathSync7(directory);
|
|
2409
|
+
this.socket = join31(this.directory, "s");
|
|
2392
2410
|
if (Buffer.byteLength(this.socket) > 100)
|
|
2393
2411
|
throw new Error("socket_path_too_long");
|
|
2394
|
-
this.metadataPath =
|
|
2395
|
-
this.configPath =
|
|
2412
|
+
this.metadataPath = join31(this.directory, "harness.json");
|
|
2413
|
+
this.configPath = join31(this.directory, "tmux.conf");
|
|
2396
2414
|
this.owner = text(
|
|
2397
2415
|
object8(readPrivateJson2(this.metadataPath)).owner,
|
|
2398
2416
|
"owner"
|
|
@@ -2402,17 +2420,17 @@ var init_records = __esm({
|
|
|
2402
2420
|
static create() {
|
|
2403
2421
|
const root = stateDirectory();
|
|
2404
2422
|
mkdirSync9(root, { recursive: true, mode: 448 });
|
|
2405
|
-
const directory =
|
|
2406
|
-
if (Buffer.byteLength(
|
|
2423
|
+
const directory = join31(root, randomUUID7().slice(0, 16));
|
|
2424
|
+
if (Buffer.byteLength(join31(directory, "s")) > 100)
|
|
2407
2425
|
throw new Error("socket_path_too_long");
|
|
2408
2426
|
mkdirSync9(directory, { mode: 448 });
|
|
2409
|
-
mkdirSync9(
|
|
2410
|
-
privateJson(
|
|
2411
|
-
owner:
|
|
2427
|
+
mkdirSync9(join31(directory, "sessions"), { mode: 448 });
|
|
2428
|
+
privateJson(join31(directory, "harness.json"), {
|
|
2429
|
+
owner: randomUUID7(),
|
|
2412
2430
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2413
2431
|
});
|
|
2414
2432
|
writeFileSync10(
|
|
2415
|
-
|
|
2433
|
+
join31(directory, "tmux.conf"),
|
|
2416
2434
|
[
|
|
2417
2435
|
"set -g remain-on-exit on",
|
|
2418
2436
|
"set -g history-limit 2000",
|
|
@@ -2428,7 +2446,7 @@ var init_records = __esm({
|
|
|
2428
2446
|
}
|
|
2429
2447
|
path(id2) {
|
|
2430
2448
|
runtimeUnit(id2);
|
|
2431
|
-
return
|
|
2449
|
+
return join31(this.directory, "sessions", `${id2}.json`);
|
|
2432
2450
|
}
|
|
2433
2451
|
read(id2) {
|
|
2434
2452
|
const record = object8(readPrivateJson2(this.path(id2)));
|
|
@@ -2450,7 +2468,7 @@ var init_records = __esm({
|
|
|
2450
2468
|
return record;
|
|
2451
2469
|
}
|
|
2452
2470
|
all() {
|
|
2453
|
-
return readdirSync10(
|
|
2471
|
+
return readdirSync10(join31(this.directory, "sessions")).filter((name) => name.endsWith(".json")).map((name) => this.read(name.slice(0, -5)));
|
|
2454
2472
|
}
|
|
2455
2473
|
save(record) {
|
|
2456
2474
|
privateJson(this.path(record.id), record);
|
|
@@ -2532,8 +2550,8 @@ var init_launch_identity = __esm({
|
|
|
2532
2550
|
|
|
2533
2551
|
// src/harness/launch/journal.ts
|
|
2534
2552
|
import { createHash as createHash16 } from "crypto";
|
|
2535
|
-
import { existsSync as
|
|
2536
|
-
import { join as
|
|
2553
|
+
import { existsSync as existsSync26, readdirSync as readdirSync11 } from "fs";
|
|
2554
|
+
import { join as join32 } from "path";
|
|
2537
2555
|
function launchJournals(records2) {
|
|
2538
2556
|
return readdirSync11(records2.directory).filter((name) => name.endsWith(".intent.json")).map((name) => new LaunchJournal(records2, name.slice(0, -12)));
|
|
2539
2557
|
}
|
|
@@ -2541,7 +2559,7 @@ function pendingLaunches(records2) {
|
|
|
2541
2559
|
return launchJournals(records2).filter((journal) => {
|
|
2542
2560
|
const aborted = journal.abortRecord();
|
|
2543
2561
|
if (aborted) return aborted.state !== "stopped";
|
|
2544
|
-
if (!
|
|
2562
|
+
if (!existsSync26(journal.path("committed"))) return true;
|
|
2545
2563
|
journal.assertCommitted();
|
|
2546
2564
|
return false;
|
|
2547
2565
|
});
|
|
@@ -2568,7 +2586,7 @@ var init_journal = __esm({
|
|
|
2568
2586
|
path(kind2) {
|
|
2569
2587
|
if (!["intent", "proof", "grant", "committed", "aborted"].includes(kind2))
|
|
2570
2588
|
throw new Error("invalid_launch_artifact");
|
|
2571
|
-
return
|
|
2589
|
+
return join32(this.records.directory, `${this.id}.${kind2}.json`);
|
|
2572
2590
|
}
|
|
2573
2591
|
intent() {
|
|
2574
2592
|
const value = object8(readPrivateJson2(this.path("intent")));
|
|
@@ -2597,13 +2615,13 @@ var init_journal = __esm({
|
|
|
2597
2615
|
throw new Error("invalid_docker_bridge_digest");
|
|
2598
2616
|
if (!bridge && creation.bridgeSha256 !== void 0)
|
|
2599
2617
|
throw new Error("ungranted_docker_bridge_digest");
|
|
2600
|
-
const policy = new GroupStore(
|
|
2618
|
+
const policy = new GroupStore(join32(this.records.directory, "group.json")).state.group.dockerPolicy;
|
|
2601
2619
|
const profile = selectedProfile(
|
|
2602
2620
|
policy,
|
|
2603
2621
|
creation.privateProfile,
|
|
2604
2622
|
text(value.runtime, "runtime")
|
|
2605
2623
|
);
|
|
2606
|
-
const expectedState = profile?.directory ??
|
|
2624
|
+
const expectedState = profile?.directory ?? join32(
|
|
2607
2625
|
this.records.directory,
|
|
2608
2626
|
"containers",
|
|
2609
2627
|
text(value.nonce, "nonce"),
|
|
@@ -2649,7 +2667,7 @@ var init_journal = __esm({
|
|
|
2649
2667
|
}
|
|
2650
2668
|
assertRegistered() {
|
|
2651
2669
|
const intent = this.intent();
|
|
2652
|
-
const group = new GroupStore(
|
|
2670
|
+
const group = new GroupStore(join32(this.records.directory, "group.json")).state.group;
|
|
2653
2671
|
if (group.id !== intent.groupId || group.generation !== intent.generation)
|
|
2654
2672
|
throw new Error("launch_group_generation_mismatch");
|
|
2655
2673
|
if (intent.dockerCreation && JSON.stringify(intent.dockerCreation.egress) !== JSON.stringify(group.dockerPolicy?.egress))
|
|
@@ -2665,12 +2683,12 @@ var init_journal = __esm({
|
|
|
2665
2683
|
throw new Error("launch_record_mismatch");
|
|
2666
2684
|
}
|
|
2667
2685
|
authorize() {
|
|
2668
|
-
if (
|
|
2686
|
+
if (existsSync26(this.path("aborted"))) throw new Error("launch_was_aborted");
|
|
2669
2687
|
this.assertRegistered();
|
|
2670
2688
|
privateJson(this.path("grant"), this.grant());
|
|
2671
2689
|
}
|
|
2672
2690
|
assertGrant() {
|
|
2673
|
-
if (
|
|
2691
|
+
if (existsSync26(this.path("aborted"))) throw new Error("launch_was_aborted");
|
|
2674
2692
|
const grant = object8(readPrivateJson2(this.path("grant")));
|
|
2675
2693
|
if (JSON.stringify(grant) !== JSON.stringify(this.grant()))
|
|
2676
2694
|
throw new Error("launch_grant_mismatch");
|
|
@@ -2687,7 +2705,7 @@ var init_journal = __esm({
|
|
|
2687
2705
|
throw new Error("launch_commit_mismatch");
|
|
2688
2706
|
}
|
|
2689
2707
|
abortRecord() {
|
|
2690
|
-
if (!
|
|
2708
|
+
if (!existsSync26(this.path("aborted"))) return;
|
|
2691
2709
|
const value = object8(readPrivateJson2(this.path("aborted")));
|
|
2692
2710
|
if (value.id !== this.id || value.owner !== this.records.owner || !["stopping", "stopped"].includes(String(value.state)))
|
|
2693
2711
|
throw new Error("foreign_launch_abort");
|
|
@@ -2734,30 +2752,30 @@ var init_lifecycle = __esm({
|
|
|
2734
2752
|
// src/harness/adapters/docker/private-profile.ts
|
|
2735
2753
|
import { homedir as homedir8 } from "os";
|
|
2736
2754
|
import {
|
|
2737
|
-
closeSync as
|
|
2755
|
+
closeSync as closeSync5,
|
|
2738
2756
|
constants as constants4,
|
|
2739
|
-
existsSync as
|
|
2757
|
+
existsSync as existsSync27,
|
|
2740
2758
|
fstatSync as fstatSync3,
|
|
2741
|
-
lstatSync as
|
|
2742
|
-
openSync as
|
|
2743
|
-
realpathSync as
|
|
2759
|
+
lstatSync as lstatSync7,
|
|
2760
|
+
openSync as openSync5,
|
|
2761
|
+
realpathSync as realpathSync8,
|
|
2744
2762
|
statfsSync
|
|
2745
2763
|
} from "fs";
|
|
2746
|
-
import { join as
|
|
2764
|
+
import { join as join33, relative as relative11 } from "path";
|
|
2747
2765
|
function overlap(left, right) {
|
|
2748
2766
|
const scope = relative11(left, right);
|
|
2749
2767
|
return scope === "" || scope !== ".." && !scope.startsWith("../");
|
|
2750
2768
|
}
|
|
2751
2769
|
function profileIdentity(directory, projectRoot) {
|
|
2752
|
-
if (directory === "/" || directory ===
|
|
2770
|
+
if (directory === "/" || directory === realpathSync8(homedir8()) || overlap(directory, projectRoot) || overlap(projectRoot, directory) || realpathSync8(directory) !== directory)
|
|
2753
2771
|
throw new Error("private_profile_scope_not_admitted");
|
|
2754
|
-
const stat =
|
|
2772
|
+
const stat = lstatSync7(directory);
|
|
2755
2773
|
if (!stat.isDirectory() || stat.uid !== process.getuid?.() || (stat.mode & 511) !== 448)
|
|
2756
2774
|
throw new Error("unsafe_private_profile_directory");
|
|
2757
2775
|
if ([26985, 4283649346, 4266872130].includes(statfsSync(directory).type))
|
|
2758
2776
|
throw new Error("remote_private_profile_not_admitted");
|
|
2759
|
-
const lock =
|
|
2760
|
-
|
|
2777
|
+
const lock = openSync5(
|
|
2778
|
+
join33(directory, privateProfileLock),
|
|
2761
2779
|
constants4.O_RDONLY | constants4.O_NOFOLLOW
|
|
2762
2780
|
);
|
|
2763
2781
|
try {
|
|
@@ -2766,18 +2784,18 @@ function profileIdentity(directory, projectRoot) {
|
|
|
2766
2784
|
throw new Error("unsafe_private_profile_lock");
|
|
2767
2785
|
return `${stat.dev}:${stat.ino}:${value.dev}:${value.ino}`;
|
|
2768
2786
|
} finally {
|
|
2769
|
-
|
|
2787
|
+
closeSync5(lock);
|
|
2770
2788
|
}
|
|
2771
2789
|
}
|
|
2772
2790
|
function captureProfile(grant, projectRoot) {
|
|
2773
|
-
const directory =
|
|
2774
|
-
if (directory !== grant.directory || overlap(directory, projectRoot) || overlap(projectRoot, directory) || directory === "/" || directory ===
|
|
2791
|
+
const directory = realpathSync8(grant.directory);
|
|
2792
|
+
if (directory !== grant.directory || overlap(directory, projectRoot) || overlap(projectRoot, directory) || directory === "/" || directory === realpathSync8(homedir8()))
|
|
2775
2793
|
throw new Error("private_profile_scope_not_admitted");
|
|
2776
|
-
const stat =
|
|
2794
|
+
const stat = lstatSync7(directory);
|
|
2777
2795
|
if (!stat.isDirectory() || stat.uid !== process.getuid?.() || (stat.mode & 511) !== 448)
|
|
2778
2796
|
throw new Error("unsafe_private_profile_directory");
|
|
2779
|
-
const lock =
|
|
2780
|
-
if (!
|
|
2797
|
+
const lock = join33(directory, privateProfileLock);
|
|
2798
|
+
if (!existsSync27(lock)) closeSync5(openSync5(lock, "wx", 384));
|
|
2781
2799
|
const identity = profileIdentity(directory, projectRoot);
|
|
2782
2800
|
if (grant.identity && grant.identity !== identity)
|
|
2783
2801
|
throw new Error("private_profile_identity_changed");
|
|
@@ -2795,7 +2813,7 @@ function assertProfileAvailable(grant, projectRoot) {
|
|
|
2795
2813
|
"--nonblock",
|
|
2796
2814
|
"--conflict-exit-code",
|
|
2797
2815
|
"73",
|
|
2798
|
-
|
|
2816
|
+
join33(grant.directory, privateProfileLock),
|
|
2799
2817
|
"true"
|
|
2800
2818
|
]);
|
|
2801
2819
|
} catch (error) {
|
|
@@ -2815,7 +2833,7 @@ var init_private_profile = __esm({
|
|
|
2815
2833
|
|
|
2816
2834
|
// src/harness/adapters/docker/profile.ts
|
|
2817
2835
|
import { homedir as homedir9 } from "os";
|
|
2818
|
-
import { lstatSync as
|
|
2836
|
+
import { lstatSync as lstatSync8, realpathSync as realpathSync9 } from "fs";
|
|
2819
2837
|
import { relative as relative12 } from "path";
|
|
2820
2838
|
function assertLocalDocker() {
|
|
2821
2839
|
if (process.env.DOCKER_HOST)
|
|
@@ -2832,12 +2850,12 @@ function assertLocalDocker() {
|
|
|
2832
2850
|
throw new Error("remote_docker_context_not_admitted");
|
|
2833
2851
|
}
|
|
2834
2852
|
function loadDockerPolicy(path, projectRoot, runtimes) {
|
|
2835
|
-
if (projectRoot === "/" || projectRoot ===
|
|
2853
|
+
if (projectRoot === "/" || projectRoot === realpathSync9(homedir9()))
|
|
2836
2854
|
throw new Error("docker_project_root_too_broad");
|
|
2837
2855
|
const inputPath = text(path, "docker_policy_file");
|
|
2838
|
-
if (
|
|
2856
|
+
if (lstatSync8(inputPath).isSymbolicLink())
|
|
2839
2857
|
throw new Error("symlink_docker_policy_not_admitted");
|
|
2840
|
-
const source2 =
|
|
2858
|
+
const source2 = realpathSync9(inputPath);
|
|
2841
2859
|
const scope = relative12(projectRoot, source2);
|
|
2842
2860
|
if (scope === ".." || scope.startsWith("../"))
|
|
2843
2861
|
throw new Error("docker_policy_outside_project");
|
|
@@ -2871,16 +2889,16 @@ var init_profile = __esm({
|
|
|
2871
2889
|
});
|
|
2872
2890
|
|
|
2873
2891
|
// src/harness/adapters/docker/mounts.ts
|
|
2874
|
-
import { lstatSync as
|
|
2875
|
-
import { join as
|
|
2892
|
+
import { lstatSync as lstatSync9, realpathSync as realpathSync10 } from "fs";
|
|
2893
|
+
import { join as join34 } from "path";
|
|
2876
2894
|
function assertContainerMounts(actual, statePath, projectRoot, directory, nonce, policy, privateProfile, runtime2) {
|
|
2877
2895
|
const state = text(statePath, "container_state_directory");
|
|
2878
2896
|
const profile = selectedProfile(policy, privateProfile, runtime2 ?? "");
|
|
2879
2897
|
if (profile) assertProfileIdentity(profile, projectRoot);
|
|
2880
|
-
const expected = profile?.directory ??
|
|
2881
|
-
if (state !== expected ||
|
|
2898
|
+
const expected = profile?.directory ?? join34(directory, "containers", nonce, "profile");
|
|
2899
|
+
if (state !== expected || realpathSync10(state) !== expected)
|
|
2882
2900
|
throw new Error("foreign_container_state_directory");
|
|
2883
|
-
const stat =
|
|
2901
|
+
const stat = lstatSync9(state);
|
|
2884
2902
|
if (!stat.isDirectory() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
2885
2903
|
throw new Error("unsafe_container_state_directory");
|
|
2886
2904
|
if (!Array.isArray(actual.Mounts) || actual.Mounts.length !== 2)
|
|
@@ -2972,17 +2990,17 @@ var init_entrypoint = __esm({
|
|
|
2972
2990
|
});
|
|
2973
2991
|
|
|
2974
2992
|
// src/harness/adapters/docker/admission.ts
|
|
2975
|
-
import { lstatSync as
|
|
2976
|
-
import { join as
|
|
2993
|
+
import { lstatSync as lstatSync10, realpathSync as realpathSync11 } from "fs";
|
|
2994
|
+
import { join as join35, relative as relative13 } from "path";
|
|
2977
2995
|
function admitResource(path, group, role, directory, memoryMb, runtime2) {
|
|
2978
2996
|
if (group.profile !== "docker" || !group.dockerPolicy)
|
|
2979
2997
|
throw new Error("container_requires_docker_profile");
|
|
2980
2998
|
const { projectRoot } = group;
|
|
2981
2999
|
assertLocalDocker();
|
|
2982
3000
|
const inputPath = text(path, "container_file");
|
|
2983
|
-
if (
|
|
3001
|
+
if (lstatSync10(inputPath).isSymbolicLink())
|
|
2984
3002
|
throw new Error("symlink_container_file_not_admitted");
|
|
2985
|
-
const source2 =
|
|
3003
|
+
const source2 = realpathSync11(inputPath);
|
|
2986
3004
|
const scope = relative13(projectRoot, source2);
|
|
2987
3005
|
if (scope === ".." || scope.startsWith("../"))
|
|
2988
3006
|
throw new Error("container_file_outside_project");
|
|
@@ -3029,7 +3047,7 @@ function admitClaim(value, group, role, directory, memoryMb, runtime2, cwd = gro
|
|
|
3029
3047
|
)
|
|
3030
3048
|
))
|
|
3031
3049
|
throw new Error("container_runtime_image_not_granted");
|
|
3032
|
-
if (config.WorkingDir !==
|
|
3050
|
+
if (config.WorkingDir !== join35("/workspace", relative13(projectRoot, cwd)))
|
|
3033
3051
|
throw new Error("container_cwd_not_granted");
|
|
3034
3052
|
assertContainerMounts(
|
|
3035
3053
|
actual,
|
|
@@ -3165,20 +3183,20 @@ var init_engine = __esm({
|
|
|
3165
3183
|
// src/harness/adapters/docker/bridge-artifact.ts
|
|
3166
3184
|
import { createHash as createHash17 } from "crypto";
|
|
3167
3185
|
import {
|
|
3168
|
-
closeSync as
|
|
3186
|
+
closeSync as closeSync6,
|
|
3169
3187
|
constants as constants5,
|
|
3170
3188
|
fstatSync as fstatSync4,
|
|
3171
|
-
fsyncSync as
|
|
3172
|
-
openSync as
|
|
3173
|
-
readFileSync as
|
|
3189
|
+
fsyncSync as fsyncSync3,
|
|
3190
|
+
openSync as openSync6,
|
|
3191
|
+
readFileSync as readFileSync36,
|
|
3174
3192
|
writeFileSync as writeFileSync11
|
|
3175
3193
|
} from "fs";
|
|
3176
|
-
import { join as
|
|
3194
|
+
import { join as join36 } from "path";
|
|
3177
3195
|
function source() {
|
|
3178
3196
|
const path = artifact("egress-bridge");
|
|
3179
3197
|
if (!path.endsWith(".js"))
|
|
3180
3198
|
throw new Error("docker_bridge_requires_compiled_cli");
|
|
3181
|
-
return
|
|
3199
|
+
return readFileSync36(path);
|
|
3182
3200
|
}
|
|
3183
3201
|
function digest(value) {
|
|
3184
3202
|
return createHash17("sha256").update(value).digest("hex");
|
|
@@ -3191,30 +3209,30 @@ function prepareBridge(creation) {
|
|
|
3191
3209
|
const content = source();
|
|
3192
3210
|
if (digest(content) !== creation.bridgeSha256)
|
|
3193
3211
|
throw new Error("docker_bridge_source_changed");
|
|
3194
|
-
const descriptor =
|
|
3195
|
-
|
|
3212
|
+
const descriptor = openSync6(
|
|
3213
|
+
join36(creation.state, bridgeName(creation.nonce)),
|
|
3196
3214
|
"wx",
|
|
3197
3215
|
384
|
|
3198
3216
|
);
|
|
3199
3217
|
try {
|
|
3200
3218
|
writeFileSync11(descriptor, content);
|
|
3201
|
-
|
|
3219
|
+
fsyncSync3(descriptor);
|
|
3202
3220
|
} finally {
|
|
3203
|
-
|
|
3221
|
+
closeSync6(descriptor);
|
|
3204
3222
|
}
|
|
3205
3223
|
}
|
|
3206
3224
|
function assertBridge(creation) {
|
|
3207
3225
|
if (!creation.egress?.bridgeExecutable) return;
|
|
3208
|
-
const descriptor =
|
|
3209
|
-
|
|
3226
|
+
const descriptor = openSync6(
|
|
3227
|
+
join36(creation.state, bridgeName(creation.nonce)),
|
|
3210
3228
|
constants5.O_RDONLY | constants5.O_NOFOLLOW
|
|
3211
3229
|
);
|
|
3212
3230
|
try {
|
|
3213
3231
|
const stat = fstatSync4(descriptor);
|
|
3214
|
-
if (!stat.isFile() || stat.uid !== process.getuid?.() || stat.nlink !== 1 || (stat.mode & 63) !== 0 || stat.size > 2097152 || digest(
|
|
3232
|
+
if (!stat.isFile() || stat.uid !== process.getuid?.() || stat.nlink !== 1 || (stat.mode & 63) !== 0 || stat.size > 2097152 || digest(readFileSync36(descriptor)) !== creation.bridgeSha256)
|
|
3215
3233
|
throw new Error("docker_bridge_artifact_changed");
|
|
3216
3234
|
} finally {
|
|
3217
|
-
|
|
3235
|
+
closeSync6(descriptor);
|
|
3218
3236
|
}
|
|
3219
3237
|
}
|
|
3220
3238
|
var init_bridge_artifact = __esm({
|
|
@@ -3226,7 +3244,7 @@ var init_bridge_artifact = __esm({
|
|
|
3226
3244
|
});
|
|
3227
3245
|
|
|
3228
3246
|
// src/harness/adapters/docker/creation-plan.ts
|
|
3229
|
-
import { join as
|
|
3247
|
+
import { join as join37, relative as relative14 } from "path";
|
|
3230
3248
|
function creationPlan(group, directory, id2, nonce, role, runtime2, cwd, preparation) {
|
|
3231
3249
|
if (group.profile !== "docker" || !group.dockerPolicy)
|
|
3232
3250
|
throw new Error("container_requires_docker_profile");
|
|
@@ -3243,7 +3261,7 @@ function creationPlan(group, directory, id2, nonce, role, runtime2, cwd, prepara
|
|
|
3243
3261
|
throw new Error("invalid_container_image_environment");
|
|
3244
3262
|
const profile = selectedProfile(policy, preparation.privateProfile, runtime2);
|
|
3245
3263
|
if (profile) assertProfileAvailable(profile, group.projectRoot);
|
|
3246
|
-
const state = profile?.directory ??
|
|
3264
|
+
const state = profile?.directory ?? join37(directory, "containers", nonce, "profile");
|
|
3247
3265
|
return {
|
|
3248
3266
|
name: containerName(group.owner, id2),
|
|
3249
3267
|
nonce,
|
|
@@ -3260,7 +3278,7 @@ function creationPlan(group, directory, id2, nonce, role, runtime2, cwd, prepara
|
|
|
3260
3278
|
preparation.privateProfile
|
|
3261
3279
|
),
|
|
3262
3280
|
Cmd: preparation.args.length ? preparation.args : null,
|
|
3263
|
-
WorkingDir:
|
|
3281
|
+
WorkingDir: join37("/workspace", relative14(group.projectRoot, cwd)),
|
|
3264
3282
|
User: `${process.getuid?.()}:${process.getgid?.()}`,
|
|
3265
3283
|
Env: [
|
|
3266
3284
|
...environment2.filter(
|
|
@@ -3359,12 +3377,12 @@ var init_creation_plan = __esm({
|
|
|
3359
3377
|
|
|
3360
3378
|
// src/harness/adapters/docker/creation.ts
|
|
3361
3379
|
import { mkdirSync as mkdirSync10 } from "fs";
|
|
3362
|
-
import { join as
|
|
3380
|
+
import { join as join38 } from "path";
|
|
3363
3381
|
async function prepareCreation(journal) {
|
|
3364
3382
|
const intent = journal.intent();
|
|
3365
3383
|
const creation = intent.dockerCreation;
|
|
3366
3384
|
if (!creation) throw new Error("container_creation_missing");
|
|
3367
|
-
const group = new GroupStore(
|
|
3385
|
+
const group = new GroupStore(join38(journal.records.directory, "group.json")).state.group;
|
|
3368
3386
|
const profile = selectedProfile(
|
|
3369
3387
|
group.dockerPolicy,
|
|
3370
3388
|
creation.privateProfile,
|
|
@@ -3378,7 +3396,7 @@ async function prepareCreation(journal) {
|
|
|
3378
3396
|
creation.name,
|
|
3379
3397
|
creation.configuration
|
|
3380
3398
|
);
|
|
3381
|
-
const current = new GroupStore(
|
|
3399
|
+
const current = new GroupStore(join38(journal.records.directory, "group.json")).state.group;
|
|
3382
3400
|
if (current.id !== intent.groupId || current.generation !== intent.generation)
|
|
3383
3401
|
throw new Error("launch_group_generation_mismatch");
|
|
3384
3402
|
const resource = {
|
|
@@ -3446,7 +3464,7 @@ function findCreation(journal) {
|
|
|
3446
3464
|
assertResourceOwner(resource, actual);
|
|
3447
3465
|
assertCreationConfiguration(actual, creation);
|
|
3448
3466
|
if (!intent.resource) {
|
|
3449
|
-
const group = new GroupStore(
|
|
3467
|
+
const group = new GroupStore(join38(journal.records.directory, "group.json")).state.group;
|
|
3450
3468
|
return {
|
|
3451
3469
|
...admitClaim(
|
|
3452
3470
|
{ ...resource, runtime: intent.runtime, state: creation.state },
|
|
@@ -3483,7 +3501,7 @@ var init_creation = __esm({
|
|
|
3483
3501
|
});
|
|
3484
3502
|
|
|
3485
3503
|
// src/harness/launch/recovery.ts
|
|
3486
|
-
import { existsSync as
|
|
3504
|
+
import { existsSync as existsSync28 } from "fs";
|
|
3487
3505
|
function abortLaunch(connection, journal) {
|
|
3488
3506
|
const intent = journal.intent();
|
|
3489
3507
|
if (!controllerExited(intent.controllerIdentity))
|
|
@@ -3500,7 +3518,7 @@ function abortLaunch(connection, journal) {
|
|
|
3500
3518
|
inputReplayed: false
|
|
3501
3519
|
};
|
|
3502
3520
|
}
|
|
3503
|
-
if (!
|
|
3521
|
+
if (!existsSync28(journal.path("proof"))) {
|
|
3504
3522
|
const paneUnknown = connection.alive() && (!intent.dockerCreation || connection.tmux(["list-sessions", "-F", "#{session_name}"]).trim().split("\n").includes(`pmcp-${intent.id}`));
|
|
3505
3523
|
if (paneUnknown || unitActive(intent.unit))
|
|
3506
3524
|
throw new Error("launch_identity_unknown");
|
|
@@ -3527,7 +3545,7 @@ function abortLaunch(connection, journal) {
|
|
|
3527
3545
|
assertProof(connection, proof);
|
|
3528
3546
|
connection.tmux(["kill-pane", "-t", proof.session.paneId]);
|
|
3529
3547
|
}
|
|
3530
|
-
if (
|
|
3548
|
+
if (existsSync28(journal.records.path(intent.id)))
|
|
3531
3549
|
journal.records.save({
|
|
3532
3550
|
...proof.session,
|
|
3533
3551
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3553,10 +3571,10 @@ var init_recovery = __esm({
|
|
|
3553
3571
|
});
|
|
3554
3572
|
|
|
3555
3573
|
// src/harness/adapters/tmux/lifecycle.ts
|
|
3556
|
-
import { randomUUID as
|
|
3557
|
-
import { existsSync as
|
|
3574
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
3575
|
+
import { existsSync as existsSync29 } from "fs";
|
|
3558
3576
|
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
3559
|
-
import { join as
|
|
3577
|
+
import { join as join39 } from "path";
|
|
3560
3578
|
async function start(connection, spec) {
|
|
3561
3579
|
const records2 = connection.records;
|
|
3562
3580
|
if (spec.budgetSlice !== budgetSlice(records2.owner))
|
|
@@ -3566,11 +3584,11 @@ async function start(connection, spec) {
|
|
|
3566
3584
|
runtimeUnit(spec.generation);
|
|
3567
3585
|
if (spec.role !== "main" && spec.role !== "worker")
|
|
3568
3586
|
throw new Error("invalid_launch_role");
|
|
3569
|
-
const id2 =
|
|
3570
|
-
const nonce =
|
|
3587
|
+
const id2 = randomUUID8();
|
|
3588
|
+
const nonce = randomUUID8();
|
|
3571
3589
|
const journal = new LaunchJournal(records2, id2);
|
|
3572
3590
|
const dockerCreation = spec.dockerPreparation ? creationPlan(
|
|
3573
|
-
new GroupStore(
|
|
3591
|
+
new GroupStore(join39(records2.directory, "group.json")).state.group,
|
|
3574
3592
|
records2.directory,
|
|
3575
3593
|
id2,
|
|
3576
3594
|
nonce,
|
|
@@ -3630,7 +3648,7 @@ async function start(connection, spec) {
|
|
|
3630
3648
|
`exec ${argv.map(quote2).join(" ")}`
|
|
3631
3649
|
]);
|
|
3632
3650
|
const deadline = Date.now() + 5e3;
|
|
3633
|
-
while (!
|
|
3651
|
+
while (!existsSync29(journal.path("proof"))) {
|
|
3634
3652
|
if (Date.now() >= deadline) throw new Error("launch_proof_timeout");
|
|
3635
3653
|
await setTimeout2(25);
|
|
3636
3654
|
}
|
|
@@ -3641,7 +3659,7 @@ async function start(connection, spec) {
|
|
|
3641
3659
|
}
|
|
3642
3660
|
function stop(connection, id2) {
|
|
3643
3661
|
const journal = new LaunchJournal(connection.records, id2);
|
|
3644
|
-
if (
|
|
3662
|
+
if (existsSync29(journal.path("intent"))) {
|
|
3645
3663
|
abortLaunch(connection, journal);
|
|
3646
3664
|
return;
|
|
3647
3665
|
}
|
|
@@ -3668,7 +3686,7 @@ function cleanup(connection) {
|
|
|
3668
3686
|
}
|
|
3669
3687
|
for (const record of records2) {
|
|
3670
3688
|
if (record.resource) stopResource(record.resource);
|
|
3671
|
-
if (record.stoppedAt || !
|
|
3689
|
+
if (record.stoppedAt || !existsSync29(
|
|
3672
3690
|
new LaunchJournal(connection.records, record.id).path("intent")
|
|
3673
3691
|
)) {
|
|
3674
3692
|
stopUnit(record.unit);
|
|
@@ -3698,9 +3716,9 @@ var init_lifecycle2 = __esm({
|
|
|
3698
3716
|
});
|
|
3699
3717
|
|
|
3700
3718
|
// src/harness/adapters/tmux/input.ts
|
|
3701
|
-
import { randomUUID as
|
|
3702
|
-
import { closeSync as
|
|
3703
|
-
import { join as
|
|
3719
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
3720
|
+
import { closeSync as closeSync7, openSync as openSync7, unlinkSync as unlinkSync3, writeFileSync as writeFileSync12 } from "fs";
|
|
3721
|
+
import { join as join40 } from "path";
|
|
3704
3722
|
function validateText(text3) {
|
|
3705
3723
|
if (typeof text3 !== "string" || Buffer.byteLength(text3) > 16384)
|
|
3706
3724
|
throw new Error("invalid_text");
|
|
@@ -3731,9 +3749,9 @@ function write(connection, id2, text3, submit) {
|
|
|
3731
3749
|
validateText(text3);
|
|
3732
3750
|
const session = connection.inspect(id2);
|
|
3733
3751
|
if (!session.alive) throw new Error("runtime_exited");
|
|
3734
|
-
const lock =
|
|
3735
|
-
const descriptor =
|
|
3736
|
-
const buffer = `pmcp-${
|
|
3752
|
+
const lock = join40(connection.records.directory, `${id2}.input.lock`);
|
|
3753
|
+
const descriptor = openSync7(lock, "wx", 384);
|
|
3754
|
+
const buffer = `pmcp-${randomUUID9()}`;
|
|
3737
3755
|
try {
|
|
3738
3756
|
writeFileSync12(
|
|
3739
3757
|
descriptor,
|
|
@@ -3773,7 +3791,7 @@ function write(connection, id2, text3, submit) {
|
|
|
3773
3791
|
connection.tmux(["delete-buffer", "-b", buffer]);
|
|
3774
3792
|
} catch {
|
|
3775
3793
|
}
|
|
3776
|
-
|
|
3794
|
+
closeSync7(descriptor);
|
|
3777
3795
|
unlinkSync3(lock);
|
|
3778
3796
|
}
|
|
3779
3797
|
}
|
|
@@ -3890,10 +3908,10 @@ var init_backend = __esm({
|
|
|
3890
3908
|
});
|
|
3891
3909
|
|
|
3892
3910
|
// src/harness/adapters/systemd/budget.ts
|
|
3893
|
-
import { join as
|
|
3911
|
+
import { join as join41 } from "path";
|
|
3894
3912
|
async function budgetOperation(directory, operation) {
|
|
3895
3913
|
const backend = openBackend(directory);
|
|
3896
|
-
const path =
|
|
3914
|
+
const path = join41(directory, "runtime-policy.json");
|
|
3897
3915
|
const metadata2 = object8(readPrivateJson2(backend.metadataPath));
|
|
3898
3916
|
const slice = budgetSlice(backend.owner);
|
|
3899
3917
|
if (operation.action === "configure") {
|
|
@@ -3994,7 +4012,7 @@ var init_budget = __esm({
|
|
|
3994
4012
|
|
|
3995
4013
|
// src/harness/adapters/systemd/configure.ts
|
|
3996
4014
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3997
|
-
import { join as
|
|
4015
|
+
import { join as join42 } from "path";
|
|
3998
4016
|
function configureBudget(context, input) {
|
|
3999
4017
|
const { store, backend } = context;
|
|
4000
4018
|
assertOwnedRecords(backend);
|
|
@@ -4016,7 +4034,7 @@ function configureBudget(context, input) {
|
|
|
4016
4034
|
throw new Error("capacity_below_active_sessions");
|
|
4017
4035
|
if (active.reduce((total, session) => total + session.memoryMb, 0) > memoryMb)
|
|
4018
4036
|
throw new Error("budget_below_reserved_memory");
|
|
4019
|
-
const path =
|
|
4037
|
+
const path = join42(backend.runDirectory, "runtime-policy.json");
|
|
4020
4038
|
const policy = object8(readPrivateJson2(path));
|
|
4021
4039
|
const metadata2 = object8(readPrivateJson2(backend.metadataPath));
|
|
4022
4040
|
const slice = budgetSlice(backend.owner);
|
|
@@ -4054,11 +4072,11 @@ var init_configure = __esm({
|
|
|
4054
4072
|
|
|
4055
4073
|
// src/harness/adapters/process/executable.ts
|
|
4056
4074
|
import { accessSync, constants as constants6, statSync as statSync16 } from "fs";
|
|
4057
|
-
import { delimiter, resolve as
|
|
4075
|
+
import { delimiter, resolve as resolve18 } from "path";
|
|
4058
4076
|
function locateExecutable(binary, cwd = process.cwd()) {
|
|
4059
4077
|
for (const directory of (process.env.PATH ?? "").split(delimiter)) {
|
|
4060
4078
|
if (!directory) continue;
|
|
4061
|
-
const path =
|
|
4079
|
+
const path = resolve18(cwd, directory, binary);
|
|
4062
4080
|
try {
|
|
4063
4081
|
if (!statSync16(path).isFile()) continue;
|
|
4064
4082
|
accessSync(path, constants6.X_OK);
|
|
@@ -4110,11 +4128,11 @@ var init_admission2 = __esm({
|
|
|
4110
4128
|
});
|
|
4111
4129
|
|
|
4112
4130
|
// src/harness/groups/service.ts
|
|
4113
|
-
import { randomUUID as
|
|
4114
|
-
import { realpathSync as
|
|
4115
|
-
import { join as
|
|
4131
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
4132
|
+
import { realpathSync as realpathSync12, statSync as statSync17, unlinkSync as unlinkSync4 } from "fs";
|
|
4133
|
+
import { join as join43 } from "path";
|
|
4116
4134
|
async function createGroup(input) {
|
|
4117
|
-
const projectRoot =
|
|
4135
|
+
const projectRoot = realpathSync12(text(input.projectRoot, "project_root"));
|
|
4118
4136
|
if (!statSync17(projectRoot).isDirectory())
|
|
4119
4137
|
throw new Error("invalid_project_root");
|
|
4120
4138
|
const allowedRuntimes = strings2(input.allowedRuntimes, "allowed_runtimes");
|
|
@@ -4134,8 +4152,8 @@ async function createGroup(input) {
|
|
|
4134
4152
|
const group = {
|
|
4135
4153
|
schemaVersion: 1,
|
|
4136
4154
|
owner: backend.owner,
|
|
4137
|
-
id:
|
|
4138
|
-
generation:
|
|
4155
|
+
id: randomUUID10(),
|
|
4156
|
+
generation: randomUUID10(),
|
|
4139
4157
|
projectRoot,
|
|
4140
4158
|
allowedRuntimes,
|
|
4141
4159
|
profile,
|
|
@@ -4145,7 +4163,7 @@ async function createGroup(input) {
|
|
|
4145
4163
|
sessions: [],
|
|
4146
4164
|
...dockerPolicy2 ? { dockerPolicy: dockerPolicy2 } : {}
|
|
4147
4165
|
};
|
|
4148
|
-
const groupFile =
|
|
4166
|
+
const groupFile = join43(backend.runDirectory, "group.json");
|
|
4149
4167
|
privateJson(groupFile, { group, leases: [], deliveries: [] });
|
|
4150
4168
|
try {
|
|
4151
4169
|
await budgetOperation(backend.runDirectory, {
|
|
@@ -4158,7 +4176,7 @@ async function createGroup(input) {
|
|
|
4158
4176
|
await budgetOperation(backend.runDirectory, { action: "cleanup" });
|
|
4159
4177
|
unlinkSync4(groupFile);
|
|
4160
4178
|
} catch (cleanupError) {
|
|
4161
|
-
privateJson(
|
|
4179
|
+
privateJson(join43(backend.runDirectory, "initialization-failure.json"), {
|
|
4162
4180
|
error: String(error),
|
|
4163
4181
|
cleanupError: String(cleanupError),
|
|
4164
4182
|
recoveryRequired: true
|
|
@@ -4220,16 +4238,16 @@ var init_service2 = __esm({
|
|
|
4220
4238
|
});
|
|
4221
4239
|
|
|
4222
4240
|
// src/harness/groups/catalog.ts
|
|
4223
|
-
import { existsSync as
|
|
4224
|
-
import { join as
|
|
4241
|
+
import { existsSync as existsSync30, readdirSync as readdirSync12 } from "fs";
|
|
4242
|
+
import { join as join44 } from "path";
|
|
4225
4243
|
function listGroups() {
|
|
4226
4244
|
const directory = stateDirectory();
|
|
4227
|
-
if (!
|
|
4245
|
+
if (!existsSync30(directory)) return { groups: [] };
|
|
4228
4246
|
const groups2 = [];
|
|
4229
4247
|
for (const entry2 of readdirSync12(directory, { withFileTypes: true })) {
|
|
4230
4248
|
if (!entry2.isDirectory()) continue;
|
|
4231
|
-
const path =
|
|
4232
|
-
if (!
|
|
4249
|
+
const path = join44(directory, entry2.name, "group.json");
|
|
4250
|
+
if (!existsSync30(path)) continue;
|
|
4233
4251
|
try {
|
|
4234
4252
|
const store = new GroupStore(path);
|
|
4235
4253
|
groups2.push({ groupFile: store.path, group: store.state.group });
|
|
@@ -4288,10 +4306,10 @@ var init_service3 = __esm({
|
|
|
4288
4306
|
});
|
|
4289
4307
|
|
|
4290
4308
|
// src/harness/application/context.ts
|
|
4291
|
-
import { dirname as
|
|
4309
|
+
import { dirname as dirname23 } from "path";
|
|
4292
4310
|
function openGroup(path) {
|
|
4293
4311
|
const store = new GroupStore(path);
|
|
4294
|
-
const backend = openBackend(
|
|
4312
|
+
const backend = openBackend(dirname23(store.path));
|
|
4295
4313
|
if (store.state.group.owner !== backend.owner)
|
|
4296
4314
|
throw new Error("foreign_group");
|
|
4297
4315
|
return { store, backend };
|
|
@@ -4425,8 +4443,8 @@ var init_watch = __esm({
|
|
|
4425
4443
|
});
|
|
4426
4444
|
|
|
4427
4445
|
// src/harness/control/leases.ts
|
|
4428
|
-
import { randomUUID as
|
|
4429
|
-
import { join as
|
|
4446
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
4447
|
+
import { join as join45 } from "path";
|
|
4430
4448
|
function resolveLease(context, file2, allowExpired = false) {
|
|
4431
4449
|
const credential2 = object8(readPrivateJson2(file2));
|
|
4432
4450
|
const lease = context.store.state.leases.find(
|
|
@@ -4458,7 +4476,7 @@ function acquireControl(context, sessionId, controller, mode) {
|
|
|
4458
4476
|
if (mode !== "cli" && mode !== "native")
|
|
4459
4477
|
throw new Error("invalid_control_mode");
|
|
4460
4478
|
const lease = {
|
|
4461
|
-
token:
|
|
4479
|
+
token: randomUUID11(),
|
|
4462
4480
|
sessionId,
|
|
4463
4481
|
generation: store.state.group.generation,
|
|
4464
4482
|
controller: id(controller, "controller"),
|
|
@@ -4470,7 +4488,7 @@ function acquireControl(context, sessionId, controller, mode) {
|
|
|
4470
4488
|
);
|
|
4471
4489
|
store.state.leases.push(lease);
|
|
4472
4490
|
store.save();
|
|
4473
|
-
const leaseFile =
|
|
4491
|
+
const leaseFile = join45(backend.runDirectory, `lease-${randomUUID11()}.json`);
|
|
4474
4492
|
privateJson(leaseFile, { token: lease.token });
|
|
4475
4493
|
return { sessionId, controller, mode, expiresAt: lease.expiresAt, leaseFile };
|
|
4476
4494
|
}
|
|
@@ -4617,18 +4635,18 @@ var init_service5 = __esm({
|
|
|
4617
4635
|
});
|
|
4618
4636
|
|
|
4619
4637
|
// src/harness/adapters/process/recover-lock.ts
|
|
4620
|
-
import { existsSync as
|
|
4621
|
-
import { join as
|
|
4638
|
+
import { existsSync as existsSync31, lstatSync as lstatSync11, unlinkSync as unlinkSync5 } from "fs";
|
|
4639
|
+
import { join as join46 } from "path";
|
|
4622
4640
|
function recoverInputLock(backend, sessionId) {
|
|
4623
4641
|
backend.inspect(sessionId);
|
|
4624
|
-
const path =
|
|
4625
|
-
if (!
|
|
4626
|
-
const before =
|
|
4642
|
+
const path = join46(backend.runDirectory, `${sessionId}.input.lock`);
|
|
4643
|
+
if (!existsSync31(path)) return;
|
|
4644
|
+
const before = lstatSync11(path);
|
|
4627
4645
|
const record = object8(readPrivateJson2(path));
|
|
4628
4646
|
if (record.id !== sessionId) throw new Error("foreign_writer_lock");
|
|
4629
4647
|
if (!controllerExited(text(record.controller, "controller_identity")))
|
|
4630
4648
|
throw new Error("writer_still_alive");
|
|
4631
|
-
if (
|
|
4649
|
+
if (lstatSync11(path).ino !== before.ino)
|
|
4632
4650
|
throw new Error("writer_lock_changed");
|
|
4633
4651
|
unlinkSync5(path);
|
|
4634
4652
|
}
|
|
@@ -4642,7 +4660,7 @@ var init_recover_lock = __esm({
|
|
|
4642
4660
|
});
|
|
4643
4661
|
|
|
4644
4662
|
// src/harness/launch/service.ts
|
|
4645
|
-
import { existsSync as
|
|
4663
|
+
import { existsSync as existsSync32 } from "fs";
|
|
4646
4664
|
function assertLaunchAdmission(records2) {
|
|
4647
4665
|
for (const journal of launchJournals(records2)) {
|
|
4648
4666
|
if (journal.abortRecord()?.state === "stopped" && unitActive(journal.intent().unit))
|
|
@@ -4667,12 +4685,12 @@ function inspectPendingStarts(context) {
|
|
|
4667
4685
|
role: journal.intent().role,
|
|
4668
4686
|
runtime: journal.intent().runtime,
|
|
4669
4687
|
controllerExited: controllerExited(journal.intent().controllerIdentity),
|
|
4670
|
-
proofPresent:
|
|
4671
|
-
recordPresent:
|
|
4688
|
+
proofPresent: existsSync32(journal.path("proof")),
|
|
4689
|
+
recordPresent: existsSync32(journal.records.path(journal.id)),
|
|
4672
4690
|
registered: context.store.state.group.sessions.some(
|
|
4673
4691
|
(session) => session.id === journal.id
|
|
4674
4692
|
),
|
|
4675
|
-
authorizationPresent:
|
|
4693
|
+
authorizationPresent: existsSync32(journal.path("grant")),
|
|
4676
4694
|
processingConfirmed: false,
|
|
4677
4695
|
inputReplayed: false
|
|
4678
4696
|
}));
|
|
@@ -4690,7 +4708,7 @@ var init_service6 = __esm({
|
|
|
4690
4708
|
});
|
|
4691
4709
|
|
|
4692
4710
|
// src/harness/sessions/service.ts
|
|
4693
|
-
import { realpathSync as
|
|
4711
|
+
import { realpathSync as realpathSync13 } from "fs";
|
|
4694
4712
|
import { relative as relative15 } from "path";
|
|
4695
4713
|
async function startSession(context, input) {
|
|
4696
4714
|
const { store, backend } = context;
|
|
@@ -4724,7 +4742,7 @@ async function startSession(context, input) {
|
|
|
4724
4742
|
);
|
|
4725
4743
|
if (!backend.inspect(main2.id).alive) throw new Error("main_unavailable");
|
|
4726
4744
|
}
|
|
4727
|
-
const cwd =
|
|
4745
|
+
const cwd = realpathSync13(text(input.cwd ?? group.projectRoot, "cwd"));
|
|
4728
4746
|
const scope = relative15(group.projectRoot, cwd);
|
|
4729
4747
|
if (scope === ".." || scope.startsWith("../"))
|
|
4730
4748
|
throw new Error("cwd_outside_project");
|
|
@@ -4832,7 +4850,7 @@ var init_service7 = __esm({
|
|
|
4832
4850
|
});
|
|
4833
4851
|
|
|
4834
4852
|
// src/harness/recovery/service.ts
|
|
4835
|
-
import { randomUUID as
|
|
4853
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
4836
4854
|
function inspectRecovery(context, includeStarts = false) {
|
|
4837
4855
|
const pendingStarts = includeStarts ? inspectPendingStarts(context) : [];
|
|
4838
4856
|
return {
|
|
@@ -4915,7 +4933,7 @@ async function replaceMain(context, input) {
|
|
|
4915
4933
|
if (observation.alive) throw new Error("main_still_alive");
|
|
4916
4934
|
previous.stopped = true;
|
|
4917
4935
|
}
|
|
4918
|
-
store.state.group.generation =
|
|
4936
|
+
store.state.group.generation = randomUUID12();
|
|
4919
4937
|
store.state.leases = [];
|
|
4920
4938
|
store.save();
|
|
4921
4939
|
const result = await startSession(context, { ...input, role: "main" });
|
|
@@ -5187,9 +5205,9 @@ var init_invoke = __esm({
|
|
|
5187
5205
|
});
|
|
5188
5206
|
|
|
5189
5207
|
// src/tui/adapters/process/runtime.ts
|
|
5190
|
-
import { dirname as
|
|
5208
|
+
import { dirname as dirname24, join as join47 } from "path";
|
|
5191
5209
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5192
|
-
import { existsSync as
|
|
5210
|
+
import { existsSync as existsSync33, realpathSync as realpathSync14 } from "fs";
|
|
5193
5211
|
function dependencies() {
|
|
5194
5212
|
const tmux = locateExecutable("tmux");
|
|
5195
5213
|
const flock = locateExecutable("flock");
|
|
@@ -5222,10 +5240,10 @@ function runtime() {
|
|
|
5222
5240
|
if (!available.supported || !available.node)
|
|
5223
5241
|
throw new Error("tui_dependencies_unavailable");
|
|
5224
5242
|
const current = fileURLToPath4(import.meta.url);
|
|
5225
|
-
const directory =
|
|
5226
|
-
const bundled =
|
|
5227
|
-
if (!
|
|
5228
|
-
return { node:
|
|
5243
|
+
const directory = dirname24(current);
|
|
5244
|
+
const bundled = join47(directory, "tui-worker.js");
|
|
5245
|
+
if (!existsSync33(bundled)) throw new Error("tui_build_required");
|
|
5246
|
+
return { node: realpathSync14(available.node), worker: bundled };
|
|
5229
5247
|
}
|
|
5230
5248
|
var init_runtime2 = __esm({
|
|
5231
5249
|
"src/tui/adapters/process/runtime.ts"() {
|
|
@@ -5237,34 +5255,34 @@ var init_runtime2 = __esm({
|
|
|
5237
5255
|
});
|
|
5238
5256
|
|
|
5239
5257
|
// src/tui/state/store.ts
|
|
5240
|
-
import { randomUUID as
|
|
5258
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
5241
5259
|
import {
|
|
5242
|
-
existsSync as
|
|
5243
|
-
lstatSync as
|
|
5260
|
+
existsSync as existsSync34,
|
|
5261
|
+
lstatSync as lstatSync12,
|
|
5244
5262
|
mkdirSync as mkdirSync11,
|
|
5245
|
-
realpathSync as
|
|
5263
|
+
realpathSync as realpathSync15,
|
|
5246
5264
|
unlinkSync as unlinkSync6,
|
|
5247
5265
|
writeFileSync as writeFileSync13
|
|
5248
5266
|
} from "fs";
|
|
5249
5267
|
import { homedir as homedir10 } from "os";
|
|
5250
|
-
import { dirname as
|
|
5268
|
+
import { dirname as dirname25, join as join48 } from "path";
|
|
5251
5269
|
function createDirectory() {
|
|
5252
|
-
const root =
|
|
5253
|
-
process.env.XDG_STATE_HOME ??
|
|
5270
|
+
const root = join48(
|
|
5271
|
+
process.env.XDG_STATE_HOME ?? join48(homedir10(), ".local/state"),
|
|
5254
5272
|
"pmcp/tui"
|
|
5255
5273
|
);
|
|
5256
5274
|
mkdirSync11(root, { recursive: true, mode: 448 });
|
|
5257
|
-
const directory =
|
|
5258
|
-
if (Buffer.byteLength(
|
|
5275
|
+
const directory = join48(realpathSync15(root), randomUUID13().slice(0, 16));
|
|
5276
|
+
if (Buffer.byteLength(join48(directory, "s")) > 100)
|
|
5259
5277
|
throw new Error("socket_path_too_long");
|
|
5260
5278
|
mkdirSync11(directory, { mode: 448 });
|
|
5261
|
-
const owner =
|
|
5262
|
-
privateJson(
|
|
5279
|
+
const owner = randomUUID13();
|
|
5280
|
+
privateJson(join48(directory, "owner.json"), {
|
|
5263
5281
|
owner,
|
|
5264
5282
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5265
5283
|
});
|
|
5266
5284
|
writeFileSync13(
|
|
5267
|
-
|
|
5285
|
+
join48(directory, "tmux.conf"),
|
|
5268
5286
|
[
|
|
5269
5287
|
"set -g remain-on-exit on",
|
|
5270
5288
|
"set -g history-limit 2000",
|
|
@@ -5279,13 +5297,13 @@ function createDirectory() {
|
|
|
5279
5297
|
return { directory, owner };
|
|
5280
5298
|
}
|
|
5281
5299
|
function readView(path) {
|
|
5282
|
-
const file2 =
|
|
5283
|
-
const directory =
|
|
5284
|
-
const stat =
|
|
5300
|
+
const file2 = realpathSync15(path);
|
|
5301
|
+
const directory = dirname25(file2);
|
|
5302
|
+
const stat = lstatSync12(directory);
|
|
5285
5303
|
if (!stat.isDirectory() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
5286
5304
|
throw new Error("unsafe_view_directory");
|
|
5287
5305
|
const value = object8(readPrivateJson2(file2));
|
|
5288
|
-
const metadata2 = object8(readPrivateJson2(
|
|
5306
|
+
const metadata2 = object8(readPrivateJson2(join48(directory, "owner.json")));
|
|
5289
5307
|
if (value.schemaVersion !== 1 || value.directory !== directory || value.owner !== metadata2.owner)
|
|
5290
5308
|
throw new Error("foreign_view_owner");
|
|
5291
5309
|
for (const key of [
|
|
@@ -5322,8 +5340,8 @@ function readView(path) {
|
|
|
5322
5340
|
throw new Error("invalid_view_identity");
|
|
5323
5341
|
}
|
|
5324
5342
|
const view = value;
|
|
5325
|
-
const fallback =
|
|
5326
|
-
if (
|
|
5343
|
+
const fallback = join48(directory, "callback-failure.json");
|
|
5344
|
+
if (existsSync34(fallback)) {
|
|
5327
5345
|
const recorded = object8(readPrivateJson2(fallback));
|
|
5328
5346
|
const failure2 = object8(recorded.failure);
|
|
5329
5347
|
if (recorded.owner !== view.owner || typeof failure2.code !== "string" || !/^[a-z][a-z0-9_]{0,79}$/.test(failure2.code))
|
|
@@ -5334,8 +5352,8 @@ function readView(path) {
|
|
|
5334
5352
|
return { file: file2, view };
|
|
5335
5353
|
}
|
|
5336
5354
|
function dismissFailure(file2, view) {
|
|
5337
|
-
const fallback =
|
|
5338
|
-
if (
|
|
5355
|
+
const fallback = join48(view.directory, "callback-failure.json");
|
|
5356
|
+
if (existsSync34(fallback)) unlinkSync6(fallback);
|
|
5339
5357
|
delete view.failure;
|
|
5340
5358
|
saveView(file2, view);
|
|
5341
5359
|
}
|
|
@@ -5397,7 +5415,7 @@ var init_lock = __esm({
|
|
|
5397
5415
|
});
|
|
5398
5416
|
|
|
5399
5417
|
// src/tui/adapters/process/budget.ts
|
|
5400
|
-
import { readFileSync as
|
|
5418
|
+
import { readFileSync as readFileSync37 } from "fs";
|
|
5401
5419
|
function viewUnit(owner) {
|
|
5402
5420
|
if (!/^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(owner))
|
|
5403
5421
|
throw new Error("invalid_view_owner");
|
|
@@ -5425,7 +5443,7 @@ function startPresentation(owner, args, env) {
|
|
|
5425
5443
|
);
|
|
5426
5444
|
}
|
|
5427
5445
|
function assertPresentationBudget(owner, pid) {
|
|
5428
|
-
if (!
|
|
5446
|
+
if (!readFileSync37(`/proc/${pid}/cgroup`, "utf8").includes(
|
|
5429
5447
|
`/${viewUnit(owner)}
|
|
5430
5448
|
`
|
|
5431
5449
|
))
|
|
@@ -5440,7 +5458,7 @@ var init_budget2 = __esm({
|
|
|
5440
5458
|
|
|
5441
5459
|
// src/tui/adapters/tmux/connection.ts
|
|
5442
5460
|
import { spawn as spawn3 } from "child_process";
|
|
5443
|
-
import { join as
|
|
5461
|
+
import { join as join49 } from "path";
|
|
5444
5462
|
var ViewConnection;
|
|
5445
5463
|
var init_connection2 = __esm({
|
|
5446
5464
|
"src/tui/adapters/tmux/connection.ts"() {
|
|
@@ -5453,7 +5471,7 @@ var init_connection2 = __esm({
|
|
|
5453
5471
|
this.directory = directory;
|
|
5454
5472
|
this.owner = owner;
|
|
5455
5473
|
this.identity = identity;
|
|
5456
|
-
this.socket =
|
|
5474
|
+
this.socket = join49(directory, "s");
|
|
5457
5475
|
}
|
|
5458
5476
|
directory;
|
|
5459
5477
|
owner;
|
|
@@ -5466,14 +5484,14 @@ var init_connection2 = __esm({
|
|
|
5466
5484
|
"-S",
|
|
5467
5485
|
this.socket,
|
|
5468
5486
|
"-f",
|
|
5469
|
-
|
|
5487
|
+
join49(this.directory, "tmux.conf"),
|
|
5470
5488
|
...args
|
|
5471
5489
|
];
|
|
5472
5490
|
if (args[0] === "new-session" && !this.identity)
|
|
5473
5491
|
return startPresentation(this.owner, options2, env).trim();
|
|
5474
5492
|
return command(
|
|
5475
5493
|
"tmux",
|
|
5476
|
-
["-S", this.socket, "-f",
|
|
5494
|
+
["-S", this.socket, "-f", join49(this.directory, "tmux.conf"), ...args],
|
|
5477
5495
|
{ env }
|
|
5478
5496
|
).trim();
|
|
5479
5497
|
}
|
|
@@ -5537,7 +5555,7 @@ var init_connection2 = __esm({
|
|
|
5537
5555
|
});
|
|
5538
5556
|
|
|
5539
5557
|
// src/tui/application/feedback.ts
|
|
5540
|
-
import { join as
|
|
5558
|
+
import { join as join50 } from "path";
|
|
5541
5559
|
function describeFailure(action, error) {
|
|
5542
5560
|
const message = error instanceof Error ? error.message : "view_action_failed";
|
|
5543
5561
|
const code = error instanceof RangeError ? "terminal_size_invalid" : failureCode(message);
|
|
@@ -5574,6 +5592,13 @@ var init_feedback = __esm({
|
|
|
5574
5592
|
init_store3();
|
|
5575
5593
|
init_store2();
|
|
5576
5594
|
hints = {
|
|
5595
|
+
startup_requires_recovery: "Inspect the saved group with pmcp tui plan and pmcp harness recovery inspect. No runtime was restarted.",
|
|
5596
|
+
startup_group_paused: "The saved group is paused. Resume it explicitly through the harness CLI before reopening.",
|
|
5597
|
+
startup_main_unavailable: "Inspect the saved main and recovery state. Stop the old group explicitly before using pmcp tui --new.",
|
|
5598
|
+
startup_busy: "Another workspace launch is running. Wait and retry.",
|
|
5599
|
+
startup_runtime_conflict: "The saved main uses another runtime. Stop the old group before creating a new workspace.",
|
|
5600
|
+
workspace_already_running: "Reopen the existing workspace, or stop its exact owned group before using --new.",
|
|
5601
|
+
runtime_executable_unavailable: "Install the selected native CLI on PATH, or choose an installed runtime with --runtime.",
|
|
5577
5602
|
control_connection_lost: "The native writer or lease disappeared. Inspect and release control before reconnecting.",
|
|
5578
5603
|
control_busy: "Another controller holds the lease. Release it explicitly before retrying.",
|
|
5579
5604
|
native_writer_active: "A native writer is still attached. Detach that writer before retrying.",
|
|
@@ -5935,16 +5960,16 @@ var init_common = __esm({
|
|
|
5935
5960
|
});
|
|
5936
5961
|
|
|
5937
5962
|
// src/tui/application/create.ts
|
|
5938
|
-
import { join as
|
|
5939
|
-
import { realpathSync as
|
|
5963
|
+
import { join as join51 } from "path";
|
|
5964
|
+
import { realpathSync as realpathSync16 } from "fs";
|
|
5940
5965
|
async function createView(input) {
|
|
5941
5966
|
const plan2 = planLayout(input.columns, input.rows);
|
|
5942
5967
|
const helpers = runtime();
|
|
5943
5968
|
const mailbox = input.mailbox ? {
|
|
5944
|
-
config:
|
|
5945
|
-
actorFile:
|
|
5969
|
+
config: realpathSync16(input.mailbox.config),
|
|
5970
|
+
actorFile: realpathSync16(input.mailbox.actorFile)
|
|
5946
5971
|
} : void 0;
|
|
5947
|
-
const groupFile =
|
|
5972
|
+
const groupFile = realpathSync16(input.groupFile);
|
|
5948
5973
|
const group = new GroupStore(groupFile).state.group;
|
|
5949
5974
|
const common2 = new CommonHarness({
|
|
5950
5975
|
groupFile,
|
|
@@ -5957,7 +5982,7 @@ async function createView(input) {
|
|
|
5957
5982
|
);
|
|
5958
5983
|
if (!main2) throw new Error("view_requires_live_main");
|
|
5959
5984
|
const { directory, owner } = createDirectory();
|
|
5960
|
-
const file2 =
|
|
5985
|
+
const file2 = join51(directory, "view.json");
|
|
5961
5986
|
const connection = new ViewConnection(directory, owner);
|
|
5962
5987
|
const argv = [helpers.node, helpers.worker, "attach", file2, main2.id];
|
|
5963
5988
|
const result = connection.raw([
|
|
@@ -6185,7 +6210,7 @@ var init_navigation = __esm({
|
|
|
6185
6210
|
});
|
|
6186
6211
|
|
|
6187
6212
|
// src/tui/application/history.ts
|
|
6188
|
-
import { join as
|
|
6213
|
+
import { join as join52 } from "path";
|
|
6189
6214
|
async function observeHistory(context) {
|
|
6190
6215
|
const { view, renderer, common: common2, file: file2 } = context;
|
|
6191
6216
|
await releaseControl(context);
|
|
@@ -6193,7 +6218,7 @@ async function observeHistory(context) {
|
|
|
6193
6218
|
sessionId: view.target,
|
|
6194
6219
|
input: { lines: 2e3, maxBytes: 65536 }
|
|
6195
6220
|
});
|
|
6196
|
-
privateJson(
|
|
6221
|
+
privateJson(join52(view.directory, "history.json"), {
|
|
6197
6222
|
sessionId: view.target,
|
|
6198
6223
|
content: history.content,
|
|
6199
6224
|
truncated: history.truncated
|
|
@@ -6269,8 +6294,8 @@ var init_mailbox = __esm({
|
|
|
6269
6294
|
});
|
|
6270
6295
|
|
|
6271
6296
|
// src/tui/application/lifecycle.ts
|
|
6272
|
-
import { existsSync as
|
|
6273
|
-
import { join as
|
|
6297
|
+
import { existsSync as existsSync35, unlinkSync as unlinkSync7 } from "fs";
|
|
6298
|
+
import { join as join53 } from "path";
|
|
6274
6299
|
function resizeView({ view, renderer, input }) {
|
|
6275
6300
|
renderer.resize(view, planLayout(input.columns, input.rows));
|
|
6276
6301
|
}
|
|
@@ -6285,8 +6310,8 @@ async function stopTarget(context) {
|
|
|
6285
6310
|
async function closeView(context) {
|
|
6286
6311
|
await returnToMain(context);
|
|
6287
6312
|
context.renderer.connection.cleanup();
|
|
6288
|
-
const history =
|
|
6289
|
-
if (
|
|
6313
|
+
const history = join53(context.view.directory, "history.json");
|
|
6314
|
+
if (existsSync35(history)) unlinkSync7(history);
|
|
6290
6315
|
context.view.state = "closed";
|
|
6291
6316
|
saveView(context.file, context.view);
|
|
6292
6317
|
}
|
|
@@ -6463,6 +6488,8 @@ __export(invoke_exports2, {
|
|
|
6463
6488
|
invokeTui: () => invokeTui
|
|
6464
6489
|
});
|
|
6465
6490
|
async function invokeTui(invocation2) {
|
|
6491
|
+
if (invocation2.action === "launch" || invocation2.action === "plan")
|
|
6492
|
+
throw new Error("startup_requires_cli_context");
|
|
6466
6493
|
if (invocation2.action === "doctor")
|
|
6467
6494
|
return { schemaVersion: 1, ok: true, ...dependencies() };
|
|
6468
6495
|
if (invocation2.action === "create") return createView(invocation2.create);
|
|
@@ -6511,6 +6538,319 @@ var init_invoke2 = __esm({
|
|
|
6511
6538
|
}
|
|
6512
6539
|
});
|
|
6513
6540
|
|
|
6541
|
+
// src/tui/startup/config.ts
|
|
6542
|
+
import { realpathSync as realpathSync17 } from "fs";
|
|
6543
|
+
import { resolve as resolve19 } from "path";
|
|
6544
|
+
function startupSettings(request3) {
|
|
6545
|
+
const path = request3.config ? resolve19(request3.cwd, request3.config) : findConfig(request3.cwd);
|
|
6546
|
+
const config = path ? readConfig(path) : void 0;
|
|
6547
|
+
const source2 = config?.raw.tui;
|
|
6548
|
+
const fail2 = (message) => {
|
|
6549
|
+
throw new ConfigError(path ?? "pmcp.toml", `[tui] ${message}`);
|
|
6550
|
+
};
|
|
6551
|
+
if (source2 !== void 0 && (source2 === null || typeof source2 !== "object" || Array.isArray(source2)))
|
|
6552
|
+
fail2("must be a table");
|
|
6553
|
+
const values = source2 ?? {};
|
|
6554
|
+
const allowed3 = /* @__PURE__ */ new Set([
|
|
6555
|
+
"runtime",
|
|
6556
|
+
"memory_mb",
|
|
6557
|
+
"session_memory_mb",
|
|
6558
|
+
"max_active",
|
|
6559
|
+
"group_file"
|
|
6560
|
+
]);
|
|
6561
|
+
for (const key of Object.keys(values)) {
|
|
6562
|
+
if (!allowed3.has(key)) fail2(`unknown key ${key}`);
|
|
6563
|
+
}
|
|
6564
|
+
const string = (key) => {
|
|
6565
|
+
const value = values[key];
|
|
6566
|
+
if (value === void 0) return void 0;
|
|
6567
|
+
if (typeof value !== "string" || !value.trim())
|
|
6568
|
+
fail2(`${key} must be a non-empty string`);
|
|
6569
|
+
return value;
|
|
6570
|
+
};
|
|
6571
|
+
const integer2 = (key, fallback, min, max) => {
|
|
6572
|
+
const value = values[key] ?? fallback;
|
|
6573
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value))
|
|
6574
|
+
fail2(`${key} must be an integer`);
|
|
6575
|
+
if (Number(value) < min || Number(value) > max)
|
|
6576
|
+
fail2(`${key} must be between ${min} and ${max}`);
|
|
6577
|
+
return Number(value);
|
|
6578
|
+
};
|
|
6579
|
+
const configuredRuntime = string("runtime");
|
|
6580
|
+
if (configuredRuntime !== void 0 && configuredRuntime !== "auto" && !Object.hasOwn(runtimeExecutables, configuredRuntime))
|
|
6581
|
+
fail2("runtime must be auto or a supported runtime ID");
|
|
6582
|
+
const available = preference.filter(
|
|
6583
|
+
(runtime3) => locateExecutable(runtimeExecutables[runtime3], request3.cwd)
|
|
6584
|
+
);
|
|
6585
|
+
let runtime2 = request3.runtime ?? configuredRuntime ?? "auto";
|
|
6586
|
+
if (runtime2 === "auto") runtime2 = available[0] ?? "codex-cli";
|
|
6587
|
+
if (!Object.hasOwn(runtimeExecutables, runtime2))
|
|
6588
|
+
fail2("runtime must be a supported runtime ID");
|
|
6589
|
+
const memoryMb = integer2("memory_mb", 2048, 128, 4096);
|
|
6590
|
+
const sessionMemoryMb = integer2("session_memory_mb", 768, 32, 1536);
|
|
6591
|
+
if (sessionMemoryMb > memoryMb)
|
|
6592
|
+
fail2("session memory exceeds the group budget");
|
|
6593
|
+
const groupFile = string("group_file");
|
|
6594
|
+
return {
|
|
6595
|
+
projectRoot: realpathSync17(config?.dir ?? request3.cwd),
|
|
6596
|
+
...config ? { configFile: config.path } : {},
|
|
6597
|
+
runtime: runtime2,
|
|
6598
|
+
allowedRuntimes: [.../* @__PURE__ */ new Set([...available, runtime2])],
|
|
6599
|
+
memoryMb,
|
|
6600
|
+
sessionMemoryMb,
|
|
6601
|
+
maxActive: integer2("max_active", 2, 1, 5),
|
|
6602
|
+
...groupFile ? { groupFile: resolve19(config?.dir ?? request3.cwd, groupFile) } : {}
|
|
6603
|
+
};
|
|
6604
|
+
}
|
|
6605
|
+
var preference;
|
|
6606
|
+
var init_config3 = __esm({
|
|
6607
|
+
"src/tui/startup/config.ts"() {
|
|
6608
|
+
"use strict";
|
|
6609
|
+
init_config();
|
|
6610
|
+
init_registry();
|
|
6611
|
+
init_executable();
|
|
6612
|
+
preference = [
|
|
6613
|
+
"codex-cli",
|
|
6614
|
+
"gemini-cli",
|
|
6615
|
+
"antigravity",
|
|
6616
|
+
"grok-cli",
|
|
6617
|
+
"claude-code"
|
|
6618
|
+
];
|
|
6619
|
+
}
|
|
6620
|
+
});
|
|
6621
|
+
|
|
6622
|
+
// src/tui/startup/state.ts
|
|
6623
|
+
import { createHash as createHash19 } from "crypto";
|
|
6624
|
+
import { existsSync as existsSync36, lstatSync as lstatSync13, mkdirSync as mkdirSync12 } from "fs";
|
|
6625
|
+
import { homedir as homedir11 } from "os";
|
|
6626
|
+
import { dirname as dirname26, join as join54 } from "path";
|
|
6627
|
+
function startupFile(projectRoot) {
|
|
6628
|
+
const root = join54(
|
|
6629
|
+
process.env.XDG_STATE_HOME ?? join54(homedir11(), ".local/state"),
|
|
6630
|
+
"pmcp/launcher"
|
|
6631
|
+
);
|
|
6632
|
+
const key = createHash19("sha256").update(projectRoot).digest("hex");
|
|
6633
|
+
return join54(root, `${key}.json`);
|
|
6634
|
+
}
|
|
6635
|
+
function ensureStartupDirectory(file2) {
|
|
6636
|
+
const directory = dirname26(file2);
|
|
6637
|
+
mkdirSync12(directory, { recursive: true, mode: 448 });
|
|
6638
|
+
const stat = lstatSync13(directory);
|
|
6639
|
+
if (!stat.isDirectory() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
6640
|
+
throw new Error("unsafe_startup_directory");
|
|
6641
|
+
}
|
|
6642
|
+
function readStartup(file2, projectRoot) {
|
|
6643
|
+
if (!existsSync36(file2)) return null;
|
|
6644
|
+
const value = object8(readPrivateJson2(file2));
|
|
6645
|
+
if (value.schemaVersion !== 1 || value.projectRoot !== projectRoot)
|
|
6646
|
+
throw new Error("foreign_startup_state");
|
|
6647
|
+
if (!["creating", "starting", "view-pending", "ready"].includes(
|
|
6648
|
+
String(value.phase)
|
|
6649
|
+
))
|
|
6650
|
+
throw new Error("invalid_startup_state");
|
|
6651
|
+
text(value.runtime, "runtime");
|
|
6652
|
+
if (value.phase !== "creating") {
|
|
6653
|
+
text(value.groupFile, "group_file");
|
|
6654
|
+
text(value.generation, "generation");
|
|
6655
|
+
}
|
|
6656
|
+
if (value.phase === "ready") text(value.viewFile, "view_file");
|
|
6657
|
+
return value;
|
|
6658
|
+
}
|
|
6659
|
+
var init_state = __esm({
|
|
6660
|
+
"src/tui/startup/state.ts"() {
|
|
6661
|
+
"use strict";
|
|
6662
|
+
init_store2();
|
|
6663
|
+
init_validation();
|
|
6664
|
+
}
|
|
6665
|
+
});
|
|
6666
|
+
|
|
6667
|
+
// src/tui/startup/host.ts
|
|
6668
|
+
var host_exports = {};
|
|
6669
|
+
__export(host_exports, {
|
|
6670
|
+
requireTerminal: () => requireTerminal,
|
|
6671
|
+
terminalSupport: () => terminalSupport
|
|
6672
|
+
});
|
|
6673
|
+
function terminalSupport() {
|
|
6674
|
+
try {
|
|
6675
|
+
if (!dependencies().supported)
|
|
6676
|
+
throw new Error("tui_dependencies_unavailable");
|
|
6677
|
+
assertNativeHost();
|
|
6678
|
+
planLayout(process.stdout.columns ?? 0, process.stdout.rows ?? 0);
|
|
6679
|
+
return { supported: true };
|
|
6680
|
+
} catch (error) {
|
|
6681
|
+
return {
|
|
6682
|
+
supported: false,
|
|
6683
|
+
reason: error instanceof Error ? error.message : "terminal_unavailable"
|
|
6684
|
+
};
|
|
6685
|
+
}
|
|
6686
|
+
}
|
|
6687
|
+
function requireTerminal() {
|
|
6688
|
+
if (!interactiveEntry({
|
|
6689
|
+
stdin: process.stdin.isTTY === true,
|
|
6690
|
+
stdout: process.stdout.isTTY === true,
|
|
6691
|
+
term: process.env.TERM,
|
|
6692
|
+
ci: process.env.CI
|
|
6693
|
+
}))
|
|
6694
|
+
throw new Error("terminal_required");
|
|
6695
|
+
const support = terminalSupport();
|
|
6696
|
+
if (!support.supported) throw new Error(support.reason);
|
|
6697
|
+
}
|
|
6698
|
+
var init_host = __esm({
|
|
6699
|
+
"src/tui/startup/host.ts"() {
|
|
6700
|
+
"use strict";
|
|
6701
|
+
init_entry();
|
|
6702
|
+
init_layout();
|
|
6703
|
+
init_admission2();
|
|
6704
|
+
init_runtime2();
|
|
6705
|
+
}
|
|
6706
|
+
});
|
|
6707
|
+
|
|
6708
|
+
// src/tui/startup/lock.ts
|
|
6709
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
6710
|
+
import { closeSync as closeSync8, constants as constants7, fstatSync as fstatSync5, openSync as openSync8 } from "fs";
|
|
6711
|
+
function serializedStartup(request3) {
|
|
6712
|
+
const settings2 = startupSettings(request3);
|
|
6713
|
+
const file2 = startupFile(settings2.projectRoot);
|
|
6714
|
+
ensureStartupDirectory(file2);
|
|
6715
|
+
const lock = `${file2}.lock`;
|
|
6716
|
+
const descriptor = openSync8(
|
|
6717
|
+
lock,
|
|
6718
|
+
constants7.O_CREAT | constants7.O_WRONLY | constants7.O_NOFOLLOW,
|
|
6719
|
+
384
|
|
6720
|
+
);
|
|
6721
|
+
try {
|
|
6722
|
+
const stat = fstatSync5(descriptor);
|
|
6723
|
+
if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
6724
|
+
throw new Error("unsafe_startup_lock");
|
|
6725
|
+
} finally {
|
|
6726
|
+
closeSync8(descriptor);
|
|
6727
|
+
}
|
|
6728
|
+
const helpers = runtime();
|
|
6729
|
+
const encoded = Buffer.from(JSON.stringify(request3)).toString("base64url");
|
|
6730
|
+
const child = spawnSync6(
|
|
6731
|
+
"flock",
|
|
6732
|
+
[
|
|
6733
|
+
"--wait",
|
|
6734
|
+
"10",
|
|
6735
|
+
"--close",
|
|
6736
|
+
"--conflict-exit-code",
|
|
6737
|
+
"73",
|
|
6738
|
+
lock,
|
|
6739
|
+
helpers.node,
|
|
6740
|
+
helpers.worker,
|
|
6741
|
+
"startup",
|
|
6742
|
+
encoded
|
|
6743
|
+
],
|
|
6744
|
+
{
|
|
6745
|
+
encoding: "utf8",
|
|
6746
|
+
timeout: 6e4,
|
|
6747
|
+
maxBuffer: 256 * 1024,
|
|
6748
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
6749
|
+
env: {
|
|
6750
|
+
...process.env,
|
|
6751
|
+
PMCP_TUI_COLUMNS: String(process.stdout.columns),
|
|
6752
|
+
PMCP_TUI_ROWS: String(process.stdout.rows)
|
|
6753
|
+
}
|
|
6754
|
+
}
|
|
6755
|
+
);
|
|
6756
|
+
if (child.status === 73) throw new Error("startup_busy");
|
|
6757
|
+
if (child.error || !child.stdout.trim())
|
|
6758
|
+
throw new Error("startup_requires_recovery");
|
|
6759
|
+
return JSON.parse(child.stdout);
|
|
6760
|
+
}
|
|
6761
|
+
var init_lock2 = __esm({
|
|
6762
|
+
"src/tui/startup/lock.ts"() {
|
|
6763
|
+
"use strict";
|
|
6764
|
+
init_runtime2();
|
|
6765
|
+
init_state();
|
|
6766
|
+
init_config3();
|
|
6767
|
+
}
|
|
6768
|
+
});
|
|
6769
|
+
|
|
6770
|
+
// src/tui/startup/entry.ts
|
|
6771
|
+
var entry_exports = {};
|
|
6772
|
+
__export(entry_exports, {
|
|
6773
|
+
launchStartup: () => launchStartup,
|
|
6774
|
+
planStartup: () => planStartup
|
|
6775
|
+
});
|
|
6776
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
6777
|
+
function planStartup(request3) {
|
|
6778
|
+
const settings2 = startupSettings(request3);
|
|
6779
|
+
const state = readStartup(
|
|
6780
|
+
startupFile(settings2.projectRoot),
|
|
6781
|
+
settings2.projectRoot
|
|
6782
|
+
);
|
|
6783
|
+
return {
|
|
6784
|
+
schemaVersion: 1,
|
|
6785
|
+
ok: true,
|
|
6786
|
+
settings: settings2,
|
|
6787
|
+
savedWorkspace: state,
|
|
6788
|
+
willStartRuntime: !state && !settings2.groupFile,
|
|
6789
|
+
runtimeExecutable: locateExecutable(
|
|
6790
|
+
runtimeExecutables[settings2.runtime],
|
|
6791
|
+
request3.cwd
|
|
6792
|
+
),
|
|
6793
|
+
providerInvoked: false
|
|
6794
|
+
};
|
|
6795
|
+
}
|
|
6796
|
+
async function launchStartup(request3, confirmed) {
|
|
6797
|
+
requireTerminal();
|
|
6798
|
+
const settings2 = startupSettings(request3);
|
|
6799
|
+
const state = readStartup(
|
|
6800
|
+
startupFile(settings2.projectRoot),
|
|
6801
|
+
settings2.projectRoot
|
|
6802
|
+
);
|
|
6803
|
+
const starting = (!state || request3.fresh) && !settings2.groupFile;
|
|
6804
|
+
if (starting) {
|
|
6805
|
+
if (!locateExecutable(runtimeExecutables[settings2.runtime], request3.cwd))
|
|
6806
|
+
throw new Error("runtime_executable_unavailable");
|
|
6807
|
+
process.stderr.write(
|
|
6808
|
+
`PMCP main: ${settings2.runtime} | ${settings2.projectRoot}
|
|
6809
|
+
`
|
|
6810
|
+
);
|
|
6811
|
+
if (!confirmed && !await confirmStart(settings2.runtime))
|
|
6812
|
+
return {
|
|
6813
|
+
schemaVersion: 1,
|
|
6814
|
+
ok: true,
|
|
6815
|
+
cancelled: true,
|
|
6816
|
+
providerInvoked: false
|
|
6817
|
+
};
|
|
6818
|
+
}
|
|
6819
|
+
const prepared = serializedStartup(request3);
|
|
6820
|
+
if (prepared.ok !== true) return prepared;
|
|
6821
|
+
return invokeTui({
|
|
6822
|
+
action: "open",
|
|
6823
|
+
viewFile: String(prepared.viewFile),
|
|
6824
|
+
input: {}
|
|
6825
|
+
});
|
|
6826
|
+
}
|
|
6827
|
+
async function confirmStart(runtime2) {
|
|
6828
|
+
const prompt = createInterface2({
|
|
6829
|
+
input: process.stdin,
|
|
6830
|
+
output: process.stderr
|
|
6831
|
+
});
|
|
6832
|
+
try {
|
|
6833
|
+
const answer = await prompt.question(
|
|
6834
|
+
`Start ${runtime2}? [Enter=start, q=cancel] `
|
|
6835
|
+
);
|
|
6836
|
+
return answer.trim() === "";
|
|
6837
|
+
} finally {
|
|
6838
|
+
prompt.close();
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6841
|
+
var init_entry2 = __esm({
|
|
6842
|
+
"src/tui/startup/entry.ts"() {
|
|
6843
|
+
"use strict";
|
|
6844
|
+
init_executable();
|
|
6845
|
+
init_registry();
|
|
6846
|
+
init_invoke2();
|
|
6847
|
+
init_config3();
|
|
6848
|
+
init_state();
|
|
6849
|
+
init_host();
|
|
6850
|
+
init_lock2();
|
|
6851
|
+
}
|
|
6852
|
+
});
|
|
6853
|
+
|
|
6514
6854
|
// src/cli/command.ts
|
|
6515
6855
|
var ArgumentError = class extends Error {
|
|
6516
6856
|
constructor(message) {
|
|
@@ -6585,7 +6925,12 @@ function printHelp(ui, commands, notImplemented) {
|
|
|
6585
6925
|
ui.line();
|
|
6586
6926
|
ui.table(commands.map((command2) => [command2.name, command2.describe]));
|
|
6587
6927
|
ui.line();
|
|
6588
|
-
ui.line(
|
|
6928
|
+
ui.line(
|
|
6929
|
+
" With no command, pmcp opens a terminal workspace on a supported TTY."
|
|
6930
|
+
);
|
|
6931
|
+
ui.line(
|
|
6932
|
+
" Piped launches retain MCP stdio. Use pmcp serve explicitly for MCP."
|
|
6933
|
+
);
|
|
6589
6934
|
ui.line();
|
|
6590
6935
|
ui.table([
|
|
6591
6936
|
["-q, --quiet", "only errors"],
|
|
@@ -6688,6 +7033,9 @@ var Ui = class {
|
|
|
6688
7033
|
}
|
|
6689
7034
|
};
|
|
6690
7035
|
|
|
7036
|
+
// src/commands/index.ts
|
|
7037
|
+
init_entry();
|
|
7038
|
+
|
|
6691
7039
|
// src/intent.ts
|
|
6692
7040
|
function normaliseIntent(intent) {
|
|
6693
7041
|
return intent.toLowerCase().replaceAll(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/u).filter(Boolean).join(" ");
|
|
@@ -9163,7 +9511,7 @@ function registerMailboxTools(server, service) {
|
|
|
9163
9511
|
|
|
9164
9512
|
// src/mcp.ts
|
|
9165
9513
|
var SERVER_NAME = "pmcp";
|
|
9166
|
-
var SERVER_VERSION = "0.
|
|
9514
|
+
var SERVER_VERSION = "0.15.0";
|
|
9167
9515
|
function registerSkillTools(server, tools) {
|
|
9168
9516
|
registerCatalogTool(server, tools);
|
|
9169
9517
|
registerFindTool(server, tools);
|
|
@@ -9351,7 +9699,7 @@ async function prepareSearch(catalog, options2) {
|
|
|
9351
9699
|
// src/commands/serve.ts
|
|
9352
9700
|
var serveCommand = {
|
|
9353
9701
|
name: "serve",
|
|
9354
|
-
describe: "Run the MCP server on stdio (the default
|
|
9702
|
+
describe: "Run the MCP server on stdio (also the default for piped launches)",
|
|
9355
9703
|
usage: "pmcp serve [--root <dir>] [--scope <prefix>] [--marketplace <dir>] [--package <dir>] [--workspace <dir>] [--config <file> | --no-config] [--prompts]",
|
|
9356
9704
|
options: [
|
|
9357
9705
|
...CATALOG_OPTIONS,
|
|
@@ -9812,6 +10160,10 @@ function installedPackageDirectory(project2, name) {
|
|
|
9812
10160
|
for (; ; ) {
|
|
9813
10161
|
const candidate2 = join11(directory, "node_modules", name);
|
|
9814
10162
|
if (existsSync10(join11(candidate2, "package.json"))) return candidate2;
|
|
10163
|
+
if (existsSync10(join11(directory, ".git")))
|
|
10164
|
+
throw new Error(
|
|
10165
|
+
"Installed package manifest was not found in this repository"
|
|
10166
|
+
);
|
|
9815
10167
|
const parent = dirname11(directory);
|
|
9816
10168
|
if (parent === directory)
|
|
9817
10169
|
throw new Error("Installed package manifest was not found");
|
|
@@ -9829,6 +10181,8 @@ function locate(name, project2) {
|
|
|
9829
10181
|
);
|
|
9830
10182
|
} catch (error) {
|
|
9831
10183
|
if (!absent(error)) return { name, reason: "unreadable_manifest" };
|
|
10184
|
+
if (existsSync10(join11(directory, ".git")))
|
|
10185
|
+
return { name, reason: "not_installed" };
|
|
9832
10186
|
const parent = dirname11(directory);
|
|
9833
10187
|
if (parent === directory) return { name, reason: "not_installed" };
|
|
9834
10188
|
directory = parent;
|
|
@@ -9885,30 +10239,77 @@ function readInstalledDependencies(projectDirectory) {
|
|
|
9885
10239
|
}
|
|
9886
10240
|
|
|
9887
10241
|
// src/install/plan.ts
|
|
9888
|
-
import { createHash as createHash7 } from "crypto";
|
|
9889
|
-
import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
|
|
9890
|
-
import { dirname as dirname12, join as join12, relative as relative6, resolve as resolve9, sep as sep5 } from "path";
|
|
9891
10242
|
import semver from "semver";
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
|
|
9895
|
-
|
|
9896
|
-
|
|
9897
|
-
|
|
10243
|
+
|
|
10244
|
+
// src/install/manager.ts
|
|
10245
|
+
import { existsSync as existsSync11 } from "fs";
|
|
10246
|
+
import { join as join12, relative as relative6, sep as sep5 } from "path";
|
|
10247
|
+
function installationCommand(context, specs) {
|
|
10248
|
+
if (context.manager === "npm") {
|
|
10249
|
+
const args2 = [
|
|
10250
|
+
"install",
|
|
10251
|
+
"--save-dev",
|
|
10252
|
+
"--save-exact",
|
|
10253
|
+
"--ignore-scripts",
|
|
10254
|
+
"--no-audit",
|
|
10255
|
+
"--no-fund"
|
|
10256
|
+
];
|
|
10257
|
+
if (context.project !== context.root)
|
|
10258
|
+
args2.push(
|
|
10259
|
+
`--workspace=${relative6(context.root, context.project).split(sep5).join("/")}`
|
|
9898
10260
|
);
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
|
|
9904
|
-
|
|
9905
|
-
|
|
10261
|
+
return { executable: "npm", args: [...args2, ...specs], cwd: context.root };
|
|
10262
|
+
}
|
|
10263
|
+
const args = context.manager === "pnpm" ? ["add", "--save-dev", "--save-exact", "--ignore-scripts"] : ["add", "--dev", "--exact", "--ignore-scripts"];
|
|
10264
|
+
if (context.manager === "pnpm" && context.project === context.root && existsSync11(join12(context.root, "pnpm-workspace.yaml")))
|
|
10265
|
+
args.push("--workspace-root");
|
|
10266
|
+
return {
|
|
10267
|
+
executable: context.manager,
|
|
10268
|
+
args: [...args, ...specs],
|
|
10269
|
+
cwd: context.project
|
|
10270
|
+
};
|
|
10271
|
+
}
|
|
10272
|
+
function integrityArguments(manager, spec) {
|
|
10273
|
+
const verb = manager === "bun" ? "info" : "view";
|
|
10274
|
+
return [verb, spec, "dist.integrity", "--json"];
|
|
10275
|
+
}
|
|
10276
|
+
|
|
10277
|
+
// src/install/context.ts
|
|
10278
|
+
import { createHash as createHash7 } from "crypto";
|
|
10279
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15, realpathSync as realpathSync4 } from "fs";
|
|
10280
|
+
import { dirname as dirname12, join as join14 } from "path";
|
|
10281
|
+
|
|
10282
|
+
// src/install/workspace.ts
|
|
10283
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14, realpathSync as realpathSync3 } from "fs";
|
|
10284
|
+
import { join as join13 } from "path";
|
|
10285
|
+
import { parse as parse3 } from "yaml";
|
|
10286
|
+
function workspaceContains(root, project2) {
|
|
10287
|
+
const file2 = join13(root, "pnpm-workspace.yaml");
|
|
10288
|
+
if (!existsSync12(file2)) return workspacePackageDirs(root).includes(project2);
|
|
10289
|
+
const value = parse3(readFileSync14(file2, "utf8"));
|
|
10290
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
10291
|
+
throw new Error("invalid pnpm workspace configuration");
|
|
10292
|
+
const patterns = value.packages ?? [];
|
|
10293
|
+
if (!Array.isArray(patterns) || patterns.some((item) => typeof item !== "string"))
|
|
10294
|
+
throw new Error("pnpm workspace packages must be string patterns");
|
|
10295
|
+
const included = /* @__PURE__ */ new Set([root]);
|
|
10296
|
+
const excluded = /* @__PURE__ */ new Set();
|
|
10297
|
+
for (const raw of patterns) {
|
|
10298
|
+
const negated = raw.startsWith("!");
|
|
10299
|
+
const pattern = negated ? raw.slice(1) : raw;
|
|
10300
|
+
if (!pattern || pattern.split("/").includes("..") || pattern.startsWith("/"))
|
|
10301
|
+
throw new Error("workspace patterns must stay inside the workspace");
|
|
10302
|
+
for (const alternative of expandBraces(pattern)) {
|
|
10303
|
+
for (const match of globDirs(root, alternative)) {
|
|
10304
|
+
(negated ? excluded : included).add(realpathSync3(match));
|
|
9906
10305
|
}
|
|
9907
10306
|
}
|
|
9908
10307
|
}
|
|
9909
|
-
return
|
|
10308
|
+
return project2 === root || included.has(project2) && !excluded.has(project2);
|
|
9910
10309
|
}
|
|
9911
|
-
|
|
10310
|
+
|
|
10311
|
+
// src/install/context.ts
|
|
10312
|
+
var locks = [
|
|
9912
10313
|
"package-lock.json",
|
|
9913
10314
|
"npm-shrinkwrap.json",
|
|
9914
10315
|
"bun.lock",
|
|
@@ -9921,7 +10322,7 @@ function fingerprint(files) {
|
|
|
9921
10322
|
for (const file2 of files) {
|
|
9922
10323
|
hash2.update(file2).update("\0");
|
|
9923
10324
|
try {
|
|
9924
|
-
hash2.update(
|
|
10325
|
+
hash2.update(readFileSync15(file2));
|
|
9925
10326
|
} catch (error) {
|
|
9926
10327
|
if (error.code !== "ENOENT") throw error;
|
|
9927
10328
|
hash2.update("absent");
|
|
@@ -9932,65 +10333,71 @@ function fingerprint(files) {
|
|
|
9932
10333
|
}
|
|
9933
10334
|
function manifestAt(directory) {
|
|
9934
10335
|
const value = JSON.parse(
|
|
9935
|
-
|
|
10336
|
+
readFileSync15(join14(directory, "package.json"), "utf8")
|
|
9936
10337
|
);
|
|
9937
10338
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
9938
10339
|
throw new Error("invalid project manifest");
|
|
9939
10340
|
return value;
|
|
9940
10341
|
}
|
|
9941
|
-
function
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
|
|
10342
|
+
function managerForLock(file2) {
|
|
10343
|
+
if (file2.startsWith("bun.")) return "bun";
|
|
10344
|
+
if (file2 === "pnpm-lock.yaml") return "pnpm";
|
|
10345
|
+
if (file2 === "package-lock.json" || file2 === "npm-shrinkwrap.json")
|
|
10346
|
+
return "npm";
|
|
10347
|
+
return "unsupported";
|
|
10348
|
+
}
|
|
10349
|
+
function workspaceRoot(project2) {
|
|
9945
10350
|
let ancestor = project2;
|
|
9946
10351
|
for (; ; ) {
|
|
9947
|
-
if (ancestor !== project2 &&
|
|
10352
|
+
if (ancestor !== project2 && existsSync13(join14(ancestor, "package.json"))) {
|
|
9948
10353
|
const candidate2 = manifestAt(ancestor);
|
|
9949
|
-
if (candidate2.workspaces !== void 0) {
|
|
9950
|
-
|
|
9951
|
-
|
|
10354
|
+
if (candidate2.workspaces !== void 0 || existsSync13(join14(ancestor, "pnpm-workspace.yaml"))) {
|
|
10355
|
+
if (!workspaceContains(ancestor, project2))
|
|
10356
|
+
throw new Error("project is excluded from the ancestor workspace");
|
|
10357
|
+
return ancestor;
|
|
9952
10358
|
}
|
|
9953
|
-
if (
|
|
10359
|
+
if (locks.some((file2) => existsSync13(join14(ancestor, file2))))
|
|
10360
|
+
return project2;
|
|
9954
10361
|
}
|
|
9955
|
-
if (
|
|
9956
|
-
|
|
10362
|
+
if (existsSync13(join14(ancestor, ".git")) || dirname12(ancestor) === ancestor)
|
|
10363
|
+
return project2;
|
|
9957
10364
|
ancestor = dirname12(ancestor);
|
|
9958
10365
|
}
|
|
10366
|
+
}
|
|
10367
|
+
function installContext(directory) {
|
|
10368
|
+
const project2 = realpathSync4(directory);
|
|
10369
|
+
const manifest2 = manifestAt(project2);
|
|
10370
|
+
const root = workspaceRoot(project2);
|
|
9959
10371
|
const owner = manifestAt(root);
|
|
9960
|
-
const lockNames =
|
|
9961
|
-
const lockManagers = new Set(
|
|
9962
|
-
lockNames.map((file2) => {
|
|
9963
|
-
if (file2.startsWith("bun.")) return "bun";
|
|
9964
|
-
if (file2 === "package-lock.json" || file2 === "npm-shrinkwrap.json")
|
|
9965
|
-
return "npm";
|
|
9966
|
-
return "unsupported";
|
|
9967
|
-
})
|
|
9968
|
-
);
|
|
10372
|
+
const lockNames = locks.filter((file2) => existsSync13(join14(root, file2)));
|
|
10373
|
+
const lockManagers = new Set(lockNames.map(managerForLock));
|
|
9969
10374
|
if (lockManagers.size > 1 || lockManagers.has("unsupported"))
|
|
9970
10375
|
throw new Error("conflicting or unsupported package manager lockfiles");
|
|
9971
10376
|
const declared = owner.packageManager?.match(
|
|
9972
|
-
/^(npm|bun)@\d+\.\d+\.\d+(?:[-+].+)?$/u
|
|
10377
|
+
/^(npm|bun|pnpm)@\d+\.\d+\.\d+(?:[-+].+)?$/u
|
|
9973
10378
|
)?.[1];
|
|
9974
10379
|
if (owner.packageManager !== void 0 && !declared)
|
|
9975
|
-
throw new Error("only npm and
|
|
10380
|
+
throw new Error("only npm, Bun and pnpm projects are supported");
|
|
9976
10381
|
const locked = [...lockManagers][0];
|
|
9977
10382
|
if (declared && locked && declared !== locked)
|
|
9978
10383
|
throw new Error("packageManager and lockfile disagree");
|
|
9979
10384
|
const manager = declared ?? locked;
|
|
9980
|
-
if (manager !== "npm" && manager !== "bun")
|
|
10385
|
+
if (manager !== "npm" && manager !== "bun" && manager !== "pnpm")
|
|
9981
10386
|
throw new Error(
|
|
9982
10387
|
"declare packageManager or install project dependencies first"
|
|
9983
10388
|
);
|
|
9984
|
-
if (root !== project2 &&
|
|
10389
|
+
if (root !== project2 && locks.some((file2) => existsSync13(join14(project2, file2))))
|
|
9985
10390
|
throw new Error(
|
|
9986
10391
|
"nested project has its own lockfile; resolve the workspace boundary first"
|
|
9987
10392
|
);
|
|
9988
|
-
}
|
|
9989
10393
|
const files = [
|
|
9990
10394
|
.../* @__PURE__ */ new Set([
|
|
9991
|
-
|
|
9992
|
-
|
|
9993
|
-
|
|
10395
|
+
join14(root, "package.json"),
|
|
10396
|
+
join14(project2, "package.json"),
|
|
10397
|
+
join14(root, "pnpm-workspace.yaml"),
|
|
10398
|
+
join14(root, ".npmrc"),
|
|
10399
|
+
join14(project2, ".npmrc"),
|
|
10400
|
+
...locks.map((file2) => join14(root, file2))
|
|
9994
10401
|
])
|
|
9995
10402
|
];
|
|
9996
10403
|
return {
|
|
@@ -10002,6 +10409,27 @@ function installContext(directory) {
|
|
|
10002
10409
|
fingerprint: fingerprint(files)
|
|
10003
10410
|
};
|
|
10004
10411
|
}
|
|
10412
|
+
|
|
10413
|
+
// src/install/plan.ts
|
|
10414
|
+
function matchingSkills(catalog, inventory) {
|
|
10415
|
+
const matches2 = [];
|
|
10416
|
+
for (const dependency of inventory.dependencies) {
|
|
10417
|
+
for (const entry2 of catalog.entries) {
|
|
10418
|
+
const target2 = entry2.targets.find(
|
|
10419
|
+
(target3) => target3.packageName === dependency.name
|
|
10420
|
+
);
|
|
10421
|
+
if (target2 && semver.satisfies(dependency.version, target2.range)) {
|
|
10422
|
+
matches2.push({
|
|
10423
|
+
targetName: dependency.name,
|
|
10424
|
+
targetVersion: dependency.version,
|
|
10425
|
+
verified: target2.verifiedVersions.includes(dependency.version),
|
|
10426
|
+
entry: entry2
|
|
10427
|
+
});
|
|
10428
|
+
}
|
|
10429
|
+
}
|
|
10430
|
+
}
|
|
10431
|
+
return matches2;
|
|
10432
|
+
}
|
|
10005
10433
|
function planInstall(options2) {
|
|
10006
10434
|
if (options2.inventory.issues.length)
|
|
10007
10435
|
throw new Error(
|
|
@@ -10066,49 +10494,27 @@ function planInstall(options2) {
|
|
|
10066
10494
|
const specs = deliveries.map(
|
|
10067
10495
|
(entry2) => `${entry2.packageName}@${entry2.version}`
|
|
10068
10496
|
);
|
|
10069
|
-
let args;
|
|
10070
|
-
if (context.manager === "npm") {
|
|
10071
|
-
args = [
|
|
10072
|
-
"install",
|
|
10073
|
-
"--save-dev",
|
|
10074
|
-
"--save-exact",
|
|
10075
|
-
"--ignore-scripts",
|
|
10076
|
-
"--no-audit",
|
|
10077
|
-
"--no-fund"
|
|
10078
|
-
];
|
|
10079
|
-
if (context.project !== context.root)
|
|
10080
|
-
args.push(
|
|
10081
|
-
`--workspace=${relative6(context.root, context.project).split(sep5).join("/")}`
|
|
10082
|
-
);
|
|
10083
|
-
} else {
|
|
10084
|
-
args = ["add", "--dev", "--exact", "--ignore-scripts"];
|
|
10085
|
-
}
|
|
10086
|
-
args.push(...specs);
|
|
10087
10497
|
return {
|
|
10088
10498
|
catalogRevision: catalog.revision,
|
|
10089
10499
|
context,
|
|
10090
10500
|
changes: deliveries,
|
|
10091
10501
|
recalled,
|
|
10092
|
-
command:
|
|
10093
|
-
executable: context.manager,
|
|
10094
|
-
args,
|
|
10095
|
-
cwd: context.manager === "npm" ? context.root : context.project
|
|
10096
|
-
}
|
|
10502
|
+
command: installationCommand(context, specs)
|
|
10097
10503
|
};
|
|
10098
10504
|
}
|
|
10099
10505
|
|
|
10100
10506
|
// src/commands/remote-options.ts
|
|
10101
|
-
import { join as
|
|
10102
|
-
import { readFileSync as
|
|
10507
|
+
import { dirname as dirname13, join as join16, resolve as resolve9 } from "path";
|
|
10508
|
+
import { existsSync as existsSync14, readFileSync as readFileSync18 } from "fs";
|
|
10103
10509
|
|
|
10104
10510
|
// src/remote/catalog.ts
|
|
10105
|
-
import { readFileSync as
|
|
10511
|
+
import { readFileSync as readFileSync17, statSync as statSync8 } from "fs";
|
|
10106
10512
|
import semver2 from "semver";
|
|
10107
10513
|
import { z as z13 } from "zod";
|
|
10108
10514
|
|
|
10109
10515
|
// src/remote/catalog-cache.ts
|
|
10110
|
-
import { mkdirSync as mkdirSync5, readFileSync as
|
|
10111
|
-
import { join as
|
|
10516
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync16, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
10517
|
+
import { join as join15 } from "path";
|
|
10112
10518
|
var MAX_CACHE_BYTES = 8 * 1024 * 1024;
|
|
10113
10519
|
function memoryCatalogCache() {
|
|
10114
10520
|
const held = /* @__PURE__ */ new Map();
|
|
@@ -10120,11 +10526,11 @@ function memoryCatalogCache() {
|
|
|
10120
10526
|
};
|
|
10121
10527
|
}
|
|
10122
10528
|
function fileCatalogCache(dir) {
|
|
10123
|
-
const path =
|
|
10529
|
+
const path = join15(dir, "catalog-cache.json");
|
|
10124
10530
|
return {
|
|
10125
10531
|
read(origin) {
|
|
10126
10532
|
try {
|
|
10127
|
-
const raw =
|
|
10533
|
+
const raw = readFileSync16(path, "utf8");
|
|
10128
10534
|
if (raw.length > MAX_CACHE_BYTES) return null;
|
|
10129
10535
|
const held = JSON.parse(raw);
|
|
10130
10536
|
if (typeof held !== "object" || held === null) return null;
|
|
@@ -10247,7 +10653,7 @@ function readCatalogFile(path) {
|
|
|
10247
10653
|
const stat = statSync8(path);
|
|
10248
10654
|
if (!stat.isFile() || stat.size > MAX_CATALOG_BYTES)
|
|
10249
10655
|
throw new Error("catalog file exceeds the supported size");
|
|
10250
|
-
const bytes =
|
|
10656
|
+
const bytes = readFileSync17(path);
|
|
10251
10657
|
if (bytes.byteLength > MAX_CATALOG_BYTES)
|
|
10252
10658
|
throw new Error("catalog file exceeds the supported size");
|
|
10253
10659
|
return parseCatalog(JSON.parse(bytes.toString("utf8")));
|
|
@@ -10349,7 +10755,17 @@ function catalogProvider(context) {
|
|
|
10349
10755
|
return given ?? (api2 ? "hosted" : "public");
|
|
10350
10756
|
}
|
|
10351
10757
|
function projectFrom(context) {
|
|
10352
|
-
|
|
10758
|
+
const explicit = one(context.args, "project");
|
|
10759
|
+
if (explicit) return resolve9(context.cwd, explicit);
|
|
10760
|
+
let directory = resolve9(context.cwd);
|
|
10761
|
+
for (; ; ) {
|
|
10762
|
+
if (existsSync14(join16(directory, "package.json"))) return directory;
|
|
10763
|
+
if (existsSync14(join16(directory, ".git")) || dirname13(directory) === directory)
|
|
10764
|
+
throw new ArgumentError(
|
|
10765
|
+
"No project package.json found; provide --project"
|
|
10766
|
+
);
|
|
10767
|
+
directory = dirname13(directory);
|
|
10768
|
+
}
|
|
10353
10769
|
}
|
|
10354
10770
|
function apiOrigin(context) {
|
|
10355
10771
|
return one(context.args, "api") ?? context.env["PMCP_API_ORIGIN"] ?? DEFAULT_API_ORIGIN;
|
|
@@ -10360,11 +10776,11 @@ async function remoteCatalog(context) {
|
|
|
10360
10776
|
if (file2) {
|
|
10361
10777
|
if (one(context.args, "api"))
|
|
10362
10778
|
throw new Error("choose --catalog or --api, not both");
|
|
10363
|
-
return readCatalogFile(
|
|
10779
|
+
return readCatalogFile(resolve9(context.cwd, file2));
|
|
10364
10780
|
}
|
|
10365
10781
|
if (provider === "public") {
|
|
10366
10782
|
const catalog = JSON.parse(
|
|
10367
|
-
|
|
10783
|
+
readFileSync18(join16(publicMarketplaceRoot(), "docs/catalog.json"), "utf8")
|
|
10368
10784
|
);
|
|
10369
10785
|
return parseCatalog({
|
|
10370
10786
|
schemaVersion: 1,
|
|
@@ -10517,7 +10933,7 @@ function createPackageManagerRunner(userConfig, baseEnv = process.env, verifiedI
|
|
|
10517
10933
|
const alreadyVerified = verifiedIntegrity?.get(packageSpec);
|
|
10518
10934
|
if (alreadyVerified) return alreadyVerified;
|
|
10519
10935
|
const { manager, project: project2 } = plan2.context;
|
|
10520
|
-
const args = manager
|
|
10936
|
+
const args = integrityArguments(manager, packageSpec);
|
|
10521
10937
|
const result = spawnSync(manager, args, {
|
|
10522
10938
|
cwd: project2,
|
|
10523
10939
|
env: environment(userConfig, baseEnv),
|
|
@@ -10592,8 +11008,8 @@ function applyInstallPlan(plan2, runner = packageManagerRunner) {
|
|
|
10592
11008
|
|
|
10593
11009
|
// src/install/verify-content.ts
|
|
10594
11010
|
import { createHash as createHash8 } from "crypto";
|
|
10595
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
10596
|
-
import { join as
|
|
11011
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync19, statSync as statSync9 } from "fs";
|
|
11012
|
+
import { join as join17, relative as relative7, resolve as resolve10, sep as sep6 } from "path";
|
|
10597
11013
|
var SKILL = "SKILL.md";
|
|
10598
11014
|
var SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", ".cache"]);
|
|
10599
11015
|
function skillFiles2(root) {
|
|
@@ -10607,7 +11023,7 @@ function skillFiles2(root) {
|
|
|
10607
11023
|
}
|
|
10608
11024
|
for (const name of entries) {
|
|
10609
11025
|
if (SKIP.has(name)) continue;
|
|
10610
|
-
const path =
|
|
11026
|
+
const path = join17(dir, name);
|
|
10611
11027
|
let stats;
|
|
10612
11028
|
try {
|
|
10613
11029
|
stats = statSync9(path);
|
|
@@ -10618,7 +11034,7 @@ function skillFiles2(root) {
|
|
|
10618
11034
|
else if (name === SKILL)
|
|
10619
11035
|
found.set(
|
|
10620
11036
|
relative7(root, path).split(sep6).join("/"),
|
|
10621
|
-
|
|
11037
|
+
readFileSync19(path, "utf8")
|
|
10622
11038
|
);
|
|
10623
11039
|
}
|
|
10624
11040
|
};
|
|
@@ -10634,11 +11050,11 @@ function contentDigest(files) {
|
|
|
10634
11050
|
return `sha256:${hash2.digest("hex")}`;
|
|
10635
11051
|
}
|
|
10636
11052
|
function verifyInstalledContent(project2, packageName2, expected) {
|
|
10637
|
-
let root =
|
|
11053
|
+
let root = resolve10(project2, "node_modules", ...packageName2.split("/"));
|
|
10638
11054
|
try {
|
|
10639
11055
|
root = installedPackageDirectory(project2, packageName2);
|
|
10640
11056
|
} catch {
|
|
10641
|
-
root =
|
|
11057
|
+
root = resolve10(project2, "node_modules", ...packageName2.split("/"));
|
|
10642
11058
|
}
|
|
10643
11059
|
const files = skillFiles2(root);
|
|
10644
11060
|
const actual = contentDigest(files);
|
|
@@ -10652,8 +11068,8 @@ function verifyInstalledContent(project2, packageName2, expected) {
|
|
|
10652
11068
|
}
|
|
10653
11069
|
|
|
10654
11070
|
// src/install/public.ts
|
|
10655
|
-
import { lstatSync, readFileSync as
|
|
10656
|
-
import { join as
|
|
11071
|
+
import { lstatSync, readFileSync as readFileSync20, readdirSync as readdirSync6 } from "fs";
|
|
11072
|
+
import { join as join18 } from "path";
|
|
10657
11073
|
|
|
10658
11074
|
// src/install/archive.ts
|
|
10659
11075
|
import { createHash as createHash9 } from "crypto";
|
|
@@ -10820,7 +11236,7 @@ function verifyPublicDelivery(project2, delivery) {
|
|
|
10820
11236
|
for (const entry2 of readdirSync6(directory, { withFileTypes: true })) {
|
|
10821
11237
|
const path = prefix ? `${prefix}/${entry2.name}` : entry2.name;
|
|
10822
11238
|
if (entry2.isDirectory()) {
|
|
10823
|
-
walk(
|
|
11239
|
+
walk(join18(directory, entry2.name), path);
|
|
10824
11240
|
continue;
|
|
10825
11241
|
}
|
|
10826
11242
|
if (!entry2.isFile() || !expected.has(path))
|
|
@@ -10831,8 +11247,8 @@ function verifyPublicDelivery(project2, delivery) {
|
|
|
10831
11247
|
};
|
|
10832
11248
|
walk(root);
|
|
10833
11249
|
for (const [path, bytes] of files) {
|
|
10834
|
-
const destination =
|
|
10835
|
-
if (!lstatSync(destination).isFile() || !
|
|
11250
|
+
const destination = join18(root, path.slice("package/".length));
|
|
11251
|
+
if (!lstatSync(destination).isFile() || !readFileSync20(destination).equals(bytes))
|
|
10836
11252
|
throw new Error(
|
|
10837
11253
|
"Installed public package differs from its verified archive; inspect the project before retrying"
|
|
10838
11254
|
);
|
|
@@ -10843,7 +11259,7 @@ function verifyPublicDelivery(project2, delivery) {
|
|
|
10843
11259
|
// src/remote/distribution.ts
|
|
10844
11260
|
import { chmodSync as chmodSync4, mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
10845
11261
|
import { tmpdir } from "os";
|
|
10846
|
-
import { join as
|
|
11262
|
+
import { join as join19 } from "path";
|
|
10847
11263
|
function api(origin, path) {
|
|
10848
11264
|
const base = new URL(catalogEndpoint(origin));
|
|
10849
11265
|
base.pathname = path;
|
|
@@ -10983,9 +11399,9 @@ async function revokeDistributionCredential(accessToken, id2, origin = DEFAULT_A
|
|
|
10983
11399
|
}
|
|
10984
11400
|
}
|
|
10985
11401
|
function temporaryRegistryConfig(value) {
|
|
10986
|
-
const directory = mkdtempSync(
|
|
11402
|
+
const directory = mkdtempSync(join19(tmpdir(), "pmcp-registry-"));
|
|
10987
11403
|
chmodSync4(directory, 448);
|
|
10988
|
-
const path =
|
|
11404
|
+
const path = join19(directory, ".npmrc");
|
|
10989
11405
|
const registry = new URL(value.registry);
|
|
10990
11406
|
const auth = `//${registry.host}${registry.pathname}:_authToken=${value.token}`;
|
|
10991
11407
|
writeFileSync3(path, `${value.scope}:registry=${registry.href}
|
|
@@ -11238,36 +11654,34 @@ import { homedir as homedir6 } from "os";
|
|
|
11238
11654
|
// src/project/apply.ts
|
|
11239
11655
|
import { createHash as createHash13 } from "crypto";
|
|
11240
11656
|
import {
|
|
11241
|
-
existsSync as
|
|
11242
|
-
lstatSync as
|
|
11657
|
+
existsSync as existsSync20,
|
|
11658
|
+
lstatSync as lstatSync5,
|
|
11243
11659
|
mkdirSync as mkdirSync7,
|
|
11244
|
-
readFileSync as
|
|
11660
|
+
readFileSync as readFileSync25,
|
|
11245
11661
|
readdirSync as readdirSync9,
|
|
11246
11662
|
readlinkSync as readlinkSync2,
|
|
11247
|
-
rmSync as
|
|
11248
|
-
symlinkSync,
|
|
11249
|
-
writeFileSync as writeFileSync5
|
|
11663
|
+
rmSync as rmSync5
|
|
11250
11664
|
} from "fs";
|
|
11251
|
-
import { dirname as
|
|
11252
|
-
import { parse as
|
|
11665
|
+
import { dirname as dirname18, join as join24, posix as posix2 } from "path";
|
|
11666
|
+
import { parse as parse5 } from "smol-toml";
|
|
11253
11667
|
|
|
11254
11668
|
// src/project/backup.ts
|
|
11255
11669
|
import { createHash as createHash10 } from "crypto";
|
|
11256
11670
|
import {
|
|
11257
|
-
existsSync as
|
|
11671
|
+
existsSync as existsSync16,
|
|
11258
11672
|
lstatSync as lstatSync3,
|
|
11259
11673
|
mkdirSync as mkdirSync6,
|
|
11260
11674
|
mkdtempSync as mkdtempSync2,
|
|
11261
|
-
readFileSync as
|
|
11675
|
+
readFileSync as readFileSync21,
|
|
11262
11676
|
writeFileSync as writeFileSync4
|
|
11263
11677
|
} from "fs";
|
|
11264
11678
|
import { homedir as homedir3 } from "os";
|
|
11265
|
-
import { dirname as
|
|
11679
|
+
import { dirname as dirname15, isAbsolute as isAbsolute4, join as join21, relative as relative8, resolve as resolve11 } from "path";
|
|
11266
11680
|
|
|
11267
11681
|
// src/project/package-rules.ts
|
|
11268
11682
|
import { execFileSync } from "child_process";
|
|
11269
|
-
import { existsSync as
|
|
11270
|
-
import { dirname as
|
|
11683
|
+
import { existsSync as existsSync15, lstatSync as lstatSync2, readdirSync as readdirSync7, readlinkSync } from "fs";
|
|
11684
|
+
import { dirname as dirname14, join as join20, posix } from "path";
|
|
11271
11685
|
var ProjectError = class extends Error {
|
|
11272
11686
|
constructor(message) {
|
|
11273
11687
|
super(message);
|
|
@@ -11312,11 +11726,11 @@ function linkTarget(abs) {
|
|
|
11312
11726
|
}
|
|
11313
11727
|
function legacyLinks(root) {
|
|
11314
11728
|
const found = /* @__PURE__ */ new Map();
|
|
11315
|
-
const dir =
|
|
11316
|
-
if (!
|
|
11729
|
+
const dir = join20(root, RULES_DIR);
|
|
11730
|
+
if (!existsSync15(dir)) return found;
|
|
11317
11731
|
for (const entry2 of readdirSync7(dir).sort()) {
|
|
11318
11732
|
if (!entry2.endsWith(".md")) continue;
|
|
11319
|
-
const target2 = linkTarget(
|
|
11733
|
+
const target2 = linkTarget(join20(dir, entry2));
|
|
11320
11734
|
if (target2 === null || !target2.startsWith("../../")) continue;
|
|
11321
11735
|
found.set(entry2.slice(0, -3), target2.slice("../../".length));
|
|
11322
11736
|
}
|
|
@@ -11336,14 +11750,14 @@ function packageRuleOutputs(root, spec, owned) {
|
|
|
11336
11750
|
const legacyByDir = /* @__PURE__ */ new Map();
|
|
11337
11751
|
for (const [name, target2] of legacy) {
|
|
11338
11752
|
if (!byTarget.has(target2)) byTarget.set(target2, name);
|
|
11339
|
-
const dir =
|
|
11753
|
+
const dir = dirname14(target2);
|
|
11340
11754
|
if (!legacyByDir.has(dir)) legacyByDir.set(dir, name);
|
|
11341
11755
|
}
|
|
11342
11756
|
const outputs = [];
|
|
11343
11757
|
const taken = /* @__PURE__ */ new Map();
|
|
11344
11758
|
for (const file2 of files) {
|
|
11345
11759
|
const pkg = file2.slice(0, -suffix.length);
|
|
11346
|
-
const name = byTarget.get(file2) ?? legacyByDir.get(
|
|
11760
|
+
const name = byTarget.get(file2) ?? legacyByDir.get(dirname14(file2)) ?? deriveName(pkg, spec.strip);
|
|
11347
11761
|
const other = taken.get(name);
|
|
11348
11762
|
if (other !== void 0) {
|
|
11349
11763
|
throw new ProjectError(
|
|
@@ -11362,8 +11776,8 @@ function packageRuleOutputs(root, spec, owned) {
|
|
|
11362
11776
|
|
|
11363
11777
|
// src/project/backup.ts
|
|
11364
11778
|
function backupFiles(root, paths) {
|
|
11365
|
-
const base =
|
|
11366
|
-
const inside = relative8(
|
|
11779
|
+
const base = join21(homedir3(), ".cache/pmcp/project-backups");
|
|
11780
|
+
const inside = relative8(resolve11(root), resolve11(base));
|
|
11367
11781
|
if (!isAbsolute4(inside) && inside !== ".." && !inside.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
|
|
11368
11782
|
throw new ProjectError("Project backups must be outside the project");
|
|
11369
11783
|
}
|
|
@@ -11378,12 +11792,12 @@ function backupFiles(root, paths) {
|
|
|
11378
11792
|
);
|
|
11379
11793
|
}
|
|
11380
11794
|
const snapshots = [...new Set(paths)].map((path) => {
|
|
11381
|
-
const absolute2 =
|
|
11382
|
-
const within = relative8(
|
|
11795
|
+
const absolute2 = resolve11(root, path);
|
|
11796
|
+
const within = relative8(resolve11(root), absolute2);
|
|
11383
11797
|
if (isAbsolute4(within) || within === ".." || within.startsWith("../") || within.startsWith("..\\")) {
|
|
11384
11798
|
throw new ProjectError("Backup paths must stay inside the project");
|
|
11385
11799
|
}
|
|
11386
|
-
if (!
|
|
11800
|
+
if (!existsSync16(absolute2)) {
|
|
11387
11801
|
return { path, contents: null };
|
|
11388
11802
|
}
|
|
11389
11803
|
const stat = lstatSync3(absolute2);
|
|
@@ -11392,15 +11806,15 @@ function backupFiles(root, paths) {
|
|
|
11392
11806
|
`Refusing to back up a nonregular config file: ${path}`
|
|
11393
11807
|
);
|
|
11394
11808
|
}
|
|
11395
|
-
return { path, contents:
|
|
11809
|
+
return { path, contents: readFileSync21(absolute2) };
|
|
11396
11810
|
});
|
|
11397
|
-
const destination = mkdtempSync2(
|
|
11811
|
+
const destination = mkdtempSync2(join21(base, "project-"));
|
|
11398
11812
|
const entries = snapshots.map(({ path, contents }) => {
|
|
11399
11813
|
if (contents === null) {
|
|
11400
11814
|
return { path, present: false };
|
|
11401
11815
|
}
|
|
11402
|
-
const output =
|
|
11403
|
-
mkdirSync6(
|
|
11816
|
+
const output = join21(destination, "files", path);
|
|
11817
|
+
mkdirSync6(dirname15(output), { recursive: true, mode: 448 });
|
|
11404
11818
|
writeFileSync4(output, contents, { mode: 384 });
|
|
11405
11819
|
return {
|
|
11406
11820
|
path,
|
|
@@ -11409,11 +11823,11 @@ function backupFiles(root, paths) {
|
|
|
11409
11823
|
};
|
|
11410
11824
|
});
|
|
11411
11825
|
writeFileSync4(
|
|
11412
|
-
|
|
11826
|
+
join21(destination, "receipt.json"),
|
|
11413
11827
|
JSON.stringify(
|
|
11414
11828
|
{
|
|
11415
11829
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11416
|
-
projectRoot:
|
|
11830
|
+
projectRoot: resolve11(root),
|
|
11417
11831
|
entries
|
|
11418
11832
|
},
|
|
11419
11833
|
null,
|
|
@@ -11704,15 +12118,15 @@ function mcpOutputs(spec) {
|
|
|
11704
12118
|
|
|
11705
12119
|
// src/project/hooks.ts
|
|
11706
12120
|
import { createHash as createHash12 } from "crypto";
|
|
11707
|
-
import { existsSync as
|
|
11708
|
-
import { join as
|
|
12121
|
+
import { existsSync as existsSync18, readFileSync as readFileSync23 } from "fs";
|
|
12122
|
+
import { join as join22 } from "path";
|
|
11709
12123
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11710
12124
|
|
|
11711
12125
|
// src/project/hook-adapter.ts
|
|
11712
12126
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
11713
12127
|
import { createHash as createHash11 } from "crypto";
|
|
11714
|
-
import { existsSync as
|
|
11715
|
-
import { dirname as
|
|
12128
|
+
import { existsSync as existsSync17, readFileSync as readFileSync22, realpathSync as realpathSync5, statSync as statSync10 } from "fs";
|
|
12129
|
+
import { dirname as dirname16, isAbsolute as isAbsolute6, relative as relative10, resolve as resolve12 } from "path";
|
|
11716
12130
|
var denied = () => ({
|
|
11717
12131
|
decision: "deny",
|
|
11718
12132
|
reason: "Shell guard failed validation or execution"
|
|
@@ -11722,16 +12136,16 @@ function guardPath(root, command2) {
|
|
|
11722
12136
|
if (!script?.startsWith(".agents/guards/")) {
|
|
11723
12137
|
throw new Error("Hook command must execute a script under .agents/guards/");
|
|
11724
12138
|
}
|
|
11725
|
-
const absolute2 =
|
|
11726
|
-
const base =
|
|
12139
|
+
const absolute2 = resolve12(root, script);
|
|
12140
|
+
const base = resolve12(root, ".agents/guards");
|
|
11727
12141
|
const inside = relative10(base, absolute2);
|
|
11728
12142
|
if (isAbsolute6(inside) || inside === ".." || inside.startsWith("../") || inside.startsWith("..\\")) {
|
|
11729
12143
|
throw new Error("Hook guard must stay under .agents/guards/");
|
|
11730
12144
|
}
|
|
11731
|
-
if (!
|
|
12145
|
+
if (!existsSync17(absolute2) || !statSync10(absolute2).isFile()) {
|
|
11732
12146
|
throw new Error("Hook guard script must exist");
|
|
11733
12147
|
}
|
|
11734
|
-
const actual = relative10(base,
|
|
12148
|
+
const actual = relative10(base, realpathSync5(absolute2));
|
|
11735
12149
|
if (isAbsolute6(actual) || actual === ".." || actual.startsWith("../") || actual.startsWith("..\\")) {
|
|
11736
12150
|
throw new Error("Hook guard cannot resolve outside .agents/guards/");
|
|
11737
12151
|
}
|
|
@@ -11849,8 +12263,8 @@ async function main() {
|
|
|
11849
12263
|
if (!file2) {
|
|
11850
12264
|
throw new Error("Missing hook policy");
|
|
11851
12265
|
}
|
|
11852
|
-
const bytes =
|
|
11853
|
-
const adapter =
|
|
12266
|
+
const bytes = readFileSync22(file2);
|
|
12267
|
+
const adapter = readFileSync22(process.argv[1]);
|
|
11854
12268
|
const actual = createHash11("sha256").update(adapter).update(bytes).digest("hex");
|
|
11855
12269
|
if (actual !== integrity2) {
|
|
11856
12270
|
throw new Error("Hook integrity mismatch");
|
|
@@ -11869,8 +12283,8 @@ async function main() {
|
|
|
11869
12283
|
}
|
|
11870
12284
|
chunks.push(bytes2);
|
|
11871
12285
|
}
|
|
11872
|
-
const root =
|
|
11873
|
-
const guardSha256 = createHash11("sha256").update(
|
|
12286
|
+
const root = resolve12(dirname16(file2), "../..");
|
|
12287
|
+
const guardSha256 = createHash11("sha256").update(readFileSync22(guardPath(root, policy.command))).digest("hex");
|
|
11874
12288
|
if (policy.contractVersion !== 1 || guardSha256 !== policy.guardSha256) {
|
|
11875
12289
|
throw new Error(
|
|
11876
12290
|
"Hook guard source changed; reproject and review native trust"
|
|
@@ -11923,13 +12337,13 @@ function object4(value) {
|
|
|
11923
12337
|
return value;
|
|
11924
12338
|
}
|
|
11925
12339
|
function groups(root, file2, key) {
|
|
11926
|
-
const absolute2 =
|
|
11927
|
-
if (!
|
|
12340
|
+
const absolute2 = join22(root, file2);
|
|
12341
|
+
if (!existsSync18(absolute2)) {
|
|
11928
12342
|
return [];
|
|
11929
12343
|
}
|
|
11930
12344
|
let value;
|
|
11931
12345
|
try {
|
|
11932
|
-
value = JSON.parse(
|
|
12346
|
+
value = JSON.parse(readFileSync23(absolute2, "utf8"));
|
|
11933
12347
|
} catch {
|
|
11934
12348
|
throw new ProjectError(
|
|
11935
12349
|
"Cannot parse native hook configuration; contents are redacted"
|
|
@@ -11953,15 +12367,15 @@ function adapterSource() {
|
|
|
11953
12367
|
const sourceBuild = fileURLToPath2(
|
|
11954
12368
|
new URL("../../dist/project/hook-adapter.js", import.meta.url)
|
|
11955
12369
|
);
|
|
11956
|
-
const file2 =
|
|
11957
|
-
if (!
|
|
12370
|
+
const file2 = existsSync18(published) ? published : sourceBuild;
|
|
12371
|
+
if (!existsSync18(file2)) {
|
|
11958
12372
|
throw new ProjectError("Build pmcp before projecting shell hook adapters");
|
|
11959
12373
|
}
|
|
11960
|
-
return
|
|
12374
|
+
return readFileSync23(file2, "utf8");
|
|
11961
12375
|
}
|
|
11962
12376
|
function hookOutputs(spec) {
|
|
11963
|
-
const lockFile =
|
|
11964
|
-
const lock =
|
|
12377
|
+
const lockFile = join22(spec.root, "pmcp.lock");
|
|
12378
|
+
const lock = existsSync18(lockFile) ? JSON.parse(readFileSync23(lockFile, "utf8")) : {};
|
|
11965
12379
|
const previous = new Map(
|
|
11966
12380
|
(lock.outputs ?? []).filter((entry2) => entry2.managed !== void 0).map((entry2) => [entry2.path, entry2.managed])
|
|
11967
12381
|
);
|
|
@@ -11978,7 +12392,7 @@ function hookOutputs(spec) {
|
|
|
11978
12392
|
outputs.push({ kind: "file", path: ADAPTER, content: adapter });
|
|
11979
12393
|
for (const hook of selected) {
|
|
11980
12394
|
const policyFile = `${HOOK_DIRECTORY}/${hook.name}.json`;
|
|
11981
|
-
const guardSha256 = createHash12("sha256").update(
|
|
12395
|
+
const guardSha256 = createHash12("sha256").update(readFileSync23(guardPath(spec.root, hook.command))).digest("hex");
|
|
11982
12396
|
const policy = `${JSON.stringify({ ...hook, contractVersion: 1, guardSha256 }, null, 2)}
|
|
11983
12397
|
`;
|
|
11984
12398
|
outputs.push({ kind: "file", path: policyFile, content: policy });
|
|
@@ -12042,11 +12456,11 @@ function hookOutputs(spec) {
|
|
|
12042
12456
|
}
|
|
12043
12457
|
|
|
12044
12458
|
// src/project/frontmatter.ts
|
|
12045
|
-
import { parse as
|
|
12459
|
+
import { parse as parse4 } from "yaml";
|
|
12046
12460
|
function splitFrontmatter(source2) {
|
|
12047
12461
|
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/u.exec(source2);
|
|
12048
12462
|
if (!match) return { meta: {}, body: source2 };
|
|
12049
|
-
const meta =
|
|
12463
|
+
const meta = parse4(match[1] ?? "");
|
|
12050
12464
|
const object9 = meta !== null && typeof meta === "object" && !Array.isArray(meta) ? meta : {};
|
|
12051
12465
|
return { meta: object9, body: match[2] ?? "" };
|
|
12052
12466
|
}
|
|
@@ -12076,28 +12490,28 @@ ${lines2.join("\n")}
|
|
|
12076
12490
|
}
|
|
12077
12491
|
|
|
12078
12492
|
// src/project/plan.ts
|
|
12079
|
-
import { existsSync as
|
|
12080
|
-
import { join as
|
|
12493
|
+
import { existsSync as existsSync19, readFileSync as readFileSync24, readdirSync as readdirSync8 } from "fs";
|
|
12494
|
+
import { join as join23 } from "path";
|
|
12081
12495
|
var AGENT_FIELDS = {
|
|
12082
12496
|
claude: ["name", "description", "tools", "model", "effort"],
|
|
12083
12497
|
gemini: ["name", "description"]
|
|
12084
12498
|
};
|
|
12085
12499
|
var SETTINGS = ".claude/settings.json";
|
|
12086
12500
|
function listDirs(dir) {
|
|
12087
|
-
if (!
|
|
12501
|
+
if (!existsSync19(dir)) return [];
|
|
12088
12502
|
return readdirSync8(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
12089
12503
|
}
|
|
12090
12504
|
var tomlString = (value) => JSON.stringify(typeof value === "string" ? value : String(value ?? ""));
|
|
12091
12505
|
function skillNames(root) {
|
|
12092
|
-
const dir =
|
|
12506
|
+
const dir = join23(root, ".agents/skills");
|
|
12093
12507
|
return listDirs(dir).filter(
|
|
12094
|
-
(name) =>
|
|
12508
|
+
(name) => existsSync19(join23(dir, name, "SKILL.md"))
|
|
12095
12509
|
);
|
|
12096
12510
|
}
|
|
12097
12511
|
function agentNames(root) {
|
|
12098
|
-
const dir =
|
|
12512
|
+
const dir = join23(root, ".agents/agents");
|
|
12099
12513
|
return listDirs(dir).filter(
|
|
12100
|
-
(name) =>
|
|
12514
|
+
(name) => existsSync19(join23(dir, name, "agent.md"))
|
|
12101
12515
|
);
|
|
12102
12516
|
}
|
|
12103
12517
|
function skillOutputs(spec) {
|
|
@@ -12131,13 +12545,13 @@ policy:
|
|
|
12131
12545
|
}
|
|
12132
12546
|
function ruleOutputs(spec) {
|
|
12133
12547
|
if (!spec.tools.has("claude")) return [];
|
|
12134
|
-
const dir =
|
|
12135
|
-
if (!
|
|
12548
|
+
const dir = join23(spec.root, ".agents/rules");
|
|
12549
|
+
if (!existsSync19(dir)) return [];
|
|
12136
12550
|
const outputs = [];
|
|
12137
12551
|
for (const file2 of readdirSync8(dir).filter((f) => f.endsWith(".md")).sort()) {
|
|
12138
12552
|
const rel = `.agents/rules/${file2}`;
|
|
12139
12553
|
const { meta, body } = splitFrontmatter(
|
|
12140
|
-
|
|
12554
|
+
readFileSync24(join23(spec.root, rel), "utf8")
|
|
12141
12555
|
);
|
|
12142
12556
|
const globs = [meta["globs"] ?? []].flat().filter((g) => typeof g === "string" && g !== "");
|
|
12143
12557
|
const head = globs.length > 0 ? renderFrontmatter({ paths: globs }) : "";
|
|
@@ -12156,7 +12570,7 @@ function agentOutputs(spec) {
|
|
|
12156
12570
|
for (const name of agentNames(spec.root)) {
|
|
12157
12571
|
const rel = `.agents/agents/${name}/agent.md`;
|
|
12158
12572
|
const { meta, body } = splitFrontmatter(
|
|
12159
|
-
|
|
12573
|
+
readFileSync24(join23(spec.root, rel), "utf8")
|
|
12160
12574
|
);
|
|
12161
12575
|
const overrides = spec.agents[name] ?? {};
|
|
12162
12576
|
const note = `<!-- ${MARK} from ${rel} -->
|
|
@@ -12225,6 +12639,67 @@ function censusDirs(spec) {
|
|
|
12225
12639
|
return dirs;
|
|
12226
12640
|
}
|
|
12227
12641
|
|
|
12642
|
+
// src/project/write.ts
|
|
12643
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
12644
|
+
import {
|
|
12645
|
+
closeSync as closeSync3,
|
|
12646
|
+
fchmodSync as fchmodSync2,
|
|
12647
|
+
fsyncSync,
|
|
12648
|
+
lstatSync as lstatSync4,
|
|
12649
|
+
openSync as openSync3,
|
|
12650
|
+
renameSync as renameSync3,
|
|
12651
|
+
rmSync as rmSync4,
|
|
12652
|
+
symlinkSync,
|
|
12653
|
+
writeFileSync as writeFileSync5
|
|
12654
|
+
} from "fs";
|
|
12655
|
+
import { dirname as dirname17 } from "path";
|
|
12656
|
+
function fileMode(path) {
|
|
12657
|
+
try {
|
|
12658
|
+
const stat = lstatSync4(path);
|
|
12659
|
+
if (stat.isFile()) return stat.mode & 511;
|
|
12660
|
+
} catch (error) {
|
|
12661
|
+
if (error.code !== "ENOENT") throw error;
|
|
12662
|
+
}
|
|
12663
|
+
return 438 & ~process.umask();
|
|
12664
|
+
}
|
|
12665
|
+
function syncDirectory(path) {
|
|
12666
|
+
if (process.platform === "win32") return;
|
|
12667
|
+
const descriptor = openSync3(dirname17(path), "r");
|
|
12668
|
+
try {
|
|
12669
|
+
fsyncSync(descriptor);
|
|
12670
|
+
} finally {
|
|
12671
|
+
closeSync3(descriptor);
|
|
12672
|
+
}
|
|
12673
|
+
}
|
|
12674
|
+
function replaceFile(path, content) {
|
|
12675
|
+
const mode = fileMode(path);
|
|
12676
|
+
const temporary = `${path}.${randomUUID4()}.new`;
|
|
12677
|
+
const descriptor = openSync3(temporary, "wx", 384);
|
|
12678
|
+
try {
|
|
12679
|
+
try {
|
|
12680
|
+
writeFileSync5(descriptor, content);
|
|
12681
|
+
fchmodSync2(descriptor, mode);
|
|
12682
|
+
fsyncSync(descriptor);
|
|
12683
|
+
} finally {
|
|
12684
|
+
closeSync3(descriptor);
|
|
12685
|
+
}
|
|
12686
|
+
renameSync3(temporary, path);
|
|
12687
|
+
syncDirectory(path);
|
|
12688
|
+
} finally {
|
|
12689
|
+
rmSync4(temporary, { force: true });
|
|
12690
|
+
}
|
|
12691
|
+
}
|
|
12692
|
+
function replaceLink(path, target2) {
|
|
12693
|
+
const temporary = `${path}.${randomUUID4()}.new`;
|
|
12694
|
+
try {
|
|
12695
|
+
symlinkSync(target2, temporary);
|
|
12696
|
+
renameSync3(temporary, path);
|
|
12697
|
+
syncDirectory(path);
|
|
12698
|
+
} finally {
|
|
12699
|
+
rmSync4(temporary, { force: true });
|
|
12700
|
+
}
|
|
12701
|
+
}
|
|
12702
|
+
|
|
12228
12703
|
// src/project/apply.ts
|
|
12229
12704
|
var LOCK_FILE = "pmcp.lock";
|
|
12230
12705
|
var BEGIN = "# >>> pmcp project (edit pmcp.toml, not this block) >>>";
|
|
@@ -12235,24 +12710,24 @@ var PREDECESSORS = [
|
|
|
12235
12710
|
];
|
|
12236
12711
|
var sha = (text3) => createHash13("sha256").update(text3).digest("hex");
|
|
12237
12712
|
function readLock(root) {
|
|
12238
|
-
const path =
|
|
12239
|
-
if (!
|
|
12240
|
-
const lock = JSON.parse(
|
|
12713
|
+
const path = join24(root, LOCK_FILE);
|
|
12714
|
+
if (!existsSync20(path)) return [];
|
|
12715
|
+
const lock = JSON.parse(readFileSync25(path, "utf8"));
|
|
12241
12716
|
return lock.outputs ?? [];
|
|
12242
12717
|
}
|
|
12243
12718
|
function linkOf(abs) {
|
|
12244
12719
|
try {
|
|
12245
|
-
return
|
|
12720
|
+
return lstatSync5(abs).isSymbolicLink() ? readlinkSync2(abs) : null;
|
|
12246
12721
|
} catch {
|
|
12247
12722
|
return null;
|
|
12248
12723
|
}
|
|
12249
12724
|
}
|
|
12250
|
-
var exists = (abs) =>
|
|
12725
|
+
var exists = (abs) => existsSync20(abs) || linkOf(abs) !== null;
|
|
12251
12726
|
function readJson2(abs) {
|
|
12252
|
-
if (!
|
|
12727
|
+
if (!existsSync20(abs)) return {};
|
|
12253
12728
|
let value;
|
|
12254
12729
|
try {
|
|
12255
|
-
value = JSON.parse(
|
|
12730
|
+
value = JSON.parse(readFileSync25(abs, "utf8"));
|
|
12256
12731
|
} catch {
|
|
12257
12732
|
throw new ProjectError(
|
|
12258
12733
|
"Cannot parse native JSON configuration; contents are redacted"
|
|
@@ -12304,19 +12779,19 @@ function withoutBlock(text3) {
|
|
|
12304
12779
|
return `${text3.slice(0, start2).replace(/\n+$/u, "\n")}${after}`;
|
|
12305
12780
|
}
|
|
12306
12781
|
function present(root, output) {
|
|
12307
|
-
const abs =
|
|
12782
|
+
const abs = join24(root, "file" in output ? output.file : output.path);
|
|
12308
12783
|
switch (output.kind) {
|
|
12309
12784
|
case "link":
|
|
12310
12785
|
return linkOf(abs) === output.target;
|
|
12311
12786
|
case "file":
|
|
12312
|
-
return
|
|
12787
|
+
return existsSync20(abs) && linkOf(abs) === null && readFileSync25(abs, "utf8") === output.content;
|
|
12313
12788
|
case "json":
|
|
12314
12789
|
if (output.managed && Array.isArray(output.value) && output.value.length === 0) {
|
|
12315
12790
|
return getKey(readJson2(abs), output.key) === void 0;
|
|
12316
12791
|
}
|
|
12317
12792
|
return same(getKey(readJson2(abs), output.key), output.value);
|
|
12318
12793
|
case "block":
|
|
12319
|
-
return
|
|
12794
|
+
return existsSync20(abs) && readFileSync25(abs, "utf8").includes(block(output.content));
|
|
12320
12795
|
}
|
|
12321
12796
|
}
|
|
12322
12797
|
function replaceable(root, output, owned) {
|
|
@@ -12324,28 +12799,28 @@ function replaceable(root, output, owned) {
|
|
|
12324
12799
|
if (output.key[0] !== "mcpServers" || owned.has(output.path)) {
|
|
12325
12800
|
return true;
|
|
12326
12801
|
}
|
|
12327
|
-
const existing = getKey(readJson2(
|
|
12802
|
+
const existing = getKey(readJson2(join24(root, output.file)), output.key);
|
|
12328
12803
|
return existing === void 0 || same(existing, output.value);
|
|
12329
12804
|
}
|
|
12330
12805
|
if (output.kind === "block") {
|
|
12331
|
-
const absolute2 =
|
|
12332
|
-
const text4 =
|
|
12333
|
-
const outside =
|
|
12334
|
-
const proposed =
|
|
12806
|
+
const absolute2 = join24(root, output.file);
|
|
12807
|
+
const text4 = existsSync20(absolute2) ? readFileSync25(absolute2, "utf8") : "";
|
|
12808
|
+
const outside = parse5(withoutBlock(text4));
|
|
12809
|
+
const proposed = parse5(output.content);
|
|
12335
12810
|
const existing = objectKeys(outside["mcp_servers"]);
|
|
12336
12811
|
return !objectKeys(proposed["mcp_servers"]).some(
|
|
12337
12812
|
(name) => existing.includes(name)
|
|
12338
12813
|
);
|
|
12339
12814
|
}
|
|
12340
12815
|
if (owned.has(output.path)) return true;
|
|
12341
|
-
const abs =
|
|
12816
|
+
const abs = join24(root, output.path);
|
|
12342
12817
|
if (!exists(abs)) return true;
|
|
12343
12818
|
const target2 = linkOf(abs);
|
|
12344
12819
|
if (target2 !== null) {
|
|
12345
12820
|
return output.kind === "link" && target2 === output.target;
|
|
12346
12821
|
}
|
|
12347
|
-
if (
|
|
12348
|
-
const text3 =
|
|
12822
|
+
if (lstatSync5(abs).isDirectory()) return false;
|
|
12823
|
+
const text3 = readFileSync25(abs, "utf8");
|
|
12349
12824
|
if (output.kind === "file" && text3 === output.content) return true;
|
|
12350
12825
|
return PREDECESSORS.some((mark) => text3.includes(mark));
|
|
12351
12826
|
}
|
|
@@ -12359,15 +12834,15 @@ function undeclared(spec, outputs) {
|
|
|
12359
12834
|
const declared = new Set(outputs.map((o) => o.path));
|
|
12360
12835
|
const found = [];
|
|
12361
12836
|
for (const dir of censusDirs(spec)) {
|
|
12362
|
-
const abs =
|
|
12363
|
-
if (!
|
|
12837
|
+
const abs = join24(spec.root, dir);
|
|
12838
|
+
if (!existsSync20(abs)) continue;
|
|
12364
12839
|
for (const entry2 of readdirSync9(abs)) {
|
|
12365
12840
|
const path = `${dir}/${entry2}`;
|
|
12366
12841
|
if (!declared.has(path)) found.push(path);
|
|
12367
12842
|
}
|
|
12368
12843
|
}
|
|
12369
|
-
const rules =
|
|
12370
|
-
if (spec.tools.has("grok") &&
|
|
12844
|
+
const rules = join24(spec.root, RULES_DIR);
|
|
12845
|
+
if (spec.tools.has("grok") && existsSync20(rules)) {
|
|
12371
12846
|
for (const entry2 of readdirSync9(rules, { withFileTypes: true })) {
|
|
12372
12847
|
if (entry2.isDirectory()) continue;
|
|
12373
12848
|
found.push(`${RULES_DIR}/${entry2.name}`);
|
|
@@ -12394,9 +12869,9 @@ function deadGlobs(root, outputs) {
|
|
|
12394
12869
|
if (rule.kind === "file") {
|
|
12395
12870
|
text3 = rule.content;
|
|
12396
12871
|
} else if (rule.kind === "link") {
|
|
12397
|
-
const abs =
|
|
12398
|
-
if (!
|
|
12399
|
-
text3 =
|
|
12872
|
+
const abs = join24(root, posix2.join(posix2.dirname(rule.path), rule.target));
|
|
12873
|
+
if (!existsSync20(abs)) continue;
|
|
12874
|
+
text3 = readFileSync25(abs, "utf8");
|
|
12400
12875
|
} else {
|
|
12401
12876
|
continue;
|
|
12402
12877
|
}
|
|
@@ -12446,7 +12921,7 @@ function project(spec, options2) {
|
|
|
12446
12921
|
}
|
|
12447
12922
|
}
|
|
12448
12923
|
for (const path of jsonPaths) {
|
|
12449
|
-
readJson2(
|
|
12924
|
+
readJson2(join24(spec.root, path));
|
|
12450
12925
|
}
|
|
12451
12926
|
if (options2.check) {
|
|
12452
12927
|
const problems = [
|
|
@@ -12508,11 +12983,13 @@ function project(spec, options2) {
|
|
|
12508
12983
|
for (const output of outputs) {
|
|
12509
12984
|
if (output.kind !== "link" && output.kind !== "file") continue;
|
|
12510
12985
|
if (present(spec.root, output)) continue;
|
|
12511
|
-
const abs =
|
|
12512
|
-
mkdirSync7(
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12986
|
+
const abs = join24(spec.root, output.path);
|
|
12987
|
+
mkdirSync7(dirname18(abs), { recursive: true });
|
|
12988
|
+
if (output.kind === "link") {
|
|
12989
|
+
replaceLink(abs, output.target);
|
|
12990
|
+
continue;
|
|
12991
|
+
}
|
|
12992
|
+
replaceFile(abs, output.content);
|
|
12516
12993
|
}
|
|
12517
12994
|
let pruned = 0;
|
|
12518
12995
|
const jsonFiles = /* @__PURE__ */ new Map();
|
|
@@ -12534,12 +13011,12 @@ function project(spec, options2) {
|
|
|
12534
13011
|
group.drops.push(keyed.key);
|
|
12535
13012
|
jsonFiles.set(keyed.file, group);
|
|
12536
13013
|
} else {
|
|
12537
|
-
|
|
13014
|
+
rmSync5(join24(spec.root, entry2.path), { force: true, recursive: true });
|
|
12538
13015
|
}
|
|
12539
13016
|
pruned += 1;
|
|
12540
13017
|
}
|
|
12541
13018
|
for (const [file2, { sets, drops }] of jsonFiles) {
|
|
12542
|
-
const abs =
|
|
13019
|
+
const abs = join24(spec.root, file2);
|
|
12543
13020
|
const before = readJson2(abs);
|
|
12544
13021
|
const after = structuredClone(before);
|
|
12545
13022
|
for (const key of drops) deleteKey(after, key);
|
|
@@ -12554,22 +13031,22 @@ function project(spec, options2) {
|
|
|
12554
13031
|
}
|
|
12555
13032
|
}
|
|
12556
13033
|
if (same(before, after)) continue;
|
|
12557
|
-
if (!
|
|
12558
|
-
mkdirSync7(
|
|
12559
|
-
|
|
13034
|
+
if (!existsSync20(abs) && Object.keys(after).length === 0) continue;
|
|
13035
|
+
mkdirSync7(dirname18(abs), { recursive: true });
|
|
13036
|
+
replaceFile(abs, `${JSON.stringify(after, null, 2)}
|
|
12560
13037
|
`);
|
|
12561
13038
|
}
|
|
12562
13039
|
for (const [file2, content] of blockFiles) {
|
|
12563
|
-
const abs =
|
|
12564
|
-
const before =
|
|
13040
|
+
const abs = join24(spec.root, file2);
|
|
13041
|
+
const before = existsSync20(abs) ? readFileSync25(abs, "utf8") : "";
|
|
12565
13042
|
let after = withoutBlock(before);
|
|
12566
13043
|
if (content !== null) {
|
|
12567
13044
|
const sep7 = after === "" || after.endsWith("\n\n") ? "" : after.endsWith("\n") ? "\n" : "\n\n";
|
|
12568
13045
|
after = `${after}${sep7}${block(content)}`;
|
|
12569
13046
|
}
|
|
12570
13047
|
if (after === before) continue;
|
|
12571
|
-
mkdirSync7(
|
|
12572
|
-
|
|
13048
|
+
mkdirSync7(dirname18(abs), { recursive: true });
|
|
13049
|
+
replaceFile(abs, after);
|
|
12573
13050
|
}
|
|
12574
13051
|
if (spec.packageRules) {
|
|
12575
13052
|
const linkedDirs = new Set(
|
|
@@ -12580,15 +13057,15 @@ function project(spec, options2) {
|
|
|
12580
13057
|
)
|
|
12581
13058
|
);
|
|
12582
13059
|
for (const [name, target2] of legacyLinks(spec.root)) {
|
|
12583
|
-
const abs =
|
|
12584
|
-
const broken = !
|
|
13060
|
+
const abs = join24(spec.root, RULES_DIR, `${name}.md`);
|
|
13061
|
+
const broken = !existsSync20(abs);
|
|
12585
13062
|
if (!broken && !linkedDirs.has(posix2.dirname(target2))) continue;
|
|
12586
|
-
|
|
13063
|
+
rmSync5(abs, { force: true });
|
|
12587
13064
|
pruned += 1;
|
|
12588
13065
|
}
|
|
12589
13066
|
}
|
|
12590
|
-
|
|
12591
|
-
|
|
13067
|
+
replaceFile(
|
|
13068
|
+
join24(spec.root, LOCK_FILE),
|
|
12592
13069
|
`${JSON.stringify({ version: 1, outputs: outputs.filter((output) => output.kind !== "json" || output.managed === void 0 || output.managed.length > 0).map(lockEntry) }, null, 2)}
|
|
12593
13070
|
`
|
|
12594
13071
|
);
|
|
@@ -12602,11 +13079,11 @@ function project(spec, options2) {
|
|
|
12602
13079
|
}
|
|
12603
13080
|
|
|
12604
13081
|
// src/project/mcp-import.ts
|
|
12605
|
-
import { readFileSync as
|
|
13082
|
+
import { readFileSync as readFileSync26, statSync as statSync11, writeFileSync as writeFileSync6 } from "fs";
|
|
12606
13083
|
import { createHash as createHash14 } from "crypto";
|
|
12607
|
-
import { basename as basename6, dirname as
|
|
13084
|
+
import { basename as basename6, dirname as dirname19, resolve as resolve13 } from "path";
|
|
12608
13085
|
import { homedir as homedir4 } from "os";
|
|
12609
|
-
import { parse as
|
|
13086
|
+
import { parse as parse6, stringify } from "smol-toml";
|
|
12610
13087
|
|
|
12611
13088
|
// src/project/mcp-spec.ts
|
|
12612
13089
|
init_config();
|
|
@@ -13034,11 +13511,11 @@ function sourceServers(source2, root) {
|
|
|
13034
13511
|
"Use --source <claude|codex|gemini|grok|antigravity>:<file>"
|
|
13035
13512
|
);
|
|
13036
13513
|
}
|
|
13037
|
-
const path =
|
|
13514
|
+
const path = resolve13(root, source2.slice(split + 1));
|
|
13038
13515
|
if (statSync11(path).size > 2 * 1024 * 1024) {
|
|
13039
13516
|
throw new ProjectError("MCP source exceeds the 2 MiB limit");
|
|
13040
13517
|
}
|
|
13041
|
-
const bytes =
|
|
13518
|
+
const bytes = readFileSync26(path);
|
|
13042
13519
|
const sha2562 = createHash14("sha256").update(bytes).digest("hex");
|
|
13043
13520
|
const text3 = bytes.toString("utf8").replace(/^\uFEFF/u, "");
|
|
13044
13521
|
let parsed;
|
|
@@ -13046,7 +13523,7 @@ function sourceServers(source2, root) {
|
|
|
13046
13523
|
if (text3.trim() === "") {
|
|
13047
13524
|
parsed = {};
|
|
13048
13525
|
} else if (path.endsWith(".toml")) {
|
|
13049
|
-
parsed =
|
|
13526
|
+
parsed = parse6(text3);
|
|
13050
13527
|
} else {
|
|
13051
13528
|
parsed = object6(JSON.parse(text3));
|
|
13052
13529
|
}
|
|
@@ -13229,9 +13706,9 @@ function equivalent(alias, left, right) {
|
|
|
13229
13706
|
return JSON.stringify(readMcp("import", { [alias]: left })[alias]) === JSON.stringify(readMcp("import", { [alias]: right })[alias]);
|
|
13230
13707
|
}
|
|
13231
13708
|
function planMcpImport(config, sources2, names = []) {
|
|
13232
|
-
const original =
|
|
13233
|
-
const root =
|
|
13234
|
-
const existing = object6(
|
|
13709
|
+
const original = readFileSync26(config, "utf8");
|
|
13710
|
+
const root = dirname19(resolve13(config));
|
|
13711
|
+
const existing = object6(parse6(original)["mcp"] ?? {});
|
|
13235
13712
|
const additions = {};
|
|
13236
13713
|
const entries = [];
|
|
13237
13714
|
const fingerprints = [];
|
|
@@ -13280,11 +13757,11 @@ function writeMcpImport(plan2) {
|
|
|
13280
13757
|
"Resolve blocked or conflicting MCP entries before importing"
|
|
13281
13758
|
);
|
|
13282
13759
|
}
|
|
13283
|
-
if (
|
|
13760
|
+
if (readFileSync26(plan2.config, "utf8") !== plan2.original) {
|
|
13284
13761
|
throw new ProjectError("pmcp.toml changed after planning; plan again");
|
|
13285
13762
|
}
|
|
13286
13763
|
for (const source2 of plan2.sources) {
|
|
13287
|
-
const current = createHash14("sha256").update(
|
|
13764
|
+
const current = createHash14("sha256").update(readFileSync26(source2.path)).digest("hex");
|
|
13288
13765
|
if (current !== source2.sha256) {
|
|
13289
13766
|
throw new ProjectError("MCP source changed after planning; plan again");
|
|
13290
13767
|
}
|
|
@@ -13293,8 +13770,8 @@ function writeMcpImport(plan2) {
|
|
|
13293
13770
|
const after = `${plan2.original.trimEnd()}
|
|
13294
13771
|
|
|
13295
13772
|
${additions}`;
|
|
13296
|
-
|
|
13297
|
-
const backup = backupFiles(
|
|
13773
|
+
parse6(after);
|
|
13774
|
+
const backup = backupFiles(dirname19(resolve13(plan2.config)), [
|
|
13298
13775
|
basename6(plan2.config)
|
|
13299
13776
|
]);
|
|
13300
13777
|
writeFileSync6(plan2.config, after);
|
|
@@ -13303,25 +13780,25 @@ ${additions}`;
|
|
|
13303
13780
|
|
|
13304
13781
|
// src/project/mcp-env.ts
|
|
13305
13782
|
import { spawn } from "child_process";
|
|
13306
|
-
import { readFileSync as
|
|
13783
|
+
import { readFileSync as readFileSync27, statSync as statSync12 } from "fs";
|
|
13307
13784
|
import { homedir as homedir5 } from "os";
|
|
13308
|
-
import { resolve as
|
|
13309
|
-
import { parse as
|
|
13785
|
+
import { resolve as resolve14 } from "path";
|
|
13786
|
+
import { parse as parse7 } from "smol-toml";
|
|
13310
13787
|
function sourcePath(file2, root) {
|
|
13311
13788
|
if (file2.startsWith("~/")) {
|
|
13312
|
-
return
|
|
13789
|
+
return resolve14(homedir5(), file2.slice(2));
|
|
13313
13790
|
}
|
|
13314
|
-
return
|
|
13791
|
+
return resolve14(root, file2.replaceAll("${PROJECT_ROOT}", root));
|
|
13315
13792
|
}
|
|
13316
13793
|
function sourceValue(file2, pointer) {
|
|
13317
13794
|
try {
|
|
13318
13795
|
if (statSync12(file2).size > 2 * 1024 * 1024) {
|
|
13319
13796
|
throw new Error("size");
|
|
13320
13797
|
}
|
|
13321
|
-
const text3 =
|
|
13798
|
+
const text3 = readFileSync27(file2, "utf8").replace(/^\uFEFF/u, "");
|
|
13322
13799
|
let value;
|
|
13323
13800
|
if (file2.endsWith(".toml")) {
|
|
13324
|
-
value =
|
|
13801
|
+
value = parse7(text3);
|
|
13325
13802
|
} else {
|
|
13326
13803
|
value = JSON.parse(text3);
|
|
13327
13804
|
}
|
|
@@ -13399,9 +13876,9 @@ async function execMcp(spec, alias, env) {
|
|
|
13399
13876
|
|
|
13400
13877
|
// src/project/doctor.ts
|
|
13401
13878
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
13402
|
-
import { existsSync as
|
|
13403
|
-
import { join as
|
|
13404
|
-
import { parse as
|
|
13879
|
+
import { existsSync as existsSync21, readFileSync as readFileSync28 } from "fs";
|
|
13880
|
+
import { join as join25 } from "path";
|
|
13881
|
+
import { parse as parse8 } from "smol-toml";
|
|
13405
13882
|
var spawnRunner = (command2, args, cwd, env) => {
|
|
13406
13883
|
const result = spawnSync3(command2, [...args], {
|
|
13407
13884
|
cwd,
|
|
@@ -13477,10 +13954,10 @@ function claude(spec, o) {
|
|
|
13477
13954
|
}
|
|
13478
13955
|
function codex(spec, o) {
|
|
13479
13956
|
const findings = [];
|
|
13480
|
-
const config =
|
|
13957
|
+
const config = join25(o.home, ".codex/config.toml");
|
|
13481
13958
|
let trusted = false;
|
|
13482
|
-
if (
|
|
13483
|
-
const projects =
|
|
13959
|
+
if (existsSync21(config)) {
|
|
13960
|
+
const projects = parse8(readFileSync28(config, "utf8")).projects ?? {};
|
|
13484
13961
|
const root = spec.root.replace(/\/$/u, "");
|
|
13485
13962
|
trusted = projects[root]?.trust_level === "trusted" || projects[`${root}/`]?.trust_level === "trusted";
|
|
13486
13963
|
}
|
|
@@ -13606,8 +14083,8 @@ function grok(spec, o) {
|
|
|
13606
14083
|
}
|
|
13607
14084
|
function antigravity(spec, options2) {
|
|
13608
14085
|
const foreign = agentNames(spec.root).filter((name) => {
|
|
13609
|
-
const text3 =
|
|
13610
|
-
|
|
14086
|
+
const text3 = readFileSync28(
|
|
14087
|
+
join25(spec.root, ".agents/agents", name, "agent.md"),
|
|
13611
14088
|
"utf8"
|
|
13612
14089
|
);
|
|
13613
14090
|
return splitFrontmatter(text3).meta["tools"] !== void 0;
|
|
@@ -13627,7 +14104,7 @@ function antigravity(spec, options2) {
|
|
|
13627
14104
|
if (serversForTool(spec, "antigravity").length > 0) {
|
|
13628
14105
|
const result = options2.run(
|
|
13629
14106
|
"agy",
|
|
13630
|
-
["plugin", "validate",
|
|
14107
|
+
["plugin", "validate", join25(spec.root, ANTIGRAVITY_MCP_PLUGIN)],
|
|
13631
14108
|
spec.root
|
|
13632
14109
|
);
|
|
13633
14110
|
findings.push(
|
|
@@ -13658,9 +14135,9 @@ function doctor(spec, options2) {
|
|
|
13658
14135
|
}
|
|
13659
14136
|
|
|
13660
14137
|
// src/project/memory.ts
|
|
13661
|
-
import { existsSync as
|
|
13662
|
-
import { isAbsolute as isAbsolute7, join as
|
|
13663
|
-
import { parse as
|
|
14138
|
+
import { existsSync as existsSync22, readFileSync as readFileSync29 } from "fs";
|
|
14139
|
+
import { isAbsolute as isAbsolute7, join as join26, resolve as resolve15 } from "path";
|
|
14140
|
+
import { parse as parse9 } from "smol-toml";
|
|
13664
14141
|
function object7(value) {
|
|
13665
14142
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
13666
14143
|
throw new Error("Configuration is not an object");
|
|
@@ -13668,11 +14145,11 @@ function object7(value) {
|
|
|
13668
14145
|
return value;
|
|
13669
14146
|
}
|
|
13670
14147
|
function settings(file2, toml = false) {
|
|
13671
|
-
if (!
|
|
14148
|
+
if (!existsSync22(file2)) {
|
|
13672
14149
|
return {};
|
|
13673
14150
|
}
|
|
13674
|
-
const text3 =
|
|
13675
|
-
return object7(toml ?
|
|
14151
|
+
const text3 = readFileSync29(file2, "utf8");
|
|
14152
|
+
return object7(toml ? parse9(text3) : JSON.parse(text3));
|
|
13676
14153
|
}
|
|
13677
14154
|
function configuredState(value) {
|
|
13678
14155
|
if (value === true) {
|
|
@@ -13684,11 +14161,11 @@ function configuredState(value) {
|
|
|
13684
14161
|
return "unknown";
|
|
13685
14162
|
}
|
|
13686
14163
|
function located(metadata2, path, pathScope) {
|
|
13687
|
-
return { ...metadata2, path, pathScope, exists:
|
|
14164
|
+
return { ...metadata2, path, pathScope, exists: existsSync22(path) };
|
|
13688
14165
|
}
|
|
13689
14166
|
function metadata(tool, spec, options2) {
|
|
13690
14167
|
const env = options2.env ?? {};
|
|
13691
|
-
const directory = (name, fallback) =>
|
|
14168
|
+
const directory = (name, fallback) => resolve15(spec.root, env[name] ?? join26(options2.home, fallback));
|
|
13692
14169
|
if (tool === "antigravity") {
|
|
13693
14170
|
return {
|
|
13694
14171
|
state: "unsupported",
|
|
@@ -13712,7 +14189,7 @@ function metadata(tool, spec, options2) {
|
|
|
13712
14189
|
state,
|
|
13713
14190
|
source: result.missing ? "Native CLI not installed" : "codex features list; active session overrides are not inspected"
|
|
13714
14191
|
},
|
|
13715
|
-
|
|
14192
|
+
join26(home2, "memories"),
|
|
13716
14193
|
"store"
|
|
13717
14194
|
);
|
|
13718
14195
|
}
|
|
@@ -13725,12 +14202,12 @@ function metadata(tool, spec, options2) {
|
|
|
13725
14202
|
}
|
|
13726
14203
|
if (tool === "claude") {
|
|
13727
14204
|
const home2 = directory("CLAUDE_CONFIG_DIR", ".claude");
|
|
13728
|
-
const user2 = settings(
|
|
14205
|
+
const user2 = settings(join26(home2, "settings.json"));
|
|
13729
14206
|
const state = env["CLAUDE_CODE_DISABLE_AUTO_MEMORY"] === "1" ? "disabled" : configuredState(user2["autoMemoryEnabled"]);
|
|
13730
14207
|
const source2 = "User configuration and disable environment flag; project trust and active session overrides are not inspected";
|
|
13731
14208
|
const override = user2["autoMemoryDirectory"];
|
|
13732
14209
|
if (typeof override === "string") {
|
|
13733
|
-
const path = override.startsWith("~/") ?
|
|
14210
|
+
const path = override.startsWith("~/") ? join26(options2.home, override.slice(2)) : override;
|
|
13734
14211
|
if (!isAbsolute7(path)) {
|
|
13735
14212
|
return {
|
|
13736
14213
|
state: "unknown",
|
|
@@ -13743,7 +14220,7 @@ function metadata(tool, spec, options2) {
|
|
|
13743
14220
|
if (env["CLAUDE_CONFIG_DIR"] && projectName && !projectName.includes("/") && !projectName.includes("\\") && projectName !== "." && projectName !== "..") {
|
|
13744
14221
|
return located(
|
|
13745
14222
|
{ state, source: source2 },
|
|
13746
|
-
|
|
14223
|
+
join26(home2, "projects", projectName, "memory"),
|
|
13747
14224
|
"store"
|
|
13748
14225
|
);
|
|
13749
14226
|
}
|
|
@@ -13752,14 +14229,14 @@ function metadata(tool, spec, options2) {
|
|
|
13752
14229
|
state,
|
|
13753
14230
|
source: `${source2}; native project directory encoding is not guessed`
|
|
13754
14231
|
},
|
|
13755
|
-
|
|
14232
|
+
join26(home2, "projects"),
|
|
13756
14233
|
"base"
|
|
13757
14234
|
);
|
|
13758
14235
|
}
|
|
13759
14236
|
if (tool === "grok") {
|
|
13760
|
-
const home2 =
|
|
13761
|
-
const user2 = settings(
|
|
13762
|
-
const project2 = settings(
|
|
14237
|
+
const home2 = join26(options2.home, ".grok");
|
|
14238
|
+
const user2 = settings(join26(home2, "config.toml"), true);
|
|
14239
|
+
const project2 = settings(join26(spec.root, ".grok/config.toml"), true);
|
|
13763
14240
|
const userMemory = user2["memory"] === void 0 ? {} : object7(user2["memory"]);
|
|
13764
14241
|
const projectMemory = project2["memory"] === void 0 ? {} : object7(project2["memory"]);
|
|
13765
14242
|
const overridden = projectMemory["enabled"] !== void 0;
|
|
@@ -13769,12 +14246,12 @@ function metadata(tool, spec, options2) {
|
|
|
13769
14246
|
state,
|
|
13770
14247
|
source: "User memory configuration; project trust and workspace encoding are not exposed by a read-only native memory command"
|
|
13771
14248
|
},
|
|
13772
|
-
|
|
14249
|
+
join26(home2, "memory-v2/workspaces"),
|
|
13773
14250
|
"base"
|
|
13774
14251
|
);
|
|
13775
14252
|
}
|
|
13776
14253
|
const home = directory("GEMINI_CLI_HOME", ".gemini");
|
|
13777
|
-
const user = settings(
|
|
14254
|
+
const user = settings(join26(home, "settings.json"));
|
|
13778
14255
|
const context = user["context"] === void 0 ? {} : object7(user["context"]);
|
|
13779
14256
|
const names = context["fileName"] ?? "GEMINI.md";
|
|
13780
14257
|
const contextFileNames = typeof names === "string" ? [names] : names;
|
|
@@ -13790,7 +14267,7 @@ function metadata(tool, spec, options2) {
|
|
|
13790
14267
|
source: "Global memory file existence and user context filenames; active memory tool enablement is not exposed",
|
|
13791
14268
|
contextFileNames
|
|
13792
14269
|
},
|
|
13793
|
-
|
|
14270
|
+
join26(home, "GEMINI.md"),
|
|
13794
14271
|
"store"
|
|
13795
14272
|
);
|
|
13796
14273
|
}
|
|
@@ -13823,14 +14300,14 @@ function memoryFindings(spec, options2) {
|
|
|
13823
14300
|
// src/project/edit.ts
|
|
13824
14301
|
import {
|
|
13825
14302
|
cpSync,
|
|
13826
|
-
existsSync as
|
|
14303
|
+
existsSync as existsSync23,
|
|
13827
14304
|
mkdirSync as mkdirSync8,
|
|
13828
|
-
readFileSync as
|
|
13829
|
-
rmSync as
|
|
14305
|
+
readFileSync as readFileSync30,
|
|
14306
|
+
rmSync as rmSync6,
|
|
13830
14307
|
statSync as statSync13,
|
|
13831
14308
|
writeFileSync as writeFileSync7
|
|
13832
14309
|
} from "fs";
|
|
13833
|
-
import { basename as basename7, dirname as
|
|
14310
|
+
import { basename as basename7, dirname as dirname20, join as join27 } from "path";
|
|
13834
14311
|
var CROSS_TOOL = ["name", "description"];
|
|
13835
14312
|
var tomlValue2 = (value) => JSON.stringify(value);
|
|
13836
14313
|
function findEntry(entries, wanted) {
|
|
@@ -13854,7 +14331,7 @@ function addEntry(root, entry2) {
|
|
|
13854
14331
|
`${entry2.name} is a ${kind2}; only skills and agents are added to .agents/`
|
|
13855
14332
|
);
|
|
13856
14333
|
}
|
|
13857
|
-
const { meta, body } = splitFrontmatter(
|
|
14334
|
+
const { meta, body } = splitFrontmatter(readFileSync30(entry2.path, "utf8"));
|
|
13858
14335
|
const name = basename7(entry2.slug);
|
|
13859
14336
|
if (!/^[a-z0-9][a-z0-9-]*$/u.test(name)) {
|
|
13860
14337
|
throw new ProjectError(
|
|
@@ -13862,11 +14339,11 @@ function addEntry(root, entry2) {
|
|
|
13862
14339
|
);
|
|
13863
14340
|
}
|
|
13864
14341
|
if (kind2 === "skill") {
|
|
13865
|
-
const dest2 =
|
|
13866
|
-
if (
|
|
14342
|
+
const dest2 = join27(root, ".agents/skills", name);
|
|
14343
|
+
if (existsSync23(dest2))
|
|
13867
14344
|
throw new ProjectError(`.agents/skills/${name} already exists`);
|
|
13868
|
-
const source2 = entry2.root ??
|
|
13869
|
-
mkdirSync8(
|
|
14345
|
+
const source2 = entry2.root ?? dirname20(entry2.path);
|
|
14346
|
+
mkdirSync8(dirname20(dest2), { recursive: true });
|
|
13870
14347
|
if (statSync13(source2).isDirectory()) {
|
|
13871
14348
|
cpSync(source2, dest2, { recursive: true });
|
|
13872
14349
|
} else {
|
|
@@ -13878,14 +14355,14 @@ function addEntry(root, entry2) {
|
|
|
13878
14355
|
description: meta["description"] ?? entry2.description
|
|
13879
14356
|
});
|
|
13880
14357
|
writeFileSync7(
|
|
13881
|
-
|
|
14358
|
+
join27(dest2, "SKILL.md"),
|
|
13882
14359
|
`${head}
|
|
13883
14360
|
${body.replace(/^\n/u, "")}`
|
|
13884
14361
|
);
|
|
13885
14362
|
return { kind: kind2, name, path: `.agents/skills/${name}` };
|
|
13886
14363
|
}
|
|
13887
|
-
const dest =
|
|
13888
|
-
if (
|
|
14364
|
+
const dest = join27(root, ".agents/agents", name);
|
|
14365
|
+
if (existsSync23(dest))
|
|
13889
14366
|
throw new ProjectError(`.agents/agents/${name} already exists`);
|
|
13890
14367
|
const kept = {};
|
|
13891
14368
|
const moved = {};
|
|
@@ -13895,17 +14372,17 @@ ${body.replace(/^\n/u, "")}`
|
|
|
13895
14372
|
}
|
|
13896
14373
|
mkdirSync8(dest, { recursive: true });
|
|
13897
14374
|
writeFileSync7(
|
|
13898
|
-
|
|
14375
|
+
join27(dest, "agent.md"),
|
|
13899
14376
|
`${renderFrontmatter(kept)}
|
|
13900
14377
|
${body.replace(/^\n/u, "")}`
|
|
13901
14378
|
);
|
|
13902
14379
|
if (Object.keys(moved).length > 0) {
|
|
13903
|
-
const config =
|
|
14380
|
+
const config = join27(root, "pmcp.toml");
|
|
13904
14381
|
const lines2 = [
|
|
13905
14382
|
`[agents.${name}.claude]`,
|
|
13906
14383
|
...Object.entries(moved).map(([k, v]) => `${k} = ${tomlValue2(v)}`)
|
|
13907
14384
|
];
|
|
13908
|
-
const before =
|
|
14385
|
+
const before = existsSync23(config) ? readFileSync30(config, "utf8") : "";
|
|
13909
14386
|
const sep7 = before === "" || before.endsWith("\n\n") ? "" : "\n";
|
|
13910
14387
|
writeFileSync7(config, `${before}${sep7}${lines2.join("\n")}
|
|
13911
14388
|
`);
|
|
@@ -13928,15 +14405,15 @@ function withoutTables(toml, name) {
|
|
|
13928
14405
|
function removeAsset(root, name) {
|
|
13929
14406
|
const removed = [];
|
|
13930
14407
|
for (const rel of [`.agents/skills/${name}`, `.agents/agents/${name}`]) {
|
|
13931
|
-
if (!
|
|
13932
|
-
|
|
14408
|
+
if (!existsSync23(join27(root, rel))) continue;
|
|
14409
|
+
rmSync6(join27(root, rel), { recursive: true, force: true });
|
|
13933
14410
|
removed.push(rel);
|
|
13934
14411
|
}
|
|
13935
14412
|
if (removed.length === 0)
|
|
13936
14413
|
throw new ProjectError(`no skill or agent named ${name} in .agents/`);
|
|
13937
|
-
const config =
|
|
13938
|
-
if (
|
|
13939
|
-
const before =
|
|
14414
|
+
const config = join27(root, "pmcp.toml");
|
|
14415
|
+
if (existsSync23(config)) {
|
|
14416
|
+
const before = readFileSync30(config, "utf8");
|
|
13940
14417
|
const after = withoutTables(before, name);
|
|
13941
14418
|
if (after !== before) writeFileSync7(config, after);
|
|
13942
14419
|
}
|
|
@@ -14175,16 +14652,16 @@ var doctorCommand = {
|
|
|
14175
14652
|
};
|
|
14176
14653
|
|
|
14177
14654
|
// src/validate.ts
|
|
14178
|
-
import { existsSync as
|
|
14179
|
-
import { basename as basename8, join as
|
|
14655
|
+
import { existsSync as existsSync24, readFileSync as readFileSync31, statSync as statSync14 } from "fs";
|
|
14656
|
+
import { basename as basename8, join as join28, resolve as resolve16 } from "path";
|
|
14180
14657
|
function validateSkillDir(dir) {
|
|
14181
|
-
const root =
|
|
14182
|
-
const skillMd = basename8(root) === "SKILL.md" ? root :
|
|
14183
|
-
if (!
|
|
14658
|
+
const root = resolve16(dir);
|
|
14659
|
+
const skillMd = basename8(root) === "SKILL.md" ? root : join28(root, "SKILL.md");
|
|
14660
|
+
if (!existsSync24(skillMd) || !statSync14(skillMd).isFile()) {
|
|
14184
14661
|
return [{ path: root, reason: "no SKILL.md", level: "error" }];
|
|
14185
14662
|
}
|
|
14186
|
-
const front = frontmatterObject(
|
|
14187
|
-
const directory = basename8(skillMd === root ?
|
|
14663
|
+
const front = frontmatterObject(readFileSync31(skillMd, "utf8"));
|
|
14664
|
+
const directory = basename8(skillMd === root ? join28(root, "..") : root);
|
|
14188
14665
|
return specProblems(front, directory).map((reason) => ({
|
|
14189
14666
|
path: skillMd,
|
|
14190
14667
|
reason,
|
|
@@ -14229,8 +14706,8 @@ var validateCommand = {
|
|
|
14229
14706
|
};
|
|
14230
14707
|
|
|
14231
14708
|
// src/commands/mailbox.ts
|
|
14232
|
-
import { readFileSync as
|
|
14233
|
-
import { resolve as
|
|
14709
|
+
import { readFileSync as readFileSync32, writeFileSync as writeFileSync8 } from "fs";
|
|
14710
|
+
import { resolve as resolve17 } from "path";
|
|
14234
14711
|
init_config2();
|
|
14235
14712
|
init_service();
|
|
14236
14713
|
init_schemas();
|
|
@@ -14313,7 +14790,7 @@ var mailboxCommand = {
|
|
|
14313
14790
|
}
|
|
14314
14791
|
if (verb === "init") {
|
|
14315
14792
|
const config2 = initializeMailboxConfig(
|
|
14316
|
-
|
|
14793
|
+
resolve17(context.cwd, one(context.args, "config") ?? "pmcp.toml"),
|
|
14317
14794
|
one(context.args, "state-dir")
|
|
14318
14795
|
);
|
|
14319
14796
|
const store2 = await MailboxStore.open(config2);
|
|
@@ -14338,7 +14815,7 @@ var mailboxCommand = {
|
|
|
14338
14815
|
if (verb === "restore") {
|
|
14339
14816
|
await restoreBackup(required(context, "input"), {
|
|
14340
14817
|
...config,
|
|
14341
|
-
databasePath:
|
|
14818
|
+
databasePath: resolve17(context.cwd, required(context, "output"))
|
|
14342
14819
|
});
|
|
14343
14820
|
context.ui.data(
|
|
14344
14821
|
`${JSON.stringify({ ok: true, data: { restored: true } })}
|
|
@@ -14412,7 +14889,7 @@ var mailboxCommand = {
|
|
|
14412
14889
|
ownsBinding = true;
|
|
14413
14890
|
}
|
|
14414
14891
|
const inputFile = one(context.args, "input");
|
|
14415
|
-
const input = inputFile ? JSON.parse(
|
|
14892
|
+
const input = inputFile ? JSON.parse(readFileSync32(inputFile, "utf8")) : {};
|
|
14416
14893
|
const result = await new MailboxService(store, binding).call(
|
|
14417
14894
|
operation,
|
|
14418
14895
|
input
|
|
@@ -14496,7 +14973,7 @@ var harnessOptions = [
|
|
|
14496
14973
|
];
|
|
14497
14974
|
|
|
14498
14975
|
// src/commands/harness/invocation.ts
|
|
14499
|
-
import { readFileSync as
|
|
14976
|
+
import { readFileSync as readFileSync33, statSync as statSync15 } from "fs";
|
|
14500
14977
|
init_validation();
|
|
14501
14978
|
var common = ["group-file", "generation"];
|
|
14502
14979
|
var options = {
|
|
@@ -14536,7 +15013,7 @@ function file(path) {
|
|
|
14536
15013
|
const stat = statSync15(path);
|
|
14537
15014
|
if (!stat.isFile() || stat.size > 65536)
|
|
14538
15015
|
throw new ArgumentError("Input file exceeds 65536 bytes");
|
|
14539
|
-
return
|
|
15016
|
+
return readFileSync33(path, "utf8");
|
|
14540
15017
|
}
|
|
14541
15018
|
function parseInvocation(context) {
|
|
14542
15019
|
const [family, action, ...extra] = context.args.positional;
|
|
@@ -14642,6 +15119,9 @@ var harnessCommand = {
|
|
|
14642
15119
|
}
|
|
14643
15120
|
};
|
|
14644
15121
|
|
|
15122
|
+
// src/commands/tui.ts
|
|
15123
|
+
init_config();
|
|
15124
|
+
|
|
14645
15125
|
// src/commands/tui/invocation.ts
|
|
14646
15126
|
var tuiOptions = [
|
|
14647
15127
|
{
|
|
@@ -14664,9 +15144,24 @@ var tuiOptions = [
|
|
|
14664
15144
|
},
|
|
14665
15145
|
{
|
|
14666
15146
|
name: "config",
|
|
14667
|
-
describe: "
|
|
15147
|
+
describe: "Project pmcp.toml (or mailbox config for create)",
|
|
14668
15148
|
placeholder: "<file>"
|
|
14669
15149
|
},
|
|
15150
|
+
{
|
|
15151
|
+
name: "runtime",
|
|
15152
|
+
describe: "Main native runtime ID for launch",
|
|
15153
|
+
placeholder: "<runtime>"
|
|
15154
|
+
},
|
|
15155
|
+
{
|
|
15156
|
+
name: "yes",
|
|
15157
|
+
describe: "Confirm the first native runtime start",
|
|
15158
|
+
boolean: true
|
|
15159
|
+
},
|
|
15160
|
+
{
|
|
15161
|
+
name: "new",
|
|
15162
|
+
describe: "Create a workspace after the previous group is stopped",
|
|
15163
|
+
boolean: true
|
|
15164
|
+
},
|
|
14670
15165
|
{
|
|
14671
15166
|
name: "actor-file",
|
|
14672
15167
|
describe: "Optional private mailbox observer credential",
|
|
@@ -14674,6 +15169,8 @@ var tuiOptions = [
|
|
|
14674
15169
|
}
|
|
14675
15170
|
];
|
|
14676
15171
|
var allowed2 = {
|
|
15172
|
+
launch: ["config", "runtime", "yes", "new"],
|
|
15173
|
+
plan: ["config", "runtime"],
|
|
14677
15174
|
doctor: [],
|
|
14678
15175
|
create: ["group-file", "columns", "rows", "config", "actor-file"],
|
|
14679
15176
|
inspect: ["view"],
|
|
@@ -14694,7 +15191,8 @@ var allowed2 = {
|
|
|
14694
15191
|
check: ["view"]
|
|
14695
15192
|
};
|
|
14696
15193
|
function parseTui(args) {
|
|
14697
|
-
|
|
15194
|
+
let [action, ...extra] = args.positional;
|
|
15195
|
+
if (action === void 0) action = one(args, "view") ? "open" : "launch";
|
|
14698
15196
|
const options2 = action ? allowed2[action] : void 0;
|
|
14699
15197
|
if (!action || !options2 || extra.length)
|
|
14700
15198
|
throw new ArgumentError("Provide one supported TUI operation");
|
|
@@ -14714,6 +15212,17 @@ function parseTui(args) {
|
|
|
14714
15212
|
return Number(value);
|
|
14715
15213
|
};
|
|
14716
15214
|
if (action === "doctor") return { action, input: {} };
|
|
15215
|
+
if (action === "launch" || action === "plan")
|
|
15216
|
+
return {
|
|
15217
|
+
action,
|
|
15218
|
+
input: {},
|
|
15219
|
+
startup: {
|
|
15220
|
+
config: one(args, "config"),
|
|
15221
|
+
runtime: one(args, "runtime"),
|
|
15222
|
+
fresh: args.flags.has("new"),
|
|
15223
|
+
confirmed: args.flags.has("yes")
|
|
15224
|
+
}
|
|
15225
|
+
};
|
|
14717
15226
|
if (action === "create") {
|
|
14718
15227
|
const config = one(args, "config");
|
|
14719
15228
|
const actorFile = one(args, "actor-file");
|
|
@@ -14753,19 +15262,30 @@ function parseTui(args) {
|
|
|
14753
15262
|
// src/commands/tui.ts
|
|
14754
15263
|
var tuiCommand = {
|
|
14755
15264
|
name: "tui",
|
|
14756
|
-
describe: "
|
|
14757
|
-
usage: "pmcp tui
|
|
15265
|
+
describe: "Open a native terminal workspace with optional configuration",
|
|
15266
|
+
usage: "pmcp tui [--config <file>] [--runtime <id>] | plan | doctor | operation --view <file>",
|
|
14758
15267
|
options: tuiOptions,
|
|
14759
15268
|
async run(context) {
|
|
14760
15269
|
const invocation2 = parseTui(context.args);
|
|
14761
15270
|
const { invokeTui: invokeTui2, failedInvocation: failedInvocation2 } = await Promise.resolve().then(() => (init_invoke2(), invoke_exports2));
|
|
14762
15271
|
try {
|
|
14763
|
-
|
|
14764
|
-
if (invocation2.action
|
|
15272
|
+
let receipt;
|
|
15273
|
+
if (invocation2.action === "launch" || invocation2.action === "plan") {
|
|
15274
|
+
const { launchStartup: launchStartup2, planStartup: planStartup2 } = await Promise.resolve().then(() => (init_entry2(), entry_exports));
|
|
15275
|
+
const request3 = { ...invocation2.startup, cwd: context.cwd };
|
|
15276
|
+
receipt = invocation2.action === "plan" ? planStartup2(request3) : await launchStartup2(
|
|
15277
|
+
request3,
|
|
15278
|
+
invocation2.startup?.confirmed === true
|
|
15279
|
+
);
|
|
15280
|
+
} else {
|
|
15281
|
+
receipt = await invokeTui2(invocation2);
|
|
15282
|
+
}
|
|
15283
|
+
if (!["open", "launch"].includes(invocation2.action) || receipt.ok !== true)
|
|
14765
15284
|
context.ui.data(`${JSON.stringify(receipt)}
|
|
14766
15285
|
`);
|
|
14767
15286
|
return receipt.ok === true ? 0 : 1;
|
|
14768
15287
|
} catch (error) {
|
|
15288
|
+
if (error instanceof ConfigError) throw new ArgumentError(error.message);
|
|
14769
15289
|
context.ui.data(
|
|
14770
15290
|
`${JSON.stringify(failedInvocation2(invocation2.action, error))}
|
|
14771
15291
|
`
|
|
@@ -14780,14 +15300,14 @@ import { resolve as resolve21 } from "path";
|
|
|
14780
15300
|
|
|
14781
15301
|
// src/marketplace/export.ts
|
|
14782
15302
|
import {
|
|
14783
|
-
existsSync as
|
|
14784
|
-
lstatSync as
|
|
14785
|
-
mkdirSync as
|
|
14786
|
-
readFileSync as
|
|
15303
|
+
existsSync as existsSync37,
|
|
15304
|
+
lstatSync as lstatSync14,
|
|
15305
|
+
mkdirSync as mkdirSync13,
|
|
15306
|
+
readFileSync as readFileSync38,
|
|
14787
15307
|
readdirSync as readdirSync13,
|
|
14788
15308
|
writeFileSync as writeFileSync14
|
|
14789
15309
|
} from "fs";
|
|
14790
|
-
import { dirname as
|
|
15310
|
+
import { dirname as dirname27, join as join55, resolve as resolve20 } from "path";
|
|
14791
15311
|
var NATIVE_PLUGIN_RUNTIMES = [
|
|
14792
15312
|
"claude",
|
|
14793
15313
|
"codex",
|
|
@@ -14812,7 +15332,7 @@ function readResources(directory, prefix, files) {
|
|
|
14812
15332
|
for (const entry2 of readdirSync13(directory, { withFileTypes: true })) {
|
|
14813
15333
|
if (entry2.isSymbolicLink())
|
|
14814
15334
|
throw new Error("Plugin exports cannot contain symlinks");
|
|
14815
|
-
const path =
|
|
15335
|
+
const path = join55(directory, entry2.name);
|
|
14816
15336
|
const relativePath = `${prefix}/${entry2.name}`;
|
|
14817
15337
|
if (entry2.isDirectory()) {
|
|
14818
15338
|
if (relativePath.split("/").length > 6)
|
|
@@ -14822,10 +15342,10 @@ function readResources(directory, prefix, files) {
|
|
|
14822
15342
|
}
|
|
14823
15343
|
if (!entry2.isFile())
|
|
14824
15344
|
throw new Error("Plugin resources must be regular files");
|
|
14825
|
-
const stats =
|
|
15345
|
+
const stats = lstatSync14(path);
|
|
14826
15346
|
if (stats.size > 1024 * 1024 || files.size >= 256)
|
|
14827
15347
|
throw new Error("Plugin resources exceed the export budget");
|
|
14828
|
-
files.set(relativePath,
|
|
15348
|
+
files.set(relativePath, readFileSync38(path));
|
|
14829
15349
|
}
|
|
14830
15350
|
}
|
|
14831
15351
|
function pluginExportFiles(root, name, runtime2) {
|
|
@@ -14834,7 +15354,7 @@ function pluginExportFiles(root, name, runtime2) {
|
|
|
14834
15354
|
const files = /* @__PURE__ */ new Map();
|
|
14835
15355
|
readResources(containedPath(plugin.directory, "skills"), "skills", files);
|
|
14836
15356
|
for (const name2 of ["LICENSE", "NOTICE"])
|
|
14837
|
-
files.set(name2,
|
|
15357
|
+
files.set(name2, readFileSync38(containedPath(root, name2)));
|
|
14838
15358
|
const manifests = {
|
|
14839
15359
|
claude: [".claude-plugin/plugin.json", ".mcp.json"],
|
|
14840
15360
|
codex: ["plugin.json", "mcp.json"],
|
|
@@ -14843,10 +15363,10 @@ function pluginExportFiles(root, name, runtime2) {
|
|
|
14843
15363
|
agy: []
|
|
14844
15364
|
};
|
|
14845
15365
|
for (const filename of manifests[runtime2]) {
|
|
14846
|
-
if (
|
|
15366
|
+
if (existsSync37(join55(plugin.directory, filename)))
|
|
14847
15367
|
files.set(
|
|
14848
15368
|
filename,
|
|
14849
|
-
|
|
15369
|
+
readFileSync38(containedPath(plugin.directory, filename))
|
|
14850
15370
|
);
|
|
14851
15371
|
}
|
|
14852
15372
|
if (runtime2 === "agy") {
|
|
@@ -14857,11 +15377,11 @@ function pluginExportFiles(root, name, runtime2) {
|
|
|
14857
15377
|
`
|
|
14858
15378
|
)
|
|
14859
15379
|
);
|
|
14860
|
-
const mcp =
|
|
14861
|
-
if (
|
|
15380
|
+
const mcp = join55(plugin.directory, ".mcp.json");
|
|
15381
|
+
if (existsSync37(mcp))
|
|
14862
15382
|
files.set(
|
|
14863
15383
|
"mcp_config.json",
|
|
14864
|
-
|
|
15384
|
+
readFileSync38(containedPath(plugin.directory, mcp))
|
|
14865
15385
|
);
|
|
14866
15386
|
}
|
|
14867
15387
|
return files;
|
|
@@ -14869,9 +15389,9 @@ function pluginExportFiles(root, name, runtime2) {
|
|
|
14869
15389
|
function exportPublicPlugin(root, name, runtime2, output) {
|
|
14870
15390
|
const destination = resolve20(output);
|
|
14871
15391
|
const files = pluginExportFiles(root, name, runtime2);
|
|
14872
|
-
|
|
15392
|
+
mkdirSync13(dirname27(destination), { recursive: true });
|
|
14873
15393
|
try {
|
|
14874
|
-
|
|
15394
|
+
mkdirSync13(destination);
|
|
14875
15395
|
} catch (error) {
|
|
14876
15396
|
if (error.code === "EEXIST")
|
|
14877
15397
|
throw new Error(
|
|
@@ -14880,8 +15400,8 @@ function exportPublicPlugin(root, name, runtime2, output) {
|
|
|
14880
15400
|
throw error;
|
|
14881
15401
|
}
|
|
14882
15402
|
for (const [path, bytes] of files) {
|
|
14883
|
-
const target2 =
|
|
14884
|
-
|
|
15403
|
+
const target2 = join55(destination, path);
|
|
15404
|
+
mkdirSync13(dirname27(target2), { recursive: true });
|
|
14885
15405
|
writeFileSync14(target2, bytes, { flag: "wx" });
|
|
14886
15406
|
}
|
|
14887
15407
|
return { plugin: name, runtime: runtime2, output: destination, files: files.size };
|
|
@@ -15002,7 +15522,7 @@ function dispatch(argv, options2 = {}) {
|
|
|
15002
15522
|
});
|
|
15003
15523
|
const [verb, ...rest] = rest0;
|
|
15004
15524
|
if (verb === void 0) {
|
|
15005
|
-
return
|
|
15525
|
+
return defaultEntry(options2, { ui, env, cwd });
|
|
15006
15526
|
}
|
|
15007
15527
|
if (verb === "--help" || verb === "-h" || verb === "help") {
|
|
15008
15528
|
const named2 = COMMANDS.find((command3) => command3.name === rest[0]);
|
|
@@ -15032,7 +15552,30 @@ function dispatch(argv, options2 = {}) {
|
|
|
15032
15552
|
}
|
|
15033
15553
|
return run(command2, rest, { ui, env, cwd });
|
|
15034
15554
|
}
|
|
15035
|
-
var VERSION = "0.
|
|
15555
|
+
var VERSION = "0.15.0";
|
|
15556
|
+
async function defaultEntry(options2, context) {
|
|
15557
|
+
const terminal = options2.terminal ?? {
|
|
15558
|
+
stdin: process.stdin.isTTY === true,
|
|
15559
|
+
stdout: process.stdout.isTTY === true
|
|
15560
|
+
};
|
|
15561
|
+
const mode = entryMode({
|
|
15562
|
+
...terminal,
|
|
15563
|
+
term: context.env.TERM,
|
|
15564
|
+
ci: context.env.CI
|
|
15565
|
+
});
|
|
15566
|
+
if (mode === "server") return run(serveCommand, [], context);
|
|
15567
|
+
if (mode === "terminal") {
|
|
15568
|
+
const { terminalSupport: terminalSupport2 } = await Promise.resolve().then(() => (init_host(), host_exports));
|
|
15569
|
+
const support = terminalSupport2();
|
|
15570
|
+
if (support.supported) return run(tuiCommand, [], context);
|
|
15571
|
+
context.ui.info(`Terminal workspace unavailable: ${support.reason}`);
|
|
15572
|
+
context.ui.line(
|
|
15573
|
+
"Run pmcp tui doctor for prerequisites, or pmcp serve for MCP."
|
|
15574
|
+
);
|
|
15575
|
+
}
|
|
15576
|
+
printHelp(context.ui, COMMANDS, PLANNED);
|
|
15577
|
+
return 0;
|
|
15578
|
+
}
|
|
15036
15579
|
async function run(command2, argv, context) {
|
|
15037
15580
|
try {
|
|
15038
15581
|
return await command2.run({
|