@scp-dev/cli 0.1.0 → 0.1.3

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.
@@ -0,0 +1,88 @@
1
+ import {
2
+ entryArgv,
3
+ mcpFile,
4
+ mountedEntry,
5
+ whichOnPath
6
+ } from "./chunk-IH2RJRIW.js";
7
+ import "./chunk-QQZF5TQD.js";
8
+ import {
9
+ CHANNELS_ENV,
10
+ RelayUsage
11
+ } from "./chunk-URSBFDR6.js";
12
+ import "./chunk-ABR5UABL.js";
13
+ import "./chunk-YYSLYVNZ.js";
14
+ import "./chunk-3PGDKPRN.js";
15
+ import "./chunk-M2DCR2J4.js";
16
+
17
+ // src/claude.ts
18
+ import { spawn } from "child_process";
19
+ import { CLI_NAME, validHandle } from "@scp-dev/core";
20
+ var CHANNEL_FLAGS = ["--dangerously-load-development-channels", "server:scp"];
21
+ function claudeArgv(argv) {
22
+ let name;
23
+ let i = 0;
24
+ if (argv[0] === "--name") {
25
+ if (argv[1] === void 0) throw new RelayUsage(`${CLI_NAME} claude --name takes a handle`);
26
+ name = argv[1];
27
+ i = 2;
28
+ } else if (argv[0]?.startsWith("--name=")) {
29
+ name = argv[0].slice("--name=".length);
30
+ i = 1;
31
+ }
32
+ if (argv[i] === "--") i++;
33
+ if (name !== void 0 && !validHandle(name)) throw new RelayUsage(`--name ${JSON.stringify(name)} is not a valid agent handle`);
34
+ return { ...name !== void 0 ? { name } : {}, args: argv.slice(i) };
35
+ }
36
+ function mountHint(env) {
37
+ const file = mcpFile("claude", env);
38
+ let entry;
39
+ try {
40
+ entry = mountedEntry("claude", file);
41
+ } catch {
42
+ return `${CLI_NAME} claude: ${file} is unreadable, so the scp mount is unchecked`;
43
+ }
44
+ if (!entry) return `${CLI_NAME} claude: Claude Code has no scp MCP mount; run ${CLI_NAME} setup --write`;
45
+ if (!entryArgv("claude", entry).includes("--channels"))
46
+ return `${CLI_NAME} claude: the scp mount lacks --channels, so messages will not wake this session; re-run ${CLI_NAME} setup --write`;
47
+ return void 0;
48
+ }
49
+ async function claudeCommand(argv, env = process.env, err = (line) => void process.stderr.write(`${line}
50
+ `)) {
51
+ if (argv[0] === "--help" || argv[0] === "-h") {
52
+ process.stdout.write(`${CLI_NAME} claude [--name N] [--] [claude args...] start Claude Code with the scp channel loaded, so peer messages wake it
53
+ `);
54
+ return 0;
55
+ }
56
+ const { name, args } = claudeArgv(argv);
57
+ const bin = whichOnPath("claude", env);
58
+ if (!bin) throw new RelayUsage("claude is not on PATH");
59
+ const hint = mountHint(env);
60
+ if (hint) err(hint);
61
+ const child = spawn(bin, [...CHANNEL_FLAGS, ...args], {
62
+ stdio: "inherit",
63
+ env: { ...env, ...name !== void 0 ? { SCP_NAME: name } : {}, [CHANNELS_ENV]: String(process.pid) }
64
+ });
65
+ const forward = (signal) => () => void child.kill(signal);
66
+ const handlers = ["SIGTERM", "SIGHUP"].map((signal) => [signal, forward(signal)]);
67
+ const ignore = () => {
68
+ };
69
+ process.on("SIGINT", ignore);
70
+ for (const [signal, handler] of handlers) process.on(signal, handler);
71
+ try {
72
+ return await new Promise((resolve, reject) => {
73
+ child.once("error", reject);
74
+ child.once("exit", (code, signal) => resolve(code ?? (signal ? 128 + (signalNumber(signal) ?? 1) : 1)));
75
+ });
76
+ } finally {
77
+ process.off("SIGINT", ignore);
78
+ for (const [signal, handler] of handlers) process.off(signal, handler);
79
+ }
80
+ }
81
+ var SIGNALS = { SIGHUP: 1, SIGINT: 2, SIGKILL: 9, SIGTERM: 15 };
82
+ var signalNumber = (signal) => SIGNALS[signal];
83
+ export {
84
+ CHANNEL_FLAGS,
85
+ claudeArgv,
86
+ claudeCommand,
87
+ mountHint
88
+ };
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-QP32TYTL.js");
20
+ const { run } = await import("./main-PZQAJGHQ.js");
21
21
  await run(process.argv.slice(2));
