@scp-dev/cli 0.2.1 → 0.2.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.
@@ -374,7 +374,8 @@ var OPTIONS = {
374
374
  who: { type: "boolean" },
375
375
  peek: { type: "string" },
376
376
  endpoint: { type: "string" },
377
- live: { type: "boolean" }
377
+ live: { type: "boolean" },
378
+ parent: { type: "string" }
378
379
  };
379
380
  var SPAWN_OPTIONS = [
380
381
  "as",
@@ -486,7 +487,7 @@ async function open(verb, values, cwd, bodyFromStdin, walk) {
486
487
  `${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)`
487
488
  );
488
489
  }
489
- const { machineName, openCloud } = await import("./remote-2OWBWLJH.js");
490
+ const { machineName, openCloud } = await import("./remote-NTHRHVEP.js");
490
491
  const handle2 = named ? localHandle(named) : void 0;
491
492
  const claim = handle2 ? {
492
493
  handle: handle2,
@@ -743,7 +744,7 @@ function relayVerb(positionals, asJson, env = process.env) {
743
744
  }
744
745
  async function loginMain(positionals, values) {
745
746
  if (positionals.length) throw new RelayUsage(`${CLI_NAME3} login takes only flags`);
746
- return (await import("./login-UB72MU7W.js")).login(values);
747
+ return (await import("./login-IPS75GLQ.js")).login(values);
747
748
  }
748
749
  function logoutMain(positionals, asJson, env = process.env) {
749
750
  if (positionals.length) throw new RelayUsage(`${CLI_NAME3} logout takes no arguments`);
@@ -3,10 +3,10 @@ import {
3
3
  agySettingsFile,
4
4
  mcpFile,
5
5
  mountedEntry
6
- } from "./chunk-NJLFHBSS.js";
6
+ } from "./chunk-W3VNCRBX.js";
7
7
  import {
8
8
  steerPrompt
9
- } from "./chunk-HF4KP6AR.js";
9
+ } from "./chunk-X4ZLEM47.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-ZLL5EGI2.js";
21
+ } from "./chunk-E7JG5PJB.js";
22
22
  import {
23
23
  SELF,
24
24
  note
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  hooksTarget
3
- } from "./chunk-KC5AV5NQ.js";
3
+ } from "./chunk-ZMVFN6MU.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-HF4KP6AR.js";
15
+ } from "./chunk-X4ZLEM47.js";
16
16
  import {
17
17
  RelayUsage,
18
18
  gitToplevel,
19
19
  setJoinMode
20
- } from "./chunk-ZLL5EGI2.js";
20
+ } from "./chunk-E7JG5PJB.js";
21
21
 
22
22
  // src/setup.ts
23
23
  import { spawnSync } from "child_process";
@@ -35,6 +35,7 @@ import {
35
35
  mkdtempSync,
36
36
  readFileSync,
37
37
  readlinkSync,
38
+ realpathSync,
38
39
  renameSync,
39
40
  rmSync,
40
41
  statSync,
@@ -611,6 +612,23 @@ function museInstalled(env) {
611
612
  }
612
613
  }
613
614
  var installedFrom = (record) => record && isObject(record.source) ? String(record.source.path) : void 0;
615
+ function samePath(a, b) {
616
+ if (a === void 0) return false;
617
+ if (a === b) return true;
618
+ try {
619
+ return realpathSync(a) === realpathSync(b);
620
+ } catch {
621
+ return false;
622
+ }
623
+ }
624
+ function museReviewNeeded(cli, run) {
625
+ try {
626
+ const caps = JSON.parse(run([cli, "plugins", "inspect", SERVER, "--json"]).output).runtime_capabilities;
627
+ return Array.isArray(caps) && caps.some((cap) => isObject(cap) && cap.status === "review_needed");
628
+ } catch {
629
+ return false;
630
+ }
631
+ }
614
632
  function pluginDir(manifest) {
615
633
  return dirname(dirname(manifest));
616
634
  }
