@scp-dev/cli 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-G2SQPY6E.js → chunk-IXCHAWOL.js} +2 -2
- package/dist/{chunk-NOYMSD4F.js → chunk-W3VNCRBX.js} +33 -10
- package/dist/{chunk-OTO6DBU6.js → chunk-X4ZLEM47.js} +13 -0
- package/dist/{chunk-F5QVHJIT.js → chunk-ZMVFN6MU.js} +2 -2
- package/dist/{claude-CVWECMRQ.js → claude-JD7WXMAT.js} +3 -3
- package/dist/cli.js +1 -1
- package/dist/{codex-Y7SGLZ64.js → codex-4IIM6ZE2.js} +5 -4
- package/dist/{hooks-EKGY5O5N.js → hooks-P4J6OZCO.js} +1 -1
- package/dist/{install-4VBSX6RI.js → install-FKGGPT6L.js} +2 -2
- package/dist/{main-YN3J6MGW.js → main-SQKCKV3B.js} +25 -21
- package/dist/{mcp-BVC4PLZ7.js → mcp-TUWMLGE4.js} +4 -4
- package/dist/{pi-R23XR4AC.js → pi-JZ4DXY7V.js} +8 -6
- package/dist/pi-extension.js +137 -0
- package/dist/{setup-H7GGUID2.js → setup-RO7356M4.js} +3 -3
- package/dist/{spawn-AJAYD6CW.js → spawn-BYU2X46L.js} +4 -4
- package/dist/{up-HZKGURPI.js → up-GDD24GPL.js} +1 -1
- package/package.json +6 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hooksTarget
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZMVFN6MU.js";
|
|
4
4
|
import {
|
|
5
5
|
AGY_HOOK_SET,
|
|
6
6
|
BLOCK_STOP,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
mergeHooks,
|
|
13
13
|
musePlugin,
|
|
14
14
|
opencodePlugin
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-X4ZLEM47.js";
|
|
16
16
|
import {
|
|
17
17
|
RelayUsage,
|
|
18
18
|
gitToplevel,
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
mkdtempSync,
|
|
36
36
|
readFileSync,
|
|
37
37
|
readlinkSync,
|
|
38
|
+
realpathSync,
|
|
38
39
|
renameSync,
|
|
39
40
|
rmSync,
|
|
40
41
|
statSync,
|
|
@@ -611,6 +612,23 @@ function museInstalled(env) {
|
|
|
611
612
|
}
|
|
612
613
|
}
|
|
613
614
|
var installedFrom = (record) => record && isObject(record.source) ? String(record.source.path) : void 0;
|
|
615
|
+
function samePath(a, b) {
|
|
616
|
+
if (a === void 0) return false;
|
|
617
|
+
if (a === b) return true;
|
|
618
|
+
try {
|
|
619
|
+
return realpathSync(a) === realpathSync(b);
|
|
620
|
+
} catch {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
function museReviewNeeded(cli, run) {
|
|
625
|
+
try {
|
|
626
|
+
const caps = JSON.parse(run([cli, "plugins", "inspect", SERVER, "--json"]).output).runtime_capabilities;
|
|
627
|
+
return Array.isArray(caps) && caps.some((cap) => isObject(cap) && cap.status === "review_needed");
|
|
628
|
+
} catch {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
614
632
|
function pluginDir(manifest) {
|
|
615
633
|
return dirname(dirname(manifest));
|
|
616
634
|
}
|
|
@@ -655,19 +673,24 @@ function planHooks(plan, harness, ctx) {
|
|
|
655
673
|
const from = installedFrom(record);
|
|
656
674
|
const current = content ?? readFileSync(resolved(file), "utf8");
|
|
657
675
|
const disabled = record?.enabled === false;
|
|
658
|
-
const install = content !== void 0 || from
|
|
659
|
-
|
|
676
|
+
const install = content !== void 0 || !samePath(from, dir) || record?.manifest_sha256 !== sha256(current);
|
|
677
|
+
const cli = ctx.found.get("muse");
|
|
678
|
+
const review = install || museReviewNeeded(cli, run);
|
|
679
|
+
if (!install && !disabled && !review) {
|
|
660
680
|
note(plan, harness, `hooks: up to date in ${file}${held}`);
|
|
661
681
|
return;
|
|
662
682
|
}
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
683
|
+
const commands = [
|
|
684
|
+
...install ? [[cli, "plugins", "install", dir, "--scope", "user"]] : [],
|
|
685
|
+
...disabled ? [[cli, "plugins", "enable", SERVER]] : [],
|
|
686
|
+
...review ? [[cli, "plugins", "approve", SERVER]] : []
|
|
687
|
+
];
|
|
688
|
+
const head = install ? `${content ? "write" : "install"} the ${CLI_NAME} plugin ${file}${held}${from && !samePath(from, dir) ? `, replacing the scp plugin installed from ${from}` : ""}${disabled ? ", and enable it, which Muse has disabled" : ""}` : disabled ? `enable the ${CLI_NAME} plugin ${file}, which Muse has disabled` : `approve the hooks of the ${CLI_NAME} plugin ${file}, which Muse holds for review`;
|
|
666
689
|
addStep(plan, {
|
|
667
690
|
harness,
|
|
668
691
|
label: "hooks",
|
|
669
692
|
file,
|
|
670
|
-
plan: [head, ...commands.map(shown), ...
|
|
693
|
+
plan: [head, ...commands.map(shown), ...review ? ["approves its hooks: a headless muse exec runs no hook Muse holds for review"] : []],
|
|
671
694
|
apply: () => {
|
|
672
695
|
const next = install ? desiredHooks(kind, file, blockStop) : void 0;
|
|
673
696
|
const saved = next === void 0 ? void 0 : writeFile(file, next, now, { mode: 420 });
|
|
@@ -741,7 +764,7 @@ function planUnhook(plan, harness, kind, file, items, ctx) {
|
|
|
741
764
|
const gone = { harness, label: "hooks", file, plan: [`forget ${file}, which is gone`], apply: () => void 0, manifest: forgotten };
|
|
742
765
|
if (kind === "muse") {
|
|
743
766
|
const cli = ctx.found.get("muse");
|
|
744
|
-
const installed = installedFrom(museInstalled(ctx.env))
|
|
767
|
+
const installed = samePath(installedFrom(museInstalled(ctx.env)), pluginDir(file));
|
|
745
768
|
if (!existsSync(target) && !installed) {
|
|
746
769
|
addStep(plan, gone);
|
|
747
770
|
return;
|
|
@@ -936,7 +959,7 @@ function verifyMusePlugin(cli, dir, run, manifest) {
|
|
|
936
959
|
} catch {
|
|
937
960
|
return { ok: false, line: `unknown: muse plugins list --json exited ${result.code}: ${lastLine(result.output)}` };
|
|
938
961
|
}
|
|
939
|
-
const ours = installedFrom(record)
|
|
962
|
+
const ours = samePath(installedFrom(record), dir);
|
|
940
963
|
if (manifest === void 0) return { ok: !ours, line: ours ? "still installed (muse plugins list)" : "removed (muse plugins list)" };
|
|
941
964
|
if (!ours) return { ok: false, line: "failed: muse plugins list does not show the scp plugin from setup" };
|
|
942
965
|
const current = record.manifest_sha256 === sha256(manifest) && record.enabled !== false;
|
|
@@ -345,6 +345,15 @@ function inferredTitle(harness, event, named, payload) {
|
|
|
345
345
|
const text = harness === "claude-code" ? transcriptTitle(asString(field(payload, "transcript_path", "transcriptPath")), asString(field(payload, "session_id", "sessionId"))) : own && !OPENCODE_UNNAMED.test(own) ? titleText(own) : void 0;
|
|
346
346
|
return text ? { text, from: "summary" } : void 0;
|
|
347
347
|
}
|
|
348
|
+
function launchedSubagent(harness, event, payload) {
|
|
349
|
+
if (harness !== "claude-code" || event.kind !== "tool" || event.agent || !/^(agent|task)$/i.test(event.tool ?? "")) return void 0;
|
|
350
|
+
const input = field(payload, "tool_input", "toolInput");
|
|
351
|
+
const response = field(payload, "tool_response", "toolResponse");
|
|
352
|
+
if (typeof input !== "object" || typeof response !== "object" || Array.isArray(response)) return void 0;
|
|
353
|
+
const id = asString(response.agentId);
|
|
354
|
+
const title = titleText(asString(input.description));
|
|
355
|
+
return id && title && /^[A-Za-z0-9_-]{1,64}$/.test(id) ? { id, title } : void 0;
|
|
356
|
+
}
|
|
348
357
|
function hookReply(harness, kind, text, unread = 0, continues = true) {
|
|
349
358
|
if (kind === "stop" && harness === "claude-code" && continues)
|
|
350
359
|
return JSON.stringify({ hookSpecificOutput: { hookEventName: "Stop", additionalContext: text } });
|
|
@@ -448,6 +457,8 @@ async function runHook(args, options) {
|
|
|
448
457
|
if (turn) posted.push(client.request("peek_turn", turn));
|
|
449
458
|
const title = inferredTitle(claim.harness, event, named, payload);
|
|
450
459
|
if (title) posted.push(client.request("title_infer", title));
|
|
460
|
+
const launched = launchedSubagent(claim.harness, event, payload);
|
|
461
|
+
if (launched) posted.push(client.request("subagent_title", launched));
|
|
451
462
|
const injected = inject ? client.request(
|
|
452
463
|
"inject",
|
|
453
464
|
counts ? { count: true, ...stop ? { stop: true, directives: true, ...blockStop ? { hold_unread: true } : {} } : {} } : {}
|
|
@@ -510,6 +521,8 @@ async function wakeHook(harnessArg, payload, waitS, options) {
|
|
|
510
521
|
const client = await options.connect(false);
|
|
511
522
|
try {
|
|
512
523
|
await client.hello({ transient: true, claim });
|
|
524
|
+
await client.request("wake", { via: OPENCODE_WAKE }).catch(() => {
|
|
525
|
+
});
|
|
513
526
|
const result = await client.request("inject", { wait_s: waitMs / 1e3 });
|
|
514
527
|
if (!result.offered.length) return;
|
|
515
528
|
const intro = result.intro ? introText(result.intro, result.intro_key) : void 0;
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
mergeHooks,
|
|
11
11
|
musePlugin,
|
|
12
12
|
opencodePlugin
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-X4ZLEM47.js";
|
|
14
14
|
import {
|
|
15
15
|
RelayUsage,
|
|
16
16
|
gitToplevel
|
|
@@ -166,7 +166,7 @@ function injectionNotes(harness, scope, target, blockStop, wait) {
|
|
|
166
166
|
];
|
|
167
167
|
case "muse":
|
|
168
168
|
return [
|
|
169
|
-
`Muse loads hooks only from plugins: muse plugins install ${dirname(dirname(target))} --scope ${scope}`,
|
|
169
|
+
`Muse loads hooks only from plugins: muse plugins install ${dirname(dirname(target))} --scope ${scope}, then muse plugins approve scp (a headless muse exec runs no hook Muse holds for review)`,
|
|
170
170
|
`new relay messages reach the model after each tool call and with each prompt; ${held}`,
|
|
171
171
|
pushed,
|
|
172
172
|
"Muse hands plugin hooks no SCP_* variables: they key the session by the harness process, so set no SCP_SESSION for Muse's shim"
|
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
mcpFile,
|
|
4
4
|
mountedEntry,
|
|
5
5
|
whichOnPath
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-W3VNCRBX.js";
|
|
7
|
+
import "./chunk-ZMVFN6MU.js";
|
|
8
|
+
import "./chunk-X4ZLEM47.js";
|
|
9
9
|
import "./chunk-YYSLYVNZ.js";
|
|
10
10
|
import {
|
|
11
11
|
CHANNELS_ENV,
|
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-SQKCKV3B.js");
|
|
21
21
|
await run(process.argv.slice(2));
|
|
22
22
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
codexMount,
|
|
3
3
|
codexOverrides
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-IXCHAWOL.js";
|
|
5
5
|
import "./chunk-KN7WEIE4.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-W3VNCRBX.js";
|
|
7
|
+
import "./chunk-ZMVFN6MU.js";
|
|
8
|
+
import "./chunk-X4ZLEM47.js";
|
|
9
9
|
import "./chunk-YYSLYVNZ.js";
|
|
10
10
|
import {
|
|
11
11
|
RelayUsage,
|
|
@@ -486,6 +486,7 @@ ${stderr.toString().trim()}` : ""}`
|
|
|
486
486
|
},
|
|
487
487
|
claim,
|
|
488
488
|
channels: true,
|
|
489
|
+
wake: "codex-app-server",
|
|
489
490
|
input,
|
|
490
491
|
env: launch.env,
|
|
491
492
|
output: (chunk) => {
|
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
hooksTarget,
|
|
3
3
|
installHooks,
|
|
4
4
|
mcpInstall
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-ZMVFN6MU.js";
|
|
6
|
+
import "./chunk-X4ZLEM47.js";
|
|
7
7
|
import "./chunk-YYSLYVNZ.js";
|
|
8
8
|
import "./chunk-E7JG5PJB.js";
|
|
9
9
|
import "./chunk-MNTIEWKF.js";
|
|
@@ -33,7 +33,7 @@ import { parseArgs } from "util";
|
|
|
33
33
|
// package.json
|
|
34
34
|
var package_default = {
|
|
35
35
|
name: "@scp-dev/cli",
|
|
36
|
-
version: "0.2.
|
|
36
|
+
version: "0.2.3",
|
|
37
37
|
description: "The swarm command line tool.",
|
|
38
38
|
license: "Apache-2.0",
|
|
39
39
|
homepage: "https://scp.dev",
|
|
@@ -80,11 +80,13 @@ import {
|
|
|
80
80
|
deliveryText,
|
|
81
81
|
LIMITS,
|
|
82
82
|
PREAMBLE,
|
|
83
|
+
presenceText,
|
|
83
84
|
reachLines,
|
|
84
85
|
receiptLines,
|
|
85
86
|
RelayError,
|
|
86
87
|
shortId,
|
|
87
88
|
stripControl,
|
|
89
|
+
wakeText,
|
|
88
90
|
widenText,
|
|
89
91
|
wrapMessage,
|
|
90
92
|
CLI_NAME
|
|
@@ -99,7 +101,7 @@ results back, as with native subagents.
|
|
|
99
101
|
${CLI_NAME} reply <id> [text|-] answer a message in its thread: ${CLI_NAME} reply 5af1f2 "done"
|
|
100
102
|
${CLI_NAME} ask <to> [text|-] [--wait S] send a request and wait for its reply: ${CLI_NAME} ask lead "which branch?"
|
|
101
103
|
${CLI_NAME} inbox [--wait S] your unread messages, then marked read: ${CLI_NAME} inbox
|
|
102
|
-
${CLI_NAME} list [--all] agents on the relay: address, title, presence, status; --all adds offline ones
|
|
104
|
+
${CLI_NAME} list [--all] agents on the relay: address, title, presence, wake, status; --all adds offline ones
|
|
103
105
|
${CLI_NAME} join [name] put this session on the relay, named once: ${CLI_NAME} join lead (${CLI_NAME} name renames it)
|
|
104
106
|
${CLI_NAME} join --under <parent> link this session under an orchestrator, as a spawned worker sits: ${CLI_NAME} join --under lead
|
|
105
107
|
${CLI_NAME} status [id] the fleet in short; with the id of a message you sent: who read it when
|
|
@@ -122,7 +124,7 @@ More: ${CLI_NAME} help --all (the full reference), ${CLI_NAME} <verb> --help (on
|
|
|
122
124
|
var RELAY_HELP = `${CLI_NAME}: messages between the agent sessions on this machine, and a live view of the fleet
|
|
123
125
|
|
|
124
126
|
usage:
|
|
125
|
-
${CLI_NAME} list [--all] [--roots] [--tree] agents on the relay: address, title, presence, status, cwd; offline
|
|
127
|
+
${CLI_NAME} list [--all] [--roots] [--tree] agents on the relay: address, title, presence, wake, status, cwd; offline only with --all;
|
|
126
128
|
sessions nobody spawned that spawned nobody fold into one line, --roots (or --all) lists
|
|
127
129
|
them; --tree nests them by parent; (you) marks your own
|
|
128
130
|
${CLI_NAME} list --contacts the endpoints of other accounts your contacts let you reach: address, harness, presence
|
|
@@ -230,7 +232,7 @@ scopes (selectors are comma-separated, e.g. tree:parent,tree:children,group:team
|
|
|
230
232
|
messages with the relay MCP tools (relay_send, relay_inbox, relay_reply). A
|
|
231
233
|
${CLI_NAME} command from codex's workspace-write sandbox also needs
|
|
232
234
|
-c sandbox_workspace_write.network_access=true (the relay socket counts as network)
|
|
233
|
-
${CLI_NAME} pi [--name N] [--] [pi args...] runs pi with the
|
|
235
|
+
${CLI_NAME} pi [--name N] [--] [pi args...] runs pi with the bundled SCP extension: the relay tools, and each new
|
|
234
236
|
message as a turn (steering a running one, waking an idle session)
|
|
235
237
|
${CLI_NAME} codex [--name N] [--headless] [--] [codex args...]
|
|
236
238
|
runs codex with relay messages waking an idle thread or steering a running turn
|
|
@@ -263,7 +265,7 @@ agent sessions:
|
|
|
263
265
|
${CLI_NAME} hook <harness> [event] what those hooks run: one event from the hook JSON on stdin; prints only the reply
|
|
264
266
|
A session's shim and hooks share one endpoint, keyed by $SCP_SESSION, else the harness process's pid and start time.
|
|
265
267
|
Without --as or $SCP_NAME the handle is <harness>-<worktree>; labels gain machine=<short hostname>.
|
|
266
|
-
A Claude Code subagent's hooks give it a child endpoint, <session handle>-<type>-<first
|
|
268
|
+
A Claude Code subagent's hooks give it a child endpoint, <session handle>-<short type>-<first 4 of its id>, whose messages
|
|
267
269
|
reach it after its tools; its shell is the session's, so it acts as that child with --as.
|
|
268
270
|
Grok runs the Claude Code hooks, labelled grok; Muse takes them as a plugin; Codex runs new hooks after /hooks review.
|
|
269
271
|
${CLI_NAME} claude [--name N] [--] [claude args...] start Claude Code with the scp channel loaded, so peer messages wake
|
|
@@ -464,15 +466,16 @@ function printEndpoints(endpoints, me, host = shortHostname()) {
|
|
|
464
466
|
const row = (e, indent) => [
|
|
465
467
|
`${indent}${addressCell(e, me)}`,
|
|
466
468
|
e.title ?? "",
|
|
467
|
-
e
|
|
469
|
+
presenceText(e),
|
|
470
|
+
wakeText(e),
|
|
468
471
|
e.status ? statusText(e.status) : e.kind === "human" ? "" : "-",
|
|
469
472
|
Object.entries(e.labels).filter(([k]) => k !== "machine").map(([k, v]) => `${k}=${v}`).join(","),
|
|
470
473
|
shortCwd(e.cwd),
|
|
471
474
|
...peek ? [e.peekable ?? ""] : []
|
|
472
475
|
];
|
|
473
|
-
const blank = (first) => [first, "", "", "", "", "", ...peek ? [""] : []];
|
|
476
|
+
const blank = (first) => [first, "", "", "", "", "", "", ...peek ? [""] : []];
|
|
474
477
|
const rows = [
|
|
475
|
-
["ADDRESS", "TITLE", "PRESENCE", "STATUS", "LABELS", "CWD", ...peek ? ["PEEK"] : []],
|
|
478
|
+
["ADDRESS", "TITLE", "PRESENCE", "WAKE", "STATUS", "LABELS", "CWD", ...peek ? ["PEEK"] : []],
|
|
476
479
|
...endpoints.filter((e) => e.kind === "human").map((e) => row(e, ""))
|
|
477
480
|
];
|
|
478
481
|
const machines = /* @__PURE__ */ new Map();
|
|
@@ -535,13 +538,13 @@ async function relayMain(argv, cwd = process.cwd()) {
|
|
|
535
538
|
out(package_default.version);
|
|
536
539
|
return 0;
|
|
537
540
|
}
|
|
538
|
-
if (verb === "hook") return (await import("./hooks-
|
|
539
|
-
if (verb === "setup") return (await import("./setup-
|
|
540
|
-
if (verb === "claude") return (await import("./claude-
|
|
541
|
-
if (verb === "pi") return (await import("./pi-
|
|
542
|
-
if (verb === "codex") return (await import("./codex-
|
|
541
|
+
if (verb === "hook") return (await import("./hooks-P4J6OZCO.js")).relayHookCommand(rest, startDaemon);
|
|
542
|
+
if (verb === "setup") return (await import("./setup-RO7356M4.js")).relaySetupCommand(rest, cwd, out);
|
|
543
|
+
if (verb === "claude") return (await import("./claude-JD7WXMAT.js")).claudeCommand(rest);
|
|
544
|
+
if (verb === "pi") return (await import("./pi-JZ4DXY7V.js")).piCommand(rest);
|
|
545
|
+
if (verb === "codex") return (await import("./codex-4IIM6ZE2.js")).codexCommand(rest);
|
|
543
546
|
if (verb === "spawn") {
|
|
544
|
-
const { splitSpawn, spawnCommand } = await import("./spawn-
|
|
547
|
+
const { splitSpawn, spawnCommand } = await import("./spawn-BYU2X46L.js");
|
|
545
548
|
const { flags } = splitSpawn(rest);
|
|
546
549
|
if (flags.includes("--help") || flags.includes("-h")) {
|
|
547
550
|
out(flags.includes("--all") ? RELAY_HELP : SPAWN_HELP);
|
|
@@ -569,11 +572,11 @@ async function relayMain(argv, cwd = process.cwd()) {
|
|
|
569
572
|
return daemon(values.idle, values.policy);
|
|
570
573
|
}
|
|
571
574
|
if (verb === "mcp")
|
|
572
|
-
return (await import("./mcp-
|
|
575
|
+
return (await import("./mcp-TUWMLGE4.js")).relayMcpCommand(values, positionals);
|
|
573
576
|
if (verb === "hooks") {
|
|
574
577
|
if (positionals[0] !== "install" || positionals.length > 1) throw new RelayUsage(`${CLI_NAME} hooks takes install --harness claude|codex|opencode|agy|muse`);
|
|
575
578
|
if (values.write && values.print) throw new RelayUsage("--print and --write: pick one");
|
|
576
|
-
return (await import("./install-
|
|
579
|
+
return (await import("./install-FKGGPT6L.js")).installHooks({
|
|
577
580
|
harness: values.harness,
|
|
578
581
|
scope: values.scope,
|
|
579
582
|
write: values.write === true,
|
|
@@ -585,7 +588,7 @@ async function relayMain(argv, cwd = process.cwd()) {
|
|
|
585
588
|
});
|
|
586
589
|
}
|
|
587
590
|
if (verb === "relay") return (await import("./relay-U634RHIJ.js")).relayVerb(positionals, values.json === true);
|
|
588
|
-
if (verb === "up") return (await import("./up-
|
|
591
|
+
if (verb === "up") return (await import("./up-GDD24GPL.js")).up(positionals, values, cwd, relinkBroker);
|
|
589
592
|
if (verb === "login") {
|
|
590
593
|
const code = await (await import("./relay-U634RHIJ.js")).loginMain(positionals, values);
|
|
591
594
|
if (code === 0) await relinkRunning();
|
|
@@ -679,7 +682,7 @@ async function runVerb({ client, me, broker, where, bound, harnessPid }, verb, v
|
|
|
679
682
|
out(`${me.address} (${me.kind}${me.harness ? `, ${me.harness}` : ""})`);
|
|
680
683
|
const how = bound === "as" ? "--as" : bound === "lineage" ? "this shell's harness process" : bound === "human" ? "human inbox" : "-";
|
|
681
684
|
const fields = [
|
|
682
|
-
["presence", me
|
|
685
|
+
["presence", presenceText(me)],
|
|
683
686
|
["title", me.title ?? "-"],
|
|
684
687
|
...me.kind === "human" ? [] : [
|
|
685
688
|
["status", me.status?.state ?? "-"],
|
|
@@ -1223,7 +1226,7 @@ function printTree(endpoints, me) {
|
|
|
1223
1226
|
if (e.parent && byAddress.has(e.parent) && e.parent !== e.address) children.set(e.parent, [...children.get(e.parent) ?? [], e]);
|
|
1224
1227
|
else roots.push(e);
|
|
1225
1228
|
}
|
|
1226
|
-
const rows = [["ADDRESS", "SCOPE", "TITLE", "PRESENCE", "STATUS", "LABELS", "CWD"]];
|
|
1229
|
+
const rows = [["ADDRESS", "SCOPE", "TITLE", "PRESENCE", "WAKE", "STATUS", "LABELS", "CWD"]];
|
|
1227
1230
|
const seen = /* @__PURE__ */ new Set();
|
|
1228
1231
|
const walk = (e, depth) => {
|
|
1229
1232
|
if (seen.has(e.address)) return;
|
|
@@ -1233,7 +1236,8 @@ function printTree(endpoints, me) {
|
|
|
1233
1236
|
`${" ".repeat(depth)}${addressCell(e, me)}`,
|
|
1234
1237
|
e.kind === "human" ? "" : e.scope ?? "open",
|
|
1235
1238
|
e.title ?? "",
|
|
1236
|
-
e
|
|
1239
|
+
presenceText(e),
|
|
1240
|
+
wakeText(e),
|
|
1237
1241
|
e.status ? statusText(e.status) : e.kind === "human" ? "" : "-",
|
|
1238
1242
|
labels,
|
|
1239
1243
|
shortCwd(e.cwd)
|
|
@@ -1247,7 +1251,7 @@ var STATES = ["working", "waiting", "blocked", "done"];
|
|
|
1247
1251
|
function printSummary(snapshot, me, all) {
|
|
1248
1252
|
if (me)
|
|
1249
1253
|
out(
|
|
1250
|
-
`you: ${me.address} (${me.kind}${me.harness ? `, ${me.harness}` : ""}) ${me
|
|
1254
|
+
`you: ${me.address} (${me.kind}${me.harness ? `, ${me.harness}` : ""}) ${presenceText(me)}${me.title ? ` ${quoted(me.title)}` : ""}${me.kind === "human" ? "" : `, status ${me.status ? statusText(me.status) : "-"}`}`
|
|
1251
1255
|
);
|
|
1252
1256
|
const agents = snapshot.endpoints.filter((e) => e.kind !== "human");
|
|
1253
1257
|
const count = (presence) => agents.filter((e) => e.presence === presence).length;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
spawnDetached
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-IXCHAWOL.js";
|
|
4
4
|
import "./chunk-KN7WEIE4.js";
|
|
5
|
-
import "./chunk-
|
|
5
|
+
import "./chunk-W3VNCRBX.js";
|
|
6
6
|
import {
|
|
7
7
|
mcpInstall
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-ZMVFN6MU.js";
|
|
9
|
+
import "./chunk-X4ZLEM47.js";
|
|
10
10
|
import "./chunk-YYSLYVNZ.js";
|
|
11
11
|
import {
|
|
12
12
|
CHANNELS_ENV,
|
|
@@ -5,10 +5,11 @@ import "./chunk-MNTIEWKF.js";
|
|
|
5
5
|
|
|
6
6
|
// src/pi.ts
|
|
7
7
|
import { spawn } from "child_process";
|
|
8
|
+
import { existsSync } from "fs";
|
|
8
9
|
import { fileURLToPath } from "url";
|
|
9
10
|
import { CLI_NAME, validHandle } from "@scp-dev/core";
|
|
10
11
|
var PI_HELP = `${CLI_NAME} pi [--name NAME | --as NAME] [--] [pi args...]
|
|
11
|
-
runs pi with the
|
|
12
|
+
runs pi with the bundled SCP extension: the relay tools, and each new message as a turn, waking the session when idle.
|
|
12
13
|
$SCP_PI_BIN picks the pi binary (default pi on PATH), $SCP_PI_EXTENSION the extension file.`;
|
|
13
14
|
function piLaunch(args, env, extension) {
|
|
14
15
|
let name;
|
|
@@ -32,13 +33,13 @@ function piLaunch(args, env, extension) {
|
|
|
32
33
|
env: { ...env, SCP_HARNESS: "pi", ...name !== void 0 ? { SCP_NAME: name } : {} }
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
|
-
function extensionPath(env) {
|
|
36
|
+
function extensionPath(env, base = import.meta.url) {
|
|
36
37
|
if (env.SCP_PI_EXTENSION) return env.SCP_PI_EXTENSION;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
throw new RelayUsage(`${CLI_NAME} pi needs the @scp-dev/pi extension: install it next to ${CLI_NAME}, or set $SCP_PI_EXTENSION to its file`);
|
|
38
|
+
for (const candidate of ["./pi-extension.js", "../../pi/src/index.ts"]) {
|
|
39
|
+
const file = fileURLToPath(new URL(candidate, base));
|
|
40
|
+
if (existsSync(file)) return file;
|
|
41
41
|
}
|
|
42
|
+
throw new RelayUsage(`${CLI_NAME} pi found no pi extension next to ${CLI_NAME}: reinstall it, or set $SCP_PI_EXTENSION to the extension file`);
|
|
42
43
|
}
|
|
43
44
|
async function piCommand(args) {
|
|
44
45
|
const own = args.includes("--") ? args.slice(0, args.indexOf("--")) : args;
|
|
@@ -66,6 +67,7 @@ async function piCommand(args) {
|
|
|
66
67
|
}
|
|
67
68
|
export {
|
|
68
69
|
PI_HELP,
|
|
70
|
+
extensionPath,
|
|
69
71
|
piCommand,
|
|
70
72
|
piLaunch
|
|
71
73
|
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// ../pi/src/index.ts
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { createInterface } from "readline";
|
|
4
|
+
var PROTOCOL = "2025-06-18";
|
|
5
|
+
var CHANNEL = "notifications/claude/channel";
|
|
6
|
+
function swarmWords(env) {
|
|
7
|
+
const raw = env.SCP_PI_SWARM;
|
|
8
|
+
if (!raw) return ["swarm"];
|
|
9
|
+
const words = JSON.parse(raw);
|
|
10
|
+
if (!Array.isArray(words) || !words.length || !words.every((word) => typeof word === "string"))
|
|
11
|
+
throw new Error("SCP_PI_SWARM must be a JSON array of strings");
|
|
12
|
+
return words;
|
|
13
|
+
}
|
|
14
|
+
function shimArgv(env) {
|
|
15
|
+
return [...swarmWords(env), "mcp", "--harness", "pi", "--channels", ...env.SCP_NAME ? ["--as", env.SCP_NAME] : []];
|
|
16
|
+
}
|
|
17
|
+
var McpLink = class {
|
|
18
|
+
child;
|
|
19
|
+
pending = /* @__PURE__ */ new Map();
|
|
20
|
+
next = 1;
|
|
21
|
+
closed = false;
|
|
22
|
+
constructor(argv, env, onPush, onExit) {
|
|
23
|
+
this.child = spawn(argv[0], argv.slice(1), { env, stdio: ["pipe", "pipe", "ignore"] });
|
|
24
|
+
this.child.stdin.on("error", () => {
|
|
25
|
+
});
|
|
26
|
+
createInterface({ input: this.child.stdout }).on("line", (line) => {
|
|
27
|
+
let message;
|
|
28
|
+
try {
|
|
29
|
+
message = JSON.parse(line);
|
|
30
|
+
} catch {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (typeof message.id === "number" && this.pending.has(message.id)) {
|
|
34
|
+
const waiter = this.pending.get(message.id);
|
|
35
|
+
this.pending.delete(message.id);
|
|
36
|
+
const error = message.error;
|
|
37
|
+
if (error) waiter.reject(new Error(error.message ?? "mcp error"));
|
|
38
|
+
else waiter.resolve(message.result ?? {});
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (message.method === CHANNEL) {
|
|
42
|
+
const params = message.params ?? {};
|
|
43
|
+
if (typeof params.content === "string") onPush(params.content, params.meta ?? {});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
this.child.once("error", () => this.fail(null, onExit));
|
|
47
|
+
this.child.once("exit", (code) => this.fail(code, onExit));
|
|
48
|
+
}
|
|
49
|
+
fail(code, onExit) {
|
|
50
|
+
if (this.closed) return;
|
|
51
|
+
this.closed = true;
|
|
52
|
+
for (const waiter of this.pending.values()) waiter.reject(new Error("the scp shim exited"));
|
|
53
|
+
this.pending.clear();
|
|
54
|
+
onExit(code);
|
|
55
|
+
}
|
|
56
|
+
request(method, params = {}) {
|
|
57
|
+
if (this.closed) return Promise.reject(new Error("the scp shim exited"));
|
|
58
|
+
const id = this.next++;
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
this.pending.set(id, { resolve, reject });
|
|
61
|
+
this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
|
|
62
|
+
`);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
notify(method, params = {}) {
|
|
66
|
+
if (!this.closed) this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
|
|
67
|
+
`);
|
|
68
|
+
}
|
|
69
|
+
close() {
|
|
70
|
+
if (this.closed) return;
|
|
71
|
+
this.closed = true;
|
|
72
|
+
this.child.stdin.end();
|
|
73
|
+
this.child.kill();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
function toolResult(result) {
|
|
77
|
+
const content = (result.content ?? []).flatMap(
|
|
78
|
+
(part) => part.type === "text" && typeof part.text === "string" ? [{ type: "text", text: part.text }] : []
|
|
79
|
+
);
|
|
80
|
+
if (result.isError) throw new Error(content.map((part) => part.text).join("\n") || "the relay tool failed");
|
|
81
|
+
return { content, details: {} };
|
|
82
|
+
}
|
|
83
|
+
function scp(pi) {
|
|
84
|
+
let link;
|
|
85
|
+
const warn = (ctx, text) => {
|
|
86
|
+
if (ctx.hasUI) ctx.ui.notify(text, "warning");
|
|
87
|
+
};
|
|
88
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
89
|
+
if (process.env.SCP_PI === "0") return;
|
|
90
|
+
link?.close();
|
|
91
|
+
const current = new McpLink(
|
|
92
|
+
shimArgv(process.env),
|
|
93
|
+
process.env,
|
|
94
|
+
(content, meta) => {
|
|
95
|
+
pi.sendMessage({ customType: "scp", content, display: true, details: meta }, { triggerTurn: true, deliverAs: "steer" });
|
|
96
|
+
},
|
|
97
|
+
(code) => {
|
|
98
|
+
if (link === current) warn(ctx, `scp: the relay shim exited (${code ?? "error"}); relay tools are unavailable`);
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
link = current;
|
|
102
|
+
try {
|
|
103
|
+
await current.request("initialize", {
|
|
104
|
+
protocolVersion: PROTOCOL,
|
|
105
|
+
capabilities: { experimental: { "claude/channel": {} } },
|
|
106
|
+
clientInfo: { name: "pi", version: "1" }
|
|
107
|
+
});
|
|
108
|
+
const { tools } = await current.request("tools/list");
|
|
109
|
+
for (const tool of tools ?? []) {
|
|
110
|
+
pi.registerTool({
|
|
111
|
+
name: tool.name,
|
|
112
|
+
label: tool.name,
|
|
113
|
+
description: tool.description ?? tool.name,
|
|
114
|
+
parameters: tool.inputSchema ?? { type: "object", properties: {} },
|
|
115
|
+
async execute(_id, params) {
|
|
116
|
+
if (link !== current) throw new Error("this scp session ended");
|
|
117
|
+
return toolResult(await current.request("tools/call", { name: tool.name, arguments: params ?? {} }));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
current.notify("notifications/initialized");
|
|
122
|
+
} catch (error) {
|
|
123
|
+
warn(ctx, `scp: could not start the relay shim: ${error instanceof Error ? error.message : String(error)}`);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
pi.on("session_shutdown", async () => {
|
|
127
|
+
link?.close();
|
|
128
|
+
link = void 0;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
export {
|
|
132
|
+
McpLink,
|
|
133
|
+
scp as default,
|
|
134
|
+
shimArgv,
|
|
135
|
+
swarmWords,
|
|
136
|
+
toolResult
|
|
137
|
+
};
|
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
verifyMount,
|
|
16
16
|
verifyMusePlugin,
|
|
17
17
|
whichOnPath
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import "./chunk-
|
|
20
|
-
import "./chunk-
|
|
18
|
+
} from "./chunk-W3VNCRBX.js";
|
|
19
|
+
import "./chunk-ZMVFN6MU.js";
|
|
20
|
+
import "./chunk-X4ZLEM47.js";
|
|
21
21
|
import "./chunk-YYSLYVNZ.js";
|
|
22
22
|
import "./chunk-E7JG5PJB.js";
|
|
23
23
|
import "./chunk-MNTIEWKF.js";
|
|
@@ -29,13 +29,13 @@ import {
|
|
|
29
29
|
steerArgv,
|
|
30
30
|
transcriptReplies,
|
|
31
31
|
workerName
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-IXCHAWOL.js";
|
|
33
33
|
import {
|
|
34
34
|
SPAWN_HELP
|
|
35
35
|
} from "./chunk-KN7WEIE4.js";
|
|
36
|
-
import "./chunk-
|
|
37
|
-
import "./chunk-
|
|
38
|
-
import "./chunk-
|
|
36
|
+
import "./chunk-W3VNCRBX.js";
|
|
37
|
+
import "./chunk-ZMVFN6MU.js";
|
|
38
|
+
import "./chunk-X4ZLEM47.js";
|
|
39
39
|
import "./chunk-YYSLYVNZ.js";
|
|
40
40
|
import "./chunk-E7JG5PJB.js";
|
|
41
41
|
import "./chunk-MNTIEWKF.js";
|
|
@@ -23,7 +23,7 @@ async function up(positionals, values, cwd, brokerStatus) {
|
|
|
23
23
|
} else out(`already logged in to ${url}`);
|
|
24
24
|
const enabled = await (await import("./remote-NTHRHVEP.js")).cloudMain(["enable"], values.machine !== void 0 ? { machine: values.machine } : {}, brokerStatus);
|
|
25
25
|
if (enabled !== 0) return enabled;
|
|
26
|
-
const setup = (await import("./setup-
|
|
26
|
+
const setup = (await import("./setup-RO7356M4.js")).relaySetupCommand(["--write"], cwd, out);
|
|
27
27
|
if (setup !== 0) return setup;
|
|
28
28
|
const broker = await brokerStatus();
|
|
29
29
|
if (!broker.enabled) out(`the broker is still local-only; run \`${CLI_NAME} daemon restart\` to link it to ${url}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scp-dev/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "The swarm command line tool.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://scp.dev",
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
"typecheck": "tsc -p ."
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@scp-dev/core": "0.1.
|
|
28
|
-
"@scp-dev/broker": "0.1.
|
|
29
|
-
"@scp-dev/hub": "0.3.
|
|
30
|
-
"@scp-dev/mcp": "0.1.
|
|
27
|
+
"@scp-dev/core": "0.1.4",
|
|
28
|
+
"@scp-dev/broker": "0.1.5",
|
|
29
|
+
"@scp-dev/hub": "0.3.1",
|
|
30
|
+
"@scp-dev/mcp": "0.1.5",
|
|
31
31
|
"smol-toml": "^1.9.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@openai/codex": "0.159.0",
|
|
35
|
-
"@scp-dev/mesh": "0.0.
|
|
35
|
+
"@scp-dev/mesh": "0.0.2",
|
|
36
36
|
"opencode-ai": "1.18.34",
|
|
37
37
|
"tsup": "^8.5.1"
|
|
38
38
|
},
|