@scp-dev/cli 0.1.2 → 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.
package/README.md CHANGED
@@ -3,21 +3,25 @@
3
3
  The `swarm` command line tool, the reference binding of SCP for shells, hooks and MCP.
4
4
 
5
5
  ```sh
6
- npm install -g @scp-dev/cli
6
+ npm i -g @scp-dev/cli
7
7
  ```
8
8
 
9
9
  ## Usage
10
10
 
11
11
  ```sh
12
- swarm login
13
- swarm setup --write
14
- swarm spawn --name parser -- claude "Fix the parser tests"
12
+ swarm up # browser login, links this machine to the org hub, installs hooks and MCP
13
+ swarm claude --name lead # start a named orchestrator that wakes on peer messages
14
+ ```
15
+
16
+ From the orchestrator's shell:
17
+
18
+ ```sh
19
+ swarm spawn --name parser -- claude -p "Fix the parser tests"
15
20
  swarm send parser "Also cover the empty-input case."
16
21
  swarm list --tree
17
- swarm inbox --wait 30
18
22
  ```
19
23
 
20
- Headless (CI, cron, cloud runners): `swarm login --token scp_t_...` or `SCP_TOKEN`. `swarm up --token ...` logs in, links the broker to the hub and runs setup in one step. The default relay is https://app.scp.dev; `swarm relay use <url>` picks another. Runs on Node 22.13 or later.
24
+ Headless (CI, cron, cloud runners): `swarm up --token scp_t_... [--machine NAME]`. The default relay is https://app.scp.dev; `swarm relay use <url>` picks another. Runs on Node 22.13 or later.
21
25
 
22
26
  ## Provides
23
27
 
@@ -1,56 +1,6 @@
1
- // src/term.ts
2
- import { spawnSync } from "child_process";
3
- import { CLI_NAME } from "@scp-dev/core";
4
- var FAILED = 125;
5
- var SELF = [process.execPath, ...process.versions.bun ? ["--no-env-file", "--config=/dev/null"] : [], process.argv[1]];
6
- function capture(argv, env) {
7
- const result = spawnSync(argv[0], argv.slice(1), { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], ...env ? { env } : {} });
8
- return result.status === 0 ? result.stdout : void 0;
9
- }
10
- function note(message) {
11
- process.stderr.write(`${message.startsWith(`${CLI_NAME} `) ? "" : `${CLI_NAME}: `}${message}
12
- `);
13
- }
14
- async function exitFlushed(code, waitMs = 1e4) {
15
- const ended = (stream) => new Promise((resolve2) => {
16
- try {
17
- stream.end(() => resolve2());
18
- } catch {
19
- resolve2();
20
- }
21
- });
22
- let timer;
23
- await Promise.race([Promise.all([ended(process.stdout), ended(process.stderr)]), new Promise((resolve2) => timer = setTimeout(resolve2, waitMs))]);
24
- clearTimeout(timer);
25
- process.exit(code);
26
- }
27
- function printTable(rows) {
28
- const widths = rows[0].map((_, column) => Math.max(...rows.map((row) => row[column].length)));
29
- for (const row of rows)
30
- console.log(
31
- row.map((cell, column) => cell.padEnd(widths[column])).join(" ").trimEnd()
32
- );
33
- }
34
- var DURATION_PART = /(\d+(?:\.\d+)?)(ms|s|m|h|d)/g;
35
- var DURATION_UNITS = { ms: 1e-3, s: 1, m: 60, h: 3600, d: 86400 };
36
- function parseSeconds(text, flag) {
37
- const trimmed = text.trim();
38
- let seconds;
39
- if (/^\d+(?:\.\d+)?$/.test(trimmed)) seconds = Number(trimmed);
40
- else {
41
- seconds = 0;
42
- let consumed = "";
43
- for (const match of trimmed.matchAll(DURATION_PART)) {
44
- seconds += Number(match[1]) * DURATION_UNITS[match[2]];
45
- consumed += match[0];
46
- }
47
- if (consumed !== trimmed || consumed === "") {
48
- throw new Error(`${flag} ${JSON.stringify(text)} is not a duration like 90s, 30m or 1h30m`);
49
- }
50
- }
51
- if (!(seconds > 0)) throw new Error(`${flag} must be more than zero`);
52
- return Math.max(1, Math.ceil(seconds));
53
- }
1
+ import {
2
+ capture
3
+ } from "./chunk-M2DCR2J4.js";
54
4
 
