@scp-dev/cli 0.5.0 → 0.6.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.
@@ -374,6 +374,7 @@ function hookHarnessPid(env, harness, ppid = process.ppid, parentOf = psParent)
374
374
  if (Number.isInteger(pid) && pid > 1) return pid;
375
375
  return harnessPid(ppid, parentOf);
376
376
  }
377
+ var HARNESS_SESSION = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
377
378
  function relayIdentity(input) {
378
379
  const resolved = resolveHarness(input.harness, input.env, input.fallbackHarness);
379
380
  const pid = input.harnessPid(resolved);
@@ -382,6 +383,7 @@ function relayIdentity(input) {
382
383
  const handle = input.as ?? input.env.SCP_NAME ?? deriveHandle(harness, cwd);
383
384
  const harnessProcess = processKey(pid, input.startOf);
384
385
  const keys = input.lineageOf ? input.lineageOf(pid) : lineage(pid);
386
+ const harnessSession = harness === "claude-code" ? [input.harnessSession, input.env.CLAUDE_CODE_SESSION_ID].find((id) => id !== void 0 && HARNESS_SESSION.test(id)) : void 0;
385
387
  return {
386
388
  handle,
387
389
  harness,
@@ -391,7 +393,8 @@ function relayIdentity(input) {
391
393
  labels: labelsFor(input.env),
392
394
  ...input.env.SCP_PARENT ? { parent: input.env.SCP_PARENT } : {},
393
395
  ...keys.length ? { lineage: keys } : {},
394
- ...input.env.SCP_GRANT ? { grant: input.env.SCP_GRANT } : {}
396
+ ...input.env.SCP_GRANT ? { grant: input.env.SCP_GRANT } : {},
397
+ ...harnessSession ? { harness_session: harnessSession } : {}
395
398
  };
396
399
  }
397
400
 
@@ -466,6 +469,7 @@ var OPTIONS = {
466
469
  who: { type: "boolean" },
467
470
  peek: { type: "string" },
468
471
  endpoint: { type: "string" },
472
+ "take-over": { type: "boolean" },
469
473
  live: { type: "boolean" },
470
474
  parent: { type: "string" }
471
475
  };
@@ -591,7 +595,7 @@ async function open(verb, values, cwd, bodyFromStdin, walk) {
591
595
  `${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)`
592
596
  );
593
597
  }
594
- const { machineName, openCloud } = await import("./remote-UQJ5YYCC.js");
598
+ const { machineName, openCloud } = await import("./remote-3R4GRDGI.js");
595
599
  const handle2 = named ? localHandle(named) : void 0;
596
600
  const claim = handle2 ? {
597
601
  handle: handle2,
@@ -849,7 +853,7 @@ function relayVerb(positionals, asJson, env = process.env) {
849
853
  }
850
854
  async function loginMain(positionals, values) {
851
855
  if (positionals.length) throw new RelayUsage(`${CLI_NAME3} login takes only flags`);
852
- return (await import("./login-6MVD3VLC.js")).login(values);
856
+ return (await import("./login-4IF26HH5.js")).login(values);
853
857
  }
854
858
  function logoutMain(positionals, asJson, env = process.env) {
855
859
  if (positionals.length) throw new RelayUsage(`${CLI_NAME3} logout takes no arguments`);
@@ -10,11 +10,11 @@ import {
10
10
  mergeHooks,
11
11
  musePlugin,
12
12
  opencodePlugin
13
- } from "./chunk-XQHPDNVG.js";
13
+ } from "./chunk-GG33NSBW.js";
14
14
  import {
15
15
  RelayUsage,
16
16
  gitToplevel
17
- } from "./chunk-AAFHLHXG.js";
17
+ } from "./chunk-BSX24XMZ.js";
18
18
 
19
19
  // src/install.ts
20
20
  import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
@@ -5,7 +5,7 @@ import {
5
5
  configDir,
6
6
  relayCredential,
7
7
  writePrivateJson
8
- } from "./chunk-AAFHLHXG.js";
8
+ } from "./chunk-BSX24XMZ.js";
9
9
  import {
10
10
  note,
11
11
  printTable
@@ -3,10 +3,10 @@ import {
3
3
  agySettingsFile,
4
4
  mcpFile,
5
5
  mountedEntry
6
- } from "./chunk-UKQBH4AQ.js";
6
+ } from "./chunk-DEK3TE6T.js";
7
7
  import {
8
8
  steerPrompt
9
- } from "./chunk-XQHPDNVG.js";
9
+ } from "./chunk-GG33NSBW.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-AAFHLHXG.js";
21
+ } from "./chunk-BSX24XMZ.js";
22
22
  import {
23
23
  SELF,
24
24
  note
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  hooksTarget
3
- } from "./chunk-2342GTME.js";
3
+ } from "./chunk-C5PYVRJE.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-XQHPDNVG.js";
15
+ } from "./chunk-GG33NSBW.js";
16
16
  import {
17
17
  RelayUsage,
18
18
  gitToplevel,
19
19
  setJoinMode
20
- } from "./chunk-AAFHLHXG.js";
20
+ } from "./chunk-BSX24XMZ.js";
21
21
 
22
22
  // src/setup.ts
23
23
  import { spawnSync } from "child_process";
@@ -13,7 +13,7 @@ import {
13
13
  psStart,
14
14
  relayIdentity,
15
15
  sessionKey
16
- } from "./chunk-AAFHLHXG.js";
16
+ } from "./chunk-BSX24XMZ.js";
17
17
 
18
18
  // src/hooks.ts
19
19
  import { Readable } from "stream";
@@ -445,8 +445,10 @@ async function runHook(args, options) {
445
445
  )
446
446
  );
447
447
  if ("dark" in joined) return;