22
22
  }
@@ -0,0 +1,52 @@
1
+ import {
2
+ capture
3
+ } from "./chunk-M2DCR2J4.js";
4
+
5
+ // src/daemon.ts
6
+ import { readFileSync } from "fs";
7
+ import { join } from "path";
8
+ import { setTimeout as sleep } from "timers/promises";
9
+ function brokerPid(dir) {
10
+ let pid;
11
+ try {
12
+ pid = Number(readFileSync(join(dir, "broker.lock"), "utf8").split(":")[0]);
13
+ } catch {
14
+ return null;
15
+ }
16
+ if (!Number.isInteger(pid) || pid <= 0) return null;
17
+ const command = capture(["ps", "-p", String(pid), "-o", "command="])?.trim() ?? "";
18
+ return /(^|\s)daemon(\s|$)/.test(command) && (/scp|swarm/.test(command) || command.includes(process.argv[1] ?? "\0")) ? pid : null;
19
+ }
20
+ function alive(pid) {
21
+ try {
22
+ process.kill(pid, 0);
23
+ return true;
24
+ } catch (error) {
25
+ return error.code === "EPERM";
26
+ }
27
+ }
28
+ async function exited(pid, ms = 5e3) {
29
+ const deadline = Date.now() + ms;
30
+ while (alive(pid)) {
31
+ if (Date.now() >= deadline) return false;
32
+ await sleep(50);
33
+ }
34
+ return true;
35
+ }
36
+ async function stopBroker(dir) {
37
+ const pid = brokerPid(dir);
38
+ if (pid === null) return null;
39
+ try {
40
+ process.kill(pid, "SIGTERM");
41
+ } catch (error) {
42
+ if (error.code === "ESRCH") return null;
43
+ throw error;
44
+ }
45
+ if (!await exited(pid)) throw new Error(`the relay broker (pid ${pid}) did not stop within 5 s`);
46
+ return pid;
47
+ }
48
+ export {
49
+ brokerPid,
50
+ exited,
51
+ stopBroker
52
+ };
@@ -30,11 +30,12 @@ import {
30
30
  subagentIntro,
31
31
  summarize,
32
32
  unreadPointer
33
- } from "./chunk-RO7CUOOH.js";
33
+ } from "./chunk-ABR5UABL.js";
34
34
  import {
35
35
  SUBAGENT
36
36
  } from "./chunk-YYSLYVNZ.js";
37
- import "./chunk-DA3EB2YF.js";
37
+ import "./chunk-3PGDKPRN.js";
38
+ import "./chunk-M2DCR2J4.js";
38
39
  export {
39
40
  AGY_HOOK_SET,
40
41
  AGY_UNVERIFIED,
@@ -0,0 +1,15 @@
1
+ import {
2
+ hooksTarget,
3
+ installHooks,
4
+ mcpInstall
5
+ } from "./chunk-QQZF5TQD.js";
6
+ import "./chunk-URSBFDR6.js";
7
+ import "./chunk-ABR5UABL.js";
8
+ import "./chunk-YYSLYVNZ.js";
9
+ import "./chunk-3PGDKPRN.js";
10
+ import "./chunk-M2DCR2J4.js";
11
+ export {
12
+ hooksTarget,
13
+ installHooks,
14
+ mcpInstall
15
+ };
@@ -1,16 +1,17 @@
1
1
  import {
2
2
  machineName
3
- } from "./chunk-6UFMKYPH.js";
3
+ } from "./chunk-5UWLTO2Y.js";
4
4
  import {
5
5
  activeRelay,
6
6
  saveCredential
7
- } from "./chunk-4QYUPPGV.js";
7
+ } from "./chunk-BUC5G5PY.js";
8
8
  import {
9
9
  RelayUsage
10
- } from "./chunk-7KP6TAJW.js";
10
+ } from "./chunk-URSBFDR6.js";
11
+ import "./chunk-3PGDKPRN.js";
11
12
  import {
12
13
  note
13
- } from "./chunk-DA3EB2YF.js";
14
+ } from "./chunk-M2DCR2J4.js";
14
15
 