@@ -655,19 +673,24 @@ function planHooks(plan, harness, ctx) {
655
673
  const from = installedFrom(record);
656
674
  const current = content ?? readFileSync(resolved(file), "utf8");
657
675
  const disabled = record?.enabled === false;
658
- const install = content !== void 0 || from !== dir || record?.manifest_sha256 !== sha256(current);
659
- if (!install && !disabled) {
676
+ const install = content !== void 0 || !samePath(from, dir) || record?.manifest_sha256 !== sha256(current);
677
+ const cli = ctx.found.get("muse");
678
+ const review = install || museReviewNeeded(cli, run);
679
+ if (!install && !disabled && !review) {
660
680
  note(plan, harness, `hooks: up to date in ${file}${held}`);
661
681
  return;
662
682
  }
663
- const cli = ctx.found.get("muse");
664
- const commands = [...install ? [[cli, "plugins", "install", dir, "--scope", "user"]] : [], ...disabled ? [[cli, "plugins", "enable", SERVER]] : []];
665
- const head = install ? `${content ? "write" : "install"} the ${CLI_NAME} plugin ${file}${held}${from && from !== dir ? `, replacing the scp plugin installed from ${from}` : ""}${disabled ? ", and enable it, which Muse has disabled" : ""}` : `enable the ${CLI_NAME} plugin ${file}, which Muse has disabled`;
683
+ const commands = [
684
+ ...install ? [[cli, "plugins", "install", dir, "--scope", "user"]] : [],
685
+ ...disabled ? [[cli, "plugins", "enable", SERVER]] : [],
686
+ ...review ? [[cli, "plugins", "approve", SERVER]] : []
687
+ ];
688
+ const head = install ? `${content ? "write" : "install"} the ${CLI_NAME} plugin ${file}${held}${from && !samePath(from, dir) ? `, replacing the scp plugin installed from ${from}` : ""}${disabled ? ", and enable it, which Muse has disabled" : ""}` : disabled ? `enable the ${CLI_NAME} plugin ${file}, which Muse has disabled` : `approve the hooks of the ${CLI_NAME} plugin ${file}, which Muse holds for review`;
666
689
  addStep(plan, {
667
690
  harness,
668
691
  label: "hooks",
669
692
  file,
670
- plan: [head, ...commands.map(shown), ...install ? ["Muse asks you to review a plugin's hooks before it runs them"] : []],
693
+ plan: [head, ...commands.map(shown), ...review ? ["approves its hooks: a headless muse exec runs no hook Muse holds for review"] : []],
671
694
  apply: () => {
672
695
  const next = install ? desiredHooks(kind, file, blockStop) : void 0;
673
696
  const saved = next === void 0 ? void 0 : writeFile(file, next, now, { mode: 420 });
@@ -741,7 +764,7 @@ function planUnhook(plan, harness, kind, file, items, ctx) {
741
764
  const gone = { harness, label: "hooks", file, plan: [`forget ${file}, which is gone`], apply: () => void 0, manifest: forgotten };
742
765
  if (kind === "muse") {
743
766
  const cli = ctx.found.get("muse");
744
- const installed = installedFrom(museInstalled(ctx.env)) === pluginDir(file);
767
+ const installed = samePath(installedFrom(museInstalled(ctx.env)), pluginDir(file));
745
768
  if (!existsSync(target) && !installed) {
746
769
  addStep(plan, gone);
747
770
  return;
@@ -936,7 +959,7 @@ function verifyMusePlugin(cli, dir, run, manifest) {
936
959
  } catch {
937
960
  return { ok: false, line: `unknown: muse plugins list --json exited ${result.code}: ${lastLine(result.output)}` };
938
961
  }
939
- const ours = installedFrom(record) === dir;
962
+ const ours = samePath(installedFrom(record), dir);
940
963
  if (manifest === void 0) return { ok: !ours, line: ours ? "still installed (muse plugins list)" : "removed (muse plugins list)" };
941
964
  if (!ours) return { ok: false, line: "failed: muse plugins list does not show the scp plugin from setup" };
942
965
  const current = record.manifest_sha256 === sha256(manifest) && record.enabled !== false;
@@ -12,7 +12,7 @@ import {
12
12
  psStart,
13
13
  relayIdentity,
14
14
  sessionKey
15
- } from "./chunk-ZLL5EGI2.js";
15
+ } from "./chunk-E7JG5PJB.js";
16
16
 
17
17
  // src/hooks.ts
18
18
  import { Readable } from "stream";
@@ -345,6 +345,15 @@ function inferredTitle(harness, event, named, payload) {
345
345
  const text = harness === "claude-code" ? transcriptTitle(asString(field(payload, "transcript_path", "transcriptPath")), asString(field(payload, "session_id", "sessionId"))) : own && !OPENCODE_UNNAMED.test(own) ? titleText(own) : void 0;
346
346
  return text ? { text, from: "summary" } : void 0;
347
347
  }
348
+ function launchedSubagent(harness, event, payload) {
349
+ if (harness !== "claude-code" || event.kind !== "tool" || event.agent || !/^(agent|task)$/i.test(event.tool ?? "")) return void 0;
350
+ const input = field(payload, "tool_input", "toolInput");
351
+ const response = field(payload, "tool_response", "toolResponse");
352
+ if (typeof input !== "object" || typeof response !== "object" || Array.isArray(response)) return void 0;
353
+ const id = asString(response.agentId);
354
+ const title = titleText(asString(input.description));
355
+ return id && title && /^[A-Za-z0-9_-]{1,64}$/.test(id) ? { id, title } : void 0;
356
+ }
348
357
  function hookReply(harness, kind, text, unread = 0, continues = true) {
349
358
  if (kind === "stop" && harness === "claude-code" && continues)
350
359
  return JSON.stringify({ hookSpecificOutput: { hookEventName: "Stop", additionalContext: text } });
@@ -448,6 +457,8 @@ async function runHook(args, options) {
448
457
  if (turn) posted.push(client.request("peek_turn", turn));
449
458
  const title = inferredTitle(claim.harness, event, named, payload);
450
459
  if (title) posted.push(client.request("title_infer", title));
460
+ const launched = launchedSubagent(claim.harness, event, payload);
461
+ if (launched) posted.push(client.request("subagent_title", launched));
451
462
  const injected = inject ? client.request(
452
463
  "inject",
453
464
  counts ? { count: true, ...stop ? { stop: true, directives: true, ...blockStop ? { hold_unread: true } : {} } : {} } : {}
@@ -510,6 +521,8 @@ async function wakeHook(harnessArg, payload, waitS, options) {
510
521
  const client = await options.connect(false);
511
522
  try {
512
523
  await client.hello({ transient: true, claim });
524
+ await client.request("wake", { via: OPENCODE_WAKE }).catch(() => {
525
+ });
513
526
  const result = await client.request("inject", { wait_s: waitMs / 1e3 });
514
527
  if (!result.offered.length) return;
515
528
  const intro = result.intro ? introText(result.intro, result.intro_key) : void 0;
@@ -5,7 +5,7 @@ import {
5
5
  configDir,
6
6
  relayCredential,
7
7
  writePrivateJson
8
- } from "./chunk-ZLL5EGI2.js";
8
+ } from "./chunk-E7JG5PJB.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-HF4KP6AR.js";
13
+ } from "./chunk-X4ZLEM47.js";
14
14
  import {
15
15
  RelayUsage,
16
16
  gitToplevel
17
- } from "./chunk-ZLL5EGI2.js";
17
+ } from "./chunk-E7JG5PJB.js";
18
18
 
19
19
  // src/install.ts
20
20
  import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
@@ -166,7 +166,7 @@ function injectionNotes(harness, scope, target, blockStop, wait) {
166
166
  ];
167
167
  case "muse":
168
168
  return [
169
- `Muse loads hooks only from plugins: muse plugins install ${dirname(dirname(target))} --scope ${scope}`,
169
+ `Muse loads hooks only from plugins: muse plugins install ${dirname(dirname(target))} --scope ${scope}, then muse plugins approve scp (a headless muse exec runs no hook Muse holds for review)`,
170
170
  `new relay messages reach the model after each tool call and with each prompt; ${held}`,
171
171
  pushed,
172
172
  "Muse hands plugin hooks no SCP_* variables: they key the session by the harness process, so set no SCP_SESSION for Muse's shim"
@@ -3,16 +3,16 @@ import {
3
3
  mcpFile,
4
4
  mountedEntry,
5
5
  whichOnPath
6
- } from "./chunk-NJLFHBSS.js";
7
- import "./chunk-KC5AV5NQ.js";
8
- import "./chunk-HF4KP6AR.js";
6
+ } from "./chunk-W3VNCRBX.js";
7
+ import "./chunk-ZMVFN6MU.js";
8
+ import "./chunk-X4ZLEM47.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-ZLL5EGI2.js";
15
+ } from "./chunk-E7JG5PJB.js";
16
16
  import "./chunk-MNTIEWKF.js";
17
17
 
18
18
  // src/claude.ts
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-I4F53MMA.js");
20
+ const { run } = await import("./main-SQKCKV3B.js");
21
21
  await run(process.argv.slice(2));
22
22
  }
@@ -1,18 +1,18 @@
1
1
  import {
2
2
  codexMount,
3
3
  codexOverrides
4
- } from "./chunk-25ZVL5LD.js";
4
+ } from "./chunk-IXCHAWOL.js";
5
5
  import "./chunk-KN7WEIE4.js";
6
- import "./chunk-NJLFHBSS.js";
7
- import "./chunk-KC5AV5NQ.js";
8
- import "./chunk-HF4KP6AR.js";
6
+ import "./chunk-W3VNCRBX.js";
7
+ import "./chunk-ZMVFN6MU.js";
8
+ import "./chunk-X4ZLEM47.js";
9
9
  import "./chunk-YYSLYVNZ.js";
10
10
  import {
11
11
  RelayUsage,
12
12
  readPsTable,
13
13
  relayIdentity,
14
14
  startDaemon
15
- } from "./chunk-ZLL5EGI2.js";
15
+ } from "./chunk-E7JG5PJB.js";
16
16
  import {
17
17
  SELF
18
18
  } from "./chunk-MNTIEWKF.js";
@@ -486,6 +486,7 @@ ${stderr.toString().trim()}` : ""}`
486
486
  },
487
487
  claim,
488
488
  channels: true,
489
+ wake: "codex-app-server",
489
490
  input,
490
491
  env: launch.env,
491
492
  output: (chunk) => {
@@ -38,11 +38,11 @@ import {
38
38
  tldrEvery,
39
39
  transcriptTitle,
40
40
  unreadPointer
41
- } from "./chunk-HF4KP6AR.js";
41
+ } from "./chunk-X4ZLEM47.js";
42
42
  import {
43
43
  SUBAGENT
44
44
  } from "./chunk-YYSLYVNZ.js";
45
- import "./chunk-ZLL5EGI2.js";
45
+ import "./chunk-E7JG5PJB.js";
46
46
  import "./chunk-MNTIEWKF.js";
47
47
  export {
48
48
  AGY_HOOK_SET,
@@ -2,10 +2,10 @@ import {
2
2
  hooksTarget,
3
3
  installHooks,
4
4
  mcpInstall
5
- } from "./chunk-KC5AV5NQ.js";
6
- import "./chunk-HF4KP6AR.js";
5
+ } from "./chunk-ZMVFN6MU.js";
6
+ import "./chunk-X4ZLEM47.js";
7
7
  import "./chunk-YYSLYVNZ.js";
8
- import "./chunk-ZLL5EGI2.js";
8
+ import "./chunk-E7JG5PJB.js";
9
9
  import "./chunk-MNTIEWKF.js";
10
10
  export {
11
11
  hooksTarget,
@@ -5,7 +5,7 @@ import {
5
5
  sessionKey,
6
6
  startDaemon,
7
7
  walkNow
8
- } from "./chunk-ZLL5EGI2.js";
8
+ } from "./chunk-E7JG5PJB.js";
9
9
  import "./chunk-MNTIEWKF.js";
10
10
 
11
11
  // src/join.ts
@@ -32,9 +32,12 @@ async function joinCommand(values, positionals, cwd, out) {
32
32
  });
33
33
  const client = await connectBroker(dir, () => startDaemon(dir));
34
34
  try {
35
- const hello = await client.hello({ transient: true, claim, join: handle ? { handle } : {} });
36
- if (values.json === true) out(JSON.stringify(hello.endpoint, null, 2));
37
- else out(`joined as ${hello.endpoint.address}: peers can message it, and its hooks hand it new messages at turn boundaries`);
35
+ const under = typeof values.under === "string" ? values.under : void 0;
36
+ const hello = await client.hello({ transient: true, claim, join: { ...handle ? { handle } : {}, ...under ? { under } : {} } });
37
+ const view = hello.endpoint;
38
+ if (values.json === true) out(JSON.stringify(view, null, 2));
39
+ else
40
+ out(`joined as ${view.address}${view.parent ? ` under ${view.parent}` : ""}: peers can message it, and its hooks hand it new messages at turn boundaries`);
38
41
  } finally {
39
42
  client.close();
40
43
  }
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  machineName
3
- } from "./chunk-MXQ7B3CQ.js";
3
+ } from "./chunk-ZDU53ZRF.js";
4
4
  import {
5
5
  RelayUsage,
6
6
  activeRelay,
7
7
  saveCredential
8
- } from "./chunk-ZLL5EGI2.js";
8
+ } from "./chunk-E7JG5PJB.js";
9
9
  import {
10
10
  note
11
11
  } from "./chunk-MNTIEWKF.js";
