@scp-dev/cli 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -6
- package/dist/{chunk-DA3EB2YF.js → chunk-3PGDKPRN.js} +4 -59
- package/dist/{chunk-6UFMKYPH.js → chunk-5UWLTO2Y.js} +4 -4
- package/dist/{spawn-7P4EMSVB.js → chunk-ABP5AKFH.js} +119 -54
- package/dist/{chunk-RO7CUOOH.js → chunk-ABR5UABL.js} +16 -7
- package/dist/{chunk-4QYUPPGV.js → chunk-BUC5G5PY.js} +3 -3
- package/dist/{chunk-O4J5G2X7.js → chunk-IH2RJRIW.js} +6 -5
- package/dist/{chunk-IE5VI25P.js → chunk-KN7WEIE4.js} +1 -1
- package/dist/chunk-M2DCR2J4.js +63 -0
- package/dist/{chunk-WSLKRAB7.js → chunk-QQZF5TQD.js} +4 -4
- package/dist/{chunk-7KP6TAJW.js → chunk-URSBFDR6.js} +9 -5
- package/dist/claude-5WTICOYS.js +88 -0
- package/dist/cli.js +1 -1
- package/dist/daemon-SUTLTY5U.js +52 -0
- package/dist/{hooks-MBKU56HG.js → hooks-4YVIPZKE.js} +3 -2
- package/dist/install-4KQQWGGB.js +15 -0
- package/dist/{login-JTRUWG6Q.js → login-5Y3HPSVC.js} +5 -4
- package/dist/{main-QP32TYTL.js → main-PZQAJGHQ.js} +124 -31
- package/dist/{mcp-EQCSCD3X.js → mcp-GI5OMTUT.js} +35 -5
- package/dist/{peek-YCOWBEPD.js → peek-X4SEGEUB.js} +3 -2
- package/dist/{relay-VVJPYVXE.js → relay-W2NFUE2U.js} +4 -3
- package/dist/{remote-3BWFISXJ.js → remote-GC4XN44U.js} +5 -4
- package/dist/{setup-T4ZZKV7K.js → setup-UF4AA6RO.js} +8 -5
- package/dist/spawn-KT5235WX.js +75 -0
- package/dist/{up-Z4RSJMED.js → up-VA54QWA6.js} +8 -7
- package/package.json +5 -5
- package/dist/install-4HATFIFI.js +0 -14
package/README.md
CHANGED
|
@@ -3,21 +3,25 @@
|
|
|
3
3
|
The `swarm` command line tool, the reference binding of SCP for shells, hooks and MCP.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npm
|
|
6
|
+
npm i -g @scp-dev/cli
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
swarm login
|
|
13
|
-
swarm
|
|
14
|
-
|
|
12
|
+
swarm up # browser login, links this machine to the org hub, installs hooks and MCP
|
|
13
|
+
swarm claude --name lead # start a named orchestrator that wakes on peer messages
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
From the orchestrator's shell:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
swarm spawn --name parser -- claude -p "Fix the parser tests"
|
|
15
20
|
swarm send parser "Also cover the empty-input case."
|
|
16
21
|
swarm list --tree
|
|
17
|
-
swarm inbox --wait 30
|
|
18
22
|
```
|
|
19
23
|
|
|
20
|
-
Headless (CI, cron, cloud runners): `swarm
|
|
24
|
+
Headless (CI, cron, cloud runners): `swarm up --token scp_t_... [--machine NAME]`. The default relay is https://app.scp.dev; `swarm relay use <url>` picks another. Runs on Node 22.13 or later.
|
|
21
25
|
|
|
22
26
|
## Provides
|
|
23
27
|
|
|
@@ -1,56 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var FAILED = 125;
|
|
5
|
-
var SELF = [process.execPath, ...process.versions.bun ? ["--no-env-file", "--config=/dev/null"] : [], process.argv[1]];
|
|
6
|
-
function capture(argv, env) {
|
|
7
|
-
const result = spawnSync(argv[0], argv.slice(1), { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], ...env ? { env } : {} });
|
|
8
|
-
return result.status === 0 ? result.stdout : void 0;
|
|
9
|
-
}
|
|
10
|
-
function note(message) {
|
|
11
|
-
process.stderr.write(`${message.startsWith(`${CLI_NAME} `) ? "" : `${CLI_NAME}: `}${message}
|
|
12
|
-
`);
|
|
13
|
-
}
|
|
14
|
-
async function exitFlushed(code, waitMs = 1e4) {
|
|
15
|
-
const ended = (stream) => new Promise((resolve2) => {
|
|
16
|
-
try {
|
|
17
|
-
stream.end(() => resolve2());
|
|
18
|
-
} catch {
|
|
19
|
-
resolve2();
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
let timer;
|
|
23
|
-
await Promise.race([Promise.all([ended(process.stdout), ended(process.stderr)]), new Promise((resolve2) => timer = setTimeout(resolve2, waitMs))]);
|
|
24
|
-
clearTimeout(timer);
|
|
25
|
-
process.exit(code);
|
|
26
|
-
}
|
|
27
|
-
function printTable(rows) {
|
|
28
|
-
const widths = rows[0].map((_, column) => Math.max(...rows.map((row) => row[column].length)));
|
|
29
|
-
for (const row of rows)
|
|
30
|
-
console.log(
|
|
31
|
-
row.map((cell, column) => cell.padEnd(widths[column])).join(" ").trimEnd()
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
var DURATION_PART = /(\d+(?:\.\d+)?)(ms|s|m|h|d)/g;
|
|
35
|
-
var DURATION_UNITS = { ms: 1e-3, s: 1, m: 60, h: 3600, d: 86400 };
|
|
36
|
-
function parseSeconds(text, flag) {
|
|
37
|
-
const trimmed = text.trim();
|
|
38
|
-
let seconds;
|
|
39
|
-
if (/^\d+(?:\.\d+)?$/.test(trimmed)) seconds = Number(trimmed);
|
|
40
|
-
else {
|
|
41
|
-
seconds = 0;
|
|
42
|
-
let consumed = "";
|
|
43
|
-
for (const match of trimmed.matchAll(DURATION_PART)) {
|
|
44
|
-
seconds += Number(match[1]) * DURATION_UNITS[match[2]];
|
|
45
|
-
consumed += match[0];
|
|
46
|
-
}
|
|
47
|
-
if (consumed !== trimmed || consumed === "") {
|
|
48
|
-
throw new Error(`${flag} ${JSON.stringify(text)} is not a duration like 90s, 30m or 1h30m`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (!(seconds > 0)) throw new Error(`${flag} must be more than zero`);
|
|
52
|
-
return Math.max(1, Math.ceil(seconds));
|
|
53
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
capture
|
|
3
|
+
} from "./chunk-M2DCR2J4.js";
|
|
54
4
|
|
|
55
5
|
// src/identity.ts
|
|
56
6
|
import { existsSync, realpathSync } from "fs";
|
|
@@ -337,12 +287,6 @@ function relayIdentity(input) {
|
|
|
337
287
|
}
|
|
338
288
|
|
|
339
289
|
export {
|
|
340
|
-
FAILED,
|
|
341
|
-
SELF,
|
|
342
|
-
note,
|
|
343
|
-
exitFlushed,
|
|
344
|
-
printTable,
|
|
345
|
-
parseSeconds,
|
|
346
290
|
shortHostname,
|
|
347
291
|
detectHarness,
|
|
348
292
|
hookHarness,
|
|
@@ -353,6 +297,7 @@ export {
|
|
|
353
297
|
hiddenHarness,
|
|
354
298
|
lineage,
|
|
355
299
|
spawnerKey,
|
|
300
|
+
psParent,
|
|
356
301
|
harnessPid,
|
|
357
302
|
hookHarnessPid,
|
|
358
303
|
relayIdentity
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
configDir,
|
|
5
5
|
relayCredential,
|
|
6
6
|
writePrivateJson
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BUC5G5PY.js";
|
|
8
8
|
import {
|
|
9
9
|
RelayUsage
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-URSBFDR6.js";
|
|
11
11
|
import {
|
|
12
12
|
note,
|
|
13
13
|
printTable
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-M2DCR2J4.js";
|
|
15
15
|
|
|
16
16
|
// src/remote.ts
|
|
17
17
|
import { existsSync, readFileSync, rmSync } from "fs";
|
|
@@ -202,7 +202,7 @@ async function cloudMain(positionals, values, brokerStatus) {
|
|
|
202
202
|
writePrivateJson(path, file);
|
|
203
203
|
if (values.json) json({ file: path, ...file, token: problem ? "missing" : "found" });
|
|
204
204
|
else {
|
|
205
|
-
out(`the relay broker links to ${url} as machine ${machine}
|
|
205
|
+
out(`the relay broker links to ${url} as machine ${machine} (${path})`);
|
|
206
206
|
if (problem) note(problem);
|
|
207
207
|
}
|
|
208
208
|
return 0;
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
SPAWN_HELP
|
|
3
|
-
} from "./chunk-IE5VI25P.js";
|
|
4
1
|
import {
|
|
5
2
|
agyRules,
|
|
6
3
|
agySettingsFile,
|
|
7
4
|
mcpFile,
|
|
8
5
|
mountedEntry
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-WSLKRAB7.js";
|
|
6
|
+
} from "./chunk-IH2RJRIW.js";
|
|
11
7
|
import {
|
|
12
8
|
OPTIONS,
|
|
13
9
|
RelayUsage,
|
|
@@ -17,17 +13,18 @@ import {
|
|
|
17
13
|
open,
|
|
18
14
|
selectorList,
|
|
19
15
|
walkNow
|
|
20
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-URSBFDR6.js";
|
|
21
17
|
import {
|
|
22
18
|
steerPrompt
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
import "./chunk-YYSLYVNZ.js";
|
|
19
|
+
} from "./chunk-ABR5UABL.js";
|
|
25
20
|
import {
|
|
26
|
-
SELF,
|
|
27
21
|
detectHarness,
|
|
28
|
-
note,
|
|
29
22
|
spawnerKey
|
|
30
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-3PGDKPRN.js";
|
|
24
|
+
import {
|
|
25
|
+
SELF,
|
|
26
|
+
note
|
|
27
|
+
} from "./chunk-M2DCR2J4.js";
|
|
31
28
|
|
|
32
29
|
// src/spawn.ts
|
|
33
30
|
import { spawn, spawnSync } from "child_process";
|
|
@@ -297,6 +294,35 @@ function closedStdin(command) {
|
|
|
297
294
|
if (run.sub?.name === "resume") return run.operands.length >= (hasFlag(args, ["--last"]) ? 1 : 2);
|
|
298
295
|
return run.sub !== void 0 || run.operands.length > 0;
|
|
299
296
|
}
|
|
297
|
+
var STDIN_WAIT_MS = 300;
|
|
298
|
+
async function harnessStdin(waitMs) {
|
|
299
|
+
if (process.stdin.isTTY === true) return "ignore";
|
|
300
|
+
let stat;
|
|
301
|
+
try {
|
|
302
|
+
stat = fstatSync(0);
|
|
303
|
+
} catch {
|
|
304
|
+
return "ignore";
|
|
305
|
+
}
|
|
306
|
+
if (stat.isFile()) return stat.size > 0 ? "inherit" : "ignore";
|
|
307
|
+
if (!stat.isFIFO() && !stat.isSocket()) return "ignore";
|
|
308
|
+
const input = process.stdin;
|
|
309
|
+
return new Promise((resolve2) => {
|
|
310
|
+
const settle = (result) => {
|
|
311
|
+
clearTimeout(timer);
|
|
312
|
+
input.off("data", data);
|
|
313
|
+
input.off("end", ended);
|
|
314
|
+
input.off("error", ended);
|
|
315
|
+
input.pause();
|
|
316
|
+
resolve2(result);
|
|
317
|
+
};
|
|
318
|
+
const data = (chunk) => settle({ first: chunk, rest: input });
|
|
319
|
+
const ended = () => settle("ignore");
|
|
320
|
+
const timer = waitMs === void 0 ? void 0 : setTimeout(ended, waitMs);
|
|
321
|
+
input.on("data", data);
|
|
322
|
+
input.once("end", ended);
|
|
323
|
+
input.once("error", ended);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
300
326
|
function harnessArgv(command, env, self = SELF, mount = codexMount, cwd = process.cwd(), probe = codexSkipsGitCheck) {
|
|
301
327
|
const at = programAt(command);
|
|
302
328
|
const program = command[at];
|
|
@@ -492,11 +518,19 @@ function runHarness(argv, env, options = {}) {
|
|
|
492
518
|
return new Promise((resolve2) => {
|
|
493
519
|
const tty = process.stdin.isTTY === true;
|
|
494
520
|
const group = options.group === true && !tty && process.stdout.isTTY !== true && process.stderr.isTTY !== true;
|
|
521
|
+
const input = options.stdin ?? "inherit";
|
|
522
|
+
const piped = typeof input === "object" ? input : void 0;
|
|
495
523
|
const child = spawn(argv[0], argv.slice(1), {
|
|
496
|
-
stdio: [
|
|
524
|
+
stdio: [typeof input === "object" ? "pipe" : input, capture ? "pipe" : "inherit", captureErr ? "pipe" : "inherit"],
|
|
497
525
|
env,
|
|
498
526
|
detached: group
|
|
499
527
|
});
|
|
528
|
+
if (piped && child.stdin) {
|
|
529
|
+
child.stdin.on("error", () => {
|
|
530
|
+
});
|
|
531
|
+
child.stdin.write(piped.first);
|
|
532
|
+
piped.rest.pipe(child.stdin);
|
|
533
|
+
}
|
|
500
534
|
const pipes = [child.stdout, child.stderr].filter((pipe) => pipe !== null);
|
|
501
535
|
const stdout = child.stdout ? tee(child.stdout, out) : void 0;
|
|
502
536
|
const stderr = child.stderr ? tee(child.stderr, options.err ?? process.stderr) : void 0;
|
|
@@ -638,7 +672,11 @@ ${LIMIT_CUT}`);
|
|
|
638
672
|
return replies;
|
|
639
673
|
}
|
|
640
674
|
function repliesOutput(replies, bytes = LIMITS.handbackBytes) {
|
|
641
|
-
const
|
|
675
|
+
const lines = (text) => text.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
676
|
+
const all = replies.map((reply) => handbackOutput(reply.replace(/\r\n?/g, "\n"), false, Number.MAX_SAFE_INTEGER)).filter(Boolean);
|
|
677
|
+
const restated = (text, later) => lines(text).length >= 2 && lines(text).every((line) => lines(later).includes(line));
|
|
678
|
+
const clean = all.filter((text, i) => text === HANDBACK_CUT || !all.slice(i + 1).some((later) => restated(text, later)));
|
|
679
|
+
if (clean.every((text) => text === HANDBACK_CUT)) return "";
|
|
642
680
|
const size = (text) => new TextEncoder().encode(text).length;
|
|
643
681
|
const framed = (from) => {
|
|
644
682
|
const text = clean.slice(from).join("\n\n");
|
|
@@ -648,7 +686,7 @@ ${text}` : text;
|
|
|
648
686
|
};
|
|
649
687
|
let fit;
|
|
650
688
|
for (let from = clean.length - 1; from >= 0 && size(framed(from)) <= bytes; from--) fit = framed(from);
|
|
651
|
-
if (fit !== void 0
|
|
689
|
+
if (fit !== void 0) return fit;
|
|
652
690
|
return keepLast(clean.at(-1), bytes, clean.length > 1);
|
|
653
691
|
}
|
|
654
692
|
function splitSpawn(args) {
|
|
@@ -682,20 +720,9 @@ function handbackBytes(value) {
|
|
|
682
720
|
}
|
|
683
721
|
var STEER_RESUMES = 2;
|
|
684
722
|
async function spawnCommand(args, cwd) {
|
|
685
|
-
const
|
|
686
|
-
let values;
|
|
687
|
-
|
|
688
|
-
values = parseArgs({ args: split.flags, options: OPTIONS, strict: true }).values;
|
|
689
|
-
} catch (error) {
|
|
690
|
-
throw new RelayUsage(error instanceof Error ? error.message : String(error));
|
|
691
|
-
}
|
|
692
|
-
for (const key of Object.keys(values)) if (!SPAWN_OPTIONS.includes(key)) throw new RelayUsage(`${CLI_NAME} spawn does not take --${key}`);
|
|
693
|
-
if (values.bg && values.fg) throw new RelayUsage("--bg and --fg: pick one");
|
|
694
|
-
const command = split.command;
|
|
695
|
-
if (!command.length)
|
|
696
|
-
throw new RelayUsage(`${CLI_NAME} spawn runs a harness command: ${CLI_NAME} spawn [flags] [--] <harness cmd...>, e.g. ${CLI_NAME} spawn codex exec "..."`);
|
|
697
|
-
const refusal = contributorRefusal(command);
|
|
698
|
-
if (refusal) throw new RelayUsage(refusal);
|
|
723
|
+
const parsed = parseSpawn(args);
|
|
724
|
+
let values = parsed.values;
|
|
725
|
+
const { command } = parsed;
|
|
699
726
|
const bytes = values["handback-bytes"] === void 0 ? void 0 : handbackBytes(values["handback-bytes"]);
|
|
700
727
|
const runner = process.env[SPAWN_RUNNER];
|
|
701
728
|
delete process.env[SPAWN_RUNNER];
|
|
@@ -737,8 +764,10 @@ async function spawnCommand(args, cwd) {
|
|
|
737
764
|
const agy = agyNote(command, process.env);
|
|
738
765
|
if (agy) note(agy);
|
|
739
766
|
const started = handshake && ((failed) => handshake(failed ? { error: failed.message, code: failed.code } : { name, pid: process.pid }));
|
|
740
|
-
const launch = (argv, extra = {}) => runHarness(harnessArgv(argv, env), env, {
|
|
741
|
-
|
|
767
|
+
const launch = (argv, extra = {}) => runHarness(harnessArgv(argv, env), env, { group: runner !== void 0, ...closedStdin(argv) ? { stdin: "ignore" } : {}, ...extra });
|
|
768
|
+
const first = steerArgv(command, steer);
|
|
769
|
+
const stdin = closedStdin(first) ? await harnessStdin(agentShell(process.env) ? STDIN_WAIT_MS : void 0) : void 0;
|
|
770
|
+
let run = await launch(first, { ...started ? { started } : {}, ...stdin ? { stdin } : {} });
|
|
742
771
|
if (!run.started) {
|
|
743
772
|
await spawnExit(values, cwd, grant.SCP_GRANT, run.code, name, "", walk, [], [], false);
|
|
744
773
|
return run.code;
|
|
@@ -761,6 +790,32 @@ async function spawnCommand(args, cwd) {
|
|
|
761
790
|
await spawnExit(values, cwd, grant.SCP_GRANT, run.code, name, replies || harnessOutput(run, bytes), walk, unapplied, exits, true, stderrOutput(run));
|
|
762
791
|
return run.code;
|
|
763
792
|
}
|
|
793
|
+
function parseSpawn(args) {
|
|
794
|
+
const split = splitSpawn(args);
|
|
795
|
+
let values;
|
|
796
|
+
try {
|
|
797
|
+
values = parseArgs({ args: split.flags, options: OPTIONS, strict: true }).values;
|
|
798
|
+
} catch (error) {
|
|
799
|
+
throw new RelayUsage(error instanceof Error ? error.message : String(error));
|
|
800
|
+
}
|
|
801
|
+
if (values.help) return { values, command: split.command };
|
|
802
|
+
for (const key of Object.keys(values)) if (!SPAWN_OPTIONS.includes(key)) throw new RelayUsage(`${CLI_NAME} spawn does not take --${key}`);
|
|
803
|
+
if (values.bg && values.fg) throw new RelayUsage("--bg and --fg: pick one");
|
|
804
|
+
const command = split.command;
|
|
805
|
+
if (!command.length)
|
|
806
|
+
throw new RelayUsage(`${CLI_NAME} spawn runs a harness command: ${CLI_NAME} spawn [flags] [--] <harness cmd...>, e.g. ${CLI_NAME} spawn codex exec "..."`);
|
|
807
|
+
const refusal = contributorRefusal(command);
|
|
808
|
+
if (refusal) throw new RelayUsage(refusal);
|
|
809
|
+
if (values["handback-bytes"] !== void 0) handbackBytes(values["handback-bytes"]);
|
|
810
|
+
return { values, command };
|
|
811
|
+
}
|
|
812
|
+
async function spawnDetached(args, cwd) {
|
|
813
|
+
const { values } = parseSpawn(args);
|
|
814
|
+
if (values.help || values.fg) throw new RelayUsage("a detached spawn takes no --help or --fg");
|
|
815
|
+
const reply = await detachedRunner(args, cwd);
|
|
816
|
+
if ("error" in reply) throw new Error(reply.error);
|
|
817
|
+
return reply;
|
|
818
|
+
}
|
|
764
819
|
function agentShell(env) {
|
|
765
820
|
return detectHarness(env) !== void 0 || ["SCP_NAME", "SCP_GRANT", "SCP_SESSION"].some((key) => env[key]);
|
|
766
821
|
}
|
|
@@ -782,6 +837,17 @@ function once(fn) {
|
|
|
782
837
|
};
|
|
783
838
|
}
|
|
784
839
|
async function detachSpawn(args, cwd) {
|
|
840
|
+
const reply = await detachedRunner(args, cwd);
|
|
841
|
+
if ("error" in reply) {
|
|
842
|
+
note(reply.error);
|
|
843
|
+
return reply.code;
|
|
844
|
+
}
|
|
845
|
+
console.log(
|
|
846
|
+
`${reply.name} started (pid ${reply.pid}) and runs in the background: its result arrives as a relay message by itself; steer it with ${CLI_NAME} send ${reply.name} <text>`
|
|
847
|
+
);
|
|
848
|
+
return 0;
|
|
849
|
+
}
|
|
850
|
+
async function detachedRunner(args, cwd) {
|
|
785
851
|
const walk = walkNow();
|
|
786
852
|
const child = spawn(SELF[0], [...SELF.slice(1), "spawn", ...args], {
|
|
787
853
|
cwd,
|
|
@@ -810,13 +876,9 @@ async function detachSpawn(args, cwd) {
|
|
|
810
876
|
if ("error" in reply) {
|
|
811
877
|
if (reply.usage) throw new RelayUsage(reply.error);
|
|
812
878
|
if (reply.code === void 0) throw new Error(reply.error);
|
|
813
|
-
|
|
814
|
-
return reply.code;
|
|
879
|
+
return { error: reply.error, code: reply.code };
|
|
815
880
|
}
|
|
816
|
-
|
|
817
|
-
`${reply.name} started (pid ${reply.pid}) and runs in the background: its result arrives as a relay message by itself; steer it with ${CLI_NAME} send ${reply.name} <text>`
|
|
818
|
-
);
|
|
819
|
-
return 0;
|
|
881
|
+
return reply;
|
|
820
882
|
}
|
|
821
883
|
async function spawnSteers(values, cwd, token, walk, take) {
|
|
822
884
|
try {
|
|
@@ -853,33 +915,36 @@ async function spawnExit(values, cwd, token, code, name, output, walk, unapplied
|
|
|
853
915
|
}
|
|
854
916
|
}
|
|
855
917
|
var SPAWN_GRANT_TTL_S = 3600;
|
|
918
|
+
|
|
856
919
|
export {
|
|
857
|
-
MESSAGING,
|
|
858
|
-
SPAWN_HELP,
|
|
859
920
|
SPAWN_RUNNER,
|
|
860
|
-
|
|
861
|
-
agyNote,
|
|
862
|
-
claudeTranscripts,
|
|
863
|
-
closedStdin,
|
|
921
|
+
spawnEnvironment,
|
|
864
922
|
codexMount,
|
|
865
|
-
codexOverrideAt,
|
|
866
923
|
codexOverrides,
|
|
867
|
-
|
|
924
|
+
codexOverrideAt,
|
|
925
|
+
programAt,
|
|
926
|
+
MESSAGING,
|
|
868
927
|
contributorRefusal,
|
|
928
|
+
codexSkipsGitCheck,
|
|
929
|
+
closedStdin,
|
|
930
|
+
STDIN_WAIT_MS,
|
|
931
|
+
harnessStdin,
|
|
869
932
|
harnessArgv,
|
|
870
|
-
harnessOutput,
|
|
871
|
-
programAt,
|
|
872
|
-
repliesOutput,
|
|
873
933
|
resumeArgv,
|
|
874
|
-
runHarness,
|
|
875
|
-
spawnCommand,
|
|
876
|
-
spawnDefaults,
|
|
877
|
-
spawnEnvironment,
|
|
878
934
|
spawnPrompt,
|
|
935
|
+
steerArgv,
|
|
879
936
|
spawnTitle,
|
|
880
|
-
|
|
937
|
+
workerName,
|
|
938
|
+
agyNote,
|
|
939
|
+
runHarness,
|
|
940
|
+
harnessOutput,
|
|
881
941
|
stderrOutput,
|
|
882
|
-
|
|
942
|
+
claudeTranscripts,
|
|
883
943
|
transcriptReplies,
|
|
884
|
-
|
|
944
|
+
repliesOutput,
|
|
945
|
+
splitSpawn,
|
|
946
|
+
spawnDefaults,
|
|
947
|
+
spawnCommand,
|
|
948
|
+
spawnDetached,
|
|
949
|
+
agentShell
|
|
885
950
|
};
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
hookHarness,
|
|
8
8
|
hookHarnessPid,
|
|
9
9
|
relayIdentity
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-3PGDKPRN.js";
|
|
11
11
|
|
|
12
12
|
// src/hooks.ts
|
|
13
13
|
import { Readable } from "stream";
|
|
@@ -238,9 +238,16 @@ function pushText(result) {
|
|
|
238
238
|
`Push budget used up for this prompt; read the rest with ${CLI_NAME} inbox.${running.length ? ` Workers you spawned still running: ${running.join(", ")}.` : ""}`
|
|
239
239
|
);
|
|
240
240
|
} else {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
241
|
+
const background = result.background ?? [];
|
|
242
|
+
if (running.length) {
|
|
243
|
+
parts.push(
|
|
244
|
+
`Workers you spawned still running: ${running.join(", ")}. End your turn to wait for them; no reply needed.${background.length ? ` Also running with --no-push: ${background.join(", ")}.` : ""}`
|
|
245
|
+
);
|
|
246
|
+
} else if (background.length) {
|
|
247
|
+
parts.push(
|
|
248
|
+
`No worker your stop waits on is still running; ${background.join(", ")} (--no-push) still ${background.length === 1 ? "runs" : "run"} in the background.`
|
|
249
|
+
);
|
|
250
|
+
} else parts.push("No worker you spawned is still running.");
|
|
244
251
|
}
|
|
245
252
|
return parts.join("\n\n");
|
|
246
253
|
}
|
|
@@ -290,7 +297,8 @@ function hookReply(harness, kind, text, unread = 0, continues = true) {
|
|
|
290
297
|
const hookEventName = kind === "tool" ? "PostToolUse" : kind === "session_start" ? "SessionStart" : "UserPromptSubmit";
|
|
291
298
|
return JSON.stringify({ hookSpecificOutput: { hookEventName, additionalContext: text } });
|
|
292
299
|
}
|
|
293
|
-
var
|
|
300
|
+
var DEFERS = /* @__PURE__ */ new Set(["claude-code", "codex"]);
|
|
301
|
+
var NUDGE = `${CLI_NAME} (optional): a title and a one-line now (relay_set_title / relay_set_now) help peers find you; skip it when you only run workers.`;
|
|
294
302
|
async function runHook(args, options) {
|
|
295
303
|
const began = Date.now();
|
|
296
304
|
const blockStop = args.includes(BLOCK_STOP);
|
|
@@ -391,7 +399,8 @@ async function runHook(args, options) {
|
|
|
391
399
|
...nudged.length ? { nudged: nudged.map((n) => n.seq) } : {},
|
|
392
400
|
...directive ? { restated: directive.restated } : {},
|
|
393
401
|
...full ? { preamble: true } : {},
|
|
394
|
-
...intro ? { intro: true } : {}
|
|
402
|
+
...intro ? { intro: true } : {},
|
|
403
|
+
...event.kind === "tool" && DEFERS.has(claim.harness) ? { deferred: true } : {}
|
|
395
404
|
}).catch(() => {
|
|
396
405
|
});
|
|
397
406
|
}
|
|
@@ -442,7 +451,7 @@ async function subagentHook(client, claim, sub, event, takes, options) {
|
|
|
442
451
|
}
|
|
443
452
|
async function push(client, harness, waitMs, began, prompts, options, continues = true) {
|
|
444
453
|
const end = Date.now() + waitMs;
|
|
445
|
-
if (waitMs > 0) options.deadline?.(end - began + PUSH_PRINT_MS);
|
|
454
|
+
if (waitMs > 0) options.deadline?.(end - began + LIMITS.pushGraceS * 1e3 + PUSH_PRINT_MS);
|
|
446
455
|
let result;
|
|
447
456
|
for (; ; ) {
|
|
448
457
|
result = await client.request("stop_wait", {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RelayUsage
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-URSBFDR6.js";
|
|
4
4
|
import {
|
|
5
5
|
note
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-M2DCR2J4.js";
|
|
7
7
|
|
|
8
8
|
// src/relay.ts
|
|
9
9
|
import { chmodSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
@@ -119,7 +119,7 @@ function relayVerb(positionals, asJson, env = process.env) {
|
|
|
119
119
|
}
|
|
120
120
|
async function loginMain(positionals, values) {
|
|
121
121
|
if (positionals.length) throw new RelayUsage(`${CLI_NAME} login takes only flags`);
|
|
122
|
-
return (await import("./login-
|
|
122
|
+
return (await import("./login-5Y3HPSVC.js")).login(values);
|
|
123
123
|
}
|
|
124
124
|
function logoutMain(positionals, asJson, env = process.env) {
|
|
125
125
|
if (positionals.length) throw new RelayUsage(`${CLI_NAME} logout takes no arguments`);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hooksTarget
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-QQZF5TQD.js";
|
|
4
4
|
import {
|
|
5
5
|
RelayUsage
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-URSBFDR6.js";
|
|
7
7
|
import {
|
|
8
8
|
AGY_HOOK_SET,
|
|
9
9
|
BLOCK_STOP,
|
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
mergeHooks,
|
|
16
16
|
musePlugin,
|
|
17
17
|
opencodePlugin
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-ABR5UABL.js";
|
|
19
19
|
import {
|
|
20
20
|
gitToplevel
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-3PGDKPRN.js";
|
|
22
22
|
|
|
23
23
|
// src/setup.ts
|
|
24
24
|
import { spawnSync } from "child_process";
|
|
@@ -179,7 +179,7 @@ function entryRight(harness, entry, argv) {
|
|
|
179
179
|
}
|
|
180
180
|
var withoutRemote = (entry) => Object.fromEntries(Object.entries(entry).filter(([key]) => !REMOTE_KEYS.includes(key)));
|
|
181
181
|
function mountArgv(harness, bin) {
|
|
182
|
-
return [bin, "mcp", "--harness", harness];
|
|
182
|
+
return [bin, "mcp", "--harness", harness, ...harness === "claude" ? ["--channels"] : []];
|
|
183
183
|
}
|
|
184
184
|
function agyRules() {
|
|
185
185
|
return TOOLS.map((tool) => `mcp(${SERVER}/${tool.name})`);
|
|
@@ -1172,6 +1172,7 @@ export {
|
|
|
1172
1172
|
agySettingsFile,
|
|
1173
1173
|
manifestFile,
|
|
1174
1174
|
mountedEntry,
|
|
1175
|
+
entryArgv,
|
|
1175
1176
|
mountArgv,
|
|
1176
1177
|
agyRules,
|
|
1177
1178
|
verifyMount,
|
|
@@ -29,7 +29,7 @@ Steer: ${CLI_NAME} send <name> <text>; a claude -p or codex exec prompt gets a n
|
|
|
29
29
|
A codex exec worker that exited before a late steer is resumed with it (codex exec resume, at most twice).
|
|
30
30
|
Harnesses: codex gets -c overrides that hand it the grant (also behind env, timeout, nohup or nice); a ${CLI_NAME}
|
|
31
31
|
command from its workspace-write sandbox needs -c sandbox_workspace_write.network_access=true. codex exec
|
|
32
|
-
gets --skip-git-repo-check outside a git repo and
|
|
32
|
+
gets --skip-git-repo-check outside a git repo, and with its prompt in argv piped stdin only under --fg. Workers
|
|
33
33
|
message with the relay MCP tools (relay_send, relay_inbox, relay_reply). claude -p and grok -p get those tools allowed
|
|
34
34
|
unless you pass a permission flag. muse runs with --contributor-mode off unless you pass on. agy needs the allow rules ${CLI_NAME}
|
|
35
35
|
setup writes.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/term.ts
|
|
2
|
+
import { spawnSync } from "child_process";
|
|
3
|
+
import { CLI_NAME } from "@scp-dev/core";
|
|
4
|
+
var FAILED = 125;
|
|
5
|
+
var SELF = [process.execPath, ...process.versions.bun ? ["--no-env-file", "--config=/dev/null"] : [], process.argv[1]];
|
|
6
|
+
function capture(argv, env) {
|
|
7
|
+
const result = spawnSync(argv[0], argv.slice(1), { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], ...env ? { env } : {} });
|
|
8
|
+
return result.status === 0 ? result.stdout : void 0;
|
|
9
|
+
}
|
|
10
|
+
function note(message) {
|
|
11
|
+
process.stderr.write(`${message.startsWith(`${CLI_NAME} `) ? "" : `${CLI_NAME}: `}${message}
|
|
12
|
+
`);
|
|
13
|
+
}
|
|
14
|
+
async function exitFlushed(code, waitMs = 1e4) {
|
|
15
|
+
const ended = (stream) => new Promise((resolve) => {
|
|
16
|
+
try {
|
|
17
|
+
stream.end(() => resolve());
|
|
18
|
+
} catch {
|
|
19
|
+
resolve();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
let timer;
|
|
23
|
+
await Promise.race([Promise.all([ended(process.stdout), ended(process.stderr)]), new Promise((resolve) => timer = setTimeout(resolve, waitMs))]);
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
process.exit(code);
|
|
26
|
+
}
|
|
27
|
+
function printTable(rows) {
|
|
28
|
+
const widths = rows[0].map((_, column) => Math.max(...rows.map((row) => row[column].length)));
|
|
29
|
+
for (const row of rows)
|
|
30
|
+
console.log(
|
|
31
|
+
row.map((cell, column) => cell.padEnd(widths[column])).join(" ").trimEnd()
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
var DURATION_PART = /(\d+(?:\.\d+)?)(ms|s|m|h|d)/g;
|
|
35
|
+
var DURATION_UNITS = { ms: 1e-3, s: 1, m: 60, h: 3600, d: 86400 };
|
|
36
|
+
function parseSeconds(text, flag) {
|
|
37
|
+
const trimmed = text.trim();
|
|
38
|
+
let seconds;
|
|
39
|
+
if (/^\d+(?:\.\d+)?$/.test(trimmed)) seconds = Number(trimmed);
|
|
40
|
+
else {
|
|
41
|
+
seconds = 0;
|
|
42
|
+
let consumed = "";
|
|
43
|
+
for (const match of trimmed.matchAll(DURATION_PART)) {
|
|
44
|
+
seconds += Number(match[1]) * DURATION_UNITS[match[2]];
|
|
45
|
+
consumed += match[0];
|
|
46
|
+
}
|
|
47
|
+
if (consumed !== trimmed || consumed === "") {
|
|
48
|
+
throw new Error(`${flag} ${JSON.stringify(text)} is not a duration like 90s, 30m or 1h30m`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!(seconds > 0)) throw new Error(`${flag} must be more than zero`);
|
|
52
|
+
return Math.max(1, Math.ceil(seconds));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
FAILED,
|
|
57
|
+
SELF,
|
|
58
|
+
capture,
|
|
59
|
+
note,
|
|
60
|
+
exitFlushed,
|
|
61
|
+
printTable,
|
|
62
|
+
parseSeconds
|
|
63
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RelayUsage
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-URSBFDR6.js";
|
|
4
4
|
import {
|
|
5
5
|
AGY_HOOK_SET,
|
|
6
6
|
AGY_UNVERIFIED,
|
|
@@ -13,10 +13,10 @@ import {
|
|
|
13
13
|
mergeHooks,
|
|
14
14
|
musePlugin,
|
|
15
15
|
opencodePlugin
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-ABR5UABL.js";
|
|
17
17
|
import {
|
|
18
18
|
gitToplevel
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-3PGDKPRN.js";
|
|
20
20
|
|
|
21
21
|
// src/install.ts
|
|
22
22
|
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -185,7 +185,7 @@ function mcpInstall(harness, as, out) {
|
|
|
185
185
|
const line = (cli, extra = []) => out([...cli, CLI_NAME, ...args(extra)].map(shellWord).join(" "));
|
|
186
186
|
switch (harness) {
|
|
187
187
|
case "claude":
|
|
188
|
-
line(["claude", "mcp", "add", "scp", "--"]);
|
|
188
|
+
line(["claude", "mcp", "add", "scp", "--"], ["--harness", "claude", "--channels"]);
|
|
189
189
|
return 0;
|
|
190
190
|
case "codex":
|
|
191
191
|
line(["codex", "mcp", "add", "scp", "--"], ["--harness", "codex"]);
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
-
SELF,
|
|
3
2
|
harnessLineage,
|
|
4
3
|
hiddenHarness,
|
|
5
4
|
labelsFor,
|
|
6
5
|
lineage,
|
|
7
|
-
parseSeconds,
|
|
8
6
|
readPsTable
|
|
9
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-3PGDKPRN.js";
|
|
8
|
+
import {
|
|
9
|
+
SELF,
|
|
10
|
+
parseSeconds
|
|
11
|
+
} from "./chunk-M2DCR2J4.js";
|
|
10
12
|
|
|
11
13
|
// src/common.ts
|
|
12
14
|
import { spawn } from "child_process";
|
|
@@ -184,7 +186,7 @@ async function open(verb, values, cwd, bodyFromStdin, walk) {
|
|
|
184
186
|
`${CLI_NAME} ${verb} needs an identity: --as HANDLE (or $SCP_NAME) for an agent, or --human for your own inbox me/ (no terminal is attached, so me/ is not assumed)`
|
|
185
187
|
);
|
|
186
188
|
}
|
|
187
|
-
const { machineName, openCloud } = await import("./remote-
|
|
189
|
+
const { machineName, openCloud } = await import("./remote-GC4XN44U.js");
|
|
188
190
|
const handle2 = named ? localHandle(named) : void 0;
|
|
189
191
|
const claim = handle2 ? {
|
|
190
192
|
handle: handle2,
|
|
@@ -317,6 +319,7 @@ function stamp(iso) {
|
|
|
317
319
|
const at = new Date(iso);
|
|
318
320
|
return Number.isNaN(at.getTime()) ? iso : `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())} ${time(iso)}`;
|
|
319
321
|
}
|
|
322
|
+
var CHANNELS_ENV = "SCP_CHANNELS";
|
|
320
323
|
|
|
321
324
|
export {
|
|
322
325
|
RelayUsage,
|
|
@@ -332,5 +335,6 @@ export {
|
|
|
332
335
|
peekGrant,
|
|
333
336
|
grantParams,
|
|
334
337
|
time,
|
|
335
|
-
stamp
|
|
338
|
+
stamp,
|
|
339
|
+
CHANNELS_ENV
|
|
336
340
|
};
|