@scp-dev/cli 0.7.0 → 0.8.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 (30) hide show
  1. package/dist/{chunk-3SOIVANO.js → chunk-6S3V5ZGU.js} +16 -4
  2. package/dist/{chunk-SNW53KT3.js → chunk-77NUOWQ4.js} +3 -3
  3. package/dist/{chunk-KN7WEIE4.js → chunk-FPNFRDXG.js} +1 -0
  4. package/dist/{chunk-LUDLDWH2.js → chunk-GTU3EWEY.js} +1 -1
  5. package/dist/{chunk-LRQP25RI.js → chunk-MHBWDAXO.js} +2 -2
  6. package/dist/{chunk-LWMTZH36.js → chunk-S5KEVIGO.js} +2 -2
  7. package/dist/{chunk-277BAY7J.js → chunk-UUFTEXDU.js} +1 -1
  8. package/dist/{chunk-RZUQ422C.js → chunk-ZEWZBRVM.js} +23 -6
  9. package/dist/{claude-WEHOLJ26.js → claude-7BA4PGK3.js} +16 -6
  10. package/dist/cli.js +1 -1
  11. package/dist/{codex-54UWPDY5.js → codex-KQNXVGTE.js} +7 -7
  12. package/dist/{hooks-ULKKUHPF.js → hooks-DRIUG546.js} +2 -2
  13. package/dist/{install-5A3ETGF6.js → install-MZC4PAIC.js} +3 -3
  14. package/dist/{join-FFT7RQRW.js → join-JW7C2IDC.js} +1 -1
  15. package/dist/{login-DCNCC3RS.js → login-22BLC7KI.js} +2 -2
  16. package/dist/{main-CDLW3MV5.js → main-6BPNG53C.js} +92 -28
  17. package/dist/{mcp-7GB35CTY.js → mcp-4VAPXGF6.js} +6 -6
  18. package/dist/{mesh-TDOVAHZ3.js → mesh-P3EQXYLH.js} +1 -1
  19. package/dist/{muse-S7FPY4CL.js → muse-QWCI627T.js} +7 -7
  20. package/dist/{opencode-WFMQYCI2.js → opencode-7XYA7E3Q.js} +4 -4
  21. package/dist/{peek-X4QQFMQJ.js → peek-G6RLQGKO.js} +1 -1
  22. package/dist/{pi-5IPU6GA4.js → pi-QUW4MSVZ.js} +1 -1
  23. package/dist/{relay-7WBNUACH.js → relay-7VBBQCFB.js} +1 -1
  24. package/dist/{remote-YO5B2HQI.js → remote-VRVONVET.js} +2 -2
  25. package/dist/remote-spawn-7Z2FLG33.js +309 -0
  26. package/dist/{resume-3ZRDXSTX.js → resume-B5BOZO2G.js} +6 -6
  27. package/dist/{setup-U7CWN5QP.js → setup-NK2HRFZN.js} +4 -4
  28. package/dist/{spawn-VZK5MD3R.js → spawn-3IYGUBG6.js} +6 -6
  29. package/dist/{up-S4KJBWI6.js → up-QL3FLYUK.js} +5 -5
  30. package/package.json +6 -6
