@scp-dev/cli 0.1.0 → 0.1.2

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.
@@ -13,7 +13,7 @@ import {
13
13
  mergeHooks,
14
14
  musePlugin,
15
15
  opencodePlugin
16
- } from "./chunk-RO7CUOOH.js";
16
+ } from "./chunk-XWLF7ON2.js";
17
17
  import {
18
18
  gitToplevel
19
19
  } from "./chunk-DA3EB2YF.js";
@@ -29,7 +29,7 @@ Steer: ${CLI_NAME} send <name> <text>; a claude -p or codex exec prompt gets a n
29
29
  A codex exec worker that exited before a late steer is resumed with it (codex exec resume, at most twice).
30
30
  Harnesses: codex gets -c overrides that hand it the grant (also behind env, timeout, nohup or nice); a ${CLI_NAME}
31
31
  command from its workspace-write sandbox needs -c sandbox_workspace_write.network_access=true. codex exec
32
- gets --skip-git-repo-check outside a git repo and a closed stdin unless its prompt is - (read from stdin). Workers
32
+ gets --skip-git-repo-check outside a git repo, and with its prompt in argv piped stdin only under --fg. Workers
33
33
  message with the relay MCP tools (relay_send, relay_inbox, relay_reply). claude -p and grok -p get those tools allowed
34
34
  unless you pass a permission flag. muse runs with --contributor-mode off unless you pass on. agy needs the allow rules ${CLI_NAME}
35
35
  setup writes.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  hooksTarget
3
- } from "./chunk-WSLKRAB7.js";
3
+ } from "./chunk-4DNQFATX.js";
4
4
  import {
5
5
  RelayUsage
6
6
  } from "./chunk-7KP6TAJW.js";
@@ -15,7 +15,7 @@ import {
15
15
  mergeHooks,
16
16
  musePlugin,
17
17
  opencodePlugin
18
- } from "./chunk-RO7CUOOH.js";
18
+ } from "./chunk-XWLF7ON2.js";
19
19
  import {
20
20
  gitToplevel
21
21
  } from "./chunk-DA3EB2YF.js";