448
+ const harnessSession = event.agent ? void 0 : asString(field(payload, "session_id", "sessionId"));
448
449
  const claim = relayIdentity({
449
450
  ...joined.handle ? { as: joined.handle } : {},
451
+ ...harnessSession ? { harnessSession } : {},
450
452
  harness,
451
453
  fallbackHarness: "hook",
452
454
  cwd: event.cwd ?? options.cwd,
@@ -556,8 +558,10 @@ async function wakeHook(harnessArg, payload, waitS, options) {
556
558
  )
557
559
  );
558
560
  if ("dark" in joined) return;
561
+ const harnessSession = asString(field(payload, "session_id", "sessionId"));
559
562
  const claim = relayIdentity({
560
563
  ...joined.handle ? { as: joined.handle } : {},
564
+ ...harnessSession ? { harnessSession } : {},
561
565
  harness,
562
566
  fallbackHarness: "hook",
563
567
  cwd: asString(payload.cwd) ?? options.cwd,
@@ -0,0 +1,290 @@
1
+ import {
2
+ entryArgv,
3
+ mcpFile,
4
+ mountedEntry,
5
+ whichOnPath
6
+ } from "./chunk-DEK3TE6T.js";
7
+ import "./chunk-C5PYVRJE.js";
8
+ import "./chunk-GG33NSBW.js";
9
+ import "./chunk-YYSLYVNZ.js";
10
+ import {
11
+ CHANNELS_ENV,
12
+ RelayUsage,
13
+ deriveHandle,
14
+ joinMode
15
+ } from "./chunk-BSX24XMZ.js";
16
+ import {
17
+ SELF
18
+ } from "./chunk-MNTIEWKF.js";
19
+
20
+ // src/claude.ts
21
+ import { spawn, spawnSync } from "child_process";
22
+ import { randomBytes } from "crypto";
23
+ import { existsSync, readFileSync, symlinkSync, unlinkSync, writeFileSync } from "fs";
24
+ import { basename, join } from "path";
25
+ import { privateDir, relayDir } from "@scp-dev/broker";
26
+ import { CLI_NAME, validHandle } from "@scp-dev/core";
27
+ var CHANNEL_FLAGS = ["--dangerously-load-development-channels", "server:scp"];
28
+ function claudeArgv(argv) {
29
+ let name;
30
+ let bg = false;
31
+ let i = 0;
32
+ for (; ; ) {
33
+ const arg = argv[i];
34
+ if (arg === "--bg" || arg === "--background") {
35
+ bg = true;
36
+ i++;
37
+ } else if (arg === "--name" || arg === "--as") {
38
+ if (argv[i + 1] === void 0) throw new RelayUsage(`${CLI_NAME} claude ${arg} takes a handle`);
39
+ name = argv[i + 1];
40
+ i += 2;
41
+ } else if (arg?.startsWith("--name=") || arg?.startsWith("--as=")) {
42
+ name = arg.slice(arg.indexOf("=") + 1);
43
+ i++;
44
+ } else break;
45
+ }
46
+ if (argv[i] === "--") i++;
47
+ name = name?.replace(/^me\//, "");
48
+ if (name !== void 0 && !validHandle(name)) throw new RelayUsage(`--name ${JSON.stringify(name)} is not a valid agent handle`);
49
+ const args = argv.slice(i);
50
+ if (!bg) bg = optionsOf(args).some((arg) => arg === "--bg" || arg === "--background");
51
+ return { ...name !== void 0 ? { name } : {}, ...bg ? { bg: true } : {}, args };
52
+ }
53
+ var optionsOf = (args) => args.includes("--") ? args.slice(0, args.indexOf("--")) : args;
54
+ function beforeDashes(args, added) {
55
+ const at = optionsOf(args).length;
56
+ return [...args.slice(0, at), ...added, ...args.slice(at)];
57
+ }
58
+ function claudeLaunchArgs(args, added) {
59
+ const channels = optionsOf(args).some((arg) => arg === "server:scp" || arg === `${CHANNEL_FLAGS[0]}=server:scp`) ? [] : CHANNEL_FLAGS;
60
+ return beforeDashes(args, [...channels, ...added]);
61
+ }
62
+ var LAUNCHER_IDENTITY = ["SCP_NAME", "SCP_GRANT", "SCP_SESSION", "SCP_PARENT", "SCP_HARNESS", "SCP_SHIM_BINDING", "SCP_SPAWN_RUNNER", CHANNELS_ENV];
63
+ var SHIM_VARS = ["SCP_LABELS", "SCP_DIR", "XDG_STATE_HOME"];
64
+ var bgDir = (env) => join(relayDir(env), "claude-bg");
65
+ function bgMount(env, name) {
66
+ let entry;
67
+ try {
68
+ entry = mountedEntry("claude", mcpFile("claude", env));
69
+ } catch {
70
+ }
71
+ const mounted = entry ? entryArgv("claude", entry) : [];
72
+ const argv = mounted.includes("--channels") ? mounted : [...SELF, "mcp", "--harness", "claude", "--channels"];
73
+ const base = entry?.env && typeof entry.env === "object" ? entry.env : {};
74
+ const vars = Object.fromEntries(SHIM_VARS.flatMap((key) => env[key] ? [[key, env[key]]] : []));
75
+ return {
76
+ type: "stdio",
77
+ command: argv[0],
78
+ args: argv.slice(1),
79
+ env: { ...base, ...vars, ...name !== void 0 ? { SCP_NAME: name } : {}, SCP_GRANT: env.SCP_GRANT ?? "" }
80
+ };
81
+ }
82
+ var STRICT_HINT = `${CLI_NAME} claude: --strict-mcp-config without --mcp-config drops the scp mount, so messages cannot wake this session`;
83
+ function externalMcp(args) {
84
+ return args.some((arg) => arg === "--mcp-config" || arg.startsWith("--mcp-config="));
85
+ }
86
+ function namePrompt(name, args, env, cwd) {
87
+ if (name !== void 0 || joinMode(env) === "manual" || args.some((arg) => /^--(append-)?system-prompt/.test(arg))) return [];
88
+ const handle = env.SCP_NAME || deriveHandle("claude-code", cwd);
89
+ return [
90
+ "--append-system-prompt",
91
+ `You are on the SCP network as ${handle}. Pick a short descriptive name for yourself with \`${CLI_NAME} name <name>\` when you know what you're working on.`
92
+ ];
93
+ }
94
+ function mountHint(env, cwd = process.cwd()) {
95
+ const file = mcpFile("claude", env);
96
+ const entries = [];
97
+ try {
98
+ const user = mountedEntry("claude", file);
99
+ if (user) entries.push(user);
100
+ const config = existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : {};
101
+ const local = config.projects?.[cwd]?.mcpServers?.scp;
102
+ if (local && typeof local === "object") entries.push(local);
103
+ } catch {
104
+ return `${CLI_NAME} claude: ${file} is unreadable, so the scp mount is unchecked; fix it or run ${CLI_NAME} setup --write`;
105
+ }
106
+ const project = join(cwd, ".mcp.json");
107
+ try {
108
+ const entry = existsSync(project) ? mountedEntry("claude", project) : void 0;
109
+ if (entry) entries.push(entry);
110
+ } catch {
111
+ }
112
+ if (!entries.length) return `${CLI_NAME} claude: Claude Code has no scp MCP mount, so messages cannot wake this session; run ${CLI_NAME} setup --write`;
113
+ if (!entries.some((entry) => entryArgv("claude", entry).includes("--channels")))
114
+ return `${CLI_NAME} claude: the scp mount lacks --channels, so messages cannot wake this session; run ${CLI_NAME} setup --write`;
115
+ return void 0;
116
+ }
117
+ var SHORT_ID = /^[0-9a-f]{8}$/;
118
+ var BACKGROUNDED = /backgrounded\s*\S\s*([0-9a-f]{8})/;
119
+ function liveAgents(bin, env) {
120
+ const run = spawnSync(bin, ["agents", "--json"], { env, encoding: "utf8", timeout: 15e3 });
121
+ try {
122
+ const listed = JSON.parse(run.stdout);
123
+ return Array.isArray(listed) ? listed.filter((agent) => typeof agent?.id === "string" && agent.state !== "failed") : [];
124
+ } catch {
125
+ return [];
126
+ }
127
+ }
128
+ function resumeTarget(args) {
129
+ const options = optionsOf(args);
130
+ for (const [i, arg] of options.entries()) {
131
+ if (arg.startsWith("--resume=")) return arg.slice("--resume=".length);
132
+ if ((arg === "--resume" || arg === "-r") && options[i + 1] && !options[i + 1].startsWith("-")) return options[i + 1];
133
+ }
134
+ return void 0;
135
+ }
136
+ async function runClaude(bin, args, env, cwd, stdout = "inherit", onOut) {
137
+ const child = spawn(bin, args, { cwd, stdio: ["inherit", stdout, "inherit"], env });
138
+ child.stdout?.on("data", (chunk) => {
139
+ process.stdout.write(chunk);
140
+ onOut?.(chunk.toString("utf8"));
141
+ });
142
+ const forward = (signal) => () => void child.kill(signal);
143
+ const handlers = ["SIGTERM", "SIGHUP"].map((signal) => [signal, forward(signal)]);
144
+ const ignore = () => {
145
+ };
146
+ process.on("SIGINT", ignore);
147
+ for (const [signal, handler] of handlers) process.on(signal, handler);
148
+ try {
149
+ return await new Promise((resolve, reject) => {
150
+ child.once("error", reject);
151
+ child.once("close", (code, signal) => resolve(code ?? (signal ? 128 + (signalNumber(signal) ?? 1) : 1)));
152
+ });
153
+ } finally {
154
+ process.off("SIGINT", ignore);
155
+ for (const [signal, handler] of handlers) process.off(signal, handler);
156
+ }
157
+ }
158
+ async function backgroundCommand(name, args, env, cwd, bin, out) {
159
+ const handle = name ?? (env.SCP_NAME || void 0);
160
+ const dir = privateDir(bgDir(env));
161
+ const file = join(dir, `${randomBytes(8).toString("hex")}.json`);
162
+ writeFileSync(file, `${JSON.stringify({ mcpServers: { scp: bgMount(env, handle) } }, null, 2)}
163
+ `, { mode: 384 });
164
+ const options = optionsOf(args);
165
+ const added = [
166
+ ...options.includes("--bg") || options.includes("--background") ? [] : ["--bg"],
167
+ ...handle !== void 0 && !options.some((arg) => arg === "-n" || arg === "--name" || arg.startsWith("--name=")) ? ["--name", handle] : [],
168
+ ...namePrompt(handle, args, env, cwd),
169
+ "--mcp-config",
170
+ file
171
+ ];
172
+ const clean = Object.fromEntries(Object.entries(env).filter(([key]) => !LAUNCHER_IDENTITY.includes(key)));
173
+ let printed = "";
174
+ const code = await runClaude(bin, beforeDashes(args, added), clean, cwd, "pipe", (text) => {
175
+ printed += text;
176
+ });
177
+ const id = BACKGROUNDED.exec(printed)?.[1];
178
+ if (!id) {
179
+ unlinkSync(file);
180
+ return code || 1;
181
+ }
182
+ try {
183
+ unlinkSync(join(dir, `${id}.json`));
184
+ } catch {
185
+ }
186
+ symlinkSync(basename(file), join(dir, `${id}.json`));
187
+ const address = `me/${handle ?? deriveHandle("claude-code", cwd)}`;
188
+ out(`${CLI_NAME} claude: background session ${id} is ${address} on SCP; messages wake it through its inbox. ${CLI_NAME} claude attach ${id} opens it`);
189
+ return code;
190
+ }
191
+ async function attachCommand(argv, env, err, cwd, bin, deps) {
192
+ let target;
193
+ let rename;
194
+ for (let i = 0; i < argv.length; i++) {
195
+ const arg = argv[i];
196
+ if (arg === "--name" || arg === "--as") {
197
+ rename = argv[++i];
198
+ if (rename === void 0) throw new RelayUsage(`${CLI_NAME} claude attach ${arg} takes a handle`);
199
+ } else if (arg.startsWith("--name=") || arg.startsWith("--as=")) rename = arg.slice(arg.indexOf("=") + 1);
200
+ else if (target === void 0) target = arg;
201
+ else throw new RelayUsage(`${CLI_NAME} claude attach takes one session: <id|name> [--name N]`);
202
+ }
203
+ if (target === void 0) throw new RelayUsage(`${CLI_NAME} claude attach takes a background session: <id|name> [--name N]`);
204
+ rename = rename?.replace(/^me\//, "");
205
+ if (rename !== void 0) {
206
+ if (!validHandle(rename)) throw new RelayUsage(`--name ${JSON.stringify(rename)} is not a valid agent handle`);
207
+ await renameBackground(target, rename, env, err, bin, deps);
208
+ }
209
+ return runClaude(bin, ["attach", target], env, cwd);
210
+ }
211
+ async function renameBackground(target, to, env, err, bin, deps) {
212
+ const agent = SHORT_ID.test(target) ? void 0 : liveAgents(bin, env).find((a) => a.name === target || a.sessionId === target);
213
+ const id = SHORT_ID.test(target) ? target : agent?.id;
214
+ const link = id ? join(bgDir(env), `${id}.json`) : void 0;
215
+ let config;
216
+ try {
217
+ config = link ? JSON.parse(readFileSync(link, "utf8")) : void 0;
218
+ } catch {
219
+ }
220
+ const from = config?.mcpServers?.scp?.env?.SCP_NAME ?? (SHORT_ID.test(target) ? void 0 : target);
221
+ if (!link || !config?.mcpServers?.scp?.env || from === void 0) {
222
+ err(`${CLI_NAME} claude: ${target} was not started by ${CLI_NAME} claude --bg --name, so its name is unknown; run ${CLI_NAME} name ${to} in it`);
223
+ return;
224
+ }
225
+ if (from === to) return;
226
+ const code = await (deps.rename ?? renameEndpoint)(from, to);
227
+ if (code !== 0) {
228
+ err(`${CLI_NAME} claude: me/${from} keeps its name`);
229
+ return;
230
+ }
231
+ config.mcpServers.scp.env.SCP_NAME = to;
232
+ writeFileSync(link, `${JSON.stringify(config, null, 2)}
233
+ `, { mode: 384 });
234
+ }
235
+ async function renameEndpoint(from, to) {
236
+ return (await import("./main-RA3MP25N.js")).relayMain(["name", "--endpoint", `me/${from}`, to]);
237
+ }
238
+ async function claudeCommand(argv, env = process.env, err = (line) => void process.stderr.write(`${line}
239
+ `), cwd = process.cwd(), deps = {}) {
240
+ if (argv[0] === "--help" || argv[0] === "-h") {
241
+ process.stdout.write(CLAUDE_HELP);
242
+ return 0;
243
+ }
244
+ const out = deps.out ?? ((line) => void process.stdout.write(`${line}
245
+ `));
246
+ const bin = whichOnPath("claude", env);
247
+ if (argv[0] === "attach") {
248
+ if (!bin) throw new RelayUsage("claude is not on PATH");
249
+ return attachCommand(argv.slice(1), env, err, cwd, bin, deps);
250
+ }
251
+ const { name, bg, args } = claudeArgv(argv);
252
+ if (!bin) throw new RelayUsage("claude is not on PATH");
253
+ if (bg) return backgroundCommand(name, args, env, cwd, bin, out);
254
+ const resume = resumeTarget(args);
255
+ const live = resume === void 0 ? void 0 : liveAgents(bin, env).find((agent) => agent.id === resume || agent.sessionId === resume);
256
+ if (live) {
257
+ err(`${CLI_NAME} claude: ${resume} is a live background session, already woken through claude-inbox; attaching to it (claude attach ${live.id})`);
258
+ return runClaude(bin, ["attach", live.id], env, cwd);
259
+ }
260
+ const external = externalMcp(args);
261
+ const hint = external ? void 0 : args.includes("--strict-mcp-config") ? STRICT_HINT : mountHint(env, cwd);
262
+ if (hint) throw new RelayUsage(hint);
263
+ if (external) err(`${CLI_NAME} claude: using your --mcp-config; it needs the scp server with --channels for messages to wake this session`);
264
+ return runClaude(
265
+ bin,
266
+ claudeLaunchArgs(args, namePrompt(name, args, env, cwd)),
267
+ { ...env, ...name !== void 0 ? { SCP_NAME: name } : {}, [CHANNELS_ENV]: String(process.pid) },
268
+ cwd
269
+ );
270
+ }
271
+ var CLAUDE_HELP = `${CLI_NAME} claude [--name N | --as N] [--] [claude args...] start Claude Code with the scp channel loaded, so peer messages wake it
272
+ ${CLI_NAME} claude --bg [--name N] [--] [claude args...] start it in the background (claude --bg) with an scp mount that claims N and
273
+ $SCP_GRANT, so env $(${CLI_NAME} spawn-env --name N) links it under you; messages wake it
274
+ through its inbox. Prints its short id and address
275
+ ${CLI_NAME} claude attach <id|name> [--name N] claude attach, renaming its endpoint to N first
276
+ --resume <id> of a live background session attaches to it: it is already woken through claude-inbox
277
+ `;
278
+ var SIGNALS = { SIGHUP: 1, SIGINT: 2, SIGKILL: 9, SIGTERM: 15 };
279
+ var signalNumber = (signal) => SIGNALS[signal];
280
+ export {
281
+ CHANNEL_FLAGS,
282
+ bgDir,
283
+ bgMount,
284
+ claudeArgv,
285
+ claudeCommand,
286
+ claudeLaunchArgs,
287
+ mountHint,
288
+ namePrompt,
289
+ resumeTarget
290
+ };
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-F2NFQ2KC.js");
20
+ const { run } = await import("./main-RA3MP25N.js");
21
21
  await run(process.argv.slice(2));
22
22
  }
@@ -3,18 +3,18 @@ import {
3
3
  codexMount,
4
4
  codexOverrides,
5
5
  toml
6
- } from "./chunk-7RA7E5WR.js";
6
+ } from "./chunk-CVW3INZO.js";
7
7
  import "./chunk-KN7WEIE4.js";
8
- import "./chunk-UKQBH4AQ.js";
9
- import "./chunk-2342GTME.js";
10
- import "./chunk-XQHPDNVG.js";
8
+ import "./chunk-DEK3TE6T.js";
9
+ import "./chunk-C5PYVRJE.js";
10
+ import "./chunk-GG33NSBW.js";
11
11
  import "./chunk-YYSLYVNZ.js";
12
12
  import {
13
13
  RelayUsage,
14
14
  readPsTable,
15
15
  relayIdentity,
16
16
  startDaemon
17
- } from "./chunk-AAFHLHXG.js";
17
+ } from "./chunk-BSX24XMZ.js";
18
18
  import {
19
19
  SELF
20
20
  } from "./chunk-MNTIEWKF.js";
@@ -40,11 +40,11 @@ import {
40
40
  tldrEvery,
41
41
  transcriptTitle,
42
42
  unreadPointer
43
- } from "./chunk-XQHPDNVG.js";
43
+ } from "./chunk-GG33NSBW.js";
44
44
  import {
45
45
  SUBAGENT
46
46
  } from "./chunk-YYSLYVNZ.js";
47
- import "./chunk-AAFHLHXG.js";
47
+ import "./chunk-BSX24XMZ.js";
48
48
  import "./chunk-MNTIEWKF.js";
49
49
  export {
50
50
  AGY_HOOK_SET,
@@ -2,10 +2,10 @@ import {
2
2
  hooksTarget,
3
3
  installHooks,
4
4
  mcpInstall
5
- } from "./chunk-2342GTME.js";
6
- import "./chunk-XQHPDNVG.js";
5
+ } from "./chunk-C5PYVRJE.js";
6
+ import "./chunk-GG33NSBW.js";
7
7
  import "./chunk-YYSLYVNZ.js";
8
- import "./chunk-AAFHLHXG.js";
8
+ import "./chunk-BSX24XMZ.js";
9
9
  import "./chunk-MNTIEWKF.js";
10
10
  export {
11
11
  hooksTarget,
@@ -7,7 +7,7 @@ import {
7
7
  sessionKey,
8
8
  startDaemon,
9
9
  walkNow
10
- } from "./chunk-AAFHLHXG.js";
10
+ } from "./chunk-BSX24XMZ.js";
11
11
  import "./chunk-MNTIEWKF.js";
12
12
 
13
13
  // src/join.ts
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  machineName
3
- } from "./chunk-H2XQXMZU.js";
3
+ } from "./chunk-C7ZUACP7.js";
4
4
  import {
5
5
  RelayUsage,
6
6
  activeRelay,
7
7
  saveCredential
8
- } from "./chunk-AAFHLHXG.js";
8
+ } from "./chunk-BSX24XMZ.js";
9
9
  import {
10
10
  note
11
11
  } from "./chunk-MNTIEWKF.js";