55
5
  // src/identity.ts
56
6
  import { existsSync, realpathSync } from "fs";
@@ -337,12 +287,6 @@ function relayIdentity(input) {
337
287
  }
338
288
 
339
289
  export {
340
- FAILED,
341
- SELF,
342
- note,
343
- exitFlushed,
344
- printTable,
345
- parseSeconds,
346
290
  shortHostname,
347
291
  detectHarness,
348
292
  hookHarness,
@@ -353,6 +297,7 @@ export {
353
297
  hiddenHarness,
354
298
  lineage,
355
299
  spawnerKey,
300
+ psParent,
356
301
  harnessPid,
357
302
  hookHarnessPid,
358
303
  relayIdentity
@@ -4,14 +4,14 @@ import {
4
4
  configDir,
5
5
  relayCredential,
6
6
  writePrivateJson
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
11
  import {
12
12
  note,
13
13
  printTable
14
- } from "./chunk-DA3EB2YF.js";
14
+ } from "./chunk-M2DCR2J4.js";
15
15
 
16
16
  // src/remote.ts
17
17
  import { existsSync, readFileSync, rmSync } from "fs";
@@ -202,7 +202,7 @@ async function cloudMain(positionals, values, brokerStatus) {
202
202
  writePrivateJson(path, file);
203
203
  if (values.json) json({ file: path, ...file, token: problem ? "missing" : "found" });
204
204
  else {
205
- out(`the relay broker links to ${url} as machine ${machine} the next time it starts (${path})`);
205
+ out(`the relay broker links to ${url} as machine ${machine} (${path})`);
206
206
  if (problem) note(problem);
207
207
  }
208
208
  return 0;
@@ -3,7 +3,7 @@ import {
3
3
  agySettingsFile,
4
4
  mcpFile,
5
5
  mountedEntry
6
- } from "./chunk-N3ZROLFA.js";
6
+ } from "./chunk-IH2RJRIW.js";
7
7
  import {
8
8
  OPTIONS,
9
9
  RelayUsage,
@@ -13,16 +13,18 @@ import {
13
13
  open,
14
14
  selectorList,
15
15
  walkNow
16
- } from "./chunk-7KP6TAJW.js";
16
+ } from "./chunk-URSBFDR6.js";
17
17
  import {
18
18
  steerPrompt
19
- } from "./chunk-XWLF7ON2.js";
19
+ } from "./chunk-ABR5UABL.js";
20
20
  import {
21
- SELF,
22
21
  detectHarness,
23
- note,
24
22
  spawnerKey
25
- } from "./chunk-DA3EB2YF.js";
23
+ } from "./chunk-3PGDKPRN.js";
24
+ import {
25
+ SELF,
26
+ note
27
+ } from "./chunk-M2DCR2J4.js";
26
28
 
27
29
  // src/spawn.ts
28
30
  import { spawn, spawnSync } from "child_process";
@@ -7,7 +7,7 @@ import {
7
7
  hookHarness,
8
8
  hookHarnessPid,
9
9
  relayIdentity
10
- } from "./chunk-DA3EB2YF.js";
10
+ } from "./chunk-3PGDKPRN.js";
11
11
 
12
12
  // src/hooks.ts
13
13
  import { Readable } from "stream";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  RelayUsage
3
- } from "./chunk-7KP6TAJW.js";
3
+ } from "./chunk-URSBFDR6.js";
4
4
  import {
5
5
  note
6
- } from "./chunk-DA3EB2YF.js";
6
+ } from "./chunk-M2DCR2J4.js";
7
7
 
8
8
  // src/relay.ts
