@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
@@ -3,18 +3,18 @@ import {
3
3
  codexMount,
4
4
  codexOverrides,
5
5
  toml
6
- } from "./chunk-HHVNSHGB.js";
6
+ } from "./chunk-CVW3INZO.js";
7
7
  import "./chunk-KN7WEIE4.js";
8
- import "./chunk-VW7DYSGM.js";
9
- import "./chunk-VQU5CPE4.js";
10
- import "./chunk-B2G3XTW7.js";
8
+ import "./chunk-DEK3TE6T.js";
9
+ import "./chunk-C5PYVRJE.js";
10
+ import "./chunk-GG33NSBW.js";
11
11
  import "./chunk-YYSLYVNZ.js";
12
12
  import {
13
13
  RelayUsage,
14
14
  readPsTable,
15
15
  relayIdentity,
16
16
  startDaemon
17
- } from "./chunk-2LIJXXTL.js";
17
+ } from "./chunk-BSX24XMZ.js";
18
18
  import {
19
19
  SELF
20
20
  } from "./chunk-MNTIEWKF.js";
@@ -29,7 +29,7 @@ import { join } from "path";
29
29
  import { PassThrough } from "stream";
30
30
  import { setTimeout as sleep } from "timers/promises";
31
31
  import { connectBroker, relayDir } from "@scp-dev/broker";
32
- import { CLI_NAME, validHandle } from "@scp-dev/core";
32
+ import { CLI_NAME, RelayError, validHandle } from "@scp-dev/core";
33
33
  import { MCP_PROTOCOLS, runRelayMcp } from "@scp-dev/mcp";
34
34
  import { parse } from "smol-toml";
35
35
  var CODEX_HELP = `${CLI_NAME} codex [--name N | --as N] [--headless] [--resume THREAD] [--] [codex args...]
@@ -61,13 +61,13 @@ function codexLaunch(args, env, _self) {
61
61
  }
62
62
  name = name?.replace(/^me\//, "");
63
63
  if (name !== void 0 && !validHandle(name)) throw new RelayUsage(`${JSON.stringify(name)} is not a valid agent handle`);
64
- const { SCP_SESSION: _session, SCP_GRANT: _grant, SCP_PARENT: _parent, ...clean } = env;
64
+ const { SCP_SESSION: _session, SCP_PARENT: _parent, ...clean } = env;
65
65
  return {
66
66
  ...name !== void 0 ? { name } : {},
67
67
  headless,
68
68
  ...resume !== void 0 ? { resume } : {},
69
69
  codexArgs: resume !== void 0 && !headless ? ["resume", resume, ...codexArgs] : codexArgs,
70
- env: { ...clean, SCP_HARNESS: "codex", ...name !== void 0 ? { SCP_NAME: name } : {} }
70
+ env: { ...clean, SCP_HARNESS: "codex", SCP_SHIM_BINDING: randomBytes(16).toString("hex"), ...name !== void 0 ? { SCP_NAME: name } : {} }
71
71
  };
72
72
  }
73
73
  var CodexSocket = class {
@@ -398,6 +398,24 @@ function appServerPid(pid) {
398
398
  }
399
399
  return pid;
400
400
  }
401
+ async function connectCodexChannel(connect2, claim, cwd, stopped) {
402
+ while (!stopped()) {
403
+ const client = await connect2();
404
+ try {
405
+ const hello = await client.hello({ observe: true, self: [claim.process], ...claim.grant ? { grant: claim.grant } : {}, cwd });
406
+ if (hello.endpoint) {
407
+ client.hello = async () => hello;
408
+ return client;
409
+ }
410
+ } catch (error) {
411
+ client.close();
412
+ if (!claim.grant || !(error instanceof RelayError) || error.code !== "unauthorized") throw error;
413
+ }
414
+ client.close();
415
+ await sleep(100);
416
+ }
417
+ throw new Error("codex channel stopped");
418
+ }
401
419
  async function codexCommand(args) {
402
420
  if (args[0] === "--help" || args[0] === "-h") {
403
421
  process.stdout.write(`${CODEX_HELP}
@@ -514,24 +532,12 @@ ${stderr.toString().trim()}` : ""}`
514
532
  const broker = relayDir(launch.env);
515
533
  let lines = "";