@@ -15,7 +15,7 @@ import {
15
15
  stamp,
16
16
  startDaemon,
17
17
  time
18
- } from "./chunk-ZLL5EGI2.js";
18
+ } from "./chunk-E7JG5PJB.js";
19
19
  import {
20
20
  FAILED,
21
21
  exitFlushed,
@@ -33,7 +33,7 @@ import { parseArgs } from "util";
33
33
  // package.json
34
34
  var package_default = {
35
35
  name: "@scp-dev/cli",
36
- version: "0.2.1",
36
+ version: "0.2.3",
37
37
  description: "The swarm command line tool.",
38
38
  license: "Apache-2.0",
39
39
  homepage: "https://scp.dev",
@@ -65,6 +65,7 @@ var package_default = {
65
65
  },
66
66
  devDependencies: {
67
67
  "@openai/codex": "0.159.0",
68
+ "@scp-dev/mesh": "workspace:*",
68
69
  "opencode-ai": "1.18.34",
69
70
  tsup: "^8.5.1"
70
71
  },
@@ -79,11 +80,13 @@ import {
79
80
  deliveryText,
80
81
  LIMITS,
81
82
  PREAMBLE,
83
+ presenceText,
82
84
  reachLines,
83
85
  receiptLines,
84
86
  RelayError,
85
87
  shortId,
86
88
  stripControl,
89
+ wakeText,
87
90
  widenText,
88
91
  wrapMessage,
89
92
  CLI_NAME
@@ -98,8 +101,9 @@ results back, as with native subagents.
98
101
  ${CLI_NAME} reply <id> [text|-] answer a message in its thread: ${CLI_NAME} reply 5af1f2 "done"
99
102
  ${CLI_NAME} ask <to> [text|-] [--wait S] send a request and wait for its reply: ${CLI_NAME} ask lead "which branch?"
100
103
  ${CLI_NAME} inbox [--wait S] your unread messages, then marked read: ${CLI_NAME} inbox
101
- ${CLI_NAME} list [--all] agents on the relay: address, title, presence, status; --all adds offline ones
104
+ ${CLI_NAME} list [--all] agents on the relay: address, title, presence, wake, status; --all adds offline ones
102
105
  ${CLI_NAME} join [name] put this session on the relay, named once: ${CLI_NAME} join lead (${CLI_NAME} name renames it)
106
+ ${CLI_NAME} join --under <parent> link this session under an orchestrator, as a spawned worker sits: ${CLI_NAME} join --under lead
103
107
  ${CLI_NAME} status [id] the fleet in short; with the id of a message you sent: who read it when
104
108
  ${CLI_NAME} peek <ep> a short, redacted view of a peekable agent's last turns: ${CLI_NAME} peek rev
105
109
  ${CLI_NAME} set-status <state> [note] working, waiting, blocked or done: ${CLI_NAME} set-status done "PR open"
@@ -120,7 +124,7 @@ More: ${CLI_NAME} help --all (the full reference), ${CLI_NAME} <verb> --help (on
120
124
  var RELAY_HELP = `${CLI_NAME}: messages between the agent sessions on this machine, and a live view of the fleet
121
125
 
122
126
  usage:
123
- ${CLI_NAME} list [--all] [--roots] [--tree] agents on the relay: address, title, presence, status, cwd; offline ones only with --all;
127
+ ${CLI_NAME} list [--all] [--roots] [--tree] agents on the relay: address, title, presence, wake, status, cwd; offline only with --all;
124
128
  sessions nobody spawned that spawned nobody fold into one line, --roots (or --all) lists
125
129
  them; --tree nests them by parent; (you) marks your own
126
130
  ${CLI_NAME} list --contacts the endpoints of other accounts your contacts let you reach: address, harness, presence
@@ -228,7 +232,7 @@ scopes (selectors are comma-separated, e.g. tree:parent,tree:children,group:team
228
232
  messages with the relay MCP tools (relay_send, relay_inbox, relay_reply). A
229
233
  ${CLI_NAME} command from codex's workspace-write sandbox also needs
230
234
  -c sandbox_workspace_write.network_access=true (the relay socket counts as network)
231
- ${CLI_NAME} pi [--name N] [--] [pi args...] runs pi with the @scp-dev/pi extension: the relay tools, and each new
235
+ ${CLI_NAME} pi [--name N] [--] [pi args...] runs pi with the bundled SCP extension: the relay tools, and each new
232
236
  message as a turn (steering a running one, waking an idle session)
233
237
  ${CLI_NAME} codex [--name N] [--headless] [--] [codex args...]
234
238
  runs codex with relay messages waking an idle thread or steering a running turn
@@ -261,7 +265,7 @@ agent sessions:
261
265
  ${CLI_NAME} hook <harness> [event] what those hooks run: one event from the hook JSON on stdin; prints only the reply
262
266
  A session's shim and hooks share one endpoint, keyed by $SCP_SESSION, else the harness process's pid and start time.
263
267
  Without --as or $SCP_NAME the handle is <harness>-<worktree>; labels gain machine=<short hostname>.
264
- A Claude Code subagent's hooks give it a child endpoint, <session handle>-<type>-<first 6 of its id>, whose messages
268
+ A Claude Code subagent's hooks give it a child endpoint, <session handle>-<short type>-<first 4 of its id>, whose messages
265
269
  reach it after its tools; its shell is the session's, so it acts as that child with --as.
266
270
  Grok runs the Claude Code hooks, labelled grok; Muse takes them as a plugin; Codex runs new hooks after /hooks review.
267
271
  ${CLI_NAME} claude [--name N] [--] [claude args...] start Claude Code with the scp channel loaded, so peer messages wake
@@ -276,6 +280,17 @@ agent sessions:
276
280
  and title stay; a handle another endpoint holds, live or offline, is refused;
277
281
  a spawned worker is named by its spawner (policy defaults.rename: self lets it);
278
282
  --endpoint renames another agent, for the human at a terminal
283
+ ${CLI_NAME} join [<handle>] --under <parent> link the session under the agent <parent> (local, one it may message), as if
284
+ <parent> had spawned it: it shows under <parent> in list --tree, the dashboard
285
+ and the graph, and takes on a worker's scope (the policy's child_default: by
286
+ default it reaches only what <parent> reaches). Narrowing, so always allowed
287
+ ${CLI_NAME} adopt <child> [--parent <p>] link a local root agent and its subtree under <p>, for the human at a terminal;
288
+ from an agent's shell with a terminal attached <p> defaults to that agent.
289
+ An agent without a terminal is refused: the session joins with --under itself
290
+ ${CLI_NAME} unlink [<child>] put a linked agent back at the root, its subtree along and the child scope off;
291
+ widening, so the human at a terminal only (policy defaults.unlink: self lets
292
+ an agent unlink itself). A link that would put a parent under its own child
293
+ (a cycle) is refused; both are audited, as adopt and unlink
279
294
  ${CLI_NAME} leave [--endpoint EP] take this session off the relay: offline, no messages pushed, mail kept until
280
295
  ${CLI_NAME} join; a spawned worker stays joined while it runs
281
296
  With setup --join manual a session stays off the relay until it runs ${CLI_NAME} join; the default, auto, joins every
@@ -347,7 +362,9 @@ var IDENTITY = ["as", "harness", "human", "json", "cloud", "machine"];
347
362
  var VERBS = {
348
363
  list: [...IDENTITY, "all", "tree", "roots", "contacts"],
349
364
  send: [...IDENTITY, "reply-to", "thread", "private", "attach", "live"],
350
- join: ["harness", "json"],
365
+ join: ["harness", "json", "under"],
366
+ adopt: [...IDENTITY, "parent"],
367
+ unlink: IDENTITY,
351
368
  leave: [...IDENTITY, "endpoint"],
352
369
  name: [...IDENTITY, "endpoint"],
353
370
  inbox: [...IDENTITY, "wait", "ack", "keep"],
@@ -449,15 +466,16 @@ function printEndpoints(endpoints, me, host = shortHostname()) {
449
466
  const row = (e, indent) => [
450
467
  `${indent}${addressCell(e, me)}`,
451
468
  e.title ?? "",
452
- e.presence,
469
+ presenceText(e),
470
+ wakeText(e),
453
471
  e.status ? statusText(e.status) : e.kind === "human" ? "" : "-",
454
472
  Object.entries(e.labels).filter(([k]) => k !== "machine").map(([k, v]) => `${k}=${v}`).join(","),
455
473
  shortCwd(e.cwd),
456
474
  ...peek ? [e.peekable ?? ""] : []
457
475
  ];
458
- const blank = (first) => [first, "", "", "", "", "", ...peek ? [""] : []];
476
+ const blank = (first) => [first, "", "", "", "", "", "", ...peek ? [""] : []];
459
477
  const rows = [
460
- ["ADDRESS", "TITLE", "PRESENCE", "STATUS", "LABELS", "CWD", ...peek ? ["PEEK"] : []],
478
+ ["ADDRESS", "TITLE", "PRESENCE", "WAKE", "STATUS", "LABELS", "CWD", ...peek ? ["PEEK"] : []],
461
479
  ...endpoints.filter((e) => e.kind === "human").map((e) => row(e, ""))
462
480
  ];
463
481
  const machines = /* @__PURE__ */ new Map();
@@ -520,13 +538,13 @@ async function relayMain(argv, cwd = process.cwd()) {
520
538
  out(package_default.version);
521
539
  return 0;
522
540
  }
523
- if (verb === "hook") return (await import("./hooks-4B5NOH7X.js")).relayHookCommand(rest, startDaemon);
524
- if (verb === "setup") return (await import("./setup-HPH7HE6O.js")).relaySetupCommand(rest, cwd, out);
525
- if (verb === "claude") return (await import("./claude-WTCPUYU3.js")).claudeCommand(rest);
526
- if (verb === "pi") return (await import("./pi-IAVFVOOO.js")).piCommand(rest);
527
- if (verb === "codex") return (await import("./codex-YZZNH6IQ.js")).codexCommand(rest);
541
+ if (verb === "hook") return (await import("./hooks-P4J6OZCO.js")).relayHookCommand(rest, startDaemon);
542
+ if (verb === "setup") return (await import("./setup-RO7356M4.js")).relaySetupCommand(rest, cwd, out);
543
+ if (verb === "claude") return (await import("./claude-JD7WXMAT.js")).claudeCommand(rest);
544
+ if (verb === "pi") return (await import("./pi-JZ4DXY7V.js")).piCommand(rest);
545
+ if (verb === "codex") return (await import("./codex-4IIM6ZE2.js")).codexCommand(rest);
528
546
  if (verb === "spawn") {
529
- const { splitSpawn, spawnCommand } = await import("./spawn-V66GYUNC.js");
547
+ const { splitSpawn, spawnCommand } = await import("./spawn-BYU2X46L.js");
530
548
  const { flags } = splitSpawn(rest);
531
549
  if (flags.includes("--help") || flags.includes("-h")) {
532
550
  out(flags.includes("--all") ? RELAY_HELP : SPAWN_HELP);
@@ -534,6 +552,7 @@ async function relayMain(argv, cwd = process.cwd()) {
534
552
  }
535
553
  return spawnCommand(rest, cwd);
536
554
  }
555
+ if (verb === "mesh") return (await import("./mesh-VNZV3POO.js")).meshCommand(rest);
537
556
  const allowed = VERBS[verb];
538
557
  if (!allowed) throw new RelayUsage(`unknown command "${verb}"`);
539
558
  let parsed;
@@ -553,11 +572,11 @@ async function relayMain(argv, cwd = process.cwd()) {
553
572
  return daemon(values.idle, values.policy);
554
573
  }
555
574
  if (verb === "mcp")
556
- return (await import("./mcp-RXAVQBMP.js")).relayMcpCommand(values, positionals);
575
+ return (await import("./mcp-TUWMLGE4.js")).relayMcpCommand(values, positionals);
557
576
  if (verb === "hooks") {
558
577
  if (positionals[0] !== "install" || positionals.length > 1) throw new RelayUsage(`${CLI_NAME} hooks takes install --harness claude|codex|opencode|agy|muse`);
559
578
  if (values.write && values.print) throw new RelayUsage("--print and --write: pick one");
560
- return (await import("./install-2ZM3IYXG.js")).installHooks({
579
+ return (await import("./install-FKGGPT6L.js")).installHooks({
561
580
  harness: values.harness,
562
581
  scope: values.scope,
563
582
  write: values.write === true,
@@ -568,26 +587,26 @@ async function relayMain(argv, cwd = process.cwd()) {
568
587
  out
569
588
  });
570
589
  }
571
- if (verb === "relay") return (await import("./relay-QDX2BNZH.js")).relayVerb(positionals, values.json === true);
572
- if (verb === "up") return (await import("./up-2YGEDA5J.js")).up(positionals, values, cwd, relinkBroker);
590
+ if (verb === "relay") return (await import("./relay-U634RHIJ.js")).relayVerb(positionals, values.json === true);
591
+ if (verb === "up") return (await import("./up-GDD24GPL.js")).up(positionals, values, cwd, relinkBroker);
573
592
  if (verb === "login") {
574
- const code = await (await import("./relay-QDX2BNZH.js")).loginMain(positionals, values);
593
+ const code = await (await import("./relay-U634RHIJ.js")).loginMain(positionals, values);
575
594
  if (code === 0) await relinkRunning();
576
595
  return code;
577
596
  }
578
- if (verb === "logout") return (await import("./relay-QDX2BNZH.js")).logoutMain(positionals, values.json === true);
597
+ if (verb === "logout") return (await import("./relay-U634RHIJ.js")).logoutMain(positionals, values.json === true);
579
598
  if (verb === "cloud") {
580
- const code = await (await import("./remote-2OWBWLJH.js")).cloudMain(positionals, values, cloudStatus);
599
+ const code = await (await import("./remote-NTHRHVEP.js")).cloudMain(positionals, values, cloudStatus);
581
600
  if (code === 0 && positionals[0] === "enable") await relinkRunning();
582
601
  return code;
583
602
  }
584
- if (verb === "join") return (await import("./join-BH5CS6SL.js")).joinCommand(values, positionals, cwd, out);
603
+ if (verb === "join") return (await import("./join-RGFHOSLE.js")).joinCommand(values, positionals, cwd, out);
585
604
  if (verb === "holds") holdsArgs(values, positionals);
586
605
  const bodyFromStdin = (verb === "send" || verb === "ask" || verb === "reply") && (positionals[1] === void 0 || positionals[1] === "-");
587
606
  const opened = await open(verb, values, cwd, bodyFromStdin);
588
607
  const { client } = opened;
589
608
  try {
590
- if (verb === "machines") return await (await import("./remote-2OWBWLJH.js")).machinesVerb(client, positionals, values.json === true);
609
+ if (verb === "machines") return await (await import("./remote-NTHRHVEP.js")).machinesVerb(client, positionals, values.json === true);
591
610
  return await runVerb(opened, verb, values, positionals, cwd);
592
611
  } finally {
593
612
  client.close();
@@ -663,7 +682,7 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
663
682
  out(`${me.address} (${me.kind}${me.harness ? `, ${me.harness}` : ""})`);
664
683
  const how = bound === "as" ? "--as" : bound === "lineage" ? "this shell's harness process" : bound === "human" ? "human inbox" : "-";
665
684
  const fields = [
666
- ["presence", me.presence],
685
+ ["presence", presenceText(me)],
667
686
  ["title", me.title ?? "-"],
668
687
  ...me.kind === "human" ? [] : [
669
688
  ["status", me.status?.state ?? "-"],
@@ -764,9 +783,9 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
764
783
  return 0;
765
784
  }
766
785
  case "peek":
767
- return (await import("./peek-M2HQVO4O.js")).peekCommand(localClient(client, broker, verb), values, positionals);
786
+ return (await import("./peek-4D4VGKOW.js")).peekCommand(localClient(client, broker, verb), values, positionals);
768
787
  case "set-peek":
769
- return (await import("./peek-M2HQVO4O.js")).setPeekCommand(localClient(client, broker, verb), values, positionals);
788
+ return (await import("./peek-4D4VGKOW.js")).setPeekCommand(localClient(client, broker, verb), values, positionals);
770
789
  case "why": {
771
790
  const [from, to] = positionals;
772
791
  if (!from || !to || positionals.length > 2) throw new RelayUsage(`${CLI_NAME} why takes <from> <to>`);
@@ -983,6 +1002,26 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
983
1002
  else out(before && before !== view.address ? `${before} is now ${view.address}` : `${view.address}`);
984
1003
  return 0;
985
1004
  }
1005
+ case "adopt": {
1006
+ if (positionals.length !== 1) throw new RelayUsage(`${CLI_NAME} adopt takes the child to link: ${CLI_NAME} adopt <child> [--parent <p>]`);
1007
+ const parent = values.parent;
1008
+ if (parent === void 0 && me?.kind !== "agent")
1009
+ throw new RelayUsage(`${CLI_NAME} adopt: name the parent with --parent <p> (run from an agent's shell, it defaults to that agent)`);
1010
+ const result = await client.request("adopt", {
1011
+ endpoint: positionals[0],
1012
+ ...parent !== void 0 ? { parent } : {}
1013
+ });
1014
+ if (asJson) json(result);
1015
+ else out(`${result.endpoint.address} now sits under ${result.parent}`);
1016
+ return 0;
1017
+ }
1018
+ case "unlink": {
1019
+ if (positionals.length > 1) throw new RelayUsage(`${CLI_NAME} unlink takes one child, or none for this session`);
1020
+ const result = await client.request("unlink", positionals.length ? { endpoint: positionals[0] } : {});
1021
+ if (asJson) json(result);
1022
+ else out(`${result.endpoint.address} is a root again (was under ${result.parent})`);
1023
+ return 0;
1024
+ }
986
1025
  case "leave": {
987
1026
  if (positionals.length) throw new RelayUsage(`${CLI_NAME} leave takes no arguments (--endpoint EP for the human at a terminal)`);
988
1027
  const endpoint = values.endpoint;
@@ -1187,7 +1226,7 @@ function printTree(endpoints, me) {
1187
1226
  if (e.parent && byAddress.has(e.parent) && e.parent !== e.address) children.set(e.parent, [...children.get(e.parent) ?? [], e]);
1188
1227
  else roots.push(e);
1189
1228
  }
1190
- const rows = [["ADDRESS", "SCOPE", "TITLE", "PRESENCE", "STATUS", "LABELS", "CWD"]];
1229
+ const rows = [["ADDRESS", "SCOPE", "TITLE", "PRESENCE", "WAKE", "STATUS", "LABELS", "CWD"]];
1191
1230
  const seen = /* @__PURE__ */ new Set();
1192
1231
  const walk = (e, depth) => {
1193
1232
  if (seen.has(e.address)) return;
@@ -1197,7 +1236,8 @@ function printTree(endpoints, me) {
1197
1236
  `${" ".repeat(depth)}${addressCell(e, me)}`,
1198
1237
  e.kind === "human" ? "" : e.scope ?? "open",
1199
1238
  e.title ?? "",
1200
- e.presence,
1239
+ presenceText(e),
1240
+ wakeText(e),
1201
1241
  e.status ? statusText(e.status) : e.kind === "human" ? "" : "-",
1202
1242
  labels,
1203
1243
  shortCwd(e.cwd)
@@ -1211,7 +1251,7 @@ var STATES = ["working", "waiting", "blocked", "done"];
1211
1251
  function printSummary(snapshot, me, all) {
1212
1252
  if (me)
1213
1253
  out(
1214
- `you: ${me.address} (${me.kind}${me.harness ? `, ${me.harness}` : ""}) ${me.presence}${me.title ? ` ${quoted(me.title)}` : ""}${me.kind === "human" ? "" : `, status ${me.status ? statusText(me.status) : "-"}`}`
1254
+ `you: ${me.address} (${me.kind}${me.harness ? `, ${me.harness}` : ""}) ${presenceText(me)}${me.title ? ` ${quoted(me.title)}` : ""}${me.kind === "human" ? "" : `, status ${me.status ? statusText(me.status) : "-"}`}`
1215
1255
  );
1216
1256
  const agents = snapshot.endpoints.filter((e) => e.kind !== "human");
1217
1257
  const count = (presence) => agents.filter((e) => e.presence === presence).length;
@@ -1343,11 +1383,11 @@ async function daemon(idle, policyFile) {
1343
1383
  });
1344
1384
  let broker;
1345
1385
  try {
1346
- const { daemonBridge } = await import("./remote-2OWBWLJH.js");
1386
+ const { daemonBridge } = await import("./remote-NTHRHVEP.js");
1347
1387
  const bridge = await daemonBridge();
1348
1388
  broker = await startBroker({
1349
1389
  dir: relayDir(),
1350
- ...bridge ? { bridge } : {},
1390
+ ...bridge ? { bridge } : await (await import("./mesh-VNZV3POO.js")).meshBrokerOptions(),
1351
1391
  bridgeConfig: () => daemonBridge(),
1352
1392
  ...idleMs !== void 0 ? { idleMs } : {},
1353
1393
  ...policyFile ? { policyPath: resolve(policyFile) } : {}
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  spawnDetached
3
- } from "./chunk-25ZVL5LD.js";
3
+ } from "./chunk-IXCHAWOL.js";
4
4
  import "./chunk-KN7WEIE4.js";
5
- import "./chunk-NJLFHBSS.js";
5
+ import "./chunk-W3VNCRBX.js";
6
6
  import {
7
7
  mcpInstall
8
- } from "./chunk-KC5AV5NQ.js";
9
- import "./chunk-HF4KP6AR.js";
8
+ } from "./chunk-ZMVFN6MU.js";
9
+ import "./chunk-X4ZLEM47.js";
10
10
  import "./chunk-YYSLYVNZ.js";
11
11
  import {
12
12
  CHANNELS_ENV,
@@ -16,7 +16,7 @@ import {
16
16
  psParent,
17
17
  relayIdentity,
18
18
  startDaemon
19
- } from "./chunk-ZLL5EGI2.js";
19
+ } from "./chunk-E7JG5PJB.js";
20
20
  import "./chunk-MNTIEWKF.js";
21
21
 
22
22
  // src/mcp.ts
@@ -0,0 +1,28 @@
1
+ import {
2
+ RelayUsage
3
+ } from "./chunk-E7JG5PJB.js";
4
+ import "./chunk-MNTIEWKF.js";
5
+
6
+ // src/mesh.ts
7
+ import { existsSync } from "fs";
8
+ import { join } from "path";
9
+ import { relayDir } from "@scp-dev/broker";
10
+ var MESH_MODULE = "@scp-dev/mesh";
11
+ async function load() {
12
+ try {
13
+ return await import(MESH_MODULE);
14
+ } catch {
15
+ throw new RelayUsage("swarm mesh is experimental and runs only from a source checkout of the swarm CLI");
16
+ }
17
+ }
18
+ async function meshCommand(argv) {
19
+ return (await load()).meshCommand(argv);
20
+ }
21
+ async function meshBrokerOptions() {
22
+ if (!existsSync(join(relayDir(), "mesh", "mesh.json"))) return {};
23
+ return (await load()).meshBrokerOptions();
24
+ }
25
+ export {
26
+ meshBrokerOptions,
27
+ meshCommand
28
+ };
@@ -2,7 +2,7 @@ import {
2
2
  RelayUsage,
3
3
  peekGrant,
4
4
  stamp
5
- } from "./chunk-ZLL5EGI2.js";
5
+ } from "./chunk-E7JG5PJB.js";
6
6
  import {
7
7
  note
8
8
  } from "./chunk-MNTIEWKF.js";
@@ -1,14 +1,15 @@
1
1
  import {
2
2
  RelayUsage
3
- } from "./chunk-ZLL5EGI2.js";
3
+ } from "./chunk-E7JG5PJB.js";
4
4
  import "./chunk-MNTIEWKF.js";
5
5
 
6
6
  // src/pi.ts
7
7
  import { spawn } from "child_process";
8
+ import { existsSync } from "fs";
8
9
  import { fileURLToPath } from "url";
9
10
  import { CLI_NAME, validHandle } from "@scp-dev/core";
10
11
  var PI_HELP = `${CLI_NAME} pi [--name NAME | --as NAME] [--] [pi args...]
11
- runs pi with the @scp-dev/pi extension: the relay tools, and each new message as a turn, waking the session when idle.
12
+ runs pi with the bundled SCP extension: the relay tools, and each new message as a turn, waking the session when idle.
12
13
  $SCP_PI_BIN picks the pi binary (default pi on PATH), $SCP_PI_EXTENSION the extension file.`;
13
14
  function piLaunch(args, env, extension) {
14
15
  let name;
@@ -32,13 +33,13 @@ function piLaunch(args, env, extension) {
32
33
  env: { ...env, SCP_HARNESS: "pi", ...name !== void 0 ? { SCP_NAME: name } : {} }
33
34
  };
34
35
  }
35
- function extensionPath(env) {
36
+ function extensionPath(env, base = import.meta.url) {
36
37
  if (env.SCP_PI_EXTENSION) return env.SCP_PI_EXTENSION;
37
- try {
38
- return fileURLToPath(import.meta.resolve("@scp-dev/pi"));
39
- } catch {
40
- throw new RelayUsage(`${CLI_NAME} pi needs the @scp-dev/pi extension: install it next to ${CLI_NAME}, or set $SCP_PI_EXTENSION to its file`);
38
+ for (const candidate of ["./pi-extension.js", "../../pi/src/index.ts"]) {
39
+ const file = fileURLToPath(new URL(candidate, base));
40
+ if (existsSync(file)) return file;
41
41
  }
42
+ throw new RelayUsage(`${CLI_NAME} pi found no pi extension next to ${CLI_NAME}: reinstall it, or set $SCP_PI_EXTENSION to the extension file`);
42
43
  }
43
44
  async function piCommand(args) {
44
45
  const own = args.includes("--") ? args.slice(0, args.indexOf("--")) : args;
@@ -66,6 +67,7 @@ async function piCommand(args) {
66
67
  }
67
68
  export {
68
69
  PI_HELP,
70
+ extensionPath,
69
71
  piCommand,
70
72
  piLaunch
71
73
  };
@@ -0,0 +1,137 @@
1
+ // ../pi/src/index.ts
2
+ import { spawn } from "child_process";
3
+ import { createInterface } from "readline";
4
+ var PROTOCOL = "2025-06-18";
5
+ var CHANNEL = "notifications/claude/channel";
6
+ function swarmWords(env) {
7
+ const raw = env.SCP_PI_SWARM;
8
+ if (!raw) return ["swarm"];
9
+ const words = JSON.parse(raw);
10
+ if (!Array.isArray(words) || !words.length || !words.every((word) => typeof word === "string"))
11
+ throw new Error("SCP_PI_SWARM must be a JSON array of strings");
12
+ return words;
13
+ }
14
+ function shimArgv(env) {
15
+ return [...swarmWords(env), "mcp", "--harness", "pi", "--channels", ...env.SCP_NAME ? ["--as", env.SCP_NAME] : []];
16
+ }
17
+ var McpLink = class {
18
+ child;
19
+ pending = /* @__PURE__ */ new Map();
20
+ next = 1;
21
+ closed = false;
22
+ constructor(argv, env, onPush, onExit) {
23
+ this.child = spawn(argv[0], argv.slice(1), { env, stdio: ["pipe", "pipe", "ignore"] });
24
+ this.child.stdin.on("error", () => {
25
+ });
26
+ createInterface({ input: this.child.stdout }).on("line", (line) => {
27
+ let message;
28
+ try {
29
+ message = JSON.parse(line);
30
+ } catch {
31
+ return;
32
+ }
33
+ if (typeof message.id === "number" && this.pending.has(message.id)) {
34
+ const waiter = this.pending.get(message.id);
35
+ this.pending.delete(message.id);
36
+ const error = message.error;
37
+ if (error) waiter.reject(new Error(error.message ?? "mcp error"));
38
+ else waiter.resolve(message.result ?? {});
39
+ return;
40
+ }
41
+ if (message.method === CHANNEL) {
42
+ const params = message.params ?? {};
43
+ if (typeof params.content === "string") onPush(params.content, params.meta ?? {});
44
+ }
45
+ });
46
+ this.child.once("error", () => this.fail(null, onExit));
47
+ this.child.once("exit", (code) => this.fail(code, onExit));
48
+ }
49
+ fail(code, onExit) {
50
+ if (this.closed) return;
51
+ this.closed = true;
52
+ for (const waiter of this.pending.values()) waiter.reject(new Error("the scp shim exited"));
53
+ this.pending.clear();
54
+ onExit(code);
55
+ }
56
+ request(method, params = {}) {
57
+ if (this.closed) return Promise.reject(new Error("the scp shim exited"));
58
+ const id = this.next++;
59
+ return new Promise((resolve, reject) => {
60
+ this.pending.set(id, { resolve, reject });
61
+ this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
62
+ `);
63
+ });
64
+ }
65
+ notify(method, params = {}) {
66
+ if (!this.closed) this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
67
+ `);
68
+ }
69
+ close() {
70
+ if (this.closed) return;
71
+ this.closed = true;
72
+ this.child.stdin.end();
73
+ this.child.kill();
74
+ }
75
+ };
76
+ function toolResult(result) {
77
+ const content = (result.content ?? []).flatMap(
78
+ (part) => part.type === "text" && typeof part.text === "string" ? [{ type: "text", text: part.text }] : []
79
+ );
80
+ if (result.isError) throw new Error(content.map((part) => part.text).join("\n") || "the relay tool failed");
81
+ return { content, details: {} };
82
+ }
83
+ function scp(pi) {
84
+ let link;
85
+ const warn = (ctx, text) => {
86
+ if (ctx.hasUI) ctx.ui.notify(text, "warning");
87
+ };
88
+ pi.on("session_start", async (_event, ctx) => {
89
+ if (process.env.SCP_PI === "0") return;
90
+ link?.close();
91
+ const current = new McpLink(
92
+ shimArgv(process.env),
93
+ process.env,
94
+ (content, meta) => {
95
+ pi.sendMessage({ customType: "scp", content, display: true, details: meta }, { triggerTurn: true, deliverAs: "steer" });
96
+ },
97
+ (code) => {
98
+ if (link === current) warn(ctx, `scp: the relay shim exited (${code ?? "error"}); relay tools are unavailable`);
99
+ }
100
+ );
101
+ link = current;
102
+ try {
103
+ await current.request("initialize", {
104
+ protocolVersion: PROTOCOL,
105
+ capabilities: { experimental: { "claude/channel": {} } },
106
+ clientInfo: { name: "pi", version: "1" }
107
+ });
108
+ const { tools } = await current.request("tools/list");
109
+ for (const tool of tools ?? []) {
110
+ pi.registerTool({
111
+ name: tool.name,
112
+ label: tool.name,
113
+ description: tool.description ?? tool.name,
114
+ parameters: tool.inputSchema ?? { type: "object", properties: {} },
115
+ async execute(_id, params) {
116
+ if (link !== current) throw new Error("this scp session ended");
117
+ return toolResult(await current.request("tools/call", { name: tool.name, arguments: params ?? {} }));
118
+ }
119
+ });
120
+ }
121
+ current.notify("notifications/initialized");
122
+ } catch (error) {
123
+ warn(ctx, `scp: could not start the relay shim: ${error instanceof Error ? error.message : String(error)}`);
124
+ }
125
+ });
126
+ pi.on("session_shutdown", async () => {
127
+ link?.close();
128
+ link = void 0;
129
+ });
130
+ }
131
+ export {
132
+ McpLink,
133
+ scp as default,
134
+ shimArgv,
135
+ swarmWords,
136
+ toolResult
137
+ };
@@ -13,7 +13,7 @@ import {
13
13
  saveCredential,
14
14
  setJoinMode,
15
15
  writePrivateJson
16
- } from "./chunk-ZLL5EGI2.js";
16
+ } from "./chunk-E7JG5PJB.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-MXQ7B3CQ.js";
13
- import "./chunk-ZLL5EGI2.js";
12
+ } from "./chunk-ZDU53ZRF.js";
13
+ import "./chunk-E7JG5PJB.js";
14
14
  import "./chunk-MNTIEWKF.js";
15
15
  export {
16
16
  CloudRelayClient,
@@ -15,11 +15,11 @@ import {
15
15
  verifyMount,
16
16
  verifyMusePlugin,
17
17
  whichOnPath
18
- } from "./chunk-NJLFHBSS.js";
19
- import "./chunk-KC5AV5NQ.js";
20
- import "./chunk-HF4KP6AR.js";
18
+ } from "./chunk-W3VNCRBX.js";
19
+ import "./chunk-ZMVFN6MU.js";
20
+ import "./chunk-X4ZLEM47.js";
21
21
  import "./chunk-YYSLYVNZ.js";
22
- import "./chunk-ZLL5EGI2.js";
22
+ import "./chunk-E7JG5PJB.js";
23
23
  import "./chunk-MNTIEWKF.js";
24
24
  export {
25
25
  SETUP_HARNESSES,
@@ -29,15 +29,15 @@ import {
29
29
  steerArgv,
30
30
  transcriptReplies,
31
31
  workerName
32
- } from "./chunk-25ZVL5LD.js";
32
+ } from "./chunk-IXCHAWOL.js";
33
33
  import {
34
34
  SPAWN_HELP
35
35
  } from "./chunk-KN7WEIE4.js";
36
- import "./chunk-NJLFHBSS.js";
37
- import "./chunk-KC5AV5NQ.js";
38
- import "./chunk-HF4KP6AR.js";
36
+ import "./chunk-W3VNCRBX.js";
37
+ import "./chunk-ZMVFN6MU.js";
38
+ import "./chunk-X4ZLEM47.js";
39
39
  import "./chunk-YYSLYVNZ.js";
40
- import "./chunk-ZLL5EGI2.js";
40
+ import "./chunk-E7JG5PJB.js";
41
41
  import "./chunk-MNTIEWKF.js";
42
42
  export {
43
43
  MESSAGING,
@@ -2,7 +2,7 @@ import {
2
2
  RelayUsage,
3
3
  activeRelay,
4
4
  relayCredential
5
- } from "./chunk-ZLL5EGI2.js";
5
+ } from "./chunk-E7JG5PJB.js";
6
6
  import "./chunk-MNTIEWKF.js";
7
7
 
8
8
  // src/up.ts
@@ -14,16 +14,16 @@ async function up(positionals, values, cwd, brokerStatus) {
14
14
  const { url } = activeRelay(process.env);
15
15
  const headless = values.token !== void 0 || Boolean(process.env.SCP_TOKEN?.trim());
16
16
  if (headless || !relayCredential(process.env, url).token) {
17
- const code = await (await import("./login-UB72MU7W.js")).login({
17
+ const code = await (await import("./login-IPS75GLQ.js")).login({
18
18
  ...values.token !== void 0 ? { token: values.token } : {},
19
19
  ...values.label !== void 0 ? { label: values.label } : {},
20
20
  ...values["no-browser"] ? { "no-browser": true } : {}
21
21
  });
22
22
  if (code !== 0) return code;
23
23
  } else out(`already logged in to ${url}`);
24
- const enabled = await (await import("./remote-2OWBWLJH.js")).cloudMain(["enable"], values.machine !== void 0 ? { machine: values.machine } : {}, brokerStatus);
24
+ const enabled = await (await import("./remote-NTHRHVEP.js")).cloudMain(["enable"], values.machine !== void 0 ? { machine: values.machine } : {}, brokerStatus);
25
25
  if (enabled !== 0) return enabled;
26
- const setup = (await import("./setup-HPH7HE6O.js")).relaySetupCommand(["--write"], cwd, out);
26
+ const setup = (await import("./setup-RO7356M4.js")).relaySetupCommand(["--write"], cwd, out);
27
27
  if (setup !== 0) return setup;
28
28
  const broker = await brokerStatus();
29
29
  if (!broker.enabled) out(`the broker is still local-only; run \`${CLI_NAME} daemon restart\` to link it to ${url}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scp-dev/cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "The swarm command line tool.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://scp.dev",
@@ -24,14 +24,15 @@
24
24
  "typecheck": "tsc -p ."
25
25
  },
26
26
  "dependencies": {
27
- "@scp-dev/core": "0.1.2",
28
- "@scp-dev/broker": "0.1.3",
29
- "@scp-dev/hub": "0.2.0",
30
- "@scp-dev/mcp": "0.1.3",
27
+ "@scp-dev/core": "0.1.4",
28
+ "@scp-dev/broker": "0.1.5",
29
+ "@scp-dev/hub": "0.3.1",
30
+ "@scp-dev/mcp": "0.1.5",
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.2",
35
36
  "opencode-ai": "1.18.34",
36
37
  "tsup": "^8.5.1"
37
38
  },