@@ -431,6 +431,7 @@ function relayIdentity(input) {
431
431
  process: harnessProcess,
432
432
  labels: labelsFor(input.env),
433
433
  ...input.env.SCP_PARENT ? { parent: input.env.SCP_PARENT } : {},
434
+ ...input.env.SCP_UNDER ? { under: input.env.SCP_UNDER } : {},
434
435
  ...keys.length ? { lineage: keys } : {},
435
436
  ...grant ? { grant } : {},
436
437
  ...harnessSession ? { harness_session: harnessSession } : {}
@@ -510,7 +511,14 @@ var OPTIONS = {
510
511
  endpoint: { type: "string" },
511
512
  "take-over": { type: "boolean" },
512
513
  live: { type: "boolean" },
513
- parent: { type: "string" }
514
+ parent: { type: "string" },
515
+ via: { type: "string" },
516
+ wake: { type: "string" },
517
+ eta: { type: "string" },
518
+ ref: { type: "string" },
519
+ gone: { type: "boolean" },
520
+ "secret-env": { type: "string" },
521
+ clear: { type: "boolean" }
514
522
  };
515
523
  var SPAWN_OPTIONS = [
516
524
  "as",
@@ -531,7 +539,8 @@ var SPAWN_OPTIONS = [
531
539
  "handback-bytes",
532
540
  "no-push",
533
541
  "bg",
534
- "fg"
542
+ "fg",
543
+ "via"
535
544
  ];
536
545
  var OBSERVE = /* @__PURE__ */ new Set([
537
546
  "list",
@@ -634,7 +643,7 @@ async function open(verb, values, cwd, bodyFromStdin, walk) {
634
643
  `${CLI_NAME2} ${verb} needs an identity: --as HANDLE (or $SCP_NAME) for an agent, or --human for your own inbox me/ (no terminal is attached, so me/ is not assumed)`
635
644
  );
636
645
  }
637
- const { machineName, openCloud } = await import("./remote-YO5B2HQI.js");
646
+ const { machineName, openCloud } = await import("./remote-VRVONVET.js");
638
647
  const handle2 = named ? localHandle(named) : void 0;
639
648
  const claim = handle2 ? {
640
649
  handle: handle2,
@@ -677,6 +686,7 @@ async function open(verb, values, cwd, bodyFromStdin, walk) {
677
686
  harness: claimHarness(values, process.env, here),
678
687
  cwd,
679
688
  ...process.env.SCP_PARENT ? { parent: process.env.SCP_PARENT } : {},
689
+ ...process.env.SCP_UNDER ? { under: process.env.SCP_UNDER } : {},
680
690
  labels: labelsFor(process.env),
681
691
  ...lineageClaim,
682
692
  ...grant ? { grant } : {}
@@ -892,7 +902,7 @@ function relayVerb(positionals, asJson, env = process.env) {
892
902
  }
893
903
  async function loginMain(positionals, values) {
894
904
  if (positionals.length) throw new RelayUsage(`${CLI_NAME3} login takes only flags`);
895
- return (await import("./login-DCNCC3RS.js")).login(values);
905
+ return (await import("./login-22BLC7KI.js")).login(values);
896
906
  }
897
907
  function logoutMain(positionals, asJson, env = process.env) {
898
908
  if (positionals.length) throw new RelayUsage(`${CLI_NAME3} logout takes no arguments`);
@@ -914,6 +924,8 @@ export {
914
924
  hookHarness,
915
925
  gitToplevel,
916
926
  deriveHandle,
927
+ parseLabels,
928
+ labelsFor,
917
929
  processKey,
918
930
  sessionKey,
919
931
  psStart,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  hooksTarget
3
- } from "./chunk-LRQP25RI.js";
3
+ } from "./chunk-MHBWDAXO.js";
4
4
  import {
5
5
  AGY_HOOK_SET,
6
6
  BLOCK_STOP,
@@ -12,12 +12,12 @@ import {
12
12
  mergeHooks,
13
13
  musePlugin,
14
14
  opencodePlugin
15
- } from "./chunk-277BAY7J.js";
15
+ } from "./chunk-UUFTEXDU.js";
16
16
  import {
17
17
  RelayUsage,
18
18
  gitToplevel,
19
19
  setJoinMode
20
- } from "./chunk-3SOIVANO.js";
20
+ } from "./chunk-6S3V5ZGU.js";
21
21
 
22
22
  // src/setup.ts
23
23
  import { spawnSync } from "child_process";
@@ -16,6 +16,7 @@ var SPAWN_HELP = `${CLI_NAME} spawn [flags] [--] <harness cmd...>: run a worker
16
16
  --peek local peekable from its start
17
17
  --scope P --rules S --in S --out S --allow S --group G --under EP --ttl S --labels K=V,...
18
18
  its scope and grant, as for ${CLI_NAME} spawn-env (default scope: parent)
19
+ --via R start it on another machine through runner R (runners.json, see help --all)
19
20
  The command starts after -- or at the first word that is not one of these flags.
20
21
 
21
22
  It returns at once, printing the worker's name and its runner's pid: do not background it (no &, no
@@ -5,7 +5,7 @@ import {
5
5
  configDir,
6
6
  relayCredential,
7
7
  writePrivateJson
8
- } from "./chunk-3SOIVANO.js";
8
+ } from "./chunk-6S3V5ZGU.js";
9
9
  import {
10
10
  note,
11
11
  printTable
@@ -10,11 +10,11 @@ import {
10
10
  mergeHooks,
11
11
  musePlugin,
12
12
  opencodePlugin
13
- } from "./chunk-277BAY7J.js";
13
+ } from "./chunk-UUFTEXDU.js";
14
14
  import {
15
15
  RelayUsage,
16
16
  gitToplevel
17
- } from "./chunk-3SOIVANO.js";
17
+ } from "./chunk-6S3V5ZGU.js";
18
18
 
19
19
  // src/install.ts
20
20
  import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
@@ -3,13 +3,13 @@ import {
3
3
  codexMount,
4
4
  codexOverrides,
5
5
  toml
6
- } from "./chunk-RZUQ422C.js";
6
+ } from "./chunk-ZEWZBRVM.js";
7
7
  import {
8
8
  RelayUsage,
9
9
  readPsTable,
10
10
  relayIdentity,
11
11
  startDaemon
12
- } from "./chunk-3SOIVANO.js";
12
+ } from "./chunk-6S3V5ZGU.js";
13
13
  import {
14
14
  SELF
15
15
  } from "./chunk-MNTIEWKF.js";
@@ -13,7 +13,7 @@ import {
13
13
  psStart,
14
14
  relayIdentity,
15
15
  sessionKey
16
- } from "./chunk-3SOIVANO.js";
16
+ } from "./chunk-6S3V5ZGU.js";
17
17
 
18
18
  // src/hooks.ts
19
19
  import { Readable } from "stream";
@@ -3,10 +3,10 @@ import {
3
3
  agySettingsFile,
4
4
  mcpFile,
5
5
  mountedEntry
6
- } from "./chunk-SNW53KT3.js";
6
+ } from "./chunk-77NUOWQ4.js";
7
7
  import {
8
8
  steerPrompt
9
- } from "./chunk-277BAY7J.js";
9
+ } from "./chunk-UUFTEXDU.js";
10
10
  import {
11
11
  OPTIONS,
12
12
  RelayUsage,
@@ -18,7 +18,7 @@ import {
18
18
  selectorList,
19
19
  spawnerKey,
20
20
  walkNow
21
- } from "./chunk-3SOIVANO.js";
21
+ } from "./chunk-6S3V5ZGU.js";
22
22
  import {
23
23
  SELF,
24
24
  note
@@ -44,8 +44,8 @@ import {
44
44
  } from "@scp-dev/core";
45
45
  var SERVER = "scp";
46
46
  var SPAWN_RUNNER = "SCP_SPAWN_RUNNER";
47
- var SPAWNER_IDENTITY = ["SCP_NAME", "SCP_SESSION", "SCP_GRANT", "SCP_PARENT", "SCP_HARNESS", "SCP_SHIM_BINDING", "SCP_HARNESS_PID", SPAWN_RUNNER];
48
- var SHIM_ENV = ["SCP_GRANT", "SCP_NAME", "SCP_LABELS", "SCP_DIR", "XDG_STATE_HOME", "SCP_SHIM_BINDING"];
47
+ var SPAWNER_IDENTITY = ["SCP_NAME", "SCP_SESSION", "SCP_GRANT", "SCP_PARENT", "SCP_UNDER", "SCP_HARNESS", "SCP_SHIM_BINDING", "SCP_HARNESS_PID", SPAWN_RUNNER];
48
+ var SHIM_ENV = ["SCP_GRANT", "SCP_NAME", "SCP_UNDER", "SCP_LABELS", "SCP_DIR", "XDG_STATE_HOME", "SCP_SHIM_BINDING"];
49
49
  function spawnEnvironment(parent, grant, labels) {
50
50
  const env = {};
51
51
  for (const [key, value] of Object.entries(parent)) if (value !== void 0 && !SPAWNER_IDENTITY.includes(key)) env[key] = value;
@@ -728,6 +728,7 @@ async function spawnCommand(args, cwd) {
728
728
  const bytes = values["handback-bytes"] === void 0 ? void 0 : handbackBytes(values["handback-bytes"]);
729
729
  const runner = process.env[SPAWN_RUNNER];
730
730
  delete process.env[SPAWN_RUNNER];
731
+ if (typeof values.via === "string") return (await import("./remote-spawn-7Z2FLG33.js")).viaSpawn(values, command, cwd);
731
732
  if (runner === void 0 && (values.bg === true || values.fg !== true && (process.stdout.isTTY !== true || process.stdin.isTTY !== true && agentShell(process.env))))
732
733
  return detachSpawn(args, cwd);
733
734
  const channel = runner === void 0 ? void 0 : handshakeChannel();
@@ -739,6 +740,21 @@ async function spawnCommand(args, cwd) {
739
740
  } catch {
740
741
  }
741
742
  });
743
+ if (typeof values.under === "string") {
744
+ const remote = await import("./remote-spawn-7Z2FLG33.js");
745
+ let plan;
746
+ try {
747
+ plan = await remote.linkedPlan(values, command);
748
+ } catch (error) {
749
+ handshake?.({ error: error instanceof Error ? error.message : String(error), ...error instanceof RelayUsage ? { usage: true } : {} });
750
+ throw error;
751
+ }
752
+ if (plan) {
753
+ const name2 = plan.name;
754
+ const started2 = handshake && ((failed) => handshake(failed ? { error: failed.message, code: failed.code } : { name: name2, pid: process.pid }));
755
+ return remote.linkedSpawn(plan, command, cwd, { group: runner !== void 0, ...started2 ? { started: started2 } : {}, ...bytes !== void 0 ? { bytes } : {} });
756
+ }
757
+ }
742
758
  const bound = spawnerKey();
743
759
  const prompt = values.peek === void 0 ? void 0 : spawnPrompt(command)?.slice(0, PEEK.rawChars);
744
760
  const walk = runner === void 0 ? walkNow() : JSON.parse(runner);
@@ -801,7 +817,8 @@ function parseSpawn(args) {
801
817
  throw new RelayUsage(error instanceof Error ? error.message : String(error));
802
818
  }
803
819
  if (values.help) return { values, command: split.command };
804
- for (const key of Object.keys(values)) if (!SPAWN_OPTIONS.includes(key)) throw new RelayUsage(`${CLI_NAME} spawn does not take --${key}`);
820
+ for (const key of Object.keys(values))
821
+ if (!SPAWN_OPTIONS.includes(key) && !(key === "json" && values.via !== void 0)) throw new RelayUsage(`${CLI_NAME} spawn does not take --${key}`);
805
822
  if (values.bg && values.fg) throw new RelayUsage("--bg and --fg: pick one");
806
823
  const command = split.command;
807
824
  if (!command.length)
@@ -3,16 +3,16 @@ import {
3
3
  mcpFile,
4
4
  mountedEntry,
5
5
  whichOnPath
6
- } from "./chunk-SNW53KT3.js";
7
- import "./chunk-LRQP25RI.js";
8
- import "./chunk-277BAY7J.js";
6
+ } from "./chunk-77NUOWQ4.js";
7
+ import "./chunk-MHBWDAXO.js";
8
+ import "./chunk-UUFTEXDU.js";
9
9
  import "./chunk-YYSLYVNZ.js";
10
10
  import {
11
11
  CHANNELS_ENV,
12
12
  RelayUsage,
13
13
  deriveHandle,
14
14
  joinMode
15
- } from "./chunk-3SOIVANO.js";
15
+ } from "./chunk-6S3V5ZGU.js";
16
16
  import {
17
17
  SELF
18
18
  } from "./chunk-MNTIEWKF.js";
@@ -59,7 +59,17 @@ function claudeLaunchArgs(args, added) {
59
59
  const channels = optionsOf(args).some((arg) => arg === "server:scp" || arg === `${CHANNEL_FLAGS[0]}=server:scp`) ? [] : CHANNEL_FLAGS;
60
60
  return beforeDashes(args, [...channels, ...added]);
61
61
  }
62
- var LAUNCHER_IDENTITY = ["SCP_NAME", "SCP_GRANT", "SCP_SESSION", "SCP_PARENT", "SCP_HARNESS", "SCP_SHIM_BINDING", "SCP_SPAWN_RUNNER", CHANNELS_ENV];
62
+ var LAUNCHER_IDENTITY = [
63
+ "SCP_NAME",
64
+ "SCP_GRANT",
65
+ "SCP_SESSION",
66
+ "SCP_PARENT",
67
+ "SCP_UNDER",
68
+ "SCP_HARNESS",
69
+ "SCP_SHIM_BINDING",
70
+ "SCP_SPAWN_RUNNER",
71
+ CHANNELS_ENV
72
+ ];
63
73
  var SHIM_VARS = ["SCP_LABELS", "SCP_DIR", "XDG_STATE_HOME"];
64
74
  var bgDir = (env) => join(relayDir(env), "claude-bg");
65
75
  function bgMount(env, name) {
@@ -233,7 +243,7 @@ async function renameBackground(target, to, env, err, bin, deps) {
233
243
  `, { mode: 384 });
234
244
  }
235
245
  async function renameEndpoint(from, to) {
236
- return (await import("./main-CDLW3MV5.js")).relayMain(["name", "--endpoint", `me/${from}`, to]);
246
+ return (await import("./main-6BPNG53C.js")).relayMain(["name", "--endpoint", `me/${from}`, to]);
237
247
  }
238
248
  async function claudeCommand(argv, env = process.env, err = (line) => void process.stderr.write(`${line}
239
249
  `), cwd = process.cwd(), deps = {}) {
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-CDLW3MV5.js");
20
+ const { run } = await import("./main-6BPNG53C.js");
21
21
  await run(process.argv.slice(2));
22
22
  }
@@ -6,14 +6,14 @@ import {
6
6
  codexLaunch,
7
7
  connectCodexChannel,
8
8
  serverOptions
9
- } from "./chunk-LWMTZH36.js";
10
- import "./chunk-RZUQ422C.js";
11
- import "./chunk-KN7WEIE4.js";
12
- import "./chunk-SNW53KT3.js";
13
- import "./chunk-LRQP25RI.js";
14
- import "./chunk-277BAY7J.js";
9
+ } from "./chunk-S5KEVIGO.js";
10
+ import "./chunk-ZEWZBRVM.js";
11
+ import "./chunk-FPNFRDXG.js";
12
+ import "./chunk-77NUOWQ4.js";
13
+ import "./chunk-MHBWDAXO.js";
14
+ import "./chunk-UUFTEXDU.js";
15
15
  import "./chunk-YYSLYVNZ.js";
16
- import "./chunk-3SOIVANO.js";
16
+ import "./chunk-6S3V5ZGU.js";
17
17
  import "./chunk-MNTIEWKF.js";
18
18
  export {
19
19
  CODEX_HELP,
@@ -42,11 +42,11 @@ import {
42
42
  tldrEvery,
43
43
  transcriptTitle,
44
44
  unreadPointer
45
- } from "./chunk-277BAY7J.js";
45
+ } from "./chunk-UUFTEXDU.js";
46
46
  import {
47
47
  SUBAGENT
48
48
  } from "./chunk-YYSLYVNZ.js";
49
- import "./chunk-3SOIVANO.js";
49
+ import "./chunk-6S3V5ZGU.js";
50
50
  import "./chunk-MNTIEWKF.js";
51
51
  export {
52
52
  AGY_CONVERSATION,
@@ -2,10 +2,10 @@ import {
2
2
  hooksTarget,
3
3
  installHooks,
4
4
  mcpInstall
5
- } from "./chunk-LRQP25RI.js";
6
- import "./chunk-277BAY7J.js";
5
+ } from "./chunk-MHBWDAXO.js";
6
+ import "./chunk-UUFTEXDU.js";
7
7
  import "./chunk-YYSLYVNZ.js";
8
- import "./chunk-3SOIVANO.js";
8
+ import "./chunk-6S3V5ZGU.js";
9
9
  import "./chunk-MNTIEWKF.js";
10
10
  export {
11
11
  hooksTarget,
@@ -7,7 +7,7 @@ import {
7
7
  sessionKey,
8
8
  startDaemon,
9
9
  walkNow
10
- } from "./chunk-3SOIVANO.js";
10
+ } from "./chunk-6S3V5ZGU.js";
11
11
  import "./chunk-MNTIEWKF.js";
12
12
 
13
13
  // src/join.ts
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  machineName
3
- } from "./chunk-LUDLDWH2.js";
3
+ } from "./chunk-GTU3EWEY.js";
4
4
  import {
5
5
  RelayUsage,
6
6
  activeRelay,
7
7
  saveCredential
8
- } from "./chunk-3SOIVANO.js";
8
+ } from "./chunk-6S3V5ZGU.js";
9
9
  import {
10
10
  note
11
11
  } from "./chunk-MNTIEWKF.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SPAWN_HELP