9
9
  import { chmodSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
@@ -119,7 +119,7 @@ function relayVerb(positionals, asJson, env = process.env) {
119
119
  }
120
120
  async function loginMain(positionals, values) {
121
121
  if (positionals.length) throw new RelayUsage(`${CLI_NAME} login takes only flags`);
122
- return (await import("./login-JTRUWG6Q.js")).login(values);
122
+ return (await import("./login-5Y3HPSVC.js")).login(values);
123
123
  }
124
124
  function logoutMain(positionals, asJson, env = process.env) {
125
125
  if (positionals.length) throw new RelayUsage(`${CLI_NAME} logout takes no arguments`);
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  hooksTarget
3
- } from "./chunk-4DNQFATX.js";
3
+ } from "./chunk-QQZF5TQD.js";
4
4
  import {
5
5
  RelayUsage
6
- } from "./chunk-7KP6TAJW.js";
6
+ } from "./chunk-URSBFDR6.js";
7
7
  import {
8
8
  AGY_HOOK_SET,
9
9
  BLOCK_STOP,
@@ -15,10 +15,10 @@ import {
15
15
  mergeHooks,
16
16
  musePlugin,
17
17
  opencodePlugin
18
- } from "./chunk-XWLF7ON2.js";
18
+ } from "./chunk-ABR5UABL.js";
19
19
  import {
20
20
  gitToplevel
21
- } from "./chunk-DA3EB2YF.js";
21
+ } from "./chunk-3PGDKPRN.js";
22
22
 
23
23
  // src/setup.ts
24
24
  import { spawnSync } from "child_process";
@@ -179,7 +179,7 @@ function entryRight(harness, entry, argv) {
179
179
  }
180
180
  var withoutRemote = (entry) => Object.fromEntries(Object.entries(entry).filter(([key]) => !REMOTE_KEYS.includes(key)));
181
181
  function mountArgv(harness, bin) {
182
- return [bin, "mcp", "--harness", harness];
182
+ return [bin, "mcp", "--harness", harness, ...harness === "claude" ? ["--channels"] : []];
183
183
  }
184
184
  function agyRules() {
185
185
  return TOOLS.map((tool) => `mcp(${SERVER}/${tool.name})`);
@@ -1172,6 +1172,7 @@ export {
1172
1172
  agySettingsFile,
1173
1173
  manifestFile,
1174
1174
  mountedEntry,
1175
+ entryArgv,
1175
1176
  mountArgv,
1176
1177
  agyRules,
1177
1178
  verifyMount,
@@ -0,0 +1,63 @@
1
+ // src/term.ts
2
+ import { spawnSync } from "child_process";
3
+ import { CLI_NAME } from "@scp-dev/core";
4
+ var FAILED = 125;
5
+ var SELF = [process.execPath, ...process.versions.bun ? ["--no-env-file", "--config=/dev/null"] : [], process.argv[1]];
6
+ function capture(argv, env) {
7
+ const result = spawnSync(argv[0], argv.slice(1), { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], ...env ? { env } : {} });
8
+ return result.status === 0 ? result.stdout : void 0;
9
+ }
10
+ function note(message) {
11
+ process.stderr.write(`${message.startsWith(`${CLI_NAME} `) ? "" : `${CLI_NAME}: `}${message}
12
+ `);
13
+ }
14
+ async function exitFlushed(code, waitMs = 1e4) {
15
+ const ended = (stream) => new Promise((resolve) => {
16
+ try {
17
+ stream.end(() => resolve());
18
+ } catch {
19
+ resolve();
20
+ }
21
+ });
22
+ let timer;
23
+ await Promise.race([Promise.all([ended(process.stdout), ended(process.stderr)]), new Promise((resolve) => timer = setTimeout(resolve, waitMs))]);
24
+ clearTimeout(timer);
25
+ process.exit(code);
26
+ }
27
+ function printTable(rows) {
28
+ const widths = rows[0].map((_, column) => Math.max(...rows.map((row) => row[column].length)));
29
+ for (const row of rows)
30
+ console.log(
31
+ row.map((cell, column) => cell.padEnd(widths[column])).join(" ").trimEnd()
32
+ );
33
+ }
34
+ var DURATION_PART = /(\d+(?:\.\d+)?)(ms|s|m|h|d)/g;
35
+ var DURATION_UNITS = { ms: 1e-3, s: 1, m: 60, h: 3600, d: 86400 };
36
+ function parseSeconds(text, flag) {
37
+ const trimmed = text.trim();
38
+ let seconds;
39
+ if (/^\d+(?:\.\d+)?$/.test(trimmed)) seconds = Number(trimmed);
40
+ else {
41
+ seconds = 0;
42
+ let consumed = "";
43
+ for (const match of trimmed.matchAll(DURATION_PART)) {
44
+ seconds += Number(match[1]) * DURATION_UNITS[match[2]];
45
+ consumed += match[0];
46
+ }
47
+ if (consumed !== trimmed || consumed === "") {
48
+ throw new Error(`${flag} ${JSON.stringify(text)} is not a duration like 90s, 30m or 1h30m`);
49
+ }
50
+ }
51
+ if (!(seconds > 0)) throw new Error(`${flag} must be more than zero`);
52
+ return Math.max(1, Math.ceil(seconds));
53
+ }
54
+
55
+ export {
56
+ FAILED,
57
+ SELF,
58
+ capture,
59
+ note,
60
+ exitFlushed,
61
+ printTable,
62
+ parseSeconds
63
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RelayUsage
3
- } from "./chunk-7KP6TAJW.js";
3
+ } from "./chunk-URSBFDR6.js";
4
4
  import {
5
5
  AGY_HOOK_SET,
6
6
  AGY_UNVERIFIED,
@@ -13,10 +13,10 @@ import {
13
13
  mergeHooks,
14
14
  musePlugin,
15
15
  opencodePlugin
16
- } from "./chunk-XWLF7ON2.js";
16
+ } from "./chunk-ABR5UABL.js";
17
17
  import {
18
18
  gitToplevel
19
- } from "./chunk-DA3EB2YF.js";
19
+ } from "./chunk-3PGDKPRN.js";
20
20
 