@@ -16,7 +16,7 @@ import {
16
16
  startDaemon,
17
17
  terminalAttached,
18
18
  time
19
- } from "./chunk-AAFHLHXG.js";
19
+ } from "./chunk-BSX24XMZ.js";
20
20
  import {
21
21
  FAILED,
22
22
  exitFlushed,
@@ -35,7 +35,7 @@ import { parseArgs } from "util";
35
35
  // package.json
36
36
  var package_default = {
37
37
  name: "@scp-dev/cli",
38
- version: "0.5.0",
38
+ version: "0.6.0",
39
39
  description: "The swarm command line tool.",
40
40
  license: "Apache-2.0",
41
41
  homepage: "https://scp.dev",
@@ -281,6 +281,9 @@ agent sessions:
281
281
  it while idle (claude --dangerously-load-development-channels server:scp; SCP_NAME=N)
282
282
  --channels pushes messages into a Claude Code that ${CLI_NAME} claude started; setup mounts claude's shim with it.
283
283
  Without --name, ${CLI_NAME} claude asks the session to pick its own name with ${CLI_NAME} name <name>.
284
+ ${CLI_NAME} claude --bg [--name N] [--] [claude args...] the same in the background (claude --bg): an scp mount claims N and
285
+ $SCP_GRANT, so under env $(${CLI_NAME} spawn-env --name N) it links under you; prints its short id
286
+ ${CLI_NAME} claude attach <id|name> [--name N] claude attach, renaming its endpoint to N first; --resume of a live bg session attaches
284
287
  ${CLI_NAME} join [<handle>] put the session that runs this shell on the relay (it then shows in list,
285
288
  peers can message it, and its hooks hand it new messages), named <handle>
286
289
  when given; on a joined session it renames it in place. Works for a session
@@ -289,6 +292,9 @@ agent sessions:
289
292
  and title stay; a handle another endpoint holds, live or offline, is refused;
290
293
  a spawned worker is named by its spawner (policy defaults.rename: self lets it);
291
294
  --endpoint renames another agent, for the human at a terminal
295
+ ${CLI_NAME} name <handle> --take-over [--endpoint EP] take the handle from an offline endpoint: it is retired and its
296
+ children, links and unread mail move over (the same harness session, such as
297
+ a resumed one, needs no flag); for the human at a terminal
292
298
  ${CLI_NAME} join [<handle>] --under <parent> link the session under the agent <parent> (one it may message), as if
293
299
  <parent> had spawned it: it shows under <parent> in list --tree, the dashboard
294
300
  and the graph, and takes on a worker's scope (the policy's child_default: by
@@ -396,7 +402,7 @@ var VERBS = {
396
402
  link: IDENTITY,
397
403
  unlink: IDENTITY,
398
404
  leave: [...IDENTITY, "endpoint"],
399
- name: [...IDENTITY, "endpoint"],
405
+ name: [...IDENTITY, "endpoint", "take-over"],
400
406
  inbox: [...IDENTITY, "wait", "ack", "keep"],
401
407
  reply: IDENTITY,
402
408
  ack: [...IDENTITY, "all"],
@@ -569,14 +575,14 @@ async function relayMain(argv, cwd = process.cwd()) {
569
575
  out(cliVersion());
570
576
  return 0;
571
577
  }
572
- if (verb === "hook") return (await import("./hooks-BZANUJPY.js")).relayHookCommand(rest, startDaemon);
573
- if (verb === "setup") return (await import("./setup-UJCIMYU4.js")).relaySetupCommand(rest, cwd, out);
574
- if (verb === "claude") return (await import("./claude-RNH2E4GU.js")).claudeCommand(rest);
575
- if (verb === "pi") return (await import("./pi-N3DX37PD.js")).piCommand(rest);
576
- if (verb === "codex") return (await import("./codex-OSGHQMK6.js")).codexCommand(rest);
577
- if (verb === "opencode") return (await import("./opencode-XV33XTL5.js")).opencodeCommand(rest, process.env, void 0, cwd);
578
+ if (verb === "hook") return (await import("./hooks-SWVIDONV.js")).relayHookCommand(rest, startDaemon);
579
+ if (verb === "setup") return (await import("./setup-O45SE5L7.js")).relaySetupCommand(rest, cwd, out);
580
+ if (verb === "claude") return (await import("./claude-NCPNZLIX.js")).claudeCommand(rest);
581
+ if (verb === "pi") return (await import("./pi-XHZAUDP4.js")).piCommand(rest);
582
+ if (verb === "codex") return (await import("./codex-4GTXURTS.js")).codexCommand(rest);
583
+ if (verb === "opencode") return (await import("./opencode-CPNR2G3W.js")).opencodeCommand(rest, process.env, void 0, cwd);
578
584
  if (verb === "spawn") {
579
- const { splitSpawn, spawnCommand } = await import("./spawn-JYD7NCQT.js");
585
+ const { splitSpawn, spawnCommand } = await import("./spawn-CJSHYE7E.js");
580
586
  const { flags } = splitSpawn(rest);
581
587
  if (flags.includes("--help") || flags.includes("-h")) {
582
588
  out(flags.includes("--all") ? RELAY_HELP : SPAWN_HELP);
@@ -584,7 +590,7 @@ async function relayMain(argv, cwd = process.cwd()) {
584
590
  }
585
591
  return spawnCommand(rest, cwd);
586
592
  }
587
- if (verb === "mesh") return (await import("./mesh-OZILTV5P.js")).meshCommand(rest);
593
+ if (verb === "mesh") return (await import("./mesh-XTMBRJLR.js")).meshCommand(rest);
588
594
  const allowed = VERBS[verb];
589
595
  if (!allowed) throw new RelayUsage(`unknown command "${verb}"`);
590
596
  let parsed;
@@ -604,11 +610,11 @@ async function relayMain(argv, cwd = process.cwd()) {
604
610
  return daemon(values.idle, values.policy);
605
611
  }
606
612
  if (verb === "mcp")
607
- return (await import("./mcp-BH526TOG.js")).relayMcpCommand(values, positionals);
613
+ return (await import("./mcp-RERRUUIX.js")).relayMcpCommand(values, positionals);
608
614
  if (verb === "hooks") {
609
615
  if (positionals[0] !== "install" || positionals.length > 1) throw new RelayUsage(`${CLI_NAME} hooks takes install --harness claude|codex|opencode|agy|muse`);
610
616
  if (values.write && values.print) throw new RelayUsage("--print and --write: pick one");
611
- return (await import("./install-25J5QB63.js")).installHooks({
617
+ return (await import("./install-YE7LCGPC.js")).installHooks({
612
618
  harness: values.harness,
613
619
  scope: values.scope,
614
620
  write: values.write === true,
@@ -619,27 +625,27 @@ async function relayMain(argv, cwd = process.cwd()) {
619
625
  out
620
626
  });
621
627
  }
622
- if (verb === "relay") return (await import("./relay-SF3UWIPM.js")).relayVerb(positionals, values.json === true);
623
- if (verb === "up") return (await import("./up-3YJU7YX6.js")).up(positionals, values, cwd, relinkBroker);
628
+ if (verb === "relay") return (await import("./relay-E4JFR33E.js")).relayVerb(positionals, values.json === true);
629
+ if (verb === "up") return (await import("./up-O2CMDGXP.js")).up(positionals, values, cwd, relinkBroker);
624
630
  if (verb === "login") {
625
- const code = await (await import("./relay-SF3UWIPM.js")).loginMain(positionals, values);
631
+ const code = await (await import("./relay-E4JFR33E.js")).loginMain(positionals, values);
626
632
  if (code === 0) await relinkRunning();
627
633
  return code;
628
634
  }
629
- if (verb === "logout") return (await import("./relay-SF3UWIPM.js")).logoutMain(positionals, values.json === true);
635
+ if (verb === "logout") return (await import("./relay-E4JFR33E.js")).logoutMain(positionals, values.json === true);
630
636
  if (verb === "cloud") {
631
- const code = await (await import("./remote-UQJ5YYCC.js")).cloudMain(positionals, values, cloudStatus);
637
+ const code = await (await import("./remote-3R4GRDGI.js")).cloudMain(positionals, values, cloudStatus);
632
638
  if (code === 0 && positionals[0] === "enable") await relinkRunning();
633
639
  return code;
634
640
  }
635
- if (verb === "join") return (await import("./join-EOLHE3LJ.js")).joinCommand(values, positionals, cwd, out);
641
+ if (verb === "join") return (await import("./join-64BU6SEY.js")).joinCommand(values, positionals, cwd, out);
636
642
  if (verb === "holds") holdsArgs(values, positionals);
637
643
  const bodyFromStdin = (verb === "send" || verb === "ask" || verb === "reply") && (positionals[1] === void 0 || positionals[1] === "-");
638
644
  const humanAdopt = verb === "adopt" && values.parent !== void 0 && values.as === void 0 && terminalAttached(process, false);
639
645
  const opened = humanAdopt ? await openAdoptHuman(cwd, values) : await open(verb, values, cwd, bodyFromStdin);
640
646
  const { client } = opened;
641
647
  try {
642
- if (verb === "machines") return await (await import("./remote-UQJ5YYCC.js")).machinesVerb(client, positionals, values.json === true);
648
+ if (verb === "machines") return await (await import("./remote-3R4GRDGI.js")).machinesVerb(client, positionals, values.json === true);
643
649
  return await runVerb(opened, verb, values, positionals, cwd);
644
650
  } finally {
645
651
  client.close();
@@ -647,7 +653,7 @@ async function relayMain(argv, cwd = process.cwd()) {
647
653
  }
648
654
  async function openAdoptHuman(cwd, values) {
649
655
  if (values.cloud || process.env.SCP_TRANSPORT === "cloud") {
650
- const { openCloud } = await import("./remote-UQJ5YYCC.js");
656
+ const { openCloud } = await import("./remote-3R4GRDGI.js");
651
657
  return { ...await openCloud({ tty: true, ...values.machine !== void 0 ? { machine: values.machine } : {} }), broker: null, bound: "human" };
652
658
  }
653
659
  if (values.machine !== void 0) throw new RelayUsage("--machine names the machine of the cloud transport: add --cloud");
@@ -832,9 +838,9 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
832
838
  return 0;
833
839
  }
834
840
  case "peek":
835
- return (await import("./peek-LFQR6TCS.js")).peekCommand(localClient(client, broker, verb), values, positionals);
841
+ return (await import("./peek-GEYQ7SHV.js")).peekCommand(localClient(client, broker, verb), values, positionals);
836
842
  case "set-peek":
837
- return (await import("./peek-LFQR6TCS.js")).setPeekCommand(localClient(client, broker, verb), values, positionals);
843
+ return (await import("./peek-GEYQ7SHV.js")).setPeekCommand(localClient(client, broker, verb), values, positionals);
838
844
  case "why": {
839
845
  const [from, to] = positionals;
840
846
  if (!from || !to || positionals.length > 2) throw new RelayUsage(`${CLI_NAME} why takes <from> <to>`);
@@ -1058,7 +1064,11 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
1058
1064
  return 0;
1059
1065
  }
1060
1066
  const before = endpoint ?? me?.address;
1061
- const view = await client.request("rename", { handle: positionals[0], ...endpoint !== void 0 ? { endpoint } : {} });
1067
+ const view = await client.request("rename", {
1068
+ handle: positionals[0],
1069
+ ...endpoint !== void 0 ? { endpoint } : {},
1070
+ ...values["take-over"] === true ? { take_over: true } : {}
1071
+ });
1062
1072
  if (asJson) json(view);
1063
1073
  else out(before && before !== view.address ? `${before} is now ${view.address}` : `${view.address}`);
1064
1074
  return 0;
@@ -1454,11 +1464,11 @@ async function daemon(idle, policyFile) {
1454
1464
  });
1455
1465
  let broker;
1456
1466
  try {
1457
- const { daemonBridge } = await import("./remote-UQJ5YYCC.js");
1467
+ const { daemonBridge } = await import("./remote-3R4GRDGI.js");
1458
1468
  const bridge = await daemonBridge();
1459
1469
  broker = await startBroker({
1460
1470
  dir: relayDir(),
1461
- ...bridge ? { bridge } : await (await import("./mesh-OZILTV5P.js")).meshBrokerOptions(),
1471
+ ...bridge ? { bridge } : await (await import("./mesh-XTMBRJLR.js")).meshBrokerOptions(),
1462
1472
  bridgeConfig: () => daemonBridge(),
1463
1473
  ...idleMs !== void 0 ? { idleMs } : {},
1464
1474
  ...policyFile ? { policyPath: resolve(policyFile) } : {}
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  spawnDetached
3
- } from "./chunk-7RA7E5WR.js";
3
+ } from "./chunk-CVW3INZO.js";
4
4
  import "./chunk-KN7WEIE4.js";
5
- import "./chunk-UKQBH4AQ.js";
5
+ import "./chunk-DEK3TE6T.js";
6
6
  import {
7
7
  mcpInstall
8
- } from "./chunk-2342GTME.js";
9
- import "./chunk-XQHPDNVG.js";
8
+ } from "./chunk-C5PYVRJE.js";
9
+ import "./chunk-GG33NSBW.js";
10
10
  import "./chunk-YYSLYVNZ.js";
11
11
  import {
12
12
  CHANNELS_ENV,
@@ -18,7 +18,7 @@ import {
18
18
  relayIdentity,
19
19
  startDaemon,
20
20
  writeShimBinding
21
- } from "./chunk-AAFHLHXG.js";
21
+ } from "./chunk-BSX24XMZ.js";
22
22
  import "./chunk-MNTIEWKF.js";
23
23
 
24
24
  // src/mcp.ts
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RelayUsage
3
- } from "./chunk-AAFHLHXG.js";
3
+ } from "./chunk-BSX24XMZ.js";
4
4
  import "./chunk-MNTIEWKF.js";
5
5
 
6
6
  // src/mesh.ts
@@ -2,18 +2,18 @@ import {
2
2
  mcpFile,
3
3
  mountedEntry,
4
4
  whichOnPath
5
- } from "./chunk-UKQBH4AQ.js";
5
+ } from "./chunk-DEK3TE6T.js";
6
6
  import {
7
7
  hooksTarget
8
- } from "./chunk-2342GTME.js";
8
+ } from "./chunk-C5PYVRJE.js";
9
9
  import {
10
10
  opencodePlugin
11
- } from "./chunk-XQHPDNVG.js";
11
+ } from "./chunk-GG33NSBW.js";
12
12
  import "./chunk-YYSLYVNZ.js";
13
13
  import {
14
14
  RelayUsage,
15
15
  gitToplevel
16
- } from "./chunk-AAFHLHXG.js";
16
+ } from "./chunk-BSX24XMZ.js";
17
17
  import "./chunk-MNTIEWKF.js";
18
18
 
19
19
  // src/opencode.ts
@@ -2,7 +2,7 @@ import {
2
2
  RelayUsage,
3
3
  peekGrant,
4
4
  stamp
5
- } from "./chunk-AAFHLHXG.js";
5
+ } from "./chunk-BSX24XMZ.js";
6
6
  import {
7
7
  note
8
8
  } from "./chunk-MNTIEWKF.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RelayUsage