3
- } from "./chunk-KN7WEIE4.js";
3
+ } from "./chunk-FPNFRDXG.js";
4
4
  import {
5
5
  OPTIONS,
6
6
  RelayUsage,
@@ -16,7 +16,7 @@ import {
16
16
  startDaemon,
17
17
  terminalAttached,
18
18
  time
19
- } from "./chunk-3SOIVANO.js";
19
+ } from "./chunk-6S3V5ZGU.js";
20
20
  import {
21
21
  FAILED,
22
22
  exitFlushed,
@@ -35,7 +35,7 @@ import { parseArgs } from "util";
35
35
  // package.json
36
36
  var package_default = {
37
37
  name: "@scp-dev/cli",
38
- version: "0.7.0",
38
+ version: "0.8.0",
39
39
  description: "The swarm command line tool.",
40
40
  license: "Apache-2.0",
41
41
  homepage: "https://scp.dev",
@@ -238,6 +238,14 @@ scopes (selectors are comma-separated, e.g. tree:parent,tree:children,group:team
238
238
  messages with the relay MCP tools (relay_send, relay_inbox, relay_reply). A
239
239
  ${CLI_NAME} command from codex's workspace-write sandbox also needs
240
240
  -c sandbox_workspace_write.network_access=true (the relay socket counts as network)
241
+ ${CLI_NAME} spawn --via R [--name N] [--title T] [--labels K=V,...] [--json] [--] <harness cmd...>
242
+ hands the spawn to runner R of ~/.config/scp/runners.json ($SCP_RUNNERS_FILE):
243
+ it gets the spawn as one JSON line on stdin and starts the worker on another
244
+ machine logged in to your account, whose ${CLI_NAME} spawn --under me/<this
245
+ machine>/<you> runs it linked under you, without a grant; it hands back as a
246
+ relay message. Needs this broker linked to a hub; returns once the runner exits 0,
247
+ naming the worker's address and attach command when the runner prints them
248
+ (--json: as JSON)
241
249
  ${CLI_NAME} pi [--name N] [--] [pi args...] runs pi with the bundled SCP extension: the relay tools, and each new
242
250
  message as a turn (steering a running one, waking an idle session)
243
251
  ${CLI_NAME} codex [--name N] [--headless] [--] [codex args...]
@@ -322,6 +330,15 @@ agent sessions:
322
330
  (a cycle) is refused; both are audited, as adopt and unlink
323
331
  ${CLI_NAME} leave [--endpoint EP] take this session off the relay: offline, no messages pushed, mail kept until
324
332
  ${CLI_NAME} join; a spawned worker stays joined while it runs
333
+ ${CLI_NAME} hibernate --wake <provider> [--ref R] [<endpoint>] mark this session (or, for the human, <endpoint>) hibernated:
334
+ suspended but wakeable. A message to it runs the provider's wake hook from
335
+ ~/.config/scp/wakers.json with R and the message's metadata (never its body),
336
+ at most once a minute; the session drains its queue once it is back
337
+ ${CLI_NAME} hibernate --wake none [<endpoint>] the session resumed or is gone: hibernation clears (--gone is the same); it also
338
+ clears when the session claims its endpoint again
339
+ ${CLI_NAME} hibernate --all --wake <provider> [--ref R] [--eta S] the human marks every live agent of this machine hibernated
340
+ (--eta: how long a wake takes, shown to senders); it waits until the hub has it
341
+ ("the hub has it"), so the machine can be suspended right after
325
342
  With setup --join manual a session stays off the relay until it runs ${CLI_NAME} join; the default, auto, joins every
326
343
  hooked session when it starts.
327
344
 
@@ -333,6 +350,9 @@ relay:
333
350
  ${CLI_NAME} logout remove the stored credential for the active relay
334
351
  ${CLI_NAME} cloud enable [--machine NAME] | disable | status link the local broker to the hub, or inspect its link
335
352
  ${CLI_NAME} machines [rm NAME] list the hub's machines, or release one
353
+ ${CLI_NAME} machine wake-url <url> [--secret-env VAR] | --clear the URL the hub POSTs to, signed with the secret in $VAR
354
+ (default $SCP_MACHINE_WAKE_SECRET), when a message reaches a hibernated agent of
355
+ this machine while its broker is not linked; for the human
336
356
  --cloud (or $SCP_TRANSPORT=cloud) sends messaging commands directly to the hub using the active relay's login.
337
357
  --machine NAME ($SCP_MACHINE, else the short hostname) names this machine on the hub; it requires --cloud.
338
358
 
@@ -409,6 +429,8 @@ var VERBS = {
409
429
  link: IDENTITY,
410
430
  unlink: IDENTITY,
411
431
  leave: [...IDENTITY, "endpoint"],
432
+ hibernate: [...IDENTITY, "wake", "ref", "gone", "all", "eta"],
433
+ machine: [...IDENTITY, "secret-env", "clear"],
412
434
  name: [...IDENTITY, "endpoint", "take-over"],
413
435
  inbox: [...IDENTITY, "wait", "ack", "keep"],
414
436
  reply: IDENTITY,
@@ -582,16 +604,16 @@ async function relayMain(argv, cwd = process.cwd()) {
582
604
  out(cliVersion());
583
605
  return 0;
584
606
  }
585
- if (verb === "hook") return (await import("./hooks-ULKKUHPF.js")).relayHookCommand(rest, startDaemon);
586
- if (verb === "setup") return (await import("./setup-U7CWN5QP.js")).relaySetupCommand(rest, cwd, out);
587
- if (verb === "claude") return (await import("./claude-WEHOLJ26.js")).claudeCommand(rest);
588
- if (verb === "pi") return (await import("./pi-5IPU6GA4.js")).piCommand(rest);
589
- if (verb === "codex") return (await import("./codex-54UWPDY5.js")).codexCommand(rest);
590
- if (verb === "muse") return (await import("./muse-S7FPY4CL.js")).museCommand(rest);
591
- if (verb === "opencode") return (await import("./opencode-WFMQYCI2.js")).opencodeCommand(rest, process.env, void 0, cwd);
592
- if (verb === "agy" || verb === "grok") return (await import("./resume-3ZRDXSTX.js")).resumeCommand(verb, rest, process.env, cwd);
607
+ if (verb === "hook") return (await import("./hooks-DRIUG546.js")).relayHookCommand(rest, startDaemon);
608
+ if (verb === "setup") return (await import("./setup-NK2HRFZN.js")).relaySetupCommand(rest, cwd, out);
609
+ if (verb === "claude") return (await import("./claude-7BA4PGK3.js")).claudeCommand(rest);
610
+ if (verb === "pi") return (await import("./pi-QUW4MSVZ.js")).piCommand(rest);
611
+ if (verb === "codex") return (await import("./codex-KQNXVGTE.js")).codexCommand(rest);
612
+ if (verb === "muse") return (await import("./muse-QWCI627T.js")).museCommand(rest);
613
+ if (verb === "opencode") return (await import("./opencode-7XYA7E3Q.js")).opencodeCommand(rest, process.env, void 0, cwd);
614
+ if (verb === "agy" || verb === "grok") return (await import("./resume-B5BOZO2G.js")).resumeCommand(verb, rest, process.env, cwd);
593
615
  if (verb === "spawn") {
594
- const { splitSpawn, spawnCommand } = await import("./spawn-VZK5MD3R.js");
616
+ const { splitSpawn, spawnCommand } = await import("./spawn-3IYGUBG6.js");
595
617
  const { flags } = splitSpawn(rest);
596
618
  if (flags.includes("--help") || flags.includes("-h")) {
597
619
  out(flags.includes("--all") ? RELAY_HELP : SPAWN_HELP);
@@ -599,7 +621,7 @@ async function relayMain(argv, cwd = process.cwd()) {
599
621
  }
600
622
  return spawnCommand(rest, cwd);
601
623
  }
602
- if (verb === "mesh") return (await import("./mesh-TDOVAHZ3.js")).meshCommand(rest);
624
+ if (verb === "mesh") return (await import("./mesh-P3EQXYLH.js")).meshCommand(rest);
603
625
  const allowed = VERBS[verb];
604
626
  if (!allowed) throw new RelayUsage(`unknown command "${verb}"`);
605
627
  let parsed;
@@ -619,11 +641,11 @@ async function relayMain(argv, cwd = process.cwd()) {
619
641
  return daemon(values.idle, values.policy);
620
642
  }
621
643
  if (verb === "mcp")
622
- return (await import("./mcp-7GB35CTY.js")).relayMcpCommand(values, positionals);
644
+ return (await import("./mcp-4VAPXGF6.js")).relayMcpCommand(values, positionals);
623
645
  if (verb === "hooks") {
624
646
  if (positionals[0] !== "install" || positionals.length > 1) throw new RelayUsage(`${CLI_NAME} hooks takes install --harness claude|codex|opencode|agy|muse`);
625
647
  if (values.write && values.print) throw new RelayUsage("--print and --write: pick one");
626
- return (await import("./install-5A3ETGF6.js")).installHooks({
648
+ return (await import("./install-MZC4PAIC.js")).installHooks({
627
649
  harness: values.harness,
628
650
  scope: values.scope,
629
651
  write: values.write === true,
@@ -634,27 +656,27 @@ async function relayMain(argv, cwd = process.cwd()) {
634
656
  out
635
657
  });
636
658
  }
637
- if (verb === "relay") return (await import("./relay-7WBNUACH.js")).relayVerb(positionals, values.json === true);
638
- if (verb === "up") return (await import("./up-S4KJBWI6.js")).up(positionals, values, cwd, relinkBroker);
659
+ if (verb === "relay") return (await import("./relay-7VBBQCFB.js")).relayVerb(positionals, values.json === true);
660
+ if (verb === "up") return (await import("./up-QL3FLYUK.js")).up(positionals, values, cwd, relinkBroker);
639
661
  if (verb === "login") {
640
- const code = await (await import("./relay-7WBNUACH.js")).loginMain(positionals, values);
662
+ const code = await (await import("./relay-7VBBQCFB.js")).loginMain(positionals, values);
641
663
  if (code === 0) await relinkRunning();
642
664
  return code;
643
665
  }
644
- if (verb === "logout") return (await import("./relay-7WBNUACH.js")).logoutMain(positionals, values.json === true);
666
+ if (verb === "logout") return (await import("./relay-7VBBQCFB.js")).logoutMain(positionals, values.json === true);
645
667
  if (verb === "cloud") {
646
- const code = await (await import("./remote-YO5B2HQI.js")).cloudMain(positionals, values, cloudStatus);
668
+ const code = await (await import("./remote-VRVONVET.js")).cloudMain(positionals, values, cloudStatus);
647
669
  if (code === 0 && positionals[0] === "enable") await relinkRunning();
648
670
  return code;
649
671
  }
650
- if (verb === "join") return (await import("./join-FFT7RQRW.js")).joinCommand(values, positionals, cwd, out);
672
+ if (verb === "join") return (await import("./join-JW7C2IDC.js")).joinCommand(values, positionals, cwd, out);
651
673
  if (verb === "holds") holdsArgs(values, positionals);
652
674
  const bodyFromStdin = (verb === "send" || verb === "ask" || verb === "reply") && (positionals[1] === void 0 || positionals[1] === "-");
653
675
  const humanAdopt = verb === "adopt" && values.parent !== void 0 && values.as === void 0 && terminalAttached(process, false);
654
676
  const opened = humanAdopt ? await openAdoptHuman(cwd, values) : await open(verb, values, cwd, bodyFromStdin);
655
677
  const { client } = opened;
656
678
  try {
657
- if (verb === "machines") return await (await import("./remote-YO5B2HQI.js")).machinesVerb(client, positionals, values.json === true);
679
+ if (verb === "machines") return await (await import("./remote-VRVONVET.js")).machinesVerb(client, positionals, values.json === true);
658
680
  return await runVerb(opened, verb, values, positionals, cwd);
659
681
  } finally {
660
682
  client.close();
@@ -662,7 +684,7 @@ async function relayMain(argv, cwd = process.cwd()) {
662
684
  }
663
685
  async function openAdoptHuman(cwd, values) {
664
686
  if (values.cloud || process.env.SCP_TRANSPORT === "cloud") {
665
- const { openCloud } = await import("./remote-YO5B2HQI.js");
687
+ const { openCloud } = await import("./remote-VRVONVET.js");
666
688
  return { ...await openCloud({ tty: true, ...values.machine !== void 0 ? { machine: values.machine } : {} }), broker: null, bound: "human" };
667
689
  }
668
690
  if (values.machine !== void 0) throw new RelayUsage("--machine names the machine of the cloud transport: add --cloud");
@@ -847,9 +869,9 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
847
869
  return 0;
848
870
  }
849
871
  case "peek":
850
- return (await import("./peek-X4QQFMQJ.js")).peekCommand(localClient(client, broker, verb), values, positionals);
872
+ return (await import("./peek-G6RLQGKO.js")).peekCommand(localClient(client, broker, verb), values, positionals);
851
873
  case "set-peek":
852
- return (await import("./peek-X4QQFMQJ.js")).setPeekCommand(localClient(client, broker, verb), values, positionals);
874
+ return (await import("./peek-G6RLQGKO.js")).setPeekCommand(localClient(client, broker, verb), values, positionals);
853
875
  case "why": {
854
876
  const [from, to] = positionals;
855
877
  if (!from || !to || positionals.length > 2) throw new RelayUsage(`${CLI_NAME} why takes <from> <to>`);
@@ -1112,6 +1134,47 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
1112
1134
  else out(`${result.endpoint.address} is a root again (was under ${result.parent})`);
1113
1135
  return 0;
1114
1136
  }
1137
+ case "hibernate": {
1138
+ if (positionals.length > 1) throw new RelayUsage(`${CLI_NAME} hibernate takes at most one endpoint`);
1139
+ if (values.gone === true && values.wake !== void 0) throw new RelayUsage("--gone and --wake: pick one");
1140
+ const wake = values.gone === true ? "none" : values.wake;
1141
+ if (wake === void 0) throw new RelayUsage(`${CLI_NAME} hibernate takes --wake <provider>, or --wake none (--gone) to clear it`);
1142
+ if (wake === "none" && (values.ref !== void 0 || values.eta !== void 0))
1143
+ throw new RelayUsage("--ref and --eta go with a provider, not with --wake none");
1144
+ if (values.all === true && positionals.length) throw new RelayUsage(`${CLI_NAME} hibernate --all names no endpoint`);
1145
+ const eta = values.eta === void 0 ? void 0 : Math.round(parseSeconds(values.eta, "--eta"));
1146
+ const result = await client.request("hibernate", {
1147
+ ...values.all === true ? { all: true } : positionals.length ? { endpoint: positionals[0] } : {},
1148
+ wake,
1149
+ ...values.ref !== void 0 ? { ref: values.ref } : {},
1150
+ ...eta !== void 0 ? { eta_s: eta } : {}
1151
+ });
1152
+ if (asJson) json(result);
1153
+ else {
1154
+ for (const e of result.endpoints ?? (result.endpoint ? [result.endpoint] : []))
1155
+ out(`${e.address} ${presenceText(e)}${e.presence === "hibernated" ? `, woken by ${wakeText(e)}` : ""}`);
1156
+ if (result.mirrored) note("the hub has it: the machine may be suspended now");
1157
+ }
1158
+ return 0;
1159
+ }
1160
+ case "machine": {
1161
+ if (positionals[0] !== "wake-url") throw new RelayUsage(`${CLI_NAME} machine takes wake-url <url> [--secret-env VAR] | wake-url --clear`);
1162
+ if (values.clear === true) {
1163
+ if (positionals.length > 1 || values["secret-env"] !== void 0) throw new RelayUsage(`${CLI_NAME} machine wake-url --clear takes no url`);
1164
+ const result2 = await client.request("machine_wake", { url: null });
1165
+ if (asJson) json(result2);
1166
+ else out("machine waker cleared");
1167
+ return 0;
1168
+ }
1169
+ if (positionals.length !== 2) throw new RelayUsage(`${CLI_NAME} machine wake-url takes one url, or --clear`);
1170
+ const name = values["secret-env"] ?? "SCP_MACHINE_WAKE_SECRET";
1171
+ const secret = process.env[name];
1172
+ if (!secret) throw new RelayUsage(`set $${name} to the waker's secret (16 to 256 characters); it is never read from the command line`);
1173
+ const result = await client.request("machine_wake", { url: positionals[1], secret });
1174
+ if (asJson) json(result);
1175
+ else out("machine waker set; the hub signs each call with the secret");
1176
+ return 0;
1177
+ }
1115
1178
  case "leave": {
1116
1179
  if (positionals.length) throw new RelayUsage(`${CLI_NAME} leave takes no arguments (--endpoint EP for the human at a terminal)`);
1117
1180
  const endpoint = values.endpoint;
@@ -1346,7 +1409,8 @@ function printSummary(snapshot, me, all) {
1346
1409
  const agents = snapshot.endpoints.filter((e) => e.kind !== "human");
1347
1410
  const count = (presence) => agents.filter((e) => e.presence === presence).length;
1348
1411
  out();
1349
- out(`agents: ${count("online")} online, ${count("idle")} idle, ${count("offline")} offline`);
1412
+ const hibernated = count("hibernated");
1413
+ out(`agents: ${count("online")} online, ${count("idle")} idle, ${hibernated ? `${hibernated} hibernated, ` : ""}${count("offline")} offline`);
1350
1414
  const counted = all ? agents : agents.filter((e) => e.presence !== "offline");
1351
1415
  const harnesses = /* @__PURE__ */ new Map();
1352
1416
  for (const e of counted) harnesses.set(e.harness ?? "?", [...harnesses.get(e.harness ?? "?") ?? [], e]);
@@ -1473,11 +1537,11 @@ async function daemon(idle, policyFile) {
1473
1537
  });
1474
1538
  let broker;
1475
1539
  try {
1476
- const { daemonBridge } = await import("./remote-YO5B2HQI.js");
1540
+ const { daemonBridge } = await import("./remote-VRVONVET.js");
1477
1541
  const bridge = await daemonBridge();
1478
1542
  broker = await startBroker({
1479
1543
  dir: relayDir(),
1480
- ...bridge ? { bridge } : await (await import("./mesh-TDOVAHZ3.js")).meshBrokerOptions(),
1544
+ ...bridge ? { bridge } : await (await import("./mesh-P3EQXYLH.js")).meshBrokerOptions(),
1481
1545
  bridgeConfig: () => daemonBridge(),
1482
1546
  ...idleMs !== void 0 ? { idleMs } : {},
1483
1547
  ...policyFile ? { policyPath: resolve(policyFile) } : {}
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  spawnDetached
3
- } from "./chunk-RZUQ422C.js";
4
- import "./chunk-KN7WEIE4.js";
5
- import "./chunk-SNW53KT3.js";
3
+ } from "./chunk-ZEWZBRVM.js";
4
+ import "./chunk-FPNFRDXG.js";
5
+ import "./chunk-77NUOWQ4.js";
6
6
  import {
7
7
  mcpInstall
8
- } from "./chunk-LRQP25RI.js";
9
- import "./chunk-277BAY7J.js";
8
+ } from "./chunk-MHBWDAXO.js";
9
+ import "./chunk-UUFTEXDU.js";
10
10
  import "./chunk-YYSLYVNZ.js";
11
11
  import {
12
12
  CHANNELS_ENV,
@@ -19,7 +19,7 @@ import {
19
19
  relayIdentity,
20
20
  startDaemon,
21
21
  writeShimBinding
22
- } from "./chunk-3SOIVANO.js";
22
+ } from "./chunk-6S3V5ZGU.js";
23
23
  import "./chunk-MNTIEWKF.js";
24
24
 
25
25
  // src/mcp.ts
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RelayUsage
3
- } from "./chunk-3SOIVANO.js";
3
+ } from "./chunk-6S3V5ZGU.js";
4
4
  import "./chunk-MNTIEWKF.js";
5
5
 
6
6
  // src/mesh.ts
@@ -1,18 +1,18 @@
1
1
  import {
2
2
  connectCodexChannel
3
- } from "./chunk-LWMTZH36.js";
3
+ } from "./chunk-S5KEVIGO.js";
4
4
  import {
5
5
  MUSE_MODE,
6
6
  MUSE_MODES_ALLOWED,
7
7
  museModes
8
- } from "./chunk-RZUQ422C.js";
9
- import "./chunk-KN7WEIE4.js";
8
+ } from "./chunk-ZEWZBRVM.js";
9
+ import "./chunk-FPNFRDXG.js";
10
10
  import {
11
11
  mcpFile,
12
12
  mountedEntry
13
- } from "./chunk-SNW53KT3.js";
14
- import "./chunk-LRQP25RI.js";
15
- import "./chunk-277BAY7J.js";
13
+ } from "./chunk-77NUOWQ4.js";
14
+ import "./chunk-MHBWDAXO.js";
15
+ import "./chunk-UUFTEXDU.js";
16
16
  import "./chunk-YYSLYVNZ.js";
17
17
  import {
18
18
  RelayUsage,
@@ -20,7 +20,7 @@ import {
20
20
  relayIdentity,
21
21
  startDaemon,
22
22
  writeMuseLaunch
23
- } from "./chunk-3SOIVANO.js";
23
+ } from "./chunk-6S3V5ZGU.js";
24
24
  import "./chunk-MNTIEWKF.js";
25
25
 
26
26
  // src/muse.ts
@@ -2,18 +2,18 @@ import {
2
2
  mcpFile,
3
3
  mountedEntry,
4
4
  whichOnPath
5
- } from "./chunk-SNW53KT3.js";
5
+ } from "./chunk-77NUOWQ4.js";
6
6
  import {
7
7
  hooksTarget
8
- } from "./chunk-LRQP25RI.js";
8
+ } from "./chunk-MHBWDAXO.js";
9
9
  import {
10
10
  opencodePlugin
11
- } from "./chunk-277BAY7J.js";
11
+ } from "./chunk-UUFTEXDU.js";
12
12
  import "./chunk-YYSLYVNZ.js";
13
13
  import {
14
14
  RelayUsage,
15
15
  gitToplevel
16
- } from "./chunk-3SOIVANO.js";
16
+ } from "./chunk-6S3V5ZGU.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-3SOIVANO.js";
5
+ } from "./chunk-6S3V5ZGU.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-3SOIVANO.js";
3
+ } from "./chunk-6S3V5ZGU.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-3SOIVANO.js";
16
+ } from "./chunk-6S3V5ZGU.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-LUDLDWH2.js";
13
- import "./chunk-3SOIVANO.js";
12
+ } from "./chunk-GTU3EWEY.js";
13
+ import "./chunk-6S3V5ZGU.js";
14
14
  import "./chunk-MNTIEWKF.js";