21
21
  // src/install.ts
22
22
  import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
@@ -185,7 +185,7 @@ function mcpInstall(harness, as, out) {
185
185
  const line = (cli, extra = []) => out([...cli, CLI_NAME, ...args(extra)].map(shellWord).join(" "));
186
186
  switch (harness) {
187
187
  case "claude":
188
- line(["claude", "mcp", "add", "scp", "--"]);
188
+ line(["claude", "mcp", "add", "scp", "--"], ["--harness", "claude", "--channels"]);
189
189
  return 0;
190
190
  case "codex":
191
191
  line(["codex", "mcp", "add", "scp", "--"], ["--harness", "codex"]);
@@ -1,12 +1,14 @@
1
1
  import {
2
- SELF,
3
2
  harnessLineage,
4
3
  hiddenHarness,
5
4
  labelsFor,
6
5
  lineage,
7
- parseSeconds,
8
6
  readPsTable
9
- } from "./chunk-DA3EB2YF.js";
7
+ } from "./chunk-3PGDKPRN.js";
8
+ import {
9
+ SELF,
10
+ parseSeconds
11
+ } from "./chunk-M2DCR2J4.js";
10
12
 
11
13
  // src/common.ts
12
14
  import { spawn } from "child_process";
@@ -184,7 +186,7 @@ async function open(verb, values, cwd, bodyFromStdin, walk) {
184
186
  `${CLI_NAME} ${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)`
185
187
  );
186
188
  }
187
- const { machineName, openCloud } = await import("./remote-3BWFISXJ.js");
189
+ const { machineName, openCloud } = await import("./remote-GC4XN44U.js");
188
190
  const handle2 = named ? localHandle(named) : void 0;