@@ -1,13 +1,9 @@
1
- import {
2
- SPAWN_HELP
3
- } from "./chunk-IE5VI25P.js";
4
1
  import {
5
2
  agyRules,
6
3
  agySettingsFile,
7
4
  mcpFile,
8
5
  mountedEntry
9
- } from "./chunk-O4J5G2X7.js";
10
- import "./chunk-WSLKRAB7.js";
6
+ } from "./chunk-N3ZROLFA.js";
11
7
  import {
12
8
  OPTIONS,
13
9
  RelayUsage,
@@ -20,8 +16,7 @@ import {
20
16
  } from "./chunk-7KP6TAJW.js";
21
17
  import {
22
18
  steerPrompt
23
- } from "./chunk-RO7CUOOH.js";
24
- import "./chunk-YYSLYVNZ.js";
19
+ } from "./chunk-XWLF7ON2.js";
25
20
  import {
26
21
  SELF,
27
22
  detectHarness,
@@ -297,6 +292,35 @@ function closedStdin(command) {
297
292
  if (run.sub?.name === "resume") return run.operands.length >= (hasFlag(args, ["--last"]) ? 1 : 2);
298
293
  return run.sub !== void 0 || run.operands.length > 0;
299
294
  }
295
+ var STDIN_WAIT_MS = 300;
296
+ async function harnessStdin(waitMs) {
297
+ if (process.stdin.isTTY === true) return "ignore";
298
+ let stat;
299
+ try {
300
+ stat = fstatSync(0);
301
+ } catch {
302
+ return "ignore";
303
+ }
304
+ if (stat.isFile()) return stat.size > 0 ? "inherit" : "ignore";
305
+ if (!stat.isFIFO() && !stat.isSocket()) return "ignore";
306
+ const input = process.stdin;
307
+ return new Promise((resolve2) => {
308
+ const settle = (result) => {
309
+ clearTimeout(timer);
310
+ input.off("data", data);
311
+ input.off("end", ended);
312
+ input.off("error", ended);
313
+ input.pause();
314
+ resolve2(result);
315
+ };
316
+ const data = (chunk) => settle({ first: chunk, rest: input });
317
+ const ended = () => settle("ignore");
318
+ const timer = waitMs === void 0 ? void 0 : setTimeout(ended, waitMs);
319
+ input.on("data", data);
320
+ input.once("end", ended);
321
+ input.once("error", ended);
322
+ });
323
+ }
300
324
  function harnessArgv(command, env, self = SELF, mount = codexMount, cwd = process.cwd(), probe = codexSkipsGitCheck) {
301
325
  const at = programAt(command);
302
326
  const program = command[at];
@@ -492,11 +516,19 @@ function runHarness(argv, env, options = {}) {
492
516
  return new Promise((resolve2) => {
493
517
  const tty = process.stdin.isTTY === true;
494
518
  const group = options.group === true && !tty && process.stdout.isTTY !== true && process.stderr.isTTY !== true;
519
+ const input = options.stdin ?? "inherit";
520
+ const piped = typeof input === "object" ? input : void 0;
495
521
  const child = spawn(argv[0], argv.slice(1), {
496
- stdio: [options.stdin ?? "inherit", capture ? "pipe" : "inherit", captureErr ? "pipe" : "inherit"],
522
+ stdio: [typeof input === "object" ? "pipe" : input, capture ? "pipe" : "inherit", captureErr ? "pipe" : "inherit"],
497
523
  env,
498
524
  detached: group
499
525
  });
526
+ if (piped && child.stdin) {
527
+ child.stdin.on("error", () => {
528
+ });
529
+ child.stdin.write(piped.first);
530
+ piped.rest.pipe(child.stdin);
531
+ }
500
532
  const pipes = [child.stdout, child.stderr].filter((pipe) => pipe !== null);
501
533
  const stdout = child.stdout ? tee(child.stdout, out) : void 0;
502
534
  const stderr = child.stderr ? tee(child.stderr, options.err ?? process.stderr) : void 0;
@@ -638,7 +670,11 @@ ${LIMIT_CUT}`);
638
670
  return replies;
639
671
  }
640
672
  function repliesOutput(replies, bytes = LIMITS.handbackBytes) {
641
- const clean = replies.map((reply) => handbackOutput(reply.replace(/\r\n?/g, "\n"), false, Number.MAX_SAFE_INTEGER)).filter(Boolean);
673
+ const lines = (text) => text.split("\n").map((line) => line.trim()).filter(Boolean);
674
+ const all = replies.map((reply) => handbackOutput(reply.replace(/\r\n?/g, "\n"), false, Number.MAX_SAFE_INTEGER)).filter(Boolean);
675
+ const restated = (text, later) => lines(text).length >= 2 && lines(text).every((line) => lines(later).includes(line));
676
+ const clean = all.filter((text, i) => text === HANDBACK_CUT || !all.slice(i + 1).some((later) => restated(text, later)));
677
+ if (clean.every((text) => text === HANDBACK_CUT)) return "";
642
678
  const size = (text) => new TextEncoder().encode(text).length;
643
679
  const framed = (from) => {
644
680
  const text = clean.slice(from).join("\n\n");
@@ -648,7 +684,7 @@ ${text}` : text;
648
684
  };
649
685
  let fit;
650
686
  for (let from = clean.length - 1; from >= 0 && size(framed(from)) <= bytes; from--) fit = framed(from);
651
- if (fit !== void 0 || !clean.length) return fit ?? "";
687
+ if (fit !== void 0) return fit;
652
688
  return keepLast(clean.at(-1), bytes, clean.length > 1);
653
689
  }
654
690
  function splitSpawn(args) {
@@ -682,20 +718,9 @@ function handbackBytes(value) {
682
718
  }
683
719
  var STEER_RESUMES = 2;
684
720
  async function spawnCommand(args, cwd) {
685
- const split = splitSpawn(args);
686
- let values;
687
- try {
688
- values = parseArgs({ args: split.flags, options: OPTIONS, strict: true }).values;
689
- } catch (error) {
690
- throw new RelayUsage(error instanceof Error ? error.message : String(error));
691
- }
692
- for (const key of Object.keys(values)) if (!SPAWN_OPTIONS.includes(key)) throw new RelayUsage(`${CLI_NAME} spawn does not take --${key}`);
693
- if (values.bg && values.fg) throw new RelayUsage("--bg and --fg: pick one");
694
- const command = split.command;
695
- if (!command.length)
696
- throw new RelayUsage(`${CLI_NAME} spawn runs a harness command: ${CLI_NAME} spawn [flags] [--] <harness cmd...>, e.g. ${CLI_NAME} spawn codex exec "..."`);
697
- const refusal = contributorRefusal(command);
698
- if (refusal) throw new RelayUsage(refusal);
721
+ const parsed = parseSpawn(args);
722
+ let values = parsed.values;
723
+ const { command } = parsed;
699
724
  const bytes = values["handback-bytes"] === void 0 ? void 0 : handbackBytes(values["handback-bytes"]);
700
725
  const runner = process.env[SPAWN_RUNNER];
701
726
  delete process.env[SPAWN_RUNNER];
@@ -737,8 +762,10 @@ async function spawnCommand(args, cwd) {
737
762
  const agy = agyNote(command, process.env);
738
763
  if (agy) note(agy);
739
764
  const started = handshake && ((failed) => handshake(failed ? { error: failed.message, code: failed.code } : { name, pid: process.pid }));
740
- const launch = (argv, extra = {}) => runHarness(harnessArgv(argv, env), env, { ...extra, group: runner !== void 0, ...closedStdin(argv) ? { stdin: "ignore" } : {} });
741
- let run = await launch(steerArgv(command, steer), started ? { started } : {});
765
+ const launch = (argv, extra = {}) => runHarness(harnessArgv(argv, env), env, { group: runner !== void 0, ...closedStdin(argv) ? { stdin: "ignore" } : {}, ...extra });
766
+ const first = steerArgv(command, steer);
767
+ const stdin = closedStdin(first) ? await harnessStdin(agentShell(process.env) ? STDIN_WAIT_MS : void 0) : void 0;
768
+ let run = await launch(first, { ...started ? { started } : {}, ...stdin ? { stdin } : {} });
742
769
  if (!run.started) {
743
770
  await spawnExit(values, cwd, grant.SCP_GRANT, run.code, name, "", walk, [], [], false);
744
771
  return run.code;
@@ -761,6 +788,32 @@ async function spawnCommand(args, cwd) {
761
788
  await spawnExit(values, cwd, grant.SCP_GRANT, run.code, name, replies || harnessOutput(run, bytes), walk, unapplied, exits, true, stderrOutput(run));
762
789
  return run.code;
763
790
  }
791
+ function parseSpawn(args) {
792
+ const split = splitSpawn(args);
793
+ let values;
794
+ try {
795
+ values = parseArgs({ args: split.flags, options: OPTIONS, strict: true }).values;
796
+ } catch (error) {
797
+ throw new RelayUsage(error instanceof Error ? error.message : String(error));
798
+ }
799
+ if (values.help) return { values, command: split.command };
800
+ for (const key of Object.keys(values)) if (!SPAWN_OPTIONS.includes(key)) throw new RelayUsage(`${CLI_NAME} spawn does not take --${key}`);
801
+ if (values.bg && values.fg) throw new RelayUsage("--bg and --fg: pick one");
802
+ const command = split.command;
803
+ if (!command.length)
804
+ throw new RelayUsage(`${CLI_NAME} spawn runs a harness command: ${CLI_NAME} spawn [flags] [--] <harness cmd...>, e.g. ${CLI_NAME} spawn codex exec "..."`);
805
+ const refusal = contributorRefusal(command);
806
+ if (refusal) throw new RelayUsage(refusal);
807
+ if (values["handback-bytes"] !== void 0) handbackBytes(values["handback-bytes"]);
808
+ return { values, command };
809
+ }
810
+ async function spawnDetached(args, cwd) {
811
+ const { values } = parseSpawn(args);
812
+ if (values.help || values.fg) throw new RelayUsage("a detached spawn takes no --help or --fg");
813
+ const reply = await detachedRunner(args, cwd);
814
+ if ("error" in reply) throw new Error(reply.error);
815
+ return reply;
816
+ }
764
817
  function agentShell(env) {
765
818
  return detectHarness(env) !== void 0 || ["SCP_NAME", "SCP_GRANT", "SCP_SESSION"].some((key) => env[key]);
766
819
  }
@@ -782,6 +835,17 @@ function once(fn) {
782
835
  };
783
836
  }
784
837
  async function detachSpawn(args, cwd) {
838
+ const reply = await detachedRunner(args, cwd);
839
+ if ("error" in reply) {
840
+ note(reply.error);
841
+ return reply.code;
842
+ }
843
+ console.log(
844
+ `${reply.name} started (pid ${reply.pid}) and runs in the background: its result arrives as a relay message by itself; steer it with ${CLI_NAME} send ${reply.name} <text>`
845
+ );
846
+ return 0;
847
+ }
848
+ async function detachedRunner(args, cwd) {
785
849
  const walk = walkNow();
786
850
  const child = spawn(SELF[0], [...SELF.slice(1), "spawn", ...args], {
787
851
  cwd,
@@ -810,13 +874,9 @@ async function detachSpawn(args, cwd) {
810
874
  if ("error" in reply) {
811
875
  if (reply.usage) throw new RelayUsage(reply.error);
812
876
  if (reply.code === void 0) throw new Error(reply.error);
813
- note(reply.error);
814
- return reply.code;
877
+ return { error: reply.error, code: reply.code };
815
878
  }
816
- console.log(
817
- `${reply.name} started (pid ${reply.pid}) and runs in the background: its result arrives as a relay message by itself; steer it with ${CLI_NAME} send ${reply.name} <text>`
818
- );
819
- return 0;
879
+ return reply;
820
880
  }
821
881
  async function spawnSteers(values, cwd, token, walk, take) {
822
882
  try {
@@ -853,33 +913,36 @@ async function spawnExit(values, cwd, token, code, name, output, walk, unapplied
853
913
  }
854
914
  }
855
915
  var SPAWN_GRANT_TTL_S = 3600;
916
+
856
917
  export {
857
- MESSAGING,
858
- SPAWN_HELP,
859
918
  SPAWN_RUNNER,
860
- agentShell,
861
- agyNote,
862
- claudeTranscripts,
863
- closedStdin,
919
+ spawnEnvironment,
864
920
  codexMount,
865
- codexOverrideAt,
866
921
  codexOverrides,
867
- codexSkipsGitCheck,
922
+ codexOverrideAt,
923
+ programAt,
924
+ MESSAGING,
868
925
  contributorRefusal,
926
+ codexSkipsGitCheck,
927
+ closedStdin,
928
+ STDIN_WAIT_MS,
929
+ harnessStdin,
869
930
  harnessArgv,
870
- harnessOutput,
871
- programAt,
872
- repliesOutput,
873
931
  resumeArgv,
874
- runHarness,
875
- spawnCommand,
876
- spawnDefaults,
877
- spawnEnvironment,
878
932
  spawnPrompt,
933
+ steerArgv,
879
934
  spawnTitle,
880
- splitSpawn,
935
+ workerName,
936
+ agyNote,
937
+ runHarness,
938
+ harnessOutput,
881
939
  stderrOutput,
882
- steerArgv,
940
+ claudeTranscripts,
883
941
  transcriptReplies,
884
- workerName
942
+ repliesOutput,
943
+ splitSpawn,
944
+ spawnDefaults,
945
+ spawnCommand,
946
+ spawnDetached,
947
+ agentShell
885
948
  };
@@ -238,9 +238,16 @@ function pushText(result) {
238
238
  `Push budget used up for this prompt; read the rest with ${CLI_NAME} inbox.${running.length ? ` Workers you spawned still running: ${running.join(", ")}.` : ""}`
239
239
  );
240
240
  } else {
241
- parts.push(
242
- running.length ? `Workers you spawned still running: ${running.join(", ")}; their next message reaches you when you stop.` : "No worker you spawned is still running."
243
- );
241
+ const background = result.background ?? [];
242
+ if (running.length) {
243
+ parts.push(
244
+ `Workers you spawned still running: ${running.join(", ")}. End your turn to wait for them; no reply needed.${background.length ? ` Also running with --no-push: ${background.join(", ")}.` : ""}`
245
+ );
246
+ } else if (background.length) {
247
+ parts.push(
248
+ `No worker your stop waits on is still running; ${background.join(", ")} (--no-push) still ${background.length === 1 ? "runs" : "run"} in the background.`
249
+ );
250
+ } else parts.push("No worker you spawned is still running.");
244
251
  }
245
252
  return parts.join("\n\n");
246
253
  }
@@ -290,7 +297,8 @@ function hookReply(harness, kind, text, unread = 0, continues = true) {
290
297
  const hookEventName = kind === "tool" ? "PostToolUse" : kind === "session_start" ? "SessionStart" : "UserPromptSubmit";
291
298
  return JSON.stringify({ hookSpecificOutput: { hookEventName, additionalContext: text } });
292
299
  }
293
- var NUDGE = `${CLI_NAME}: set your title and a one-line now with relay_set_title / relay_set_now so peers can find you.`;
300
+ var DEFERS = /* @__PURE__ */ new Set(["claude-code", "codex"]);
301
+ var NUDGE = `${CLI_NAME} (optional): a title and a one-line now (relay_set_title / relay_set_now) help peers find you; skip it when you only run workers.`;
294
302
  async function runHook(args, options) {
295
303
  const began = Date.now();
296
304
  const blockStop = args.includes(BLOCK_STOP);
@@ -391,7 +399,8 @@ async function runHook(args, options) {
391
399
  ...nudged.length ? { nudged: nudged.map((n) => n.seq) } : {},
392
400
  ...directive ? { restated: directive.restated } : {},
393
401
  ...full ? { preamble: true } : {},
394
- ...intro ? { intro: true } : {}
402
+ ...intro ? { intro: true } : {},
403
+ ...event.kind === "tool" && DEFERS.has(claim.harness) ? { deferred: true } : {}
395
404
  }).catch(() => {
396
405
  });
397
406
  }
@@ -442,7 +451,7 @@ async function subagentHook(client, claim, sub, event, takes, options) {
442
451
  }
443
452
  async function push(client, harness, waitMs, began, prompts, options, continues = true) {
444
453
  const end = Date.now() + waitMs;
445
- if (waitMs > 0) options.deadline?.(end - began + PUSH_PRINT_MS);
454
+ if (waitMs > 0) options.deadline?.(end - began + LIMITS.pushGraceS * 1e3 + PUSH_PRINT_MS);
446
455
  let result;
447
456
  for (; ; ) {
448
457
  result = await client.request("stop_wait", {
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-5CAHHN55.js");
21
21
  await run(process.argv.slice(2));
22
22
  }
@@ -30,7 +30,7 @@ import {
30
30
  subagentIntro,
31
31
  summarize,
32
32
  unreadPointer
33
- } from "./chunk-RO7CUOOH.js";
33
+ } from "./chunk-XWLF7ON2.js";
34
34
  import {
35
35
  SUBAGENT
36
36
  } from "./chunk-YYSLYVNZ.js";
@@ -2,9 +2,9 @@ import {
2
2
  hooksTarget,
3
3
  installHooks,
4
4
  mcpInstall
5
- } from "./chunk-WSLKRAB7.js";
5
+ } from "./chunk-4DNQFATX.js";
6
6
  import "./chunk-7KP6TAJW.js";
7
- import "./chunk-RO7CUOOH.js";
7
+ import "./chunk-XWLF7ON2.js";
8
8
  import "./chunk-YYSLYVNZ.js";
9
9
  import "./chunk-DA3EB2YF.js";
10
10
  export {
@@ -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,
@@ -33,7 +33,7 @@ import { parseArgs } from "util";
33
33
  // package.json
34
34
  var package_default = {
35
35
  name: "@scp-dev/cli",
36
- version: "0.1.0",
36
+ version: "0.1.2",
37
37
  description: "The swarm command line tool.",
38
38
  license: "Apache-2.0",
39
39
  homepage: "https://scp.dev",
@@ -78,6 +78,7 @@ import {
78
78
  LIMITS,
79
79
  PREAMBLE,
80
80
  reachLines,
81
+ receiptLines,
81
82
  RelayError,
82
83
  shortId,
83
84
  stripControl,
@@ -96,14 +97,16 @@ results back, as with native subagents.
96
97
  ${CLI_NAME} ask <to> [text|-] [--wait S] send a request and wait for its reply: ${CLI_NAME} ask lead "which branch?"
97
98
  ${CLI_NAME} inbox [--wait S] your unread messages, then marked read: ${CLI_NAME} inbox
98
99
  ${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
100
+ ${CLI_NAME} status [id] the fleet in short; with the id of a message you sent: who read it when
100
101
  ${CLI_NAME} peek <ep> a short, redacted view of a peekable agent's last turns: ${CLI_NAME} peek rev
101
102
  ${CLI_NAME} set-status <state> [note] working, waiting, blocked or done: ${CLI_NAME} set-status done "PR open"
102
103
 
103
104
  - spawn returns at once with the worker's name: do not background it (no &, no run_in_background).
104
105
  - A worker's messages and its handback (exit code and its whole reply) arrive by themselves: when you stop, your stop
105
106
  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>.
107
+ - Address a worker by the name spawn printed. Steer it meanwhile with ${CLI_NAME} send <name> <text>: it gets
108
+ the message after its next tool call or before it finishes, so its task need not tell it to check an inbox.
109
+ - With the relay MCP tools: relay_spawn starts a worker in one call, relay_send steers it, relay_status reads receipts.
107
110
  - --no-push: a worker your stop does not wait on; its messages and handback still reach you after your tool calls
108
111
  or with another worker's push.
109
112
  - An <id> is a message id, its short id (the last 6 characters, shown with every message) or last.
@@ -132,6 +135,9 @@ usage:
132
135
  ${CLI_NAME} ask-wait <id> [--wait S] keep waiting for the reply to a request
133
136
  ${CLI_NAME} status [--all] [--activity] the fleet in short: you, agents by presence and status, who waits or is blocked,
134
137
  unread counts; --activity adds the latest activity, --all counts offline agents
138
+ ${CLI_NAME} status <id> a message you sent, a line per recipient: read at HH:MM:SS, delivered at
139
+ HH:MM:SS (a background hook handed it over; it reaches the model with its next
140
+ request), shown by a hook at HH:MM:SS (in its context, not yet marked read), or unread
135
141
  ${CLI_NAME} watch a live tail of the fleet: presence, messages, status, activity
136
142
  ${CLI_NAME} set-status <state> [note] your status line: working, waiting, blocked or done
137
143
  ${CLI_NAME} set-title <text> what you work on, shown in list, peers and whoami ("" clears it)
@@ -188,8 +194,10 @@ scopes (selectors are comma-separated, e.g. tree:parent,tree:children,group:team
188
194
  still reach you after your tool calls, with another worker's push, or in your inbox.
189
195
  codex gets -c overrides that hand the grant to its scp MCP server
190
196
  (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).
197
+ repo gets --skip-git-repo-check when its codex has it. With its prompt in argv it
198
+ runs on /dev/null for stdin, except in the foreground (--fg), where piped context
199
+ or a file reaches it (from an agent's shell only a pipe with data within 300 ms);
200
+ a - prompt, or none, keeps stdin.
193
201
  claude -p and grok -p get the relay messaging tools allowed (--allowedTools,
194
202
  grok --allow) unless you pass a permission flag. muse runs with
195
203
  --contributor-mode off unless you pass --contributor-mode on (or a -c contributor
@@ -348,6 +356,7 @@ var VERBS = {
348
356
  machines: IDENTITY
349
357
  };
350
358
  var MESSAGE_REF = /^(last|[0-9a-z]{4,})$/;
359
+ var ACK_ALL_PAGES = Math.ceil(2 * LIMITS.inboxMax / LIMITS.inboxPage) + 1;
351
360
  async function lookup(client, id, member = false) {
352
361
  try {
353
362
  return (await client.request("message", { id, ...member ? { member: true } : {} })).message;
@@ -386,6 +395,7 @@ function printMessage(message) {
386
395
  out(
387
396
  `#${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
397
  );
398
+ 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
399
  out(wrapMessage(message));
390
400
  out();
391
401
  }
@@ -481,10 +491,10 @@ async function relayMain(argv, cwd = process.cwd()) {
481
491
  out(package_default.version);
482
492
  return 0;
483
493
  }
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);
494
+ if (verb === "hook") return (await import("./hooks-O3UZXDYG.js")).relayHookCommand(rest, startDaemon);
495
+ if (verb === "setup") return (await import("./setup-RDPVGE4H.js")).relaySetupCommand(rest, cwd, out);
486
496
  if (verb === "spawn") {
487
- const { splitSpawn, spawnCommand } = await import("./spawn-7P4EMSVB.js");
497
+ const { splitSpawn, spawnCommand } = await import("./spawn-TQ3HOZTQ.js");
488
498
  const { flags } = splitSpawn(rest);
489
499
  if (flags.includes("--help") || flags.includes("-h")) {
490
500
  out(flags.includes("--all") ? RELAY_HELP : SPAWN_HELP);
@@ -508,11 +518,11 @@ async function relayMain(argv, cwd = process.cwd()) {
508
518
  for (const key of Object.keys(values)) if (!allowed.includes(key)) throw new RelayUsage(`${CLI_NAME} ${verb} does not take --${key}`);
509
519
  if (verb === "daemon") return daemon(values.idle, values.policy);
510
520
  if (verb === "mcp")
511
- return (await import("./mcp-EQCSCD3X.js")).relayMcpCommand(values, positionals);
521
+ return (await import("./mcp-P2AZYP5G.js")).relayMcpCommand(values, positionals);
512
522
  if (verb === "hooks") {
513
523
  if (positionals[0] !== "install" || positionals.length > 1) throw new RelayUsage(`${CLI_NAME} hooks takes install --harness claude|codex|opencode|agy|muse`);
514
524
  if (values.write && values.print) throw new RelayUsage("--print and --write: pick one");
515
- return (await import("./install-4HATFIFI.js")).installHooks({
525
+ return (await import("./install-HFBSKSTG.js")).installHooks({
516
526
  harness: values.harness,
517
527
  scope: values.scope,
518
528
  write: values.write === true,
@@ -524,7 +534,7 @@ async function relayMain(argv, cwd = process.cwd()) {
524
534
  });
525
535
  }
526
536
  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);
537
+ if (verb === "up") return (await import("./up-KXOYO7QV.js")).up(positionals, values, cwd, cloudStatus);
528
538
  if (verb === "login")
529
539
  return (await import("./relay-VVJPYVXE.js")).loginMain(positionals, values);
530
540
  if (verb === "logout") return (await import("./relay-VVJPYVXE.js")).logoutMain(positionals, values.json === true);
@@ -697,6 +707,8 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
697
707
  const lines = reachLines(result2.deliveries);
698
708
  out(`sent ${result2.id} to ${result2.deliveries.map((d) => lines.length ? d.address : deliveryText(d)).join(", ")}${redactedText(result2.redacted)}`);
699
709
  for (const line of lines) out(` ${line}`);
710
+ if (result2.deliveries.some((d) => d.state === "running" || d.state === "starting"))
711
+ out(` ${CLI_NAME} status ${shortId(result2.id)} shows when it is read`);
700
712
  }
701
713
  return 0;
702
714
  }
@@ -757,9 +769,9 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
757
769
  result.acked += (await client.request("ack", { seqs: seqs.slice(i, i + LIMITS.inboxPage) })).acked;
758
770
  };
759
771
  if (values.all) {
760
- for (let before = -1; before !== result.acked; ) {
761
- before = result.acked;
772
+ for (let page = 0; page < ACK_ALL_PAGES; page++) {
762
773
  const { messages } = await client.request("inbox", { wait_s: 0 });
774
+ if (!messages.length) break;
763
775
  await ack(messages.flatMap((m) => m.seq === void 0 ? [] : [m.seq]));
764
776
  }
765
777
  } else {
@@ -778,6 +790,20 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
778
790
  return 0;
779
791
  }
780
792
  case "status": {
793
+ if (positionals.length) {
794
+ if (positionals.length > 1 || values.all || values.activity)
795
+ throw new RelayUsage(`${CLI_NAME} status takes one message id, or --all and --activity for the fleet`);
796
+ if (!broker) throw new RelayUsage(`${CLI_NAME} status <id> is the local broker's; it has no --cloud form`);
797
+ 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`);
798
+ const receipts = await client.request("receipts", { id: positionals[0] });
799
+ if (asJson) json(receipts);
800
+ else {
801
+ out(`${receipts.id}:`);
802
+ const lines = receiptLines(receipts);
803
+ for (const line of lines.length ? lines : ["no deliveries of it are kept any more"]) out(` ${line}`);
804
+ }
805
+ return 0;
806
+ }
781
807
  const snapshot = await client.request("snapshot", { all: values.all === true });
782
808
  if (asJson) {
783
809
  json(snapshot);
@@ -1,11 +1,16 @@
1
+ import {
2
+ spawnDetached
3
+ } from "./chunk-V5I7KEU7.js";
4
+ import "./chunk-KN7WEIE4.js";
5
+ import "./chunk-N3ZROLFA.js";
1
6
  import {
2
7
  mcpInstall
3
- } from "./chunk-WSLKRAB7.js";
8
+ } from "./chunk-4DNQFATX.js";
4
9
  import {
5
10
  RelayUsage,
6
11
  startDaemon
7
12
  } from "./chunk-7KP6TAJW.js";
8
- import "./chunk-RO7CUOOH.js";
13
+ import "./chunk-XWLF7ON2.js";
9
14
  import "./chunk-YYSLYVNZ.js";
10
15
  import {
11
16
  harnessPid,
@@ -41,11 +46,27 @@ async function relayMcpCommand(values, positionals) {
41
46
  output: (line) => void process.stdout.write(line),
42
47
  env: process.env,
43
48
  channels: values.channels === true,
49
+ spawn: (request) => spawnDetached(spawnWords(request), request.cwd ?? claim.cwd),
44
50
  log: (line) => void process.stderr.write(`${line}
45
51
  `)
46
52
  });
47
53
  return 0;
48
54
  }
55
+ function spawnWords(request) {
56
+ return [
57
+ "--bg",
58
+ "--as",
59
+ request.as,
60
+ ...request.harness !== void 0 ? ["--harness", request.harness] : [],
61
+ ...request.name !== void 0 ? ["--name", request.name] : [],
62
+ ...request.title !== void 0 ? ["--title", request.title] : [],
63
+ ...request.no_push ? ["--no-push"] : [],
64
+ ...request.handback_bytes !== void 0 ? ["--handback-bytes", String(request.handback_bytes)] : [],
65
+ "--",
66
+ ...request.command
67
+ ];
68
+ }
49
69
  export {
50
- relayMcpCommand
70
+ relayMcpCommand,
71
+ spawnWords
51
72
  };
@@ -14,10 +14,10 @@ import {
14
14
  verifyMount,
15
15
  verifyMusePlugin,
16
16
  whichOnPath
17
- } from "./chunk-O4J5G2X7.js";
18
- import "./chunk-WSLKRAB7.js";
17
+ } from "./chunk-N3ZROLFA.js";
18
+ import "./chunk-4DNQFATX.js";
19
19
  import "./chunk-7KP6TAJW.js";
20
- import "./chunk-RO7CUOOH.js";
20
+ import "./chunk-XWLF7ON2.js";
21
21
  import "./chunk-YYSLYVNZ.js";
22
22
  import "./chunk-DA3EB2YF.js";
23
23
  export {
@@ -0,0 +1,74 @@
1
+ import {
2
+ MESSAGING,
3
+ SPAWN_RUNNER,
4
+ STDIN_WAIT_MS,
5
+ agentShell,
6
+ agyNote,
7
+ claudeTranscripts,
8
+ closedStdin,
9
+ codexMount,
10
+ codexOverrideAt,
11
+ codexOverrides,
12
+ codexSkipsGitCheck,
13
+ contributorRefusal,
14
+ harnessArgv,
15
+ harnessOutput,
16
+ harnessStdin,
17
+ programAt,
18
+ repliesOutput,
19
+ resumeArgv,
20
+ runHarness,
21
+ spawnCommand,
22
+ spawnDefaults,
23
+ spawnDetached,
24
+ spawnEnvironment,
25
+ spawnPrompt,
26
+ spawnTitle,
27
+ splitSpawn,
28
+ stderrOutput,
29
+ steerArgv,
30
+ transcriptReplies,
31
+ workerName
32
+ } from "./chunk-V5I7KEU7.js";
33
+ import {
34
+ SPAWN_HELP
35
+ } from "./chunk-KN7WEIE4.js";
36
+ import "./chunk-N3ZROLFA.js";
37
+ import "./chunk-4DNQFATX.js";
38
+ import "./chunk-7KP6TAJW.js";
39
+ import "./chunk-XWLF7ON2.js";
40
+ import "./chunk-YYSLYVNZ.js";
41
+ import "./chunk-DA3EB2YF.js";
42
+ export {
43
+ MESSAGING,
44
+ SPAWN_HELP,
45
+ SPAWN_RUNNER,
46
+ STDIN_WAIT_MS,
47
+ agentShell,
48
+ agyNote,
49
+ claudeTranscripts,
50
+ closedStdin,
51
+ codexMount,
52
+ codexOverrideAt,
53
+ codexOverrides,
54
+ codexSkipsGitCheck,
55
+ contributorRefusal,
56
+ harnessArgv,
57
+ harnessOutput,
58
+ harnessStdin,
59
+ programAt,
60
+ repliesOutput,
61
+ resumeArgv,
62
+ runHarness,
63
+ spawnCommand,
64
+ spawnDefaults,
65
+ spawnDetached,
66
+ spawnEnvironment,
67
+ spawnPrompt,
68
+ spawnTitle,
69
+ splitSpawn,
70
+ stderrOutput,
71
+ steerArgv,
72
+ transcriptReplies,
73
+ workerName
74
+ };
@@ -25,7 +25,7 @@ async function up(positionals, values, cwd, brokerStatus) {
25
25
  } else out(`already logged in to ${url}`);
26
26
  const enabled = await (await import("./remote-3BWFISXJ.js")).cloudMain(["enable"], values.machine !== void 0 ? { machine: values.machine } : {}, brokerStatus);
27
27
  if (enabled !== 0) return enabled;
28
- const setup = (await import("./setup-T4ZZKV7K.js")).relaySetupCommand(["--write"], cwd, out);
28
+ const setup = (await import("./setup-RDPVGE4H.js")).relaySetupCommand(["--write"], cwd, out);
29
29
  if (setup !== 0) return setup;
30
30
  const broker = await brokerStatus();
31
31
  if (!broker.enabled) out(`the broker that was already running stays local-only until it exits (it stops when idle); the next one links to ${url}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scp-dev/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "The swarm command line tool.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://scp.dev",
@@ -24,10 +24,10 @@
24
24
  "typecheck": "tsc -p ."
25
25
  },
26
26
  "dependencies": {
27
- "@scp-dev/core": "0.1.0",
28
- "@scp-dev/broker": "0.1.0",
29
- "@scp-dev/hub": "0.1.0",
30
- "@scp-dev/mcp": "0.1.0",
27
+ "@scp-dev/core": "0.1.1",
28
+ "@scp-dev/broker": "0.1.1",
29
+ "@scp-dev/hub": "0.1.1",
30
+ "@scp-dev/mcp": "0.1.1",
31
31
  "smol-toml": "^1.9.1"
32
32
  },
33
33
  "devDependencies": {