15
15
  export {
16
16
  CloudRelayClient,
@@ -0,0 +1,309 @@
1
+ import {
2
+ STDIN_WAIT_MS,
3
+ agentShell,
4
+ closedStdin,
5
+ harnessArgv,
6
+ harnessOutput,
7
+ harnessStdin,
8
+ programAt,
9
+ runHarness,
10
+ spawnEnvironment,
11
+ spawnTitle,
12
+ stderrOutput,
13
+ workerName
14
+ } from "./chunk-ZEWZBRVM.js";
15
+ import "./chunk-FPNFRDXG.js";
16
+ import "./chunk-77NUOWQ4.js";
17
+ import "./chunk-MHBWDAXO.js";
18
+ import "./chunk-UUFTEXDU.js";
19
+ import "./chunk-YYSLYVNZ.js";
20
+ import {
21
+ RelayUsage,
22
+ labelsFor,
23
+ localClient,
24
+ normalizeHarness,
25
+ open,
26
+ parseLabels,
27
+ startDaemon
28
+ } from "./chunk-6S3V5ZGU.js";
29
+ import {
30
+ note
31
+ } from "./chunk-MNTIEWKF.js";
32
+
33
+ // src/remote-spawn.ts
34
+ import { spawn } from "child_process";
35
+ import { existsSync, readFileSync } from "fs";
36
+ import { basename, dirname, join } from "path";
37
+ import { constants } from "os";
38
+ import { CLI_NAME, findSecret, handbackOutput, LIMITS, validHandle } from "@scp-dev/core";
39
+ import { connectBroker, policyPath, relayDir } from "@scp-dev/broker";
40
+ var RUNNER_NAME = /^[a-z0-9][a-z0-9-]{0,31}$/;
41
+ var RUNNER_TIMEOUT_S = 60;
42
+ var RUNNER_TIMEOUT_MAX_S = 3600;
43
+ var RUNNER_STDERR_BYTES = 2048;
44
+ var RUNNER_STDOUT_BYTES = 4096;
45
+ var RUNNER_REF = /^[\x21-\x7e]{1,256}$/;
46
+ var RUNNER_ATTACH = /^[^\u0000-\u001f\u007f-\u009f]{1,1024}$/;
47
+ var RUNNER_DRAIN_MS = 500;
48
+ var TITLE_CHARS = 64;
49
+ var MACHINE = "[a-z0-9][a-z0-9-]{0,31}";
50
+ var MACHINE_ADDRESS = new RegExp(`^me/(${MACHINE})/([^/]+)$`);
51
+ var STDERR_LABEL = `stderr (last ${LIMITS.stderrBytes / 1024} KB):`;
52
+ var VIA_FLAGS = ["as", "harness", "name", "title", "labels", "bg", "json", "via"];
53
+ var GRANT_FLAGS = ["scope", "rules", "in", "out", "allow", "group", "ttl", "peek", "no-push", "as", "human", "harness"];
54
+ function runnersPath(env = process.env) {
55
+ return env.SCP_RUNNERS_FILE || join(dirname(policyPath()), "runners.json");
56
+ }
57
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
58
+ function parseRunners(text, path) {
59
+ let value;
60
+ try {
61
+ value = JSON.parse(text);
62
+ } catch {
63
+ throw new RelayUsage(`${path} is not JSON`);
64
+ }
65
+ if (!isObject(value)) throw new RelayUsage(`${path} maps runner names to {command, timeout_s?}`);
66
+ const runners = {};
67
+ for (const [name, entry] of Object.entries(value)) {
68
+ if (!RUNNER_NAME.test(name))
69
+ throw new RelayUsage(`${path}: runner ${JSON.stringify(name)} is not a valid name: lowercase letters, digits and -, at most 32`);
70
+ const command = isObject(entry) ? entry.command : void 0;
71
+ if (!Array.isArray(command) || !command.length || !command.every((word) => typeof word === "string" && word.length > 0))
72
+ throw new RelayUsage(`${path}: runner ${name} needs command, a non-empty list of words`);
73
+ const timeout = entry.timeout_s ?? RUNNER_TIMEOUT_S;
74
+ if (typeof timeout !== "number" || !Number.isInteger(timeout) || timeout < 1 || timeout > RUNNER_TIMEOUT_MAX_S)
75
+ throw new RelayUsage(`${path}: runner ${name}'s timeout_s is whole seconds, 1 to ${RUNNER_TIMEOUT_MAX_S}`);
76
+ runners[name] = { command: [...command], timeout_s: timeout };
77
+ }
78
+ return runners;
79
+ }
80
+ function readRunners(path = runnersPath()) {
81
+ return existsSync(path) ? parseRunners(readFileSync(path, "utf8"), path) : {};
82
+ }
83
+ function runnerFor(name, runners, path) {
84
+ const runner = Object.hasOwn(runners, name) ? runners[name] : void 0;
85
+ if (runner) return runner;
86
+ const known = Object.keys(runners);
87
+ throw new RelayUsage(`no runner ${JSON.stringify(name)} in ${path}: ${known.length ? `configured: ${known.join(", ")}` : "none is configured"}`);
88
+ }
89
+ function spawnRequest(o) {
90
+ return {
91
+ scp: 1,
92
+ event: "spawn",
93
+ name: o.name,
94
+ title: o.title,
95
+ cmd: o.command,
96
+ cwd: o.cwd,
97
+ parent: o.parent,
98
+ labels: o.labels,
99
+ join: { under: o.parent, name: o.name, mode: "account" }
100
+ };
101
+ }
102
+ function tail(from, bytes) {
103
+ let kept = Buffer.alloc(0);
104
+ let cut = false;
105
+ from.on("data", (chunk) => {
106
+ kept = Buffer.concat([kept, chunk]);
107
+ if (kept.length > bytes) {
108
+ kept = kept.subarray(kept.length - bytes);
109
+ cut = true;
110
+ }
111
+ });
112
+ return { text: () => kept.toString("utf8"), cut: () => cut };
113
+ }
114
+ function runRunner(runner, request, env) {
115
+ return new Promise((resolve) => {
116
+ const child = spawn(runner.command[0], runner.command.slice(1), { detached: true, stdio: ["pipe", "pipe", "pipe"], env, cwd: request.cwd });
117
+ const out = tail(child.stdout, RUNNER_STDOUT_BYTES);
118
+ const err = tail(child.stderr, RUNNER_STDERR_BYTES);
119
+ const pipes = [child.stdout, child.stderr];
120
+ let settled = false;
121
+ const finish = (code, timedOut = false, message) => {
122
+ if (settled) return;
123
+ settled = true;
124
+ clearTimeout(timer);
125
+ const done = () => {
126
+ for (const pipe of pipes) pipe.destroy();
127
+ child.unref();
128
+ const stderr = message ?? handbackOutput(err.text(), err.cut(), RUNNER_STDERR_BYTES);
129
+ resolve({ code, timedOut, stdout: out.cut() ? "" : out.text(), stderr });
130
+ };
131
+ const open2 = pipes.filter((pipe) => !pipe.readableEnded);
132
+ if (!open2.length || message) return done();
133
+ const drain = setTimeout(done, RUNNER_DRAIN_MS);
134
+ let left = open2.length;
135
+ for (const pipe of open2)
136
+ pipe.once("end", () => {
137
+ if (--left > 0) return;
138
+ clearTimeout(drain);
139
+ done();
140
+ });
141
+ };
142
+ const timer = setTimeout(() => {
143
+ try {
144
+ process.kill(-child.pid, "SIGTERM");
145
+ } catch {
146
+ }
147
+ finish(124, true);
148
+ }, runner.timeout_s * 1e3);
149
+ child.once(
150
+ "error",
151
+ (error) => finish(error.code === "ENOENT" ? 127 : 126, false, `${runner.command[0]}: ${error.code === "ENOENT" ? "command not found" : error.message}`)
152
+ );
153
+ child.once("exit", (code, signal) => finish(code ?? 128 + (signal ? constants.signals[signal] ?? 0 : 0)));
154
+ child.stdin.on("error", () => {
155
+ });
156
+ child.stdin.end(`${JSON.stringify(request)}
157
+ `);
158
+ });
159
+ }
160
+ function runnerReply(stdout, name) {
161
+ let value;
162
+ try {
163
+ value = JSON.parse(stdout.trim());
164
+ } catch {
165
+ return void 0;
166
+ }
167
+ if (!isObject(value) || typeof value.machine !== "string" || typeof value.address !== "string") return void 0;
168
+ const address = MACHINE_ADDRESS.exec(value.address);
169
+ if (!address || address[1] !== value.machine || address[2] !== name) return void 0;
170
+ if (value.ref !== void 0 && (typeof value.ref !== "string" || !RUNNER_REF.test(value.ref))) return void 0;
171
+ if (value.attach !== void 0 && (typeof value.attach !== "string" || !RUNNER_ATTACH.test(value.attach))) return void 0;
172
+ const ref = typeof value.ref === "string" && !findSecret(value.ref) ? value.ref : void 0;
173
+ const attach = typeof value.attach === "string" && !findSecret(value.attach) ? value.attach : void 0;
174
+ return { address: value.address, machine: value.machine, ...ref ? { ref } : {}, ...attach ? { attach } : {} };
175
+ }
176
+ function viaStarted(o) {
177
+ if (o.json) return [JSON.stringify({ name: o.name, runner: o.runner, parent: o.parent, ...o.reply })];
178
+ const where = o.reply ? `on machine ${o.reply.machine} and shows under you as ${o.reply.address}` : `on another machine and shows under you as me/<machine>/${o.name}`;
179
+ return [
180
+ `${o.name} handed to runner ${o.runner}: it starts ${where}; its result arrives as a relay message by itself`,
181
+ ...o.reply?.attach ? [`attach: ${o.reply.attach}`] : []
182
+ ];
183
+ }
184
+ function spawnName(values, command) {
185
+ const name = typeof values.name === "string" ? values.name.replace(/^me\//, "") : workerName(command);
186
+ if (!validHandle(name)) throw new RelayUsage(`--name ${JSON.stringify(name)} is not a valid agent handle: lowercase letters, digits, . _ -`);
187
+ return name;
188
+ }
189
+ async function hubMachine(client, why) {
190
+ const bridge = await client.request("cloud_status");
191
+ if (!bridge.enabled || !bridge.machine)
192
+ throw new RelayUsage(`${why}, but this relay broker is not linked to a hub (${CLI_NAME} login, then ${CLI_NAME} cloud enable)`);
193
+ return bridge.machine;
194
+ }
195
+ async function viaSpawn(values, command, cwd, out = (line) => console.log(line)) {
196
+ const via = values.via;
197
+ const extra = Object.keys(values).find((key) => !VIA_FLAGS.includes(key));
198
+ if (extra)
199
+ throw new RelayUsage(
200
+ `--via starts the worker on another machine under you, without a local grant: it takes --name, --title, --labels and --json, not --${extra}`
201
+ );
202
+ const path = runnersPath();
203
+ const runner = runnerFor(via, readRunners(path), path);
204
+ const name = spawnName(values, command);
205
+ const title = typeof values.title === "string" ? values.title : spawnTitle(command) ?? null;
206
+ if (title !== null && title.length > TITLE_CHARS) throw new RelayUsage(`--title is longer than ${TITLE_CHARS} characters`);
207
+ const opened = await open("spawn", values, cwd, false);
208
+ const client = localClient(opened.client, opened.broker, "spawn");
209
+ let parent;
210
+ try {
211
+ const me = opened.me;
212
+ if (me?.kind !== "agent" || !me.address.startsWith("me/"))
213
+ throw new RelayUsage(`${CLI_NAME} spawn --via starts a worker under you: run it from an agent's session, or name the agent with --as`);
214
+ parent = `me/${await hubMachine(client, `${CLI_NAME} spawn --via shows the worker under you through the hub`)}/${me.address.slice(3)}`;
215
+ } finally {
216
+ client.close();
217
+ }
218
+ const request = spawnRequest({ name, title, command, cwd, parent, labels: parseLabels(values.labels) });
219
+ const result = await runRunner(runner, request, spawnEnvironment(process.env, {}));
220
+ if (result.code === 0) {
221
+ for (const line of viaStarted({ name, runner: via, parent, reply: runnerReply(result.stdout, name), json: values.json === true })) out(line);
222
+ return 0;
223
+ }
224
+ const failed = result.timedOut ? `did not finish within ${runner.timeout_s} s` : `failed (exit ${result.code})`;
225
+ note(`${CLI_NAME} spawn: runner ${via} ${failed}${result.stderr ? `:
226
+ ${result.stderr}` : ""}`);
227
+ return result.code;
228
+ }
229
+ async function linkedPlan(values, command) {
230
+ const under = typeof values.under === "string" ? values.under : void 0;
231
+ const remote = under === void 0 ? null : MACHINE_ADDRESS.exec(under);
232
+ if (!under || !remote) return null;
233
+ const dir = relayDir();
234
+ const client = await connectBroker(dir, () => startDaemon(dir));
235
+ let machine;
236
+ try {
237
+ await client.hello({ observe: true });
238
+ machine = await hubMachine(client, `--under ${under} names an agent on machine ${remote[1]}`);
239
+ } finally {
240
+ client.close();
241
+ }
242
+ if (machine === remote[1]) return null;
243
+ if (values.via !== void 0) throw new RelayUsage("--via and --under an agent on another machine: the runner already starts this spawn there");
244
+ const granted = Object.keys(values).find((key) => GRANT_FLAGS.includes(key));
245
+ if (granted) throw new RelayUsage(`a worker under an agent on another machine runs without a grant: --${granted} does not apply`);
246
+ return {
247
+ parent: under,
248
+ machine,
249
+ name: spawnName(values, command),
250
+ ...typeof values.title === "string" ? { title: values.title } : {},
251
+ ...typeof values.labels === "string" ? { labels: values.labels } : {}
252
+ };
253
+ }
254
+ function linkedHandbackBody(address, code, output, stderr) {
255
+ const tail2 = code !== 0 && stderr ? `${STDERR_LABEL}
256
+ ${stderr}` : "";
257
+ const kept = tail2 && output ? handbackOutput(output, false, LIMITS.handbackBytes - Buffer.byteLength(tail2) - 2) : output;
258
+ const head = kept ? `worker ${address} finished (exit ${code})` : `worker ${address} exited without replying (exit ${code})`;
259
+ return [head, kept, tail2].filter(Boolean).join("\n\n");
260
+ }
261
+ async function linkedSpawn(plan, command, cwd, options) {
262
+ const env = spawnEnvironment(process.env, { SCP_NAME: plan.name, SCP_UNDER: plan.parent }, plan.labels);
263
+ const argv = harnessArgv(command, env);
264
+ const stdin = closedStdin(command) ? await harnessStdin(agentShell(process.env) ? STDIN_WAIT_MS : void 0) : void 0;
265
+ const run = await runHarness(argv, env, {
266
+ group: options.group,
267
+ ...closedStdin(argv) ? { stdin: "ignore" } : {},
268
+ ...options.started ? { started: options.started } : {},
269
+ ...stdin ? { stdin } : {}
270
+ });
271
+ if (run.started) await linkedHandback(plan, command, cwd, env, run, options.bytes);
272
+ return run.code;
273
+ }
274
+ async function linkedHandback(plan, command, cwd, env, run, bytes) {
275
+ const harness = normalizeHarness(basename(command[programAt(command)] ?? ""), "cli");
276
+ const dir = relayDir();
277
+ try {
278
+ const client = await connectBroker(dir, () => startDaemon(dir));
279
+ try {
280
+ const hello = await client.hello({ claim: { handle: plan.name, exact: true, harness, cwd, under: plan.parent, labels: labelsFor(env) } });
281
+ const me = hello.endpoint;
282
+ const address = `me/${plan.machine}/${me.address.slice(3)}`;
283
+ if (plan.title && !me.title) await client.request("title", { text: plan.title });
284
+ const code = Math.min(run.code, 255);
285
+ await client.request("send", { to: plan.parent, kind: "message", body: linkedHandbackBody(address, code, harnessOutput(run, bytes), stderrOutput(run)) });
286
+ await client.request("status", { state: "done", note: `exited ${code}` });
287
+ } finally {
288
+ client.close();
289
+ }
290
+ } catch (error) {
291
+ note(`${CLI_NAME} spawn: could not hand ${plan.name}'s result back to ${plan.parent}: ${error instanceof Error ? error.message : String(error)}`);
292
+ }
293
+ }
294
+ export {
295
+ RUNNER_NAME,
296
+ RUNNER_TIMEOUT_S,
297
+ linkedHandbackBody,
298
+ linkedPlan,
299
+ linkedSpawn,
300
+ parseRunners,
301
+ readRunners,
302
+ runRunner,
303
+ runnerFor,
304
+ runnerReply,
305
+ runnersPath,
306
+ spawnRequest,
307
+ viaSpawn,
308
+ viaStarted
309
+ };
@@ -1,24 +1,24 @@
1
1
  import {
2
2
  agyNote,
3
3
  messagingAllow
4
- } from "./chunk-RZUQ422C.js";
5
- import "./chunk-KN7WEIE4.js";
4
+ } from "./chunk-ZEWZBRVM.js";
5
+ import "./chunk-FPNFRDXG.js";
6
6
  import {
7
7
  whichOnPath
8
- } from "./chunk-SNW53KT3.js";
8
+ } from "./chunk-77NUOWQ4.js";
9
9
  import {
10
10
  hooksTarget
11
- } from "./chunk-LRQP25RI.js";
11
+ } from "./chunk-MHBWDAXO.js";
12
12
  import {
13
13
  AGY_CONVERSATION,
14
14
  AGY_HOOK_SET
15
- } from "./chunk-277BAY7J.js";
15
+ } from "./chunk-UUFTEXDU.js";
16
16
  import "./chunk-YYSLYVNZ.js";