189
191
  const claim = handle2 ? {
190
192
  handle: handle2,
@@ -317,6 +319,7 @@ function stamp(iso) {
317
319
  const at = new Date(iso);
318
320
  return Number.isNaN(at.getTime()) ? iso : `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())} ${time(iso)}`;
319
321
  }
322
+ var CHANNELS_ENV = "SCP_CHANNELS";
320
323
 
321
324
  export {
322
325
  RelayUsage,
@@ -332,5 +335,6 @@ export {
332
335
  peekGrant,
333
336
  grantParams,
334
337
  time,
335
- stamp
338
+ stamp,
339
+ CHANNELS_ENV
336
340
  };
@@ -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-5CAHHN55.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-XWLF7ON2.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";
@@ -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.2",
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",
@@ -230,6 +232,7 @@ scopes (selectors are comma-separated, e.g. tree:parent,tree:children,group:team
230
232
  other process under an agent they may only narrow. An ancestor always reaches its descendants, and a recipient
231
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.
232
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
233
236
 
234
237
  agent sessions:
235
238
  ${CLI_NAME} mcp [--as NAME] [--harness H] [--channels] the MCP shim a harness mounts: relay tools for one agent, never me/
@@ -254,7 +257,9 @@ agent sessions:
254
257
  A Claude Code subagent's hooks give it a child endpoint, <session handle>-<type>-<first 6 of its id>, whose messages
255
258
  reach it after its tools; its shell is the session's, so it acts as that child with --as.
256
259
  Grok runs the Claude Code hooks, labelled grok; Muse takes them as a plugin; Codex runs new hooks after /hooks review.
257
- --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.
258
263
 
259
264
  relay:
260
265
  ${CLI_NAME} relay use <url> | show select a relay, or show its source and credential status
@@ -491,10 +496,11 @@ async function relayMain(argv, cwd = process.cwd()) {
491
496
  out(package_default.version);
492
497
  return 0;
493
498
  }
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);
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);
496
502
  if (verb === "spawn") {
497
- const { splitSpawn, spawnCommand } = await import("./spawn-TQ3HOZTQ.js");
503
+ const { splitSpawn, spawnCommand } = await import("./spawn-KT5235WX.js");
498
504
  const { flags } = splitSpawn(rest);
499
505
  if (flags.includes("--help") || flags.includes("-h")) {
500
506
  out(flags.includes("--all") ? RELAY_HELP : SPAWN_HELP);
@@ -516,13 +522,16 @@ async function relayMain(argv, cwd = process.cwd()) {
516
522
  return 0;
517
523
  }
518
524
  for (const key of Object.keys(values)) if (!allowed.includes(key)) throw new RelayUsage(`${CLI_NAME} ${verb} does not take --${key}`);
519
- 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
+ }
520
529
  if (verb === "mcp")
521
- return (await import("./mcp-P2AZYP5G.js")).relayMcpCommand(values, positionals);
530
+ return (await import("./mcp-GI5OMTUT.js")).relayMcpCommand(values, positionals);
522
531
  if (verb === "hooks") {
523
532
  if (positionals[0] !== "install" || positionals.length > 1) throw new RelayUsage(`${CLI_NAME} hooks takes install --harness claude|codex|opencode|agy|muse`);
524
533
  if (values.write && values.print) throw new RelayUsage("--print and --write: pick one");
525
- return (await import("./install-HFBSKSTG.js")).installHooks({
534
+ return (await import("./install-4KQQWGGB.js")).installHooks({
526
535
  harness: values.harness,
527
536
  scope: values.scope,
528
537
  write: values.write === true,
@@ -533,18 +542,25 @@ async function relayMain(argv, cwd = process.cwd()) {
533
542
  out
534
543
  });
535
544
  }
536
- if (verb === "relay") return (await import("./relay-VVJPYVXE.js")).relayVerb(positionals, values.json === true);
537
- if (verb === "up") return (await import("./up-KXOYO7QV.js")).up(positionals, values, cwd, cloudStatus);
538
- if (verb === "login")
539
- return (await import("./relay-VVJPYVXE.js")).loginMain(positionals, values);
540
- if (verb === "logout") return (await import("./relay-VVJPYVXE.js")).logoutMain(positionals, values.json === true);
541
- 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
+ }
542
558
  if (verb === "holds") holdsArgs(values, positionals);
543
559
  const bodyFromStdin = (verb === "send" || verb === "ask" || verb === "reply") && (positionals[1] === void 0 || positionals[1] === "-");
544
560
  const opened = await open(verb, values, cwd, bodyFromStdin);
545
561
  const { client } = opened;
546
562
  try {
547
- 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);
548
564
  return await runVerb(opened, verb, values, positionals, cwd);
549
565
  } finally {
550
566
  client.close();
@@ -560,6 +576,55 @@ async function cloudStatus() {
560
576
  client.close();
561
577
  }
562
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
+ }
563
628
  async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, values, positionals, cwd) {
564
629
  const asJson = values.json === true;
565
630
  const attachments = (values.attach ?? []).map((path) => ({ kind: "path", path: path.startsWith("/") ? path : `${cwd}/${path}` }));
@@ -672,9 +737,9 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
672
737
  return 0;
673
738
  }
674
739
  case "peek":
675
- 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);
676
741
  case "set-peek":
677
- 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);
678
743
  case "why": {
679
744
  const [from, to] = positionals;
680
745
  if (!from || !to || positionals.length > 2) throw new RelayUsage(`${CLI_NAME} why takes <from> <to>`);
@@ -747,9 +812,10 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
747
812
  }
748
813
  case "inbox": {
749
814
  if (values.ack && values.keep) throw new RelayUsage("--ack and --keep: pick one");
750
- 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 });
751
817
  if (asJson) json({ preamble: PREAMBLE, messages });
752
- 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)` : ""}`);
753
819
  else {
754
820
  out(PREAMBLE);
755
821
  out();
@@ -1226,11 +1292,12 @@ async function daemon(idle, policyFile) {
1226
1292
  });
