@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.
Files changed (26) hide show
  1. package/dist/{chunk-2LIJXXTL.js → chunk-BSX24XMZ.js} +89 -20
  2. package/dist/{chunk-VQU5CPE4.js → chunk-C5PYVRJE.js} +2 -2
  3. package/dist/{chunk-IEFXYBX7.js → chunk-C7ZUACP7.js} +1 -1
  4. package/dist/{chunk-HHVNSHGB.js → chunk-CVW3INZO.js} +5 -5
  5. package/dist/{chunk-VW7DYSGM.js → chunk-DEK3TE6T.js} +3 -3
  6. package/dist/{chunk-B2G3XTW7.js → chunk-GG33NSBW.js} +5 -1
  7. package/dist/claude-NCPNZLIX.js +290 -0
  8. package/dist/cli.js +1 -1
  9. package/dist/{codex-4WPBAFAT.js → codex-4GTXURTS.js} +33 -26
  10. package/dist/{hooks-ISZTCOLZ.js → hooks-SWVIDONV.js} +2 -2
  11. package/dist/{install-SDLWXW32.js → install-YE7LCGPC.js} +3 -3
  12. package/dist/{join-FITJVZCA.js → join-64BU6SEY.js} +6 -2
  13. package/dist/{login-AUMLI4FS.js → login-4IF26HH5.js} +2 -2
  14. package/dist/{main-STUKDZ5L.js → main-RA3MP25N.js} +72 -28
  15. package/dist/{mcp-J2CL6A4R.js → mcp-RERRUUIX.js} +31 -20
  16. package/dist/{mesh-UG6VO7ZO.js → mesh-XTMBRJLR.js} +1 -1
  17. package/dist/{opencode-WFP57BZD.js → opencode-CPNR2G3W.js} +4 -4
  18. package/dist/{peek-YTCWVPF7.js → peek-GEYQ7SHV.js} +1 -1
  19. package/dist/{pi-QEZVFDNR.js → pi-XHZAUDP4.js} +1 -1
  20. package/dist/{relay-QWJM372C.js → relay-E4JFR33E.js} +1 -1
  21. package/dist/{remote-P4GFUIAP.js → remote-3R4GRDGI.js} +2 -2
  22. package/dist/{setup-R5SNWVVF.js → setup-O45SE5L7.js} +4 -4
  23. package/dist/{spawn-WJYOTAKI.js → spawn-CJSHYE7E.js} +5 -5
  24. package/dist/{up-UWV5BQUH.js → up-O2CMDGXP.js} +5 -5
  25. package/package.json +6 -6
  26. 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 relayDir2 } from "@scp-dev/broker";
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(relayDir(env), session);
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
- privateDir(dir);
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 table = readPsTable();
504
- const self = harnessLineage(process.ppid, table);
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: lineage(process.ppid, table), self, ...harness ? { harness } : {} };
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-P4GFUIAP.js");
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 = relayDir2();
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 = readFileSync(path, "utf8");
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
- chmodSync(dir, 448);
773
+ chmodSync2(dir, 448);
708
774
  const temp = join3(dir, `.${randomUUID()}.tmp`);
709
775
  try {
710
- writeFileSync(temp, `${JSON.stringify(value, null, 2)}
776
+ writeFileSync2(temp, `${JSON.stringify(value, null, 2)}
711
777
  `, { mode: 384, flag: "wx" });
712
- chmodSync(temp, 384);
778
+ chmodSync2(temp, 384);
713
779
  renameSync(temp, path);
714
780
  } finally {
715
- rmSync(temp, { force: true });
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-AUMLI4FS.js")).login(values);
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-B2G3XTW7.js";
13
+ } from "./chunk-GG33NSBW.js";
14
14
  import {
15
15
  RelayUsage,
16
16
  gitToplevel
17
- } from "./chunk-2LIJXXTL.js";
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";
@@ -5,7 +5,7 @@ import {
5
5
  configDir,
6
6
  relayCredential,
7
7
  writePrivateJson
8
- } from "./chunk-2LIJXXTL.js";
8
+ } from "./chunk-BSX24XMZ.js";
9
9
  import {
10
10
  note,
11
11
  printTable
@@ -3,10 +3,10 @@ import {
3
3
  agySettingsFile,
4
4
  mcpFile,
5
5
  mountedEntry
6
- } from "./chunk-VW7DYSGM.js";
6
+ } from "./chunk-DEK3TE6T.js";
7
7
  import {
8
8
  steerPrompt
9
- } from "./chunk-B2G3XTW7.js";
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-2LIJXXTL.js";
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-VQU5CPE4.js";
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-B2G3XTW7.js";
15
+ } from "./chunk-GG33NSBW.js";
16
16
  import {
17
17
  RelayUsage,
18
18
  gitToplevel,
19
19
  setJoinMode
20
- } from "./chunk-2LIJXXTL.js";
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-2LIJXXTL.js";
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-STUKDZ5L.js");
20
+ const { run } = await import("./main-RA3MP25N.js");
21
21
  await run(process.argv.slice(2));
22
22
  }