17
17
  import {
18
18
  RelayUsage,
19
19
  relayIdentity,
20
20
  startDaemon
21
- } from "./chunk-3SOIVANO.js";
21
+ } from "./chunk-6S3V5ZGU.js";
22
22
  import "./chunk-MNTIEWKF.js";
23
23
 
24
24
  // src/resume.ts
@@ -15,11 +15,11 @@ import {
15
15
  verifyMount,
16
16
  verifyMusePlugin,
17
17
  whichOnPath
18
- } from "./chunk-SNW53KT3.js";
19
- import "./chunk-LRQP25RI.js";
20
- import "./chunk-277BAY7J.js";
18
+ } from "./chunk-77NUOWQ4.js";
19
+ import "./chunk-MHBWDAXO.js";
20
+ import "./chunk-UUFTEXDU.js";
21
21
  import "./chunk-YYSLYVNZ.js";
22
- import "./chunk-3SOIVANO.js";
22
+ import "./chunk-6S3V5ZGU.js";
23
23
  import "./chunk-MNTIEWKF.js";
24
24
  export {
25
25
  SETUP_HARNESSES,
@@ -35,15 +35,15 @@ import {
35
35
  toml,
36
36
  transcriptReplies,
37
37
  workerName
38
- } from "./chunk-RZUQ422C.js";
38
+ } from "./chunk-ZEWZBRVM.js";
39
39
  import {
40
40
  SPAWN_HELP
41
- } from "./chunk-KN7WEIE4.js";
42
- import "./chunk-SNW53KT3.js";
43
- import "./chunk-LRQP25RI.js";
44
- import "./chunk-277BAY7J.js";
41
+ } from "./chunk-FPNFRDXG.js";
42
+ import "./chunk-77NUOWQ4.js";
43
+ import "./chunk-MHBWDAXO.js";
44
+ import "./chunk-UUFTEXDU.js";
45
45
  import "./chunk-YYSLYVNZ.js";