1227
1293
  let broker;
1228
1294
  try {
1229
- const { daemonBridge } = await import("./remote-3BWFISXJ.js");
1295
+ const { daemonBridge } = await import("./remote-GC4XN44U.js");
1230
1296
  const bridge = await daemonBridge();
1231
1297
  broker = await startBroker({
1232
1298
  dir: relayDir(),
1233
1299
  ...bridge ? { bridge } : {},
1300
+ bridgeConfig: () => daemonBridge(),
1234
1301
  ...idleMs !== void 0 ? { idleMs } : {},
1235
1302
  ...policyFile ? { policyPath: resolve(policyFile) } : {}
1236
1303
  });
@@ -1,21 +1,24 @@
1
1
  import {
2
2
  spawnDetached
3
- } from "./chunk-V5I7KEU7.js";
3
+ } from "./chunk-ABP5AKFH.js";
4
4
  import "./chunk-KN7WEIE4.js";
5
- import "./chunk-N3ZROLFA.js";
5
+ import "./chunk-IH2RJRIW.js";
6
6
  import {
7
7
  mcpInstall
8
- } from "./chunk-4DNQFATX.js";
8
+ } from "./chunk-QQZF5TQD.js";
9
9
  import {
10
+ CHANNELS_ENV,
10
11
  RelayUsage,
11
12
  startDaemon
12
- } from "./chunk-7KP6TAJW.js";
13
- import "./chunk-XWLF7ON2.js";
13
+ } from "./chunk-URSBFDR6.js";
14
+ import "./chunk-ABR5UABL.js";
14
15
  import "./chunk-YYSLYVNZ.js";
15
16
  import {
16
17
  harnessPid,
18
+ psParent,
17
19
  relayIdentity
18
- } from "./chunk-DA3EB2YF.js";
20
+ } from "./chunk-3PGDKPRN.js";
21
+ import "./chunk-M2DCR2J4.js";
19
22
 
20
23
  // src/mcp.ts
21
24
  import { CLI_NAME, validHandle } from "@scp-dev/core";
@@ -46,6 +49,7 @@ async function relayMcpCommand(values, positionals) {
46
49
  output: (line) => void process.stdout.write(line),
47
50
  env: process.env,
48
51
  channels: values.channels === true,
52
+ launchedWithChannels: values.channels === true && launchedWithChannels(process.env),
49
53
  spawn: (request) => spawnDetached(spawnWords(request), request.cwd ?? claim.cwd),
50
54
  log: (line) => void process.stderr.write(`${line}
51
55
  `)
@@ -66,7 +70,12 @@ function spawnWords(request) {
66
70
  ...request.command
67
71
  ];
68
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
+ }
69
77
  export {
78
+ launchedWithChannels,
70
79
  relayMcpCommand,
71
80
  spawnWords
72
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,
@@ -4,6 +4,7 @@ import {
4
4
  agyRules,
5
5
  agySettingsFile,
6
6
  detectHarnesses,
7
+ entryArgv,
7
8
  manifestFile,
8
9
  mcpFile,
9
10
  mountArgv,
@@ -14,18 +15,20 @@ import {
14
15
  verifyMount,
15
16
  verifyMusePlugin,
16
17
  whichOnPath
17
- } from "./chunk-N3ZROLFA.js";
18
- import "./chunk-4DNQFATX.js";
19
- import "./chunk-7KP6TAJW.js";
20
- import "./chunk-XWLF7ON2.js";
18
+ } from "./chunk-IH2RJRIW.js";
19
+ import "./chunk-QQZF5TQD.js";
20
+ import "./chunk-URSBFDR6.js";
21
+ import "./chunk-ABR5UABL.js";
21
22
  import "./chunk-YYSLYVNZ.js";
22
- import "./chunk-DA3EB2YF.js";
23
+ import "./chunk-3PGDKPRN.js";
24
+ import "./chunk-M2DCR2J4.js";
23
25
  export {
24
26
  SETUP_HARNESSES,
25
27
  SETUP_HELP,
26
28
  agyRules,
27
29
  agySettingsFile,
28
30
  detectHarnesses,
31
+ entryArgv,
29
32
  manifestFile,
30
33
  mcpFile,
31
34
  mountArgv,
@@ -29,16 +29,17 @@ import {
29
29
  steerArgv,
30
30
  transcriptReplies,
31
31
  workerName
32
- } from "./chunk-V5I7KEU7.js";
32
+ } from "./chunk-ABP5AKFH.js";
33
33
  import {
34
34
  SPAWN_HELP
35
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";
36
+ import "./chunk-IH2RJRIW.js";
37
+ import "./chunk-QQZF5TQD.js";
38
+ import "./chunk-URSBFDR6.js";
39
+ import "./chunk-ABR5UABL.js";
40
40
  import "./chunk-YYSLYVNZ.js";
41
- import "./chunk-DA3EB2YF.js";
41
+ import "./chunk-3PGDKPRN.js";
42
+ import "./chunk-M2DCR2J4.js";
42
43
  export {
43
44
  MESSAGING,
44
45
  SPAWN_HELP,
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  activeRelay,
3
3
  relayCredential
4
- } from "./chunk-4QYUPPGV.js";
4
+ } from "./chunk-BUC5G5PY.js";
5
5
  import {
6
6
  RelayUsage
7
- } from "./chunk-7KP6TAJW.js";
8
- import "./chunk-DA3EB2YF.js";
7
+ } from "./chunk-URSBFDR6.js";
8
+ import "./chunk-3PGDKPRN.js";
9
+ import "./chunk-M2DCR2J4.js";
9
10
 