3
- } from "./chunk-AAFHLHXG.js";
3
+ } from "./chunk-BSX24XMZ.js";
4
4
  import "./chunk-MNTIEWKF.js";
5
5
 
6
6
  // src/pi.ts
@@ -13,7 +13,7 @@ import {
13
13
  saveCredential,
14
14
  setJoinMode,
15
15
  writePrivateJson
16
- } from "./chunk-AAFHLHXG.js";
16
+ } from "./chunk-BSX24XMZ.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-H2XQXMZU.js";
13
- import "./chunk-AAFHLHXG.js";
12
+ } from "./chunk-C7ZUACP7.js";
13
+ import "./chunk-BSX24XMZ.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-UKQBH4AQ.js";
19
- import "./chunk-2342GTME.js";
20
- import "./chunk-XQHPDNVG.js";
18
+ } from "./chunk-DEK3TE6T.js";
19
+ import "./chunk-C5PYVRJE.js";
20
+ import "./chunk-GG33NSBW.js";
21
21
  import "./chunk-YYSLYVNZ.js";
22
- import "./chunk-AAFHLHXG.js";
22
+ import "./chunk-BSX24XMZ.js";
23
23
  import "./chunk-MNTIEWKF.js";
24
24
  export {
25
25
  SETUP_HARNESSES,
@@ -31,15 +31,15 @@ import {
31
31
  toml,
32
32
  transcriptReplies,
33
33
  workerName
34
- } from "./chunk-7RA7E5WR.js";
34
+ } from "./chunk-CVW3INZO.js";
35
35
  import {
36
36
  SPAWN_HELP
37
37
  } from "./chunk-KN7WEIE4.js";
