@scp-dev/cli 0.4.0 → 0.6.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/dist/{chunk-2LIJXXTL.js → chunk-BSX24XMZ.js} +89 -20
- package/dist/{chunk-VQU5CPE4.js → chunk-C5PYVRJE.js} +2 -2
- package/dist/{chunk-IEFXYBX7.js → chunk-C7ZUACP7.js} +1 -1
- package/dist/{chunk-HHVNSHGB.js → chunk-CVW3INZO.js} +5 -5
- package/dist/{chunk-VW7DYSGM.js → chunk-DEK3TE6T.js} +3 -3
- package/dist/{chunk-B2G3XTW7.js → chunk-GG33NSBW.js} +5 -1
- package/dist/claude-NCPNZLIX.js +290 -0
- package/dist/cli.js +1 -1
- package/dist/{codex-4WPBAFAT.js → codex-4GTXURTS.js} +33 -26
- package/dist/{hooks-ISZTCOLZ.js → hooks-SWVIDONV.js} +2 -2
- package/dist/{install-SDLWXW32.js → install-YE7LCGPC.js} +3 -3
- package/dist/{join-FITJVZCA.js → join-64BU6SEY.js} +6 -2
- package/dist/{login-AUMLI4FS.js → login-4IF26HH5.js} +2 -2
- package/dist/{main-STUKDZ5L.js → main-RA3MP25N.js} +72 -28
- package/dist/{mcp-J2CL6A4R.js → mcp-RERRUUIX.js} +31 -20
- package/dist/{mesh-UG6VO7ZO.js → mesh-XTMBRJLR.js} +1 -1
- package/dist/{opencode-WFP57BZD.js → opencode-CPNR2G3W.js} +4 -4
- package/dist/{peek-YTCWVPF7.js → peek-GEYQ7SHV.js} +1 -1
- package/dist/{pi-QEZVFDNR.js → pi-XHZAUDP4.js} +1 -1
- package/dist/{relay-QWJM372C.js → relay-E4JFR33E.js} +1 -1
- package/dist/{remote-P4GFUIAP.js → remote-3R4GRDGI.js} +2 -2
- package/dist/{setup-R5SNWVVF.js → setup-O45SE5L7.js} +4 -4
- package/dist/{spawn-WJYOTAKI.js → spawn-CJSHYE7E.js} +5 -5
- package/dist/{up-UWV5BQUH.js → up-O2CMDGXP.js} +5 -5
- package/package.json +6 -6
- package/dist/claude-L2I5ZM2B.js +0 -130
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "./chunk-MNTIEWKF.js";
|
|
7
7
|
|
|
8
8
|
// src/relay.ts
|
|
9
|
-
import { chmodSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
9
|
+
import { chmodSync as chmodSync2, mkdirSync, readFileSync as readFileSync2, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
10
10
|
import { join as join3, dirname as dirname2 } from "path";
|
|
11
11
|
import { randomUUID } from "crypto";
|
|
12
12
|
import { CLI_NAME as CLI_NAME3 } from "@scp-dev/core";
|
|
@@ -16,13 +16,13 @@ import { spawn } from "child_process";
|
|
|
16
16
|
import { closeSync, openSync, statSync, truncateSync } from "fs";
|
|
17
17
|
import { join as join2 } from "path";
|
|
18
18
|
import { CLI_NAME as CLI_NAME2 } from "@scp-dev/core";
|
|
19
|
-
import { connectBroker, privateDir, relayDir as
|
|
19
|
+
import { connectBroker, privateDir as privateDir2, relayDir as relayDir3 } from "@scp-dev/broker";
|
|
20
20
|
|
|
21
21
|
// src/identity.ts
|
|
22
|
-
import { existsSync, realpathSync } from "fs";
|
|
22
|
+
import { chmodSync, existsSync, readFileSync, realpathSync, rmSync, writeFileSync } from "fs";
|
|
23
23
|
import { tmpdir } from "os";
|
|
24
24
|
import { basename, dirname, isAbsolute, join, resolve } from "path";
|
|
25
|
-
import { shortHostname } from "@scp-dev/broker";
|
|
25
|
+
import { privateDir, relayDir, shortHostname } from "@scp-dev/broker";
|
|
26
26
|
import { GENERIC_HARNESSES, LIMITS, validHandle, validHarness } from "@scp-dev/core";
|
|
27
27
|
var SHORT = { "claude-code": "claude" };
|
|
28
28
|
var SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "zsh", "dash", "fish"]);
|
|
@@ -34,6 +34,61 @@ function detectHarness(env) {
|
|
|
34
34
|
if (env.ANTIGRAVITY_AGENT === "1" || env.ANTIGRAVITY_CONVERSATION_ID) return "agy";
|
|
35
35
|
if (env.CLAUDECODE === "1") return "claude-code";
|
|
36
36
|
if (env.CODEX_THREAD_ID) return "codex";
|
|
37
|
+
if (inferring(env) && (env.CODEX_SANDBOX || env.CODEX_SANDBOX_NETWORK_DISABLED)) return "codex";
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
var BINDING_KEY = /^[0-9a-f]{1,128}$/;
|
|
41
|
+
var BINDING_PROCESS = /^pid:([1-9][0-9]*):[A-Za-z0-9:-]{1,64}$/;
|
|
42
|
+
function codexShell(env) {
|
|
43
|
+
return env.SCP_HARNESS === "codex" || inferring(env) && detectHarness(env) === "codex";
|
|
44
|
+
}
|
|
45
|
+
function writeShimBinding(claim, env) {
|
|
46
|
+
const pid = BINDING_PROCESS.exec(claim.process)?.[1];
|
|
47
|
+
if (claim.harness !== "codex" || !pid) return () => {
|
|
48
|
+
};
|
|
49
|
+
const text = JSON.stringify({ process: claim.process, handle: claim.handle, harness: claim.harness });
|
|
50
|
+
const names = [`pid-${pid}`, ...BINDING_KEY.test(env.SCP_SHIM_BINDING ?? "") ? [`key-${env.SCP_SHIM_BINDING}`] : []];
|
|
51
|
+
const paths = [];
|
|
52
|
+
for (const name of names) {
|
|
53
|
+
try {
|
|
54
|
+
const path = join(privateDir(join(relayDir(env), "bindings")), `${name}.json`);
|
|
55
|
+
writeFileSync(path, text, { mode: 384 });
|
|
56
|
+
chmodSync(path, 384);
|
|
57
|
+
paths.push(path);
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return () => {
|
|
62
|
+
for (const path of paths) {
|
|
63
|
+
try {
|
|
64
|
+
if (readFileSync(path, "utf8") === text) rmSync(path);
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function readShimBinding(env, pid, table) {
|
|
71
|
+
const read = (name, expectedPid) => {
|
|
72
|
+
try {
|
|
73
|
+
const binding = JSON.parse(readFileSync(join(relayDir(env), "bindings", `${name}.json`), "utf8"));
|
|
74
|
+
const match = typeof binding.process === "string" ? BINDING_PROCESS.exec(binding.process) : null;
|
|
75
|
+
if (match && Number(match[1]) > 1 && (expectedPid === void 0 || Number(match[1]) === expectedPid) && binding.harness === "codex" && typeof binding.handle === "string" && validHandle(binding.handle))
|
|
76
|
+
return binding;
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
return void 0;
|
|
80
|
+
};
|
|
81
|
+
if (BINDING_KEY.test(env.SCP_SHIM_BINDING ?? "")) {
|
|
82
|
+
const binding = read(`key-${env.SCP_SHIM_BINDING}`);
|
|
83
|
+
if (binding) return binding;
|
|
84
|
+
}
|
|
85
|
+
const seen = /* @__PURE__ */ new Set();
|
|
86
|
+
while (pid > 1 && !seen.has(pid)) {
|
|
87
|
+
seen.add(pid);
|
|
88
|
+
const entry = table?.get(pid);
|
|
89
|
+
if (!entry) return read(`pid-${pid}`, pid);
|
|
90
|
+
pid = entry.ppid;
|
|
91
|
+
}
|
|
37
92
|
return void 0;
|
|
38
93
|
}
|
|
39
94
|
function normalizeHarness(raw, fallback) {
|
|
@@ -319,6 +374,7 @@ function hookHarnessPid(env, harness, ppid = process.ppid, parentOf = psParent)
|
|
|
319
374
|
if (Number.isInteger(pid) && pid > 1) return pid;
|
|
320
375
|
return harnessPid(ppid, parentOf);
|
|
321
376
|
}
|
|
377
|
+
var HARNESS_SESSION = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
322
378
|
function relayIdentity(input) {
|
|
323
379
|
const resolved = resolveHarness(input.harness, input.env, input.fallbackHarness);
|
|
324
380
|
const pid = input.harnessPid(resolved);
|
|
@@ -327,6 +383,7 @@ function relayIdentity(input) {
|
|
|
327
383
|
const handle = input.as ?? input.env.SCP_NAME ?? deriveHandle(harness, cwd);
|
|
328
384
|
const harnessProcess = processKey(pid, input.startOf);
|
|
329
385
|
const keys = input.lineageOf ? input.lineageOf(pid) : lineage(pid);
|
|
386
|
+
const harnessSession = harness === "claude-code" ? [input.harnessSession, input.env.CLAUDE_CODE_SESSION_ID].find((id) => id !== void 0 && HARNESS_SESSION.test(id)) : void 0;
|
|
330
387
|
return {
|
|
331
388
|
handle,
|
|
332
389
|
harness,
|
|
@@ -336,15 +393,16 @@ function relayIdentity(input) {
|
|
|
336
393
|
labels: labelsFor(input.env),
|
|
337
394
|
...input.env.SCP_PARENT ? { parent: input.env.SCP_PARENT } : {},
|
|
338
395
|
...keys.length ? { lineage: keys } : {},
|
|
339
|
-
...input.env.SCP_GRANT ? { grant: input.env.SCP_GRANT } : {}
|
|
396
|
+
...input.env.SCP_GRANT ? { grant: input.env.SCP_GRANT } : {},
|
|
397
|
+
...harnessSession ? { harness_session: harnessSession } : {}
|
|
340
398
|
};
|
|
341
399
|
}
|
|
342
400
|
|
|
343
401
|
// src/session.ts
|
|
344
402
|
import { CLI_NAME } from "@scp-dev/core";
|
|
345
|
-
import { readSessionMarker, relayDir } from "@scp-dev/broker";
|
|
403
|
+
import { readSessionMarker, relayDir as relayDir2 } from "@scp-dev/broker";
|
|
346
404
|
function membership(env, session) {
|
|
347
|
-
const marker = readSessionMarker(
|
|
405
|
+
const marker = readSessionMarker(relayDir2(env), session);
|
|
348
406
|
if (marker?.joined === false) return { dark: `this session left the relay; ${CLI_NAME} join (or relay_join) joins it again` };
|
|
349
407
|
if (!marker?.joined && joinMode(env) === "manual") return { dark: `this session has not joined the relay; ${CLI_NAME} join <name> (or relay_join) joins it` };
|
|
350
408
|
return marker?.handle ? { handle: marker.handle } : {};
|
|
@@ -411,6 +469,7 @@ var OPTIONS = {
|
|
|
411
469
|
who: { type: "boolean" },
|
|
412
470
|
peek: { type: "string" },
|
|
413
471
|
endpoint: { type: "string" },
|
|
472
|
+
"take-over": { type: "boolean" },
|
|
414
473
|
live: { type: "boolean" },
|
|
415
474
|
parent: { type: "string" }
|
|
416
475
|
};
|
|
@@ -471,7 +530,7 @@ var DAEMON_ENV = [
|
|
|
471
530
|
];
|
|
472
531
|
var LOG_MAX_BYTES = 1024 * 1024;
|
|
473
532
|
function startDaemon(dir) {
|
|
474
|
-
|
|
533
|
+
privateDir2(dir);
|
|
475
534
|
const logFile = join2(dir, "broker.log");
|
|
476
535
|
if ((statSync(logFile, { throwIfNoEntry: false })?.size ?? 0) > LOG_MAX_BYTES) truncateSync(logFile, 0);
|
|
477
536
|
const log = openSync(logFile, "a", 384);
|
|
@@ -499,11 +558,18 @@ function localHandle(raw) {
|
|
|
499
558
|
if (handle.includes("/")) throw new RelayUsage(`--as names a local handle, not ${JSON.stringify(raw)}`);
|
|
500
559
|
return handle;
|
|
501
560
|
}
|
|
502
|
-
function walkNow() {
|
|
503
|
-
const
|
|
504
|
-
const self = harnessLineage(
|
|
561
|
+
function walkNow(env = process.env, pid = process.ppid, table = readPsTable()) {
|
|
562
|
+
const keys = lineage(pid, table);
|
|
563
|
+
const self = harnessLineage(pid, table);
|
|
564
|
+
if (!self.length && codexShell(env)) {
|
|
565
|
+
const binding = readShimBinding(env, pid, table);
|
|
566
|
+
if (binding) {
|
|
567
|
+
const bound = [...keys, binding.process];
|
|
568
|
+
return { keys: bound, self: bound, harness: "codex" };
|
|
569
|
+
}
|
|
570
|
+
}
|
|
505
571
|
const harness = self.length ? harnessAt(Number(/^pid:(\d+)/.exec(self.at(-1))?.[1]), table) : void 0;
|
|
506
|
-
return { keys
|
|
572
|
+
return { keys, self, ...harness ? { harness } : {} };
|
|
507
573
|
}
|
|
508
574
|
function claimHarness(values, env, walk) {
|
|
509
575
|
return values.harness ?? env.SCP_HARNESS ?? (inferring(env) ? walk?.harness ?? detectHarness(env) : void 0) ?? "cli";
|
|
@@ -529,7 +595,7 @@ async function open(verb, values, cwd, bodyFromStdin, walk) {
|
|
|
529
595
|
`${CLI_NAME2} ${verb} needs an identity: --as HANDLE (or $SCP_NAME) for an agent, or --human for your own inbox me/ (no terminal is attached, so me/ is not assumed)`
|
|
530
596
|
);
|
|
531
597
|
}
|
|
532
|
-
const { machineName, openCloud } = await import("./remote-
|
|
598
|
+
const { machineName, openCloud } = await import("./remote-3R4GRDGI.js");
|
|
533
599
|
const handle2 = named ? localHandle(named) : void 0;
|
|
534
600
|
const claim = handle2 ? {
|
|
535
601
|
handle: handle2,
|
|
@@ -558,7 +624,7 @@ async function open(verb, values, cwd, bodyFromStdin, walk) {
|
|
|
558
624
|
const handle = "handle" in joined && joined.handle ? joined.handle : named ? localHandle(named) : void 0;
|
|
559
625
|
const harnessPid2 = self.length ? Number(/^pid:(\d+)/.exec(self.at(-1))?.[1]) : void 0;
|
|
560
626
|
const lineageClaim = keys.length ? { lineage: keys } : {};
|
|
561
|
-
const dir =
|
|
627
|
+
const dir = relayDir3();
|
|
562
628
|
const client = await connectBroker(dir, () => startDaemon(dir));
|
|
563
629
|
try {
|
|
564
630
|
let bound;
|
|
@@ -688,7 +754,7 @@ function checkRelayUrl(text, what = "relay URL") {
|
|
|
688
754
|
function readObject(path) {
|
|
689
755
|
let text;
|
|
690
756
|
try {
|
|
691
|
-
text =
|
|
757
|
+
text = readFileSync2(path, "utf8");
|
|
692
758
|
} catch (error) {
|
|
693
759
|
if (error.code === "ENOENT") return void 0;
|
|
694
760
|
throw new RelayUsage(`could not read ${path}`);
|
|
@@ -704,15 +770,15 @@ function readObject(path) {
|
|
|
704
770
|
function writePrivateJson(path, value) {
|
|
705
771
|
const dir = dirname2(path);
|
|
706
772
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
707
|
-
|
|
773
|
+
chmodSync2(dir, 448);
|
|
708
774
|
const temp = join3(dir, `.${randomUUID()}.tmp`);
|
|
709
775
|
try {
|
|
710
|
-
|
|
776
|
+
writeFileSync2(temp, `${JSON.stringify(value, null, 2)}
|
|
711
777
|
`, { mode: 384, flag: "wx" });
|
|
712
|
-
|
|
778
|
+
chmodSync2(temp, 384);
|
|
713
779
|
renameSync(temp, path);
|
|
714
780
|
} finally {
|
|
715
|
-
|
|
781
|
+
rmSync2(temp, { force: true });
|
|
716
782
|
}
|
|
717
783
|
}
|
|
718
784
|
function activeRelay(env = process.env) {
|
|
@@ -787,7 +853,7 @@ function relayVerb(positionals, asJson, env = process.env) {
|
|
|
787
853
|
}
|
|
788
854
|
async function loginMain(positionals, values) {
|
|
789
855
|
if (positionals.length) throw new RelayUsage(`${CLI_NAME3} login takes only flags`);
|
|
790
|
-
return (await import("./login-
|
|
856
|
+
return (await import("./login-4IF26HH5.js")).login(values);
|
|
791
857
|
}
|
|
792
858
|
function logoutMain(positionals, asJson, env = process.env) {
|
|
793
859
|
if (positionals.length) throw new RelayUsage(`${CLI_NAME3} logout takes no arguments`);
|
|
@@ -802,6 +868,8 @@ function logoutMain(positionals, asJson, env = process.env) {
|
|
|
802
868
|
export {
|
|
803
869
|
shortHostname,
|
|
804
870
|
detectHarness,
|
|
871
|
+
codexShell,
|
|
872
|
+
writeShimBinding,
|
|
805
873
|
normalizeHarness,
|
|
806
874
|
hookHarness,
|
|
807
875
|
gitToplevel,
|
|
@@ -835,6 +903,7 @@ export {
|
|
|
835
903
|
OPTIONS,
|
|
836
904
|
SPAWN_OPTIONS,
|
|
837
905
|
startDaemon,
|
|
906
|
+
terminalAttached,
|
|
838
907
|
localHandle,
|
|
839
908
|
walkNow,
|
|
840
909
|
open,
|
|
@@ -10,11 +10,11 @@ import {
|
|
|
10
10
|
mergeHooks,
|
|
11
11
|
musePlugin,
|
|
12
12
|
opencodePlugin
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-GG33NSBW.js";
|
|
14
14
|
import {
|
|
15
15
|
RelayUsage,
|
|
16
16
|
gitToplevel
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-BSX24XMZ.js";
|
|
18
18
|
|
|
19
19
|
// src/install.ts
|
|
20
20
|
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
agySettingsFile,
|
|
4
4
|
mcpFile,
|
|
5
5
|
mountedEntry
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-DEK3TE6T.js";
|
|
7
7
|
import {
|
|
8
8
|
steerPrompt
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-GG33NSBW.js";
|
|
10
10
|
import {
|
|
11
11
|
OPTIONS,
|
|
12
12
|
RelayUsage,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
selectorList,
|
|
19
19
|
spawnerKey,
|
|
20
20
|
walkNow
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-BSX24XMZ.js";
|
|
22
22
|
import {
|
|
23
23
|
SELF,
|
|
24
24
|
note
|
|
@@ -44,8 +44,8 @@ import {
|
|
|
44
44
|
} from "@scp-dev/core";
|
|
45
45
|
var SERVER = "scp";
|
|
46
46
|
var SPAWN_RUNNER = "SCP_SPAWN_RUNNER";
|
|
47
|
-
var SPAWNER_IDENTITY = ["SCP_NAME", "SCP_SESSION", "SCP_GRANT", "SCP_PARENT", "SCP_HARNESS", SPAWN_RUNNER];
|
|
48
|
-
var SHIM_ENV = ["SCP_GRANT", "SCP_NAME", "SCP_LABELS", "SCP_DIR", "XDG_STATE_HOME"];
|
|
47
|
+
var SPAWNER_IDENTITY = ["SCP_NAME", "SCP_SESSION", "SCP_GRANT", "SCP_PARENT", "SCP_HARNESS", "SCP_SHIM_BINDING", SPAWN_RUNNER];
|
|
48
|
+
var SHIM_ENV = ["SCP_GRANT", "SCP_NAME", "SCP_LABELS", "SCP_DIR", "XDG_STATE_HOME", "SCP_SHIM_BINDING"];
|
|
49
49
|
function spawnEnvironment(parent, grant, labels) {
|
|
50
50
|
const env = {};
|
|
51
51
|
for (const [key, value] of Object.entries(parent)) if (value !== void 0 && !SPAWNER_IDENTITY.includes(key)) env[key] = value;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hooksTarget
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-C5PYVRJE.js";
|
|
4
4
|
import {
|
|
5
5
|
AGY_HOOK_SET,
|
|
6
6
|
BLOCK_STOP,
|
|
@@ -12,12 +12,12 @@ import {
|
|
|
12
12
|
mergeHooks,
|
|
13
13
|
musePlugin,
|
|
14
14
|
opencodePlugin
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-GG33NSBW.js";
|
|
16
16
|
import {
|
|
17
17
|
RelayUsage,
|
|
18
18
|
gitToplevel,
|
|
19
19
|
setJoinMode
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-BSX24XMZ.js";
|
|
21
21
|
|
|
22
22
|
// src/setup.ts
|
|
23
23
|
import { spawnSync } from "child_process";
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
psStart,
|
|
14
14
|
relayIdentity,
|
|
15
15
|
sessionKey
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-BSX24XMZ.js";
|
|
17
17
|
|
|
18
18
|
// src/hooks.ts
|
|
19
19
|
import { Readable } from "stream";
|
|
@@ -445,8 +445,10 @@ async function runHook(args, options) {
|
|
|
445
445
|
)
|
|
446
446
|
);
|
|
447
447
|
if ("dark" in joined) return;
|
|
448
|
+
const harnessSession = event.agent ? void 0 : asString(field(payload, "session_id", "sessionId"));
|
|
448
449
|
const claim = relayIdentity({
|
|
449
450
|
...joined.handle ? { as: joined.handle } : {},
|
|
451
|
+
...harnessSession ? { harnessSession } : {},
|
|
450
452
|
harness,
|
|
451
453
|
fallbackHarness: "hook",
|
|
452
454
|
cwd: event.cwd ?? options.cwd,
|
|
@@ -556,8 +558,10 @@ async function wakeHook(harnessArg, payload, waitS, options) {
|
|
|
556
558
|
)
|
|
557
559
|
);
|
|
558
560
|
if ("dark" in joined) return;
|
|
561
|
+
const harnessSession = asString(field(payload, "session_id", "sessionId"));
|
|
559
562
|
const claim = relayIdentity({
|
|
560
563
|
...joined.handle ? { as: joined.handle } : {},
|
|
564
|
+
...harnessSession ? { harnessSession } : {},
|
|
561
565
|
harness,
|
|
562
566
|
fallbackHarness: "hook",
|
|
563
567
|
cwd: asString(payload.cwd) ?? options.cwd,
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import {
|
|
2
|
+
entryArgv,
|
|
3
|
+
mcpFile,
|
|
4
|
+
mountedEntry,
|
|
5
|
+
whichOnPath
|
|
6
|
+
} from "./chunk-DEK3TE6T.js";
|
|
7
|
+
import "./chunk-C5PYVRJE.js";
|
|
8
|
+
import "./chunk-GG33NSBW.js";
|
|
9
|
+
import "./chunk-YYSLYVNZ.js";
|
|
10
|
+
import {
|
|
11
|
+
CHANNELS_ENV,
|
|
12
|
+
RelayUsage,
|
|
13
|
+
deriveHandle,
|
|
14
|
+
joinMode
|
|
15
|
+
} from "./chunk-BSX24XMZ.js";
|
|
16
|
+
import {
|
|
17
|
+
SELF
|
|
18
|
+
} from "./chunk-MNTIEWKF.js";
|
|
19
|
+
|
|
20
|
+
// src/claude.ts
|
|
21
|
+
import { spawn, spawnSync } from "child_process";
|
|
22
|
+
import { randomBytes } from "crypto";
|
|
23
|
+
import { existsSync, readFileSync, symlinkSync, unlinkSync, writeFileSync } from "fs";
|
|
24
|
+
import { basename, join } from "path";
|
|
25
|
+
import { privateDir, relayDir } from "@scp-dev/broker";
|
|
26
|
+
import { CLI_NAME, validHandle } from "@scp-dev/core";
|
|
27
|
+
var CHANNEL_FLAGS = ["--dangerously-load-development-channels", "server:scp"];
|
|
28
|
+
function claudeArgv(argv) {
|
|
29
|
+
let name;
|
|
30
|
+
let bg = false;
|
|
31
|
+
let i = 0;
|
|
32
|
+
for (; ; ) {
|
|
33
|
+
const arg = argv[i];
|
|
34
|
+
if (arg === "--bg" || arg === "--background") {
|
|
35
|
+
bg = true;
|
|
36
|
+
i++;
|
|
37
|
+
} else if (arg === "--name" || arg === "--as") {
|
|
38
|
+
if (argv[i + 1] === void 0) throw new RelayUsage(`${CLI_NAME} claude ${arg} takes a handle`);
|
|
39
|
+
name = argv[i + 1];
|
|
40
|
+
i += 2;
|
|
41
|
+
} else if (arg?.startsWith("--name=") || arg?.startsWith("--as=")) {
|
|
42
|
+
name = arg.slice(arg.indexOf("=") + 1);
|
|
43
|
+
i++;
|
|
44
|
+
} else break;
|
|
45
|
+
}
|
|
46
|
+
if (argv[i] === "--") i++;
|
|
47
|
+
name = name?.replace(/^me\//, "");
|
|
48
|
+
if (name !== void 0 && !validHandle(name)) throw new RelayUsage(`--name ${JSON.stringify(name)} is not a valid agent handle`);
|
|
49
|
+
const args = argv.slice(i);
|
|
50
|
+
if (!bg) bg = optionsOf(args).some((arg) => arg === "--bg" || arg === "--background");
|
|
51
|
+
return { ...name !== void 0 ? { name } : {}, ...bg ? { bg: true } : {}, args };
|
|
52
|
+
}
|
|
53
|
+
var optionsOf = (args) => args.includes("--") ? args.slice(0, args.indexOf("--")) : args;
|
|
54
|
+
function beforeDashes(args, added) {
|
|
55
|
+
const at = optionsOf(args).length;
|
|
56
|
+
return [...args.slice(0, at), ...added, ...args.slice(at)];
|
|
57
|
+
}
|
|
58
|
+
function claudeLaunchArgs(args, added) {
|
|
59
|
+
const channels = optionsOf(args).some((arg) => arg === "server:scp" || arg === `${CHANNEL_FLAGS[0]}=server:scp`) ? [] : CHANNEL_FLAGS;
|
|
60
|
+
return beforeDashes(args, [...channels, ...added]);
|
|
61
|
+
}
|
|
62
|
+
var LAUNCHER_IDENTITY = ["SCP_NAME", "SCP_GRANT", "SCP_SESSION", "SCP_PARENT", "SCP_HARNESS", "SCP_SHIM_BINDING", "SCP_SPAWN_RUNNER", CHANNELS_ENV];
|
|
63
|
+
var SHIM_VARS = ["SCP_LABELS", "SCP_DIR", "XDG_STATE_HOME"];
|
|
64
|
+
var bgDir = (env) => join(relayDir(env), "claude-bg");
|
|
65
|
+
function bgMount(env, name) {
|
|
66
|
+
let entry;
|
|
67
|
+
try {
|
|
68
|
+
entry = mountedEntry("claude", mcpFile("claude", env));
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
const mounted = entry ? entryArgv("claude", entry) : [];
|
|
72
|
+
const argv = mounted.includes("--channels") ? mounted : [...SELF, "mcp", "--harness", "claude", "--channels"];
|
|
73
|
+
const base = entry?.env && typeof entry.env === "object" ? entry.env : {};
|
|
74
|
+
const vars = Object.fromEntries(SHIM_VARS.flatMap((key) => env[key] ? [[key, env[key]]] : []));
|
|
75
|
+
return {
|
|
76
|
+
type: "stdio",
|
|
77
|
+
command: argv[0],
|
|
78
|
+
args: argv.slice(1),
|
|
79
|
+
env: { ...base, ...vars, ...name !== void 0 ? { SCP_NAME: name } : {}, SCP_GRANT: env.SCP_GRANT ?? "" }
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
var STRICT_HINT = `${CLI_NAME} claude: --strict-mcp-config without --mcp-config drops the scp mount, so messages cannot wake this session`;
|
|
83
|
+
function externalMcp(args) {
|
|
84
|
+
return args.some((arg) => arg === "--mcp-config" || arg.startsWith("--mcp-config="));
|
|
85
|
+
}
|
|
86
|
+
function namePrompt(name, args, env, cwd) {
|
|
87
|
+
if (name !== void 0 || joinMode(env) === "manual" || args.some((arg) => /^--(append-)?system-prompt/.test(arg))) return [];
|
|
88
|
+
const handle = env.SCP_NAME || deriveHandle("claude-code", cwd);
|
|
89
|
+
return [
|
|
90
|
+
"--append-system-prompt",
|
|
91
|
+
`You are on the SCP network as ${handle}. Pick a short descriptive name for yourself with \`${CLI_NAME} name <name>\` when you know what you're working on.`
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
function mountHint(env, cwd = process.cwd()) {
|
|
95
|
+
const file = mcpFile("claude", env);
|
|
96
|
+
const entries = [];
|
|
97
|
+
try {
|
|
98
|
+
const user = mountedEntry("claude", file);
|
|
99
|
+
if (user) entries.push(user);
|
|
100
|
+
const config = existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : {};
|
|
101
|
+
const local = config.projects?.[cwd]?.mcpServers?.scp;
|
|
102
|
+
if (local && typeof local === "object") entries.push(local);
|
|
103
|
+
} catch {
|
|
104
|
+
return `${CLI_NAME} claude: ${file} is unreadable, so the scp mount is unchecked; fix it or run ${CLI_NAME} setup --write`;
|
|
105
|
+
}
|
|
106
|
+
const project = join(cwd, ".mcp.json");
|
|
107
|
+
try {
|
|
108
|
+
const entry = existsSync(project) ? mountedEntry("claude", project) : void 0;
|
|
109
|
+
if (entry) entries.push(entry);
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
if (!entries.length) return `${CLI_NAME} claude: Claude Code has no scp MCP mount, so messages cannot wake this session; run ${CLI_NAME} setup --write`;
|
|
113
|
+
if (!entries.some((entry) => entryArgv("claude", entry).includes("--channels")))
|
|
114
|
+
return `${CLI_NAME} claude: the scp mount lacks --channels, so messages cannot wake this session; run ${CLI_NAME} setup --write`;
|
|
115
|
+
return void 0;
|
|
116
|
+
}
|
|
117
|
+
var SHORT_ID = /^[0-9a-f]{8}$/;
|
|
118
|
+
var BACKGROUNDED = /backgrounded\s*\S\s*([0-9a-f]{8})/;
|
|
119
|
+
function liveAgents(bin, env) {
|
|
120
|
+
const run = spawnSync(bin, ["agents", "--json"], { env, encoding: "utf8", timeout: 15e3 });
|
|
121
|
+
try {
|
|
122
|
+
const listed = JSON.parse(run.stdout);
|
|
123
|
+
return Array.isArray(listed) ? listed.filter((agent) => typeof agent?.id === "string" && agent.state !== "failed") : [];
|
|
124
|
+
} catch {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function resumeTarget(args) {
|
|
129
|
+
const options = optionsOf(args);
|
|
130
|
+
for (const [i, arg] of options.entries()) {
|
|
131
|
+
if (arg.startsWith("--resume=")) return arg.slice("--resume=".length);
|
|
132
|
+
if ((arg === "--resume" || arg === "-r") && options[i + 1] && !options[i + 1].startsWith("-")) return options[i + 1];
|
|
133
|
+
}
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
136
|
+
async function runClaude(bin, args, env, cwd, stdout = "inherit", onOut) {
|
|
137
|
+
const child = spawn(bin, args, { cwd, stdio: ["inherit", stdout, "inherit"], env });
|
|
138
|
+
child.stdout?.on("data", (chunk) => {
|
|
139
|
+
process.stdout.write(chunk);
|
|
140
|
+
onOut?.(chunk.toString("utf8"));
|
|
141
|
+
});
|
|
142
|
+
const forward = (signal) => () => void child.kill(signal);
|
|
143
|
+
const handlers = ["SIGTERM", "SIGHUP"].map((signal) => [signal, forward(signal)]);
|
|
144
|
+
const ignore = () => {
|
|
145
|
+
};
|
|
146
|
+
process.on("SIGINT", ignore);
|
|
147
|
+
for (const [signal, handler] of handlers) process.on(signal, handler);
|
|
148
|
+
try {
|
|
149
|
+
return await new Promise((resolve, reject) => {
|
|
150
|
+
child.once("error", reject);
|
|
151
|
+
child.once("close", (code, signal) => resolve(code ?? (signal ? 128 + (signalNumber(signal) ?? 1) : 1)));
|
|
152
|
+
});
|
|
153
|
+
} finally {
|
|
154
|
+
process.off("SIGINT", ignore);
|
|
155
|
+
for (const [signal, handler] of handlers) process.off(signal, handler);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async function backgroundCommand(name, args, env, cwd, bin, out) {
|
|
159
|
+
const handle = name ?? (env.SCP_NAME || void 0);
|
|
160
|
+
const dir = privateDir(bgDir(env));
|
|
161
|
+
const file = join(dir, `${randomBytes(8).toString("hex")}.json`);
|
|
162
|
+
writeFileSync(file, `${JSON.stringify({ mcpServers: { scp: bgMount(env, handle) } }, null, 2)}
|
|
163
|
+
`, { mode: 384 });
|
|
164
|
+
const options = optionsOf(args);
|
|
165
|
+
const added = [
|
|
166
|
+
...options.includes("--bg") || options.includes("--background") ? [] : ["--bg"],
|
|
167
|
+
...handle !== void 0 && !options.some((arg) => arg === "-n" || arg === "--name" || arg.startsWith("--name=")) ? ["--name", handle] : [],
|
|
168
|
+
...namePrompt(handle, args, env, cwd),
|
|
169
|
+
"--mcp-config",
|
|
170
|
+
file
|
|
171
|
+
];
|
|
172
|
+
const clean = Object.fromEntries(Object.entries(env).filter(([key]) => !LAUNCHER_IDENTITY.includes(key)));
|
|
173
|
+
let printed = "";
|
|
174
|
+
const code = await runClaude(bin, beforeDashes(args, added), clean, cwd, "pipe", (text) => {
|
|
175
|
+
printed += text;
|
|
176
|
+
});
|
|
177
|
+
const id = BACKGROUNDED.exec(printed)?.[1];
|
|
178
|
+
if (!id) {
|
|
179
|
+
unlinkSync(file);
|
|
180
|
+
return code || 1;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
unlinkSync(join(dir, `${id}.json`));
|
|
184
|
+
} catch {
|
|
185
|
+
}
|
|
186
|
+
symlinkSync(basename(file), join(dir, `${id}.json`));
|
|
187
|
+
const address = `me/${handle ?? deriveHandle("claude-code", cwd)}`;
|
|
188
|
+
out(`${CLI_NAME} claude: background session ${id} is ${address} on SCP; messages wake it through its inbox. ${CLI_NAME} claude attach ${id} opens it`);
|
|
189
|
+
return code;
|
|
190
|
+
}
|
|
191
|
+
async function attachCommand(argv, env, err, cwd, bin, deps) {
|
|
192
|
+
let target;
|
|
193
|
+
let rename;
|
|
194
|
+
for (let i = 0; i < argv.length; i++) {
|
|
195
|
+
const arg = argv[i];
|
|
196
|
+
if (arg === "--name" || arg === "--as") {
|
|
197
|
+
rename = argv[++i];
|
|
198
|
+
if (rename === void 0) throw new RelayUsage(`${CLI_NAME} claude attach ${arg} takes a handle`);
|
|
199
|
+
} else if (arg.startsWith("--name=") || arg.startsWith("--as=")) rename = arg.slice(arg.indexOf("=") + 1);
|
|
200
|
+
else if (target === void 0) target = arg;
|
|
201
|
+
else throw new RelayUsage(`${CLI_NAME} claude attach takes one session: <id|name> [--name N]`);
|
|
202
|
+
}
|
|
203
|
+
if (target === void 0) throw new RelayUsage(`${CLI_NAME} claude attach takes a background session: <id|name> [--name N]`);
|
|
204
|
+
rename = rename?.replace(/^me\//, "");
|
|
205
|
+
if (rename !== void 0) {
|
|
206
|
+
if (!validHandle(rename)) throw new RelayUsage(`--name ${JSON.stringify(rename)} is not a valid agent handle`);
|
|
207
|
+
await renameBackground(target, rename, env, err, bin, deps);
|
|
208
|
+
}
|
|
209
|
+
return runClaude(bin, ["attach", target], env, cwd);
|
|
210
|
+
}
|
|
211
|
+
async function renameBackground(target, to, env, err, bin, deps) {
|
|
212
|
+
const agent = SHORT_ID.test(target) ? void 0 : liveAgents(bin, env).find((a) => a.name === target || a.sessionId === target);
|
|
213
|
+
const id = SHORT_ID.test(target) ? target : agent?.id;
|
|
214
|
+
const link = id ? join(bgDir(env), `${id}.json`) : void 0;
|
|
215
|
+
let config;
|
|
216
|
+
try {
|
|
217
|
+
config = link ? JSON.parse(readFileSync(link, "utf8")) : void 0;
|
|
218
|
+
} catch {
|
|
219
|
+
}
|
|
220
|
+
const from = config?.mcpServers?.scp?.env?.SCP_NAME ?? (SHORT_ID.test(target) ? void 0 : target);
|
|
221
|
+
if (!link || !config?.mcpServers?.scp?.env || from === void 0) {
|
|
222
|
+
err(`${CLI_NAME} claude: ${target} was not started by ${CLI_NAME} claude --bg --name, so its name is unknown; run ${CLI_NAME} name ${to} in it`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (from === to) return;
|
|
226
|
+
const code = await (deps.rename ?? renameEndpoint)(from, to);
|
|
227
|
+
if (code !== 0) {
|
|
228
|
+
err(`${CLI_NAME} claude: me/${from} keeps its name`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
config.mcpServers.scp.env.SCP_NAME = to;
|
|
232
|
+
writeFileSync(link, `${JSON.stringify(config, null, 2)}
|
|
233
|
+
`, { mode: 384 });
|
|
234
|
+
}
|
|
235
|
+
async function renameEndpoint(from, to) {
|
|
236
|
+
return (await import("./main-RA3MP25N.js")).relayMain(["name", "--endpoint", `me/${from}`, to]);
|
|
237
|
+
}
|
|
238
|
+
async function claudeCommand(argv, env = process.env, err = (line) => void process.stderr.write(`${line}
|
|
239
|
+
`), cwd = process.cwd(), deps = {}) {
|
|
240
|
+
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
241
|
+
process.stdout.write(CLAUDE_HELP);
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
const out = deps.out ?? ((line) => void process.stdout.write(`${line}
|
|
245
|
+
`));
|
|
246
|
+
const bin = whichOnPath("claude", env);
|
|
247
|
+
if (argv[0] === "attach") {
|
|
248
|
+
if (!bin) throw new RelayUsage("claude is not on PATH");
|
|
249
|
+
return attachCommand(argv.slice(1), env, err, cwd, bin, deps);
|
|
250
|
+
}
|
|
251
|
+
const { name, bg, args } = claudeArgv(argv);
|
|
252
|
+
if (!bin) throw new RelayUsage("claude is not on PATH");
|
|
253
|
+
if (bg) return backgroundCommand(name, args, env, cwd, bin, out);
|
|
254
|
+
const resume = resumeTarget(args);
|
|
255
|
+
const live = resume === void 0 ? void 0 : liveAgents(bin, env).find((agent) => agent.id === resume || agent.sessionId === resume);
|
|
256
|
+
if (live) {
|
|
257
|
+
err(`${CLI_NAME} claude: ${resume} is a live background session, already woken through claude-inbox; attaching to it (claude attach ${live.id})`);
|
|
258
|
+
return runClaude(bin, ["attach", live.id], env, cwd);
|
|
259
|
+
}
|
|
260
|
+
const external = externalMcp(args);
|
|
261
|
+
const hint = external ? void 0 : args.includes("--strict-mcp-config") ? STRICT_HINT : mountHint(env, cwd);
|
|
262
|
+
if (hint) throw new RelayUsage(hint);
|
|
263
|
+
if (external) err(`${CLI_NAME} claude: using your --mcp-config; it needs the scp server with --channels for messages to wake this session`);
|
|
264
|
+
return runClaude(
|
|
265
|
+
bin,
|
|
266
|
+
claudeLaunchArgs(args, namePrompt(name, args, env, cwd)),
|
|
267
|
+
{ ...env, ...name !== void 0 ? { SCP_NAME: name } : {}, [CHANNELS_ENV]: String(process.pid) },
|
|
268
|
+
cwd
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
var CLAUDE_HELP = `${CLI_NAME} claude [--name N | --as N] [--] [claude args...] start Claude Code with the scp channel loaded, so peer messages wake it
|
|
272
|
+
${CLI_NAME} claude --bg [--name N] [--] [claude args...] start it in the background (claude --bg) with an scp mount that claims N and
|
|
273
|
+
$SCP_GRANT, so env $(${CLI_NAME} spawn-env --name N) links it under you; messages wake it
|
|
274
|
+
through its inbox. Prints its short id and address
|
|
275
|
+
${CLI_NAME} claude attach <id|name> [--name N] claude attach, renaming its endpoint to N first
|
|
276
|
+
--resume <id> of a live background session attaches to it: it is already woken through claude-inbox
|
|
277
|
+
`;
|
|
278
|
+
var SIGNALS = { SIGHUP: 1, SIGINT: 2, SIGKILL: 9, SIGTERM: 15 };
|
|
279
|
+
var signalNumber = (signal) => SIGNALS[signal];
|
|
280
|
+
export {
|
|
281
|
+
CHANNEL_FLAGS,
|
|
282
|
+
bgDir,
|
|
283
|
+
bgMount,
|
|
284
|
+
claudeArgv,
|
|
285
|
+
claudeCommand,
|
|
286
|
+
claudeLaunchArgs,
|
|
287
|
+
mountHint,
|
|
288
|
+
namePrompt,
|
|
289
|
+
resumeTarget
|
|
290
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -17,6 +17,6 @@ function quietSqliteWarning() {
|
|
|
17
17
|
// src/cli.ts
|
|
18
18
|
if (!await subagentFastPath(process.argv.slice(2))) {
|
|
19
19
|
quietSqliteWarning();
|
|
20
|
-
const { run } = await import("./main-
|
|
20
|
+
const { run } = await import("./main-RA3MP25N.js");
|
|
21
21
|
await run(process.argv.slice(2));
|
|
22
22
|
}
|