10
11
  // src/up.ts
11
12
  import { CLI_NAME } from "@scp-dev/core";
@@ -16,19 +17,19 @@ async function up(positionals, values, cwd, brokerStatus) {
16
17
  const { url } = activeRelay(process.env);
17
18
  const headless = values.token !== void 0 || Boolean(process.env.SCP_TOKEN?.trim());
18
19
  if (headless || !relayCredential(process.env, url).token) {
19
- const code = await (await import("./login-JTRUWG6Q.js")).login({
20
+ const code = await (await import("./login-5Y3HPSVC.js")).login({
20
21
  ...values.token !== void 0 ? { token: values.token } : {},
21
22
  ...values.label !== void 0 ? { label: values.label } : {},
22
23
  ...values["no-browser"] ? { "no-browser": true } : {}
23
24
  });
24
25
  if (code !== 0) return code;
25
26
  } else out(`already logged in to ${url}`);
26
- const enabled = await (await import("./remote-3BWFISXJ.js")).cloudMain(["enable"], values.machine !== void 0 ? { machine: values.machine } : {}, brokerStatus);
27
+ const enabled = await (await import("./remote-GC4XN44U.js")).cloudMain(["enable"], values.machine !== void 0 ? { machine: values.machine } : {}, brokerStatus);
27
28
  if (enabled !== 0) return enabled;
28
- const setup = (await import("./setup-RDPVGE4H.js")).relaySetupCommand(["--write"], cwd, out);
29
+ const setup = (await import("./setup-UF4AA6RO.js")).relaySetupCommand(["--write"], cwd, out);
29
30
  if (setup !== 0) return setup;
30
31
  const broker = await brokerStatus();
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}`);
32
+ if (!broker.enabled) out(`the broker is still local-only; run \`${CLI_NAME} daemon restart\` to link it to ${url}`);
32
33
  else out(`broker linked to ${broker.url ?? url}${broker.connected ? "" : " (connecting)"}`);
33
34
  return 0;
34
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scp-dev/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "The swarm command line tool.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://scp.dev",
@@ -25,9 +25,9 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@scp-dev/core": "0.1.1",
28
- "@scp-dev/broker": "0.1.1",
28
+ "@scp-dev/broker": "0.1.2",
29
29
  "@scp-dev/hub": "0.1.1",
30
- "@scp-dev/mcp": "0.1.1",
30
+ "@scp-dev/mcp": "0.1.2",
31
31
  "smol-toml": "^1.9.1"
32
32
  },
33
33
  "devDependencies": {
@@ -1,14 +0,0 @@
1
- import {
2
- hooksTarget,
3
- installHooks,
4
- mcpInstall
5
- } from "./chunk-4DNQFATX.js";
6
- import "./chunk-7KP6TAJW.js";
7
- import "./chunk-XWLF7ON2.js";
8
- import "./chunk-YYSLYVNZ.js";
9
- import "./chunk-DA3EB2YF.js";
10
- export {
11
- hooksTarget,
12
- installHooks,
13
- mcpInstall
14
- };