@scp-dev/cli 0.2.4 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-E7JG5PJB.js → chunk-2LIJXXTL.js} +53 -9
- package/dist/{chunk-W3VNCRBX.js → chunk-2OSUVJ2J.js} +3 -3
- package/dist/{chunk-IXCHAWOL.js → chunk-3SHVJIXP.js} +5 -3
- package/dist/{chunk-ZDU53ZRF.js → chunk-IEFXYBX7.js} +6 -3
- package/dist/{chunk-ZMVFN6MU.js → chunk-Q5UI4Z7H.js} +2 -2
- package/dist/{chunk-X4ZLEM47.js → chunk-QRWQIHV6.js} +56 -8
- package/dist/claude-EQVLST73.js +130 -0
- package/dist/cli.js +1 -1
- package/dist/{codex-4IIM6ZE2.js → codex-5GZ7YPES.js} +73 -24
- package/dist/{hooks-P4J6OZCO.js → hooks-B34E2HIP.js} +6 -2
- package/dist/{install-FKGGPT6L.js → install-6KXJY27V.js} +3 -3
- package/dist/{join-RGFHOSLE.js → join-FITJVZCA.js} +3 -1
- package/dist/{login-IPS75GLQ.js → login-AUMLI4FS.js} +2 -2
- package/dist/{main-MCNGHJP2.js → main-ZAYVXFAG.js} +63 -27
- package/dist/{mcp-TUWMLGE4.js → mcp-VXBRMQB7.js} +14 -6
- package/dist/{mesh-VNZV3POO.js → mesh-H4KL5XAR.js} +1 -1
- package/dist/opencode-ORLGNHWR.js +116 -0
- package/dist/{peek-4D4VGKOW.js → peek-YTCWVPF7.js} +1 -1
- package/dist/{pi-JZ4DXY7V.js → pi-QEZVFDNR.js} +8 -10
- package/dist/pi-extension.js +22 -7
- package/dist/{relay-U634RHIJ.js → relay-QWJM372C.js} +1 -1
- package/dist/{remote-NTHRHVEP.js → remote-P4GFUIAP.js} +2 -2
- package/dist/{setup-RO7356M4.js → setup-MGDWKCNU.js} +4 -4
- package/dist/{spawn-BYU2X46L.js → spawn-GG7VFZA6.js} +9 -5
- package/dist/up-B2NEOOC6.js +57 -0
- package/package.json +6 -6
- package/dist/claude-JD7WXMAT.js +0 -98
- package/dist/up-GDD24GPL.js +0 -35
|
@@ -23,7 +23,7 @@ import { existsSync, realpathSync } from "fs";
|
|
|
23
23
|
import { tmpdir } from "os";
|
|
24
24
|
import { basename, dirname, isAbsolute, join, resolve } from "path";
|
|
25
25
|
import { shortHostname } from "@scp-dev/broker";
|
|
26
|
-
import { LIMITS, validHandle, validHarness } from "@scp-dev/core";
|
|
26
|
+
import { GENERIC_HARNESSES, LIMITS, validHandle, validHarness } from "@scp-dev/core";
|
|
27
27
|
var SHORT = { "claude-code": "claude" };
|
|
28
28
|
var SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "zsh", "dash", "fish"]);
|
|
29
29
|
var LABEL_KEY = /^[a-z0-9][a-z0-9._-]{0,31}$/;
|
|
@@ -156,6 +156,42 @@ function command(args) {
|
|
|
156
156
|
return basename(args.split(/\s+/)[0].replace(/^-/, "")).toLowerCase();
|
|
157
157
|
}
|
|
158
158
|
var namesHarness = (words) => words.some((word) => !word.startsWith("-") && word.split("/").some((part) => HARNESSES.has(part.toLowerCase().replace(/\..*$/, ""))));
|
|
159
|
+
var HARNESS_IDS = /* @__PURE__ */ new Map([
|
|
160
|
+
["claude", "claude-code"],
|
|
161
|
+
["claude-code", "claude-code"],
|
|
162
|
+
["codex", "codex"],
|
|
163
|
+
["opencode", "opencode"],
|
|
164
|
+
["opencode-ai", "opencode"],
|
|
165
|
+
["grok", "grok"],
|
|
166
|
+
["grok-cli", "grok"],
|
|
167
|
+
["muse", "muse"],
|
|
168
|
+
["agy", "agy"],
|
|
169
|
+
["antigravity", "agy"],
|
|
170
|
+
["gemini", "gemini"],
|
|
171
|
+
["gemini-cli", "gemini"],
|
|
172
|
+
["pi", "pi"],
|
|
173
|
+
["pi-coding-agent", "pi"]
|
|
174
|
+
]);
|
|
175
|
+
var harnessId = (name) => HARNESS_IDS.get(name.toLowerCase().replace(/\..*$/, ""));
|
|
176
|
+
function harnessOf(args) {
|
|
177
|
+
if (!args) return void 0;
|
|
178
|
+
const name = command(args);
|
|
179
|
+
const own = harnessId(name);
|
|
180
|
+
if (own) return own;
|
|
181
|
+
if (!INTERPRETERS.has(name) && !RUNNERS.has(name) && !FETCHERS.has(name) && !/^python[0-9.]*$/.test(name)) return void 0;
|
|
182
|
+
for (const word of args.split(/\s+/).slice(1)) {
|
|
183
|
+
if (word.startsWith("-")) continue;
|
|
184
|
+
for (const part of word.split("/").reverse()) {
|
|
185
|
+
const id = part === "pi" ? void 0 : harnessId(part);
|
|
186
|
+
if (id) return id;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return void 0;
|
|
190
|
+
}
|
|
191
|
+
var inferring = (env) => env.SCP_HARNESS_DETECT !== "0";
|
|
192
|
+
function harnessAt(pid, table = readPsTable()) {
|
|
193
|
+
return inferring(process.env) ? harnessOf(table?.get(pid)?.args) : void 0;
|
|
194
|
+
}
|
|
159
195
|
var roots;
|
|
160
196
|
function workspaceRoots() {
|
|
161
197
|
if (roots) return roots;
|
|
@@ -284,10 +320,11 @@ function hookHarnessPid(env, harness, ppid = process.ppid, parentOf = psParent)
|
|
|
284
320
|
return harnessPid(ppid, parentOf);
|
|
285
321
|
}
|
|
286
322
|
function relayIdentity(input) {
|
|
287
|
-
const
|
|
323
|
+
const resolved = resolveHarness(input.harness, input.env, input.fallbackHarness);
|
|
324
|
+
const pid = input.harnessPid(resolved);
|
|
325
|
+
const harness = GENERIC_HARNESSES.has(resolved) && input.inferHarness?.(pid) || resolved;
|
|
288
326
|
const cwd = isAbsolute(input.cwd) ? input.cwd : resolve(input.cwd);
|
|
289
327
|
const handle = input.as ?? input.env.SCP_NAME ?? deriveHandle(harness, cwd);
|
|
290
|
-
const pid = input.harnessPid(harness);
|
|
291
328
|
const harnessProcess = processKey(pid, input.startOf);
|
|
292
329
|
const keys = input.lineageOf ? input.lineageOf(pid) : lineage(pid);
|
|
293
330
|
return {
|
|
@@ -464,7 +501,12 @@ function localHandle(raw) {
|
|
|
464
501
|
}
|
|
465
502
|
function walkNow() {
|
|
466
503
|
const table = readPsTable();
|
|
467
|
-
|
|
504
|
+
const self = harnessLineage(process.ppid, table);
|
|
505
|
+
const harness = self.length ? harnessAt(Number(/^pid:(\d+)/.exec(self.at(-1))?.[1]), table) : void 0;
|
|
506
|
+
return { keys: lineage(process.ppid, table), self, ...harness ? { harness } : {} };
|
|
507
|
+
}
|
|
508
|
+
function claimHarness(values, env, walk) {
|
|
509
|
+
return values.harness ?? env.SCP_HARNESS ?? (inferring(env) ? walk?.harness ?? detectHarness(env) : void 0) ?? "cli";
|
|
468
510
|
}
|
|
469
511
|
async function open(verb, values, cwd, bodyFromStdin, walk) {
|
|
470
512
|
const named = values.as ?? process.env.SCP_NAME;
|
|
@@ -487,11 +529,11 @@ async function open(verb, values, cwd, bodyFromStdin, walk) {
|
|
|
487
529
|
`${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)`
|
|
488
530
|
);
|
|
489
531
|
}
|
|
490
|
-
const { machineName, openCloud } = await import("./remote-
|
|
532
|
+
const { machineName, openCloud } = await import("./remote-P4GFUIAP.js");
|
|
491
533
|
const handle2 = named ? localHandle(named) : void 0;
|
|
492
534
|
const claim = handle2 ? {
|
|
493
535
|
handle: handle2,
|
|
494
|
-
harness: values
|
|
536
|
+
harness: claimHarness(values, process.env, walk ?? walkNow()),
|
|
495
537
|
cwd,
|
|
496
538
|
...process.env.SCP_PARENT ? { parent: process.env.SCP_PARENT } : {},
|
|
497
539
|
labels: labelsFor(process.env, machineName(values.machine))
|
|
@@ -510,7 +552,8 @@ async function open(verb, values, cwd, bodyFromStdin, walk) {
|
|
|
510
552
|
};
|
|
511
553
|
}
|
|
512
554
|
if (values.machine !== void 0) throw new RelayUsage("--machine names the machine of the cloud transport: add --cloud");
|
|
513
|
-
const
|
|
555
|
+
const here = walk ?? walkNow();
|
|
556
|
+
const { keys, self } = here;
|
|
514
557
|
const joined = !values.as && named && self.length ? membership(process.env, sessionKey(process.env, self.at(-1))) : {};
|
|
515
558
|
const handle = "handle" in joined && joined.handle ? joined.handle : named ? localHandle(named) : void 0;
|
|
516
559
|
const harnessPid2 = self.length ? Number(/^pid:(\d+)/.exec(self.at(-1))?.[1]) : void 0;
|
|
@@ -526,7 +569,7 @@ async function open(verb, values, cwd, bodyFromStdin, walk) {
|
|
|
526
569
|
claim: {
|
|
527
570
|
handle,
|
|
528
571
|
exact: true,
|
|
529
|
-
harness: values
|
|
572
|
+
harness: claimHarness(values, process.env, here),
|
|
530
573
|
cwd,
|
|
531
574
|
...process.env.SCP_PARENT ? { parent: process.env.SCP_PARENT } : {},
|
|
532
575
|
labels: labelsFor(process.env),
|
|
@@ -744,7 +787,7 @@ function relayVerb(positionals, asJson, env = process.env) {
|
|
|
744
787
|
}
|
|
745
788
|
async function loginMain(positionals, values) {
|
|
746
789
|
if (positionals.length) throw new RelayUsage(`${CLI_NAME3} login takes only flags`);
|
|
747
|
-
return (await import("./login-
|
|
790
|
+
return (await import("./login-AUMLI4FS.js")).login(values);
|
|
748
791
|
}
|
|
749
792
|
function logoutMain(positionals, asJson, env = process.env) {
|
|
750
793
|
if (positionals.length) throw new RelayUsage(`${CLI_NAME3} logout takes no arguments`);
|
|
@@ -767,6 +810,7 @@ export {
|
|
|
767
810
|
sessionKey,
|
|
768
811
|
psStart,
|
|
769
812
|
readPsTable,
|
|
813
|
+
harnessAt,
|
|
770
814
|
spawnerKey,
|
|
771
815
|
psParent,
|
|
772
816
|
harnessPid,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hooksTarget
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-Q5UI4Z7H.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-
|
|
15
|
+
} from "./chunk-QRWQIHV6.js";
|
|
16
16
|
import {
|
|
17
17
|
RelayUsage,
|
|
18
18
|
gitToplevel,
|
|
19
19
|
setJoinMode
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-2LIJXXTL.js";
|
|
21
21
|
|
|
22
22
|
// src/setup.ts
|
|
23
23
|
import { spawnSync } from "child_process";
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
agySettingsFile,
|
|
4
4
|
mcpFile,
|
|
5
5
|
mountedEntry
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-2OSUVJ2J.js";
|
|
7
7
|
import {
|
|
8
8
|
steerPrompt
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QRWQIHV6.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-
|
|
21
|
+
} from "./chunk-2LIJXXTL.js";
|
|
22
22
|
import {
|
|
23
23
|
SELF,
|
|
24
24
|
note
|
|
@@ -918,7 +918,9 @@ export {
|
|
|
918
918
|
SPAWN_RUNNER,
|
|
919
919
|
spawnEnvironment,
|
|
920
920
|
codexMount,
|
|
921
|
+
toml,
|
|
921
922
|
codexOverrides,
|
|
923
|
+
CODEX_VALUED,
|
|
922
924
|
codexOverrideAt,
|
|
923
925
|
programAt,
|
|
924
926
|
MESSAGING,
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
configDir,
|
|
6
6
|
relayCredential,
|
|
7
7
|
writePrivateJson
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-2LIJXXTL.js";
|
|
9
9
|
import {
|
|
10
10
|
note,
|
|
11
11
|
printTable
|
|
@@ -150,13 +150,15 @@ function readCloudFile(path, env = process.env) {
|
|
|
150
150
|
}
|
|
151
151
|
if (value.redact !== void 0 && typeof value.redact !== "boolean") throw new Error(`${path}: redact is true or false`);
|
|
152
152
|
if (value.private_ok !== void 0 && typeof value.private_ok !== "boolean") throw new Error(`${path}: private_ok is true or false`);
|
|
153
|
+
if (value.mirror_local !== void 0 && typeof value.mirror_local !== "boolean") throw new Error(`${path}: mirror_local is true or false`);
|
|
153
154
|
return {
|
|
154
155
|
bridge: true,
|
|
155
156
|
machine: value.machine,
|
|
156
157
|
url,
|
|
157
158
|
...value.mirror ? { mirror: value.mirror } : {},
|
|
158
159
|
...value.redact !== void 0 ? { redact: value.redact } : {},
|
|
159
|
-
...value.private_ok !== void 0 ? { private_ok: value.private_ok } : {}
|
|
160
|
+
...value.private_ok !== void 0 ? { private_ok: value.private_ok } : {},
|
|
161
|
+
...value.mirror_local !== void 0 ? { mirror_local: value.mirror_local } : {}
|
|
160
162
|
};
|
|
161
163
|
}
|
|
162
164
|
async function daemonBridge(env = process.env, log = console.error) {
|
|
@@ -184,7 +186,8 @@ async function daemonBridge(env = process.env, log = console.error) {
|
|
|
184
186
|
machine: file.machine,
|
|
185
187
|
mirror: file.mirror,
|
|
186
188
|
redact: file.redact,
|
|
187
|
-
privateOk: file.private_ok
|
|
189
|
+
privateOk: file.private_ok,
|
|
190
|
+
mirrorLocal: file.mirror_local
|
|
188
191
|
};
|
|
189
192
|
}
|
|
190
193
|
async function cloudMain(positionals, values, brokerStatus) {
|
|
@@ -10,11 +10,11 @@ import {
|
|
|
10
10
|
mergeHooks,
|
|
11
11
|
musePlugin,
|
|
12
12
|
opencodePlugin
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-QRWQIHV6.js";
|
|
14
14
|
import {
|
|
15
15
|
RelayUsage,
|
|
16
16
|
gitToplevel
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-2LIJXXTL.js";
|
|
18
18
|
|
|
19
19
|
// src/install.ts
|
|
20
20
|
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
readCapped
|
|
5
5
|
} from "./chunk-YYSLYVNZ.js";
|
|
6
6
|
import {
|
|
7
|
+
harnessAt,
|
|
7
8
|
hookHarness,
|
|
8
9
|
hookHarnessPid,
|
|
9
10
|
membership,
|
|
@@ -12,7 +13,7 @@ import {
|
|
|
12
13
|
psStart,
|
|
13
14
|
relayIdentity,
|
|
14
15
|
sessionKey
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-2LIJXXTL.js";
|
|
16
17
|
|
|
17
18
|
// src/hooks.ts
|
|
18
19
|
import { Readable } from "stream";
|
|
@@ -354,9 +355,44 @@ function launchedSubagent(harness, event, payload) {
|
|
|
354
355
|
const title = titleText(asString(input.description));
|
|
355
356
|
return id && title && /^[A-Za-z0-9_-]{1,64}$/.test(id) ? { id, title } : void 0;
|
|
356
357
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
358
|
+
var CLAUDE_CAPS = { context: { chars: 8e3, lines: 200 }, reason: { chars: 2e3, lines: 20 } };
|
|
359
|
+
var MARKER_ROOM = 80;
|
|
360
|
+
var BLOCK = /(<relay-message [^>]*?\bshort="([^"]*)"[^>]*>\n)([\s\S]*?)(\n<\/relay-message>)/g;
|
|
361
|
+
var cutAt = (text, chars) => text.slice(0, chars > 0 && /[\ud800-\udbff]/.test(text[chars - 1] ?? "") ? chars - 1 : chars);
|
|
362
|
+
function fitText(text, cap2) {
|
|
363
|
+
const parts = [];
|
|
364
|
+
let at = 0;
|
|
365
|
+
for (const match of text.matchAll(BLOCK)) {
|
|
366
|
+
parts.push(text.slice(at, match.index) + match[1], { short: match[2], body: match[3], chars: match[3].length, lines: match[3].split("\n").length });
|
|
367
|
+
at = match.index + match[0].length - match[4].length;
|
|
368
|
+
}
|
|
369
|
+
parts.push(text.slice(at));
|
|
370
|
+
const blocks = parts.filter((part) => typeof part !== "string");
|
|
371
|
+
const render = () => parts.map((part) => {
|
|
372
|
+
if (typeof part === "string") return part;
|
|
373
|
+
if (part.chars >= part.body.length && part.lines >= part.body.split("\n").length) return part.body;
|
|
374
|
+
const head = cutAt(part.body.split("\n").slice(0, part.lines).join("\n"), part.chars).trimEnd();
|
|
375
|
+
const marker = `[truncated, ${part.body.length - head.length} more chars: ${CLI_NAME} show ${part.short}]`;
|
|
376
|
+
return head ? `${head}
|
|
377
|
+
${marker}` : marker;
|
|
378
|
+
}).join("");
|
|
379
|
+
for (let i = blocks.length - 1, rendered = render(); ; rendered = render()) {
|
|
380
|
+
const chars = rendered.length - cap2.chars;
|
|
381
|
+
const lines = rendered.split("\n").length - cap2.lines;
|
|
382
|
+
if (chars <= 0 && lines <= 0) return rendered;
|
|
383
|
+
while (i >= 0 && blocks[i].chars === 0 && blocks[i].lines === 0) i--;
|
|
384
|
+
if (i < 0) return rendered;
|
|
385
|
+
const block = blocks[i];
|
|
386
|
+
if (chars > 0) block.chars = Math.max(0, Math.min(block.chars, block.body.length) - chars - MARKER_ROOM);
|
|
387
|
+
if (lines > 0) block.lines = Math.max(0, Math.min(block.lines, block.body.split("\n").length) - lines - 1);
|
|
388
|
+
if (chars <= 0 && block.lines === 0) block.chars = 0;
|
|
389
|
+
if (lines <= 0 && block.chars === 0) block.lines = 0;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function hookReply(harness, kind, raw, unread = 0, continues = true) {
|
|
393
|
+
const claude = harness === "claude-code";
|
|
394
|
+
const text = claude ? fitText(raw, kind === "stop" && !continues ? CLAUDE_CAPS.reason : CLAUDE_CAPS.context) : raw;
|
|
395
|
+
if (kind === "stop" && claude && continues) return JSON.stringify({ hookSpecificOutput: { hookEventName: "Stop", additionalContext: text } });
|
|
360
396
|
if (kind === "stop")
|
|
361
397
|
return JSON.stringify(
|
|
362
398
|
harness === "agy" ? { decision: "continue", reason: text } : { decision: "block", reason: text, ...harness === "opencode" ? { unread } : {} }
|
|
@@ -416,6 +452,7 @@ async function runHook(args, options) {
|
|
|
416
452
|
cwd: event.cwd ?? options.cwd,
|
|
417
453
|
env: options.env,
|
|
418
454
|
harnessPid: () => pid,
|
|
455
|
+
...options.harnessPid ? {} : { inferHarness: (at) => harnessAt(at) },
|
|
419
456
|
startOf: () => started
|
|
420
457
|
});
|
|
421
458
|
if (!validHandle(claim.handle)) return;
|
|
@@ -513,7 +550,8 @@ async function wakeHook(harnessArg, payload, waitS, options) {
|
|
|
513
550
|
fallbackHarness: "hook",
|
|
514
551
|
cwd: asString(payload.cwd) ?? options.cwd,
|
|
515
552
|
env: options.env,
|
|
516
|
-
harnessPid: options.harnessPid ?? ((harness) => hookHarnessPid(options.env, harness))
|
|
553
|
+
harnessPid: options.harnessPid ?? ((harness) => hookHarnessPid(options.env, harness)),
|
|
554
|
+
...options.harnessPid ? {} : { inferHarness: (at) => harnessAt(at) }
|
|
517
555
|
});
|
|
518
556
|
if (!validHandle(claim.handle)) return;
|
|
519
557
|
const waitMs = Math.min(waitS, LIMITS.waitMaxS) * 1e3;
|
|
@@ -729,6 +767,8 @@ function opencodePlugin(options = {}) {
|
|
|
729
767
|
import { spawn } from "node:child_process"
|
|
730
768
|
import { text as readAll } from "node:stream/consumers"
|
|
731
769
|
const BLOCK_STOP = ${options.blockStop === true}
|
|
770
|
+
/** before a session exists, the waiter starts this long after load, so a session opened at launch claims the first message */
|
|
771
|
+
const STARTUP_MS = 3000
|
|
732
772
|
|
|
733
773
|
type Reply = { hookSpecificOutput?: { additionalContext?: unknown }; decision?: unknown; reason?: unknown }
|
|
734
774
|
|
|
@@ -775,6 +815,7 @@ export const Scp = async ({ client, directory }: { client: any; directory: strin
|
|
|
775
815
|
const WAKE = process.env.SCP_WAKE !== "0"
|
|
776
816
|
const busy = new Set<string>()
|
|
777
817
|
let target: string | undefined
|
|
818
|
+
let fresh = true
|
|
778
819
|
let waiter: ReturnType<typeof spawn> | undefined
|
|
779
820
|
let looping = false
|
|
780
821
|
let armed = 0
|
|
@@ -784,7 +825,7 @@ export const Scp = async ({ client, directory }: { client: any; directory: strin
|
|
|
784
825
|
looping = true
|
|
785
826
|
let version = armed
|
|
786
827
|
try {
|
|
787
|
-
while (WAKE &&
|
|
828
|
+
while (WAKE && busy.size === 0 && (target || fresh)) {
|
|
788
829
|
const id = target
|
|
789
830
|
const began = Date.now()
|
|
790
831
|
let text: string | undefined
|
|
@@ -797,7 +838,7 @@ export const Scp = async ({ client, directory }: { client: any; directory: strin
|
|
|
797
838
|
child.once("close", () => resolve())
|
|
798
839
|
})
|
|
799
840
|
child.stdin!.on("error", () => {})
|
|
800
|
-
child.stdin!.end(JSON.stringify({ hook_event_name: "Wake", session_id: id, cwd: directory }))
|
|
841
|
+
child.stdin!.end(JSON.stringify({ hook_event_name: "Wake", ...(id ? { session_id: id } : {}), cwd: directory }))
|
|
801
842
|
const [output] = await Promise.all([readAll(child.stdout!), ended])
|
|
802
843
|
text = output.trim() ? context(JSON.parse(output) as Reply) : undefined
|
|
803
844
|
} catch {} finally {
|
|
@@ -805,7 +846,8 @@ export const Scp = async ({ client, directory }: { client: any; directory: strin
|
|
|
805
846
|
}
|
|
806
847
|
if (text) {
|
|
807
848
|
version = armed
|
|
808
|
-
await client.session.
|
|
849
|
+
const into = target ?? (await client.session.create({ body: {} }).then((made: any) => made?.data?.id).catch(() => undefined))
|
|
850
|
+
if (into) await client.session.promptAsync({ path: { id: into }, body: { parts: [{ type: "text", text }] } }).catch(() => {})
|
|
809
851
|
break
|
|
810
852
|
}
|
|
811
853
|
if (Date.now() - began < 2000) await new Promise((resolve) => setTimeout(resolve, 5000))
|
|
@@ -817,6 +859,7 @@ export const Scp = async ({ client, directory }: { client: any; directory: strin
|
|
|
817
859
|
}
|
|
818
860
|
const arm = (id: string) => {
|
|
819
861
|
target = id
|
|
862
|
+
fresh = false
|
|
820
863
|
armed++
|
|
821
864
|
void wake()
|
|
822
865
|
}
|
|
@@ -835,6 +878,9 @@ export const Scp = async ({ client, directory }: { client: any; directory: strin
|
|
|
835
878
|
if (child === undefined && children.get(id) === known) children.delete(id)
|
|
836
879
|
return child === true
|
|
837
880
|
}
|
|
881
|
+
setTimeout(() => {
|
|
882
|
+
if (fresh && busy.size === 0) void wake()
|
|
883
|
+
}, STARTUP_MS).unref?.()
|
|
838
884
|
return {
|
|
839
885
|
"tool.execute.after": async (input: { tool: string; sessionID: string; args?: unknown }, output: { output?: unknown }) => {
|
|
840
886
|
const payload = { hook_event_name: "PostToolUse", session_id: input.sessionID, cwd: directory, tool_name: input.tool, tool_input: input.args }
|
|
@@ -969,6 +1015,8 @@ export {
|
|
|
969
1015
|
titleText,
|
|
970
1016
|
promptTitle,
|
|
971
1017
|
transcriptTitle,
|
|
1018
|
+
CLAUDE_CAPS,
|
|
1019
|
+
fitText,
|
|
972
1020
|
hookReply,
|
|
973
1021
|
TLDR_EVERY_DEFAULT,
|
|
974
1022
|
TLDR,
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
entryArgv,
|
|
3
|
+
mcpFile,
|
|
4
|
+
mountedEntry,
|
|
5
|
+
whichOnPath
|
|
6
|
+
} from "./chunk-2OSUVJ2J.js";
|
|
7
|
+
import "./chunk-Q5UI4Z7H.js";
|
|
8
|
+
import "./chunk-QRWQIHV6.js";
|
|
9
|
+
import "./chunk-YYSLYVNZ.js";
|
|
10
|
+
import {
|
|
11
|
+
CHANNELS_ENV,
|
|
12
|
+
RelayUsage,
|
|
13
|
+
deriveHandle,
|
|
14
|
+
joinMode
|
|
15
|
+
} from "./chunk-2LIJXXTL.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
|
+
};
|
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-
|
|
20
|
+
const { run } = await import("./main-ZAYVXFAG.js");
|
|
21
21
|
await run(process.argv.slice(2));
|
|
22
22
|
}
|