@scp-dev/cli 0.2.3 → 0.3.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.
@@ -1,18 +1,20 @@
1
1
  import {
2
+ CODEX_VALUED,
2
3
  codexMount,
3
- codexOverrides
4
- } from "./chunk-IXCHAWOL.js";
4
+ codexOverrides,
5
+ toml
6
+ } from "./chunk-3SHVJIXP.js";
5
7
  import "./chunk-KN7WEIE4.js";
6
- import "./chunk-W3VNCRBX.js";
7
- import "./chunk-ZMVFN6MU.js";
8
- import "./chunk-X4ZLEM47.js";
8
+ import "./chunk-2OSUVJ2J.js";
9
+ import "./chunk-Q5UI4Z7H.js";
10
+ import "./chunk-QRWQIHV6.js";
9
11
  import "./chunk-YYSLYVNZ.js";
10
12
  import {
11
13
  RelayUsage,
12
14
  readPsTable,
13
15
  relayIdentity,
14
16
  startDaemon
15
- } from "./chunk-E7JG5PJB.js";
17
+ } from "./chunk-2LIJXXTL.js";
16
18
  import {
17
19
  SELF
18
20
  } from "./chunk-MNTIEWKF.js";
@@ -20,27 +22,30 @@ import {
20
22
  // src/codex.ts
21
23
  import { spawn } from "child_process";
22
24
  import { createHash, randomBytes } from "crypto";
23
- import { mkdtempSync, rmSync } from "fs";
25
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from "fs";
24
26
  import { connect } from "net";
25
- import { tmpdir } from "os";
27
+ import { homedir, tmpdir } from "os";
26
28
  import { join } from "path";
27
29
  import { PassThrough } from "stream";
28
30
  import { setTimeout as sleep } from "timers/promises";
29
31
  import { connectBroker, relayDir } from "@scp-dev/broker";
30
32
  import { CLI_NAME, validHandle } from "@scp-dev/core";
31
33
  import { MCP_PROTOCOLS, runRelayMcp } from "@scp-dev/mcp";
34
+ import { parse } from "smol-toml";
32
35
  var CODEX_HELP = `${CLI_NAME} codex [--name N | --as N] [--headless] [--resume THREAD] [--] [codex args...]
36
+ every codex argument, profile and config value is honoured; a -- after codex arguments is passed through.
33
37
  runs codex with relay messages waking an idle thread or steering a running turn; approvals stay with the TUI.
34
38
  --headless runs an ambient worker without a TUI; --resume THREAD reopens its thread.
35
39
  $SCP_CODEX_BIN picks the codex binary (default codex on PATH).`;
36
40
  function codexLaunch(args, env, _self) {
37
41
  let name;
42
+ let resume;
38
43
  let headless = false;
39
44
  const codexArgs = [];
40
45
  for (let i = 0; i < args.length; i++) {
41
46
  const arg = args[i];
42
47
  if (arg === "--") {
43
- codexArgs.push(...args.slice(i + 1));
48
+ codexArgs.push(...args.slice(codexArgs.length ? i : i + 1));
44
49
  break;
45
50
  }
46
51
  if (arg === "--name" || arg === "--as") {
@@ -48,6 +53,10 @@ function codexLaunch(args, env, _self) {
48
53
  if (name === void 0) throw new RelayUsage(`${CLI_NAME} codex ${arg} takes a name`);
49
54
  } else if (arg.startsWith("--name=") || arg.startsWith("--as=")) name = arg.slice(arg.indexOf("=") + 1);
50
55
  else if (arg === "--headless") headless = true;
56
+ else if (arg === "--resume") {
57
+ resume = args[++i];
58
+ if (resume === void 0) throw new RelayUsage(`${CLI_NAME} codex --resume takes a thread`);
59
+ } else if (arg.startsWith("--resume=")) resume = arg.slice("--resume=".length);
51
60
  else codexArgs.push(arg);
52
61
  }
53
62
  name = name?.replace(/^me\//, "");
@@ -56,7 +65,8 @@ function codexLaunch(args, env, _self) {
56
65
  return {
57
66
  ...name !== void 0 ? { name } : {},
58
67
  headless,
59
- codexArgs,
68
+ ...resume !== void 0 ? { resume } : {},
69
+ codexArgs: resume !== void 0 && !headless ? ["resume", resume, ...codexArgs] : codexArgs,
60
70
  env: { ...clean, SCP_HARNESS: "codex", ...name !== void 0 ? { SCP_NAME: name } : {} }
61
71
  };
62
72
  }
@@ -314,18 +324,57 @@ function option(args, ...names) {
314
324
  }
315
325
  return value;
316
326
  }
317
- function serverOptions(args) {
318
- const result = [];
319
- const names = ["-c", "--config", "--enable", "--disable"];
327
+ var SERVER_CONFIG = ["-c", "--config", "--enable", "--disable"];
328
+ var SERVER_SETTINGS = {
329
+ "-m": "model",
330
+ "--model": "model",
331
+ "-s": "sandbox_mode",
332
+ "--sandbox": "sandbox_mode",
333
+ "-a": "approval_policy",
334
+ "--ask-for-approval": "approval_policy"
335
+ };
336
+ function leaves(value, path, out) {
337
+ if (value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
338
+ for (const [key, child] of Object.entries(value)) leaves(child, [...path, key], out);
339
+ } else if (path.length) out.push("-c", `${path.join(".")}=${toml(value instanceof Date ? value.toISOString() : value)}`);
340
+ }
341
+ function serverOptions(args, env, warn = (line) => void process.stderr.write(`${line}
342
+ `)) {
343
+ const profile = [];
344
+ const config = [];
345
+ const settings = [];
346
+ const set = (key, value) => settings.push("-c", `${key}=${toml(value)}`);
347
+ let conflict = false;
320
348
  for (let i = 0; i < args.length; i++) {
321
349
  const arg = args[i];
322
- if (names.includes(arg)) {
323
- const value = args[++i];
324
- if (value === void 0) throw new RelayUsage(`${CLI_NAME} codex ${arg} takes a value`);
325
- result.push(arg, value);
326
- } else if (names.some((name) => arg.startsWith(`${name}=`))) result.push(arg);
350
+ if (arg === "--") break;
351
+ const eq = arg.startsWith("--") ? arg.indexOf("=") : -1;
352
+ const flag = eq > 0 ? arg.slice(0, eq) : arg;
353
+ if (flag === "--strict-config") config.push(flag);
354
+ else if (flag === "--dangerously-bypass-approvals-and-sandbox" || flag === "--yolo") {
355
+ set("approval_policy", "never");
356
+ set("sandbox_mode", "danger-full-access");
357
+ }
358
+ if (!CODEX_VALUED.has(flag)) continue;
359
+ const value = eq > 0 ? arg.slice(eq + 1) : args[++i];
360
+ if (value === void 0) throw new RelayUsage(`${CLI_NAME} codex ${flag} takes a value`);
361
+ if (SERVER_CONFIG.includes(flag)) {
362
+ config.push(flag, value);
363
+ if (/^mcp_servers\.("scp"|scp)\./.test(value)) conflict = true;
364
+ } else if (flag === "-p" || flag === "--profile") {
365
+ const file = join(env.CODEX_HOME || join(env.HOME || homedir(), ".codex"), `${value}.config.toml`);
366
+ profile.length = 0;
367
+ if (existsSync(file)) {
368
+ try {
369
+ leaves(parse(readFileSync(file, "utf8")), [], profile);
370
+ } catch (error) {
371
+ warn(`${CLI_NAME} codex: could not read profile ${file}: ${error instanceof Error ? error.message : String(error)}`);
372
+ }
373
+ }
374
+ } else if (SERVER_SETTINGS[flag]) set(SERVER_SETTINGS[flag], value);
327
375
  }
328
- return result;
376
+ if (conflict) warn(`${CLI_NAME} codex: your -c mcp_servers.scp settings override the ones ${CLI_NAME} sets, so messages may not wake this session`);
377
+ return [...profile, ...config, ...settings];
329
378
  }
330
379
  async function terminate(child) {
331
380
  if (child.exitCode !== null || child.signalCode !== null || !child.pid) return;
@@ -350,8 +399,7 @@ function appServerPid(pid) {
350
399
  return pid;
351
400
  }
352
401
  async function codexCommand(args) {
353
- const own = args.includes("--") ? args.slice(0, args.indexOf("--")) : args;
354
- if (own.includes("--help") || own.includes("-h")) {
402
+ if (args[0] === "--help" || args[0] === "-h") {
355
403
  process.stdout.write(`${CODEX_HELP}
356
404
  `);
357
405
  return 0;
@@ -359,7 +407,7 @@ async function codexCommand(args) {
359
407
  const launch = codexLaunch(args, process.env, SELF);
360
408
  const cwd = process.cwd();
361
409
  const bin = launch.env.SCP_CODEX_BIN || "codex";
362
- const overrides = [...codexOverrides(launch.env, codexMount(launch.env), SELF), ...serverOptions(launch.codexArgs)];
410
+ const overrides = [...codexOverrides(launch.env, codexMount(launch.env), SELF), ...serverOptions(launch.codexArgs, launch.env)];
363
411
  const dir = mkdtempSync(join(tmpdir(), "scp-codex-"));
364
412
  const path = join(dir, "app.sock");
365
413
  const server = spawn(bin, ["app-server", "--listen", `unix://${path}`, ...overrides], { env: launch.env, stdio: ["ignore", "ignore", "pipe"] });
@@ -448,7 +496,7 @@ ${stderr.toString().trim()}` : ""}`
448
496
  if (!socket || died) throw failure();
449
497
  let threadId;
450
498
  if (launch.headless) {
451
- const resume = option(own, "--resume");
499
+ const resume = launch.resume;
452
500
  const model = option(launch.codexArgs, "-m", "--model");
453
501
  const sandbox = option(launch.codexArgs, "-s", "--sandbox");
454
502
  const result = await socket.request(resume ? "thread/resume" : "thread/start", {
@@ -562,5 +610,6 @@ export {
562
610
  CodexSocket,
563
611
  CodexWaker,
564
612
  codexCommand,
565
- codexLaunch
613
+ codexLaunch,
614
+ serverOptions
566
615
  };
@@ -2,6 +2,7 @@ import {
2
2
  AGY_HOOK_SET,
3
3
  AGY_UNVERIFIED,
4
4
  BLOCK_STOP,
5
+ CLAUDE_CAPS,
5
6
  DIRECTIVE_CLOSE,
6
7
  HOOK_COMMAND,
7
8
  NO_INJECT,
@@ -15,6 +16,7 @@ import {
15
16
  WAKE,
16
17
  agyHookSet,
17
18
  claudeVersionFile,
19
+ fitText,
18
20
  hasOurHooks,
19
21
  hookEntries,
20
22
  hookReply,
@@ -38,16 +40,17 @@ import {
38
40
  tldrEvery,
39
41
  transcriptTitle,
40
42
  unreadPointer
41
- } from "./chunk-X4ZLEM47.js";
43
+ } from "./chunk-QRWQIHV6.js";
42
44
  import {
43
45
  SUBAGENT
44
46
  } from "./chunk-YYSLYVNZ.js";
45
- import "./chunk-E7JG5PJB.js";
47
+ import "./chunk-2LIJXXTL.js";
46
48
  import "./chunk-MNTIEWKF.js";
47
49
  export {
48
50
  AGY_HOOK_SET,
49
51
  AGY_UNVERIFIED,
50
52
  BLOCK_STOP,
53
+ CLAUDE_CAPS,
51
54
  DIRECTIVE_CLOSE,
52
55
  HOOK_COMMAND,
53
56
  NO_INJECT,
@@ -62,6 +65,7 @@ export {
62
65
  WAKE,
63
66
  agyHookSet,
64
67
  claudeVersionFile,
68
+ fitText,
65
69
  hasOurHooks,
66
70
  hookEntries,
67
71
  hookReply,
@@ -2,10 +2,10 @@ import {
2
2
  hooksTarget,
3
3
  installHooks,
4
4
  mcpInstall
5
- } from "./chunk-ZMVFN6MU.js";
6
- import "./chunk-X4ZLEM47.js";
5
+ } from "./chunk-Q5UI4Z7H.js";
6
+ import "./chunk-QRWQIHV6.js";
7
7
  import "./chunk-YYSLYVNZ.js";
8
- import "./chunk-E7JG5PJB.js";
8
+ import "./chunk-2LIJXXTL.js";
9
9
  import "./chunk-MNTIEWKF.js";
10
10
  export {
11
11
  hooksTarget,
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  RelayUsage,
3
+ harnessAt,
3
4
  localHandle,
4
5
  relayIdentity,
5
6
  sessionKey,
6
7
  startDaemon,
7
8
  walkNow
8
- } from "./chunk-E7JG5PJB.js";
9
+ } from "./chunk-2LIJXXTL.js";
9
10
  import "./chunk-MNTIEWKF.js";
10
11
 
11
12
  // src/join.ts
@@ -27,6 +28,7 @@ async function joinCommand(values, positionals, cwd, out) {
27
28
  cwd,
28
29
  env: process.env,
29
30
  harnessPid: () => Number(match[1]),
31
+ inferHarness: (pid) => harnessAt(pid),
30
32
  startOf: () => match[2],
31
33
  lineageOf: () => keys.slice(Math.max(0, keys.indexOf(key)))
32
34
  });
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  machineName
3
- } from "./chunk-ZDU53ZRF.js";
3
+ } from "./chunk-IEFXYBX7.js";
4
4
  import {
5
5
  RelayUsage,
6
6
  activeRelay,
7
7
  saveCredential
8
- } from "./chunk-E7JG5PJB.js";
8
+ } from "./chunk-2LIJXXTL.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-E7JG5PJB.js";
18
+ } from "./chunk-2LIJXXTL.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.3",
36
+ version: "0.3.0",
37
37
  description: "The swarm command line tool.",
38
38
  license: "Apache-2.0",
39
39
  homepage: "https://scp.dev",
@@ -137,6 +137,8 @@ usage:
137
137
  ${CLI_NAME} inbox [--wait S] [--keep] your unread messages, then marked read (--keep leaves them unread); --wait waits up
138
138
  to S seconds (max 50)
139
139
  ${CLI_NAME} ack <id|seq>... | --all mark messages read, by message id or seq; --all marks every unread one
140
+ ${CLI_NAME} show <id> one message delivered to you, whole, read or not; it stays as it is (a hook
141
+ shows a long message cut, ending in [truncated, N more chars: ${CLI_NAME} show <id>])
140
142
  An <id> is a message id, its short id (the last 6 characters, shown with every message) or any unique suffix of at least 4,
141
143
  or last: the newest message you were shown, refused while a newer one is unseen.
142
144
  ${CLI_NAME} ask <to> [text|-] [--wait S] send a request and wait for its reply (default 50 s); --require-reply holds
@@ -236,6 +238,9 @@ scopes (selectors are comma-separated, e.g. tree:parent,tree:children,group:team
236
238
  message as a turn (steering a running one, waking an idle session)
237
239
  ${CLI_NAME} codex [--name N] [--headless] [--] [codex args...]
238
240
  runs codex with relay messages waking an idle thread or steering a running turn
241
+ ${CLI_NAME} opencode [--name N] [--] [opencode args...]
242
+ runs OpenCode with your own config and arguments, wake on: the plugin wakes an
243
+ idle session, or opens one; refuses to start without the current plugin
239
244
  ${CLI_NAME} peers [--all] [--roots] who you may message: title, status and the rule that admits each; offline ones and
240
245
  sessions nobody spawned that spawned nobody only with --all (--roots: those sessions)
241
246
  ${CLI_NAME} why <from> <to> whether from may message to, and why
@@ -268,7 +273,7 @@ agent sessions:
268
273
  A Claude Code subagent's hooks give it a child endpoint, <session handle>-<short type>-<first 4 of its id>, whose messages
269
274
  reach it after its tools; its shell is the session's, so it acts as that child with --as.
270
275
  Grok runs the Claude Code hooks, labelled grok; Muse takes them as a plugin; Codex runs new hooks after /hooks review.
271
- ${CLI_NAME} claude [--name N] [--] [claude args...] start Claude Code with the scp channel loaded, so peer messages wake
276
+ ${CLI_NAME} claude [--name N | --as N] [--] [claude args...] start Claude Code with the scp channel loaded, so peer messages wake
272
277
  it while idle (claude --dangerously-load-development-channels server:scp; SCP_NAME=N)
273
278
  --channels pushes messages into a Claude Code that ${CLI_NAME} claude started; setup mounts claude's shim with it.
274
279
  Without --name, ${CLI_NAME} claude asks the session to pick its own name with ${CLI_NAME} name <name>.
@@ -299,7 +304,8 @@ agent sessions:
299
304
  relay:
300
305
  ${CLI_NAME} relay use <url> | show select a relay, or show its source and credential status
301
306
  ${CLI_NAME} login [--no-browser] [--token T] [--label NAME] sign in through the browser, or save a token checked with the relay
302
- ${CLI_NAME} up [--token T] [--machine NAME] [--no-browser] log in if needed, link the broker to the hub, run setup --write
307
+ ${CLI_NAME} up [--token T] [--machine NAME] [--no-browser] log in if needed, link the broker to the hub, run setup --write,
308
+ then print the wake-enabled launch of each installed harness
303
309
  ${CLI_NAME} logout remove the stored credential for the active relay
304
310
  ${CLI_NAME} cloud enable [--machine NAME] | disable | status link the local broker to the hub, or inspect its link
305
311
  ${CLI_NAME} machines [rm NAME] list the hub's machines, or release one
@@ -308,8 +314,8 @@ relay:
308
314
 
309
315
  identity:
310
316
  --as HANDLE (or --as=HANDLE, anywhere on the line, or $SCP_NAME, a local handle; a leading me/ is dropped) acts as that agent, with --harness H
311
- ($SCP_HARNESS, default cli), $SCP_LABELS (role=impl,lane=relay) and $SCP_PARENT. An offline
312
- agent of the same handle and harness is taken back with its inbox; a handle a live session holds is refused, never
317
+ ($SCP_HARNESS, default the harness this shell runs under, else cli), $SCP_LABELS (role=impl,lane=relay) and $SCP_PARENT. An offline
318
+ agent of the same handle and harness (or a generic one: cli, hook, mcp) is taken back with its inbox; a handle a live session holds is refused, never
313
319
  suffixed. Run from that agent's own process, it acts as the agent, narrowing only. A session renamed with join or
314
320
  name keeps its new handle: its hooks, its shim and commands with $SCP_NAME from its shell claim that one.
315
321
  Without --as, at a terminal or with --human you are your own human inbox (me/); messages sent with --human and no
@@ -370,6 +376,7 @@ var VERBS = {
370
376
  inbox: [...IDENTITY, "wait", "ack", "keep"],
371
377
  reply: IDENTITY,
372
378
  ack: [...IDENTITY, "all"],
379
+ show: IDENTITY,
373
380
  ask: [...IDENTITY, "wait", "require-reply", "thread", "private", "attach"],
374
381
  "ask-wait": [...IDENTITY, "wait"],
375
382
  status: [...IDENTITY, "all", "activity"],
@@ -538,13 +545,14 @@ async function relayMain(argv, cwd = process.cwd()) {
538
545
  out(package_default.version);
539
546
  return 0;
540
547
  }
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);
548
+ if (verb === "hook") return (await import("./hooks-B34E2HIP.js")).relayHookCommand(rest, startDaemon);
549
+ if (verb === "setup") return (await import("./setup-MGDWKCNU.js")).relaySetupCommand(rest, cwd, out);
550
+ if (verb === "claude") return (await import("./claude-EQVLST73.js")).claudeCommand(rest);
551
+ if (verb === "pi") return (await import("./pi-QEZVFDNR.js")).piCommand(rest);
552
+ if (verb === "codex") return (await import("./codex-5GZ7YPES.js")).codexCommand(rest);
553
+ if (verb === "opencode") return (await import("./opencode-ORLGNHWR.js")).opencodeCommand(rest, process.env, void 0, cwd);
546
554
  if (verb === "spawn") {
547
- const { splitSpawn, spawnCommand } = await import("./spawn-BYU2X46L.js");
555
+ const { splitSpawn, spawnCommand } = await import("./spawn-GG7VFZA6.js");
548
556
  const { flags } = splitSpawn(rest);
549
557
  if (flags.includes("--help") || flags.includes("-h")) {
550
558
  out(flags.includes("--all") ? RELAY_HELP : SPAWN_HELP);
@@ -552,7 +560,7 @@ async function relayMain(argv, cwd = process.cwd()) {
552
560
  }
553
561
  return spawnCommand(rest, cwd);
554
562
  }
555
- if (verb === "mesh") return (await import("./mesh-VNZV3POO.js")).meshCommand(rest);
563
+ if (verb === "mesh") return (await import("./mesh-H4KL5XAR.js")).meshCommand(rest);
556
564
  const allowed = VERBS[verb];
557
565
  if (!allowed) throw new RelayUsage(`unknown command "${verb}"`);
558
566
  let parsed;
@@ -572,11 +580,11 @@ async function relayMain(argv, cwd = process.cwd()) {
572
580
  return daemon(values.idle, values.policy);
573
581
  }
574
582
  if (verb === "mcp")
575
- return (await import("./mcp-TUWMLGE4.js")).relayMcpCommand(values, positionals);
583
+ return (await import("./mcp-TUXJ7LE7.js")).relayMcpCommand(values, positionals);
576
584
  if (verb === "hooks") {
577
585
  if (positionals[0] !== "install" || positionals.length > 1) throw new RelayUsage(`${CLI_NAME} hooks takes install --harness claude|codex|opencode|agy|muse`);
578
586
  if (values.write && values.print) throw new RelayUsage("--print and --write: pick one");
579
- return (await import("./install-FKGGPT6L.js")).installHooks({
587
+ return (await import("./install-6KXJY27V.js")).installHooks({
580
588
  harness: values.harness,
581
589
  scope: values.scope,
582
590
  write: values.write === true,
@@ -587,26 +595,26 @@ async function relayMain(argv, cwd = process.cwd()) {
587
595
  out
588
596
  });
589
597
  }
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);
598
+ if (verb === "relay") return (await import("./relay-QWJM372C.js")).relayVerb(positionals, values.json === true);
599
+ if (verb === "up") return (await import("./up-B2NEOOC6.js")).up(positionals, values, cwd, relinkBroker);
592
600
  if (verb === "login") {
593
- const code = await (await import("./relay-U634RHIJ.js")).loginMain(positionals, values);
601
+ const code = await (await import("./relay-QWJM372C.js")).loginMain(positionals, values);
594
602
  if (code === 0) await relinkRunning();
595
603
  return code;
596
604
  }
597
- if (verb === "logout") return (await import("./relay-U634RHIJ.js")).logoutMain(positionals, values.json === true);
605
+ if (verb === "logout") return (await import("./relay-QWJM372C.js")).logoutMain(positionals, values.json === true);
598
606
  if (verb === "cloud") {
599
- const code = await (await import("./remote-NTHRHVEP.js")).cloudMain(positionals, values, cloudStatus);
607
+ const code = await (await import("./remote-P4GFUIAP.js")).cloudMain(positionals, values, cloudStatus);
600
608
  if (code === 0 && positionals[0] === "enable") await relinkRunning();
601
609
  return code;
602
610
  }
603
- if (verb === "join") return (await import("./join-RGFHOSLE.js")).joinCommand(values, positionals, cwd, out);
611
+ if (verb === "join") return (await import("./join-FITJVZCA.js")).joinCommand(values, positionals, cwd, out);
604
612
  if (verb === "holds") holdsArgs(values, positionals);
605
613
  const bodyFromStdin = (verb === "send" || verb === "ask" || verb === "reply") && (positionals[1] === void 0 || positionals[1] === "-");
606
614
  const opened = await open(verb, values, cwd, bodyFromStdin);
607
615
  const { client } = opened;
608
616
  try {
609
- if (verb === "machines") return await (await import("./remote-NTHRHVEP.js")).machinesVerb(client, positionals, values.json === true);
617
+ if (verb === "machines") return await (await import("./remote-P4GFUIAP.js")).machinesVerb(client, positionals, values.json === true);
610
618
  return await runVerb(opened, verb, values, positionals, cwd);
611
619
  } finally {
612
620
  client.close();
@@ -783,9 +791,9 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
783
791
  return 0;
784
792
  }
785
793
  case "peek":
786
- return (await import("./peek-4D4VGKOW.js")).peekCommand(localClient(client, broker, verb), values, positionals);
794
+ return (await import("./peek-YTCWVPF7.js")).peekCommand(localClient(client, broker, verb), values, positionals);
787
795
  case "set-peek":
788
- return (await import("./peek-4D4VGKOW.js")).setPeekCommand(localClient(client, broker, verb), values, positionals);
796
+ return (await import("./peek-YTCWVPF7.js")).setPeekCommand(localClient(client, broker, verb), values, positionals);
789
797
  case "why": {
790
798
  const [from, to] = positionals;
791
799
  if (!from || !to || positionals.length > 2) throw new RelayUsage(`${CLI_NAME} why takes <from> <to>`);
@@ -870,6 +878,18 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
870
878
  if (!values.keep && messages.length) await client.request("ack", { seqs: messages.map((m) => m.seq) });
871
879
  return 0;
872
880
  }
881
+ case "show": {
882
+ if (positionals.length !== 1) throw new RelayUsage(`${CLI_NAME} show takes one message id`);
883
+ const message = await lookup(client, positionals[0]);
884
+ if (!message) throw new RelayUsage(`no message ${positionals[0]} was delivered to you`);
885
+ if (asJson) json({ preamble: PREAMBLE, message });
886
+ else {
887
+ out(PREAMBLE);
888
+ out();
889
+ printMessage(message);
890
+ }
891
+ return 0;
892
+ }
873
893
  case "ack": {
874
894
  const usage = `${CLI_NAME} ack takes message ids or seq numbers, or --all`;
875
895
  if (values.all ? positionals.length : !positionals.length) throw new RelayUsage(usage);
@@ -1383,11 +1403,11 @@ async function daemon(idle, policyFile) {
1383
1403
  });
1384
1404
  let broker;
1385
1405
  try {
1386
- const { daemonBridge } = await import("./remote-NTHRHVEP.js");
1406
+ const { daemonBridge } = await import("./remote-P4GFUIAP.js");
1387
1407
  const bridge = await daemonBridge();
1388
1408
  broker = await startBroker({
1389
1409
  dir: relayDir(),
1390
- ...bridge ? { bridge } : await (await import("./mesh-VNZV3POO.js")).meshBrokerOptions(),
1410
+ ...bridge ? { bridge } : await (await import("./mesh-H4KL5XAR.js")).meshBrokerOptions(),
1391
1411
  bridgeConfig: () => daemonBridge(),
1392
1412
  ...idleMs !== void 0 ? { idleMs } : {},
1393
1413
  ...policyFile ? { policyPath: resolve(policyFile) } : {}
@@ -1,22 +1,23 @@
1
1
  import {
2
2
  spawnDetached
3
- } from "./chunk-IXCHAWOL.js";
3
+ } from "./chunk-3SHVJIXP.js";
4
4
  import "./chunk-KN7WEIE4.js";
5
- import "./chunk-W3VNCRBX.js";
5
+ import "./chunk-2OSUVJ2J.js";
6
6
  import {
7
7
  mcpInstall
8
- } from "./chunk-ZMVFN6MU.js";
9
- import "./chunk-X4ZLEM47.js";
8
+ } from "./chunk-Q5UI4Z7H.js";
9
+ import "./chunk-QRWQIHV6.js";
10
10
  import "./chunk-YYSLYVNZ.js";
11
11
  import {
12
12
  CHANNELS_ENV,
13
13
  RelayUsage,
14
+ harnessAt,
14
15
  harnessPid,
15
16
  membership,
16
17
  psParent,
17
18
  relayIdentity,
18
19
  startDaemon
19
- } from "./chunk-E7JG5PJB.js";
20
+ } from "./chunk-2LIJXXTL.js";
20
21
  import "./chunk-MNTIEWKF.js";
21
22
 
22
23
  // src/mcp.ts
@@ -37,7 +38,8 @@ async function relayMcpCommand(values, positionals) {
37
38
  fallbackHarness: "mcp",
38
39
  cwd: process.cwd(),
39
40
  env: process.env,
40
- harnessPid: () => harnessPid()
41
+ harnessPid: () => harnessPid(),
42
+ inferHarness: (pid) => harnessAt(pid)
41
43
  });
42
44
  if (!validHandle(claim.handle)) throw new RelayUsage(`${JSON.stringify(claim.handle)} is not a valid agent handle (me, user and relay are reserved)`);
43
45
  const dir = relayDir();
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RelayUsage
3
- } from "./chunk-E7JG5PJB.js";
3
+ } from "./chunk-2LIJXXTL.js";
4
4
  import "./chunk-MNTIEWKF.js";
5
5
 
6
6
  // src/mesh.ts
@@ -0,0 +1,116 @@
1
+ import {
2
+ mcpFile,
3
+ mountedEntry,
4
+ whichOnPath
5
+ } from "./chunk-2OSUVJ2J.js";
6
+ import {
7
+ hooksTarget
8
+ } from "./chunk-Q5UI4Z7H.js";
9
+ import {
10
+ opencodePlugin
11
+ } from "./chunk-QRWQIHV6.js";
12
+ import "./chunk-YYSLYVNZ.js";
13
+ import {
14
+ RelayUsage,
15
+ gitToplevel
16
+ } from "./chunk-2LIJXXTL.js";
17
+ import "./chunk-MNTIEWKF.js";
18
+
19
+ // src/opencode.ts
20
+ import { spawn } from "child_process";
21
+ import { existsSync, readFileSync } from "fs";
22
+ import { join } from "path";
23
+ import { CLI_NAME, validHandle } from "@scp-dev/core";
24
+ var OPENCODE_HELP = `${CLI_NAME} opencode [--name N | --as N] [--] [opencode args...]
25
+ runs OpenCode with your own config and arguments, wake on: the ${CLI_NAME} plugin wakes the session on a new message, and opens
26
+ one when none is open yet. Refuses to start without the current plugin; ${CLI_NAME} setup --write installs it.
27
+ $SCP_OPENCODE_BIN picks the opencode binary (default opencode on PATH).`;
28
+ function opencodeLaunch(args, env, bin) {
29
+ let name;
30
+ const rest = [];
31
+ for (let i = 0; i < args.length; i++) {
32
+ const arg = args[i];
33
+ if (arg === "--") {
34
+ rest.push(...args.slice(i + 1));
35
+ break;
36
+ }
37
+ if (arg === "--name" || arg === "--as") {
38
+ name = args[++i];
39
+ if (name === void 0) throw new RelayUsage(`${CLI_NAME} opencode ${arg} takes a name`);
40
+ } else if (arg.startsWith("--name=") || arg.startsWith("--as=")) name = arg.slice(arg.indexOf("=") + 1);
41
+ else rest.push(arg);
42
+ }
43
+ name = name?.replace(/^me\//, "");
44
+ if (name !== void 0 && !validHandle(name)) throw new RelayUsage(`${JSON.stringify(name)} is not a valid agent handle`);
45
+ return {
46
+ argv: [bin, ...rest],
47
+ env: { ...env, SCP_HARNESS: "opencode", SCP_WAKE: "1", ...name !== void 0 ? { SCP_NAME: name } : {} }
48
+ };
49
+ }
50
+ function pluginState(env, cwd) {
51
+ const current = /* @__PURE__ */ new Set([opencodePlugin({ blockStop: false }), opencodePlugin({ blockStop: true })]);
52
+ const files = [hooksTarget("opencode", "project", env, cwd), hooksTarget("opencode", "user", env, cwd)].filter((file) => existsSync(file));
53
+ for (const file of files) {
54
+ try {
55
+ if (current.has(readFileSync(file, "utf8"))) return { file, current: true };
56
+ } catch {
57
+ }
58
+ }
59
+ return { ...files[0] ? { file: files[0] } : {}, current: false };
60
+ }
61
+ function pluginProblem(env, cwd) {
62
+ const plugin = pluginState(env, cwd);
63
+ if (plugin.current) return void 0;
64
+ const fix = `run ${CLI_NAME} setup --write (or ${CLI_NAME} hooks install --harness opencode --scope user --write)`;
65
+ return plugin.file ? `${CLI_NAME} opencode: ${plugin.file} is not this ${CLI_NAME}'s plugin, so wake is not guaranteed; ${fix}` : `${CLI_NAME} opencode: OpenCode has no ${CLI_NAME} plugin, so messages would not wake it; ${fix}`;
66
+ }
67
+ function opencodeMountHint(env, cwd) {
68
+ const project = gitToplevel(cwd) ?? cwd;
69
+ const files = [mcpFile("opencode", env), ...["opencode.json", "opencode.jsonc"].map((name) => join(project, name))];
70
+ for (const file of files) {
71
+ try {
72
+ if (existsSync(file) && mountedEntry("opencode", file)) return void 0;
73
+ } catch {
74
+ }
75
+ }
76
+ return `${CLI_NAME} opencode: OpenCode has no scp MCP mount, so the session has no relay tools; run ${CLI_NAME} setup --write`;
77
+ }
78
+ async function opencodeCommand(args, env = process.env, err = (line) => void process.stderr.write(`${line}
79
+ `), cwd = process.cwd()) {
80
+ const own = args.includes("--") ? args.slice(0, args.indexOf("--")) : args;
81
+ if (own.includes("--help") || own.includes("-h")) {
82
+ process.stdout.write(`${OPENCODE_HELP}
83
+ `);
84
+ return 0;
85
+ }
86
+ const bin = env.SCP_OPENCODE_BIN || whichOnPath("opencode", env);
87
+ if (!bin) throw new RelayUsage(`opencode is not on PATH: install it with npm i -g opencode-ai, then run ${CLI_NAME} setup --write`);
88
+ const launch = opencodeLaunch(args, env, bin);
89
+ const problem = pluginProblem(env, cwd);
90
+ if (problem) throw new RelayUsage(problem);
91
+ const hint = opencodeMountHint(env, cwd);
92
+ if (hint) err(hint);
93
+ return await new Promise((resolve, reject) => {
94
+ const child = spawn(launch.argv[0], launch.argv.slice(1), { env: launch.env, stdio: "inherit" });
95
+ const forward = (signal) => child.kill(signal);
96
+ const signals = ["SIGTERM", "SIGHUP"];
97
+ const ignore = () => {
98
+ };
99
+ for (const signal of signals) process.on(signal, forward);
100
+ process.on("SIGINT", ignore);
101
+ child.once("error", (error) => reject(new RelayUsage(`could not start ${launch.argv[0]}: ${error.message}`)));
102
+ child.once("exit", (code, signal) => {
103
+ for (const each of signals) process.off(each, forward);
104
+ process.off("SIGINT", ignore);
105
+ resolve(code ?? (signal ? 1 : 0));
106
+ });
107
+ });
108
+ }
109
+ export {
110
+ OPENCODE_HELP,
111
+ opencodeCommand,
112
+ opencodeLaunch,
113
+ opencodeMountHint,
114
+ pluginProblem,
115
+ pluginState
116
+ };
@@ -2,7 +2,7 @@ import {
2
2
  RelayUsage,
3
3
  peekGrant,
4
4
  stamp
5
- } from "./chunk-E7JG5PJB.js";
5
+ } from "./chunk-2LIJXXTL.js";
6
6
  import {
7
7
  note
8
8
  } from "./chunk-MNTIEWKF.js";