516
534
  channel = runRelayMcp({
517
- connect: async () => {
518
- while (!input.writableEnded) {
519
- const client = await connectBroker(broker, () => startDaemon(broker));
520
- try {
521
- const hello = await client.hello({ observe: true, self: [claim.process], cwd });
522
- if (hello.endpoint) {
523
- client.hello = async () => hello;
524
- return client;
525
- }
526
- } catch (error) {
527
- client.close();
528
- throw error;
529
- }
530
- client.close();
531
- await sleep(100);
532
- }
533
- throw new Error("codex channel stopped");
534
- },
535
+ connect: () => connectCodexChannel(
536
+ () => connectBroker(broker, () => startDaemon(broker)),
537
+ claim,
538
+ cwd,
539
+ () => input.writableEnded
540
+ ),
535
541
  claim,
536
542
  channels: true,
537
543
  wake: "codex-app-server",
@@ -611,5 +617,6 @@ export {
611
617
  CodexWaker,
612
618
  codexCommand,
613
619
  codexLaunch,
620
+ connectCodexChannel,
614
621
  serverOptions
615
622
  };
@@ -40,11 +40,11 @@ import {
40
40
  tldrEvery,
41
41
  transcriptTitle,
42
42
  unreadPointer
43
- } from "./chunk-B2G3XTW7.js";
43
+ } from "./chunk-GG33NSBW.js";
44
44
  import {
45
45
  SUBAGENT
46
46
  } from "./chunk-YYSLYVNZ.js";
47
- import "./chunk-2LIJXXTL.js";
47
+ import "./chunk-BSX24XMZ.js";
48
48
  import "./chunk-MNTIEWKF.js";
49
49
  export {
50
50
  AGY_HOOK_SET,
@@ -2,10 +2,10 @@ import {
2
2
  hooksTarget,
3
3
  installHooks,
4
4
  mcpInstall
5
- } from "./chunk-VQU5CPE4.js";
6
- import "./chunk-B2G3XTW7.js";
5
+ } from "./chunk-C5PYVRJE.js";
6
+ import "./chunk-GG33NSBW.js";
7
7
  import "./chunk-YYSLYVNZ.js";
8
- import "./chunk-2LIJXXTL.js";
8
+ import "./chunk-BSX24XMZ.js";
9
9
  import "./chunk-MNTIEWKF.js";
10
10
  export {
11
11
  hooksTarget,
@@ -1,12 +1,13 @@
1
1
  import {
2
2
  RelayUsage,
3
+ codexShell,
3
4
  harnessAt,
4
5
  localHandle,
5
6
  relayIdentity,
6
7
  sessionKey,
7
8
  startDaemon,
8
9
  walkNow
9
- } from "./chunk-2LIJXXTL.js";
10
+ } from "./chunk-BSX24XMZ.js";
10
11
  import "./chunk-MNTIEWKF.js";
11
12
 
12
13
  // src/join.ts
@@ -18,7 +19,10 @@ async function joinCommand(values, positionals, cwd, out) {
18
19
  const { keys, self } = walkNow();
19
20
  const key = self.at(-1);
20
21
  const match = key === void 0 ? null : /^pid:(\d+):(.+)$/.exec(key);
21
- if (!match) throw new RelayUsage(`${CLI_NAME} join runs in an agent session's shell: no harness process runs this shell`);
22
+ if (!match) {
23
+ if (codexShell(process.env)) throw new RelayUsage(`${CLI_NAME} join: the codex sandbox hides the harness; use relay_join (MCP) instead`);
24
+ throw new RelayUsage(`${CLI_NAME} join runs in an agent session's shell: no harness process runs this shell`);
25
+ }
22
26
  const dir = relayDir();
23
27
  const known = readSessionMarker(dir, sessionKey(process.env, key))?.handle;
24
28
  const claim = relayIdentity({
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  machineName
3
- } from "./chunk-IEFXYBX7.js";
3
+ } from "./chunk-C7ZUACP7.js";
4
4
  import {
5
5
  RelayUsage,
6
6
  activeRelay,
7
7
  saveCredential
8
- } from "./chunk-2LIJXXTL.js";
8
+ } from "./chunk-BSX24XMZ.js";
9
9
  import {
10
10
  note
11
11
  } from "./chunk-MNTIEWKF.js";
@@ -14,8 +14,9 @@ import {
14
14
  shortHostname,
15
15
  stamp,
16
16
  startDaemon,
17
+ terminalAttached,
17
18
  time
18
- } from "./chunk-2LIJXXTL.js";
19
+ } from "./chunk-BSX24XMZ.js";
19
20
  import {
20
21
  FAILED,
21
22
  exitFlushed,
@@ -34,7 +35,7 @@ import { parseArgs } from "util";
34
35
  // package.json
35
36
  var package_default = {
36
37
  name: "@scp-dev/cli",
37
- version: "0.4.0",
38
+ version: "0.6.0",
38
39
  description: "The swarm command line tool.",
39
40
  license: "Apache-2.0",
40
41
  homepage: "https://scp.dev",
@@ -105,6 +106,8 @@ results back, as with native subagents.
105
106
  ${CLI_NAME} list [--all] agents on the relay: address, title, presence, wake, status; --all adds offline ones
106
107
  ${CLI_NAME} join [name] put this session on the relay, named once: ${CLI_NAME} join lead (${CLI_NAME} name renames it)
107
108
  ${CLI_NAME} join --under <parent> link this session under an orchestrator, as a spawned worker sits: ${CLI_NAME} join --under lead
109
+ ${CLI_NAME} adopt <child> ask a local root agent to link under you; it accepts with ${CLI_NAME} link accept <id>
110
+ ${CLI_NAME} link accept <id> accept a link request and sit under its sender as a worker
108
111
  ${CLI_NAME} status [id] the fleet in short; with the id of a message you sent: who read it when
109
112
  ${CLI_NAME} peek <ep> a short, redacted view of a peekable agent's last turns: ${CLI_NAME} peek rev
110
113
  ${CLI_NAME} set-status <state> [note] working, waiting, blocked or done: ${CLI_NAME} set-status done "PR open"
@@ -278,6 +281,9 @@ agent sessions:
278
281
  it while idle (claude --dangerously-load-development-channels server:scp; SCP_NAME=N)
279
282
  --channels pushes messages into a Claude Code that ${CLI_NAME} claude started; setup mounts claude's shim with it.
280
283
  Without --name, ${CLI_NAME} claude asks the session to pick its own name with ${CLI_NAME} name <name>.
284
+ ${CLI_NAME} claude --bg [--name N] [--] [claude args...] the same in the background (claude --bg): an scp mount claims N and
285
+ $SCP_GRANT, so under env $(${CLI_NAME} spawn-env --name N) it links under you; prints its short id
286
+ ${CLI_NAME} claude attach <id|name> [--name N] claude attach, renaming its endpoint to N first; --resume of a live bg session attaches
281
287
  ${CLI_NAME} join [<handle>] put the session that runs this shell on the relay (it then shows in list,
282
288
  peers can message it, and its hooks hand it new messages), named <handle>
283
289
  when given; on a joined session it renames it in place. Works for a session
@@ -286,6 +292,9 @@ agent sessions:
286
292
  and title stay; a handle another endpoint holds, live or offline, is refused;
287
293
  a spawned worker is named by its spawner (policy defaults.rename: self lets it);
288
294
  --endpoint renames another agent, for the human at a terminal
295
+ ${CLI_NAME} name <handle> --take-over [--endpoint EP] take the handle from an offline endpoint: it is retired and its
296
+ children, links and unread mail move over (the same harness session, such as
297
+ a resumed one, needs no flag); for the human at a terminal
289
298
  ${CLI_NAME} join [<handle>] --under <parent> link the session under the agent <parent> (one it may message), as if
290
299
  <parent> had spawned it: it shows under <parent> in list --tree, the dashboard
291
300
  and the graph, and takes on a worker's scope (the policy's child_default: by
@@ -295,8 +304,11 @@ agent sessions:
295
304
  each other both ways
296
305
  ${CLI_NAME} adopt <child> [--parent <p>] link a local root agent and its subtree under <p>, for the human at a terminal;
297
306
  <p> may be on another machine of the account (me/<machine>/<handle>).
298
- From an agent's shell with a terminal attached <p> defaults to that agent.
299
- An agent without a terminal is refused: the session joins with --under itself
307
+ With --parent and a terminal, inherited $SCP_NAME and $SCP_GRANT are ignored
308
+ unless --as is explicit. An agent without a terminal asks the child to consent,
309
+ under itself only; the child accepts with link accept or join --under
310
+ ${CLI_NAME} link accept <id> accept a link request delivered to this session and sit under its sender as a worker;
311
+ <id> is the full or short message id. Only the session itself may accept
300
312
  ${CLI_NAME} unlink [<child>] put a linked agent back at the root, its subtree along and the child scope off;
301
313
  widening, so the human at a terminal only (policy defaults.unlink: self lets
302
314
  an agent unlink itself). A link that would put a parent under its own child
@@ -387,9 +399,10 @@ var VERBS = {
387
399
  send: [...IDENTITY, "reply-to", "thread", "private", "attach", "live"],
388
400
  join: ["harness", "json", "under"],
389
401
  adopt: [...IDENTITY, "parent"],
402
+ link: IDENTITY,
390
403
  unlink: IDENTITY,
391
404
  leave: [...IDENTITY, "endpoint"],
392
- name: [...IDENTITY, "endpoint"],
405
+ name: [...IDENTITY, "endpoint", "take-over"],
393
406
  inbox: [...IDENTITY, "wait", "ack", "keep"],
394
407
  reply: IDENTITY,
395
408
  ack: [...IDENTITY, "all"],
@@ -562,14 +575,14 @@ async function relayMain(argv, cwd = process.cwd()) {
562
575
  out(cliVersion());
563
576
  return 0;
564
577
  }
565
- if (verb === "hook") return (await import("./hooks-ISZTCOLZ.js")).relayHookCommand(rest, startDaemon);
566
- if (verb === "setup") return (await import("./setup-R5SNWVVF.js")).relaySetupCommand(rest, cwd, out);
567
- if (verb === "claude") return (await import("./claude-L2I5ZM2B.js")).claudeCommand(rest);
568
- if (verb === "pi") return (await import("./pi-QEZVFDNR.js")).piCommand(rest);
569
- if (verb === "codex") return (await import("./codex-4WPBAFAT.js")).codexCommand(rest);
570
- if (verb === "opencode") return (await import("./opencode-WFP57BZD.js")).opencodeCommand(rest, process.env, void 0, cwd);
578
+ if (verb === "hook") return (await import("./hooks-SWVIDONV.js")).relayHookCommand(rest, startDaemon);
579
+ if (verb === "setup") return (await import("./setup-O45SE5L7.js")).relaySetupCommand(rest, cwd, out);
580
+ if (verb === "claude") return (await import("./claude-NCPNZLIX.js")).claudeCommand(rest);
581
+ if (verb === "pi") return (await import("./pi-XHZAUDP4.js")).piCommand(rest);
582
+ if (verb === "codex") return (await import("./codex-4GTXURTS.js")).codexCommand(rest);
583
+ if (verb === "opencode") return (await import("./opencode-CPNR2G3W.js")).opencodeCommand(rest, process.env, void 0, cwd);
571
584
  if (verb === "spawn") {
572
- const { splitSpawn, spawnCommand } = await import("./spawn-WJYOTAKI.js");
585
+ const { splitSpawn, spawnCommand } = await import("./spawn-CJSHYE7E.js");
573
586
  const { flags } = splitSpawn(rest);
574
587
  if (flags.includes("--help") || flags.includes("-h")) {
575
588
  out(flags.includes("--all") ? RELAY_HELP : SPAWN_HELP);
@@ -577,7 +590,7 @@ async function relayMain(argv, cwd = process.cwd()) {
577
590
  }
578
591
  return spawnCommand(rest, cwd);
579
592
  }
580
- if (verb === "mesh") return (await import("./mesh-UG6VO7ZO.js")).meshCommand(rest);
593
+ if (verb === "mesh") return (await import("./mesh-XTMBRJLR.js")).meshCommand(rest);
581
594
  const allowed = VERBS[verb];
582
595
  if (!allowed) throw new RelayUsage(`unknown command "${verb}"`);
583
596
  let parsed;
@@ -597,11 +610,11 @@ async function relayMain(argv, cwd = process.cwd()) {
597
610
  return daemon(values.idle, values.policy);
598
611
  }
599
612
  if (verb === "mcp")
600
- return (await import("./mcp-J2CL6A4R.js")).relayMcpCommand(values, positionals);
613
+ return (await import("./mcp-RERRUUIX.js")).relayMcpCommand(values, positionals);
601
614
  if (verb === "hooks") {
602
615
  if (positionals[0] !== "install" || positionals.length > 1) throw new RelayUsage(`${CLI_NAME} hooks takes install --harness claude|codex|opencode|agy|muse`);
603
616
  if (values.write && values.print) throw new RelayUsage("--print and --write: pick one");
604
- return (await import("./install-SDLWXW32.js")).installHooks({
617
+ return (await import("./install-YE7LCGPC.js")).installHooks({
605
618
  harness: values.harness,
606
619
  scope: values.scope,
607
620
  write: values.write === true,
@@ -612,31 +625,48 @@ async function relayMain(argv, cwd = process.cwd()) {
612
625
  out
613
626
  });
614
627
  }
615
- if (verb === "relay") return (await import("./relay-QWJM372C.js")).relayVerb(positionals, values.json === true);
616
- if (verb === "up") return (await import("./up-UWV5BQUH.js")).up(positionals, values, cwd, relinkBroker);
628
+ if (verb === "relay") return (await import("./relay-E4JFR33E.js")).relayVerb(positionals, values.json === true);
629
+ if (verb === "up") return (await import("./up-O2CMDGXP.js")).up(positionals, values, cwd, relinkBroker);
617
630
  if (verb === "login") {
618
- const code = await (await import("./relay-QWJM372C.js")).loginMain(positionals, values);
631
+ const code = await (await import("./relay-E4JFR33E.js")).loginMain(positionals, values);
619
632
  if (code === 0) await relinkRunning();
620
633
  return code;
621
634
  }
622
- if (verb === "logout") return (await import("./relay-QWJM372C.js")).logoutMain(positionals, values.json === true);
635
+ if (verb === "logout") return (await import("./relay-E4JFR33E.js")).logoutMain(positionals, values.json === true);
623
636
  if (verb === "cloud") {
624
- const code = await (await import("./remote-P4GFUIAP.js")).cloudMain(positionals, values, cloudStatus);
637
+ const code = await (await import("./remote-3R4GRDGI.js")).cloudMain(positionals, values, cloudStatus);
625
638
  if (code === 0 && positionals[0] === "enable") await relinkRunning();
626
639
  return code;
627
640
  }
628
- if (verb === "join") return (await import("./join-FITJVZCA.js")).joinCommand(values, positionals, cwd, out);
641
+ if (verb === "join") return (await import("./join-64BU6SEY.js")).joinCommand(values, positionals, cwd, out);
629
642
  if (verb === "holds") holdsArgs(values, positionals);
630
643
  const bodyFromStdin = (verb === "send" || verb === "ask" || verb === "reply") && (positionals[1] === void 0 || positionals[1] === "-");
631
- const opened = await open(verb, values, cwd, bodyFromStdin);
644
+ const humanAdopt = verb === "adopt" && values.parent !== void 0 && values.as === void 0 && terminalAttached(process, false);
645
+ const opened = humanAdopt ? await openAdoptHuman(cwd, values) : await open(verb, values, cwd, bodyFromStdin);
632
646
  const { client } = opened;
633
647
  try {
634
- if (verb === "machines") return await (await import("./remote-P4GFUIAP.js")).machinesVerb(client, positionals, values.json === true);
648
+ if (verb === "machines") return await (await import("./remote-3R4GRDGI.js")).machinesVerb(client, positionals, values.json === true);
635
649
  return await runVerb(opened, verb, values, positionals, cwd);
636
650
  } finally {
637
651
  client.close();
638
652
  }
639
653
  }
654
+ async function openAdoptHuman(cwd, values) {
655
+ if (values.cloud || process.env.SCP_TRANSPORT === "cloud") {
656
+ const { openCloud } = await import("./remote-3R4GRDGI.js");
657
+ return { ...await openCloud({ tty: true, ...values.machine !== void 0 ? { machine: values.machine } : {} }), broker: null, bound: "human" };
658
+ }
659
+ if (values.machine !== void 0) throw new RelayUsage("--machine names the machine of the cloud transport: add --cloud");
660
+ const dir = relayDir();
661
+ const client = await connectBroker(dir, () => startDaemon(dir));
662
+ try {
663
+ const hello = await client.hello({ human: true, tty: true, cwd, brief: true });
664
+ return { client, me: hello.endpoint, broker: hello.broker, where: brokerLine(hello.broker), bound: "human" };
665
+ } catch (error) {
666
+ client.close();
667
+ throw error;
668
+ }
669
+ }
640
670
  async function cloudStatus() {
641
671
  const dir = relayDir();
642
672
  const client = await connectBroker(dir, () => startDaemon(dir));
@@ -808,9 +838,9 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
808
838
  return 0;
809
839
  }
810
840
  case "peek":
811
- return (await import("./peek-YTCWVPF7.js")).peekCommand(localClient(client, broker, verb), values, positionals);
841
+ return (await import("./peek-GEYQ7SHV.js")).peekCommand(localClient(client, broker, verb), values, positionals);
812
842
  case "set-peek":
813
- return (await import("./peek-YTCWVPF7.js")).setPeekCommand(localClient(client, broker, verb), values, positionals);
843
+ return (await import("./peek-GEYQ7SHV.js")).setPeekCommand(localClient(client, broker, verb), values, positionals);
814
844
  case "why": {
815
845
  const [from, to] = positionals;
816
846
  if (!from || !to || positionals.length > 2) throw new RelayUsage(`${CLI_NAME} why takes <from> <to>`);
@@ -1034,7 +1064,11 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
1034
1064
  return 0;
1035
1065
  }
1036
1066
  const before = endpoint ?? me?.address;
1037
- const view = await client.request("rename", { handle: positionals[0], ...endpoint !== void 0 ? { endpoint } : {} });
1067
+ const view = await client.request("rename", {
1068
+ handle: positionals[0],
1069
+ ...endpoint !== void 0 ? { endpoint } : {},
1070
+ ...values["take-over"] === true ? { take_over: true } : {}
1071
+ });
1038
1072
  if (asJson) json(view);
1039
1073
  else out(before && before !== view.address ? `${before} is now ${view.address}` : `${view.address}`);
1040
1074
  return 0;
@@ -1049,6 +1083,16 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
1049
1083
  ...parent !== void 0 ? { parent } : {}
1050
1084
  });
1051
1085
  if (asJson) json(result);
1086
+ else if (result.held) out(heldText(result.held));
1087
+ else if (result.requested)
1088
+ out(`asked ${result.endpoint.address} to link under ${result.parent}; it accepts with ${CLI_NAME} link accept ${shortId(result.requested)}`);
1089
+ else out(`${result.endpoint.address} now sits under ${result.parent}`);
1090
+ return 0;
1091
+ }
1092
+ case "link": {
1093
+ if (positionals.length !== 2 || positionals[0] !== "accept") throw new RelayUsage(`${CLI_NAME} link takes accept <id>`);
1094
+ const result = await client.request("link_accept", { id: positionals[1] });
1095
+ if (asJson) json(result);
1052
1096
  else out(`${result.endpoint.address} now sits under ${result.parent}`);
1053
1097
  return 0;
1054
1098
  }
@@ -1420,11 +1464,11 @@ async function daemon(idle, policyFile) {
1420
1464
  });
1421
1465
  let broker;
1422
1466
  try {
1423
- const { daemonBridge } = await import("./remote-P4GFUIAP.js");
1467
+ const { daemonBridge } = await import("./remote-3R4GRDGI.js");
1424
1468
  const bridge = await daemonBridge();
1425
1469
  broker = await startBroker({
1426
1470
  dir: relayDir(),
1427
- ...bridge ? { bridge } : await (await import("./mesh-UG6VO7ZO.js")).meshBrokerOptions(),
1471
+ ...bridge ? { bridge } : await (await import("./mesh-XTMBRJLR.js")).meshBrokerOptions(),
1428
1472
  bridgeConfig: () => daemonBridge(),
1429
1473
  ...idleMs !== void 0 ? { idleMs } : {},
1430
1474
  ...policyFile ? { policyPath: resolve(policyFile) } : {}
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  spawnDetached
3
- } from "./chunk-HHVNSHGB.js";
3
+ } from "./chunk-CVW3INZO.js";
4
4
  import "./chunk-KN7WEIE4.js";
5
- import "./chunk-VW7DYSGM.js";
5
+ import "./chunk-DEK3TE6T.js";
6
6
  import {
7
7
  mcpInstall
8
- } from "./chunk-VQU5CPE4.js";
9
- import "./chunk-B2G3XTW7.js";
8
+ } from "./chunk-C5PYVRJE.js";
9
+ import "./chunk-GG33NSBW.js";
10
10
  import "./chunk-YYSLYVNZ.js";
11
11
  import {
12
12
  CHANNELS_ENV,
@@ -16,14 +16,15 @@ import {
16
16
  membership,
17
17
  psParent,
18
18
  relayIdentity,
19
- startDaemon
20
- } from "./chunk-2LIJXXTL.js";
19
+ startDaemon,
20
+ writeShimBinding
21
+ } from "./chunk-BSX24XMZ.js";
21
22
  import "./chunk-MNTIEWKF.js";
22
23
 
23
24
  // src/mcp.ts
24
25
  import { CLI_NAME, validHandle } from "@scp-dev/core";
25
26
  import { connectBroker, relayDir } from "@scp-dev/broker";
26
- import { runRelayMcp } from "@scp-dev/mcp";
27
+ import { runRelayMcp, sessionInboxOf } from "@scp-dev/mcp";
27
28
  var ORPHAN_CHECK_MS = 5e3;
28
29
  async function relayMcpCommand(values, positionals) {
29
30
  if (positionals[0] === "install") {
@@ -43,25 +44,35 @@ async function relayMcpCommand(values, positionals) {
43
44
  inferHarness: (pid) => harnessAt(pid)
44
45
  });
45
46
  if (!validHandle(claim.handle)) throw new RelayUsage(`${JSON.stringify(claim.handle)} is not a valid agent handle (me, user and relay are reserved)`);
46
- const dir = relayDir();
47
+ const dir = relayDir(process.env);
48
+ const unbind = writeShimBinding(claim, process.env);
49
+ process.on("exit", unbind);
47
50
  const parent = process.ppid;
48
51
  const orphaned = setInterval(() => {
49
52
  if (process.ppid !== parent) process.exit(0);
50
53
  }, ORPHAN_CHECK_MS);
51
54
  orphaned.unref();
52
- await runRelayMcp({
53
- connect: () => connectBroker(dir, () => startDaemon(dir)),
54
- claim,
55
- membership: () => membership(process.env, claim.session),
56
- input: process.stdin,
57
- output: (line) => void process.stdout.write(line),
58
- env: process.env,
59
- channels: values.channels === true,
60
- launchedWithChannels: values.channels === true && launchedWithChannels(process.env),
61
- spawn: (request) => spawnDetached(spawnWords(request), request.cwd ?? claim.cwd),
62
- log: (line) => void process.stderr.write(`${line}
55
+ const inbox = sessionInboxOf(process.env);
56
+ try {
57
+ await runRelayMcp({
58
+ connect: () => connectBroker(dir, () => startDaemon(dir)),
59
+ claim,
60
+ membership: () => membership(process.env, claim.session),
61
+ input: process.stdin,
62
+ output: (line) => void process.stdout.write(line),
63
+ env: process.env,
64
+ channels: values.channels === true,
65
+ launchedWithChannels: values.channels === true && launchedWithChannels(process.env),
66
+ ...inbox ? { sessionInbox: inbox } : {},
67
+ spawn: (request) => spawnDetached(spawnWords(request), request.cwd ?? claim.cwd),
68
+ log: (line) => void process.stderr.write(`${line}
63
69
  `)
64
- });
70
+ });
71
+ } finally {
72
+ clearInterval(orphaned);
73
+ process.off("exit", unbind);
74
+ unbind();
75
+ }
65
76
  return 0;
66
77
  }
67
78
  function spawnWords(request) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RelayUsage
3
- } from "./chunk-2LIJXXTL.js";
3
+ } from "./chunk-BSX24XMZ.js";
4
4
  import "./chunk-MNTIEWKF.js";
5
5
 
6
6
  // src/mesh.ts
@@ -2,18 +2,18 @@ import {
2
2
  mcpFile,
3
3
  mountedEntry,
4
4
  whichOnPath
5
- } from "./chunk-VW7DYSGM.js";
5
+ } from "./chunk-DEK3TE6T.js";
6
6
  import {
7
7
  hooksTarget
8
- } from "./chunk-VQU5CPE4.js";
8
+ } from "./chunk-C5PYVRJE.js";
9
9
  import {
10
10
  opencodePlugin
11
- } from "./chunk-B2G3XTW7.js";
11
+ } from "./chunk-GG33NSBW.js";
12
12
  import "./chunk-YYSLYVNZ.js";
13
13
  import {
14
14
  RelayUsage,
15
15
  gitToplevel
16
- } from "./chunk-2LIJXXTL.js";
16
+ } from "./chunk-BSX24XMZ.js";
17
17
  import "./chunk-MNTIEWKF.js";
18
18
 
19
19
  // src/opencode.ts
@@ -2,7 +2,7 @@ import {
2
2
  RelayUsage,
3
3
  peekGrant,
4
4
  stamp
5
- } from "./chunk-2LIJXXTL.js";
5
+ } from "./chunk-BSX24XMZ.js";
6
6
  import {
7
7
  note
8
8
  } from "./chunk-MNTIEWKF.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RelayUsage
3
- } from "./chunk-2LIJXXTL.js";
3
+ } from "./chunk-BSX24XMZ.js";
4
4
  import "./chunk-MNTIEWKF.js";
5
5
 
6
6
  // src/pi.ts
@@ -13,7 +13,7 @@ import {
13
13
  saveCredential,
14
14
  setJoinMode,
15
15
  writePrivateJson
16
- } from "./chunk-2LIJXXTL.js";
16
+ } from "./chunk-BSX24XMZ.js";
17
17
  import "./chunk-MNTIEWKF.js";
18
18
  export {
19
19
  DEFAULT_RELAY,
@@ -9,8 +9,8 @@ import {
9
9
  machinesVerb,
10
10
  openCloud,
11
11
  readCloudFile
12
- } from "./chunk-IEFXYBX7.js";
13
- import "./chunk-2LIJXXTL.js";
12
+ } from "./chunk-C7ZUACP7.js";
13
+ import "./chunk-BSX24XMZ.js";
14
14
  import "./chunk-MNTIEWKF.js";
15
15
  export {
16
16
  CloudRelayClient,
@@ -15,11 +15,11 @@ import {
15
15
  verifyMount,
16
16
  verifyMusePlugin,
17
17
  whichOnPath
18
- } from "./chunk-VW7DYSGM.js";
19
- import "./chunk-VQU5CPE4.js";
20
- import "./chunk-B2G3XTW7.js";
18
+ } from "./chunk-DEK3TE6T.js";
19
+ import "./chunk-C5PYVRJE.js";
20
+ import "./chunk-GG33NSBW.js";
21
21
  import "./chunk-YYSLYVNZ.js";
22
- import "./chunk-2LIJXXTL.js";
22
+ import "./chunk-BSX24XMZ.js";
23
23
  import "./chunk-MNTIEWKF.js";
24
24
  export {
25
25
  SETUP_HARNESSES,
@@ -31,15 +31,15 @@ import {
31
31
  toml,
32
32
  transcriptReplies,
33
33
  workerName
34
- } from "./chunk-HHVNSHGB.js";
34
+ } from "./chunk-CVW3INZO.js";
35
35
  import {
36
36
  SPAWN_HELP
37
37
  } from "./chunk-KN7WEIE4.js";
38
- import "./chunk-VW7DYSGM.js";
39
- import "./chunk-VQU5CPE4.js";
40
- import "./chunk-B2G3XTW7.js";
38
+ import "./chunk-DEK3TE6T.js";
39
+ import "./chunk-C5PYVRJE.js";
40
+ import "./chunk-GG33NSBW.js";
41
41
  import "./chunk-YYSLYVNZ.js";
42
- import "./chunk-2LIJXXTL.js";
42
+ import "./chunk-BSX24XMZ.js";
43
43
  import "./chunk-MNTIEWKF.js";
44
44
  export {
45
45
  CODEX_VALUED,
@@ -2,7 +2,7 @@ import {
2
2
  RelayUsage,
3
3
  activeRelay,
4
4
  relayCredential
5
- } from "./chunk-2LIJXXTL.js";
5
+ } from "./chunk-BSX24XMZ.js";
6
6
  import "./chunk-MNTIEWKF.js";
7
7
 
8
8
  // src/up.ts
@@ -29,21 +29,21 @@ async function up(positionals, values, cwd, brokerStatus) {
29
29
  const { url } = activeRelay(process.env);
30
30
  const headless = values.token !== void 0 || Boolean(process.env.SCP_TOKEN?.trim());
31
31
  if (headless || !relayCredential(process.env, url).token) {
32
- const code = await (await import("./login-AUMLI4FS.js")).login({
32
+ const code = await (await import("./login-4IF26HH5.js")).login({
33
33
  ...values.token !== void 0 ? { token: values.token } : {},
34
34
  ...values.label !== void 0 ? { label: values.label } : {},
35
35
  ...values["no-browser"] ? { "no-browser": true } : {}
36
36
  });
37
37
  if (code !== 0) return code;
38
38
  } else out(`already logged in to ${url}`);
39
- const enabled = await (await import("./remote-P4GFUIAP.js")).cloudMain(["enable"], values.machine !== void 0 ? { machine: values.machine } : {}, brokerStatus);
39
+ const enabled = await (await import("./remote-3R4GRDGI.js")).cloudMain(["enable"], values.machine !== void 0 ? { machine: values.machine } : {}, brokerStatus);
40
40
  if (enabled !== 0) return enabled;
41
- const setup = (await import("./setup-R5SNWVVF.js")).relaySetupCommand(["--write"], cwd, out);
41
+ const setup = (await import("./setup-O45SE5L7.js")).relaySetupCommand(["--write"], cwd, out);
42
42
  if (setup !== 0) return setup;
43
43
  const broker = await brokerStatus();
44
44
  if (!broker.enabled) out(`the broker is still local-only; run \`${CLI_NAME} daemon restart\` to link it to ${url}`);
45
45
  else out(`broker linked to ${broker.url ?? url}${broker.connected ? "" : " (connecting)"}`);
46
- const { detectHarnesses, whichOnPath } = await import("./setup-R5SNWVVF.js");
46
+ const { detectHarnesses, whichOnPath } = await import("./setup-O45SE5L7.js");
47
47
  const which = (name) => whichOnPath(name, process.env);
48
48
  const installed = new Set(detectHarnesses(process.env, which).keys());
49
49
  if (which("pi")) installed.add("pi");