46
- import "./chunk-3SOIVANO.js";
46
+ import "./chunk-6S3V5ZGU.js";
47
47
  import "./chunk-MNTIEWKF.js";
48
48
  export {
49
49
  CODEX_VALUED,
@@ -2,7 +2,7 @@ import {
2
2
  RelayUsage,
3
3
  activeRelay,
4
4
  relayCredential
5
- } from "./chunk-3SOIVANO.js";
5
+ } from "./chunk-6S3V5ZGU.js";
6
6
  import "./chunk-MNTIEWKF.js";
7
7
 
8
8
  // src/up.ts
@@ -31,21 +31,21 @@ async function up(positionals, values, cwd, brokerStatus) {
31
31
  const { url } = activeRelay(process.env);
32
32
  const headless = values.token !== void 0 || Boolean(process.env.SCP_TOKEN?.trim());
33
33
  if (headless || !relayCredential(process.env, url).token) {
34
- const code = await (await import("./login-DCNCC3RS.js")).login({
34
+ const code = await (await import("./login-22BLC7KI.js")).login({
35
35
  ...values.token !== void 0 ? { token: values.token } : {},
36
36
  ...values.label !== void 0 ? { label: values.label } : {},
37
37
  ...values["no-browser"] ? { "no-browser": true } : {}
38
38
  });
39
39
  if (code !== 0) return code;
40
40
  } else out(`already logged in to ${url}`);
41
- const enabled = await (await import("./remote-YO5B2HQI.js")).cloudMain(["enable"], values.machine !== void 0 ? { machine: values.machine } : {}, brokerStatus);
41
+ const enabled = await (await import("./remote-VRVONVET.js")).cloudMain(["enable"], values.machine !== void 0 ? { machine: values.machine } : {}, brokerStatus);
42
42
  if (enabled !== 0) return enabled;
43
- const setup = (await import("./setup-U7CWN5QP.js")).relaySetupCommand(["--write"], cwd, out);
43
+ const setup = (await import("./setup-NK2HRFZN.js")).relaySetupCommand(["--write"], cwd, out);
44
44
  if (setup !== 0) return setup;
45
45
  const broker = await brokerStatus();
46
46
  if (!broker.enabled) out(`the broker is still local-only; run \`${CLI_NAME} daemon restart\` to link it to ${url}`);
47
47
  else out(`broker linked to ${broker.url ?? url}${broker.connected ? "" : " (connecting)"}`);
48
- const { detectHarnesses, whichOnPath } = await import("./setup-U7CWN5QP.js");
48
+ const { detectHarnesses, whichOnPath } = await import("./setup-NK2HRFZN.js");
49
49
  const which = (name) => whichOnPath(name, process.env);
50
50
  const installed = new Set(detectHarnesses(process.env, which).keys());
51
51
  if (which("pi")) installed.add("pi");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scp-dev/cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "The swarm command line tool.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://scp.dev",
@@ -24,15 +24,15 @@
24
24
  "typecheck": "tsc -p ."
25
25
  },
26
26
  "dependencies": {
27
- "@scp-dev/core": "0.4.0",
28
- "@scp-dev/broker": "0.4.2",
29
- "@scp-dev/hub": "0.6.3",
30
- "@scp-dev/mcp": "0.2.2",
27
+ "@scp-dev/core": "0.5.0",
28
+ "@scp-dev/broker": "0.5.0",
29
+ "@scp-dev/hub": "0.7.0",
30
+ "@scp-dev/mcp": "0.2.3",
31
31
  "smol-toml": "^1.9.1"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@openai/codex": "0.159.0",
35
- "@scp-dev/mesh": "0.0.9",
35
+ "@scp-dev/mesh": "0.0.10",
36
36
  "opencode-ai": "1.18.34",
37
37
  "tsup": "^8.5.1"
38
38
  },