38
- import "./chunk-UKQBH4AQ.js";
39
- import "./chunk-2342GTME.js";
40
- import "./chunk-XQHPDNVG.js";
38
+ import "./chunk-DEK3TE6T.js";
39
+ import "./chunk-C5PYVRJE.js";
40
+ import "./chunk-GG33NSBW.js";
41
41
  import "./chunk-YYSLYVNZ.js";
42
- import "./chunk-AAFHLHXG.js";
42
+ import "./chunk-BSX24XMZ.js";
43
43
  import "./chunk-MNTIEWKF.js";
44
44
  export {
45
45
  CODEX_VALUED,
@@ -2,7 +2,7 @@ import {
2
2
  RelayUsage,
3
3
  activeRelay,
4
4
  relayCredential
5
- } from "./chunk-AAFHLHXG.js";
5
+ } from "./chunk-BSX24XMZ.js";
6
6
  import "./chunk-MNTIEWKF.js";
7
7
 
8
8
  // src/up.ts
@@ -29,21 +29,21 @@ async function up(positionals, values, cwd, brokerStatus) {
29
29
  const { url } = activeRelay(process.env);
30
30
  const headless = values.token !== void 0 || Boolean(process.env.SCP_TOKEN?.trim());
31
31
  if (headless || !relayCredential(process.env, url).token) {
32
- const code = await (await import("./login-6MVD3VLC.js")).login({
32
+ const code = await (await import("./login-4IF26HH5.js")).login({
33
33
  ...values.token !== void 0 ? { token: values.token } : {},
34
34
  ...values.label !== void 0 ? { label: values.label } : {},
35
35
  ...values["no-browser"] ? { "no-browser": true } : {}
36
36
  });
37
37
  if (code !== 0) return code;
38
38
  } else out(`already logged in to ${url}`);
39
- const enabled = await (await import("./remote-UQJ5YYCC.js")).cloudMain(["enable"], values.machine !== void 0 ? { machine: values.machine } : {}, brokerStatus);
39
+ const enabled = await (await import("./remote-3R4GRDGI.js")).cloudMain(["enable"], values.machine !== void 0 ? { machine: values.machine } : {}, brokerStatus);
40
40
  if (enabled !== 0) return enabled;
41
- const setup = (await import("./setup-UJCIMYU4.js")).relaySetupCommand(["--write"], cwd, out);
41
+ const setup = (await import("./setup-O45SE5L7.js")).relaySetupCommand(["--write"], cwd, out);
42
42
  if (setup !== 0) return setup;
43
43
  const broker = await brokerStatus();
44
44
  if (!broker.enabled) out(`the broker is still local-only; run \`${CLI_NAME} daemon restart\` to link it to ${url}`);
45
45
  else out(`broker linked to ${broker.url ?? url}${broker.connected ? "" : " (connecting)"}`);
46
- const { detectHarnesses, whichOnPath } = await import("./setup-UJCIMYU4.js");
46
+ const { detectHarnesses, whichOnPath } = await import("./setup-O45SE5L7.js");
47
47
  const which = (name) => whichOnPath(name, process.env);
48
48
  const installed = new Set(detectHarnesses(process.env, which).keys());
49
49
  if (which("pi")) installed.add("pi");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scp-dev/cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "The swarm command line tool.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://scp.dev",
@@ -24,15 +24,15 @@
24
24
  "typecheck": "tsc -p ."
25
25
  },
26
26
  "dependencies": {
27
- "@scp-dev/core": "0.3.0",
28
- "@scp-dev/broker": "0.4.0",
29
- "@scp-dev/hub": "0.6.1",
30
- "@scp-dev/mcp": "0.2.0",
27
+ "@scp-dev/core": "0.3.1",
28
+ "@scp-dev/broker": "0.4.1",
29
+ "@scp-dev/hub": "0.6.2",
30
+ "@scp-dev/mcp": "0.2.1",
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.7",
35
+ "@scp-dev/mesh": "0.0.8",
36
36
  "opencode-ai": "1.18.34",
37
37
  "tsup": "^8.5.1"
38
38
  },
@@ -1,130 +0,0 @@
1
- import {
2
- entryArgv,
3
- mcpFile,
4
- mountedEntry,
5
- whichOnPath
6
- } from "./chunk-UKQBH4AQ.js";
7
- import "./chunk-2342GTME.js";
8
- import "./chunk-XQHPDNVG.js";
9
- import "./chunk-YYSLYVNZ.js";
10
- import {
11
- CHANNELS_ENV,
12
- RelayUsage,
13
- deriveHandle,
14
- joinMode
15
- } from "./chunk-AAFHLHXG.js";
16
- import "./chunk-MNTIEWKF.js";
17
-
18
- // src/claude.ts
19
- import { spawn } from "child_process";
20
- import { existsSync, readFileSync } from "fs";
21
- import { join } from "path";
22
- import { CLI_NAME, validHandle } from "@scp-dev/core";
23
- var CHANNEL_FLAGS = ["--dangerously-load-development-channels", "server:scp"];
24
- function claudeArgv(argv) {
25
- let name;
26
- let i = 0;
27
- for (; ; ) {
28
- const arg = argv[i];
29
- if (arg === "--name" || arg === "--as") {
30
- if (argv[i + 1] === void 0) throw new RelayUsage(`${CLI_NAME} claude ${arg} takes a handle`);
31
- name = argv[i + 1];
32
- i += 2;
33
- } else if (arg?.startsWith("--name=") || arg?.startsWith("--as=")) {
34
- name = arg.slice(arg.indexOf("=") + 1);
35
- i++;
36
- } else break;
37
- }
38
- if (argv[i] === "--") i++;
39
- name = name?.replace(/^me\//, "");
40
- if (name !== void 0 && !validHandle(name)) throw new RelayUsage(`--name ${JSON.stringify(name)} is not a valid agent handle`);
41
- return { ...name !== void 0 ? { name } : {}, args: argv.slice(i) };
42
- }
43
- function claudeLaunchArgs(args, added) {
44
- const end = args.indexOf("--");
45
- const at = end < 0 ? args.length : end;
46
- const channels = args.some((arg, i) => i < at && (arg === "server:scp" || arg === `${CHANNEL_FLAGS[0]}=server:scp`)) ? [] : CHANNEL_FLAGS;
47
- return [...args.slice(0, at), ...channels, ...added, ...args.slice(at)];
48
- }
49
- var STRICT_HINT = `${CLI_NAME} claude: --strict-mcp-config without --mcp-config drops the scp mount, so messages cannot wake this session`;
50
- function externalMcp(args) {
51
- return args.some((arg) => arg === "--mcp-config" || arg.startsWith("--mcp-config="));
52
- }
53
- function namePrompt(name, args, env, cwd) {
54
- if (name !== void 0 || joinMode(env) === "manual" || args.some((arg) => /^--(append-)?system-prompt/.test(arg))) return [];
55
- const handle = env.SCP_NAME || deriveHandle("claude-code", cwd);
56
- return [
57
- "--append-system-prompt",
58
- `You are on the SCP network as ${handle}. Pick a short descriptive name for yourself with \`${CLI_NAME} name <name>\` when you know what you're working on.`
59
- ];
60
- }
61
- function mountHint(env, cwd = process.cwd()) {
62
- const file = mcpFile("claude", env);
63
- const entries = [];
64
- try {
65
- const user = mountedEntry("claude", file);
66
- if (user) entries.push(user);
67
- const config = existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : {};
68
- const local = config.projects?.[cwd]?.mcpServers?.scp;
69
- if (local && typeof local === "object") entries.push(local);
70
- } catch {
71
- return `${CLI_NAME} claude: ${file} is unreadable, so the scp mount is unchecked; fix it or run ${CLI_NAME} setup --write`;
72
- }
73
- const project = join(cwd, ".mcp.json");
74
- try {
75
- const entry = existsSync(project) ? mountedEntry("claude", project) : void 0;
76
- if (entry) entries.push(entry);
77
- } catch {
78
- }
79
- if (!entries.length) return `${CLI_NAME} claude: Claude Code has no scp MCP mount, so messages cannot wake this session; run ${CLI_NAME} setup --write`;
80
- if (!entries.some((entry) => entryArgv("claude", entry).includes("--channels")))
81
- return `${CLI_NAME} claude: the scp mount lacks --channels, so messages cannot wake this session; run ${CLI_NAME} setup --write`;
82
- return void 0;
83
- }
84
- async function claudeCommand(argv, env = process.env, err = (line) => void process.stderr.write(`${line}
85
- `), cwd = process.cwd()) {
86
- if (argv[0] === "--help" || argv[0] === "-h") {
87
- process.stdout.write(
88
- `${CLI_NAME} claude [--name N | --as N] [--] [claude args...] start Claude Code with the scp channel loaded, so peer messages wake it
89
- `
90
- );
91
- return 0;
92
- }
93
- const { name, args } = claudeArgv(argv);
94
- const bin = whichOnPath("claude", env);
95
- if (!bin) throw new RelayUsage("claude is not on PATH");
96
- const external = externalMcp(args);
97
- const hint = external ? void 0 : args.includes("--strict-mcp-config") ? STRICT_HINT : mountHint(env, cwd);
98
- if (hint) throw new RelayUsage(hint);
99
- if (external) err(`${CLI_NAME} claude: using your --mcp-config; it needs the scp server with --channels for messages to wake this session`);
100
- const child = spawn(bin, claudeLaunchArgs(args, namePrompt(name, args, env, cwd)), {
101
- cwd,
102
- stdio: "inherit",
103
- env: { ...env, ...name !== void 0 ? { SCP_NAME: name } : {}, [CHANNELS_ENV]: String(process.pid) }
104
- });
105
- const forward = (signal) => () => void child.kill(signal);
106
- const handlers = ["SIGTERM", "SIGHUP"].map((signal) => [signal, forward(signal)]);
107
- const ignore = () => {
108
- };
109
- process.on("SIGINT", ignore);
110
- for (const [signal, handler] of handlers) process.on(signal, handler);
111
- try {
112
- return await new Promise((resolve, reject) => {
113
- child.once("error", reject);
114
- child.once("exit", (code, signal) => resolve(code ?? (signal ? 128 + (signalNumber(signal) ?? 1) : 1)));
115
- });
116
- } finally {
117
- process.off("SIGINT", ignore);
118
- for (const [signal, handler] of handlers) process.off(signal, handler);
119
- }
120
- }
121
- var SIGNALS = { SIGHUP: 1, SIGINT: 2, SIGKILL: 9, SIGTERM: 15 };
122
- var signalNumber = (signal) => SIGNALS[signal];
123
- export {
124
- CHANNEL_FLAGS,
125
- claudeArgv,
126
- claudeCommand,
127
- claudeLaunchArgs,
128
- mountHint,
129
- namePrompt
130
- };