@modootoday/pmcp 0.14.1 → 0.16.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 +57 -8
- package/dist/cli.js +1514 -589
- package/dist/harness-launch-gate.js +0 -3
- package/dist/harness-worker.js +0 -4
- package/dist/http.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +87 -25
- package/dist/mailbox/index.js +0 -3
- package/dist/stdio.js +1 -3
- package/dist/tui-worker.js +580 -32
- package/docs/skills.md +3 -3
- package/docs/terminal-workspace.md +161 -1
- 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;
|
|
@@ -453,8 +471,6 @@ var init_types = __esm({
|
|
|
453
471
|
this.retryable = retryable;
|
|
454
472
|
this.name = "MailboxError";
|
|
455
473
|
}
|
|
456
|
-
code;
|
|
457
|
-
retryable;
|
|
458
474
|
};
|
|
459
475
|
}
|
|
460
476
|
});
|
|
@@ -1819,11 +1835,11 @@ function dockerPolicy(value) {
|
|
|
1819
1835
|
for (const [runtime2, value2] of entries) {
|
|
1820
1836
|
if (!Object.hasOwn(runtimeExecutables, runtime2))
|
|
1821
1837
|
throw new Error("unsupported_docker_runtime");
|
|
1822
|
-
const
|
|
1823
|
-
const executable =
|
|
1824
|
-
if (!isAbsolute10(executable) || posix5.normalize(executable) !== executable ||
|
|
1838
|
+
const command3 = strings2(value2, "docker_runtime_command");
|
|
1839
|
+
const executable = command3[0];
|
|
1840
|
+
if (!isAbsolute10(executable) || posix5.normalize(executable) !== executable || command3.some((argument) => /[\r\n\x1b]/.test(argument)))
|
|
1825
1841
|
throw new Error("invalid_docker_runtime_command");
|
|
1826
|
-
parsed[runtime2] =
|
|
1842
|
+
parsed[runtime2] = command3;
|
|
1827
1843
|
}
|
|
1828
1844
|
return {
|
|
1829
1845
|
schemaVersion: 1,
|
|
@@ -1920,47 +1936,47 @@ var init_validation2 = __esm({
|
|
|
1920
1936
|
});
|
|
1921
1937
|
|
|
1922
1938
|
// src/harness/groups/store.ts
|
|
1923
|
-
import { randomUUID as
|
|
1939
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
1924
1940
|
import {
|
|
1925
|
-
closeSync as
|
|
1941
|
+
closeSync as closeSync4,
|
|
1926
1942
|
constants as constants3,
|
|
1927
1943
|
fstatSync as fstatSync2,
|
|
1928
|
-
fsyncSync,
|
|
1929
|
-
openSync as
|
|
1930
|
-
readFileSync as
|
|
1931
|
-
realpathSync as
|
|
1932
|
-
renameSync as
|
|
1944
|
+
fsyncSync as fsyncSync2,
|
|
1945
|
+
openSync as openSync4,
|
|
1946
|
+
readFileSync as readFileSync34,
|
|
1947
|
+
realpathSync as realpathSync6,
|
|
1948
|
+
renameSync as renameSync4,
|
|
1933
1949
|
writeFileSync as writeFileSync9
|
|
1934
1950
|
} from "fs";
|
|
1935
|
-
import { dirname as
|
|
1951
|
+
import { dirname as dirname21, join as join29 } from "path";
|
|
1936
1952
|
function privateJson(path, value) {
|
|
1937
|
-
const temporary = `${path}.${
|
|
1938
|
-
const descriptor =
|
|
1953
|
+
const temporary = `${path}.${randomUUID5()}.new`;
|
|
1954
|
+
const descriptor = openSync4(temporary, "wx", 384);
|
|
1939
1955
|
try {
|
|
1940
1956
|
writeFileSync9(descriptor, `${JSON.stringify(value, null, 2)}
|
|
1941
1957
|
`);
|
|
1942
|
-
|
|
1958
|
+
fsyncSync2(descriptor);
|
|
1943
1959
|
} finally {
|
|
1944
|
-
|
|
1960
|
+
closeSync4(descriptor);
|
|
1945
1961
|
}
|
|
1946
|
-
|
|
1947
|
-
const directory =
|
|
1962
|
+
renameSync4(temporary, path);
|
|
1963
|
+
const directory = openSync4(dirname21(path), "r");
|
|
1948
1964
|
try {
|
|
1949
|
-
|
|
1965
|
+
fsyncSync2(directory);
|
|
1950
1966
|
} finally {
|
|
1951
|
-
|
|
1967
|
+
closeSync4(directory);
|
|
1952
1968
|
}
|
|
1953
1969
|
}
|
|
1954
1970
|
function readPrivateJson2(path) {
|
|
1955
|
-
const descriptor =
|
|
1971
|
+
const descriptor = openSync4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
1956
1972
|
try {
|
|
1957
1973
|
const stat = fstatSync2(descriptor);
|
|
1958
1974
|
if (!stat.isFile() || (stat.mode & 63) !== 0 || stat.size > 2097152)
|
|
1959
1975
|
throw new Error("unsafe_state_file");
|
|
1960
1976
|
if (stat.uid !== process.getuid?.()) throw new Error("foreign_state_file");
|
|
1961
|
-
return JSON.parse(
|
|
1977
|
+
return JSON.parse(readFileSync34(descriptor, "utf8"));
|
|
1962
1978
|
} finally {
|
|
1963
|
-
|
|
1979
|
+
closeSync4(descriptor);
|
|
1964
1980
|
}
|
|
1965
1981
|
}
|
|
1966
1982
|
var GroupStore;
|
|
@@ -1973,10 +1989,10 @@ var init_store2 = __esm({
|
|
|
1973
1989
|
path;
|
|
1974
1990
|
state;
|
|
1975
1991
|
constructor(path) {
|
|
1976
|
-
this.path =
|
|
1992
|
+
this.path = realpathSync6(path);
|
|
1977
1993
|
this.state = validateState(readPrivateJson2(path));
|
|
1978
1994
|
const metadata2 = object8(
|
|
1979
|
-
readPrivateJson2(
|
|
1995
|
+
readPrivateJson2(join29(dirname21(this.path), "harness.json"))
|
|
1980
1996
|
);
|
|
1981
1997
|
if (this.state.group.schemaVersion !== 1 || this.state.group.owner !== metadata2.owner) {
|
|
1982
1998
|
throw new Error("foreign_group");
|
|
@@ -1997,13 +2013,13 @@ var init_store2 = __esm({
|
|
|
1997
2013
|
|
|
1998
2014
|
// src/harness/adapters/process/serialized.ts
|
|
1999
2015
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
2000
|
-
import { randomUUID as
|
|
2001
|
-
import { existsSync as
|
|
2002
|
-
import { dirname as
|
|
2016
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
2017
|
+
import { existsSync as existsSync25, unlinkSync as unlinkSync2 } from "fs";
|
|
2018
|
+
import { dirname as dirname22, join as join30 } from "path";
|
|
2003
2019
|
function serialized(operation) {
|
|
2004
2020
|
const store = new GroupStore(text(operation.groupFile, "group_file"));
|
|
2005
|
-
const directory =
|
|
2006
|
-
const operationFile =
|
|
2021
|
+
const directory = dirname22(store.path);
|
|
2022
|
+
const operationFile = join30(directory, `operation-${randomUUID6()}.json`);
|
|
2007
2023
|
privateJson(operationFile, operation);
|
|
2008
2024
|
const worker = artifact("worker");
|
|
2009
2025
|
try {
|
|
@@ -2015,7 +2031,7 @@ function serialized(operation) {
|
|
|
2015
2031
|
"--no-fork",
|
|
2016
2032
|
"--conflict-exit-code",
|
|
2017
2033
|
"73",
|
|
2018
|
-
|
|
2034
|
+
join30(directory, "group-control.lock"),
|
|
2019
2035
|
process.execPath,
|
|
2020
2036
|
worker,
|
|
2021
2037
|
operationFile
|
|
@@ -2031,7 +2047,7 @@ function serialized(operation) {
|
|
|
2031
2047
|
if (!result.stdout.trim()) throw new Error("worker_receipt_missing");
|
|
2032
2048
|
return JSON.parse(result.stdout);
|
|
2033
2049
|
} finally {
|
|
2034
|
-
if (
|
|
2050
|
+
if (existsSync25(operationFile)) unlinkSync2(operationFile);
|
|
2035
2051
|
}
|
|
2036
2052
|
}
|
|
2037
2053
|
var init_serialized = __esm({
|
|
@@ -2111,11 +2127,11 @@ var init_command = __esm({
|
|
|
2111
2127
|
});
|
|
2112
2128
|
|
|
2113
2129
|
// src/harness/adapters/process/identity.ts
|
|
2114
|
-
import { readFileSync as
|
|
2130
|
+
import { readFileSync as readFileSync35 } from "fs";
|
|
2115
2131
|
function processIdentity(pid) {
|
|
2116
2132
|
if (!Number.isSafeInteger(pid) || pid < 1)
|
|
2117
2133
|
throw new Error("invalid_process_id");
|
|
2118
|
-
const stat =
|
|
2134
|
+
const stat = readFileSync35(`/proc/${pid}/stat`, "utf8");
|
|
2119
2135
|
const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
2120
2136
|
if (!/^\d+$/.test(fields[19] ?? ""))
|
|
2121
2137
|
throw new Error("process_identity_unknown");
|
|
@@ -2285,7 +2301,6 @@ var init_connection = __esm({
|
|
|
2285
2301
|
constructor(records2) {
|
|
2286
2302
|
this.records = records2;
|
|
2287
2303
|
}
|
|
2288
|
-
records;
|
|
2289
2304
|
tmux(args, options2 = {}) {
|
|
2290
2305
|
return command(
|
|
2291
2306
|
"tmux",
|
|
@@ -2345,19 +2360,19 @@ var init_connection = __esm({
|
|
|
2345
2360
|
});
|
|
2346
2361
|
|
|
2347
2362
|
// src/harness/adapters/tmux/records.ts
|
|
2348
|
-
import { randomUUID as
|
|
2363
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
2349
2364
|
import {
|
|
2350
|
-
lstatSync as
|
|
2365
|
+
lstatSync as lstatSync6,
|
|
2351
2366
|
mkdirSync as mkdirSync9,
|
|
2352
2367
|
readdirSync as readdirSync10,
|
|
2353
|
-
realpathSync as
|
|
2368
|
+
realpathSync as realpathSync7,
|
|
2354
2369
|
writeFileSync as writeFileSync10
|
|
2355
2370
|
} from "fs";
|
|
2356
2371
|
import { homedir as homedir7 } from "os";
|
|
2357
|
-
import { join as
|
|
2372
|
+
import { join as join31 } from "path";
|
|
2358
2373
|
function stateDirectory() {
|
|
2359
|
-
return
|
|
2360
|
-
process.env.XDG_STATE_HOME ??
|
|
2374
|
+
return join31(
|
|
2375
|
+
process.env.XDG_STATE_HOME ?? join31(homedir7(), ".local/state"),
|
|
2361
2376
|
"pmcp/harness"
|
|
2362
2377
|
);
|
|
2363
2378
|
}
|
|
@@ -2384,15 +2399,15 @@ var init_records = __esm({
|
|
|
2384
2399
|
configPath;
|
|
2385
2400
|
owner;
|
|
2386
2401
|
constructor(directory) {
|
|
2387
|
-
const stat =
|
|
2402
|
+
const stat = lstatSync6(directory);
|
|
2388
2403
|
if (!stat.isDirectory() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
2389
2404
|
throw new Error("unsafe_group_directory");
|
|
2390
|
-
this.directory =
|
|
2391
|
-
this.socket =
|
|
2405
|
+
this.directory = realpathSync7(directory);
|
|
2406
|
+
this.socket = join31(this.directory, "s");
|
|
2392
2407
|
if (Buffer.byteLength(this.socket) > 100)
|
|
2393
2408
|
throw new Error("socket_path_too_long");
|
|
2394
|
-
this.metadataPath =
|
|
2395
|
-
this.configPath =
|
|
2409
|
+
this.metadataPath = join31(this.directory, "harness.json");
|
|
2410
|
+
this.configPath = join31(this.directory, "tmux.conf");
|
|
2396
2411
|
this.owner = text(
|
|
2397
2412
|
object8(readPrivateJson2(this.metadataPath)).owner,
|
|
2398
2413
|
"owner"
|
|
@@ -2402,17 +2417,17 @@ var init_records = __esm({
|
|
|
2402
2417
|
static create() {
|
|
2403
2418
|
const root = stateDirectory();
|
|
2404
2419
|
mkdirSync9(root, { recursive: true, mode: 448 });
|
|
2405
|
-
const directory =
|
|
2406
|
-
if (Buffer.byteLength(
|
|
2420
|
+
const directory = join31(root, randomUUID7().slice(0, 16));
|
|
2421
|
+
if (Buffer.byteLength(join31(directory, "s")) > 100)
|
|
2407
2422
|
throw new Error("socket_path_too_long");
|
|
2408
2423
|
mkdirSync9(directory, { mode: 448 });
|
|
2409
|
-
mkdirSync9(
|
|
2410
|
-
privateJson(
|
|
2411
|
-
owner:
|
|
2424
|
+
mkdirSync9(join31(directory, "sessions"), { mode: 448 });
|
|
2425
|
+
privateJson(join31(directory, "harness.json"), {
|
|
2426
|
+
owner: randomUUID7(),
|
|
2412
2427
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2413
2428
|
});
|
|
2414
2429
|
writeFileSync10(
|
|
2415
|
-
|
|
2430
|
+
join31(directory, "tmux.conf"),
|
|
2416
2431
|
[
|
|
2417
2432
|
"set -g remain-on-exit on",
|
|
2418
2433
|
"set -g history-limit 2000",
|
|
@@ -2428,7 +2443,7 @@ var init_records = __esm({
|
|
|
2428
2443
|
}
|
|
2429
2444
|
path(id2) {
|
|
2430
2445
|
runtimeUnit(id2);
|
|
2431
|
-
return
|
|
2446
|
+
return join31(this.directory, "sessions", `${id2}.json`);
|
|
2432
2447
|
}
|
|
2433
2448
|
read(id2) {
|
|
2434
2449
|
const record = object8(readPrivateJson2(this.path(id2)));
|
|
@@ -2450,7 +2465,7 @@ var init_records = __esm({
|
|
|
2450
2465
|
return record;
|
|
2451
2466
|
}
|
|
2452
2467
|
all() {
|
|
2453
|
-
return readdirSync10(
|
|
2468
|
+
return readdirSync10(join31(this.directory, "sessions")).filter((name) => name.endsWith(".json")).map((name) => this.read(name.slice(0, -5)));
|
|
2454
2469
|
}
|
|
2455
2470
|
save(record) {
|
|
2456
2471
|
privateJson(this.path(record.id), record);
|
|
@@ -2532,8 +2547,8 @@ var init_launch_identity = __esm({
|
|
|
2532
2547
|
|
|
2533
2548
|
// src/harness/launch/journal.ts
|
|
2534
2549
|
import { createHash as createHash16 } from "crypto";
|
|
2535
|
-
import { existsSync as
|
|
2536
|
-
import { join as
|
|
2550
|
+
import { existsSync as existsSync26, readdirSync as readdirSync11 } from "fs";
|
|
2551
|
+
import { join as join32 } from "path";
|
|
2537
2552
|
function launchJournals(records2) {
|
|
2538
2553
|
return readdirSync11(records2.directory).filter((name) => name.endsWith(".intent.json")).map((name) => new LaunchJournal(records2, name.slice(0, -12)));
|
|
2539
2554
|
}
|
|
@@ -2541,7 +2556,7 @@ function pendingLaunches(records2) {
|
|
|
2541
2556
|
return launchJournals(records2).filter((journal) => {
|
|
2542
2557
|
const aborted = journal.abortRecord();
|
|
2543
2558
|
if (aborted) return aborted.state !== "stopped";
|
|
2544
|
-
if (!
|
|
2559
|
+
if (!existsSync26(journal.path("committed"))) return true;
|
|
2545
2560
|
journal.assertCommitted();
|
|
2546
2561
|
return false;
|
|
2547
2562
|
});
|
|
@@ -2563,12 +2578,10 @@ var init_journal = __esm({
|
|
|
2563
2578
|
this.id = id2;
|
|
2564
2579
|
runtimeUnit(id2);
|
|
2565
2580
|
}
|
|
2566
|
-
records;
|
|
2567
|
-
id;
|
|
2568
2581
|
path(kind2) {
|
|
2569
2582
|
if (!["intent", "proof", "grant", "committed", "aborted"].includes(kind2))
|
|
2570
2583
|
throw new Error("invalid_launch_artifact");
|
|
2571
|
-
return
|
|
2584
|
+
return join32(this.records.directory, `${this.id}.${kind2}.json`);
|
|
2572
2585
|
}
|
|
2573
2586
|
intent() {
|
|
2574
2587
|
const value = object8(readPrivateJson2(this.path("intent")));
|
|
@@ -2597,13 +2610,13 @@ var init_journal = __esm({
|
|
|
2597
2610
|
throw new Error("invalid_docker_bridge_digest");
|
|
2598
2611
|
if (!bridge && creation.bridgeSha256 !== void 0)
|
|
2599
2612
|
throw new Error("ungranted_docker_bridge_digest");
|
|
2600
|
-
const policy = new GroupStore(
|
|
2613
|
+
const policy = new GroupStore(join32(this.records.directory, "group.json")).state.group.dockerPolicy;
|
|
2601
2614
|
const profile = selectedProfile(
|
|
2602
2615
|
policy,
|
|
2603
2616
|
creation.privateProfile,
|
|
2604
2617
|
text(value.runtime, "runtime")
|
|
2605
2618
|
);
|
|
2606
|
-
const expectedState = profile?.directory ??
|
|
2619
|
+
const expectedState = profile?.directory ?? join32(
|
|
2607
2620
|
this.records.directory,
|
|
2608
2621
|
"containers",
|
|
2609
2622
|
text(value.nonce, "nonce"),
|
|
@@ -2649,7 +2662,7 @@ var init_journal = __esm({
|
|
|
2649
2662
|
}
|
|
2650
2663
|
assertRegistered() {
|
|
2651
2664
|
const intent = this.intent();
|
|
2652
|
-
const group = new GroupStore(
|
|
2665
|
+
const group = new GroupStore(join32(this.records.directory, "group.json")).state.group;
|
|
2653
2666
|
if (group.id !== intent.groupId || group.generation !== intent.generation)
|
|
2654
2667
|
throw new Error("launch_group_generation_mismatch");
|
|
2655
2668
|
if (intent.dockerCreation && JSON.stringify(intent.dockerCreation.egress) !== JSON.stringify(group.dockerPolicy?.egress))
|
|
@@ -2665,12 +2678,12 @@ var init_journal = __esm({
|
|
|
2665
2678
|
throw new Error("launch_record_mismatch");
|
|
2666
2679
|
}
|
|
2667
2680
|
authorize() {
|
|
2668
|
-
if (
|
|
2681
|
+
if (existsSync26(this.path("aborted"))) throw new Error("launch_was_aborted");
|
|
2669
2682
|
this.assertRegistered();
|
|
2670
2683
|
privateJson(this.path("grant"), this.grant());
|
|
2671
2684
|
}
|
|
2672
2685
|
assertGrant() {
|
|
2673
|
-
if (
|
|
2686
|
+
if (existsSync26(this.path("aborted"))) throw new Error("launch_was_aborted");
|
|
2674
2687
|
const grant = object8(readPrivateJson2(this.path("grant")));
|
|
2675
2688
|
if (JSON.stringify(grant) !== JSON.stringify(this.grant()))
|
|
2676
2689
|
throw new Error("launch_grant_mismatch");
|
|
@@ -2687,7 +2700,7 @@ var init_journal = __esm({
|
|
|
2687
2700
|
throw new Error("launch_commit_mismatch");
|
|
2688
2701
|
}
|
|
2689
2702
|
abortRecord() {
|
|
2690
|
-
if (!
|
|
2703
|
+
if (!existsSync26(this.path("aborted"))) return;
|
|
2691
2704
|
const value = object8(readPrivateJson2(this.path("aborted")));
|
|
2692
2705
|
if (value.id !== this.id || value.owner !== this.records.owner || !["stopping", "stopped"].includes(String(value.state)))
|
|
2693
2706
|
throw new Error("foreign_launch_abort");
|
|
@@ -2734,30 +2747,30 @@ var init_lifecycle = __esm({
|
|
|
2734
2747
|
// src/harness/adapters/docker/private-profile.ts
|
|
2735
2748
|
import { homedir as homedir8 } from "os";
|
|
2736
2749
|
import {
|
|
2737
|
-
closeSync as
|
|
2750
|
+
closeSync as closeSync5,
|
|
2738
2751
|
constants as constants4,
|
|
2739
|
-
existsSync as
|
|
2752
|
+
existsSync as existsSync27,
|
|
2740
2753
|
fstatSync as fstatSync3,
|
|
2741
|
-
lstatSync as
|
|
2742
|
-
openSync as
|
|
2743
|
-
realpathSync as
|
|
2754
|
+
lstatSync as lstatSync7,
|
|
2755
|
+
openSync as openSync5,
|
|
2756
|
+
realpathSync as realpathSync8,
|
|
2744
2757
|
statfsSync
|
|
2745
2758
|
} from "fs";
|
|
2746
|
-
import { join as
|
|
2759
|
+
import { join as join33, relative as relative11 } from "path";
|
|
2747
2760
|
function overlap(left, right) {
|
|
2748
2761
|
const scope = relative11(left, right);
|
|
2749
2762
|
return scope === "" || scope !== ".." && !scope.startsWith("../");
|
|
2750
2763
|
}
|
|
2751
2764
|
function profileIdentity(directory, projectRoot) {
|
|
2752
|
-
if (directory === "/" || directory ===
|
|
2765
|
+
if (directory === "/" || directory === realpathSync8(homedir8()) || overlap(directory, projectRoot) || overlap(projectRoot, directory) || realpathSync8(directory) !== directory)
|
|
2753
2766
|
throw new Error("private_profile_scope_not_admitted");
|
|
2754
|
-
const stat =
|
|
2767
|
+
const stat = lstatSync7(directory);
|
|
2755
2768
|
if (!stat.isDirectory() || stat.uid !== process.getuid?.() || (stat.mode & 511) !== 448)
|
|
2756
2769
|
throw new Error("unsafe_private_profile_directory");
|
|
2757
2770
|
if ([26985, 4283649346, 4266872130].includes(statfsSync(directory).type))
|
|
2758
2771
|
throw new Error("remote_private_profile_not_admitted");
|
|
2759
|
-
const lock =
|
|
2760
|
-
|
|
2772
|
+
const lock = openSync5(
|
|
2773
|
+
join33(directory, privateProfileLock),
|
|
2761
2774
|
constants4.O_RDONLY | constants4.O_NOFOLLOW
|
|
2762
2775
|
);
|
|
2763
2776
|
try {
|
|
@@ -2766,18 +2779,18 @@ function profileIdentity(directory, projectRoot) {
|
|
|
2766
2779
|
throw new Error("unsafe_private_profile_lock");
|
|
2767
2780
|
return `${stat.dev}:${stat.ino}:${value.dev}:${value.ino}`;
|
|
2768
2781
|
} finally {
|
|
2769
|
-
|
|
2782
|
+
closeSync5(lock);
|
|
2770
2783
|
}
|
|
2771
2784
|
}
|
|
2772
2785
|
function captureProfile(grant, projectRoot) {
|
|
2773
|
-
const directory =
|
|
2774
|
-
if (directory !== grant.directory || overlap(directory, projectRoot) || overlap(projectRoot, directory) || directory === "/" || directory ===
|
|
2786
|
+
const directory = realpathSync8(grant.directory);
|
|
2787
|
+
if (directory !== grant.directory || overlap(directory, projectRoot) || overlap(projectRoot, directory) || directory === "/" || directory === realpathSync8(homedir8()))
|
|
2775
2788
|
throw new Error("private_profile_scope_not_admitted");
|
|
2776
|
-
const stat =
|
|
2789
|
+
const stat = lstatSync7(directory);
|
|
2777
2790
|
if (!stat.isDirectory() || stat.uid !== process.getuid?.() || (stat.mode & 511) !== 448)
|
|
2778
2791
|
throw new Error("unsafe_private_profile_directory");
|
|
2779
|
-
const lock =
|
|
2780
|
-
if (!
|
|
2792
|
+
const lock = join33(directory, privateProfileLock);
|
|
2793
|
+
if (!existsSync27(lock)) closeSync5(openSync5(lock, "wx", 384));
|
|
2781
2794
|
const identity = profileIdentity(directory, projectRoot);
|
|
2782
2795
|
if (grant.identity && grant.identity !== identity)
|
|
2783
2796
|
throw new Error("private_profile_identity_changed");
|
|
@@ -2795,7 +2808,7 @@ function assertProfileAvailable(grant, projectRoot) {
|
|
|
2795
2808
|
"--nonblock",
|
|
2796
2809
|
"--conflict-exit-code",
|
|
2797
2810
|
"73",
|
|
2798
|
-
|
|
2811
|
+
join33(grant.directory, privateProfileLock),
|
|
2799
2812
|
"true"
|
|
2800
2813
|
]);
|
|
2801
2814
|
} catch (error) {
|
|
@@ -2815,7 +2828,7 @@ var init_private_profile = __esm({
|
|
|
2815
2828
|
|
|
2816
2829
|
// src/harness/adapters/docker/profile.ts
|
|
2817
2830
|
import { homedir as homedir9 } from "os";
|
|
2818
|
-
import { lstatSync as
|
|
2831
|
+
import { lstatSync as lstatSync8, realpathSync as realpathSync9 } from "fs";
|
|
2819
2832
|
import { relative as relative12 } from "path";
|
|
2820
2833
|
function assertLocalDocker() {
|
|
2821
2834
|
if (process.env.DOCKER_HOST)
|
|
@@ -2832,12 +2845,12 @@ function assertLocalDocker() {
|
|
|
2832
2845
|
throw new Error("remote_docker_context_not_admitted");
|
|
2833
2846
|
}
|
|
2834
2847
|
function loadDockerPolicy(path, projectRoot, runtimes) {
|
|
2835
|
-
if (projectRoot === "/" || projectRoot ===
|
|
2848
|
+
if (projectRoot === "/" || projectRoot === realpathSync9(homedir9()))
|
|
2836
2849
|
throw new Error("docker_project_root_too_broad");
|
|
2837
2850
|
const inputPath = text(path, "docker_policy_file");
|
|
2838
|
-
if (
|
|
2851
|
+
if (lstatSync8(inputPath).isSymbolicLink())
|
|
2839
2852
|
throw new Error("symlink_docker_policy_not_admitted");
|
|
2840
|
-
const source2 =
|
|
2853
|
+
const source2 = realpathSync9(inputPath);
|
|
2841
2854
|
const scope = relative12(projectRoot, source2);
|
|
2842
2855
|
if (scope === ".." || scope.startsWith("../"))
|
|
2843
2856
|
throw new Error("docker_policy_outside_project");
|
|
@@ -2871,16 +2884,16 @@ var init_profile = __esm({
|
|
|
2871
2884
|
});
|
|
2872
2885
|
|
|
2873
2886
|
// src/harness/adapters/docker/mounts.ts
|
|
2874
|
-
import { lstatSync as
|
|
2875
|
-
import { join as
|
|
2887
|
+
import { lstatSync as lstatSync9, realpathSync as realpathSync10 } from "fs";
|
|
2888
|
+
import { join as join34 } from "path";
|
|
2876
2889
|
function assertContainerMounts(actual, statePath, projectRoot, directory, nonce, policy, privateProfile, runtime2) {
|
|
2877
2890
|
const state = text(statePath, "container_state_directory");
|
|
2878
2891
|
const profile = selectedProfile(policy, privateProfile, runtime2 ?? "");
|
|
2879
2892
|
if (profile) assertProfileIdentity(profile, projectRoot);
|
|
2880
|
-
const expected = profile?.directory ??
|
|
2881
|
-
if (state !== expected ||
|
|
2893
|
+
const expected = profile?.directory ?? join34(directory, "containers", nonce, "profile");
|
|
2894
|
+
if (state !== expected || realpathSync10(state) !== expected)
|
|
2882
2895
|
throw new Error("foreign_container_state_directory");
|
|
2883
|
-
const stat =
|
|
2896
|
+
const stat = lstatSync9(state);
|
|
2884
2897
|
if (!stat.isDirectory() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
2885
2898
|
throw new Error("unsafe_container_state_directory");
|
|
2886
2899
|
if (!Array.isArray(actual.Mounts) || actual.Mounts.length !== 2)
|
|
@@ -2944,14 +2957,14 @@ function bridgeName(nonce) {
|
|
|
2944
2957
|
function containerEntrypoint(policy, runtime2, nonce, privateProfile) {
|
|
2945
2958
|
const native2 = policy.commands[runtime2];
|
|
2946
2959
|
if (!native2) throw new Error("docker_runtime_command_not_granted");
|
|
2947
|
-
const
|
|
2960
|
+
const command3 = policy.egress?.bridgeExecutable ? [
|
|
2948
2961
|
policy.egress.bridgeExecutable,
|
|
2949
2962
|
`/state/${bridgeName(nonce)}`,
|
|
2950
2963
|
"--",
|
|
2951
2964
|
...native2
|
|
2952
2965
|
] : native2;
|
|
2953
2966
|
const profile = selectedProfile(policy, privateProfile, runtime2);
|
|
2954
|
-
if (!profile) return
|
|
2967
|
+
if (!profile) return command3;
|
|
2955
2968
|
return [
|
|
2956
2969
|
profile.lockExecutable,
|
|
2957
2970
|
"--exclusive",
|
|
@@ -2960,7 +2973,7 @@ function containerEntrypoint(policy, runtime2, nonce, privateProfile) {
|
|
|
2960
2973
|
"--conflict-exit-code",
|
|
2961
2974
|
"73",
|
|
2962
2975
|
`/state/${privateProfileLock}`,
|
|
2963
|
-
...
|
|
2976
|
+
...command3
|
|
2964
2977
|
];
|
|
2965
2978
|
}
|
|
2966
2979
|
var init_entrypoint = __esm({
|
|
@@ -2972,17 +2985,17 @@ var init_entrypoint = __esm({
|
|
|
2972
2985
|
});
|
|
2973
2986
|
|
|
2974
2987
|
// src/harness/adapters/docker/admission.ts
|
|
2975
|
-
import { lstatSync as
|
|
2976
|
-
import { join as
|
|
2988
|
+
import { lstatSync as lstatSync10, realpathSync as realpathSync11 } from "fs";
|
|
2989
|
+
import { join as join35, relative as relative13 } from "path";
|
|
2977
2990
|
function admitResource(path, group, role, directory, memoryMb, runtime2) {
|
|
2978
2991
|
if (group.profile !== "docker" || !group.dockerPolicy)
|
|
2979
2992
|
throw new Error("container_requires_docker_profile");
|
|
2980
2993
|
const { projectRoot } = group;
|
|
2981
2994
|
assertLocalDocker();
|
|
2982
2995
|
const inputPath = text(path, "container_file");
|
|
2983
|
-
if (
|
|
2996
|
+
if (lstatSync10(inputPath).isSymbolicLink())
|
|
2984
2997
|
throw new Error("symlink_container_file_not_admitted");
|
|
2985
|
-
const source2 =
|
|
2998
|
+
const source2 = realpathSync11(inputPath);
|
|
2986
2999
|
const scope = relative13(projectRoot, source2);
|
|
2987
3000
|
if (scope === ".." || scope.startsWith("../"))
|
|
2988
3001
|
throw new Error("container_file_outside_project");
|
|
@@ -3029,7 +3042,7 @@ function admitClaim(value, group, role, directory, memoryMb, runtime2, cwd = gro
|
|
|
3029
3042
|
)
|
|
3030
3043
|
))
|
|
3031
3044
|
throw new Error("container_runtime_image_not_granted");
|
|
3032
|
-
if (config.WorkingDir !==
|
|
3045
|
+
if (config.WorkingDir !== join35("/workspace", relative13(projectRoot, cwd)))
|
|
3033
3046
|
throw new Error("container_cwd_not_granted");
|
|
3034
3047
|
assertContainerMounts(
|
|
3035
3048
|
actual,
|
|
@@ -3165,20 +3178,20 @@ var init_engine = __esm({
|
|
|
3165
3178
|
// src/harness/adapters/docker/bridge-artifact.ts
|
|
3166
3179
|
import { createHash as createHash17 } from "crypto";
|
|
3167
3180
|
import {
|
|
3168
|
-
closeSync as
|
|
3181
|
+
closeSync as closeSync6,
|
|
3169
3182
|
constants as constants5,
|
|
3170
3183
|
fstatSync as fstatSync4,
|
|
3171
|
-
fsyncSync as
|
|
3172
|
-
openSync as
|
|
3173
|
-
readFileSync as
|
|
3184
|
+
fsyncSync as fsyncSync3,
|
|
3185
|
+
openSync as openSync6,
|
|
3186
|
+
readFileSync as readFileSync36,
|
|
3174
3187
|
writeFileSync as writeFileSync11
|
|
3175
3188
|
} from "fs";
|
|
3176
|
-
import { join as
|
|
3189
|
+
import { join as join36 } from "path";
|
|
3177
3190
|
function source() {
|
|
3178
3191
|
const path = artifact("egress-bridge");
|
|
3179
3192
|
if (!path.endsWith(".js"))
|
|
3180
3193
|
throw new Error("docker_bridge_requires_compiled_cli");
|
|
3181
|
-
return
|
|
3194
|
+
return readFileSync36(path);
|
|
3182
3195
|
}
|
|
3183
3196
|
function digest(value) {
|
|
3184
3197
|
return createHash17("sha256").update(value).digest("hex");
|
|
@@ -3191,30 +3204,30 @@ function prepareBridge(creation) {
|
|
|
3191
3204
|
const content = source();
|
|
3192
3205
|
if (digest(content) !== creation.bridgeSha256)
|
|
3193
3206
|
throw new Error("docker_bridge_source_changed");
|
|
3194
|
-
const descriptor =
|
|
3195
|
-
|
|
3207
|
+
const descriptor = openSync6(
|
|
3208
|
+
join36(creation.state, bridgeName(creation.nonce)),
|
|
3196
3209
|
"wx",
|
|
3197
3210
|
384
|
|
3198
3211
|
);
|
|
3199
3212
|
try {
|
|
3200
3213
|
writeFileSync11(descriptor, content);
|
|
3201
|
-
|
|
3214
|
+
fsyncSync3(descriptor);
|
|
3202
3215
|
} finally {
|
|
3203
|
-
|
|
3216
|
+
closeSync6(descriptor);
|
|
3204
3217
|
}
|
|
3205
3218
|
}
|
|
3206
3219
|
function assertBridge(creation) {
|
|
3207
3220
|
if (!creation.egress?.bridgeExecutable) return;
|
|
3208
|
-
const descriptor =
|
|
3209
|
-
|
|
3221
|
+
const descriptor = openSync6(
|
|
3222
|
+
join36(creation.state, bridgeName(creation.nonce)),
|
|
3210
3223
|
constants5.O_RDONLY | constants5.O_NOFOLLOW
|
|
3211
3224
|
);
|
|
3212
3225
|
try {
|
|
3213
3226
|
const stat = fstatSync4(descriptor);
|
|
3214
|
-
if (!stat.isFile() || stat.uid !== process.getuid?.() || stat.nlink !== 1 || (stat.mode & 63) !== 0 || stat.size > 2097152 || digest(
|
|
3227
|
+
if (!stat.isFile() || stat.uid !== process.getuid?.() || stat.nlink !== 1 || (stat.mode & 63) !== 0 || stat.size > 2097152 || digest(readFileSync36(descriptor)) !== creation.bridgeSha256)
|
|
3215
3228
|
throw new Error("docker_bridge_artifact_changed");
|
|
3216
3229
|
} finally {
|
|
3217
|
-
|
|
3230
|
+
closeSync6(descriptor);
|
|
3218
3231
|
}
|
|
3219
3232
|
}
|
|
3220
3233
|
var init_bridge_artifact = __esm({
|
|
@@ -3226,7 +3239,7 @@ var init_bridge_artifact = __esm({
|
|
|
3226
3239
|
});
|
|
3227
3240
|
|
|
3228
3241
|
// src/harness/adapters/docker/creation-plan.ts
|
|
3229
|
-
import { join as
|
|
3242
|
+
import { join as join37, relative as relative14 } from "path";
|
|
3230
3243
|
function creationPlan(group, directory, id2, nonce, role, runtime2, cwd, preparation) {
|
|
3231
3244
|
if (group.profile !== "docker" || !group.dockerPolicy)
|
|
3232
3245
|
throw new Error("container_requires_docker_profile");
|
|
@@ -3243,7 +3256,7 @@ function creationPlan(group, directory, id2, nonce, role, runtime2, cwd, prepara
|
|
|
3243
3256
|
throw new Error("invalid_container_image_environment");
|
|
3244
3257
|
const profile = selectedProfile(policy, preparation.privateProfile, runtime2);
|
|
3245
3258
|
if (profile) assertProfileAvailable(profile, group.projectRoot);
|
|
3246
|
-
const state = profile?.directory ??
|
|
3259
|
+
const state = profile?.directory ?? join37(directory, "containers", nonce, "profile");
|
|
3247
3260
|
return {
|
|
3248
3261
|
name: containerName(group.owner, id2),
|
|
3249
3262
|
nonce,
|
|
@@ -3260,7 +3273,7 @@ function creationPlan(group, directory, id2, nonce, role, runtime2, cwd, prepara
|
|
|
3260
3273
|
preparation.privateProfile
|
|
3261
3274
|
),
|
|
3262
3275
|
Cmd: preparation.args.length ? preparation.args : null,
|
|
3263
|
-
WorkingDir:
|
|
3276
|
+
WorkingDir: join37("/workspace", relative14(group.projectRoot, cwd)),
|
|
3264
3277
|
User: `${process.getuid?.()}:${process.getgid?.()}`,
|
|
3265
3278
|
Env: [
|
|
3266
3279
|
...environment2.filter(
|
|
@@ -3359,12 +3372,12 @@ var init_creation_plan = __esm({
|
|
|
3359
3372
|
|
|
3360
3373
|
// src/harness/adapters/docker/creation.ts
|
|
3361
3374
|
import { mkdirSync as mkdirSync10 } from "fs";
|
|
3362
|
-
import { join as
|
|
3375
|
+
import { join as join38 } from "path";
|
|
3363
3376
|
async function prepareCreation(journal) {
|
|
3364
3377
|
const intent = journal.intent();
|
|
3365
3378
|
const creation = intent.dockerCreation;
|
|
3366
3379
|
if (!creation) throw new Error("container_creation_missing");
|
|
3367
|
-
const group = new GroupStore(
|
|
3380
|
+
const group = new GroupStore(join38(journal.records.directory, "group.json")).state.group;
|
|
3368
3381
|
const profile = selectedProfile(
|
|
3369
3382
|
group.dockerPolicy,
|
|
3370
3383
|
creation.privateProfile,
|
|
@@ -3378,7 +3391,7 @@ async function prepareCreation(journal) {
|
|
|
3378
3391
|
creation.name,
|
|
3379
3392
|
creation.configuration
|
|
3380
3393
|
);
|
|
3381
|
-
const current = new GroupStore(
|
|
3394
|
+
const current = new GroupStore(join38(journal.records.directory, "group.json")).state.group;
|
|
3382
3395
|
if (current.id !== intent.groupId || current.generation !== intent.generation)
|
|
3383
3396
|
throw new Error("launch_group_generation_mismatch");
|
|
3384
3397
|
const resource = {
|
|
@@ -3446,7 +3459,7 @@ function findCreation(journal) {
|
|
|
3446
3459
|
assertResourceOwner(resource, actual);
|
|
3447
3460
|
assertCreationConfiguration(actual, creation);
|
|
3448
3461
|
if (!intent.resource) {
|
|
3449
|
-
const group = new GroupStore(
|
|
3462
|
+
const group = new GroupStore(join38(journal.records.directory, "group.json")).state.group;
|
|
3450
3463
|
return {
|
|
3451
3464
|
...admitClaim(
|
|
3452
3465
|
{ ...resource, runtime: intent.runtime, state: creation.state },
|
|
@@ -3483,7 +3496,7 @@ var init_creation = __esm({
|
|
|
3483
3496
|
});
|
|
3484
3497
|
|
|
3485
3498
|
// src/harness/launch/recovery.ts
|
|
3486
|
-
import { existsSync as
|
|
3499
|
+
import { existsSync as existsSync28 } from "fs";
|
|
3487
3500
|
function abortLaunch(connection, journal) {
|
|
3488
3501
|
const intent = journal.intent();
|
|
3489
3502
|
if (!controllerExited(intent.controllerIdentity))
|
|
@@ -3500,7 +3513,7 @@ function abortLaunch(connection, journal) {
|
|
|
3500
3513
|
inputReplayed: false
|
|
3501
3514
|
};
|
|
3502
3515
|
}
|
|
3503
|
-
if (!
|
|
3516
|
+
if (!existsSync28(journal.path("proof"))) {
|
|
3504
3517
|
const paneUnknown = connection.alive() && (!intent.dockerCreation || connection.tmux(["list-sessions", "-F", "#{session_name}"]).trim().split("\n").includes(`pmcp-${intent.id}`));
|
|
3505
3518
|
if (paneUnknown || unitActive(intent.unit))
|
|
3506
3519
|
throw new Error("launch_identity_unknown");
|
|
@@ -3527,7 +3540,7 @@ function abortLaunch(connection, journal) {
|
|
|
3527
3540
|
assertProof(connection, proof);
|
|
3528
3541
|
connection.tmux(["kill-pane", "-t", proof.session.paneId]);
|
|
3529
3542
|
}
|
|
3530
|
-
if (
|
|
3543
|
+
if (existsSync28(journal.records.path(intent.id)))
|
|
3531
3544
|
journal.records.save({
|
|
3532
3545
|
...proof.session,
|
|
3533
3546
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3553,10 +3566,10 @@ var init_recovery = __esm({
|
|
|
3553
3566
|
});
|
|
3554
3567
|
|
|
3555
3568
|
// src/harness/adapters/tmux/lifecycle.ts
|
|
3556
|
-
import { randomUUID as
|
|
3557
|
-
import { existsSync as
|
|
3569
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
3570
|
+
import { existsSync as existsSync29 } from "fs";
|
|
3558
3571
|
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
3559
|
-
import { join as
|
|
3572
|
+
import { join as join39 } from "path";
|
|
3560
3573
|
async function start(connection, spec) {
|
|
3561
3574
|
const records2 = connection.records;
|
|
3562
3575
|
if (spec.budgetSlice !== budgetSlice(records2.owner))
|
|
@@ -3566,11 +3579,11 @@ async function start(connection, spec) {
|
|
|
3566
3579
|
runtimeUnit(spec.generation);
|
|
3567
3580
|
if (spec.role !== "main" && spec.role !== "worker")
|
|
3568
3581
|
throw new Error("invalid_launch_role");
|
|
3569
|
-
const id2 =
|
|
3570
|
-
const nonce =
|
|
3582
|
+
const id2 = randomUUID8();
|
|
3583
|
+
const nonce = randomUUID8();
|
|
3571
3584
|
const journal = new LaunchJournal(records2, id2);
|
|
3572
3585
|
const dockerCreation = spec.dockerPreparation ? creationPlan(
|
|
3573
|
-
new GroupStore(
|
|
3586
|
+
new GroupStore(join39(records2.directory, "group.json")).state.group,
|
|
3574
3587
|
records2.directory,
|
|
3575
3588
|
id2,
|
|
3576
3589
|
nonce,
|
|
@@ -3630,7 +3643,7 @@ async function start(connection, spec) {
|
|
|
3630
3643
|
`exec ${argv.map(quote2).join(" ")}`
|
|
3631
3644
|
]);
|
|
3632
3645
|
const deadline = Date.now() + 5e3;
|
|
3633
|
-
while (!
|
|
3646
|
+
while (!existsSync29(journal.path("proof"))) {
|
|
3634
3647
|
if (Date.now() >= deadline) throw new Error("launch_proof_timeout");
|
|
3635
3648
|
await setTimeout2(25);
|
|
3636
3649
|
}
|
|
@@ -3641,7 +3654,7 @@ async function start(connection, spec) {
|
|
|
3641
3654
|
}
|
|
3642
3655
|
function stop(connection, id2) {
|
|
3643
3656
|
const journal = new LaunchJournal(connection.records, id2);
|
|
3644
|
-
if (
|
|
3657
|
+
if (existsSync29(journal.path("intent"))) {
|
|
3645
3658
|
abortLaunch(connection, journal);
|
|
3646
3659
|
return;
|
|
3647
3660
|
}
|
|
@@ -3668,7 +3681,7 @@ function cleanup(connection) {
|
|
|
3668
3681
|
}
|
|
3669
3682
|
for (const record of records2) {
|
|
3670
3683
|
if (record.resource) stopResource(record.resource);
|
|
3671
|
-
if (record.stoppedAt || !
|
|
3684
|
+
if (record.stoppedAt || !existsSync29(
|
|
3672
3685
|
new LaunchJournal(connection.records, record.id).path("intent")
|
|
3673
3686
|
)) {
|
|
3674
3687
|
stopUnit(record.unit);
|
|
@@ -3698,9 +3711,9 @@ var init_lifecycle2 = __esm({
|
|
|
3698
3711
|
});
|
|
3699
3712
|
|
|
3700
3713
|
// src/harness/adapters/tmux/input.ts
|
|
3701
|
-
import { randomUUID as
|
|
3702
|
-
import { closeSync as
|
|
3703
|
-
import { join as
|
|
3714
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
3715
|
+
import { closeSync as closeSync7, openSync as openSync7, unlinkSync as unlinkSync3, writeFileSync as writeFileSync12 } from "fs";
|
|
3716
|
+
import { join as join40 } from "path";
|
|
3704
3717
|
function validateText(text3) {
|
|
3705
3718
|
if (typeof text3 !== "string" || Buffer.byteLength(text3) > 16384)
|
|
3706
3719
|
throw new Error("invalid_text");
|
|
@@ -3731,9 +3744,9 @@ function write(connection, id2, text3, submit) {
|
|
|
3731
3744
|
validateText(text3);
|
|
3732
3745
|
const session = connection.inspect(id2);
|
|
3733
3746
|
if (!session.alive) throw new Error("runtime_exited");
|
|
3734
|
-
const lock =
|
|
3735
|
-
const descriptor =
|
|
3736
|
-
const buffer = `pmcp-${
|
|
3747
|
+
const lock = join40(connection.records.directory, `${id2}.input.lock`);
|
|
3748
|
+
const descriptor = openSync7(lock, "wx", 384);
|
|
3749
|
+
const buffer = `pmcp-${randomUUID9()}`;
|
|
3737
3750
|
try {
|
|
3738
3751
|
writeFileSync12(
|
|
3739
3752
|
descriptor,
|
|
@@ -3773,7 +3786,7 @@ function write(connection, id2, text3, submit) {
|
|
|
3773
3786
|
connection.tmux(["delete-buffer", "-b", buffer]);
|
|
3774
3787
|
} catch {
|
|
3775
3788
|
}
|
|
3776
|
-
|
|
3789
|
+
closeSync7(descriptor);
|
|
3777
3790
|
unlinkSync3(lock);
|
|
3778
3791
|
}
|
|
3779
3792
|
}
|
|
@@ -3849,7 +3862,6 @@ var init_backend = __esm({
|
|
|
3849
3862
|
this.runDirectory = recordStore.directory;
|
|
3850
3863
|
this.metadataPath = recordStore.metadataPath;
|
|
3851
3864
|
}
|
|
3852
|
-
recordStore;
|
|
3853
3865
|
connection;
|
|
3854
3866
|
owner;
|
|
3855
3867
|
socket;
|
|
@@ -3890,10 +3902,10 @@ var init_backend = __esm({
|
|
|
3890
3902
|
});
|
|
3891
3903
|
|
|
3892
3904
|
// src/harness/adapters/systemd/budget.ts
|
|
3893
|
-
import { join as
|
|
3905
|
+
import { join as join41 } from "path";
|
|
3894
3906
|
async function budgetOperation(directory, operation) {
|
|
3895
3907
|
const backend = openBackend(directory);
|
|
3896
|
-
const path =
|
|
3908
|
+
const path = join41(directory, "runtime-policy.json");
|
|
3897
3909
|
const metadata2 = object8(readPrivateJson2(backend.metadataPath));
|
|
3898
3910
|
const slice = budgetSlice(backend.owner);
|
|
3899
3911
|
if (operation.action === "configure") {
|
|
@@ -3994,7 +4006,7 @@ var init_budget = __esm({
|
|
|
3994
4006
|
|
|
3995
4007
|
// src/harness/adapters/systemd/configure.ts
|
|
3996
4008
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3997
|
-
import { join as
|
|
4009
|
+
import { join as join42 } from "path";
|
|
3998
4010
|
function configureBudget(context, input) {
|
|
3999
4011
|
const { store, backend } = context;
|
|
4000
4012
|
assertOwnedRecords(backend);
|
|
@@ -4016,7 +4028,7 @@ function configureBudget(context, input) {
|
|
|
4016
4028
|
throw new Error("capacity_below_active_sessions");
|
|
4017
4029
|
if (active.reduce((total, session) => total + session.memoryMb, 0) > memoryMb)
|
|
4018
4030
|
throw new Error("budget_below_reserved_memory");
|
|
4019
|
-
const path =
|
|
4031
|
+
const path = join42(backend.runDirectory, "runtime-policy.json");
|
|
4020
4032
|
const policy = object8(readPrivateJson2(path));
|
|
4021
4033
|
const metadata2 = object8(readPrivateJson2(backend.metadataPath));
|
|
4022
4034
|
const slice = budgetSlice(backend.owner);
|
|
@@ -4054,11 +4066,11 @@ var init_configure = __esm({
|
|
|
4054
4066
|
|
|
4055
4067
|
// src/harness/adapters/process/executable.ts
|
|
4056
4068
|
import { accessSync, constants as constants6, statSync as statSync16 } from "fs";
|
|
4057
|
-
import { delimiter, resolve as
|
|
4069
|
+
import { delimiter, resolve as resolve18 } from "path";
|
|
4058
4070
|
function locateExecutable(binary, cwd = process.cwd()) {
|
|
4059
4071
|
for (const directory of (process.env.PATH ?? "").split(delimiter)) {
|
|
4060
4072
|
if (!directory) continue;
|
|
4061
|
-
const path =
|
|
4073
|
+
const path = resolve18(cwd, directory, binary);
|
|
4062
4074
|
try {
|
|
4063
4075
|
if (!statSync16(path).isFile()) continue;
|
|
4064
4076
|
accessSync(path, constants6.X_OK);
|
|
@@ -4110,11 +4122,11 @@ var init_admission2 = __esm({
|
|
|
4110
4122
|
});
|
|
4111
4123
|
|
|
4112
4124
|
// src/harness/groups/service.ts
|
|
4113
|
-
import { randomUUID as
|
|
4114
|
-
import { realpathSync as
|
|
4115
|
-
import { join as
|
|
4125
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
4126
|
+
import { realpathSync as realpathSync12, statSync as statSync17, unlinkSync as unlinkSync4 } from "fs";
|
|
4127
|
+
import { join as join43 } from "path";
|
|
4116
4128
|
async function createGroup(input) {
|
|
4117
|
-
const projectRoot =
|
|
4129
|
+
const projectRoot = realpathSync12(text(input.projectRoot, "project_root"));
|
|
4118
4130
|
if (!statSync17(projectRoot).isDirectory())
|
|
4119
4131
|
throw new Error("invalid_project_root");
|
|
4120
4132
|
const allowedRuntimes = strings2(input.allowedRuntimes, "allowed_runtimes");
|
|
@@ -4134,8 +4146,8 @@ async function createGroup(input) {
|
|
|
4134
4146
|
const group = {
|
|
4135
4147
|
schemaVersion: 1,
|
|
4136
4148
|
owner: backend.owner,
|
|
4137
|
-
id:
|
|
4138
|
-
generation:
|
|
4149
|
+
id: randomUUID10(),
|
|
4150
|
+
generation: randomUUID10(),
|
|
4139
4151
|
projectRoot,
|
|
4140
4152
|
allowedRuntimes,
|
|
4141
4153
|
profile,
|
|
@@ -4145,7 +4157,7 @@ async function createGroup(input) {
|
|
|
4145
4157
|
sessions: [],
|
|
4146
4158
|
...dockerPolicy2 ? { dockerPolicy: dockerPolicy2 } : {}
|
|
4147
4159
|
};
|
|
4148
|
-
const groupFile =
|
|
4160
|
+
const groupFile = join43(backend.runDirectory, "group.json");
|
|
4149
4161
|
privateJson(groupFile, { group, leases: [], deliveries: [] });
|
|
4150
4162
|
try {
|
|
4151
4163
|
await budgetOperation(backend.runDirectory, {
|
|
@@ -4158,7 +4170,7 @@ async function createGroup(input) {
|
|
|
4158
4170
|
await budgetOperation(backend.runDirectory, { action: "cleanup" });
|
|
4159
4171
|
unlinkSync4(groupFile);
|
|
4160
4172
|
} catch (cleanupError) {
|
|
4161
|
-
privateJson(
|
|
4173
|
+
privateJson(join43(backend.runDirectory, "initialization-failure.json"), {
|
|
4162
4174
|
error: String(error),
|
|
4163
4175
|
cleanupError: String(cleanupError),
|
|
4164
4176
|
recoveryRequired: true
|
|
@@ -4220,16 +4232,16 @@ var init_service2 = __esm({
|
|
|
4220
4232
|
});
|
|
4221
4233
|
|
|
4222
4234
|
// src/harness/groups/catalog.ts
|
|
4223
|
-
import { existsSync as
|
|
4224
|
-
import { join as
|
|
4235
|
+
import { existsSync as existsSync30, readdirSync as readdirSync12 } from "fs";
|
|
4236
|
+
import { join as join44 } from "path";
|
|
4225
4237
|
function listGroups() {
|
|
4226
4238
|
const directory = stateDirectory();
|
|
4227
|
-
if (!
|
|
4239
|
+
if (!existsSync30(directory)) return { groups: [] };
|
|
4228
4240
|
const groups2 = [];
|
|
4229
4241
|
for (const entry2 of readdirSync12(directory, { withFileTypes: true })) {
|
|
4230
4242
|
if (!entry2.isDirectory()) continue;
|
|
4231
|
-
const path =
|
|
4232
|
-
if (!
|
|
4243
|
+
const path = join44(directory, entry2.name, "group.json");
|
|
4244
|
+
if (!existsSync30(path)) continue;
|
|
4233
4245
|
try {
|
|
4234
4246
|
const store = new GroupStore(path);
|
|
4235
4247
|
groups2.push({ groupFile: store.path, group: store.state.group });
|
|
@@ -4288,10 +4300,10 @@ var init_service3 = __esm({
|
|
|
4288
4300
|
});
|
|
4289
4301
|
|
|
4290
4302
|
// src/harness/application/context.ts
|
|
4291
|
-
import { dirname as
|
|
4303
|
+
import { dirname as dirname23 } from "path";
|
|
4292
4304
|
function openGroup(path) {
|
|
4293
4305
|
const store = new GroupStore(path);
|
|
4294
|
-
const backend = openBackend(
|
|
4306
|
+
const backend = openBackend(dirname23(store.path));
|
|
4295
4307
|
if (store.state.group.owner !== backend.owner)
|
|
4296
4308
|
throw new Error("foreign_group");
|
|
4297
4309
|
return { store, backend };
|
|
@@ -4425,8 +4437,8 @@ var init_watch = __esm({
|
|
|
4425
4437
|
});
|
|
4426
4438
|
|
|
4427
4439
|
// src/harness/control/leases.ts
|
|
4428
|
-
import { randomUUID as
|
|
4429
|
-
import { join as
|
|
4440
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
4441
|
+
import { join as join45 } from "path";
|
|
4430
4442
|
function resolveLease(context, file2, allowExpired = false) {
|
|
4431
4443
|
const credential2 = object8(readPrivateJson2(file2));
|
|
4432
4444
|
const lease = context.store.state.leases.find(
|
|
@@ -4458,7 +4470,7 @@ function acquireControl(context, sessionId, controller, mode) {
|
|
|
4458
4470
|
if (mode !== "cli" && mode !== "native")
|
|
4459
4471
|
throw new Error("invalid_control_mode");
|
|
4460
4472
|
const lease = {
|
|
4461
|
-
token:
|
|
4473
|
+
token: randomUUID11(),
|
|
4462
4474
|
sessionId,
|
|
4463
4475
|
generation: store.state.group.generation,
|
|
4464
4476
|
controller: id(controller, "controller"),
|
|
@@ -4470,7 +4482,7 @@ function acquireControl(context, sessionId, controller, mode) {
|
|
|
4470
4482
|
);
|
|
4471
4483
|
store.state.leases.push(lease);
|
|
4472
4484
|
store.save();
|
|
4473
|
-
const leaseFile =
|
|
4485
|
+
const leaseFile = join45(backend.runDirectory, `lease-${randomUUID11()}.json`);
|
|
4474
4486
|
privateJson(leaseFile, { token: lease.token });
|
|
4475
4487
|
return { sessionId, controller, mode, expiresAt: lease.expiresAt, leaseFile };
|
|
4476
4488
|
}
|
|
@@ -4617,18 +4629,18 @@ var init_service5 = __esm({
|
|
|
4617
4629
|
});
|
|
4618
4630
|
|
|
4619
4631
|
// src/harness/adapters/process/recover-lock.ts
|
|
4620
|
-
import { existsSync as
|
|
4621
|
-
import { join as
|
|
4632
|
+
import { existsSync as existsSync31, lstatSync as lstatSync11, unlinkSync as unlinkSync5 } from "fs";
|
|
4633
|
+
import { join as join46 } from "path";
|
|
4622
4634
|
function recoverInputLock(backend, sessionId) {
|
|
4623
4635
|
backend.inspect(sessionId);
|
|
4624
|
-
const path =
|
|
4625
|
-
if (!
|
|
4626
|
-
const before =
|
|
4636
|
+
const path = join46(backend.runDirectory, `${sessionId}.input.lock`);
|
|
4637
|
+
if (!existsSync31(path)) return;
|
|
4638
|
+
const before = lstatSync11(path);
|
|
4627
4639
|
const record = object8(readPrivateJson2(path));
|
|
4628
4640
|
if (record.id !== sessionId) throw new Error("foreign_writer_lock");
|
|
4629
4641
|
if (!controllerExited(text(record.controller, "controller_identity")))
|
|
4630
4642
|
throw new Error("writer_still_alive");
|
|
4631
|
-
if (
|
|
4643
|
+
if (lstatSync11(path).ino !== before.ino)
|
|
4632
4644
|
throw new Error("writer_lock_changed");
|
|
4633
4645
|
unlinkSync5(path);
|
|
4634
4646
|
}
|
|
@@ -4642,7 +4654,7 @@ var init_recover_lock = __esm({
|
|
|
4642
4654
|
});
|
|
4643
4655
|
|
|
4644
4656
|
// src/harness/launch/service.ts
|
|
4645
|
-
import { existsSync as
|
|
4657
|
+
import { existsSync as existsSync32 } from "fs";
|
|
4646
4658
|
function assertLaunchAdmission(records2) {
|
|
4647
4659
|
for (const journal of launchJournals(records2)) {
|
|
4648
4660
|
if (journal.abortRecord()?.state === "stopped" && unitActive(journal.intent().unit))
|
|
@@ -4667,12 +4679,12 @@ function inspectPendingStarts(context) {
|
|
|
4667
4679
|
role: journal.intent().role,
|
|
4668
4680
|
runtime: journal.intent().runtime,
|
|
4669
4681
|
controllerExited: controllerExited(journal.intent().controllerIdentity),
|
|
4670
|
-
proofPresent:
|
|
4671
|
-
recordPresent:
|
|
4682
|
+
proofPresent: existsSync32(journal.path("proof")),
|
|
4683
|
+
recordPresent: existsSync32(journal.records.path(journal.id)),
|
|
4672
4684
|
registered: context.store.state.group.sessions.some(
|
|
4673
4685
|
(session) => session.id === journal.id
|
|
4674
4686
|
),
|
|
4675
|
-
authorizationPresent:
|
|
4687
|
+
authorizationPresent: existsSync32(journal.path("grant")),
|
|
4676
4688
|
processingConfirmed: false,
|
|
4677
4689
|
inputReplayed: false
|
|
4678
4690
|
}));
|
|
@@ -4690,7 +4702,7 @@ var init_service6 = __esm({
|
|
|
4690
4702
|
});
|
|
4691
4703
|
|
|
4692
4704
|
// src/harness/sessions/service.ts
|
|
4693
|
-
import { realpathSync as
|
|
4705
|
+
import { realpathSync as realpathSync13 } from "fs";
|
|
4694
4706
|
import { relative as relative15 } from "path";
|
|
4695
4707
|
async function startSession(context, input) {
|
|
4696
4708
|
const { store, backend } = context;
|
|
@@ -4724,7 +4736,7 @@ async function startSession(context, input) {
|
|
|
4724
4736
|
);
|
|
4725
4737
|
if (!backend.inspect(main2.id).alive) throw new Error("main_unavailable");
|
|
4726
4738
|
}
|
|
4727
|
-
const cwd =
|
|
4739
|
+
const cwd = realpathSync13(text(input.cwd ?? group.projectRoot, "cwd"));
|
|
4728
4740
|
const scope = relative15(group.projectRoot, cwd);
|
|
4729
4741
|
if (scope === ".." || scope.startsWith("../"))
|
|
4730
4742
|
throw new Error("cwd_outside_project");
|
|
@@ -4832,7 +4844,7 @@ var init_service7 = __esm({
|
|
|
4832
4844
|
});
|
|
4833
4845
|
|
|
4834
4846
|
// src/harness/recovery/service.ts
|
|
4835
|
-
import { randomUUID as
|
|
4847
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
4836
4848
|
function inspectRecovery(context, includeStarts = false) {
|
|
4837
4849
|
const pendingStarts = includeStarts ? inspectPendingStarts(context) : [];
|
|
4838
4850
|
return {
|
|
@@ -4915,7 +4927,7 @@ async function replaceMain(context, input) {
|
|
|
4915
4927
|
if (observation.alive) throw new Error("main_still_alive");
|
|
4916
4928
|
previous.stopped = true;
|
|
4917
4929
|
}
|
|
4918
|
-
store.state.group.generation =
|
|
4930
|
+
store.state.group.generation = randomUUID12();
|
|
4919
4931
|
store.state.leases = [];
|
|
4920
4932
|
store.save();
|
|
4921
4933
|
const result = await startSession(context, { ...input, role: "main" });
|
|
@@ -5187,9 +5199,9 @@ var init_invoke = __esm({
|
|
|
5187
5199
|
});
|
|
5188
5200
|
|
|
5189
5201
|
// src/tui/adapters/process/runtime.ts
|
|
5190
|
-
import { dirname as
|
|
5202
|
+
import { dirname as dirname24, join as join47 } from "path";
|
|
5191
5203
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5192
|
-
import { existsSync as
|
|
5204
|
+
import { existsSync as existsSync33, realpathSync as realpathSync14 } from "fs";
|
|
5193
5205
|
function dependencies() {
|
|
5194
5206
|
const tmux = locateExecutable("tmux");
|
|
5195
5207
|
const flock = locateExecutable("flock");
|
|
@@ -5222,10 +5234,10 @@ function runtime() {
|
|
|
5222
5234
|
if (!available.supported || !available.node)
|
|
5223
5235
|
throw new Error("tui_dependencies_unavailable");
|
|
5224
5236
|
const current = fileURLToPath4(import.meta.url);
|
|
5225
|
-
const directory =
|
|
5226
|
-
const bundled =
|
|
5227
|
-
if (!
|
|
5228
|
-
return { node:
|
|
5237
|
+
const directory = dirname24(current);
|
|
5238
|
+
const bundled = join47(directory, "tui-worker.js");
|
|
5239
|
+
if (!existsSync33(bundled)) throw new Error("tui_build_required");
|
|
5240
|
+
return { node: realpathSync14(available.node), worker: bundled };
|
|
5229
5241
|
}
|
|
5230
5242
|
var init_runtime2 = __esm({
|
|
5231
5243
|
"src/tui/adapters/process/runtime.ts"() {
|
|
@@ -5236,35 +5248,108 @@ var init_runtime2 = __esm({
|
|
|
5236
5248
|
}
|
|
5237
5249
|
});
|
|
5238
5250
|
|
|
5251
|
+
// src/tui/startup/diagnostics.ts
|
|
5252
|
+
function remediation(tools, userManagerAvailable) {
|
|
5253
|
+
const steps = [];
|
|
5254
|
+
if (tools.platform !== "linux")
|
|
5255
|
+
steps.push(
|
|
5256
|
+
"Use a qualified Linux host for the terminal workspace. Other PMCP CLI features do not require this host profile."
|
|
5257
|
+
);
|
|
5258
|
+
const missing = ["tmux", "flock", "systemctl", "systemdRun"].filter(
|
|
5259
|
+
(name) => !tools[name]
|
|
5260
|
+
);
|
|
5261
|
+
if (missing.length)
|
|
5262
|
+
steps.push(
|
|
5263
|
+
`Install the missing tmux, util-linux or systemd tools for your distribution and add them to PATH: ${missing.join(", ")}.`
|
|
5264
|
+
);
|
|
5265
|
+
if (!tools.nodeSupported)
|
|
5266
|
+
steps.push(
|
|
5267
|
+
"Select Node.js 22 or later through your existing version manager and check node --version in this terminal."
|
|
5268
|
+
);
|
|
5269
|
+
if (tools.supported && !userManagerAvailable)
|
|
5270
|
+
steps.push(
|
|
5271
|
+
"Check systemctl --user status in your login session and restore access to its user manager before launching. Doctor does not start or repair services."
|
|
5272
|
+
);
|
|
5273
|
+
return steps;
|
|
5274
|
+
}
|
|
5275
|
+
function inspectTerminalHost() {
|
|
5276
|
+
const tools = dependencies();
|
|
5277
|
+
let userManagerAvailable = false;
|
|
5278
|
+
let reason;
|
|
5279
|
+
try {
|
|
5280
|
+
if (!tools.supported) throw new Error("tui_dependencies_unavailable");
|
|
5281
|
+
assertNativeHost();
|
|
5282
|
+
userManagerAvailable = true;
|
|
5283
|
+
} catch (error) {
|
|
5284
|
+
reason = error instanceof Error ? error.message : "native_host_unavailable";
|
|
5285
|
+
}
|
|
5286
|
+
const runtimes = Object.entries(runtimeExecutables).map(
|
|
5287
|
+
([id2, executable]) => {
|
|
5288
|
+
const path = locateExecutable(executable);
|
|
5289
|
+
return {
|
|
5290
|
+
id: id2,
|
|
5291
|
+
executable,
|
|
5292
|
+
installed: Boolean(path),
|
|
5293
|
+
...path ? { path } : {},
|
|
5294
|
+
nativeAcceptance: "not-assessed",
|
|
5295
|
+
authentication: "not-inspected"
|
|
5296
|
+
};
|
|
5297
|
+
}
|
|
5298
|
+
);
|
|
5299
|
+
return {
|
|
5300
|
+
schemaVersion: 1,
|
|
5301
|
+
ok: true,
|
|
5302
|
+
...tools,
|
|
5303
|
+
supported: tools.supported && userManagerAvailable,
|
|
5304
|
+
userManagerAvailable,
|
|
5305
|
+
...reason ? { reason } : {},
|
|
5306
|
+
runtimes,
|
|
5307
|
+
qualification: "Prerequisite availability only; native behavior and cgroup delegation require separate qualification.",
|
|
5308
|
+
remediation: remediation(tools, userManagerAvailable),
|
|
5309
|
+
nextCommands: ["pmcp tui plan", "pmcp tui recovery"],
|
|
5310
|
+
providerInvoked: false,
|
|
5311
|
+
authenticationInspected: false
|
|
5312
|
+
};
|
|
5313
|
+
}
|
|
5314
|
+
var init_diagnostics = __esm({
|
|
5315
|
+
"src/tui/startup/diagnostics.ts"() {
|
|
5316
|
+
"use strict";
|
|
5317
|
+
init_runtime2();
|
|
5318
|
+
init_admission2();
|
|
5319
|
+
init_registry();
|
|
5320
|
+
init_executable();
|
|
5321
|
+
}
|
|
5322
|
+
});
|
|
5323
|
+
|
|
5239
5324
|
// src/tui/state/store.ts
|
|
5240
|
-
import { randomUUID as
|
|
5325
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
5241
5326
|
import {
|
|
5242
|
-
existsSync as
|
|
5243
|
-
lstatSync as
|
|
5327
|
+
existsSync as existsSync34,
|
|
5328
|
+
lstatSync as lstatSync12,
|
|
5244
5329
|
mkdirSync as mkdirSync11,
|
|
5245
|
-
realpathSync as
|
|
5330
|
+
realpathSync as realpathSync15,
|
|
5246
5331
|
unlinkSync as unlinkSync6,
|
|
5247
5332
|
writeFileSync as writeFileSync13
|
|
5248
5333
|
} from "fs";
|
|
5249
5334
|
import { homedir as homedir10 } from "os";
|
|
5250
|
-
import { dirname as
|
|
5335
|
+
import { dirname as dirname25, join as join48 } from "path";
|
|
5251
5336
|
function createDirectory() {
|
|
5252
|
-
const root =
|
|
5253
|
-
process.env.XDG_STATE_HOME ??
|
|
5337
|
+
const root = join48(
|
|
5338
|
+
process.env.XDG_STATE_HOME ?? join48(homedir10(), ".local/state"),
|
|
5254
5339
|
"pmcp/tui"
|
|
5255
5340
|
);
|
|
5256
5341
|
mkdirSync11(root, { recursive: true, mode: 448 });
|
|
5257
|
-
const directory =
|
|
5258
|
-
if (Buffer.byteLength(
|
|
5342
|
+
const directory = join48(realpathSync15(root), randomUUID13().slice(0, 16));
|
|
5343
|
+
if (Buffer.byteLength(join48(directory, "s")) > 100)
|
|
5259
5344
|
throw new Error("socket_path_too_long");
|
|
5260
5345
|
mkdirSync11(directory, { mode: 448 });
|
|
5261
|
-
const owner =
|
|
5262
|
-
privateJson(
|
|
5346
|
+
const owner = randomUUID13();
|
|
5347
|
+
privateJson(join48(directory, "owner.json"), {
|
|
5263
5348
|
owner,
|
|
5264
5349
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5265
5350
|
});
|
|
5266
5351
|
writeFileSync13(
|
|
5267
|
-
|
|
5352
|
+
join48(directory, "tmux.conf"),
|
|
5268
5353
|
[
|
|
5269
5354
|
"set -g remain-on-exit on",
|
|
5270
5355
|
"set -g history-limit 2000",
|
|
@@ -5279,13 +5364,13 @@ function createDirectory() {
|
|
|
5279
5364
|
return { directory, owner };
|
|
5280
5365
|
}
|
|
5281
5366
|
function readView(path) {
|
|
5282
|
-
const file2 =
|
|
5283
|
-
const directory =
|
|
5284
|
-
const stat =
|
|
5367
|
+
const file2 = realpathSync15(path);
|
|
5368
|
+
const directory = dirname25(file2);
|
|
5369
|
+
const stat = lstatSync12(directory);
|
|
5285
5370
|
if (!stat.isDirectory() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
5286
5371
|
throw new Error("unsafe_view_directory");
|
|
5287
5372
|
const value = object8(readPrivateJson2(file2));
|
|
5288
|
-
const metadata2 = object8(readPrivateJson2(
|
|
5373
|
+
const metadata2 = object8(readPrivateJson2(join48(directory, "owner.json")));
|
|
5289
5374
|
if (value.schemaVersion !== 1 || value.directory !== directory || value.owner !== metadata2.owner)
|
|
5290
5375
|
throw new Error("foreign_view_owner");
|
|
5291
5376
|
for (const key of [
|
|
@@ -5322,8 +5407,8 @@ function readView(path) {
|
|
|
5322
5407
|
throw new Error("invalid_view_identity");
|
|
5323
5408
|
}
|
|
5324
5409
|
const view = value;
|
|
5325
|
-
const fallback =
|
|
5326
|
-
if (
|
|
5410
|
+
const fallback = join48(directory, "callback-failure.json");
|
|
5411
|
+
if (existsSync34(fallback)) {
|
|
5327
5412
|
const recorded = object8(readPrivateJson2(fallback));
|
|
5328
5413
|
const failure2 = object8(recorded.failure);
|
|
5329
5414
|
if (recorded.owner !== view.owner || typeof failure2.code !== "string" || !/^[a-z][a-z0-9_]{0,79}$/.test(failure2.code))
|
|
@@ -5334,8 +5419,8 @@ function readView(path) {
|
|
|
5334
5419
|
return { file: file2, view };
|
|
5335
5420
|
}
|
|
5336
5421
|
function dismissFailure(file2, view) {
|
|
5337
|
-
const fallback =
|
|
5338
|
-
if (
|
|
5422
|
+
const fallback = join48(view.directory, "callback-failure.json");
|
|
5423
|
+
if (existsSync34(fallback)) unlinkSync6(fallback);
|
|
5339
5424
|
delete view.failure;
|
|
5340
5425
|
saveView(file2, view);
|
|
5341
5426
|
}
|
|
@@ -5397,7 +5482,7 @@ var init_lock = __esm({
|
|
|
5397
5482
|
});
|
|
5398
5483
|
|
|
5399
5484
|
// src/tui/adapters/process/budget.ts
|
|
5400
|
-
import { readFileSync as
|
|
5485
|
+
import { readFileSync as readFileSync37 } from "fs";
|
|
5401
5486
|
function viewUnit(owner) {
|
|
5402
5487
|
if (!/^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(owner))
|
|
5403
5488
|
throw new Error("invalid_view_owner");
|
|
@@ -5425,7 +5510,7 @@ function startPresentation(owner, args, env) {
|
|
|
5425
5510
|
);
|
|
5426
5511
|
}
|
|
5427
5512
|
function assertPresentationBudget(owner, pid) {
|
|
5428
|
-
if (!
|
|
5513
|
+
if (!readFileSync37(`/proc/${pid}/cgroup`, "utf8").includes(
|
|
5429
5514
|
`/${viewUnit(owner)}
|
|
5430
5515
|
`
|
|
5431
5516
|
))
|
|
@@ -5440,7 +5525,7 @@ var init_budget2 = __esm({
|
|
|
5440
5525
|
|
|
5441
5526
|
// src/tui/adapters/tmux/connection.ts
|
|
5442
5527
|
import { spawn as spawn3 } from "child_process";
|
|
5443
|
-
import { join as
|
|
5528
|
+
import { join as join49 } from "path";
|
|
5444
5529
|
var ViewConnection;
|
|
5445
5530
|
var init_connection2 = __esm({
|
|
5446
5531
|
"src/tui/adapters/tmux/connection.ts"() {
|
|
@@ -5453,11 +5538,8 @@ var init_connection2 = __esm({
|
|
|
5453
5538
|
this.directory = directory;
|
|
5454
5539
|
this.owner = owner;
|
|
5455
5540
|
this.identity = identity;
|
|
5456
|
-
this.socket =
|
|
5541
|
+
this.socket = join49(directory, "s");
|
|
5457
5542
|
}
|
|
5458
|
-
directory;
|
|
5459
|
-
owner;
|
|
5460
|
-
identity;
|
|
5461
5543
|
socket;
|
|
5462
5544
|
raw(args) {
|
|
5463
5545
|
const env = { ...process.env };
|
|
@@ -5466,14 +5548,14 @@ var init_connection2 = __esm({
|
|
|
5466
5548
|
"-S",
|
|
5467
5549
|
this.socket,
|
|
5468
5550
|
"-f",
|
|
5469
|
-
|
|
5551
|
+
join49(this.directory, "tmux.conf"),
|
|
5470
5552
|
...args
|
|
5471
5553
|
];
|
|
5472
5554
|
if (args[0] === "new-session" && !this.identity)
|
|
5473
5555
|
return startPresentation(this.owner, options2, env).trim();
|
|
5474
5556
|
return command(
|
|
5475
5557
|
"tmux",
|
|
5476
|
-
["-S", this.socket, "-f",
|
|
5558
|
+
["-S", this.socket, "-f", join49(this.directory, "tmux.conf"), ...args],
|
|
5477
5559
|
{ env }
|
|
5478
5560
|
).trim();
|
|
5479
5561
|
}
|
|
@@ -5537,7 +5619,7 @@ var init_connection2 = __esm({
|
|
|
5537
5619
|
});
|
|
5538
5620
|
|
|
5539
5621
|
// src/tui/application/feedback.ts
|
|
5540
|
-
import { join as
|
|
5622
|
+
import { join as join50 } from "path";
|
|
5541
5623
|
function describeFailure(action, error) {
|
|
5542
5624
|
const message = error instanceof Error ? error.message : "view_action_failed";
|
|
5543
5625
|
const code = error instanceof RangeError ? "terminal_size_invalid" : failureCode(message);
|
|
@@ -5574,6 +5656,19 @@ var init_feedback = __esm({
|
|
|
5574
5656
|
init_store3();
|
|
5575
5657
|
init_store2();
|
|
5576
5658
|
hints = {
|
|
5659
|
+
startup_requires_recovery: "Run pmcp tui recovery for saved ownership and exact inspection commands. No runtime was restarted.",
|
|
5660
|
+
startup_group_paused: "The saved group is paused. Resume it explicitly through the harness CLI before reopening.",
|
|
5661
|
+
startup_main_unavailable: "Inspect the saved main and recovery state. Stop the old group explicitly before using pmcp tui --new.",
|
|
5662
|
+
startup_busy: "Another workspace launch is running. Wait and retry.",
|
|
5663
|
+
startup_runtime_conflict: "The saved main uses another runtime. Stop the old group before creating a new workspace.",
|
|
5664
|
+
workspace_already_running: "Reopen the existing workspace, or stop its exact owned group before using --new.",
|
|
5665
|
+
runtime_executable_unavailable: "Install the selected native CLI on PATH, or choose an installed runtime with --runtime.",
|
|
5666
|
+
view_client_unavailable: "Open the view before using the worker menu, or run pmcp tui start-worker with --view and --runtime.",
|
|
5667
|
+
runtime_not_granted: "Choose a runtime granted by this owned group. Inspect the group before creating another workspace.",
|
|
5668
|
+
runtime_capacity_exceeded: "Stop an exact owned worker before adding another, or use the main session alone.",
|
|
5669
|
+
aggregate_memory_budget_exceeded: "Use fewer sessions or start a worker with an explicit smaller --memory-mb reservation.",
|
|
5670
|
+
group_not_ready: "Inspect the group and resume a paused group explicitly before adding a worker.",
|
|
5671
|
+
startup_group_not_ready: "Inspect the group and resume a paused group explicitly before adding a worker.",
|
|
5577
5672
|
control_connection_lost: "The native writer or lease disappeared. Inspect and release control before reconnecting.",
|
|
5578
5673
|
control_busy: "Another controller holds the lease. Release it explicitly before retrying.",
|
|
5579
5674
|
native_writer_active: "A native writer is still attached. Detach that writer before retrying.",
|
|
@@ -5594,6 +5689,20 @@ var init_feedback = __esm({
|
|
|
5594
5689
|
}
|
|
5595
5690
|
});
|
|
5596
5691
|
|
|
5692
|
+
// src/tui/presentation/target.ts
|
|
5693
|
+
function targetStatus(view) {
|
|
5694
|
+
const role = view.target === view.mainId ? "MAIN" : "WORKER";
|
|
5695
|
+
const phase2 = view.phase === "read-only" ? "OBSERVE" : phaseLabel(view.phase);
|
|
5696
|
+
const input = view.phase === "controlled" ? "INPUT ON" : "INPUT OFF";
|
|
5697
|
+
return `${role} ${view.target.slice(0, 8)} | ${phase2} | ${input}`;
|
|
5698
|
+
}
|
|
5699
|
+
var init_target = __esm({
|
|
5700
|
+
"src/tui/presentation/target.ts"() {
|
|
5701
|
+
"use strict";
|
|
5702
|
+
init_feedback();
|
|
5703
|
+
}
|
|
5704
|
+
});
|
|
5705
|
+
|
|
5597
5706
|
// src/tui/adapters/tmux/bindings.ts
|
|
5598
5707
|
function callback(view, file2, action, args = []) {
|
|
5599
5708
|
return [
|
|
@@ -5620,7 +5729,8 @@ function bindKeys(connection, view, file2) {
|
|
|
5620
5729
|
["b", "mailbox"],
|
|
5621
5730
|
["d", "detach"],
|
|
5622
5731
|
["!", "dismiss"],
|
|
5623
|
-
["g", "management"]
|
|
5732
|
+
["g", "management"],
|
|
5733
|
+
["n", "worker-menu"]
|
|
5624
5734
|
])
|
|
5625
5735
|
connection.command([
|
|
5626
5736
|
"bind-key",
|
|
@@ -5641,7 +5751,7 @@ function bindKeys(connection, view, file2) {
|
|
|
5641
5751
|
"bind-key",
|
|
5642
5752
|
"?",
|
|
5643
5753
|
"display-message",
|
|
5644
|
-
"m main; w/1-4 worker; c control; r release; h history; b mailbox; d detach; ! dismiss error; double Ctrl-g forwards native Ctrl-g"
|
|
5754
|
+
"m main; n add worker; w/1-4 worker; c control; r release; h history; b mailbox; d detach; ! dismiss error; double Ctrl-g forwards native Ctrl-g"
|
|
5645
5755
|
]);
|
|
5646
5756
|
connection.command([
|
|
5647
5757
|
"set-hook",
|
|
@@ -5669,7 +5779,7 @@ var init_view = __esm({
|
|
|
5669
5779
|
"src/tui/adapters/tmux/view.ts"() {
|
|
5670
5780
|
"use strict";
|
|
5671
5781
|
init_command();
|
|
5672
|
-
|
|
5782
|
+
init_target();
|
|
5673
5783
|
init_connection2();
|
|
5674
5784
|
init_bindings();
|
|
5675
5785
|
TmuxView = class {
|
|
@@ -5682,8 +5792,6 @@ var init_view = __esm({
|
|
|
5682
5792
|
view.serverIdentity
|
|
5683
5793
|
);
|
|
5684
5794
|
}
|
|
5685
|
-
view;
|
|
5686
|
-
stateFile;
|
|
5687
5795
|
connection;
|
|
5688
5796
|
shell(argv) {
|
|
5689
5797
|
return `exec ${argv.map(quote2).join(" ")}`;
|
|
@@ -5855,7 +5963,7 @@ var init_view = __esm({
|
|
|
5855
5963
|
"set-option",
|
|
5856
5964
|
"-g",
|
|
5857
5965
|
"status-right",
|
|
5858
|
-
`${
|
|
5966
|
+
`${targetStatus(view)}${failure2} | Ctrl-g ?`
|
|
5859
5967
|
]);
|
|
5860
5968
|
}
|
|
5861
5969
|
input(pane, enabled) {
|
|
@@ -5878,6 +5986,32 @@ var init_view = __esm({
|
|
|
5878
5986
|
clients() {
|
|
5879
5987
|
return this.connection.command(["list-clients", "-F", "#{client_pid}"]);
|
|
5880
5988
|
}
|
|
5989
|
+
workerMenu(view) {
|
|
5990
|
+
const pane = view.workerPane ?? view.mainPane;
|
|
5991
|
+
this.connection.assertOwned(pane);
|
|
5992
|
+
const popup = [
|
|
5993
|
+
"tmux",
|
|
5994
|
+
"-S",
|
|
5995
|
+
this.connection.socket,
|
|
5996
|
+
"display-popup",
|
|
5997
|
+
"-E",
|
|
5998
|
+
"-t",
|
|
5999
|
+
pane,
|
|
6000
|
+
"-w",
|
|
6001
|
+
"90%",
|
|
6002
|
+
"-h",
|
|
6003
|
+
"80%",
|
|
6004
|
+
this.shell([
|
|
6005
|
+
view.node,
|
|
6006
|
+
view.worker,
|
|
6007
|
+
"worker-menu",
|
|
6008
|
+
"--view",
|
|
6009
|
+
this.stateFile,
|
|
6010
|
+
"--callback"
|
|
6011
|
+
])
|
|
6012
|
+
];
|
|
6013
|
+
this.connection.command(["run-shell", "-b", popup.map(quote2).join(" ")]);
|
|
6014
|
+
}
|
|
5881
6015
|
copy(pane) {
|
|
5882
6016
|
this.connection.assertOwned(pane);
|
|
5883
6017
|
this.connection.command(["copy-mode", "-t", pane]);
|
|
@@ -5901,7 +6035,6 @@ var init_common = __esm({
|
|
|
5901
6035
|
constructor(view) {
|
|
5902
6036
|
this.view = view;
|
|
5903
6037
|
}
|
|
5904
|
-
view;
|
|
5905
6038
|
async call(family, action, input = {}) {
|
|
5906
6039
|
const group = new GroupStore(this.view.groupFile).state.group;
|
|
5907
6040
|
if (group.owner !== this.view.groupOwner) throw new Error("foreign_group");
|
|
@@ -5935,16 +6068,16 @@ var init_common = __esm({
|
|
|
5935
6068
|
});
|
|
5936
6069
|
|
|
5937
6070
|
// src/tui/application/create.ts
|
|
5938
|
-
import { join as
|
|
5939
|
-
import { realpathSync as
|
|
6071
|
+
import { join as join51 } from "path";
|
|
6072
|
+
import { realpathSync as realpathSync16 } from "fs";
|
|
5940
6073
|
async function createView(input) {
|
|
5941
6074
|
const plan2 = planLayout(input.columns, input.rows);
|
|
5942
6075
|
const helpers = runtime();
|
|
5943
6076
|
const mailbox = input.mailbox ? {
|
|
5944
|
-
config:
|
|
5945
|
-
actorFile:
|
|
6077
|
+
config: realpathSync16(input.mailbox.config),
|
|
6078
|
+
actorFile: realpathSync16(input.mailbox.actorFile)
|
|
5946
6079
|
} : void 0;
|
|
5947
|
-
const groupFile =
|
|
6080
|
+
const groupFile = realpathSync16(input.groupFile);
|
|
5948
6081
|
const group = new GroupStore(groupFile).state.group;
|
|
5949
6082
|
const common2 = new CommonHarness({
|
|
5950
6083
|
groupFile,
|
|
@@ -5957,7 +6090,7 @@ async function createView(input) {
|
|
|
5957
6090
|
);
|
|
5958
6091
|
if (!main2) throw new Error("view_requires_live_main");
|
|
5959
6092
|
const { directory, owner } = createDirectory();
|
|
5960
|
-
const file2 =
|
|
6093
|
+
const file2 = join51(directory, "view.json");
|
|
5961
6094
|
const connection = new ViewConnection(directory, owner);
|
|
5962
6095
|
const argv = [helpers.node, helpers.worker, "attach", file2, main2.id];
|
|
5963
6096
|
const result = connection.raw([
|
|
@@ -6185,7 +6318,7 @@ var init_navigation = __esm({
|
|
|
6185
6318
|
});
|
|
6186
6319
|
|
|
6187
6320
|
// src/tui/application/history.ts
|
|
6188
|
-
import { join as
|
|
6321
|
+
import { join as join52 } from "path";
|
|
6189
6322
|
async function observeHistory(context) {
|
|
6190
6323
|
const { view, renderer, common: common2, file: file2 } = context;
|
|
6191
6324
|
await releaseControl(context);
|
|
@@ -6193,7 +6326,7 @@ async function observeHistory(context) {
|
|
|
6193
6326
|
sessionId: view.target,
|
|
6194
6327
|
input: { lines: 2e3, maxBytes: 65536 }
|
|
6195
6328
|
});
|
|
6196
|
-
privateJson(
|
|
6329
|
+
privateJson(join52(view.directory, "history.json"), {
|
|
6197
6330
|
sessionId: view.target,
|
|
6198
6331
|
content: history.content,
|
|
6199
6332
|
truncated: history.truncated
|
|
@@ -6269,8 +6402,8 @@ var init_mailbox = __esm({
|
|
|
6269
6402
|
});
|
|
6270
6403
|
|
|
6271
6404
|
// src/tui/application/lifecycle.ts
|
|
6272
|
-
import { existsSync as
|
|
6273
|
-
import { join as
|
|
6405
|
+
import { existsSync as existsSync35, unlinkSync as unlinkSync7 } from "fs";
|
|
6406
|
+
import { join as join53 } from "path";
|
|
6274
6407
|
function resizeView({ view, renderer, input }) {
|
|
6275
6408
|
renderer.resize(view, planLayout(input.columns, input.rows));
|
|
6276
6409
|
}
|
|
@@ -6285,8 +6418,8 @@ async function stopTarget(context) {
|
|
|
6285
6418
|
async function closeView(context) {
|
|
6286
6419
|
await returnToMain(context);
|
|
6287
6420
|
context.renderer.connection.cleanup();
|
|
6288
|
-
const history =
|
|
6289
|
-
if (
|
|
6421
|
+
const history = join53(context.view.directory, "history.json");
|
|
6422
|
+
if (existsSync35(history)) unlinkSync7(history);
|
|
6290
6423
|
context.view.state = "closed";
|
|
6291
6424
|
saveView(context.file, context.view);
|
|
6292
6425
|
}
|
|
@@ -6440,6 +6573,11 @@ var init_operations = __esm({
|
|
|
6440
6573
|
close: closeView,
|
|
6441
6574
|
check: checkControl,
|
|
6442
6575
|
dismiss: ({ view, file: file2 }) => dismissFailure(file2, view),
|
|
6576
|
+
"worker-menu": async (context) => {
|
|
6577
|
+
if (!context.renderer.clients()) throw new Error("view_client_unavailable");
|
|
6578
|
+
await releaseControl(context);
|
|
6579
|
+
context.renderer.workerMenu(context.view);
|
|
6580
|
+
},
|
|
6443
6581
|
management: async (context) => {
|
|
6444
6582
|
await releaseControl(context);
|
|
6445
6583
|
if (context.view.managementWindow) {
|
|
@@ -6463,8 +6601,9 @@ __export(invoke_exports2, {
|
|
|
6463
6601
|
invokeTui: () => invokeTui
|
|
6464
6602
|
});
|
|
6465
6603
|
async function invokeTui(invocation2) {
|
|
6466
|
-
if (
|
|
6467
|
-
|
|
6604
|
+
if (["launch", "plan", "recovery", "start-worker"].includes(invocation2.action))
|
|
6605
|
+
throw new Error("startup_requires_cli_context");
|
|
6606
|
+
if (invocation2.action === "doctor") return inspectTerminalHost();
|
|
6468
6607
|
if (invocation2.action === "create") return createView(invocation2.create);
|
|
6469
6608
|
if (invocation2.action === "inspect")
|
|
6470
6609
|
return operate("inspect", invocation2.viewFile);
|
|
@@ -6502,7 +6641,7 @@ function failedInvocation(action, error) {
|
|
|
6502
6641
|
var init_invoke2 = __esm({
|
|
6503
6642
|
"src/tui/application/invoke.ts"() {
|
|
6504
6643
|
"use strict";
|
|
6505
|
-
|
|
6644
|
+
init_diagnostics();
|
|
6506
6645
|
init_lock();
|
|
6507
6646
|
init_create();
|
|
6508
6647
|
init_operations();
|
|
@@ -6511,6 +6650,543 @@ var init_invoke2 = __esm({
|
|
|
6511
6650
|
}
|
|
6512
6651
|
});
|
|
6513
6652
|
|
|
6653
|
+
// src/tui/application/worker-start.ts
|
|
6654
|
+
var worker_start_exports = {};
|
|
6655
|
+
__export(worker_start_exports, {
|
|
6656
|
+
startViewWorker: () => startViewWorker
|
|
6657
|
+
});
|
|
6658
|
+
async function startViewWorker(file2, runtime2, memoryMb) {
|
|
6659
|
+
const { view, common: common2 } = openView(file2);
|
|
6660
|
+
if (view.state !== "open") throw new Error("view_closed");
|
|
6661
|
+
const observed = await common2.snapshot();
|
|
6662
|
+
if (observed.group.state !== "ready") throw new Error("group_not_ready");
|
|
6663
|
+
const main2 = observed.sessions.find(
|
|
6664
|
+
(session) => session.id === view.mainId && session.role === "main"
|
|
6665
|
+
);
|
|
6666
|
+
if (!main2?.alive) throw new Error("startup_main_unavailable");
|
|
6667
|
+
return common2.call("session", "start", {
|
|
6668
|
+
input: { role: "worker", runtime: runtime2, memoryMb: memoryMb ?? main2.memoryMb }
|
|
6669
|
+
});
|
|
6670
|
+
}
|
|
6671
|
+
var init_worker_start = __esm({
|
|
6672
|
+
"src/tui/application/worker-start.ts"() {
|
|
6673
|
+
"use strict";
|
|
6674
|
+
init_context2();
|
|
6675
|
+
}
|
|
6676
|
+
});
|
|
6677
|
+
|
|
6678
|
+
// src/tui/startup/config.ts
|
|
6679
|
+
import { realpathSync as realpathSync17 } from "fs";
|
|
6680
|
+
import { resolve as resolve19 } from "path";
|
|
6681
|
+
function startupSettings(request3) {
|
|
6682
|
+
const path = request3.config ? resolve19(request3.cwd, request3.config) : findConfig(request3.cwd);
|
|
6683
|
+
const config = path ? readConfig(path) : void 0;
|
|
6684
|
+
const source2 = config?.raw.tui;
|
|
6685
|
+
const fail2 = (message) => {
|
|
6686
|
+
throw new ConfigError(path ?? "pmcp.toml", `[tui] ${message}`);
|
|
6687
|
+
};
|
|
6688
|
+
if (source2 !== void 0 && (source2 === null || typeof source2 !== "object" || Array.isArray(source2)))
|
|
6689
|
+
fail2("must be a table");
|
|
6690
|
+
const values = source2 ?? {};
|
|
6691
|
+
const allowed3 = /* @__PURE__ */ new Set([
|
|
6692
|
+
"runtime",
|
|
6693
|
+
"memory_mb",
|
|
6694
|
+
"session_memory_mb",
|
|
6695
|
+
"max_active",
|
|
6696
|
+
"group_file"
|
|
6697
|
+
]);
|
|
6698
|
+
for (const key of Object.keys(values)) {
|
|
6699
|
+
if (!allowed3.has(key)) fail2(`unknown key ${key}`);
|
|
6700
|
+
}
|
|
6701
|
+
const string = (key) => {
|
|
6702
|
+
const value = values[key];
|
|
6703
|
+
if (value === void 0) return void 0;
|
|
6704
|
+
if (typeof value !== "string" || !value.trim())
|
|
6705
|
+
fail2(`${key} must be a non-empty string`);
|
|
6706
|
+
return value;
|
|
6707
|
+
};
|
|
6708
|
+
const integer2 = (key, fallback, min, max) => {
|
|
6709
|
+
const value = values[key] ?? fallback;
|
|
6710
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value))
|
|
6711
|
+
fail2(`${key} must be an integer`);
|
|
6712
|
+
if (Number(value) < min || Number(value) > max)
|
|
6713
|
+
fail2(`${key} must be between ${min} and ${max}`);
|
|
6714
|
+
return Number(value);
|
|
6715
|
+
};
|
|
6716
|
+
const configuredRuntime = string("runtime");
|
|
6717
|
+
if (configuredRuntime !== void 0 && configuredRuntime !== "auto" && !Object.hasOwn(runtimeExecutables, configuredRuntime))
|
|
6718
|
+
fail2("runtime must be auto or a supported runtime ID");
|
|
6719
|
+
const available = preference.filter(
|
|
6720
|
+
(runtime3) => locateExecutable(runtimeExecutables[runtime3], request3.cwd)
|
|
6721
|
+
);
|
|
6722
|
+
let runtime2 = request3.runtime ?? configuredRuntime ?? "auto";
|
|
6723
|
+
if (runtime2 === "auto") runtime2 = available[0] ?? "codex-cli";
|
|
6724
|
+
if (!Object.hasOwn(runtimeExecutables, runtime2))
|
|
6725
|
+
fail2("runtime must be a supported runtime ID");
|
|
6726
|
+
const memoryMb = integer2("memory_mb", 2048, 128, 4096);
|
|
6727
|
+
const sessionMemoryMb = integer2("session_memory_mb", 768, 32, 1536);
|
|
6728
|
+
if (sessionMemoryMb > memoryMb)
|
|
6729
|
+
fail2("session memory exceeds the group budget");
|
|
6730
|
+
const groupFile = string("group_file");
|
|
6731
|
+
return {
|
|
6732
|
+
projectRoot: realpathSync17(config?.dir ?? request3.cwd),
|
|
6733
|
+
...config ? { configFile: config.path } : {},
|
|
6734
|
+
runtime: runtime2,
|
|
6735
|
+
allowedRuntimes: [.../* @__PURE__ */ new Set([...available, runtime2])],
|
|
6736
|
+
memoryMb,
|
|
6737
|
+
sessionMemoryMb,
|
|
6738
|
+
maxActive: integer2("max_active", 2, 1, 5),
|
|
6739
|
+
...groupFile ? { groupFile: resolve19(config?.dir ?? request3.cwd, groupFile) } : {}
|
|
6740
|
+
};
|
|
6741
|
+
}
|
|
6742
|
+
var preference;
|
|
6743
|
+
var init_config3 = __esm({
|
|
6744
|
+
"src/tui/startup/config.ts"() {
|
|
6745
|
+
"use strict";
|
|
6746
|
+
init_config();
|
|
6747
|
+
init_registry();
|
|
6748
|
+
init_executable();
|
|
6749
|
+
preference = [
|
|
6750
|
+
"codex-cli",
|
|
6751
|
+
"gemini-cli",
|
|
6752
|
+
"antigravity",
|
|
6753
|
+
"grok-cli",
|
|
6754
|
+
"claude-code"
|
|
6755
|
+
];
|
|
6756
|
+
}
|
|
6757
|
+
});
|
|
6758
|
+
|
|
6759
|
+
// src/tui/startup/state.ts
|
|
6760
|
+
import { createHash as createHash19 } from "crypto";
|
|
6761
|
+
import { existsSync as existsSync36, lstatSync as lstatSync13, mkdirSync as mkdirSync12 } from "fs";
|
|
6762
|
+
import { homedir as homedir11 } from "os";
|
|
6763
|
+
import { dirname as dirname26, join as join54 } from "path";
|
|
6764
|
+
function startupFile(projectRoot) {
|
|
6765
|
+
const root = join54(
|
|
6766
|
+
process.env.XDG_STATE_HOME ?? join54(homedir11(), ".local/state"),
|
|
6767
|
+
"pmcp/launcher"
|
|
6768
|
+
);
|
|
6769
|
+
const key = createHash19("sha256").update(projectRoot).digest("hex");
|
|
6770
|
+
return join54(root, `${key}.json`);
|
|
6771
|
+
}
|
|
6772
|
+
function ensureStartupDirectory(file2) {
|
|
6773
|
+
const directory = dirname26(file2);
|
|
6774
|
+
mkdirSync12(directory, { recursive: true, mode: 448 });
|
|
6775
|
+
const stat = lstatSync13(directory);
|
|
6776
|
+
if (!stat.isDirectory() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
6777
|
+
throw new Error("unsafe_startup_directory");
|
|
6778
|
+
}
|
|
6779
|
+
function readStartup(file2, projectRoot) {
|
|
6780
|
+
if (!existsSync36(file2)) return null;
|
|
6781
|
+
const value = object8(readPrivateJson2(file2));
|
|
6782
|
+
if (value.schemaVersion !== 1 || value.projectRoot !== projectRoot)
|
|
6783
|
+
throw new Error("foreign_startup_state");
|
|
6784
|
+
if (!["creating", "starting", "view-pending", "ready"].includes(
|
|
6785
|
+
String(value.phase)
|
|
6786
|
+
))
|
|
6787
|
+
throw new Error("invalid_startup_state");
|
|
6788
|
+
text(value.runtime, "runtime");
|
|
6789
|
+
if (value.phase !== "creating") {
|
|
6790
|
+
text(value.groupFile, "group_file");
|
|
6791
|
+
text(value.generation, "generation");
|
|
6792
|
+
}
|
|
6793
|
+
if (value.phase === "ready") text(value.viewFile, "view_file");
|
|
6794
|
+
return value;
|
|
6795
|
+
}
|
|
6796
|
+
var init_state = __esm({
|
|
6797
|
+
"src/tui/startup/state.ts"() {
|
|
6798
|
+
"use strict";
|
|
6799
|
+
init_store2();
|
|
6800
|
+
init_validation();
|
|
6801
|
+
}
|
|
6802
|
+
});
|
|
6803
|
+
|
|
6804
|
+
// src/tui/startup/worker.ts
|
|
6805
|
+
var worker_exports = {};
|
|
6806
|
+
__export(worker_exports, {
|
|
6807
|
+
startWorkspaceWorker: () => startWorkspaceWorker
|
|
6808
|
+
});
|
|
6809
|
+
async function startWorkspaceWorker(request3, runtime2, memoryMb) {
|
|
6810
|
+
if (!Object.hasOwn(runtimeExecutables, runtime2))
|
|
6811
|
+
throw new Error("unsupported_runtime");
|
|
6812
|
+
const settings2 = startupSettings({
|
|
6813
|
+
cwd: request3.cwd,
|
|
6814
|
+
config: request3.config
|
|
6815
|
+
});
|
|
6816
|
+
const state = readStartup(
|
|
6817
|
+
startupFile(settings2.projectRoot),
|
|
6818
|
+
settings2.projectRoot
|
|
6819
|
+
);
|
|
6820
|
+
if (!state || state.phase !== "ready" || !state.groupFile || !state.generation)
|
|
6821
|
+
throw new Error("startup_requires_recovery");
|
|
6822
|
+
const store = new GroupStore(state.groupFile);
|
|
6823
|
+
const group = store.state.group;
|
|
6824
|
+
if (group.projectRoot !== settings2.projectRoot)
|
|
6825
|
+
throw new Error("startup_project_mismatch");
|
|
6826
|
+
if (group.generation !== state.generation)
|
|
6827
|
+
throw new Error("stale_group_generation");
|
|
6828
|
+
if (group.state === "paused") throw new Error("startup_group_paused");
|
|
6829
|
+
if (group.state !== "ready") throw new Error("startup_group_not_ready");
|
|
6830
|
+
if (group.profile === "native" && !locateExecutable(runtimeExecutables[runtime2], request3.cwd))
|
|
6831
|
+
throw new Error("runtime_executable_unavailable");
|
|
6832
|
+
const observed = await invoke({
|
|
6833
|
+
family: "session",
|
|
6834
|
+
action: "list",
|
|
6835
|
+
groupFile: store.path
|
|
6836
|
+
});
|
|
6837
|
+
if (observed.ok !== true) return observed;
|
|
6838
|
+
const sessions = observed.sessions;
|
|
6839
|
+
if (!sessions.some((session) => session.role === "main" && session.alive))
|
|
6840
|
+
throw new Error("startup_main_unavailable");
|
|
6841
|
+
return invoke({
|
|
6842
|
+
family: "session",
|
|
6843
|
+
action: "start",
|
|
6844
|
+
groupFile: store.path,
|
|
6845
|
+
generation: group.generation,
|
|
6846
|
+
input: {
|
|
6847
|
+
runtime: runtime2,
|
|
6848
|
+
role: "worker",
|
|
6849
|
+
memoryMb: memoryMb ?? settings2.sessionMemoryMb
|
|
6850
|
+
}
|
|
6851
|
+
});
|
|
6852
|
+
}
|
|
6853
|
+
var init_worker = __esm({
|
|
6854
|
+
"src/tui/startup/worker.ts"() {
|
|
6855
|
+
"use strict";
|
|
6856
|
+
init_invoke();
|
|
6857
|
+
init_store2();
|
|
6858
|
+
init_registry();
|
|
6859
|
+
init_executable();
|
|
6860
|
+
init_config3();
|
|
6861
|
+
init_state();
|
|
6862
|
+
}
|
|
6863
|
+
});
|
|
6864
|
+
|
|
6865
|
+
// src/tui/startup/recovery.ts
|
|
6866
|
+
var recovery_exports = {};
|
|
6867
|
+
__export(recovery_exports, {
|
|
6868
|
+
inspectStartupRecovery: () => inspectStartupRecovery
|
|
6869
|
+
});
|
|
6870
|
+
function command2(args) {
|
|
6871
|
+
return ["pmcp", ...args].map(quote2).join(" ");
|
|
6872
|
+
}
|
|
6873
|
+
function inspectStartupRecovery(request3) {
|
|
6874
|
+
const settings2 = startupSettings(request3);
|
|
6875
|
+
const checkpoint = startupFile(settings2.projectRoot);
|
|
6876
|
+
const state = readStartup(checkpoint, settings2.projectRoot);
|
|
6877
|
+
const config = settings2.configFile ? ["--config", settings2.configFile] : [];
|
|
6878
|
+
const common2 = {
|
|
6879
|
+
schemaVersion: 1,
|
|
6880
|
+
ok: true,
|
|
6881
|
+
checkpoint,
|
|
6882
|
+
projectRoot: settings2.projectRoot,
|
|
6883
|
+
providerInvoked: false,
|
|
6884
|
+
runtimeRestarted: false,
|
|
6885
|
+
inputReplayed: false,
|
|
6886
|
+
observedNativeProcesses: false
|
|
6887
|
+
};
|
|
6888
|
+
if (!state)
|
|
6889
|
+
return {
|
|
6890
|
+
...common2,
|
|
6891
|
+
status: "not-started",
|
|
6892
|
+
hint: "No saved workspace exists. Start explicitly from an interactive terminal.",
|
|
6893
|
+
commands: [command2(["tui", ...config])]
|
|
6894
|
+
};
|
|
6895
|
+
if (!state.groupFile || !state.generation)
|
|
6896
|
+
return {
|
|
6897
|
+
...common2,
|
|
6898
|
+
status: "manual-inspection",
|
|
6899
|
+
phase: state.phase,
|
|
6900
|
+
hint: "Creation stopped before a group reference was saved. Preserve this checkpoint and inspect owned launch evidence before changing it. No group ownership can be inferred from a matching project path.",
|
|
6901
|
+
commands: []
|
|
6902
|
+
};
|
|
6903
|
+
const store = new GroupStore(state.groupFile);
|
|
6904
|
+
const group = store.state.group;
|
|
6905
|
+
if (group.projectRoot !== settings2.projectRoot)
|
|
6906
|
+
throw new Error("startup_project_mismatch");
|
|
6907
|
+
if (group.generation !== state.generation)
|
|
6908
|
+
throw new Error("stale_group_generation");
|
|
6909
|
+
const target2 = ["--group-file", store.path];
|
|
6910
|
+
const generation = ["--generation", group.generation];
|
|
6911
|
+
const inspect = command2([
|
|
6912
|
+
"harness",
|
|
6913
|
+
"recovery",
|
|
6914
|
+
"inspect",
|
|
6915
|
+
...target2,
|
|
6916
|
+
"--include-starts"
|
|
6917
|
+
]);
|
|
6918
|
+
const status = group.state === "ready" ? state.phase : group.state;
|
|
6919
|
+
const commands = [inspect];
|
|
6920
|
+
let hint = "Inspect actual processes and outstanding starts before any recovery action.";
|
|
6921
|
+
if (group.state === "paused") {
|
|
6922
|
+
hint = "The group is paused. Keep it paused until you explicitly decide to resume it.";
|
|
6923
|
+
commands.push(
|
|
6924
|
+
command2(["harness", "group", "resume", ...target2, ...generation])
|
|
6925
|
+
);
|
|
6926
|
+
}
|
|
6927
|
+
if (group.state === "stopped") {
|
|
6928
|
+
hint = "The saved group is stopped. An explicitly confirmed fresh launch can create a replacement.";
|
|
6929
|
+
commands.push(command2(["tui", "--new", ...config]));
|
|
6930
|
+
}
|
|
6931
|
+
if (group.state === "ready" && !["ready", "view-pending"].includes(state.phase)) {
|
|
6932
|
+
hint = "Startup is incomplete. Inspect pending starts; stop the exact owned group before an explicit fresh launch.";
|
|
6933
|
+
commands.push(
|
|
6934
|
+
command2(["harness", "group", "stop", ...target2, ...generation])
|
|
6935
|
+
);
|
|
6936
|
+
}
|
|
6937
|
+
return {
|
|
6938
|
+
...common2,
|
|
6939
|
+
status,
|
|
6940
|
+
phase: state.phase,
|
|
6941
|
+
groupFile: store.path,
|
|
6942
|
+
generation: group.generation,
|
|
6943
|
+
hint,
|
|
6944
|
+
commands,
|
|
6945
|
+
commandsRequireExplicitAction: true
|
|
6946
|
+
};
|
|
6947
|
+
}
|
|
6948
|
+
var init_recovery2 = __esm({
|
|
6949
|
+
"src/tui/startup/recovery.ts"() {
|
|
6950
|
+
"use strict";
|
|
6951
|
+
init_store2();
|
|
6952
|
+
init_command();
|
|
6953
|
+
init_config3();
|
|
6954
|
+
init_state();
|
|
6955
|
+
}
|
|
6956
|
+
});
|
|
6957
|
+
|
|
6958
|
+
// src/tui/startup/host.ts
|
|
6959
|
+
var host_exports = {};
|
|
6960
|
+
__export(host_exports, {
|
|
6961
|
+
requireTerminal: () => requireTerminal,
|
|
6962
|
+
terminalSupport: () => terminalSupport
|
|
6963
|
+
});
|
|
6964
|
+
function terminalSupport() {
|
|
6965
|
+
try {
|
|
6966
|
+
if (!dependencies().supported)
|
|
6967
|
+
throw new Error("tui_dependencies_unavailable");
|
|
6968
|
+
assertNativeHost();
|
|
6969
|
+
planLayout(process.stdout.columns ?? 0, process.stdout.rows ?? 0);
|
|
6970
|
+
return { supported: true };
|
|
6971
|
+
} catch (error) {
|
|
6972
|
+
return {
|
|
6973
|
+
supported: false,
|
|
6974
|
+
reason: error instanceof Error ? error.message : "terminal_unavailable"
|
|
6975
|
+
};
|
|
6976
|
+
}
|
|
6977
|
+
}
|
|
6978
|
+
function requireTerminal() {
|
|
6979
|
+
if (!interactiveEntry({
|
|
6980
|
+
stdin: process.stdin.isTTY === true,
|
|
6981
|
+
stdout: process.stdout.isTTY === true,
|
|
6982
|
+
term: process.env.TERM,
|
|
6983
|
+
ci: process.env.CI
|
|
6984
|
+
}))
|
|
6985
|
+
throw new Error("terminal_required");
|
|
6986
|
+
const support = terminalSupport();
|
|
6987
|
+
if (!support.supported) throw new Error(support.reason);
|
|
6988
|
+
}
|
|
6989
|
+
var init_host = __esm({
|
|
6990
|
+
"src/tui/startup/host.ts"() {
|
|
6991
|
+
"use strict";
|
|
6992
|
+
init_entry();
|
|
6993
|
+
init_layout();
|
|
6994
|
+
init_admission2();
|
|
6995
|
+
init_runtime2();
|
|
6996
|
+
}
|
|
6997
|
+
});
|
|
6998
|
+
|
|
6999
|
+
// src/tui/startup/lock.ts
|
|
7000
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
7001
|
+
import { closeSync as closeSync8, constants as constants7, fstatSync as fstatSync5, openSync as openSync8 } from "fs";
|
|
7002
|
+
function serializedStartup(request3) {
|
|
7003
|
+
const settings2 = startupSettings(request3);
|
|
7004
|
+
const file2 = startupFile(settings2.projectRoot);
|
|
7005
|
+
ensureStartupDirectory(file2);
|
|
7006
|
+
const lock = `${file2}.lock`;
|
|
7007
|
+
const descriptor = openSync8(
|
|
7008
|
+
lock,
|
|
7009
|
+
constants7.O_CREAT | constants7.O_WRONLY | constants7.O_NOFOLLOW,
|
|
7010
|
+
384
|
|
7011
|
+
);
|
|
7012
|
+
try {
|
|
7013
|
+
const stat = fstatSync5(descriptor);
|
|
7014
|
+
if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0)
|
|
7015
|
+
throw new Error("unsafe_startup_lock");
|
|
7016
|
+
} finally {
|
|
7017
|
+
closeSync8(descriptor);
|
|
7018
|
+
}
|
|
7019
|
+
const helpers = runtime();
|
|
7020
|
+
const encoded = Buffer.from(JSON.stringify(request3)).toString("base64url");
|
|
7021
|
+
const child = spawnSync6(
|
|
7022
|
+
"flock",
|
|
7023
|
+
[
|
|
7024
|
+
"--wait",
|
|
7025
|
+
"10",
|
|
7026
|
+
"--close",
|
|
7027
|
+
"--conflict-exit-code",
|
|
7028
|
+
"73",
|
|
7029
|
+
lock,
|
|
7030
|
+
helpers.node,
|
|
7031
|
+
helpers.worker,
|
|
7032
|
+
"startup",
|
|
7033
|
+
encoded
|
|
7034
|
+
],
|
|
7035
|
+
{
|
|
7036
|
+
encoding: "utf8",
|
|
7037
|
+
timeout: 6e4,
|
|
7038
|
+
maxBuffer: 256 * 1024,
|
|
7039
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
7040
|
+
env: {
|
|
7041
|
+
...process.env,
|
|
7042
|
+
PMCP_TUI_COLUMNS: String(process.stdout.columns),
|
|
7043
|
+
PMCP_TUI_ROWS: String(process.stdout.rows)
|
|
7044
|
+
}
|
|
7045
|
+
}
|
|
7046
|
+
);
|
|
7047
|
+
if (child.status === 73) throw new Error("startup_busy");
|
|
7048
|
+
if (child.error || !child.stdout.trim())
|
|
7049
|
+
throw new Error("startup_requires_recovery");
|
|
7050
|
+
return JSON.parse(child.stdout);
|
|
7051
|
+
}
|
|
7052
|
+
var init_lock2 = __esm({
|
|
7053
|
+
"src/tui/startup/lock.ts"() {
|
|
7054
|
+
"use strict";
|
|
7055
|
+
init_runtime2();
|
|
7056
|
+
init_state();
|
|
7057
|
+
init_config3();
|
|
7058
|
+
}
|
|
7059
|
+
});
|
|
7060
|
+
|
|
7061
|
+
// src/tui/presentation/runtime-choice.ts
|
|
7062
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
7063
|
+
async function chooseRuntime(prompt, choices, preferred) {
|
|
7064
|
+
if (!choices.length) throw new Error("runtime_executable_unavailable");
|
|
7065
|
+
const fallback = Math.max(choices.indexOf(preferred), 0);
|
|
7066
|
+
process.stderr.write(
|
|
7067
|
+
`${choices.map((runtime2, index) => `${index + 1}. ${runtime2}`).join("\n")}
|
|
7068
|
+
`
|
|
7069
|
+
);
|
|
7070
|
+
while (true) {
|
|
7071
|
+
const answer = (await prompt.question(
|
|
7072
|
+
`Runtime [1-${choices.length}, Enter=${fallback + 1}, q=cancel] `
|
|
7073
|
+
)).trim();
|
|
7074
|
+
if (answer === "q") return void 0;
|
|
7075
|
+
if (answer === "") return choices[fallback];
|
|
7076
|
+
if (!/^\d+$/.test(answer)) continue;
|
|
7077
|
+
const index = Number(answer) - 1;
|
|
7078
|
+
if (Number.isSafeInteger(index) && index >= 0 && index < choices.length)
|
|
7079
|
+
return choices[index];
|
|
7080
|
+
}
|
|
7081
|
+
}
|
|
7082
|
+
async function confirmRuntimeStart(prompt, runtime2) {
|
|
7083
|
+
const answer = await prompt.question(
|
|
7084
|
+
`Start ${runtime2}? [Enter=start, q=cancel] `
|
|
7085
|
+
);
|
|
7086
|
+
return answer.trim() === "";
|
|
7087
|
+
}
|
|
7088
|
+
function terminalPrompt() {
|
|
7089
|
+
return createInterface2({ input: process.stdin, output: process.stderr });
|
|
7090
|
+
}
|
|
7091
|
+
var init_runtime_choice = __esm({
|
|
7092
|
+
"src/tui/presentation/runtime-choice.ts"() {
|
|
7093
|
+
"use strict";
|
|
7094
|
+
}
|
|
7095
|
+
});
|
|
7096
|
+
|
|
7097
|
+
// src/tui/startup/entry.ts
|
|
7098
|
+
var entry_exports = {};
|
|
7099
|
+
__export(entry_exports, {
|
|
7100
|
+
launchStartup: () => launchStartup,
|
|
7101
|
+
planStartup: () => planStartup
|
|
7102
|
+
});
|
|
7103
|
+
function planStartup(request3) {
|
|
7104
|
+
const settings2 = startupSettings(request3);
|
|
7105
|
+
const state = readStartup(
|
|
7106
|
+
startupFile(settings2.projectRoot),
|
|
7107
|
+
settings2.projectRoot
|
|
7108
|
+
);
|
|
7109
|
+
return {
|
|
7110
|
+
schemaVersion: 1,
|
|
7111
|
+
ok: true,
|
|
7112
|
+
settings: settings2,
|
|
7113
|
+
savedWorkspace: state,
|
|
7114
|
+
willStartRuntime: !state && !settings2.groupFile,
|
|
7115
|
+
runtimeExecutable: locateExecutable(
|
|
7116
|
+
runtimeExecutables[settings2.runtime],
|
|
7117
|
+
request3.cwd
|
|
7118
|
+
),
|
|
7119
|
+
providerInvoked: false
|
|
7120
|
+
};
|
|
7121
|
+
}
|
|
7122
|
+
async function launchStartup(request3, confirmed) {
|
|
7123
|
+
requireTerminal();
|
|
7124
|
+
let settings2 = startupSettings(request3);
|
|
7125
|
+
const state = readStartup(
|
|
7126
|
+
startupFile(settings2.projectRoot),
|
|
7127
|
+
settings2.projectRoot
|
|
7128
|
+
);
|
|
7129
|
+
const starting = (!state || request3.fresh) && !settings2.groupFile;
|
|
7130
|
+
if (starting) {
|
|
7131
|
+
if (!confirmed) {
|
|
7132
|
+
const prompt = terminalPrompt();
|
|
7133
|
+
try {
|
|
7134
|
+
let runtime2 = settings2.runtime;
|
|
7135
|
+
process.stderr.write(`PMCP MAIN | ${settings2.projectRoot}
|
|
7136
|
+
`);
|
|
7137
|
+
if (!request3.runtime) {
|
|
7138
|
+
const installed = settings2.allowedRuntimes.filter(
|
|
7139
|
+
(id2) => locateExecutable(runtimeExecutables[id2], request3.cwd)
|
|
7140
|
+
);
|
|
7141
|
+
const selected = await chooseRuntime(prompt, installed, runtime2);
|
|
7142
|
+
if (!selected) return cancelledStart();
|
|
7143
|
+
runtime2 = selected;
|
|
7144
|
+
}
|
|
7145
|
+
if (!await confirmRuntimeStart(prompt, runtime2))
|
|
7146
|
+
return cancelledStart();
|
|
7147
|
+
request3 = { ...request3, runtime: runtime2 };
|
|
7148
|
+
settings2 = startupSettings(request3);
|
|
7149
|
+
} finally {
|
|
7150
|
+
prompt.close();
|
|
7151
|
+
}
|
|
7152
|
+
}
|
|
7153
|
+
if (!locateExecutable(runtimeExecutables[settings2.runtime], request3.cwd))
|
|
7154
|
+
throw new Error("runtime_executable_unavailable");
|
|
7155
|
+
process.stderr.write(
|
|
7156
|
+
`PMCP main: ${settings2.runtime} | ${settings2.projectRoot}
|
|
7157
|
+
`
|
|
7158
|
+
);
|
|
7159
|
+
}
|
|
7160
|
+
const prepared = serializedStartup(request3);
|
|
7161
|
+
if (prepared.ok !== true) return prepared;
|
|
7162
|
+
return invokeTui({
|
|
7163
|
+
action: "open",
|
|
7164
|
+
viewFile: String(prepared.viewFile),
|
|
7165
|
+
input: {}
|
|
7166
|
+
});
|
|
7167
|
+
}
|
|
7168
|
+
function cancelledStart() {
|
|
7169
|
+
return {
|
|
7170
|
+
schemaVersion: 1,
|
|
7171
|
+
ok: true,
|
|
7172
|
+
cancelled: true,
|
|
7173
|
+
providerInvoked: false
|
|
7174
|
+
};
|
|
7175
|
+
}
|
|
7176
|
+
var init_entry2 = __esm({
|
|
7177
|
+
"src/tui/startup/entry.ts"() {
|
|
7178
|
+
"use strict";
|
|
7179
|
+
init_executable();
|
|
7180
|
+
init_registry();
|
|
7181
|
+
init_invoke2();
|
|
7182
|
+
init_config3();
|
|
7183
|
+
init_state();
|
|
7184
|
+
init_host();
|
|
7185
|
+
init_lock2();
|
|
7186
|
+
init_runtime_choice();
|
|
7187
|
+
}
|
|
7188
|
+
});
|
|
7189
|
+
|
|
6514
7190
|
// src/cli/command.ts
|
|
6515
7191
|
var ArgumentError = class extends Error {
|
|
6516
7192
|
constructor(message) {
|
|
@@ -6564,15 +7240,15 @@ function one(args, name) {
|
|
|
6564
7240
|
function many(args, name) {
|
|
6565
7241
|
return args.options.get(name) ?? [];
|
|
6566
7242
|
}
|
|
6567
|
-
function printCommandHelp(ui,
|
|
6568
|
-
ui.heading(`pmcp ${
|
|
6569
|
-
ui.line(` ${
|
|
7243
|
+
function printCommandHelp(ui, command3) {
|
|
7244
|
+
ui.heading(`pmcp ${command3.name}`);
|
|
7245
|
+
ui.line(` ${command3.describe}`);
|
|
6570
7246
|
ui.line();
|
|
6571
|
-
ui.line(` ${ui.paint(
|
|
6572
|
-
if (
|
|
7247
|
+
ui.line(` ${ui.paint(command3.usage, "dim")}`);
|
|
7248
|
+
if (command3.options && command3.options.length > 0) {
|
|
6573
7249
|
ui.line();
|
|
6574
7250
|
ui.table(
|
|
6575
|
-
|
|
7251
|
+
command3.options.map((option) => [
|
|
6576
7252
|
`--${option.name}${option.placeholder ? ` ${option.placeholder}` : ""}`,
|
|
6577
7253
|
option.describe
|
|
6578
7254
|
])
|
|
@@ -6583,9 +7259,14 @@ function printCommandHelp(ui, command2) {
|
|
|
6583
7259
|
function printHelp(ui, commands, notImplemented) {
|
|
6584
7260
|
ui.heading("pmcp \u2014 the skills your installed packages ship");
|
|
6585
7261
|
ui.line();
|
|
6586
|
-
ui.table(commands.map((
|
|
7262
|
+
ui.table(commands.map((command3) => [command3.name, command3.describe]));
|
|
6587
7263
|
ui.line();
|
|
6588
|
-
ui.line(
|
|
7264
|
+
ui.line(
|
|
7265
|
+
" With no command, pmcp opens a terminal workspace on a supported TTY."
|
|
7266
|
+
);
|
|
7267
|
+
ui.line(
|
|
7268
|
+
" Piped launches retain MCP stdio. Use pmcp serve explicitly for MCP."
|
|
7269
|
+
);
|
|
6589
7270
|
ui.line();
|
|
6590
7271
|
ui.table([
|
|
6591
7272
|
["-q, --quiet", "only errors"],
|
|
@@ -6688,6 +7369,9 @@ var Ui = class {
|
|
|
6688
7369
|
}
|
|
6689
7370
|
};
|
|
6690
7371
|
|
|
7372
|
+
// src/commands/index.ts
|
|
7373
|
+
init_entry();
|
|
7374
|
+
|
|
6691
7375
|
// src/intent.ts
|
|
6692
7376
|
function normaliseIntent(intent) {
|
|
6693
7377
|
return intent.toLowerCase().replaceAll(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/u).filter(Boolean).join(" ");
|
|
@@ -9163,7 +9847,7 @@ function registerMailboxTools(server, service) {
|
|
|
9163
9847
|
|
|
9164
9848
|
// src/mcp.ts
|
|
9165
9849
|
var SERVER_NAME = "pmcp";
|
|
9166
|
-
var SERVER_VERSION = "0.
|
|
9850
|
+
var SERVER_VERSION = "0.16.0";
|
|
9167
9851
|
function registerSkillTools(server, tools) {
|
|
9168
9852
|
registerCatalogTool(server, tools);
|
|
9169
9853
|
registerFindTool(server, tools);
|
|
@@ -9351,7 +10035,7 @@ async function prepareSearch(catalog, options2) {
|
|
|
9351
10035
|
// src/commands/serve.ts
|
|
9352
10036
|
var serveCommand = {
|
|
9353
10037
|
name: "serve",
|
|
9354
|
-
describe: "Run the MCP server on stdio (the default
|
|
10038
|
+
describe: "Run the MCP server on stdio (also the default for piped launches)",
|
|
9355
10039
|
usage: "pmcp serve [--root <dir>] [--scope <prefix>] [--marketplace <dir>] [--package <dir>] [--workspace <dir>] [--config <file> | --no-config] [--prompts]",
|
|
9356
10040
|
options: [
|
|
9357
10041
|
...CATALOG_OPTIONS,
|
|
@@ -9812,6 +10496,10 @@ function installedPackageDirectory(project2, name) {
|
|
|
9812
10496
|
for (; ; ) {
|
|
9813
10497
|
const candidate2 = join11(directory, "node_modules", name);
|
|
9814
10498
|
if (existsSync10(join11(candidate2, "package.json"))) return candidate2;
|
|
10499
|
+
if (existsSync10(join11(directory, ".git")))
|
|
10500
|
+
throw new Error(
|
|
10501
|
+
"Installed package manifest was not found in this repository"
|
|
10502
|
+
);
|
|
9815
10503
|
const parent = dirname11(directory);
|
|
9816
10504
|
if (parent === directory)
|
|
9817
10505
|
throw new Error("Installed package manifest was not found");
|
|
@@ -9829,6 +10517,8 @@ function locate(name, project2) {
|
|
|
9829
10517
|
);
|
|
9830
10518
|
} catch (error) {
|
|
9831
10519
|
if (!absent(error)) return { name, reason: "unreadable_manifest" };
|
|
10520
|
+
if (existsSync10(join11(directory, ".git")))
|
|
10521
|
+
return { name, reason: "not_installed" };
|
|
9832
10522
|
const parent = dirname11(directory);
|
|
9833
10523
|
if (parent === directory) return { name, reason: "not_installed" };
|
|
9834
10524
|
directory = parent;
|
|
@@ -9885,30 +10575,77 @@ function readInstalledDependencies(projectDirectory) {
|
|
|
9885
10575
|
}
|
|
9886
10576
|
|
|
9887
10577
|
// 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
10578
|
import semver from "semver";
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
|
|
9895
|
-
|
|
9896
|
-
|
|
9897
|
-
|
|
10579
|
+
|
|
10580
|
+
// src/install/manager.ts
|
|
10581
|
+
import { existsSync as existsSync11 } from "fs";
|
|
10582
|
+
import { join as join12, relative as relative6, sep as sep5 } from "path";
|
|
10583
|
+
function installationCommand(context, specs) {
|
|
10584
|
+
if (context.manager === "npm") {
|
|
10585
|
+
const args2 = [
|
|
10586
|
+
"install",
|
|
10587
|
+
"--save-dev",
|
|
10588
|
+
"--save-exact",
|
|
10589
|
+
"--ignore-scripts",
|
|
10590
|
+
"--no-audit",
|
|
10591
|
+
"--no-fund"
|
|
10592
|
+
];
|
|
10593
|
+
if (context.project !== context.root)
|
|
10594
|
+
args2.push(
|
|
10595
|
+
`--workspace=${relative6(context.root, context.project).split(sep5).join("/")}`
|
|
9898
10596
|
);
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
|
|
9904
|
-
|
|
9905
|
-
|
|
10597
|
+
return { executable: "npm", args: [...args2, ...specs], cwd: context.root };
|
|
10598
|
+
}
|
|
10599
|
+
const args = context.manager === "pnpm" ? ["add", "--save-dev", "--save-exact", "--ignore-scripts"] : ["add", "--dev", "--exact", "--ignore-scripts"];
|
|
10600
|
+
if (context.manager === "pnpm" && context.project === context.root && existsSync11(join12(context.root, "pnpm-workspace.yaml")))
|
|
10601
|
+
args.push("--workspace-root");
|
|
10602
|
+
return {
|
|
10603
|
+
executable: context.manager,
|
|
10604
|
+
args: [...args, ...specs],
|
|
10605
|
+
cwd: context.project
|
|
10606
|
+
};
|
|
10607
|
+
}
|
|
10608
|
+
function integrityArguments(manager, spec) {
|
|
10609
|
+
const verb = manager === "bun" ? "info" : "view";
|
|
10610
|
+
return [verb, spec, "dist.integrity", "--json"];
|
|
10611
|
+
}
|
|
10612
|
+
|
|
10613
|
+
// src/install/context.ts
|
|
10614
|
+
import { createHash as createHash7 } from "crypto";
|
|
10615
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15, realpathSync as realpathSync4 } from "fs";
|
|
10616
|
+
import { dirname as dirname12, join as join14 } from "path";
|
|
10617
|
+
|
|
10618
|
+
// src/install/workspace.ts
|
|
10619
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14, realpathSync as realpathSync3 } from "fs";
|
|
10620
|
+
import { join as join13 } from "path";
|
|
10621
|
+
import { parse as parse3 } from "yaml";
|
|
10622
|
+
function workspaceContains(root, project2) {
|
|
10623
|
+
const file2 = join13(root, "pnpm-workspace.yaml");
|
|
10624
|
+
if (!existsSync12(file2)) return workspacePackageDirs(root).includes(project2);
|
|
10625
|
+
const value = parse3(readFileSync14(file2, "utf8"));
|
|
10626
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
10627
|
+
throw new Error("invalid pnpm workspace configuration");
|
|
10628
|
+
const patterns = value.packages ?? [];
|
|
10629
|
+
if (!Array.isArray(patterns) || patterns.some((item) => typeof item !== "string"))
|
|
10630
|
+
throw new Error("pnpm workspace packages must be string patterns");
|
|
10631
|
+
const included = /* @__PURE__ */ new Set([root]);
|
|
10632
|
+
const excluded = /* @__PURE__ */ new Set();
|
|
10633
|
+
for (const raw of patterns) {
|
|
10634
|
+
const negated = raw.startsWith("!");
|
|
10635
|
+
const pattern = negated ? raw.slice(1) : raw;
|
|
10636
|
+
if (!pattern || pattern.split("/").includes("..") || pattern.startsWith("/"))
|
|
10637
|
+
throw new Error("workspace patterns must stay inside the workspace");
|
|
10638
|
+
for (const alternative of expandBraces(pattern)) {
|
|
10639
|
+
for (const match of globDirs(root, alternative)) {
|
|
10640
|
+
(negated ? excluded : included).add(realpathSync3(match));
|
|
9906
10641
|
}
|
|
9907
10642
|
}
|
|
9908
10643
|
}
|
|
9909
|
-
return
|
|
10644
|
+
return project2 === root || included.has(project2) && !excluded.has(project2);
|
|
9910
10645
|
}
|
|
9911
|
-
|
|
10646
|
+
|
|
10647
|
+
// src/install/context.ts
|
|
10648
|
+
var locks = [
|
|
9912
10649
|
"package-lock.json",
|
|
9913
10650
|
"npm-shrinkwrap.json",
|
|
9914
10651
|
"bun.lock",
|
|
@@ -9921,7 +10658,7 @@ function fingerprint(files) {
|
|
|
9921
10658
|
for (const file2 of files) {
|
|
9922
10659
|
hash2.update(file2).update("\0");
|
|
9923
10660
|
try {
|
|
9924
|
-
hash2.update(
|
|
10661
|
+
hash2.update(readFileSync15(file2));
|
|
9925
10662
|
} catch (error) {
|
|
9926
10663
|
if (error.code !== "ENOENT") throw error;
|
|
9927
10664
|
hash2.update("absent");
|
|
@@ -9932,65 +10669,71 @@ function fingerprint(files) {
|
|
|
9932
10669
|
}
|
|
9933
10670
|
function manifestAt(directory) {
|
|
9934
10671
|
const value = JSON.parse(
|
|
9935
|
-
|
|
10672
|
+
readFileSync15(join14(directory, "package.json"), "utf8")
|
|
9936
10673
|
);
|
|
9937
10674
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
9938
10675
|
throw new Error("invalid project manifest");
|
|
9939
10676
|
return value;
|
|
9940
10677
|
}
|
|
9941
|
-
function
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
|
|
10678
|
+
function managerForLock(file2) {
|
|
10679
|
+
if (file2.startsWith("bun.")) return "bun";
|
|
10680
|
+
if (file2 === "pnpm-lock.yaml") return "pnpm";
|
|
10681
|
+
if (file2 === "package-lock.json" || file2 === "npm-shrinkwrap.json")
|
|
10682
|
+
return "npm";
|
|
10683
|
+
return "unsupported";
|
|
10684
|
+
}
|
|
10685
|
+
function workspaceRoot(project2) {
|
|
9945
10686
|
let ancestor = project2;
|
|
9946
10687
|
for (; ; ) {
|
|
9947
|
-
if (ancestor !== project2 &&
|
|
10688
|
+
if (ancestor !== project2 && existsSync13(join14(ancestor, "package.json"))) {
|
|
9948
10689
|
const candidate2 = manifestAt(ancestor);
|
|
9949
|
-
if (candidate2.workspaces !== void 0) {
|
|
9950
|
-
|
|
9951
|
-
|
|
10690
|
+
if (candidate2.workspaces !== void 0 || existsSync13(join14(ancestor, "pnpm-workspace.yaml"))) {
|
|
10691
|
+
if (!workspaceContains(ancestor, project2))
|
|
10692
|
+
throw new Error("project is excluded from the ancestor workspace");
|
|
10693
|
+
return ancestor;
|
|
9952
10694
|
}
|
|
9953
|
-
if (
|
|
10695
|
+
if (locks.some((file2) => existsSync13(join14(ancestor, file2))))
|
|
10696
|
+
return project2;
|
|
9954
10697
|
}
|
|
9955
|
-
if (
|
|
9956
|
-
|
|
10698
|
+
if (existsSync13(join14(ancestor, ".git")) || dirname12(ancestor) === ancestor)
|
|
10699
|
+
return project2;
|
|
9957
10700
|
ancestor = dirname12(ancestor);
|
|
9958
10701
|
}
|
|
10702
|
+
}
|
|
10703
|
+
function installContext(directory) {
|
|
10704
|
+
const project2 = realpathSync4(directory);
|
|
10705
|
+
const manifest2 = manifestAt(project2);
|
|
10706
|
+
const root = workspaceRoot(project2);
|
|
9959
10707
|
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
|
-
);
|
|
10708
|
+
const lockNames = locks.filter((file2) => existsSync13(join14(root, file2)));
|
|
10709
|
+
const lockManagers = new Set(lockNames.map(managerForLock));
|
|
9969
10710
|
if (lockManagers.size > 1 || lockManagers.has("unsupported"))
|
|
9970
10711
|
throw new Error("conflicting or unsupported package manager lockfiles");
|
|
9971
10712
|
const declared = owner.packageManager?.match(
|
|
9972
|
-
/^(npm|bun)@\d+\.\d+\.\d+(?:[-+].+)?$/u
|
|
10713
|
+
/^(npm|bun|pnpm)@\d+\.\d+\.\d+(?:[-+].+)?$/u
|
|
9973
10714
|
)?.[1];
|
|
9974
10715
|
if (owner.packageManager !== void 0 && !declared)
|
|
9975
|
-
throw new Error("only npm and
|
|
10716
|
+
throw new Error("only npm, Bun and pnpm projects are supported");
|
|
9976
10717
|
const locked = [...lockManagers][0];
|
|
9977
10718
|
if (declared && locked && declared !== locked)
|
|
9978
10719
|
throw new Error("packageManager and lockfile disagree");
|
|
9979
10720
|
const manager = declared ?? locked;
|
|
9980
|
-
if (manager !== "npm" && manager !== "bun")
|
|
10721
|
+
if (manager !== "npm" && manager !== "bun" && manager !== "pnpm")
|
|
9981
10722
|
throw new Error(
|
|
9982
10723
|
"declare packageManager or install project dependencies first"
|
|
9983
10724
|
);
|
|
9984
|
-
if (root !== project2 &&
|
|
10725
|
+
if (root !== project2 && locks.some((file2) => existsSync13(join14(project2, file2))))
|
|
9985
10726
|
throw new Error(
|
|
9986
10727
|
"nested project has its own lockfile; resolve the workspace boundary first"
|
|
9987
10728
|
);
|
|
9988
|
-
}
|
|
9989
10729
|
const files = [
|
|
9990
10730
|
.../* @__PURE__ */ new Set([
|
|
9991
|
-
|
|
9992
|
-
|
|
9993
|
-
|
|
10731
|
+
join14(root, "package.json"),
|
|
10732
|
+
join14(project2, "package.json"),
|
|
10733
|
+
join14(root, "pnpm-workspace.yaml"),
|
|
10734
|
+
join14(root, ".npmrc"),
|
|
10735
|
+
join14(project2, ".npmrc"),
|
|
10736
|
+
...locks.map((file2) => join14(root, file2))
|
|
9994
10737
|
])
|
|
9995
10738
|
];
|
|
9996
10739
|
return {
|
|
@@ -10002,6 +10745,27 @@ function installContext(directory) {
|
|
|
10002
10745
|
fingerprint: fingerprint(files)
|
|
10003
10746
|
};
|
|
10004
10747
|
}
|
|
10748
|
+
|
|
10749
|
+
// src/install/plan.ts
|
|
10750
|
+
function matchingSkills(catalog, inventory) {
|
|
10751
|
+
const matches2 = [];
|
|
10752
|
+
for (const dependency of inventory.dependencies) {
|
|
10753
|
+
for (const entry2 of catalog.entries) {
|
|
10754
|
+
const target2 = entry2.targets.find(
|
|
10755
|
+
(target3) => target3.packageName === dependency.name
|
|
10756
|
+
);
|
|
10757
|
+
if (target2 && semver.satisfies(dependency.version, target2.range)) {
|
|
10758
|
+
matches2.push({
|
|
10759
|
+
targetName: dependency.name,
|
|
10760
|
+
targetVersion: dependency.version,
|
|
10761
|
+
verified: target2.verifiedVersions.includes(dependency.version),
|
|
10762
|
+
entry: entry2
|
|
10763
|
+
});
|
|
10764
|
+
}
|
|
10765
|
+
}
|
|
10766
|
+
}
|
|
10767
|
+
return matches2;
|
|
10768
|
+
}
|
|
10005
10769
|
function planInstall(options2) {
|
|
10006
10770
|
if (options2.inventory.issues.length)
|
|
10007
10771
|
throw new Error(
|
|
@@ -10066,49 +10830,27 @@ function planInstall(options2) {
|
|
|
10066
10830
|
const specs = deliveries.map(
|
|
10067
10831
|
(entry2) => `${entry2.packageName}@${entry2.version}`
|
|
10068
10832
|
);
|
|
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
10833
|
return {
|
|
10088
10834
|
catalogRevision: catalog.revision,
|
|
10089
10835
|
context,
|
|
10090
10836
|
changes: deliveries,
|
|
10091
10837
|
recalled,
|
|
10092
|
-
command:
|
|
10093
|
-
executable: context.manager,
|
|
10094
|
-
args,
|
|
10095
|
-
cwd: context.manager === "npm" ? context.root : context.project
|
|
10096
|
-
}
|
|
10838
|
+
command: installationCommand(context, specs)
|
|
10097
10839
|
};
|
|
10098
10840
|
}
|
|
10099
10841
|
|
|
10100
10842
|
// src/commands/remote-options.ts
|
|
10101
|
-
import { join as
|
|
10102
|
-
import { readFileSync as
|
|
10843
|
+
import { dirname as dirname13, join as join16, resolve as resolve9 } from "path";
|
|
10844
|
+
import { existsSync as existsSync14, readFileSync as readFileSync18 } from "fs";
|
|
10103
10845
|
|
|
10104
10846
|
// src/remote/catalog.ts
|
|
10105
|
-
import { readFileSync as
|
|
10847
|
+
import { readFileSync as readFileSync17, statSync as statSync8 } from "fs";
|
|
10106
10848
|
import semver2 from "semver";
|
|
10107
10849
|
import { z as z13 } from "zod";
|
|
10108
10850
|
|
|
10109
10851
|
// src/remote/catalog-cache.ts
|
|
10110
|
-
import { mkdirSync as mkdirSync5, readFileSync as
|
|
10111
|
-
import { join as
|
|
10852
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync16, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
10853
|
+
import { join as join15 } from "path";
|
|
10112
10854
|
var MAX_CACHE_BYTES = 8 * 1024 * 1024;
|
|
10113
10855
|
function memoryCatalogCache() {
|
|
10114
10856
|
const held = /* @__PURE__ */ new Map();
|
|
@@ -10120,11 +10862,11 @@ function memoryCatalogCache() {
|
|
|
10120
10862
|
};
|
|
10121
10863
|
}
|
|
10122
10864
|
function fileCatalogCache(dir) {
|
|
10123
|
-
const path =
|
|
10865
|
+
const path = join15(dir, "catalog-cache.json");
|
|
10124
10866
|
return {
|
|
10125
10867
|
read(origin) {
|
|
10126
10868
|
try {
|
|
10127
|
-
const raw =
|
|
10869
|
+
const raw = readFileSync16(path, "utf8");
|
|
10128
10870
|
if (raw.length > MAX_CACHE_BYTES) return null;
|
|
10129
10871
|
const held = JSON.parse(raw);
|
|
10130
10872
|
if (typeof held !== "object" || held === null) return null;
|
|
@@ -10247,7 +10989,7 @@ function readCatalogFile(path) {
|
|
|
10247
10989
|
const stat = statSync8(path);
|
|
10248
10990
|
if (!stat.isFile() || stat.size > MAX_CATALOG_BYTES)
|
|
10249
10991
|
throw new Error("catalog file exceeds the supported size");
|
|
10250
|
-
const bytes =
|
|
10992
|
+
const bytes = readFileSync17(path);
|
|
10251
10993
|
if (bytes.byteLength > MAX_CATALOG_BYTES)
|
|
10252
10994
|
throw new Error("catalog file exceeds the supported size");
|
|
10253
10995
|
return parseCatalog(JSON.parse(bytes.toString("utf8")));
|
|
@@ -10349,7 +11091,17 @@ function catalogProvider(context) {
|
|
|
10349
11091
|
return given ?? (api2 ? "hosted" : "public");
|
|
10350
11092
|
}
|
|
10351
11093
|
function projectFrom(context) {
|
|
10352
|
-
|
|
11094
|
+
const explicit = one(context.args, "project");
|
|
11095
|
+
if (explicit) return resolve9(context.cwd, explicit);
|
|
11096
|
+
let directory = resolve9(context.cwd);
|
|
11097
|
+
for (; ; ) {
|
|
11098
|
+
if (existsSync14(join16(directory, "package.json"))) return directory;
|
|
11099
|
+
if (existsSync14(join16(directory, ".git")) || dirname13(directory) === directory)
|
|
11100
|
+
throw new ArgumentError(
|
|
11101
|
+
"No project package.json found; provide --project"
|
|
11102
|
+
);
|
|
11103
|
+
directory = dirname13(directory);
|
|
11104
|
+
}
|
|
10353
11105
|
}
|
|
10354
11106
|
function apiOrigin(context) {
|
|
10355
11107
|
return one(context.args, "api") ?? context.env["PMCP_API_ORIGIN"] ?? DEFAULT_API_ORIGIN;
|
|
@@ -10360,11 +11112,11 @@ async function remoteCatalog(context) {
|
|
|
10360
11112
|
if (file2) {
|
|
10361
11113
|
if (one(context.args, "api"))
|
|
10362
11114
|
throw new Error("choose --catalog or --api, not both");
|
|
10363
|
-
return readCatalogFile(
|
|
11115
|
+
return readCatalogFile(resolve9(context.cwd, file2));
|
|
10364
11116
|
}
|
|
10365
11117
|
if (provider === "public") {
|
|
10366
11118
|
const catalog = JSON.parse(
|
|
10367
|
-
|
|
11119
|
+
readFileSync18(join16(publicMarketplaceRoot(), "docs/catalog.json"), "utf8")
|
|
10368
11120
|
);
|
|
10369
11121
|
return parseCatalog({
|
|
10370
11122
|
schemaVersion: 1,
|
|
@@ -10517,7 +11269,7 @@ function createPackageManagerRunner(userConfig, baseEnv = process.env, verifiedI
|
|
|
10517
11269
|
const alreadyVerified = verifiedIntegrity?.get(packageSpec);
|
|
10518
11270
|
if (alreadyVerified) return alreadyVerified;
|
|
10519
11271
|
const { manager, project: project2 } = plan2.context;
|
|
10520
|
-
const args = manager
|
|
11272
|
+
const args = integrityArguments(manager, packageSpec);
|
|
10521
11273
|
const result = spawnSync(manager, args, {
|
|
10522
11274
|
cwd: project2,
|
|
10523
11275
|
env: environment(userConfig, baseEnv),
|
|
@@ -10592,8 +11344,8 @@ function applyInstallPlan(plan2, runner = packageManagerRunner) {
|
|
|
10592
11344
|
|
|
10593
11345
|
// src/install/verify-content.ts
|
|
10594
11346
|
import { createHash as createHash8 } from "crypto";
|
|
10595
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
10596
|
-
import { join as
|
|
11347
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync19, statSync as statSync9 } from "fs";
|
|
11348
|
+
import { join as join17, relative as relative7, resolve as resolve10, sep as sep6 } from "path";
|
|
10597
11349
|
var SKILL = "SKILL.md";
|
|
10598
11350
|
var SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", ".cache"]);
|
|
10599
11351
|
function skillFiles2(root) {
|
|
@@ -10607,7 +11359,7 @@ function skillFiles2(root) {
|
|
|
10607
11359
|
}
|
|
10608
11360
|
for (const name of entries) {
|
|
10609
11361
|
if (SKIP.has(name)) continue;
|
|
10610
|
-
const path =
|
|
11362
|
+
const path = join17(dir, name);
|
|
10611
11363
|
let stats;
|
|
10612
11364
|
try {
|
|
10613
11365
|
stats = statSync9(path);
|
|
@@ -10618,7 +11370,7 @@ function skillFiles2(root) {
|
|
|
10618
11370
|
else if (name === SKILL)
|
|
10619
11371
|
found.set(
|
|
10620
11372
|
relative7(root, path).split(sep6).join("/"),
|
|
10621
|
-
|
|
11373
|
+
readFileSync19(path, "utf8")
|
|
10622
11374
|
);
|
|
10623
11375
|
}
|
|
10624
11376
|
};
|
|
@@ -10634,11 +11386,11 @@ function contentDigest(files) {
|
|
|
10634
11386
|
return `sha256:${hash2.digest("hex")}`;
|
|
10635
11387
|
}
|
|
10636
11388
|
function verifyInstalledContent(project2, packageName2, expected) {
|
|
10637
|
-
let root =
|
|
11389
|
+
let root = resolve10(project2, "node_modules", ...packageName2.split("/"));
|
|
10638
11390
|
try {
|
|
10639
11391
|
root = installedPackageDirectory(project2, packageName2);
|
|
10640
11392
|
} catch {
|
|
10641
|
-
root =
|
|
11393
|
+
root = resolve10(project2, "node_modules", ...packageName2.split("/"));
|
|
10642
11394
|
}
|
|
10643
11395
|
const files = skillFiles2(root);
|
|
10644
11396
|
const actual = contentDigest(files);
|
|
@@ -10652,8 +11404,8 @@ function verifyInstalledContent(project2, packageName2, expected) {
|
|
|
10652
11404
|
}
|
|
10653
11405
|
|
|
10654
11406
|
// src/install/public.ts
|
|
10655
|
-
import { lstatSync, readFileSync as
|
|
10656
|
-
import { join as
|
|
11407
|
+
import { lstatSync, readFileSync as readFileSync20, readdirSync as readdirSync6 } from "fs";
|
|
11408
|
+
import { join as join18 } from "path";
|
|
10657
11409
|
|
|
10658
11410
|
// src/install/archive.ts
|
|
10659
11411
|
import { createHash as createHash9 } from "crypto";
|
|
@@ -10820,7 +11572,7 @@ function verifyPublicDelivery(project2, delivery) {
|
|
|
10820
11572
|
for (const entry2 of readdirSync6(directory, { withFileTypes: true })) {
|
|
10821
11573
|
const path = prefix ? `${prefix}/${entry2.name}` : entry2.name;
|
|
10822
11574
|
if (entry2.isDirectory()) {
|
|
10823
|
-
walk(
|
|
11575
|
+
walk(join18(directory, entry2.name), path);
|
|
10824
11576
|
continue;
|
|
10825
11577
|
}
|
|
10826
11578
|
if (!entry2.isFile() || !expected.has(path))
|
|
@@ -10831,8 +11583,8 @@ function verifyPublicDelivery(project2, delivery) {
|
|
|
10831
11583
|
};
|
|
10832
11584
|
walk(root);
|
|
10833
11585
|
for (const [path, bytes] of files) {
|
|
10834
|
-
const destination =
|
|
10835
|
-
if (!lstatSync(destination).isFile() || !
|
|
11586
|
+
const destination = join18(root, path.slice("package/".length));
|
|
11587
|
+
if (!lstatSync(destination).isFile() || !readFileSync20(destination).equals(bytes))
|
|
10836
11588
|
throw new Error(
|
|
10837
11589
|
"Installed public package differs from its verified archive; inspect the project before retrying"
|
|
10838
11590
|
);
|
|
@@ -10843,7 +11595,7 @@ function verifyPublicDelivery(project2, delivery) {
|
|
|
10843
11595
|
// src/remote/distribution.ts
|
|
10844
11596
|
import { chmodSync as chmodSync4, mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
10845
11597
|
import { tmpdir } from "os";
|
|
10846
|
-
import { join as
|
|
11598
|
+
import { join as join19 } from "path";
|
|
10847
11599
|
function api(origin, path) {
|
|
10848
11600
|
const base = new URL(catalogEndpoint(origin));
|
|
10849
11601
|
base.pathname = path;
|
|
@@ -10983,9 +11735,9 @@ async function revokeDistributionCredential(accessToken, id2, origin = DEFAULT_A
|
|
|
10983
11735
|
}
|
|
10984
11736
|
}
|
|
10985
11737
|
function temporaryRegistryConfig(value) {
|
|
10986
|
-
const directory = mkdtempSync(
|
|
11738
|
+
const directory = mkdtempSync(join19(tmpdir(), "pmcp-registry-"));
|
|
10987
11739
|
chmodSync4(directory, 448);
|
|
10988
|
-
const path =
|
|
11740
|
+
const path = join19(directory, ".npmrc");
|
|
10989
11741
|
const registry = new URL(value.registry);
|
|
10990
11742
|
const auth = `//${registry.host}${registry.pathname}:_authToken=${value.token}`;
|
|
10991
11743
|
writeFileSync3(path, `${value.scope}:registry=${registry.href}
|
|
@@ -11238,36 +11990,34 @@ import { homedir as homedir6 } from "os";
|
|
|
11238
11990
|
// src/project/apply.ts
|
|
11239
11991
|
import { createHash as createHash13 } from "crypto";
|
|
11240
11992
|
import {
|
|
11241
|
-
existsSync as
|
|
11242
|
-
lstatSync as
|
|
11993
|
+
existsSync as existsSync20,
|
|
11994
|
+
lstatSync as lstatSync5,
|
|
11243
11995
|
mkdirSync as mkdirSync7,
|
|
11244
|
-
readFileSync as
|
|
11996
|
+
readFileSync as readFileSync25,
|
|
11245
11997
|
readdirSync as readdirSync9,
|
|
11246
11998
|
readlinkSync as readlinkSync2,
|
|
11247
|
-
rmSync as
|
|
11248
|
-
symlinkSync,
|
|
11249
|
-
writeFileSync as writeFileSync5
|
|
11999
|
+
rmSync as rmSync5
|
|
11250
12000
|
} from "fs";
|
|
11251
|
-
import { dirname as
|
|
11252
|
-
import { parse as
|
|
12001
|
+
import { dirname as dirname18, join as join24, posix as posix2 } from "path";
|
|
12002
|
+
import { parse as parse5 } from "smol-toml";
|
|
11253
12003
|
|
|
11254
12004
|
// src/project/backup.ts
|
|
11255
12005
|
import { createHash as createHash10 } from "crypto";
|
|
11256
12006
|
import {
|
|
11257
|
-
existsSync as
|
|
12007
|
+
existsSync as existsSync16,
|
|
11258
12008
|
lstatSync as lstatSync3,
|
|
11259
12009
|
mkdirSync as mkdirSync6,
|
|
11260
12010
|
mkdtempSync as mkdtempSync2,
|
|
11261
|
-
readFileSync as
|
|
12011
|
+
readFileSync as readFileSync21,
|
|
11262
12012
|
writeFileSync as writeFileSync4
|
|
11263
12013
|
} from "fs";
|
|
11264
12014
|
import { homedir as homedir3 } from "os";
|
|
11265
|
-
import { dirname as
|
|
12015
|
+
import { dirname as dirname15, isAbsolute as isAbsolute4, join as join21, relative as relative8, resolve as resolve11 } from "path";
|
|
11266
12016
|
|
|
11267
12017
|
// src/project/package-rules.ts
|
|
11268
12018
|
import { execFileSync } from "child_process";
|
|
11269
|
-
import { existsSync as
|
|
11270
|
-
import { dirname as
|
|
12019
|
+
import { existsSync as existsSync15, lstatSync as lstatSync2, readdirSync as readdirSync7, readlinkSync } from "fs";
|
|
12020
|
+
import { dirname as dirname14, join as join20, posix } from "path";
|
|
11271
12021
|
var ProjectError = class extends Error {
|
|
11272
12022
|
constructor(message) {
|
|
11273
12023
|
super(message);
|
|
@@ -11312,11 +12062,11 @@ function linkTarget(abs) {
|
|
|
11312
12062
|
}
|
|
11313
12063
|
function legacyLinks(root) {
|
|
11314
12064
|
const found = /* @__PURE__ */ new Map();
|
|
11315
|
-
const dir =
|
|
11316
|
-
if (!
|
|
12065
|
+
const dir = join20(root, RULES_DIR);
|
|
12066
|
+
if (!existsSync15(dir)) return found;
|
|
11317
12067
|
for (const entry2 of readdirSync7(dir).sort()) {
|
|
11318
12068
|
if (!entry2.endsWith(".md")) continue;
|
|
11319
|
-
const target2 = linkTarget(
|
|
12069
|
+
const target2 = linkTarget(join20(dir, entry2));
|
|
11320
12070
|
if (target2 === null || !target2.startsWith("../../")) continue;
|
|
11321
12071
|
found.set(entry2.slice(0, -3), target2.slice("../../".length));
|
|
11322
12072
|
}
|
|
@@ -11336,14 +12086,14 @@ function packageRuleOutputs(root, spec, owned) {
|
|
|
11336
12086
|
const legacyByDir = /* @__PURE__ */ new Map();
|
|
11337
12087
|
for (const [name, target2] of legacy) {
|
|
11338
12088
|
if (!byTarget.has(target2)) byTarget.set(target2, name);
|
|
11339
|
-
const dir =
|
|
12089
|
+
const dir = dirname14(target2);
|
|
11340
12090
|
if (!legacyByDir.has(dir)) legacyByDir.set(dir, name);
|
|
11341
12091
|
}
|
|
11342
12092
|
const outputs = [];
|
|
11343
12093
|
const taken = /* @__PURE__ */ new Map();
|
|
11344
12094
|
for (const file2 of files) {
|
|
11345
12095
|
const pkg = file2.slice(0, -suffix.length);
|
|
11346
|
-
const name = byTarget.get(file2) ?? legacyByDir.get(
|
|
12096
|
+
const name = byTarget.get(file2) ?? legacyByDir.get(dirname14(file2)) ?? deriveName(pkg, spec.strip);
|
|
11347
12097
|
const other = taken.get(name);
|
|
11348
12098
|
if (other !== void 0) {
|
|
11349
12099
|
throw new ProjectError(
|
|
@@ -11362,8 +12112,8 @@ function packageRuleOutputs(root, spec, owned) {
|
|
|
11362
12112
|
|
|
11363
12113
|
// src/project/backup.ts
|
|
11364
12114
|
function backupFiles(root, paths) {
|
|
11365
|
-
const base =
|
|
11366
|
-
const inside = relative8(
|
|
12115
|
+
const base = join21(homedir3(), ".cache/pmcp/project-backups");
|
|
12116
|
+
const inside = relative8(resolve11(root), resolve11(base));
|
|
11367
12117
|
if (!isAbsolute4(inside) && inside !== ".." && !inside.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
|
|
11368
12118
|
throw new ProjectError("Project backups must be outside the project");
|
|
11369
12119
|
}
|
|
@@ -11378,12 +12128,12 @@ function backupFiles(root, paths) {
|
|
|
11378
12128
|
);
|
|
11379
12129
|
}
|
|
11380
12130
|
const snapshots = [...new Set(paths)].map((path) => {
|
|
11381
|
-
const absolute2 =
|
|
11382
|
-
const within = relative8(
|
|
12131
|
+
const absolute2 = resolve11(root, path);
|
|
12132
|
+
const within = relative8(resolve11(root), absolute2);
|
|
11383
12133
|
if (isAbsolute4(within) || within === ".." || within.startsWith("../") || within.startsWith("..\\")) {
|
|
11384
12134
|
throw new ProjectError("Backup paths must stay inside the project");
|
|
11385
12135
|
}
|
|
11386
|
-
if (!
|
|
12136
|
+
if (!existsSync16(absolute2)) {
|
|
11387
12137
|
return { path, contents: null };
|
|
11388
12138
|
}
|
|
11389
12139
|
const stat = lstatSync3(absolute2);
|
|
@@ -11392,15 +12142,15 @@ function backupFiles(root, paths) {
|
|
|
11392
12142
|
`Refusing to back up a nonregular config file: ${path}`
|
|
11393
12143
|
);
|
|
11394
12144
|
}
|
|
11395
|
-
return { path, contents:
|
|
12145
|
+
return { path, contents: readFileSync21(absolute2) };
|
|
11396
12146
|
});
|
|
11397
|
-
const destination = mkdtempSync2(
|
|
12147
|
+
const destination = mkdtempSync2(join21(base, "project-"));
|
|
11398
12148
|
const entries = snapshots.map(({ path, contents }) => {
|
|
11399
12149
|
if (contents === null) {
|
|
11400
12150
|
return { path, present: false };
|
|
11401
12151
|
}
|
|
11402
|
-
const output =
|
|
11403
|
-
mkdirSync6(
|
|
12152
|
+
const output = join21(destination, "files", path);
|
|
12153
|
+
mkdirSync6(dirname15(output), { recursive: true, mode: 448 });
|
|
11404
12154
|
writeFileSync4(output, contents, { mode: 384 });
|
|
11405
12155
|
return {
|
|
11406
12156
|
path,
|
|
@@ -11409,11 +12159,11 @@ function backupFiles(root, paths) {
|
|
|
11409
12159
|
};
|
|
11410
12160
|
});
|
|
11411
12161
|
writeFileSync4(
|
|
11412
|
-
|
|
12162
|
+
join21(destination, "receipt.json"),
|
|
11413
12163
|
JSON.stringify(
|
|
11414
12164
|
{
|
|
11415
12165
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11416
|
-
projectRoot:
|
|
12166
|
+
projectRoot: resolve11(root),
|
|
11417
12167
|
entries
|
|
11418
12168
|
},
|
|
11419
12169
|
null,
|
|
@@ -11704,34 +12454,34 @@ function mcpOutputs(spec) {
|
|
|
11704
12454
|
|
|
11705
12455
|
// src/project/hooks.ts
|
|
11706
12456
|
import { createHash as createHash12 } from "crypto";
|
|
11707
|
-
import { existsSync as
|
|
11708
|
-
import { join as
|
|
12457
|
+
import { existsSync as existsSync18, readFileSync as readFileSync23 } from "fs";
|
|
12458
|
+
import { join as join22 } from "path";
|
|
11709
12459
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11710
12460
|
|
|
11711
12461
|
// src/project/hook-adapter.ts
|
|
11712
12462
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
11713
12463
|
import { createHash as createHash11 } from "crypto";
|
|
11714
|
-
import { existsSync as
|
|
11715
|
-
import { dirname as
|
|
12464
|
+
import { existsSync as existsSync17, readFileSync as readFileSync22, realpathSync as realpathSync5, statSync as statSync10 } from "fs";
|
|
12465
|
+
import { dirname as dirname16, isAbsolute as isAbsolute6, relative as relative10, resolve as resolve12 } from "path";
|
|
11716
12466
|
var denied = () => ({
|
|
11717
12467
|
decision: "deny",
|
|
11718
12468
|
reason: "Shell guard failed validation or execution"
|
|
11719
12469
|
});
|
|
11720
|
-
function guardPath(root,
|
|
11721
|
-
const script =
|
|
12470
|
+
function guardPath(root, command3) {
|
|
12471
|
+
const script = command3[0]?.startsWith(".agents/guards/") ? command3[0] : command3[1];
|
|
11722
12472
|
if (!script?.startsWith(".agents/guards/")) {
|
|
11723
12473
|
throw new Error("Hook command must execute a script under .agents/guards/");
|
|
11724
12474
|
}
|
|
11725
|
-
const absolute2 =
|
|
11726
|
-
const base =
|
|
12475
|
+
const absolute2 = resolve12(root, script);
|
|
12476
|
+
const base = resolve12(root, ".agents/guards");
|
|
11727
12477
|
const inside = relative10(base, absolute2);
|
|
11728
12478
|
if (isAbsolute6(inside) || inside === ".." || inside.startsWith("../") || inside.startsWith("..\\")) {
|
|
11729
12479
|
throw new Error("Hook guard must stay under .agents/guards/");
|
|
11730
12480
|
}
|
|
11731
|
-
if (!
|
|
12481
|
+
if (!existsSync17(absolute2) || !statSync10(absolute2).isFile()) {
|
|
11732
12482
|
throw new Error("Hook guard script must exist");
|
|
11733
12483
|
}
|
|
11734
|
-
const actual = relative10(base,
|
|
12484
|
+
const actual = relative10(base, realpathSync5(absolute2));
|
|
11735
12485
|
if (isAbsolute6(actual) || actual === ".." || actual.startsWith("../") || actual.startsWith("..\\")) {
|
|
11736
12486
|
throw new Error("Hook guard cannot resolve outside .agents/guards/");
|
|
11737
12487
|
}
|
|
@@ -11776,10 +12526,10 @@ function evaluateShellHook(root, policy, tool, payload) {
|
|
|
11776
12526
|
if (!shellNames[tool].includes(name)) {
|
|
11777
12527
|
return { decision: "allow" };
|
|
11778
12528
|
}
|
|
11779
|
-
const
|
|
12529
|
+
const command3 = scalar(
|
|
11780
12530
|
input[tool === "antigravity" ? "CommandLine" : "command"]
|
|
11781
12531
|
);
|
|
11782
|
-
if (
|
|
12532
|
+
if (command3 === void 0 || command3.length > 65536) {
|
|
11783
12533
|
return denied();
|
|
11784
12534
|
}
|
|
11785
12535
|
const cwd = scalar(input["Cwd"]) ?? scalar(value["cwd"]) ?? root;
|
|
@@ -11800,7 +12550,7 @@ function evaluateShellHook(root, policy, tool, payload) {
|
|
|
11800
12550
|
input: JSON.stringify({
|
|
11801
12551
|
runtime: tool,
|
|
11802
12552
|
event: "before_shell",
|
|
11803
|
-
command:
|
|
12553
|
+
command: command3,
|
|
11804
12554
|
cwd
|
|
11805
12555
|
}),
|
|
11806
12556
|
encoding: "utf8",
|
|
@@ -11849,8 +12599,8 @@ async function main() {
|
|
|
11849
12599
|
if (!file2) {
|
|
11850
12600
|
throw new Error("Missing hook policy");
|
|
11851
12601
|
}
|
|
11852
|
-
const bytes =
|
|
11853
|
-
const adapter =
|
|
12602
|
+
const bytes = readFileSync22(file2);
|
|
12603
|
+
const adapter = readFileSync22(process.argv[1]);
|
|
11854
12604
|
const actual = createHash11("sha256").update(adapter).update(bytes).digest("hex");
|
|
11855
12605
|
if (actual !== integrity2) {
|
|
11856
12606
|
throw new Error("Hook integrity mismatch");
|
|
@@ -11869,8 +12619,8 @@ async function main() {
|
|
|
11869
12619
|
}
|
|
11870
12620
|
chunks.push(bytes2);
|
|
11871
12621
|
}
|
|
11872
|
-
const root =
|
|
11873
|
-
const guardSha256 = createHash11("sha256").update(
|
|
12622
|
+
const root = resolve12(dirname16(file2), "../..");
|
|
12623
|
+
const guardSha256 = createHash11("sha256").update(readFileSync22(guardPath(root, policy.command))).digest("hex");
|
|
11874
12624
|
if (policy.contractVersion !== 1 || guardSha256 !== policy.guardSha256) {
|
|
11875
12625
|
throw new Error(
|
|
11876
12626
|
"Hook guard source changed; reproject and review native trust"
|
|
@@ -11923,13 +12673,13 @@ function object4(value) {
|
|
|
11923
12673
|
return value;
|
|
11924
12674
|
}
|
|
11925
12675
|
function groups(root, file2, key) {
|
|
11926
|
-
const absolute2 =
|
|
11927
|
-
if (!
|
|
12676
|
+
const absolute2 = join22(root, file2);
|
|
12677
|
+
if (!existsSync18(absolute2)) {
|
|
11928
12678
|
return [];
|
|
11929
12679
|
}
|
|
11930
12680
|
let value;
|
|
11931
12681
|
try {
|
|
11932
|
-
value = JSON.parse(
|
|
12682
|
+
value = JSON.parse(readFileSync23(absolute2, "utf8"));
|
|
11933
12683
|
} catch {
|
|
11934
12684
|
throw new ProjectError(
|
|
11935
12685
|
"Cannot parse native hook configuration; contents are redacted"
|
|
@@ -11953,15 +12703,15 @@ function adapterSource() {
|
|
|
11953
12703
|
const sourceBuild = fileURLToPath2(
|
|
11954
12704
|
new URL("../../dist/project/hook-adapter.js", import.meta.url)
|
|
11955
12705
|
);
|
|
11956
|
-
const file2 =
|
|
11957
|
-
if (!
|
|
12706
|
+
const file2 = existsSync18(published) ? published : sourceBuild;
|
|
12707
|
+
if (!existsSync18(file2)) {
|
|
11958
12708
|
throw new ProjectError("Build pmcp before projecting shell hook adapters");
|
|
11959
12709
|
}
|
|
11960
|
-
return
|
|
12710
|
+
return readFileSync23(file2, "utf8");
|
|
11961
12711
|
}
|
|
11962
12712
|
function hookOutputs(spec) {
|
|
11963
|
-
const lockFile =
|
|
11964
|
-
const lock =
|
|
12713
|
+
const lockFile = join22(spec.root, "pmcp.lock");
|
|
12714
|
+
const lock = existsSync18(lockFile) ? JSON.parse(readFileSync23(lockFile, "utf8")) : {};
|
|
11965
12715
|
const previous = new Map(
|
|
11966
12716
|
(lock.outputs ?? []).filter((entry2) => entry2.managed !== void 0).map((entry2) => [entry2.path, entry2.managed])
|
|
11967
12717
|
);
|
|
@@ -11978,7 +12728,7 @@ function hookOutputs(spec) {
|
|
|
11978
12728
|
outputs.push({ kind: "file", path: ADAPTER, content: adapter });
|
|
11979
12729
|
for (const hook of selected) {
|
|
11980
12730
|
const policyFile = `${HOOK_DIRECTORY}/${hook.name}.json`;
|
|
11981
|
-
const guardSha256 = createHash12("sha256").update(
|
|
12731
|
+
const guardSha256 = createHash12("sha256").update(readFileSync23(guardPath(spec.root, hook.command))).digest("hex");
|
|
11982
12732
|
const policy = `${JSON.stringify({ ...hook, contractVersion: 1, guardSha256 }, null, 2)}
|
|
11983
12733
|
`;
|
|
11984
12734
|
outputs.push({ kind: "file", path: policyFile, content: policy });
|
|
@@ -11988,11 +12738,11 @@ function hookOutputs(spec) {
|
|
|
11988
12738
|
continue;
|
|
11989
12739
|
}
|
|
11990
12740
|
const target2 = native[tool];
|
|
11991
|
-
const
|
|
12741
|
+
const command3 = `d="$PWD"; while [ ! -f "$d/pmcp.toml" ]; do [ "$d" = / ] && exit 1; d=$(dirname "$d"); done; exec node "$d/${ADAPTER}" --pmcp-shell-hook ${tool} "$d/${policyFile}" ${integrity2}`;
|
|
11992
12742
|
const timeout = tool === "gemini" ? hook.timeoutMs + 2e3 : Math.ceil(hook.timeoutMs / 1e3) + 2;
|
|
11993
12743
|
const value = {
|
|
11994
12744
|
matcher: target2.matcher,
|
|
11995
|
-
hooks: [{ type: "command", command:
|
|
12745
|
+
hooks: [{ type: "command", command: command3, timeout }]
|
|
11996
12746
|
};
|
|
11997
12747
|
const path = `${target2.file}#${target2.key.join(".")}`;
|
|
11998
12748
|
const entry2 = desired.get(path) ?? {
|
|
@@ -12042,11 +12792,11 @@ function hookOutputs(spec) {
|
|
|
12042
12792
|
}
|
|
12043
12793
|
|
|
12044
12794
|
// src/project/frontmatter.ts
|
|
12045
|
-
import { parse as
|
|
12795
|
+
import { parse as parse4 } from "yaml";
|
|
12046
12796
|
function splitFrontmatter(source2) {
|
|
12047
12797
|
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/u.exec(source2);
|
|
12048
12798
|
if (!match) return { meta: {}, body: source2 };
|
|
12049
|
-
const meta =
|
|
12799
|
+
const meta = parse4(match[1] ?? "");
|
|
12050
12800
|
const object9 = meta !== null && typeof meta === "object" && !Array.isArray(meta) ? meta : {};
|
|
12051
12801
|
return { meta: object9, body: match[2] ?? "" };
|
|
12052
12802
|
}
|
|
@@ -12076,28 +12826,28 @@ ${lines2.join("\n")}
|
|
|
12076
12826
|
}
|
|
12077
12827
|
|
|
12078
12828
|
// src/project/plan.ts
|
|
12079
|
-
import { existsSync as
|
|
12080
|
-
import { join as
|
|
12829
|
+
import { existsSync as existsSync19, readFileSync as readFileSync24, readdirSync as readdirSync8 } from "fs";
|
|
12830
|
+
import { join as join23 } from "path";
|
|
12081
12831
|
var AGENT_FIELDS = {
|
|
12082
12832
|
claude: ["name", "description", "tools", "model", "effort"],
|
|
12083
12833
|
gemini: ["name", "description"]
|
|
12084
12834
|
};
|
|
12085
12835
|
var SETTINGS = ".claude/settings.json";
|
|
12086
12836
|
function listDirs(dir) {
|
|
12087
|
-
if (!
|
|
12837
|
+
if (!existsSync19(dir)) return [];
|
|
12088
12838
|
return readdirSync8(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
12089
12839
|
}
|
|
12090
12840
|
var tomlString = (value) => JSON.stringify(typeof value === "string" ? value : String(value ?? ""));
|
|
12091
12841
|
function skillNames(root) {
|
|
12092
|
-
const dir =
|
|
12842
|
+
const dir = join23(root, ".agents/skills");
|
|
12093
12843
|
return listDirs(dir).filter(
|
|
12094
|
-
(name) =>
|
|
12844
|
+
(name) => existsSync19(join23(dir, name, "SKILL.md"))
|
|
12095
12845
|
);
|
|
12096
12846
|
}
|
|
12097
12847
|
function agentNames(root) {
|
|
12098
|
-
const dir =
|
|
12848
|
+
const dir = join23(root, ".agents/agents");
|
|
12099
12849
|
return listDirs(dir).filter(
|
|
12100
|
-
(name) =>
|
|
12850
|
+
(name) => existsSync19(join23(dir, name, "agent.md"))
|
|
12101
12851
|
);
|
|
12102
12852
|
}
|
|
12103
12853
|
function skillOutputs(spec) {
|
|
@@ -12131,13 +12881,13 @@ policy:
|
|
|
12131
12881
|
}
|
|
12132
12882
|
function ruleOutputs(spec) {
|
|
12133
12883
|
if (!spec.tools.has("claude")) return [];
|
|
12134
|
-
const dir =
|
|
12135
|
-
if (!
|
|
12884
|
+
const dir = join23(spec.root, ".agents/rules");
|
|
12885
|
+
if (!existsSync19(dir)) return [];
|
|
12136
12886
|
const outputs = [];
|
|
12137
12887
|
for (const file2 of readdirSync8(dir).filter((f) => f.endsWith(".md")).sort()) {
|
|
12138
12888
|
const rel = `.agents/rules/${file2}`;
|
|
12139
12889
|
const { meta, body } = splitFrontmatter(
|
|
12140
|
-
|
|
12890
|
+
readFileSync24(join23(spec.root, rel), "utf8")
|
|
12141
12891
|
);
|
|
12142
12892
|
const globs = [meta["globs"] ?? []].flat().filter((g) => typeof g === "string" && g !== "");
|
|
12143
12893
|
const head = globs.length > 0 ? renderFrontmatter({ paths: globs }) : "";
|
|
@@ -12156,7 +12906,7 @@ function agentOutputs(spec) {
|
|
|
12156
12906
|
for (const name of agentNames(spec.root)) {
|
|
12157
12907
|
const rel = `.agents/agents/${name}/agent.md`;
|
|
12158
12908
|
const { meta, body } = splitFrontmatter(
|
|
12159
|
-
|
|
12909
|
+
readFileSync24(join23(spec.root, rel), "utf8")
|
|
12160
12910
|
);
|
|
12161
12911
|
const overrides = spec.agents[name] ?? {};
|
|
12162
12912
|
const note = `<!-- ${MARK} from ${rel} -->
|
|
@@ -12225,6 +12975,67 @@ function censusDirs(spec) {
|
|
|
12225
12975
|
return dirs;
|
|
12226
12976
|
}
|
|
12227
12977
|
|
|
12978
|
+
// src/project/write.ts
|
|
12979
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
12980
|
+
import {
|
|
12981
|
+
closeSync as closeSync3,
|
|
12982
|
+
fchmodSync as fchmodSync2,
|
|
12983
|
+
fsyncSync,
|
|
12984
|
+
lstatSync as lstatSync4,
|
|
12985
|
+
openSync as openSync3,
|
|
12986
|
+
renameSync as renameSync3,
|
|
12987
|
+
rmSync as rmSync4,
|
|
12988
|
+
symlinkSync,
|
|
12989
|
+
writeFileSync as writeFileSync5
|
|
12990
|
+
} from "fs";
|
|
12991
|
+
import { dirname as dirname17 } from "path";
|
|
12992
|
+
function fileMode(path) {
|
|
12993
|
+
try {
|
|
12994
|
+
const stat = lstatSync4(path);
|
|
12995
|
+
if (stat.isFile()) return stat.mode & 511;
|
|
12996
|
+
} catch (error) {
|
|
12997
|
+
if (error.code !== "ENOENT") throw error;
|
|
12998
|
+
}
|
|
12999
|
+
return 438 & ~process.umask();
|
|
13000
|
+
}
|
|
13001
|
+
function syncDirectory(path) {
|
|
13002
|
+
if (process.platform === "win32") return;
|
|
13003
|
+
const descriptor = openSync3(dirname17(path), "r");
|
|
13004
|
+
try {
|
|
13005
|
+
fsyncSync(descriptor);
|
|
13006
|
+
} finally {
|
|
13007
|
+
closeSync3(descriptor);
|
|
13008
|
+
}
|
|
13009
|
+
}
|
|
13010
|
+
function replaceFile(path, content) {
|
|
13011
|
+
const mode = fileMode(path);
|
|
13012
|
+
const temporary = `${path}.${randomUUID4()}.new`;
|
|
13013
|
+
const descriptor = openSync3(temporary, "wx", 384);
|
|
13014
|
+
try {
|
|
13015
|
+
try {
|
|
13016
|
+
writeFileSync5(descriptor, content);
|
|
13017
|
+
fchmodSync2(descriptor, mode);
|
|
13018
|
+
fsyncSync(descriptor);
|
|
13019
|
+
} finally {
|
|
13020
|
+
closeSync3(descriptor);
|
|
13021
|
+
}
|
|
13022
|
+
renameSync3(temporary, path);
|
|
13023
|
+
syncDirectory(path);
|
|
13024
|
+
} finally {
|
|
13025
|
+
rmSync4(temporary, { force: true });
|
|
13026
|
+
}
|
|
13027
|
+
}
|
|
13028
|
+
function replaceLink(path, target2) {
|
|
13029
|
+
const temporary = `${path}.${randomUUID4()}.new`;
|
|
13030
|
+
try {
|
|
13031
|
+
symlinkSync(target2, temporary);
|
|
13032
|
+
renameSync3(temporary, path);
|
|
13033
|
+
syncDirectory(path);
|
|
13034
|
+
} finally {
|
|
13035
|
+
rmSync4(temporary, { force: true });
|
|
13036
|
+
}
|
|
13037
|
+
}
|
|
13038
|
+
|
|
12228
13039
|
// src/project/apply.ts
|
|
12229
13040
|
var LOCK_FILE = "pmcp.lock";
|
|
12230
13041
|
var BEGIN = "# >>> pmcp project (edit pmcp.toml, not this block) >>>";
|
|
@@ -12235,24 +13046,24 @@ var PREDECESSORS = [
|
|
|
12235
13046
|
];
|
|
12236
13047
|
var sha = (text3) => createHash13("sha256").update(text3).digest("hex");
|
|
12237
13048
|
function readLock(root) {
|
|
12238
|
-
const path =
|
|
12239
|
-
if (!
|
|
12240
|
-
const lock = JSON.parse(
|
|
13049
|
+
const path = join24(root, LOCK_FILE);
|
|
13050
|
+
if (!existsSync20(path)) return [];
|
|
13051
|
+
const lock = JSON.parse(readFileSync25(path, "utf8"));
|
|
12241
13052
|
return lock.outputs ?? [];
|
|
12242
13053
|
}
|
|
12243
13054
|
function linkOf(abs) {
|
|
12244
13055
|
try {
|
|
12245
|
-
return
|
|
13056
|
+
return lstatSync5(abs).isSymbolicLink() ? readlinkSync2(abs) : null;
|
|
12246
13057
|
} catch {
|
|
12247
13058
|
return null;
|
|
12248
13059
|
}
|
|
12249
13060
|
}
|
|
12250
|
-
var exists = (abs) =>
|
|
13061
|
+
var exists = (abs) => existsSync20(abs) || linkOf(abs) !== null;
|
|
12251
13062
|
function readJson2(abs) {
|
|
12252
|
-
if (!
|
|
13063
|
+
if (!existsSync20(abs)) return {};
|
|
12253
13064
|
let value;
|
|
12254
13065
|
try {
|
|
12255
|
-
value = JSON.parse(
|
|
13066
|
+
value = JSON.parse(readFileSync25(abs, "utf8"));
|
|
12256
13067
|
} catch {
|
|
12257
13068
|
throw new ProjectError(
|
|
12258
13069
|
"Cannot parse native JSON configuration; contents are redacted"
|
|
@@ -12304,19 +13115,19 @@ function withoutBlock(text3) {
|
|
|
12304
13115
|
return `${text3.slice(0, start2).replace(/\n+$/u, "\n")}${after}`;
|
|
12305
13116
|
}
|
|
12306
13117
|
function present(root, output) {
|
|
12307
|
-
const abs =
|
|
13118
|
+
const abs = join24(root, "file" in output ? output.file : output.path);
|
|
12308
13119
|
switch (output.kind) {
|
|
12309
13120
|
case "link":
|
|
12310
13121
|
return linkOf(abs) === output.target;
|
|
12311
13122
|
case "file":
|
|
12312
|
-
return
|
|
13123
|
+
return existsSync20(abs) && linkOf(abs) === null && readFileSync25(abs, "utf8") === output.content;
|
|
12313
13124
|
case "json":
|
|
12314
13125
|
if (output.managed && Array.isArray(output.value) && output.value.length === 0) {
|
|
12315
13126
|
return getKey(readJson2(abs), output.key) === void 0;
|
|
12316
13127
|
}
|
|
12317
13128
|
return same(getKey(readJson2(abs), output.key), output.value);
|
|
12318
13129
|
case "block":
|
|
12319
|
-
return
|
|
13130
|
+
return existsSync20(abs) && readFileSync25(abs, "utf8").includes(block(output.content));
|
|
12320
13131
|
}
|
|
12321
13132
|
}
|
|
12322
13133
|
function replaceable(root, output, owned) {
|
|
@@ -12324,28 +13135,28 @@ function replaceable(root, output, owned) {
|
|
|
12324
13135
|
if (output.key[0] !== "mcpServers" || owned.has(output.path)) {
|
|
12325
13136
|
return true;
|
|
12326
13137
|
}
|
|
12327
|
-
const existing = getKey(readJson2(
|
|
13138
|
+
const existing = getKey(readJson2(join24(root, output.file)), output.key);
|
|
12328
13139
|
return existing === void 0 || same(existing, output.value);
|
|
12329
13140
|
}
|
|
12330
13141
|
if (output.kind === "block") {
|
|
12331
|
-
const absolute2 =
|
|
12332
|
-
const text4 =
|
|
12333
|
-
const outside =
|
|
12334
|
-
const proposed =
|
|
13142
|
+
const absolute2 = join24(root, output.file);
|
|
13143
|
+
const text4 = existsSync20(absolute2) ? readFileSync25(absolute2, "utf8") : "";
|
|
13144
|
+
const outside = parse5(withoutBlock(text4));
|
|
13145
|
+
const proposed = parse5(output.content);
|
|
12335
13146
|
const existing = objectKeys(outside["mcp_servers"]);
|
|
12336
13147
|
return !objectKeys(proposed["mcp_servers"]).some(
|
|
12337
13148
|
(name) => existing.includes(name)
|
|
12338
13149
|
);
|
|
12339
13150
|
}
|
|
12340
13151
|
if (owned.has(output.path)) return true;
|
|
12341
|
-
const abs =
|
|
13152
|
+
const abs = join24(root, output.path);
|
|
12342
13153
|
if (!exists(abs)) return true;
|
|
12343
13154
|
const target2 = linkOf(abs);
|
|
12344
13155
|
if (target2 !== null) {
|
|
12345
13156
|
return output.kind === "link" && target2 === output.target;
|
|
12346
13157
|
}
|
|
12347
|
-
if (
|
|
12348
|
-
const text3 =
|
|
13158
|
+
if (lstatSync5(abs).isDirectory()) return false;
|
|
13159
|
+
const text3 = readFileSync25(abs, "utf8");
|
|
12349
13160
|
if (output.kind === "file" && text3 === output.content) return true;
|
|
12350
13161
|
return PREDECESSORS.some((mark) => text3.includes(mark));
|
|
12351
13162
|
}
|
|
@@ -12359,15 +13170,15 @@ function undeclared(spec, outputs) {
|
|
|
12359
13170
|
const declared = new Set(outputs.map((o) => o.path));
|
|
12360
13171
|
const found = [];
|
|
12361
13172
|
for (const dir of censusDirs(spec)) {
|
|
12362
|
-
const abs =
|
|
12363
|
-
if (!
|
|
13173
|
+
const abs = join24(spec.root, dir);
|
|
13174
|
+
if (!existsSync20(abs)) continue;
|
|
12364
13175
|
for (const entry2 of readdirSync9(abs)) {
|
|
12365
13176
|
const path = `${dir}/${entry2}`;
|
|
12366
13177
|
if (!declared.has(path)) found.push(path);
|
|
12367
13178
|
}
|
|
12368
13179
|
}
|
|
12369
|
-
const rules =
|
|
12370
|
-
if (spec.tools.has("grok") &&
|
|
13180
|
+
const rules = join24(spec.root, RULES_DIR);
|
|
13181
|
+
if (spec.tools.has("grok") && existsSync20(rules)) {
|
|
12371
13182
|
for (const entry2 of readdirSync9(rules, { withFileTypes: true })) {
|
|
12372
13183
|
if (entry2.isDirectory()) continue;
|
|
12373
13184
|
found.push(`${RULES_DIR}/${entry2.name}`);
|
|
@@ -12394,9 +13205,9 @@ function deadGlobs(root, outputs) {
|
|
|
12394
13205
|
if (rule.kind === "file") {
|
|
12395
13206
|
text3 = rule.content;
|
|
12396
13207
|
} else if (rule.kind === "link") {
|
|
12397
|
-
const abs =
|
|
12398
|
-
if (!
|
|
12399
|
-
text3 =
|
|
13208
|
+
const abs = join24(root, posix2.join(posix2.dirname(rule.path), rule.target));
|
|
13209
|
+
if (!existsSync20(abs)) continue;
|
|
13210
|
+
text3 = readFileSync25(abs, "utf8");
|
|
12400
13211
|
} else {
|
|
12401
13212
|
continue;
|
|
12402
13213
|
}
|
|
@@ -12446,7 +13257,7 @@ function project(spec, options2) {
|
|
|
12446
13257
|
}
|
|
12447
13258
|
}
|
|
12448
13259
|
for (const path of jsonPaths) {
|
|
12449
|
-
readJson2(
|
|
13260
|
+
readJson2(join24(spec.root, path));
|
|
12450
13261
|
}
|
|
12451
13262
|
if (options2.check) {
|
|
12452
13263
|
const problems = [
|
|
@@ -12508,11 +13319,13 @@ function project(spec, options2) {
|
|
|
12508
13319
|
for (const output of outputs) {
|
|
12509
13320
|
if (output.kind !== "link" && output.kind !== "file") continue;
|
|
12510
13321
|
if (present(spec.root, output)) continue;
|
|
12511
|
-
const abs =
|
|
12512
|
-
mkdirSync7(
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
13322
|
+
const abs = join24(spec.root, output.path);
|
|
13323
|
+
mkdirSync7(dirname18(abs), { recursive: true });
|
|
13324
|
+
if (output.kind === "link") {
|
|
13325
|
+
replaceLink(abs, output.target);
|
|
13326
|
+
continue;
|
|
13327
|
+
}
|
|
13328
|
+
replaceFile(abs, output.content);
|
|
12516
13329
|
}
|
|
12517
13330
|
let pruned = 0;
|
|
12518
13331
|
const jsonFiles = /* @__PURE__ */ new Map();
|
|
@@ -12534,12 +13347,12 @@ function project(spec, options2) {
|
|
|
12534
13347
|
group.drops.push(keyed.key);
|
|
12535
13348
|
jsonFiles.set(keyed.file, group);
|
|
12536
13349
|
} else {
|
|
12537
|
-
|
|
13350
|
+
rmSync5(join24(spec.root, entry2.path), { force: true, recursive: true });
|
|
12538
13351
|
}
|
|
12539
13352
|
pruned += 1;
|
|
12540
13353
|
}
|
|
12541
13354
|
for (const [file2, { sets, drops }] of jsonFiles) {
|
|
12542
|
-
const abs =
|
|
13355
|
+
const abs = join24(spec.root, file2);
|
|
12543
13356
|
const before = readJson2(abs);
|
|
12544
13357
|
const after = structuredClone(before);
|
|
12545
13358
|
for (const key of drops) deleteKey(after, key);
|
|
@@ -12554,22 +13367,22 @@ function project(spec, options2) {
|
|
|
12554
13367
|
}
|
|
12555
13368
|
}
|
|
12556
13369
|
if (same(before, after)) continue;
|
|
12557
|
-
if (!
|
|
12558
|
-
mkdirSync7(
|
|
12559
|
-
|
|
13370
|
+
if (!existsSync20(abs) && Object.keys(after).length === 0) continue;
|
|
13371
|
+
mkdirSync7(dirname18(abs), { recursive: true });
|
|
13372
|
+
replaceFile(abs, `${JSON.stringify(after, null, 2)}
|
|
12560
13373
|
`);
|
|
12561
13374
|
}
|
|
12562
13375
|
for (const [file2, content] of blockFiles) {
|
|
12563
|
-
const abs =
|
|
12564
|
-
const before =
|
|
13376
|
+
const abs = join24(spec.root, file2);
|
|
13377
|
+
const before = existsSync20(abs) ? readFileSync25(abs, "utf8") : "";
|
|
12565
13378
|
let after = withoutBlock(before);
|
|
12566
13379
|
if (content !== null) {
|
|
12567
13380
|
const sep7 = after === "" || after.endsWith("\n\n") ? "" : after.endsWith("\n") ? "\n" : "\n\n";
|
|
12568
13381
|
after = `${after}${sep7}${block(content)}`;
|
|
12569
13382
|
}
|
|
12570
13383
|
if (after === before) continue;
|
|
12571
|
-
mkdirSync7(
|
|
12572
|
-
|
|
13384
|
+
mkdirSync7(dirname18(abs), { recursive: true });
|
|
13385
|
+
replaceFile(abs, after);
|
|
12573
13386
|
}
|
|
12574
13387
|
if (spec.packageRules) {
|
|
12575
13388
|
const linkedDirs = new Set(
|
|
@@ -12580,15 +13393,15 @@ function project(spec, options2) {
|
|
|
12580
13393
|
)
|
|
12581
13394
|
);
|
|
12582
13395
|
for (const [name, target2] of legacyLinks(spec.root)) {
|
|
12583
|
-
const abs =
|
|
12584
|
-
const broken = !
|
|
13396
|
+
const abs = join24(spec.root, RULES_DIR, `${name}.md`);
|
|
13397
|
+
const broken = !existsSync20(abs);
|
|
12585
13398
|
if (!broken && !linkedDirs.has(posix2.dirname(target2))) continue;
|
|
12586
|
-
|
|
13399
|
+
rmSync5(abs, { force: true });
|
|
12587
13400
|
pruned += 1;
|
|
12588
13401
|
}
|
|
12589
13402
|
}
|
|
12590
|
-
|
|
12591
|
-
|
|
13403
|
+
replaceFile(
|
|
13404
|
+
join24(spec.root, LOCK_FILE),
|
|
12592
13405
|
`${JSON.stringify({ version: 1, outputs: outputs.filter((output) => output.kind !== "json" || output.managed === void 0 || output.managed.length > 0).map(lockEntry) }, null, 2)}
|
|
12593
13406
|
`
|
|
12594
13407
|
);
|
|
@@ -12602,11 +13415,11 @@ function project(spec, options2) {
|
|
|
12602
13415
|
}
|
|
12603
13416
|
|
|
12604
13417
|
// src/project/mcp-import.ts
|
|
12605
|
-
import { readFileSync as
|
|
13418
|
+
import { readFileSync as readFileSync26, statSync as statSync11, writeFileSync as writeFileSync6 } from "fs";
|
|
12606
13419
|
import { createHash as createHash14 } from "crypto";
|
|
12607
|
-
import { basename as basename6, dirname as
|
|
13420
|
+
import { basename as basename6, dirname as dirname19, resolve as resolve13 } from "path";
|
|
12608
13421
|
import { homedir as homedir4 } from "os";
|
|
12609
|
-
import { parse as
|
|
13422
|
+
import { parse as parse6, stringify } from "smol-toml";
|
|
12610
13423
|
|
|
12611
13424
|
// src/project/mcp-spec.ts
|
|
12612
13425
|
init_config();
|
|
@@ -12643,13 +13456,13 @@ function readHooks(path, root, raw) {
|
|
|
12643
13456
|
return fail2("unknown shell entry field");
|
|
12644
13457
|
}
|
|
12645
13458
|
const name = entry2["name"];
|
|
12646
|
-
const
|
|
13459
|
+
const command3 = entry2["command"];
|
|
12647
13460
|
const targets = entry2["targets"];
|
|
12648
13461
|
const timeoutMs = entry2["timeout_ms"] ?? 15e3;
|
|
12649
13462
|
if (typeof name !== "string" || !/^[a-z0-9][a-z0-9-]*$/u.test(name) || names.has(name)) {
|
|
12650
13463
|
return fail2("shell names must be unique lowercase names");
|
|
12651
13464
|
}
|
|
12652
|
-
if (!Array.isArray(
|
|
13465
|
+
if (!Array.isArray(command3) || command3.length === 0 || !command3.every(
|
|
12653
13466
|
(part) => typeof part === "string" && part.length > 0 && !part.includes("\0")
|
|
12654
13467
|
)) {
|
|
12655
13468
|
return fail2("command must be a nonempty argument list");
|
|
@@ -12663,7 +13476,7 @@ function readHooks(path, root, raw) {
|
|
|
12663
13476
|
return fail2("timeout_ms must be an integer from 100 to 60000");
|
|
12664
13477
|
}
|
|
12665
13478
|
try {
|
|
12666
|
-
guardPath(root,
|
|
13479
|
+
guardPath(root, command3);
|
|
12667
13480
|
} catch (error) {
|
|
12668
13481
|
return fail2(
|
|
12669
13482
|
error instanceof Error ? error.message : "invalid guard path"
|
|
@@ -12672,7 +13485,7 @@ function readHooks(path, root, raw) {
|
|
|
12672
13485
|
names.add(name);
|
|
12673
13486
|
return {
|
|
12674
13487
|
name,
|
|
12675
|
-
command:
|
|
13488
|
+
command: command3,
|
|
12676
13489
|
targets,
|
|
12677
13490
|
timeoutMs
|
|
12678
13491
|
};
|
|
@@ -12895,13 +13708,13 @@ function readMcp(path, value) {
|
|
|
12895
13708
|
fail(path, where, `unknown field ${key}`);
|
|
12896
13709
|
}
|
|
12897
13710
|
}
|
|
12898
|
-
const
|
|
13711
|
+
const command3 = entry2["command"];
|
|
12899
13712
|
const url = entry2["url"];
|
|
12900
|
-
if (
|
|
13713
|
+
if (command3 === void 0 === (url === void 0)) {
|
|
12901
13714
|
fail(path, where, "provide exactly one of command or url");
|
|
12902
13715
|
}
|
|
12903
13716
|
for (const [key, item] of [
|
|
12904
|
-
["command",
|
|
13717
|
+
["command", command3],
|
|
12905
13718
|
["url", url],
|
|
12906
13719
|
["cwd", entry2["cwd"]]
|
|
12907
13720
|
]) {
|
|
@@ -12999,7 +13812,7 @@ function readMcp(path, value) {
|
|
|
12999
13812
|
fail(path, where, "mailbox must be a boolean");
|
|
13000
13813
|
}
|
|
13001
13814
|
servers[alias] = {
|
|
13002
|
-
...typeof
|
|
13815
|
+
...typeof command3 === "string" ? { command: command3 } : { url },
|
|
13003
13816
|
args: list(path, `${where} args`, entry2["args"], false),
|
|
13004
13817
|
env,
|
|
13005
13818
|
...envVars.length > 0 ? { envVars } : {},
|
|
@@ -13034,11 +13847,11 @@ function sourceServers(source2, root) {
|
|
|
13034
13847
|
"Use --source <claude|codex|gemini|grok|antigravity>:<file>"
|
|
13035
13848
|
);
|
|
13036
13849
|
}
|
|
13037
|
-
const path =
|
|
13850
|
+
const path = resolve13(root, source2.slice(split + 1));
|
|
13038
13851
|
if (statSync11(path).size > 2 * 1024 * 1024) {
|
|
13039
13852
|
throw new ProjectError("MCP source exceeds the 2 MiB limit");
|
|
13040
13853
|
}
|
|
13041
|
-
const bytes =
|
|
13854
|
+
const bytes = readFileSync26(path);
|
|
13042
13855
|
const sha2562 = createHash14("sha256").update(bytes).digest("hex");
|
|
13043
13856
|
const text3 = bytes.toString("utf8").replace(/^\uFEFF/u, "");
|
|
13044
13857
|
let parsed;
|
|
@@ -13046,7 +13859,7 @@ function sourceServers(source2, root) {
|
|
|
13046
13859
|
if (text3.trim() === "") {
|
|
13047
13860
|
parsed = {};
|
|
13048
13861
|
} else if (path.endsWith(".toml")) {
|
|
13049
|
-
parsed =
|
|
13862
|
+
parsed = parse6(text3);
|
|
13050
13863
|
} else {
|
|
13051
13864
|
parsed = object6(JSON.parse(text3));
|
|
13052
13865
|
}
|
|
@@ -13229,9 +14042,9 @@ function equivalent(alias, left, right) {
|
|
|
13229
14042
|
return JSON.stringify(readMcp("import", { [alias]: left })[alias]) === JSON.stringify(readMcp("import", { [alias]: right })[alias]);
|
|
13230
14043
|
}
|
|
13231
14044
|
function planMcpImport(config, sources2, names = []) {
|
|
13232
|
-
const original =
|
|
13233
|
-
const root =
|
|
13234
|
-
const existing = object6(
|
|
14045
|
+
const original = readFileSync26(config, "utf8");
|
|
14046
|
+
const root = dirname19(resolve13(config));
|
|
14047
|
+
const existing = object6(parse6(original)["mcp"] ?? {});
|
|
13235
14048
|
const additions = {};
|
|
13236
14049
|
const entries = [];
|
|
13237
14050
|
const fingerprints = [];
|
|
@@ -13280,11 +14093,11 @@ function writeMcpImport(plan2) {
|
|
|
13280
14093
|
"Resolve blocked or conflicting MCP entries before importing"
|
|
13281
14094
|
);
|
|
13282
14095
|
}
|
|
13283
|
-
if (
|
|
14096
|
+
if (readFileSync26(plan2.config, "utf8") !== plan2.original) {
|
|
13284
14097
|
throw new ProjectError("pmcp.toml changed after planning; plan again");
|
|
13285
14098
|
}
|
|
13286
14099
|
for (const source2 of plan2.sources) {
|
|
13287
|
-
const current = createHash14("sha256").update(
|
|
14100
|
+
const current = createHash14("sha256").update(readFileSync26(source2.path)).digest("hex");
|
|
13288
14101
|
if (current !== source2.sha256) {
|
|
13289
14102
|
throw new ProjectError("MCP source changed after planning; plan again");
|
|
13290
14103
|
}
|
|
@@ -13293,8 +14106,8 @@ function writeMcpImport(plan2) {
|
|
|
13293
14106
|
const after = `${plan2.original.trimEnd()}
|
|
13294
14107
|
|
|
13295
14108
|
${additions}`;
|
|
13296
|
-
|
|
13297
|
-
const backup = backupFiles(
|
|
14109
|
+
parse6(after);
|
|
14110
|
+
const backup = backupFiles(dirname19(resolve13(plan2.config)), [
|
|
13298
14111
|
basename6(plan2.config)
|
|
13299
14112
|
]);
|
|
13300
14113
|
writeFileSync6(plan2.config, after);
|
|
@@ -13303,25 +14116,25 @@ ${additions}`;
|
|
|
13303
14116
|
|
|
13304
14117
|
// src/project/mcp-env.ts
|
|
13305
14118
|
import { spawn } from "child_process";
|
|
13306
|
-
import { readFileSync as
|
|
14119
|
+
import { readFileSync as readFileSync27, statSync as statSync12 } from "fs";
|
|
13307
14120
|
import { homedir as homedir5 } from "os";
|
|
13308
|
-
import { resolve as
|
|
13309
|
-
import { parse as
|
|
14121
|
+
import { resolve as resolve14 } from "path";
|
|
14122
|
+
import { parse as parse7 } from "smol-toml";
|
|
13310
14123
|
function sourcePath(file2, root) {
|
|
13311
14124
|
if (file2.startsWith("~/")) {
|
|
13312
|
-
return
|
|
14125
|
+
return resolve14(homedir5(), file2.slice(2));
|
|
13313
14126
|
}
|
|
13314
|
-
return
|
|
14127
|
+
return resolve14(root, file2.replaceAll("${PROJECT_ROOT}", root));
|
|
13315
14128
|
}
|
|
13316
14129
|
function sourceValue(file2, pointer) {
|
|
13317
14130
|
try {
|
|
13318
14131
|
if (statSync12(file2).size > 2 * 1024 * 1024) {
|
|
13319
14132
|
throw new Error("size");
|
|
13320
14133
|
}
|
|
13321
|
-
const text3 =
|
|
14134
|
+
const text3 = readFileSync27(file2, "utf8").replace(/^\uFEFF/u, "");
|
|
13322
14135
|
let value;
|
|
13323
14136
|
if (file2.endsWith(".toml")) {
|
|
13324
|
-
value =
|
|
14137
|
+
value = parse7(text3);
|
|
13325
14138
|
} else {
|
|
13326
14139
|
value = JSON.parse(text3);
|
|
13327
14140
|
}
|
|
@@ -13399,11 +14212,11 @@ async function execMcp(spec, alias, env) {
|
|
|
13399
14212
|
|
|
13400
14213
|
// src/project/doctor.ts
|
|
13401
14214
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
13402
|
-
import { existsSync as
|
|
13403
|
-
import { join as
|
|
13404
|
-
import { parse as
|
|
13405
|
-
var spawnRunner = (
|
|
13406
|
-
const result = spawnSync3(
|
|
14215
|
+
import { existsSync as existsSync21, readFileSync as readFileSync28 } from "fs";
|
|
14216
|
+
import { join as join25 } from "path";
|
|
14217
|
+
import { parse as parse8 } from "smol-toml";
|
|
14218
|
+
var spawnRunner = (command3, args, cwd, env) => {
|
|
14219
|
+
const result = spawnSync3(command3, [...args], {
|
|
13407
14220
|
cwd,
|
|
13408
14221
|
...env ? { env: { ...process.env, ...env } } : {},
|
|
13409
14222
|
encoding: "utf8",
|
|
@@ -13477,10 +14290,10 @@ function claude(spec, o) {
|
|
|
13477
14290
|
}
|
|
13478
14291
|
function codex(spec, o) {
|
|
13479
14292
|
const findings = [];
|
|
13480
|
-
const config =
|
|
14293
|
+
const config = join25(o.home, ".codex/config.toml");
|
|
13481
14294
|
let trusted = false;
|
|
13482
|
-
if (
|
|
13483
|
-
const projects =
|
|
14295
|
+
if (existsSync21(config)) {
|
|
14296
|
+
const projects = parse8(readFileSync28(config, "utf8")).projects ?? {};
|
|
13484
14297
|
const root = spec.root.replace(/\/$/u, "");
|
|
13485
14298
|
trusted = projects[root]?.trust_level === "trusted" || projects[`${root}/`]?.trust_level === "trusted";
|
|
13486
14299
|
}
|
|
@@ -13606,8 +14419,8 @@ function grok(spec, o) {
|
|
|
13606
14419
|
}
|
|
13607
14420
|
function antigravity(spec, options2) {
|
|
13608
14421
|
const foreign = agentNames(spec.root).filter((name) => {
|
|
13609
|
-
const text3 =
|
|
13610
|
-
|
|
14422
|
+
const text3 = readFileSync28(
|
|
14423
|
+
join25(spec.root, ".agents/agents", name, "agent.md"),
|
|
13611
14424
|
"utf8"
|
|
13612
14425
|
);
|
|
13613
14426
|
return splitFrontmatter(text3).meta["tools"] !== void 0;
|
|
@@ -13627,7 +14440,7 @@ function antigravity(spec, options2) {
|
|
|
13627
14440
|
if (serversForTool(spec, "antigravity").length > 0) {
|
|
13628
14441
|
const result = options2.run(
|
|
13629
14442
|
"agy",
|
|
13630
|
-
["plugin", "validate",
|
|
14443
|
+
["plugin", "validate", join25(spec.root, ANTIGRAVITY_MCP_PLUGIN)],
|
|
13631
14444
|
spec.root
|
|
13632
14445
|
);
|
|
13633
14446
|
findings.push(
|
|
@@ -13658,9 +14471,9 @@ function doctor(spec, options2) {
|
|
|
13658
14471
|
}
|
|
13659
14472
|
|
|
13660
14473
|
// src/project/memory.ts
|
|
13661
|
-
import { existsSync as
|
|
13662
|
-
import { isAbsolute as isAbsolute7, join as
|
|
13663
|
-
import { parse as
|
|
14474
|
+
import { existsSync as existsSync22, readFileSync as readFileSync29 } from "fs";
|
|
14475
|
+
import { isAbsolute as isAbsolute7, join as join26, resolve as resolve15 } from "path";
|
|
14476
|
+
import { parse as parse9 } from "smol-toml";
|
|
13664
14477
|
function object7(value) {
|
|
13665
14478
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
13666
14479
|
throw new Error("Configuration is not an object");
|
|
@@ -13668,11 +14481,11 @@ function object7(value) {
|
|
|
13668
14481
|
return value;
|
|
13669
14482
|
}
|
|
13670
14483
|
function settings(file2, toml = false) {
|
|
13671
|
-
if (!
|
|
14484
|
+
if (!existsSync22(file2)) {
|
|
13672
14485
|
return {};
|
|
13673
14486
|
}
|
|
13674
|
-
const text3 =
|
|
13675
|
-
return object7(toml ?
|
|
14487
|
+
const text3 = readFileSync29(file2, "utf8");
|
|
14488
|
+
return object7(toml ? parse9(text3) : JSON.parse(text3));
|
|
13676
14489
|
}
|
|
13677
14490
|
function configuredState(value) {
|
|
13678
14491
|
if (value === true) {
|
|
@@ -13684,11 +14497,11 @@ function configuredState(value) {
|
|
|
13684
14497
|
return "unknown";
|
|
13685
14498
|
}
|
|
13686
14499
|
function located(metadata2, path, pathScope) {
|
|
13687
|
-
return { ...metadata2, path, pathScope, exists:
|
|
14500
|
+
return { ...metadata2, path, pathScope, exists: existsSync22(path) };
|
|
13688
14501
|
}
|
|
13689
14502
|
function metadata(tool, spec, options2) {
|
|
13690
14503
|
const env = options2.env ?? {};
|
|
13691
|
-
const directory = (name, fallback) =>
|
|
14504
|
+
const directory = (name, fallback) => resolve15(spec.root, env[name] ?? join26(options2.home, fallback));
|
|
13692
14505
|
if (tool === "antigravity") {
|
|
13693
14506
|
return {
|
|
13694
14507
|
state: "unsupported",
|
|
@@ -13712,7 +14525,7 @@ function metadata(tool, spec, options2) {
|
|
|
13712
14525
|
state,
|
|
13713
14526
|
source: result.missing ? "Native CLI not installed" : "codex features list; active session overrides are not inspected"
|
|
13714
14527
|
},
|
|
13715
|
-
|
|
14528
|
+
join26(home2, "memories"),
|
|
13716
14529
|
"store"
|
|
13717
14530
|
);
|
|
13718
14531
|
}
|
|
@@ -13725,12 +14538,12 @@ function metadata(tool, spec, options2) {
|
|
|
13725
14538
|
}
|
|
13726
14539
|
if (tool === "claude") {
|
|
13727
14540
|
const home2 = directory("CLAUDE_CONFIG_DIR", ".claude");
|
|
13728
|
-
const user2 = settings(
|
|
14541
|
+
const user2 = settings(join26(home2, "settings.json"));
|
|
13729
14542
|
const state = env["CLAUDE_CODE_DISABLE_AUTO_MEMORY"] === "1" ? "disabled" : configuredState(user2["autoMemoryEnabled"]);
|
|
13730
14543
|
const source2 = "User configuration and disable environment flag; project trust and active session overrides are not inspected";
|
|
13731
14544
|
const override = user2["autoMemoryDirectory"];
|
|
13732
14545
|
if (typeof override === "string") {
|
|
13733
|
-
const path = override.startsWith("~/") ?
|
|
14546
|
+
const path = override.startsWith("~/") ? join26(options2.home, override.slice(2)) : override;
|
|
13734
14547
|
if (!isAbsolute7(path)) {
|
|
13735
14548
|
return {
|
|
13736
14549
|
state: "unknown",
|
|
@@ -13743,7 +14556,7 @@ function metadata(tool, spec, options2) {
|
|
|
13743
14556
|
if (env["CLAUDE_CONFIG_DIR"] && projectName && !projectName.includes("/") && !projectName.includes("\\") && projectName !== "." && projectName !== "..") {
|
|
13744
14557
|
return located(
|
|
13745
14558
|
{ state, source: source2 },
|
|
13746
|
-
|
|
14559
|
+
join26(home2, "projects", projectName, "memory"),
|
|
13747
14560
|
"store"
|
|
13748
14561
|
);
|
|
13749
14562
|
}
|
|
@@ -13752,14 +14565,14 @@ function metadata(tool, spec, options2) {
|
|
|
13752
14565
|
state,
|
|
13753
14566
|
source: `${source2}; native project directory encoding is not guessed`
|
|
13754
14567
|
},
|
|
13755
|
-
|
|
14568
|
+
join26(home2, "projects"),
|
|
13756
14569
|
"base"
|
|
13757
14570
|
);
|
|
13758
14571
|
}
|
|
13759
14572
|
if (tool === "grok") {
|
|
13760
|
-
const home2 =
|
|
13761
|
-
const user2 = settings(
|
|
13762
|
-
const project2 = settings(
|
|
14573
|
+
const home2 = join26(options2.home, ".grok");
|
|
14574
|
+
const user2 = settings(join26(home2, "config.toml"), true);
|
|
14575
|
+
const project2 = settings(join26(spec.root, ".grok/config.toml"), true);
|
|
13763
14576
|
const userMemory = user2["memory"] === void 0 ? {} : object7(user2["memory"]);
|
|
13764
14577
|
const projectMemory = project2["memory"] === void 0 ? {} : object7(project2["memory"]);
|
|
13765
14578
|
const overridden = projectMemory["enabled"] !== void 0;
|
|
@@ -13769,12 +14582,12 @@ function metadata(tool, spec, options2) {
|
|
|
13769
14582
|
state,
|
|
13770
14583
|
source: "User memory configuration; project trust and workspace encoding are not exposed by a read-only native memory command"
|
|
13771
14584
|
},
|
|
13772
|
-
|
|
14585
|
+
join26(home2, "memory-v2/workspaces"),
|
|
13773
14586
|
"base"
|
|
13774
14587
|
);
|
|
13775
14588
|
}
|
|
13776
14589
|
const home = directory("GEMINI_CLI_HOME", ".gemini");
|
|
13777
|
-
const user = settings(
|
|
14590
|
+
const user = settings(join26(home, "settings.json"));
|
|
13778
14591
|
const context = user["context"] === void 0 ? {} : object7(user["context"]);
|
|
13779
14592
|
const names = context["fileName"] ?? "GEMINI.md";
|
|
13780
14593
|
const contextFileNames = typeof names === "string" ? [names] : names;
|
|
@@ -13790,7 +14603,7 @@ function metadata(tool, spec, options2) {
|
|
|
13790
14603
|
source: "Global memory file existence and user context filenames; active memory tool enablement is not exposed",
|
|
13791
14604
|
contextFileNames
|
|
13792
14605
|
},
|
|
13793
|
-
|
|
14606
|
+
join26(home, "GEMINI.md"),
|
|
13794
14607
|
"store"
|
|
13795
14608
|
);
|
|
13796
14609
|
}
|
|
@@ -13823,14 +14636,14 @@ function memoryFindings(spec, options2) {
|
|
|
13823
14636
|
// src/project/edit.ts
|
|
13824
14637
|
import {
|
|
13825
14638
|
cpSync,
|
|
13826
|
-
existsSync as
|
|
14639
|
+
existsSync as existsSync23,
|
|
13827
14640
|
mkdirSync as mkdirSync8,
|
|
13828
|
-
readFileSync as
|
|
13829
|
-
rmSync as
|
|
14641
|
+
readFileSync as readFileSync30,
|
|
14642
|
+
rmSync as rmSync6,
|
|
13830
14643
|
statSync as statSync13,
|
|
13831
14644
|
writeFileSync as writeFileSync7
|
|
13832
14645
|
} from "fs";
|
|
13833
|
-
import { basename as basename7, dirname as
|
|
14646
|
+
import { basename as basename7, dirname as dirname20, join as join27 } from "path";
|
|
13834
14647
|
var CROSS_TOOL = ["name", "description"];
|
|
13835
14648
|
var tomlValue2 = (value) => JSON.stringify(value);
|
|
13836
14649
|
function findEntry(entries, wanted) {
|
|
@@ -13854,7 +14667,7 @@ function addEntry(root, entry2) {
|
|
|
13854
14667
|
`${entry2.name} is a ${kind2}; only skills and agents are added to .agents/`
|
|
13855
14668
|
);
|
|
13856
14669
|
}
|
|
13857
|
-
const { meta, body } = splitFrontmatter(
|
|
14670
|
+
const { meta, body } = splitFrontmatter(readFileSync30(entry2.path, "utf8"));
|
|
13858
14671
|
const name = basename7(entry2.slug);
|
|
13859
14672
|
if (!/^[a-z0-9][a-z0-9-]*$/u.test(name)) {
|
|
13860
14673
|
throw new ProjectError(
|
|
@@ -13862,11 +14675,11 @@ function addEntry(root, entry2) {
|
|
|
13862
14675
|
);
|
|
13863
14676
|
}
|
|
13864
14677
|
if (kind2 === "skill") {
|
|
13865
|
-
const dest2 =
|
|
13866
|
-
if (
|
|
14678
|
+
const dest2 = join27(root, ".agents/skills", name);
|
|
14679
|
+
if (existsSync23(dest2))
|
|
13867
14680
|
throw new ProjectError(`.agents/skills/${name} already exists`);
|
|
13868
|
-
const source2 = entry2.root ??
|
|
13869
|
-
mkdirSync8(
|
|
14681
|
+
const source2 = entry2.root ?? dirname20(entry2.path);
|
|
14682
|
+
mkdirSync8(dirname20(dest2), { recursive: true });
|
|
13870
14683
|
if (statSync13(source2).isDirectory()) {
|
|
13871
14684
|
cpSync(source2, dest2, { recursive: true });
|
|
13872
14685
|
} else {
|
|
@@ -13878,14 +14691,14 @@ function addEntry(root, entry2) {
|
|
|
13878
14691
|
description: meta["description"] ?? entry2.description
|
|
13879
14692
|
});
|
|
13880
14693
|
writeFileSync7(
|
|
13881
|
-
|
|
14694
|
+
join27(dest2, "SKILL.md"),
|
|
13882
14695
|
`${head}
|
|
13883
14696
|
${body.replace(/^\n/u, "")}`
|
|
13884
14697
|
);
|
|
13885
14698
|
return { kind: kind2, name, path: `.agents/skills/${name}` };
|
|
13886
14699
|
}
|
|
13887
|
-
const dest =
|
|
13888
|
-
if (
|
|
14700
|
+
const dest = join27(root, ".agents/agents", name);
|
|
14701
|
+
if (existsSync23(dest))
|
|
13889
14702
|
throw new ProjectError(`.agents/agents/${name} already exists`);
|
|
13890
14703
|
const kept = {};
|
|
13891
14704
|
const moved = {};
|
|
@@ -13895,17 +14708,17 @@ ${body.replace(/^\n/u, "")}`
|
|
|
13895
14708
|
}
|
|
13896
14709
|
mkdirSync8(dest, { recursive: true });
|
|
13897
14710
|
writeFileSync7(
|
|
13898
|
-
|
|
14711
|
+
join27(dest, "agent.md"),
|
|
13899
14712
|
`${renderFrontmatter(kept)}
|
|
13900
14713
|
${body.replace(/^\n/u, "")}`
|
|
13901
14714
|
);
|
|
13902
14715
|
if (Object.keys(moved).length > 0) {
|
|
13903
|
-
const config =
|
|
14716
|
+
const config = join27(root, "pmcp.toml");
|
|
13904
14717
|
const lines2 = [
|
|
13905
14718
|
`[agents.${name}.claude]`,
|
|
13906
14719
|
...Object.entries(moved).map(([k, v]) => `${k} = ${tomlValue2(v)}`)
|
|
13907
14720
|
];
|
|
13908
|
-
const before =
|
|
14721
|
+
const before = existsSync23(config) ? readFileSync30(config, "utf8") : "";
|
|
13909
14722
|
const sep7 = before === "" || before.endsWith("\n\n") ? "" : "\n";
|
|
13910
14723
|
writeFileSync7(config, `${before}${sep7}${lines2.join("\n")}
|
|
13911
14724
|
`);
|
|
@@ -13928,15 +14741,15 @@ function withoutTables(toml, name) {
|
|
|
13928
14741
|
function removeAsset(root, name) {
|
|
13929
14742
|
const removed = [];
|
|
13930
14743
|
for (const rel of [`.agents/skills/${name}`, `.agents/agents/${name}`]) {
|
|
13931
|
-
if (!
|
|
13932
|
-
|
|
14744
|
+
if (!existsSync23(join27(root, rel))) continue;
|
|
14745
|
+
rmSync6(join27(root, rel), { recursive: true, force: true });
|
|
13933
14746
|
removed.push(rel);
|
|
13934
14747
|
}
|
|
13935
14748
|
if (removed.length === 0)
|
|
13936
14749
|
throw new ProjectError(`no skill or agent named ${name} in .agents/`);
|
|
13937
|
-
const config =
|
|
13938
|
-
if (
|
|
13939
|
-
const before =
|
|
14750
|
+
const config = join27(root, "pmcp.toml");
|
|
14751
|
+
if (existsSync23(config)) {
|
|
14752
|
+
const before = readFileSync30(config, "utf8");
|
|
13940
14753
|
const after = withoutTables(before, name);
|
|
13941
14754
|
if (after !== before) writeFileSync7(config, after);
|
|
13942
14755
|
}
|
|
@@ -14175,16 +14988,16 @@ var doctorCommand = {
|
|
|
14175
14988
|
};
|
|
14176
14989
|
|
|
14177
14990
|
// src/validate.ts
|
|
14178
|
-
import { existsSync as
|
|
14179
|
-
import { basename as basename8, join as
|
|
14991
|
+
import { existsSync as existsSync24, readFileSync as readFileSync31, statSync as statSync14 } from "fs";
|
|
14992
|
+
import { basename as basename8, join as join28, resolve as resolve16 } from "path";
|
|
14180
14993
|
function validateSkillDir(dir) {
|
|
14181
|
-
const root =
|
|
14182
|
-
const skillMd = basename8(root) === "SKILL.md" ? root :
|
|
14183
|
-
if (!
|
|
14994
|
+
const root = resolve16(dir);
|
|
14995
|
+
const skillMd = basename8(root) === "SKILL.md" ? root : join28(root, "SKILL.md");
|
|
14996
|
+
if (!existsSync24(skillMd) || !statSync14(skillMd).isFile()) {
|
|
14184
14997
|
return [{ path: root, reason: "no SKILL.md", level: "error" }];
|
|
14185
14998
|
}
|
|
14186
|
-
const front = frontmatterObject(
|
|
14187
|
-
const directory = basename8(skillMd === root ?
|
|
14999
|
+
const front = frontmatterObject(readFileSync31(skillMd, "utf8"));
|
|
15000
|
+
const directory = basename8(skillMd === root ? join28(root, "..") : root);
|
|
14188
15001
|
return specProblems(front, directory).map((reason) => ({
|
|
14189
15002
|
path: skillMd,
|
|
14190
15003
|
reason,
|
|
@@ -14229,8 +15042,8 @@ var validateCommand = {
|
|
|
14229
15042
|
};
|
|
14230
15043
|
|
|
14231
15044
|
// src/commands/mailbox.ts
|
|
14232
|
-
import { readFileSync as
|
|
14233
|
-
import { resolve as
|
|
15045
|
+
import { readFileSync as readFileSync32, writeFileSync as writeFileSync8 } from "fs";
|
|
15046
|
+
import { resolve as resolve17 } from "path";
|
|
14234
15047
|
init_config2();
|
|
14235
15048
|
init_service();
|
|
14236
15049
|
init_schemas();
|
|
@@ -14313,7 +15126,7 @@ var mailboxCommand = {
|
|
|
14313
15126
|
}
|
|
14314
15127
|
if (verb === "init") {
|
|
14315
15128
|
const config2 = initializeMailboxConfig(
|
|
14316
|
-
|
|
15129
|
+
resolve17(context.cwd, one(context.args, "config") ?? "pmcp.toml"),
|
|
14317
15130
|
one(context.args, "state-dir")
|
|
14318
15131
|
);
|
|
14319
15132
|
const store2 = await MailboxStore.open(config2);
|
|
@@ -14338,7 +15151,7 @@ var mailboxCommand = {
|
|
|
14338
15151
|
if (verb === "restore") {
|
|
14339
15152
|
await restoreBackup(required(context, "input"), {
|
|
14340
15153
|
...config,
|
|
14341
|
-
databasePath:
|
|
15154
|
+
databasePath: resolve17(context.cwd, required(context, "output"))
|
|
14342
15155
|
});
|
|
14343
15156
|
context.ui.data(
|
|
14344
15157
|
`${JSON.stringify({ ok: true, data: { restored: true } })}
|
|
@@ -14412,7 +15225,7 @@ var mailboxCommand = {
|
|
|
14412
15225
|
ownsBinding = true;
|
|
14413
15226
|
}
|
|
14414
15227
|
const inputFile = one(context.args, "input");
|
|
14415
|
-
const input = inputFile ? JSON.parse(
|
|
15228
|
+
const input = inputFile ? JSON.parse(readFileSync32(inputFile, "utf8")) : {};
|
|
14416
15229
|
const result = await new MailboxService(store, binding).call(
|
|
14417
15230
|
operation,
|
|
14418
15231
|
input
|
|
@@ -14496,7 +15309,7 @@ var harnessOptions = [
|
|
|
14496
15309
|
];
|
|
14497
15310
|
|
|
14498
15311
|
// src/commands/harness/invocation.ts
|
|
14499
|
-
import { readFileSync as
|
|
15312
|
+
import { readFileSync as readFileSync33, statSync as statSync15 } from "fs";
|
|
14500
15313
|
init_validation();
|
|
14501
15314
|
var common = ["group-file", "generation"];
|
|
14502
15315
|
var options = {
|
|
@@ -14536,7 +15349,7 @@ function file(path) {
|
|
|
14536
15349
|
const stat = statSync15(path);
|
|
14537
15350
|
if (!stat.isFile() || stat.size > 65536)
|
|
14538
15351
|
throw new ArgumentError("Input file exceeds 65536 bytes");
|
|
14539
|
-
return
|
|
15352
|
+
return readFileSync33(path, "utf8");
|
|
14540
15353
|
}
|
|
14541
15354
|
function parseInvocation(context) {
|
|
14542
15355
|
const [family, action, ...extra] = context.args.positional;
|
|
@@ -14642,6 +15455,9 @@ var harnessCommand = {
|
|
|
14642
15455
|
}
|
|
14643
15456
|
};
|
|
14644
15457
|
|
|
15458
|
+
// src/commands/tui.ts
|
|
15459
|
+
init_config();
|
|
15460
|
+
|
|
14645
15461
|
// src/commands/tui/invocation.ts
|
|
14646
15462
|
var tuiOptions = [
|
|
14647
15463
|
{
|
|
@@ -14657,6 +15473,11 @@ var tuiOptions = [
|
|
|
14657
15473
|
},
|
|
14658
15474
|
{ name: "rows", describe: "Terminal rows (12\u20131000)", placeholder: "<count>" },
|
|
14659
15475
|
{ name: "index", describe: "Worker dock index", placeholder: "<count>" },
|
|
15476
|
+
{
|
|
15477
|
+
name: "memory-mb",
|
|
15478
|
+
describe: "Worker memory reservation",
|
|
15479
|
+
placeholder: "<count>"
|
|
15480
|
+
},
|
|
14660
15481
|
{
|
|
14661
15482
|
name: "confirm-session",
|
|
14662
15483
|
describe: "Exact target ID for stop",
|
|
@@ -14664,9 +15485,24 @@ var tuiOptions = [
|
|
|
14664
15485
|
},
|
|
14665
15486
|
{
|
|
14666
15487
|
name: "config",
|
|
14667
|
-
describe: "
|
|
15488
|
+
describe: "Project pmcp.toml (or mailbox config for create)",
|
|
14668
15489
|
placeholder: "<file>"
|
|
14669
15490
|
},
|
|
15491
|
+
{
|
|
15492
|
+
name: "runtime",
|
|
15493
|
+
describe: "Native runtime ID for main launch or worker start",
|
|
15494
|
+
placeholder: "<runtime>"
|
|
15495
|
+
},
|
|
15496
|
+
{
|
|
15497
|
+
name: "yes",
|
|
15498
|
+
describe: "Confirm the first native runtime start",
|
|
15499
|
+
boolean: true
|
|
15500
|
+
},
|
|
15501
|
+
{
|
|
15502
|
+
name: "new",
|
|
15503
|
+
describe: "Create a workspace after the previous group is stopped",
|
|
15504
|
+
boolean: true
|
|
15505
|
+
},
|
|
14670
15506
|
{
|
|
14671
15507
|
name: "actor-file",
|
|
14672
15508
|
describe: "Optional private mailbox observer credential",
|
|
@@ -14674,6 +15510,10 @@ var tuiOptions = [
|
|
|
14674
15510
|
}
|
|
14675
15511
|
];
|
|
14676
15512
|
var allowed2 = {
|
|
15513
|
+
launch: ["config", "runtime", "yes", "new"],
|
|
15514
|
+
plan: ["config", "runtime"],
|
|
15515
|
+
recovery: ["config"],
|
|
15516
|
+
"start-worker": ["config", "view", "runtime", "memory-mb"],
|
|
14677
15517
|
doctor: [],
|
|
14678
15518
|
create: ["group-file", "columns", "rows", "config", "actor-file"],
|
|
14679
15519
|
inspect: ["view"],
|
|
@@ -14691,10 +15531,12 @@ var allowed2 = {
|
|
|
14691
15531
|
close: ["view"],
|
|
14692
15532
|
dismiss: ["view"],
|
|
14693
15533
|
management: ["view"],
|
|
15534
|
+
"worker-menu": ["view"],
|
|
14694
15535
|
check: ["view"]
|
|
14695
15536
|
};
|
|
14696
15537
|
function parseTui(args) {
|
|
14697
|
-
|
|
15538
|
+
let [action, ...extra] = args.positional;
|
|
15539
|
+
if (action === void 0) action = one(args, "view") ? "open" : "launch";
|
|
14698
15540
|
const options2 = action ? allowed2[action] : void 0;
|
|
14699
15541
|
if (!action || !options2 || extra.length)
|
|
14700
15542
|
throw new ArgumentError("Provide one supported TUI operation");
|
|
@@ -14714,6 +15556,33 @@ function parseTui(args) {
|
|
|
14714
15556
|
return Number(value);
|
|
14715
15557
|
};
|
|
14716
15558
|
if (action === "doctor") return { action, input: {} };
|
|
15559
|
+
if (action === "start-worker") {
|
|
15560
|
+
const config = one(args, "config");
|
|
15561
|
+
const viewFile = one(args, "view");
|
|
15562
|
+
if (config && viewFile)
|
|
15563
|
+
throw new ArgumentError("Choose --config or --view for worker start");
|
|
15564
|
+
return {
|
|
15565
|
+
action,
|
|
15566
|
+
viewFile,
|
|
15567
|
+
input: {},
|
|
15568
|
+
startup: {
|
|
15569
|
+
config,
|
|
15570
|
+
runtime: required2("runtime"),
|
|
15571
|
+
memoryMb: number("memory-mb")
|
|
15572
|
+
}
|
|
15573
|
+
};
|
|
15574
|
+
}
|
|
15575
|
+
if (action === "launch" || action === "plan" || action === "recovery")
|
|
15576
|
+
return {
|
|
15577
|
+
action,
|
|
15578
|
+
input: {},
|
|
15579
|
+
startup: {
|
|
15580
|
+
config: one(args, "config"),
|
|
15581
|
+
runtime: one(args, "runtime"),
|
|
15582
|
+
fresh: args.flags.has("new"),
|
|
15583
|
+
confirmed: args.flags.has("yes")
|
|
15584
|
+
}
|
|
15585
|
+
};
|
|
14717
15586
|
if (action === "create") {
|
|
14718
15587
|
const config = one(args, "config");
|
|
14719
15588
|
const actorFile = one(args, "actor-file");
|
|
@@ -14753,19 +15622,52 @@ function parseTui(args) {
|
|
|
14753
15622
|
// src/commands/tui.ts
|
|
14754
15623
|
var tuiCommand = {
|
|
14755
15624
|
name: "tui",
|
|
14756
|
-
describe: "
|
|
14757
|
-
usage: "pmcp tui doctor |
|
|
15625
|
+
describe: "Open a native terminal workspace with optional configuration",
|
|
15626
|
+
usage: "pmcp tui [--config <file>] [--runtime <id>] | plan | recovery | doctor | start-worker --runtime <id> | operation --view <file>",
|
|
14758
15627
|
options: tuiOptions,
|
|
14759
15628
|
async run(context) {
|
|
14760
15629
|
const invocation2 = parseTui(context.args);
|
|
14761
15630
|
const { invokeTui: invokeTui2, failedInvocation: failedInvocation2 } = await Promise.resolve().then(() => (init_invoke2(), invoke_exports2));
|
|
14762
15631
|
try {
|
|
14763
|
-
|
|
14764
|
-
if (invocation2.action
|
|
15632
|
+
let receipt;
|
|
15633
|
+
if (invocation2.action === "start-worker") {
|
|
15634
|
+
if (invocation2.viewFile) {
|
|
15635
|
+
const { startViewWorker: startViewWorker2 } = await Promise.resolve().then(() => (init_worker_start(), worker_start_exports));
|
|
15636
|
+
receipt = await startViewWorker2(
|
|
15637
|
+
invocation2.viewFile,
|
|
15638
|
+
invocation2.startup.runtime,
|
|
15639
|
+
invocation2.startup?.memoryMb
|
|
15640
|
+
);
|
|
15641
|
+
} else {
|
|
15642
|
+
const { startWorkspaceWorker: startWorkspaceWorker2 } = await Promise.resolve().then(() => (init_worker(), worker_exports));
|
|
15643
|
+
receipt = await startWorkspaceWorker2(
|
|
15644
|
+
{ config: invocation2.startup?.config, cwd: context.cwd },
|
|
15645
|
+
invocation2.startup.runtime,
|
|
15646
|
+
invocation2.startup?.memoryMb
|
|
15647
|
+
);
|
|
15648
|
+
}
|
|
15649
|
+
} else if (invocation2.action === "recovery") {
|
|
15650
|
+
const { inspectStartupRecovery: inspectStartupRecovery2 } = await Promise.resolve().then(() => (init_recovery2(), recovery_exports));
|
|
15651
|
+
receipt = inspectStartupRecovery2({
|
|
15652
|
+
...invocation2.startup,
|
|
15653
|
+
cwd: context.cwd
|
|
15654
|
+
});
|
|
15655
|
+
} else if (invocation2.action === "launch" || invocation2.action === "plan") {
|
|
15656
|
+
const { launchStartup: launchStartup2, planStartup: planStartup2 } = await Promise.resolve().then(() => (init_entry2(), entry_exports));
|
|
15657
|
+
const request3 = { ...invocation2.startup, cwd: context.cwd };
|
|
15658
|
+
receipt = invocation2.action === "plan" ? planStartup2(request3) : await launchStartup2(
|
|
15659
|
+
request3,
|
|
15660
|
+
invocation2.startup?.confirmed === true
|
|
15661
|
+
);
|
|
15662
|
+
} else {
|
|
15663
|
+
receipt = await invokeTui2(invocation2);
|
|
15664
|
+
}
|
|
15665
|
+
if (!["open", "launch"].includes(invocation2.action) || receipt.ok !== true)
|
|
14765
15666
|
context.ui.data(`${JSON.stringify(receipt)}
|
|
14766
15667
|
`);
|
|
14767
15668
|
return receipt.ok === true ? 0 : 1;
|
|
14768
15669
|
} catch (error) {
|
|
15670
|
+
if (error instanceof ConfigError) throw new ArgumentError(error.message);
|
|
14769
15671
|
context.ui.data(
|
|
14770
15672
|
`${JSON.stringify(failedInvocation2(invocation2.action, error))}
|
|
14771
15673
|
`
|
|
@@ -14780,14 +15682,14 @@ import { resolve as resolve21 } from "path";
|
|
|
14780
15682
|
|
|
14781
15683
|
// src/marketplace/export.ts
|
|
14782
15684
|
import {
|
|
14783
|
-
existsSync as
|
|
14784
|
-
lstatSync as
|
|
14785
|
-
mkdirSync as
|
|
14786
|
-
readFileSync as
|
|
15685
|
+
existsSync as existsSync37,
|
|
15686
|
+
lstatSync as lstatSync14,
|
|
15687
|
+
mkdirSync as mkdirSync13,
|
|
15688
|
+
readFileSync as readFileSync38,
|
|
14787
15689
|
readdirSync as readdirSync13,
|
|
14788
15690
|
writeFileSync as writeFileSync14
|
|
14789
15691
|
} from "fs";
|
|
14790
|
-
import { dirname as
|
|
15692
|
+
import { dirname as dirname27, join as join55, resolve as resolve20 } from "path";
|
|
14791
15693
|
var NATIVE_PLUGIN_RUNTIMES = [
|
|
14792
15694
|
"claude",
|
|
14793
15695
|
"codex",
|
|
@@ -14812,7 +15714,7 @@ function readResources(directory, prefix, files) {
|
|
|
14812
15714
|
for (const entry2 of readdirSync13(directory, { withFileTypes: true })) {
|
|
14813
15715
|
if (entry2.isSymbolicLink())
|
|
14814
15716
|
throw new Error("Plugin exports cannot contain symlinks");
|
|
14815
|
-
const path =
|
|
15717
|
+
const path = join55(directory, entry2.name);
|
|
14816
15718
|
const relativePath = `${prefix}/${entry2.name}`;
|
|
14817
15719
|
if (entry2.isDirectory()) {
|
|
14818
15720
|
if (relativePath.split("/").length > 6)
|
|
@@ -14822,10 +15724,10 @@ function readResources(directory, prefix, files) {
|
|
|
14822
15724
|
}
|
|
14823
15725
|
if (!entry2.isFile())
|
|
14824
15726
|
throw new Error("Plugin resources must be regular files");
|
|
14825
|
-
const stats =
|
|
15727
|
+
const stats = lstatSync14(path);
|
|
14826
15728
|
if (stats.size > 1024 * 1024 || files.size >= 256)
|
|
14827
15729
|
throw new Error("Plugin resources exceed the export budget");
|
|
14828
|
-
files.set(relativePath,
|
|
15730
|
+
files.set(relativePath, readFileSync38(path));
|
|
14829
15731
|
}
|
|
14830
15732
|
}
|
|
14831
15733
|
function pluginExportFiles(root, name, runtime2) {
|
|
@@ -14834,7 +15736,7 @@ function pluginExportFiles(root, name, runtime2) {
|
|
|
14834
15736
|
const files = /* @__PURE__ */ new Map();
|
|
14835
15737
|
readResources(containedPath(plugin.directory, "skills"), "skills", files);
|
|
14836
15738
|
for (const name2 of ["LICENSE", "NOTICE"])
|
|
14837
|
-
files.set(name2,
|
|
15739
|
+
files.set(name2, readFileSync38(containedPath(root, name2)));
|
|
14838
15740
|
const manifests = {
|
|
14839
15741
|
claude: [".claude-plugin/plugin.json", ".mcp.json"],
|
|
14840
15742
|
codex: ["plugin.json", "mcp.json"],
|
|
@@ -14843,10 +15745,10 @@ function pluginExportFiles(root, name, runtime2) {
|
|
|
14843
15745
|
agy: []
|
|
14844
15746
|
};
|
|
14845
15747
|
for (const filename of manifests[runtime2]) {
|
|
14846
|
-
if (
|
|
15748
|
+
if (existsSync37(join55(plugin.directory, filename)))
|
|
14847
15749
|
files.set(
|
|
14848
15750
|
filename,
|
|
14849
|
-
|
|
15751
|
+
readFileSync38(containedPath(plugin.directory, filename))
|
|
14850
15752
|
);
|
|
14851
15753
|
}
|
|
14852
15754
|
if (runtime2 === "agy") {
|
|
@@ -14857,11 +15759,11 @@ function pluginExportFiles(root, name, runtime2) {
|
|
|
14857
15759
|
`
|
|
14858
15760
|
)
|
|
14859
15761
|
);
|
|
14860
|
-
const mcp =
|
|
14861
|
-
if (
|
|
15762
|
+
const mcp = join55(plugin.directory, ".mcp.json");
|
|
15763
|
+
if (existsSync37(mcp))
|
|
14862
15764
|
files.set(
|
|
14863
15765
|
"mcp_config.json",
|
|
14864
|
-
|
|
15766
|
+
readFileSync38(containedPath(plugin.directory, mcp))
|
|
14865
15767
|
);
|
|
14866
15768
|
}
|
|
14867
15769
|
return files;
|
|
@@ -14869,9 +15771,9 @@ function pluginExportFiles(root, name, runtime2) {
|
|
|
14869
15771
|
function exportPublicPlugin(root, name, runtime2, output) {
|
|
14870
15772
|
const destination = resolve20(output);
|
|
14871
15773
|
const files = pluginExportFiles(root, name, runtime2);
|
|
14872
|
-
|
|
15774
|
+
mkdirSync13(dirname27(destination), { recursive: true });
|
|
14873
15775
|
try {
|
|
14874
|
-
|
|
15776
|
+
mkdirSync13(destination);
|
|
14875
15777
|
} catch (error) {
|
|
14876
15778
|
if (error.code === "EEXIST")
|
|
14877
15779
|
throw new Error(
|
|
@@ -14880,8 +15782,8 @@ function exportPublicPlugin(root, name, runtime2, output) {
|
|
|
14880
15782
|
throw error;
|
|
14881
15783
|
}
|
|
14882
15784
|
for (const [path, bytes] of files) {
|
|
14883
|
-
const target2 =
|
|
14884
|
-
|
|
15785
|
+
const target2 = join55(destination, path);
|
|
15786
|
+
mkdirSync13(dirname27(target2), { recursive: true });
|
|
14885
15787
|
writeFileSync14(target2, bytes, { flag: "wx" });
|
|
14886
15788
|
}
|
|
14887
15789
|
return { plugin: name, runtime: runtime2, output: destination, files: files.size };
|
|
@@ -15002,10 +15904,10 @@ function dispatch(argv, options2 = {}) {
|
|
|
15002
15904
|
});
|
|
15003
15905
|
const [verb, ...rest] = rest0;
|
|
15004
15906
|
if (verb === void 0) {
|
|
15005
|
-
return
|
|
15907
|
+
return defaultEntry(options2, { ui, env, cwd });
|
|
15006
15908
|
}
|
|
15007
15909
|
if (verb === "--help" || verb === "-h" || verb === "help") {
|
|
15008
|
-
const named2 = COMMANDS.find((
|
|
15910
|
+
const named2 = COMMANDS.find((command4) => command4.name === rest[0]);
|
|
15009
15911
|
if (named2) printCommandHelp(ui, named2);
|
|
15010
15912
|
else printHelp(ui, COMMANDS, PLANNED);
|
|
15011
15913
|
return 0;
|
|
@@ -15018,8 +15920,8 @@ function dispatch(argv, options2 = {}) {
|
|
|
15018
15920
|
if (verb.startsWith("-")) {
|
|
15019
15921
|
return run(serveCommand, rest0, { ui, env, cwd });
|
|
15020
15922
|
}
|
|
15021
|
-
const
|
|
15022
|
-
if (
|
|
15923
|
+
const command3 = COMMANDS.find((candidate2) => candidate2.name === verb);
|
|
15924
|
+
if (command3 === void 0) {
|
|
15023
15925
|
if (PLANNED.includes(verb))
|
|
15024
15926
|
ui.error(`"${verb}" is designed but not implemented yet`);
|
|
15025
15927
|
else ui.error(`unknown command "${verb}"`);
|
|
@@ -15027,22 +15929,45 @@ function dispatch(argv, options2 = {}) {
|
|
|
15027
15929
|
return 2;
|
|
15028
15930
|
}
|
|
15029
15931
|
if (rest.includes("--help") || rest.includes("-h")) {
|
|
15030
|
-
printCommandHelp(ui,
|
|
15932
|
+
printCommandHelp(ui, command3);
|
|
15031
15933
|
return 0;
|
|
15032
15934
|
}
|
|
15033
|
-
return run(
|
|
15935
|
+
return run(command3, rest, { ui, env, cwd });
|
|
15936
|
+
}
|
|
15937
|
+
var VERSION = "0.16.0";
|
|
15938
|
+
async function defaultEntry(options2, context) {
|
|
15939
|
+
const terminal = options2.terminal ?? {
|
|
15940
|
+
stdin: process.stdin.isTTY === true,
|
|
15941
|
+
stdout: process.stdout.isTTY === true
|
|
15942
|
+
};
|
|
15943
|
+
const mode = entryMode({
|
|
15944
|
+
...terminal,
|
|
15945
|
+
term: context.env.TERM,
|
|
15946
|
+
ci: context.env.CI
|
|
15947
|
+
});
|
|
15948
|
+
if (mode === "server") return run(serveCommand, [], context);
|
|
15949
|
+
if (mode === "terminal") {
|
|
15950
|
+
const { terminalSupport: terminalSupport2 } = await Promise.resolve().then(() => (init_host(), host_exports));
|
|
15951
|
+
const support = terminalSupport2();
|
|
15952
|
+
if (support.supported) return run(tuiCommand, [], context);
|
|
15953
|
+
context.ui.info(`Terminal workspace unavailable: ${support.reason}`);
|
|
15954
|
+
context.ui.line(
|
|
15955
|
+
"Run pmcp tui doctor for prerequisites, or pmcp serve for MCP."
|
|
15956
|
+
);
|
|
15957
|
+
}
|
|
15958
|
+
printHelp(context.ui, COMMANDS, PLANNED);
|
|
15959
|
+
return 0;
|
|
15034
15960
|
}
|
|
15035
|
-
|
|
15036
|
-
async function run(command2, argv, context) {
|
|
15961
|
+
async function run(command3, argv, context) {
|
|
15037
15962
|
try {
|
|
15038
|
-
return await
|
|
15963
|
+
return await command3.run({
|
|
15039
15964
|
...context,
|
|
15040
|
-
args: parseArgs(argv,
|
|
15965
|
+
args: parseArgs(argv, command3.options)
|
|
15041
15966
|
});
|
|
15042
15967
|
} catch (error) {
|
|
15043
15968
|
if (error instanceof ArgumentError) {
|
|
15044
15969
|
context.ui.error(error.message);
|
|
15045
|
-
printCommandHelp(context.ui,
|
|
15970
|
+
printCommandHelp(context.ui, command3);
|
|
15046
15971
|
return 2;
|
|
15047
15972
|
}
|
|
15048
15973
|
context.ui.error(error instanceof Error ? error.message : "command failed");
|