@h-rig/cli 0.0.6-alpha.34 → 0.0.6-alpha.36
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/bin/rig.js +484 -238
- package/dist/src/commands/_help-catalog.js +14 -0
- package/dist/src/commands/_operator-view.js +35 -4
- package/dist/src/commands/_pi-frontend.js +35 -4
- package/dist/src/commands/_pi-worker-bridge-extension.js +35 -2
- package/dist/src/commands/_server-client.js +0 -9
- package/dist/src/commands/inspect.js +227 -11
- package/dist/src/commands/pi.js +168 -0
- package/dist/src/commands/run.js +62 -4
- package/dist/src/commands/server.js +7 -2
- package/dist/src/commands/task.js +49 -4
- package/dist/src/commands.js +476 -230
- package/dist/src/index.js +484 -238
- package/package.json +6 -6
|
@@ -112,6 +112,7 @@ var PRIMARY_GROUPS = [
|
|
|
112
112
|
{ command: "show <id>|--run <id> [--raw]", description: "Show a human run summary; --raw prints the full payload.", primary: true },
|
|
113
113
|
{ command: "attach <run-id>|--run <id> [--follow]", description: "Attach to the run; --follow launches native bundled Pi for live Pi runs.", primary: true },
|
|
114
114
|
{ command: "stop [<run-id>|--run <id>]", description: "Request stop for one run or local active runs.", primary: true },
|
|
115
|
+
{ command: "steer <run-id> --message <text>", description: "Queue a steering message into a live worker without attaching." },
|
|
115
116
|
{ command: "timeline --run <id> [--follow]", description: "Stream raw run timeline events." },
|
|
116
117
|
{ command: "resume", description: "Resume the most recent interrupted local run." },
|
|
117
118
|
{ command: "restart", description: "Restart the most recent local run from a clean runtime." },
|
|
@@ -213,6 +214,19 @@ var ADVANCED_GROUPS = [
|
|
|
213
214
|
{ command: "hp-next <dev|check|e2e|reset>", description: "Drive the hp-next browser test harness." }
|
|
214
215
|
]
|
|
215
216
|
},
|
|
217
|
+
{
|
|
218
|
+
name: "pi",
|
|
219
|
+
summary: "Manage Pi extension packages for this project (community extensions from npm/git).",
|
|
220
|
+
usage: ["rig pi <list|add|remove|search> [args]"],
|
|
221
|
+
commands: [
|
|
222
|
+
{ command: "list", description: "Show project and user Pi extension packages." },
|
|
223
|
+
{ command: "add <source>", description: "Add an npm/git Pi extension to .pi/settings.json (auto-installs at next session)." },
|
|
224
|
+
{ command: "remove <source>", description: "Remove an operator-added Pi extension." },
|
|
225
|
+
{ command: "search [term]", description: "Discover Pi extension packages on the npm registry." }
|
|
226
|
+
],
|
|
227
|
+
examples: ["rig pi search subagents", "rig pi add pi-subagents", "rig pi list"],
|
|
228
|
+
next: ["Config-managed extensions: declare `runtime: { pi: { packages: [...] } }` in rig.config.ts \u2014 workers pick them up automatically."]
|
|
229
|
+
},
|
|
216
230
|
{
|
|
217
231
|
name: "plugin",
|
|
218
232
|
summary: "Plugin listing, validation, and plugin-contributed commands.",
|
|
@@ -802,7 +802,38 @@ function parseWsPayload(message) {
|
|
|
802
802
|
return JSON.parse(message.data);
|
|
803
803
|
return JSON.parse(Buffer.from(message.data).toString("utf8"));
|
|
804
804
|
}
|
|
805
|
-
|
|
805
|
+
var BRIDGE_LOCAL_COMMANDS = new Set(["detach", "quit", "q", "stop"]);
|
|
806
|
+
function registerDaemonCommandsNatively(pi, options, ctx, state, commands, registered) {
|
|
807
|
+
for (const command of commands) {
|
|
808
|
+
const record = recordOf(command);
|
|
809
|
+
const name = typeof record?.name === "string" ? record.name : "";
|
|
810
|
+
if (!name || registered.has(name) || BRIDGE_LOCAL_COMMANDS.has(name))
|
|
811
|
+
continue;
|
|
812
|
+
registered.add(name);
|
|
813
|
+
const description = typeof record?.description === "string" ? record.description : undefined;
|
|
814
|
+
const source = typeof record?.source === "string" ? record.source : "worker";
|
|
815
|
+
try {
|
|
816
|
+
pi.registerCommand(name, {
|
|
817
|
+
description: `[worker ${source}] ${description ?? ""}`.trim(),
|
|
818
|
+
handler: async (args) => {
|
|
819
|
+
const text = `/${name}${args ? ` ${args}` : ""}`;
|
|
820
|
+
appendTranscript(state, "You", text);
|
|
821
|
+
try {
|
|
822
|
+
const result = await runRunPiCommandViaServer(options.context, options.runId, text);
|
|
823
|
+
const message = typeof result.message === "string" ? result.message : "worker command accepted";
|
|
824
|
+
appendTranscript(state, "System", message);
|
|
825
|
+
if (state.nativeStream)
|
|
826
|
+
ctx.ui.notify(message, "info");
|
|
827
|
+
} catch (error) {
|
|
828
|
+
reportBridgeError(ctx, state, error instanceof Error ? error.message : String(error));
|
|
829
|
+
}
|
|
830
|
+
updatePiUi(ctx, state);
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
} catch {}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
async function connectWorkerStream(options, pi, ctx, state, registeredDaemonCommands) {
|
|
806
837
|
const ready = await waitForWorkerReady(options, ctx, state);
|
|
807
838
|
if (!ready)
|
|
808
839
|
return;
|
|
@@ -857,6 +888,7 @@ async function connectWorkerStream(options, ctx, state) {
|
|
|
857
888
|
const record = recordOf(command);
|
|
858
889
|
return typeof record?.name === "string" ? [`/${record.name}`] : [];
|
|
859
890
|
});
|
|
891
|
+
registerDaemonCommandsNatively(pi, options, ctx, state, commands, registeredDaemonCommands);
|
|
860
892
|
catchupDone = true;
|
|
861
893
|
for (const payload of buffered.splice(0))
|
|
862
894
|
applyEnvelope(ctx, state, payload);
|
|
@@ -984,6 +1016,7 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
984
1016
|
};
|
|
985
1017
|
if (options.initialMessageSent)
|
|
986
1018
|
appendTranscript(state, "System", "Initial message sent to worker Pi daemon.");
|
|
1019
|
+
const registeredDaemonCommands = new Set;
|
|
987
1020
|
let nativePiUiContextAvailable = false;
|
|
988
1021
|
pi.on("user_bash", (event) => {
|
|
989
1022
|
state.nativeStream = Boolean(state.nativeStream || nativePiUiContextAvailable);
|
|
@@ -1016,7 +1049,7 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
1016
1049
|
});
|
|
1017
1050
|
return { consume: true };
|
|
1018
1051
|
});
|
|
1019
|
-
connectWorkerStream(options, ctx, state).catch((error) => {
|
|
1052
|
+
connectWorkerStream(options, pi, ctx, state, registeredDaemonCommands).catch((error) => {
|
|
1020
1053
|
appendTranscript(state, "Error", error instanceof Error ? error.message : String(error));
|
|
1021
1054
|
updatePiUi(ctx, state);
|
|
1022
1055
|
});
|
|
@@ -1063,8 +1096,6 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
1063
1096
|
"--no-tools",
|
|
1064
1097
|
"--no-builtin-tools",
|
|
1065
1098
|
"--no-skills",
|
|
1066
|
-
"--no-prompt-templates",
|
|
1067
|
-
"--no-themes",
|
|
1068
1099
|
"--no-context-files",
|
|
1069
1100
|
"--no-approve"
|
|
1070
1101
|
], {
|
|
@@ -576,7 +576,38 @@ function parseWsPayload(message) {
|
|
|
576
576
|
return JSON.parse(message.data);
|
|
577
577
|
return JSON.parse(Buffer.from(message.data).toString("utf8"));
|
|
578
578
|
}
|
|
579
|
-
|
|
579
|
+
var BRIDGE_LOCAL_COMMANDS = new Set(["detach", "quit", "q", "stop"]);
|
|
580
|
+
function registerDaemonCommandsNatively(pi, options, ctx, state, commands, registered) {
|
|
581
|
+
for (const command of commands) {
|
|
582
|
+
const record = recordOf(command);
|
|
583
|
+
const name = typeof record?.name === "string" ? record.name : "";
|
|
584
|
+
if (!name || registered.has(name) || BRIDGE_LOCAL_COMMANDS.has(name))
|
|
585
|
+
continue;
|
|
586
|
+
registered.add(name);
|
|
587
|
+
const description = typeof record?.description === "string" ? record.description : undefined;
|
|
588
|
+
const source = typeof record?.source === "string" ? record.source : "worker";
|
|
589
|
+
try {
|
|
590
|
+
pi.registerCommand(name, {
|
|
591
|
+
description: `[worker ${source}] ${description ?? ""}`.trim(),
|
|
592
|
+
handler: async (args) => {
|
|
593
|
+
const text = `/${name}${args ? ` ${args}` : ""}`;
|
|
594
|
+
appendTranscript(state, "You", text);
|
|
595
|
+
try {
|
|
596
|
+
const result = await runRunPiCommandViaServer(options.context, options.runId, text);
|
|
597
|
+
const message = typeof result.message === "string" ? result.message : "worker command accepted";
|
|
598
|
+
appendTranscript(state, "System", message);
|
|
599
|
+
if (state.nativeStream)
|
|
600
|
+
ctx.ui.notify(message, "info");
|
|
601
|
+
} catch (error) {
|
|
602
|
+
reportBridgeError(ctx, state, error instanceof Error ? error.message : String(error));
|
|
603
|
+
}
|
|
604
|
+
updatePiUi(ctx, state);
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
} catch {}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
async function connectWorkerStream(options, pi, ctx, state, registeredDaemonCommands) {
|
|
580
611
|
const ready = await waitForWorkerReady(options, ctx, state);
|
|
581
612
|
if (!ready)
|
|
582
613
|
return;
|
|
@@ -631,6 +662,7 @@ async function connectWorkerStream(options, ctx, state) {
|
|
|
631
662
|
const record = recordOf(command);
|
|
632
663
|
return typeof record?.name === "string" ? [`/${record.name}`] : [];
|
|
633
664
|
});
|
|
665
|
+
registerDaemonCommandsNatively(pi, options, ctx, state, commands, registeredDaemonCommands);
|
|
634
666
|
catchupDone = true;
|
|
635
667
|
for (const payload of buffered.splice(0))
|
|
636
668
|
applyEnvelope(ctx, state, payload);
|
|
@@ -758,6 +790,7 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
758
790
|
};
|
|
759
791
|
if (options.initialMessageSent)
|
|
760
792
|
appendTranscript(state, "System", "Initial message sent to worker Pi daemon.");
|
|
793
|
+
const registeredDaemonCommands = new Set;
|
|
761
794
|
let nativePiUiContextAvailable = false;
|
|
762
795
|
pi.on("user_bash", (event) => {
|
|
763
796
|
state.nativeStream = Boolean(state.nativeStream || nativePiUiContextAvailable);
|
|
@@ -790,7 +823,7 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
790
823
|
});
|
|
791
824
|
return { consume: true };
|
|
792
825
|
});
|
|
793
|
-
connectWorkerStream(options, ctx, state).catch((error) => {
|
|
826
|
+
connectWorkerStream(options, pi, ctx, state, registeredDaemonCommands).catch((error) => {
|
|
794
827
|
appendTranscript(state, "Error", error instanceof Error ? error.message : String(error));
|
|
795
828
|
updatePiUi(ctx, state);
|
|
796
829
|
});
|
|
@@ -837,8 +870,6 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
837
870
|
"--no-tools",
|
|
838
871
|
"--no-builtin-tools",
|
|
839
872
|
"--no-skills",
|
|
840
|
-
"--no-prompt-templates",
|
|
841
|
-
"--no-themes",
|
|
842
873
|
"--no-context-files",
|
|
843
874
|
"--no-approve"
|
|
844
875
|
], {
|
|
@@ -566,7 +566,38 @@ function parseWsPayload(message) {
|
|
|
566
566
|
return JSON.parse(message.data);
|
|
567
567
|
return JSON.parse(Buffer.from(message.data).toString("utf8"));
|
|
568
568
|
}
|
|
569
|
-
|
|
569
|
+
var BRIDGE_LOCAL_COMMANDS = new Set(["detach", "quit", "q", "stop"]);
|
|
570
|
+
function registerDaemonCommandsNatively(pi, options, ctx, state, commands, registered) {
|
|
571
|
+
for (const command of commands) {
|
|
572
|
+
const record = recordOf(command);
|
|
573
|
+
const name = typeof record?.name === "string" ? record.name : "";
|
|
574
|
+
if (!name || registered.has(name) || BRIDGE_LOCAL_COMMANDS.has(name))
|
|
575
|
+
continue;
|
|
576
|
+
registered.add(name);
|
|
577
|
+
const description = typeof record?.description === "string" ? record.description : undefined;
|
|
578
|
+
const source = typeof record?.source === "string" ? record.source : "worker";
|
|
579
|
+
try {
|
|
580
|
+
pi.registerCommand(name, {
|
|
581
|
+
description: `[worker ${source}] ${description ?? ""}`.trim(),
|
|
582
|
+
handler: async (args) => {
|
|
583
|
+
const text = `/${name}${args ? ` ${args}` : ""}`;
|
|
584
|
+
appendTranscript(state, "You", text);
|
|
585
|
+
try {
|
|
586
|
+
const result = await runRunPiCommandViaServer(options.context, options.runId, text);
|
|
587
|
+
const message = typeof result.message === "string" ? result.message : "worker command accepted";
|
|
588
|
+
appendTranscript(state, "System", message);
|
|
589
|
+
if (state.nativeStream)
|
|
590
|
+
ctx.ui.notify(message, "info");
|
|
591
|
+
} catch (error) {
|
|
592
|
+
reportBridgeError(ctx, state, error instanceof Error ? error.message : String(error));
|
|
593
|
+
}
|
|
594
|
+
updatePiUi(ctx, state);
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
} catch {}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async function connectWorkerStream(options, pi, ctx, state, registeredDaemonCommands) {
|
|
570
601
|
const ready = await waitForWorkerReady(options, ctx, state);
|
|
571
602
|
if (!ready)
|
|
572
603
|
return;
|
|
@@ -621,6 +652,7 @@ async function connectWorkerStream(options, ctx, state) {
|
|
|
621
652
|
const record = recordOf(command);
|
|
622
653
|
return typeof record?.name === "string" ? [`/${record.name}`] : [];
|
|
623
654
|
});
|
|
655
|
+
registerDaemonCommandsNatively(pi, options, ctx, state, commands, registeredDaemonCommands);
|
|
624
656
|
catchupDone = true;
|
|
625
657
|
for (const payload of buffered.splice(0))
|
|
626
658
|
applyEnvelope(ctx, state, payload);
|
|
@@ -748,6 +780,7 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
748
780
|
};
|
|
749
781
|
if (options.initialMessageSent)
|
|
750
782
|
appendTranscript(state, "System", "Initial message sent to worker Pi daemon.");
|
|
783
|
+
const registeredDaemonCommands = new Set;
|
|
751
784
|
let nativePiUiContextAvailable = false;
|
|
752
785
|
pi.on("user_bash", (event) => {
|
|
753
786
|
state.nativeStream = Boolean(state.nativeStream || nativePiUiContextAvailable);
|
|
@@ -780,7 +813,7 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
780
813
|
});
|
|
781
814
|
return { consume: true };
|
|
782
815
|
});
|
|
783
|
-
connectWorkerStream(options, ctx, state).catch((error) => {
|
|
816
|
+
connectWorkerStream(options, pi, ctx, state, registeredDaemonCommands).catch((error) => {
|
|
784
817
|
appendTranscript(state, "Error", error instanceof Error ? error.message : String(error));
|
|
785
818
|
updatePiUi(ctx, state);
|
|
786
819
|
});
|
|
@@ -416,14 +416,6 @@ async function runRunPiCommandViaServer(context, runId, text) {
|
|
|
416
416
|
});
|
|
417
417
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { type: "done" };
|
|
418
418
|
}
|
|
419
|
-
async function respondRunPiCommandViaServer(context, runId, requestId, value) {
|
|
420
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands/respond`, {
|
|
421
|
-
method: "POST",
|
|
422
|
-
headers: { "content-type": "application/json" },
|
|
423
|
-
body: JSON.stringify({ requestId, value })
|
|
424
|
-
});
|
|
425
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { type: "done" };
|
|
426
|
-
}
|
|
427
419
|
async function respondRunPiExtensionUiViaServer(context, runId, requestId, valueOrCancel) {
|
|
428
420
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/extension-ui/respond`, {
|
|
429
421
|
method: "POST",
|
|
@@ -487,7 +479,6 @@ export {
|
|
|
487
479
|
selectNextWorkspaceTaskViaServer,
|
|
488
480
|
runRunPiCommandViaServer,
|
|
489
481
|
respondRunPiExtensionUiViaServer,
|
|
490
|
-
respondRunPiCommandViaServer,
|
|
491
482
|
requestServerJson,
|
|
492
483
|
registerProjectViaServer,
|
|
493
484
|
prepareRemoteCheckoutViaServer,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/commands/inspect.ts
|
|
3
|
-
import { existsSync, readFileSync } from "fs";
|
|
4
|
-
import { resolve } from "path";
|
|
3
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
4
|
+
import { resolve as resolve3 } from "path";
|
|
5
5
|
|
|
6
6
|
// packages/cli/src/runner.ts
|
|
7
7
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
@@ -53,6 +53,205 @@ import {
|
|
|
53
53
|
import { changedFilesForTask } from "@rig/runtime/control-plane/native/task-ops";
|
|
54
54
|
import { resolveHarnessPaths, resolveMonorepoRoot, runCapture } from "@rig/runtime/control-plane/native/utils";
|
|
55
55
|
import { readTaskArtifactPreview } from "@rig/runtime/control-plane/native/workspace-ops";
|
|
56
|
+
|
|
57
|
+
// packages/cli/src/commands/_server-client.ts
|
|
58
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
59
|
+
import { resolve as resolve2 } from "path";
|
|
60
|
+
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
61
|
+
|
|
62
|
+
// packages/cli/src/commands/_connection-state.ts
|
|
63
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
64
|
+
import { homedir } from "os";
|
|
65
|
+
import { dirname, resolve } from "path";
|
|
66
|
+
function resolveGlobalConnectionsPath(env = process.env) {
|
|
67
|
+
const explicit = env.RIG_CONNECTIONS_FILE?.trim();
|
|
68
|
+
if (explicit)
|
|
69
|
+
return resolve(explicit);
|
|
70
|
+
const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
|
|
71
|
+
if (stateDir)
|
|
72
|
+
return resolve(stateDir, "connections.json");
|
|
73
|
+
return resolve(homedir(), ".rig", "connections.json");
|
|
74
|
+
}
|
|
75
|
+
function resolveRepoConnectionPath(projectRoot) {
|
|
76
|
+
return resolve(projectRoot, ".rig", "state", "connection.json");
|
|
77
|
+
}
|
|
78
|
+
function readJsonFile(path) {
|
|
79
|
+
if (!existsSync(path))
|
|
80
|
+
return null;
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function normalizeConnection(value) {
|
|
88
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
89
|
+
return null;
|
|
90
|
+
const record = value;
|
|
91
|
+
if (record.kind === "local")
|
|
92
|
+
return { kind: "local", mode: "auto" };
|
|
93
|
+
if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
|
|
94
|
+
const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
|
|
95
|
+
return { kind: "remote", baseUrl };
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
function readGlobalConnections(options = {}) {
|
|
100
|
+
const path = resolveGlobalConnectionsPath(options.env ?? process.env);
|
|
101
|
+
const payload = readJsonFile(path);
|
|
102
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
103
|
+
return { connections: {} };
|
|
104
|
+
}
|
|
105
|
+
const rawConnections = payload.connections;
|
|
106
|
+
const connections = {};
|
|
107
|
+
if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
|
|
108
|
+
for (const [alias, raw] of Object.entries(rawConnections)) {
|
|
109
|
+
const connection = normalizeConnection(raw);
|
|
110
|
+
if (connection)
|
|
111
|
+
connections[alias] = connection;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return { connections };
|
|
115
|
+
}
|
|
116
|
+
function readRepoConnection(projectRoot) {
|
|
117
|
+
const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
|
|
118
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
119
|
+
return null;
|
|
120
|
+
const record = payload;
|
|
121
|
+
const selected = typeof record.selected === "string" ? record.selected.trim() : "";
|
|
122
|
+
if (!selected)
|
|
123
|
+
return null;
|
|
124
|
+
return {
|
|
125
|
+
selected,
|
|
126
|
+
project: typeof record.project === "string" ? record.project : undefined,
|
|
127
|
+
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
131
|
+
const repo = readRepoConnection(projectRoot);
|
|
132
|
+
if (!repo)
|
|
133
|
+
return null;
|
|
134
|
+
if (repo.selected === "local")
|
|
135
|
+
return { alias: "local", connection: { kind: "local", mode: "auto" } };
|
|
136
|
+
const global = readGlobalConnections(options);
|
|
137
|
+
const connection = global.connections[repo.selected];
|
|
138
|
+
if (!connection) {
|
|
139
|
+
throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
140
|
+
}
|
|
141
|
+
return { alias: repo.selected, connection };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// packages/cli/src/commands/_server-client.ts
|
|
145
|
+
var scopedGitHubBearerTokens = new Map;
|
|
146
|
+
function cleanToken(value) {
|
|
147
|
+
const trimmed = value?.trim();
|
|
148
|
+
return trimmed ? trimmed : null;
|
|
149
|
+
}
|
|
150
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
151
|
+
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
152
|
+
if (!existsSync2(path))
|
|
153
|
+
return null;
|
|
154
|
+
try {
|
|
155
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
156
|
+
return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
162
|
+
const scopedKey = resolve2(projectRoot);
|
|
163
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
164
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
165
|
+
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
166
|
+
if (privateSession)
|
|
167
|
+
return privateSession;
|
|
168
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
169
|
+
}
|
|
170
|
+
async function ensureServerForCli(projectRoot) {
|
|
171
|
+
try {
|
|
172
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
173
|
+
if (selected?.connection.kind === "remote") {
|
|
174
|
+
return {
|
|
175
|
+
baseUrl: selected.connection.baseUrl,
|
|
176
|
+
authToken: readGitHubBearerTokenForRemote(projectRoot),
|
|
177
|
+
connectionKind: "remote"
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
181
|
+
return {
|
|
182
|
+
baseUrl: connection.baseUrl,
|
|
183
|
+
authToken: connection.authToken,
|
|
184
|
+
connectionKind: "local"
|
|
185
|
+
};
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (error instanceof Error) {
|
|
188
|
+
throw new CliError2(error.message, 1);
|
|
189
|
+
}
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function mergeHeaders(headers, authToken) {
|
|
194
|
+
const merged = new Headers(headers);
|
|
195
|
+
if (authToken) {
|
|
196
|
+
merged.set("authorization", `Bearer ${authToken}`);
|
|
197
|
+
}
|
|
198
|
+
return merged;
|
|
199
|
+
}
|
|
200
|
+
function diagnosticMessage(payload) {
|
|
201
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
202
|
+
return null;
|
|
203
|
+
const record = payload;
|
|
204
|
+
const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
|
|
205
|
+
const messages = diagnostics.flatMap((entry) => {
|
|
206
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
207
|
+
return [];
|
|
208
|
+
const diagnostic = entry;
|
|
209
|
+
const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
|
|
210
|
+
const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
|
|
211
|
+
return message ? [`${kind}: ${message}`] : [];
|
|
212
|
+
});
|
|
213
|
+
return messages.length > 0 ? messages.join("; ") : null;
|
|
214
|
+
}
|
|
215
|
+
async function requestServerJson(context, pathname, init = {}) {
|
|
216
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
217
|
+
const response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
218
|
+
...init,
|
|
219
|
+
headers: mergeHeaders(init.headers, server.authToken)
|
|
220
|
+
});
|
|
221
|
+
const text = await response.text();
|
|
222
|
+
const payload = text.trim().length > 0 ? (() => {
|
|
223
|
+
try {
|
|
224
|
+
return JSON.parse(text);
|
|
225
|
+
} catch {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
})() : null;
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
const diagnostics = diagnosticMessage(payload);
|
|
231
|
+
const detail = diagnostics ?? (text || response.statusText);
|
|
232
|
+
throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
|
|
233
|
+
}
|
|
234
|
+
return payload;
|
|
235
|
+
}
|
|
236
|
+
async function listRunsViaServer(context, options = {}) {
|
|
237
|
+
const url = new URL("http://rig.local/api/runs");
|
|
238
|
+
if (options.limit !== undefined)
|
|
239
|
+
url.searchParams.set("limit", String(options.limit));
|
|
240
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
241
|
+
const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
|
|
242
|
+
return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
|
|
243
|
+
}
|
|
244
|
+
async function getRunLogsViaServer(context, runId, options = {}) {
|
|
245
|
+
const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/logs`);
|
|
246
|
+
if (options.limit !== undefined)
|
|
247
|
+
url.searchParams.set("limit", String(options.limit));
|
|
248
|
+
if (options.cursor)
|
|
249
|
+
url.searchParams.set("cursor", options.cursor);
|
|
250
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
251
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// packages/cli/src/commands/inspect.ts
|
|
56
255
|
async function executeInspect(context, args) {
|
|
57
256
|
const [command = "failures", ...rest] = args;
|
|
58
257
|
switch (command) {
|
|
@@ -62,10 +261,27 @@ async function executeInspect(context, args) {
|
|
|
62
261
|
const requiredTask = requireTask(task, "rig inspect logs --task <task-id>");
|
|
63
262
|
const latestRun = listAuthorityRuns(context.projectRoot).map((entry) => readAuthorityRun(context.projectRoot, entry.runId)).filter((run) => Boolean(run)).filter((run) => run.taskId === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
|
|
64
263
|
if (!latestRun) {
|
|
65
|
-
|
|
264
|
+
const serverRuns = await listRunsViaServer(context, { limit: 200 }).catch(() => []);
|
|
265
|
+
const serverRun = serverRuns.filter((run) => String(run.taskId ?? "") === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
|
|
266
|
+
if (!serverRun || typeof serverRun.runId !== "string") {
|
|
267
|
+
throw new CliError2(`No runs found for ${requiredTask} (local or on the selected server).`);
|
|
268
|
+
}
|
|
269
|
+
const page = await getRunLogsViaServer(context, serverRun.runId, { limit: 500 });
|
|
270
|
+
const entries = Array.isArray(page.entries) ? page.entries : [];
|
|
271
|
+
if (context.outputMode === "text") {
|
|
272
|
+
for (const entry of entries) {
|
|
273
|
+
const record = entry && typeof entry === "object" ? entry : {};
|
|
274
|
+
const title = String(record.title ?? "");
|
|
275
|
+
const detail = String(record.detail ?? "");
|
|
276
|
+
console.log([title, detail].filter(Boolean).join(" \u2014 "));
|
|
277
|
+
}
|
|
278
|
+
if (entries.length === 0)
|
|
279
|
+
console.log(`(no log entries for run ${serverRun.runId})`);
|
|
280
|
+
}
|
|
281
|
+
return { ok: true, group: "inspect", command, details: { task: requiredTask, runId: serverRun.runId, source: "server", entries } };
|
|
66
282
|
}
|
|
67
|
-
const logsPath =
|
|
68
|
-
if (!
|
|
283
|
+
const logsPath = resolve3(resolveAuthorityRunDir(context.projectRoot, latestRun.runId), "logs.jsonl");
|
|
284
|
+
if (!existsSync3(logsPath)) {
|
|
69
285
|
throw new CliError2(`No logs found for run ${latestRun.runId}.`);
|
|
70
286
|
}
|
|
71
287
|
await context.runCommand(["cat", logsPath]);
|
|
@@ -75,7 +291,7 @@ async function executeInspect(context, args) {
|
|
|
75
291
|
const { value: task, rest: remaining } = takeOption(rest, "--task");
|
|
76
292
|
requireNoExtraArgs(remaining, "rig inspect artifacts --task <task-id>");
|
|
77
293
|
const requiredTask = requireTask(task, "rig inspect artifacts --task <task-id>");
|
|
78
|
-
const artifactRoot = resolveTaskArtifactDirs(context.projectRoot, requiredTask).find((path) =>
|
|
294
|
+
const artifactRoot = resolveTaskArtifactDirs(context.projectRoot, requiredTask).find((path) => existsSync3(path));
|
|
79
295
|
if (!artifactRoot) {
|
|
80
296
|
throw new CliError2(`No artifacts found for ${requiredTask}.`);
|
|
81
297
|
}
|
|
@@ -132,10 +348,10 @@ async function executeInspect(context, args) {
|
|
|
132
348
|
case "failures": {
|
|
133
349
|
requireNoExtraArgs(rest, "rig inspect failures");
|
|
134
350
|
const failed = resolveHarnessPaths(context.projectRoot).failedApproachesPath;
|
|
135
|
-
if (!
|
|
351
|
+
if (!existsSync3(failed)) {
|
|
136
352
|
console.log("No failures recorded.");
|
|
137
353
|
} else {
|
|
138
|
-
process.stdout.write(
|
|
354
|
+
process.stdout.write(readFileSync3(failed, "utf-8"));
|
|
139
355
|
}
|
|
140
356
|
return { ok: true, group: "inspect", command };
|
|
141
357
|
}
|
|
@@ -152,11 +368,11 @@ async function executeInspect(context, args) {
|
|
|
152
368
|
return { ok: true, group: "inspect", command };
|
|
153
369
|
case "audit": {
|
|
154
370
|
requireNoExtraArgs(rest, "rig inspect audit");
|
|
155
|
-
const auditPath =
|
|
156
|
-
if (!
|
|
371
|
+
const auditPath = resolve3(resolveHarnessPaths(context.projectRoot).logsDir, "audit.jsonl");
|
|
372
|
+
if (!existsSync3(auditPath)) {
|
|
157
373
|
console.log("No audit log found.");
|
|
158
374
|
} else {
|
|
159
|
-
const lines =
|
|
375
|
+
const lines = readFileSync3(auditPath, "utf-8").split(/\r?\n/).filter(Boolean).slice(-20);
|
|
160
376
|
for (const line of lines) {
|
|
161
377
|
console.log(line);
|
|
162
378
|
}
|