15
16
  // src/login.ts
16
17
  import { spawn } from "child_process";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SPAWN_HELP
3
- } from "./chunk-IE5VI25P.js";
3
+ } from "./chunk-KN7WEIE4.js";
4
4
  import {
5
5
  OPTIONS,
6
6
  RelayUsage,
@@ -14,15 +14,17 @@ import {
14
14
  stamp,
15
15
  startDaemon,
16
16
  time
17
- } from "./chunk-7KP6TAJW.js";
17
+ } from "./chunk-URSBFDR6.js";
18
+ import {
19
+ shortHostname
20
+ } from "./chunk-3PGDKPRN.js";
18
21
  import {
19
22
  FAILED,
20
23
  exitFlushed,
21
24
  note,
22
25
  parseSeconds,
23
- printTable,
24
- shortHostname
25
- } from "./chunk-DA3EB2YF.js";
26
+ printTable
27
+ } from "./chunk-M2DCR2J4.js";
26
28
 
27
29
  // src/main.ts
28
30
  import { text as readAll } from "stream/consumers";
@@ -33,7 +35,7 @@ import { parseArgs } from "util";
33
35
  // package.json
34
36
  var package_default = {
35
37
  name: "@scp-dev/cli",
36
- version: "0.1.0",
38
+ version: "0.1.3",
37
39
  description: "The swarm command line tool.",
38
40
  license: "Apache-2.0",
39
41
  homepage: "https://scp.dev",
@@ -78,6 +80,7 @@ import {
78
80
  LIMITS,
79
81
  PREAMBLE,
80
82
  reachLines,
83
+ receiptLines,
81
84
  RelayError,
82
85
  shortId,
83
86
  stripControl,
@@ -96,14 +99,16 @@ results back, as with native subagents.
96
99
  ${CLI_NAME} ask <to> [text|-] [--wait S] send a request and wait for its reply: ${CLI_NAME} ask lead "which branch?"
97
100
  ${CLI_NAME} inbox [--wait S] your unread messages, then marked read: ${CLI_NAME} inbox
98
101
  ${CLI_NAME} list [--all] agents on the relay: address, title, presence, status
99
- ${CLI_NAME} status the fleet in short: who works, waits or is blocked, unread counts
102
+ ${CLI_NAME} status [id] the fleet in short; with the id of a message you sent: who read it when
100
103
  ${CLI_NAME} peek <ep> a short, redacted view of a peekable agent's last turns: ${CLI_NAME} peek rev
101
104
  ${CLI_NAME} set-status <state> [note] working, waiting, blocked or done: ${CLI_NAME} set-status done "PR open"
102
105
 
103
106
  - spawn returns at once with the worker's name: do not background it (no &, no run_in_background).
104
107
  - A worker's messages and its handback (exit code and its whole reply) arrive by themselves: when you stop, your stop
105
108
  waits for them and continues with them, and new messages also reach you after your tool calls. No polling needed.
106
- - Address a worker by the name spawn printed. Steer it meanwhile with ${CLI_NAME} send <name> <text>.
109
+ - Address a worker by the name spawn printed. Steer it meanwhile with ${CLI_NAME} send <name> <text>: it gets
110
+ the message after its next tool call or before it finishes, so its task need not tell it to check an inbox.
111
+ - With the relay MCP tools: relay_spawn starts a worker in one call, relay_send steers it, relay_status reads receipts.
107
112
  - --no-push: a worker your stop does not wait on; its messages and handback still reach you after your tool calls
108
113
  or with another worker's push.
109
114
  - An <id> is a message id, its short id (the last 6 characters, shown with every message) or last.
@@ -132,6 +137,9 @@ usage:
132
137
  ${CLI_NAME} ask-wait <id> [--wait S] keep waiting for the reply to a request
133
138
  ${CLI_NAME} status [--all] [--activity] the fleet in short: you, agents by presence and status, who waits or is blocked,
134
139
  unread counts; --activity adds the latest activity, --all counts offline agents
140
+ ${CLI_NAME} status <id> a message you sent, a line per recipient: read at HH:MM:SS, delivered at
141
+ HH:MM:SS (a background hook handed it over; it reaches the model with its next
142
+ request), shown by a hook at HH:MM:SS (in its context, not yet marked read), or unread
135
143
  ${CLI_NAME} watch a live tail of the fleet: presence, messages, status, activity
136
144
  ${CLI_NAME} set-status <state> [note] your status line: working, waiting, blocked or done
137
145
  ${CLI_NAME} set-title <text> what you work on, shown in list, peers and whoami ("" clears it)
@@ -188,8 +196,10 @@ scopes (selectors are comma-separated, e.g. tree:parent,tree:children,group:team
188
196
  still reach you after your tool calls, with another worker's push, or in your inbox.
189
197
  codex gets -c overrides that hand the grant to its scp MCP server
190
198
  (also behind env, VAR=value, timeout, nohup or nice). codex exec outside a git
191
- repo gets --skip-git-repo-check when its codex has it, and with its prompt in argv
192
- runs on /dev/null for stdin (a - prompt, or none, keeps stdin).
199
+ repo gets --skip-git-repo-check when its codex has it. With its prompt in argv it
200
+ runs on /dev/null for stdin, except in the foreground (--fg), where piped context
201
+ or a file reaches it (from an agent's shell only a pipe with data within 300 ms);
202
+ a - prompt, or none, keeps stdin.
193
203
  claude -p and grok -p get the relay messaging tools allowed (--allowedTools,
194
204
  grok --allow) unless you pass a permission flag. muse runs with
195
205
  --contributor-mode off unless you pass --contributor-mode on (or a -c contributor
@@ -222,6 +232,7 @@ scopes (selectors are comma-separated, e.g. tree:parent,tree:children,group:team
222
232
  other process under an agent they may only narrow. An ancestor always reaches its descendants, and a recipient
223
233
  may always reply in the thread to a message it got; a reply to another message of the thread is checked as a send.
224
234
  ${CLI_NAME} daemon [--idle DURATION] [--policy FILE] run the broker in the foreground (it otherwise starts on demand)
235
+ ${CLI_NAME} daemon stop|restart stop the running broker, or replace it with a fresh one
225
236
 
226
237
  agent sessions:
227
238
  ${CLI_NAME} mcp [--as NAME] [--harness H] [--channels] the MCP shim a harness mounts: relay tools for one agent, never me/
@@ -246,7 +257,9 @@ agent sessions:
246
257
  A Claude Code subagent's hooks give it a child endpoint, <session handle>-<type>-<first 6 of its id>, whose messages
247
258
  reach it after its tools; its shell is the session's, so it acts as that child with --as.
248
259
  Grok runs the Claude Code hooks, labelled grok; Muse takes them as a plugin; Codex runs new hooks after /hooks review.
249
- --channels pushes messages into Claude Code started with --dangerously-load-development-channels server:scp.
260
+ ${CLI_NAME} claude [--name N] [--] [claude args...] start Claude Code with the scp channel loaded, so peer messages wake
261
+ it while idle (claude --dangerously-load-development-channels server:scp; SCP_NAME=N)
262
+ --channels pushes messages into a Claude Code that ${CLI_NAME} claude started; setup mounts claude's shim with it.
250
263
 
251
264
  relay:
252
265
  ${CLI_NAME} relay use <url> | show select a relay, or show its source and credential status
@@ -348,6 +361,7 @@ var VERBS = {
348
361
  machines: IDENTITY
349
362
  };
350
363
  var MESSAGE_REF = /^(last|[0-9a-z]{4,})$/;
364
+ var ACK_ALL_PAGES = Math.ceil(2 * LIMITS.inboxMax / LIMITS.inboxPage) + 1;
351
365
  async function lookup(client, id, member = false) {
352
366
  try {
353
367
  return (await client.request("message", { id, ...member ? { member: true } : {} })).message;
@@ -386,6 +400,7 @@ function printMessage(message) {
386
400
  out(
387
401
  `#${message.seq} ${message.kind} from ${message.from.address} (${harness}) ${time(message.created_at)} id ${shortId(message.id)} (${message.id})${message.reply_to ? ` reply to ${message.reply_to}` : ""}`
388
402
  );
403
+ if (message.shown_at) out(`(a hook already showed you this at ${time(message.shown_at)}; that copy may reach you after this command)`);
389
404
  out(wrapMessage(message));
390
405
  out();
391
406
  }
@@ -481,10 +496,11 @@ async function relayMain(argv, cwd = process.cwd()) {
481
496
  out(package_default.version);
482
497
  return 0;
483
498
  }
484
- if (verb === "hook") return (await import("./hooks-MBKU56HG.js")).relayHookCommand(rest, startDaemon);
485
- if (verb === "setup") return (await import("./setup-T4ZZKV7K.js")).relaySetupCommand(rest, cwd, out);
499
+ if (verb === "hook") return (await import("./hooks-4YVIPZKE.js")).relayHookCommand(rest, startDaemon);
500
+ if (verb === "setup") return (await import("./setup-UF4AA6RO.js")).relaySetupCommand(rest, cwd, out);
501
+ if (verb === "claude") return (await import("./claude-5WTICOYS.js")).claudeCommand(rest);
486
502
  if (verb === "spawn") {
487
- const { splitSpawn, spawnCommand } = await import("./spawn-7P4EMSVB.js");
503
+ const { splitSpawn, spawnCommand } = await import("./spawn-KT5235WX.js");
488
504
  const { flags } = splitSpawn(rest);
489
505
  if (flags.includes("--help") || flags.includes("-h")) {
490
506
  out(flags.includes("--all") ? RELAY_HELP : SPAWN_HELP);
@@ -506,13 +522,16 @@ async function relayMain(argv, cwd = process.cwd()) {
506
522
  return 0;
507
523
  }
508
524
  for (const key of Object.keys(values)) if (!allowed.includes(key)) throw new RelayUsage(`${CLI_NAME} ${verb} does not take --${key}`);
509
- if (verb === "daemon") return daemon(values.idle, values.policy);
525
+ if (verb === "daemon") {
526
+ if (positionals.length) return daemonControl(positionals, values);
527
+ return daemon(values.idle, values.policy);
528
+ }
510
529
  if (verb === "mcp")
511
- return (await import("./mcp-EQCSCD3X.js")).relayMcpCommand(values, positionals);
530
+ return (await import("./mcp-GI5OMTUT.js")).relayMcpCommand(values, positionals);
512
531
  if (verb === "hooks") {
513
532
  if (positionals[0] !== "install" || positionals.length > 1) throw new RelayUsage(`${CLI_NAME} hooks takes install --harness claude|codex|opencode|agy|muse`);
514
533
  if (values.write && values.print) throw new RelayUsage("--print and --write: pick one");
515
- return (await import("./install-4HATFIFI.js")).installHooks({
534
+ return (await import("./install-4KQQWGGB.js")).installHooks({
516
535
  harness: values.harness,
517
536
  scope: values.scope,
518
537
  write: values.write === true,
@@ -523,18 +542,25 @@ async function relayMain(argv, cwd = process.cwd()) {
523
542
  out
524
543
  });
525
544
  }
526
- if (verb === "relay") return (await import("./relay-VVJPYVXE.js")).relayVerb(positionals, values.json === true);
527
- if (verb === "up") return (await import("./up-Z4RSJMED.js")).up(positionals, values, cwd, cloudStatus);
528
- if (verb === "login")
529
- return (await import("./relay-VVJPYVXE.js")).loginMain(positionals, values);
530
- if (verb === "logout") return (await import("./relay-VVJPYVXE.js")).logoutMain(positionals, values.json === true);
531
- if (verb === "cloud") return (await import("./remote-3BWFISXJ.js")).cloudMain(positionals, values, cloudStatus);
545
+ if (verb === "relay") return (await import("./relay-W2NFUE2U.js")).relayVerb(positionals, values.json === true);
546
+ if (verb === "up") return (await import("./up-VA54QWA6.js")).up(positionals, values, cwd, relinkBroker);
547
+ if (verb === "login") {
548
+ const code = await (await import("./relay-W2NFUE2U.js")).loginMain(positionals, values);
549
+ if (code === 0) await relinkRunning();
550
+ return code;
551
+ }
552
+ if (verb === "logout") return (await import("./relay-W2NFUE2U.js")).logoutMain(positionals, values.json === true);
553
+ if (verb === "cloud") {
554
+ const code = await (await import("./remote-GC4XN44U.js")).cloudMain(positionals, values, cloudStatus);
555
+ if (code === 0 && positionals[0] === "enable") await relinkRunning();
556
+ return code;
557
+ }
532
558
  if (verb === "holds") holdsArgs(values, positionals);
533
559
  const bodyFromStdin = (verb === "send" || verb === "ask" || verb === "reply") && (positionals[1] === void 0 || positionals[1] === "-");
534
560
  const opened = await open(verb, values, cwd, bodyFromStdin);
535
561
  const { client } = opened;
536
562
  try {
537
- if (verb === "machines") return await (await import("./remote-3BWFISXJ.js")).machinesVerb(client, positionals, values.json === true);
563
+ if (verb === "machines") return await (await import("./remote-GC4XN44U.js")).machinesVerb(client, positionals, values.json === true);
538
564
  return await runVerb(opened, verb, values, positionals, cwd);
539
565
  } finally {
540
566
  client.close();
@@ -550,6 +576,55 @@ async function cloudStatus() {
550
576
  client.close();
551
577
  }
552
578
  }
579
+ async function relinkBroker() {
580
+ const dir = relayDir();
581
+ const client = await connectBroker(dir, () => startDaemon(dir));
582
+ let result;
583
+ let pid;
584
+ try {
585
+ pid = (await client.hello({ observe: true })).broker.pid;
586
+ result = await client.request("bridge_relink");
587
+ } catch (error) {
588
+ if (error instanceof RelayError && error.code === "method_not_found") return cloudStatus();
589
+ throw error;
590
+ } finally {
591
+ client.close();
592
+ }
593
+ if (!result.restarting) return result;
594
+ await (await import("./daemon-SUTLTY5U.js")).exited(pid);
595
+ return cloudStatus();
596
+ }
597
+ async function relinkRunning() {
598
+ try {
599
+ await connectBroker(relayDir()).then((client) => client.close());
600
+ } catch {
601
+ return;
602
+ }
603
+ try {
604
+ await relinkBroker();
605
+ } catch (error) {
606
+ note(`the running broker did not link again (${error instanceof Error ? error.message : String(error)}); run \`${CLI_NAME} daemon restart\``);
607
+ }
608
+ }
609
+ async function daemonControl(positionals, values) {
610
+ const [action] = positionals;
611
+ if (positionals.length !== 1 || action !== "stop" && action !== "restart" || values.idle !== void 0 || values.policy !== void 0)
612
+ throw new RelayUsage(`${CLI_NAME} daemon takes stop or restart, or --idle and --policy to run in the foreground`);
613
+ const dir = relayDir();
614
+ const pid = await (await import("./daemon-SUTLTY5U.js")).stopBroker(dir);
615
+ if (action === "stop") {
616
+ out(pid === null ? "no relay broker runs" : `stopped the relay broker (pid ${pid}); the next command starts a fresh one`);
617
+ return 0;
618
+ }
619
+ const client = await connectBroker(dir, () => startDaemon(dir));
620
+ try {
621
+ const { broker } = await client.hello({ observe: true });
622
+ out(`${pid === null ? "started" : `replaced the relay broker (pid ${pid}) with`} a fresh one (pid ${broker.pid})`);
623
+ } finally {
624
+ client.close();
625
+ }
626
+ return 0;
627
+ }
553
628
  async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, values, positionals, cwd) {
554
629
  const asJson = values.json === true;
555
630
  const attachments = (values.attach ?? []).map((path) => ({ kind: "path", path: path.startsWith("/") ? path : `${cwd}/${path}` }));
@@ -662,9 +737,9 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
662
737
  return 0;
663
738
  }
664
739
  case "peek":
665
- return (await import("./peek-YCOWBEPD.js")).peekCommand(localClient(client, broker, verb), values, positionals);
740
+ return (await import("./peek-X4SEGEUB.js")).peekCommand(localClient(client, broker, verb), values, positionals);
666
741
  case "set-peek":
667
- return (await import("./peek-YCOWBEPD.js")).setPeekCommand(localClient(client, broker, verb), values, positionals);
742
+ return (await import("./peek-X4SEGEUB.js")).setPeekCommand(localClient(client, broker, verb), values, positionals);
668
743
  case "why": {
669
744
  const [from, to] = positionals;
670
745
  if (!from || !to || positionals.length > 2) throw new RelayUsage(`${CLI_NAME} why takes <from> <to>`);
@@ -697,6 +772,8 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
697
772
  const lines = reachLines(result2.deliveries);
698
773
  out(`sent ${result2.id} to ${result2.deliveries.map((d) => lines.length ? d.address : deliveryText(d)).join(", ")}${redactedText(result2.redacted)}`);
699
774
  for (const line of lines) out(` ${line}`);
775
+ if (result2.deliveries.some((d) => d.state === "running" || d.state === "starting"))
776
+ out(` ${CLI_NAME} status ${shortId(result2.id)} shows when it is read`);
700
777
  }
701
778
  return 0;
702
779
  }
@@ -735,9 +812,10 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
735
812
  }
736
813
  case "inbox": {
737
814
  if (values.ack && values.keep) throw new RelayUsage("--ack and --keep: pick one");
738
- const { messages } = await client.request("inbox", { wait_s: waitArg(values.wait, 0) });
815
+ const waited = waitArg(values.wait, 0);
816
+ const { messages } = await client.request("inbox", { wait_s: waited });
739
817
  if (asJson) json({ preamble: PREAMBLE, messages });
740
- else if (!messages.length) note("no unread messages");
818
+ else if (!messages.length) note(`no unread messages${waited > LIMITS.waitMaxS ? `; waited ${LIMITS.waitMaxS} s (cap)` : ""}`);
741
819
  else {
742
820
  out(PREAMBLE);
743
821
  out();
@@ -757,9 +835,9 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
757
835
  result.acked += (await client.request("ack", { seqs: seqs.slice(i, i + LIMITS.inboxPage) })).acked;
758
836
  };
759
837
  if (values.all) {
760
- for (let before = -1; before !== result.acked; ) {
761
- before = result.acked;
838
+ for (let page = 0; page < ACK_ALL_PAGES; page++) {
762
839
  const { messages } = await client.request("inbox", { wait_s: 0 });
840
+ if (!messages.length) break;
763
841
  await ack(messages.flatMap((m) => m.seq === void 0 ? [] : [m.seq]));
764
842
  }
765
843
  } else {
@@ -778,6 +856,20 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
778
856
  return 0;
779
857
  }
780
858
  case "status": {
859
+ if (positionals.length) {
860
+ if (positionals.length > 1 || values.all || values.activity)
861
+ throw new RelayUsage(`${CLI_NAME} status takes one message id, or --all and --activity for the fleet`);
862
+ if (!broker) throw new RelayUsage(`${CLI_NAME} status <id> is the local broker's; it has no --cloud form`);
863
+ if (!me) throw new RelayUsage(`${CLI_NAME} status <id> reads a message you sent: pass --as HANDLE, or run it from the agent's shell or your terminal`);
864
+ const receipts = await client.request("receipts", { id: positionals[0] });
865
+ if (asJson) json(receipts);
866
+ else {
867
+ out(`${receipts.id}:`);
868
+ const lines = receiptLines(receipts);
869
+ for (const line of lines.length ? lines : ["no deliveries of it are kept any more"]) out(` ${line}`);
870
+ }
871
+ return 0;
872
+ }
781
873
  const snapshot = await client.request("snapshot", { all: values.all === true });
782
874
  if (asJson) {
783
875
  json(snapshot);
@@ -1200,11 +1292,12 @@ async function daemon(idle, policyFile) {
1200
1292
  });
1201
1293
  let broker;
1202
1294
  try {
1203
- const { daemonBridge } = await import("./remote-3BWFISXJ.js");
1295
+ const { daemonBridge } = await import("./remote-GC4XN44U.js");
1204
1296
  const bridge = await daemonBridge();
1205
1297
  broker = await startBroker({
1206
1298
  dir: relayDir(),
1207
1299
  ...bridge ? { bridge } : {},
1300
+ bridgeConfig: () => daemonBridge(),
1208
1301
  ...idleMs !== void 0 ? { idleMs } : {},
1209
1302
  ...policyFile ? { policyPath: resolve(policyFile) } : {}
1210
1303
  });
@@ -1,16 +1,24 @@
1
+ import {
2
+ spawnDetached
3
+ } from "./chunk-ABP5AKFH.js";
4
+ import "./chunk-KN7WEIE4.js";
5
+ import "./chunk-IH2RJRIW.js";
1
6
  import {
2
7
  mcpInstall
3
- } from "./chunk-WSLKRAB7.js";
8
+ } from "./chunk-QQZF5TQD.js";
4
9
  import {
10
+ CHANNELS_ENV,
5
11
  RelayUsage,
6
12
  startDaemon
7
- } from "./chunk-7KP6TAJW.js";
8
- import "./chunk-RO7CUOOH.js";
13
+ } from "./chunk-URSBFDR6.js";
14
+ import "./chunk-ABR5UABL.js";
9
15
  import "./chunk-YYSLYVNZ.js";
10
16
  import {
11
17
  harnessPid,
18
+ psParent,
12
19
  relayIdentity
13
- } from "./chunk-DA3EB2YF.js";
20
+ } from "./chunk-3PGDKPRN.js";
21
+ import "./chunk-M2DCR2J4.js";
14
22
 
15
23
  // src/mcp.ts
16
24
  import { CLI_NAME, validHandle } from "@scp-dev/core";
@@ -41,11 +49,33 @@ async function relayMcpCommand(values, positionals) {
41
49
  output: (line) => void process.stdout.write(line),
42
50
  env: process.env,
43
51
  channels: values.channels === true,
52
+ launchedWithChannels: values.channels === true && launchedWithChannels(process.env),
53
+ spawn: (request) => spawnDetached(spawnWords(request), request.cwd ?? claim.cwd),
44
54
  log: (line) => void process.stderr.write(`${line}
45
55
  `)
46
56
  });
47
57
  return 0;
48
58
  }
59
+ function spawnWords(request) {
60
+ return [
61
+ "--bg",
62
+ "--as",
63
+ request.as,
64
+ ...request.harness !== void 0 ? ["--harness", request.harness] : [],
65
+ ...request.name !== void 0 ? ["--name", request.name] : [],
66
+ ...request.title !== void 0 ? ["--title", request.title] : [],
67
+ ...request.no_push ? ["--no-push"] : [],
68
+ ...request.handback_bytes !== void 0 ? ["--handback-bytes", String(request.handback_bytes)] : [],
69
+ "--",
70
+ ...request.command
71
+ ];
72
+ }
73
+ function launchedWithChannels(env, harness = harnessPid, parentOf = psParent) {
74
+ const launcher = Number(env[CHANNELS_ENV]);
75
+ return Number.isInteger(launcher) && launcher > 1 && parentOf(harness())?.ppid === launcher;
76
+ }
49
77
  export {
50
- relayMcpCommand
78
+ launchedWithChannels,
79
+ relayMcpCommand,
80
+ spawnWords
51
81
  };
@@ -2,10 +2,11 @@ import {
2
2
  RelayUsage,
3
3
  peekGrant,
4
4
  stamp
5
- } from "./chunk-7KP6TAJW.js";
5
+ } from "./chunk-URSBFDR6.js";
6
+ import "./chunk-3PGDKPRN.js";
6
7
  import {
7
8
  note
8
- } from "./chunk-DA3EB2YF.js";
9
+ } from "./chunk-M2DCR2J4.js";
9
10
 
10
11
  // src/peek.ts
11
12
  import { PREAMBLE, wrapPeek, CLI_NAME } from "@scp-dev/core";
@@ -11,9 +11,10 @@ import {
11
11
  removeCredential,
12
12
  saveCredential,
13
13
  writePrivateJson
14
- } from "./chunk-4QYUPPGV.js";
15
- import "./chunk-7KP6TAJW.js";
16
- import "./chunk-DA3EB2YF.js";
14
+ } from "./chunk-BUC5G5PY.js";
15
+ import "./chunk-URSBFDR6.js";
16
+ import "./chunk-3PGDKPRN.js";
17
+ import "./chunk-M2DCR2J4.js";
17
18
  export {
18
19
  DEFAULT_RELAY,
19
20
  activeRelay,
@@ -9,10 +9,11 @@ import {
9
9
  machinesVerb,
10
10
  openCloud,
11
11
  readCloudFile
12
- } from "./chunk-6UFMKYPH.js";
13
- import "./chunk-4QYUPPGV.js";
14
- import "./chunk-7KP6TAJW.js";
15
- import "./chunk-DA3EB2YF.js";
12
+ } from "./chunk-5UWLTO2Y.js";
13
+ import "./chunk-BUC5G5PY.js";
14
+ import "./chunk-URSBFDR6.js";
15
+ import "./chunk-3PGDKPRN.js";
16
+ import "./chunk-M2DCR2J4.js";
16
17
  export {
17
18
  CloudRelayClient,
18
19
  CloudUsage,