@h-rig/cli 0.0.6-alpha.7 → 0.0.6-alpha.70
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 +1 -1
- package/dist/bin/rig.js +4507 -1506
- package/dist/src/commands/_async-ui.js +152 -0
- package/dist/src/commands/_authority-runs.js +2 -3
- package/dist/src/commands/_cli-format.js +369 -0
- package/dist/src/commands/_connection-state.js +30 -11
- package/dist/src/commands/_doctor-checks.js +177 -43
- package/dist/src/commands/_help-catalog.js +485 -0
- package/dist/src/commands/_json-output.js +56 -0
- package/dist/src/commands/_operator-surface.js +220 -0
- package/dist/src/commands/_operator-view.js +595 -72
- package/dist/src/commands/_parsers.js +18 -11
- package/dist/src/commands/_pi-frontend.js +411 -0
- package/dist/src/commands/_pi-install.js +4 -3
- package/dist/src/commands/_policy.js +12 -5
- package/dist/src/commands/_preflight.js +187 -127
- package/dist/src/commands/_run-driver-helpers.js +75 -22
- package/dist/src/commands/_run-replay.js +142 -0
- package/dist/src/commands/_server-client.js +343 -60
- package/dist/src/commands/_snapshot-upload.js +160 -38
- package/dist/src/commands/_spinner.js +65 -0
- package/dist/src/commands/_task-picker.js +44 -16
- package/dist/src/commands/agent.js +39 -20
- package/dist/src/commands/browser.js +28 -21
- package/dist/src/commands/connect.js +146 -33
- package/dist/src/commands/dist.js +19 -12
- package/dist/src/commands/doctor.js +304 -44
- package/dist/src/commands/github.js +301 -52
- package/dist/src/commands/inbox.js +679 -72
- package/dist/src/commands/init.js +622 -118
- package/dist/src/commands/inspect.js +515 -32
- package/dist/src/commands/inspector.js +20 -13
- package/dist/src/commands/pi.js +177 -0
- package/dist/src/commands/plugin.js +95 -27
- package/dist/src/commands/profile-and-review.js +26 -19
- package/dist/src/commands/queue.js +32 -12
- package/dist/src/commands/remote.js +43 -36
- package/dist/src/commands/repo-git-harness.js +22 -15
- package/dist/src/commands/run.js +1162 -158
- package/dist/src/commands/server.js +373 -56
- package/dist/src/commands/setup.js +316 -62
- package/dist/src/commands/stats.js +1030 -0
- package/dist/src/commands/task-report-bug.js +29 -22
- package/dist/src/commands/task-run-driver.js +862 -129
- package/dist/src/commands/task.js +1423 -311
- package/dist/src/commands/test.js +15 -8
- package/dist/src/commands/workspace.js +18 -11
- package/dist/src/commands.js +4446 -1499
- package/dist/src/index.js +4502 -1504
- package/dist/src/launcher.js +77 -13
- package/dist/src/report-bug.js +3 -3
- package/dist/src/runner.js +16 -22
- package/package.json +10 -5
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/runner.ts
|
|
3
3
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
4
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
4
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
5
5
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
6
|
-
import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
7
|
-
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
8
6
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
9
|
-
|
|
7
|
+
|
|
8
|
+
class CliError extends RuntimeCliError {
|
|
9
|
+
hint;
|
|
10
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
11
|
+
super(message, exitCode);
|
|
12
|
+
if (options.hint?.trim()) {
|
|
13
|
+
this.hint = options.hint.trim();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
10
17
|
function takeOption(args, option) {
|
|
11
18
|
const rest = [];
|
|
12
19
|
let value;
|
|
@@ -15,7 +22,7 @@ function takeOption(args, option) {
|
|
|
15
22
|
if (current === option) {
|
|
16
23
|
const next = args[index + 1];
|
|
17
24
|
if (!next || next.startsWith("-")) {
|
|
18
|
-
throw new CliError(`Missing value for ${option}`);
|
|
25
|
+
throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
|
|
19
26
|
}
|
|
20
27
|
value = next;
|
|
21
28
|
index += 1;
|
|
@@ -34,12 +41,14 @@ Usage: ${usage}`);
|
|
|
34
41
|
}
|
|
35
42
|
}
|
|
36
43
|
|
|
44
|
+
// packages/cli/src/commands/server.ts
|
|
45
|
+
import { resolveRigServerCommand } from "@rig/runtime/local-server";
|
|
46
|
+
|
|
37
47
|
// packages/cli/src/commands/_authority-runs.ts
|
|
38
48
|
import {
|
|
39
49
|
readAuthorityRun,
|
|
40
50
|
readJsonlFile,
|
|
41
|
-
|
|
42
|
-
writeJsonFile
|
|
51
|
+
writeAuthorityRunRecord
|
|
43
52
|
} from "@rig/runtime/control-plane/authority-files";
|
|
44
53
|
|
|
45
54
|
// packages/cli/src/commands/_paths.ts
|
|
@@ -60,11 +69,8 @@ function normalizeRuntimeAdapter(value) {
|
|
|
60
69
|
return "claude-code";
|
|
61
70
|
}
|
|
62
71
|
|
|
63
|
-
// packages/cli/src/commands/
|
|
64
|
-
import {
|
|
65
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
66
|
-
import { resolve as resolve2 } from "path";
|
|
67
|
-
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
72
|
+
// packages/cli/src/commands/connect.ts
|
|
73
|
+
import { cancel, isCancel, select } from "@clack/prompts";
|
|
68
74
|
|
|
69
75
|
// packages/cli/src/commands/_connection-state.ts
|
|
70
76
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -88,9 +94,14 @@ function readJsonFile(path) {
|
|
|
88
94
|
try {
|
|
89
95
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
90
96
|
} catch (error) {
|
|
91
|
-
throw new
|
|
97
|
+
throw new CliError(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1, { hint: "Fix or delete that file, then re-select a server with `rig server use <alias|local>`." });
|
|
92
98
|
}
|
|
93
99
|
}
|
|
100
|
+
function writeJsonFile(path, value) {
|
|
101
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
102
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
103
|
+
`, "utf8");
|
|
104
|
+
}
|
|
94
105
|
function normalizeConnection(value) {
|
|
95
106
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
96
107
|
return null;
|
|
@@ -120,6 +131,18 @@ function readGlobalConnections(options = {}) {
|
|
|
120
131
|
}
|
|
121
132
|
return { connections };
|
|
122
133
|
}
|
|
134
|
+
function writeGlobalConnections(state, options = {}) {
|
|
135
|
+
writeJsonFile(resolveGlobalConnectionsPath(options.env ?? process.env), state);
|
|
136
|
+
}
|
|
137
|
+
function upsertGlobalConnection(alias, connection, options = {}) {
|
|
138
|
+
const cleanAlias = alias.trim();
|
|
139
|
+
if (!cleanAlias)
|
|
140
|
+
throw new CliError("Connection alias is required.", 1, { hint: "Save a server with `rig server add <alias> <url>`." });
|
|
141
|
+
const state = readGlobalConnections(options);
|
|
142
|
+
state.connections[cleanAlias] = connection;
|
|
143
|
+
writeGlobalConnections(state, options);
|
|
144
|
+
return state;
|
|
145
|
+
}
|
|
123
146
|
function readRepoConnection(projectRoot) {
|
|
124
147
|
const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
|
|
125
148
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
@@ -131,25 +154,217 @@ function readRepoConnection(projectRoot) {
|
|
|
131
154
|
return {
|
|
132
155
|
selected,
|
|
133
156
|
project: typeof record.project === "string" ? record.project : undefined,
|
|
134
|
-
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
|
|
157
|
+
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
158
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
135
159
|
};
|
|
136
160
|
}
|
|
161
|
+
function writeRepoConnection(projectRoot, state) {
|
|
162
|
+
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
163
|
+
}
|
|
137
164
|
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
138
165
|
const repo = readRepoConnection(projectRoot);
|
|
139
166
|
if (!repo)
|
|
140
167
|
return null;
|
|
141
168
|
if (repo.selected === "local")
|
|
142
|
-
return { alias: "local", connection: { kind: "local", mode: "auto" } };
|
|
169
|
+
return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
|
|
143
170
|
const global = readGlobalConnections(options);
|
|
144
171
|
const connection = global.connections[repo.selected];
|
|
145
172
|
if (!connection) {
|
|
146
|
-
throw new
|
|
173
|
+
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
174
|
+
}
|
|
175
|
+
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
176
|
+
}
|
|
177
|
+
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
178
|
+
const repo = readRepoConnection(projectRoot);
|
|
179
|
+
if (!repo)
|
|
180
|
+
return;
|
|
181
|
+
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// packages/cli/src/commands/_cli-format.ts
|
|
185
|
+
import { log, note } from "@clack/prompts";
|
|
186
|
+
import pc from "picocolors";
|
|
187
|
+
function truncate(value, width) {
|
|
188
|
+
if (value.length <= width)
|
|
189
|
+
return value;
|
|
190
|
+
if (width <= 1)
|
|
191
|
+
return "\u2026";
|
|
192
|
+
return `${value.slice(0, width - 1)}\u2026`;
|
|
193
|
+
}
|
|
194
|
+
function pad(value, width) {
|
|
195
|
+
return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
|
|
196
|
+
}
|
|
197
|
+
function statusColor(status) {
|
|
198
|
+
const normalized = status.toLowerCase();
|
|
199
|
+
if (["completed", "merged", "closed", "done", "accepted", "pass", "selected", "approved"].includes(normalized))
|
|
200
|
+
return pc.green;
|
|
201
|
+
if (["failed", "needs_attention", "needs-attention", "blocked", "error", "rejected"].includes(normalized))
|
|
202
|
+
return pc.red;
|
|
203
|
+
if (["running", "reviewing", "validating", "in_progress", "in-progress", "remote"].includes(normalized))
|
|
204
|
+
return pc.cyan;
|
|
205
|
+
if (["ready", "open", "queued", "created", "preparing", "local", "pending"].includes(normalized))
|
|
206
|
+
return pc.yellow;
|
|
207
|
+
return pc.dim;
|
|
208
|
+
}
|
|
209
|
+
function formatStatusPill(status) {
|
|
210
|
+
const label = status || "unknown";
|
|
211
|
+
return statusColor(label)(`\u25CF ${label}`);
|
|
212
|
+
}
|
|
213
|
+
function formatSection(title, subtitle) {
|
|
214
|
+
return `${pc.bold(pc.cyan("\u25C6"))} ${pc.bold(title)}${subtitle ? pc.dim(` \u2014 ${subtitle}`) : ""}`;
|
|
215
|
+
}
|
|
216
|
+
function formatSuccessCard(title, rows = []) {
|
|
217
|
+
const body = rows.filter(([, value]) => value !== undefined && value !== null && String(value).length > 0).map(([key, value]) => `${pc.dim("\u2502")} ${pc.dim(key.padEnd(12))} ${value}`);
|
|
218
|
+
return [formatSection(title), ...body].join(`
|
|
219
|
+
`);
|
|
220
|
+
}
|
|
221
|
+
function formatNextSteps(steps) {
|
|
222
|
+
if (steps.length === 0)
|
|
223
|
+
return [];
|
|
224
|
+
return [pc.bold("Next"), ...steps.map((step) => `${pc.dim("\u203A")} ${step}`)];
|
|
225
|
+
}
|
|
226
|
+
function formatConnectionList(connections) {
|
|
227
|
+
const rows = [["local", { kind: "local", mode: "auto" }], ...Object.entries(connections)];
|
|
228
|
+
const aliasWidth = Math.min(24, Math.max(5, ...rows.map(([alias]) => alias.length)));
|
|
229
|
+
const lines = rows.map(([alias, connection]) => [
|
|
230
|
+
pc.bold(pad(truncate(alias, aliasWidth), aliasWidth)),
|
|
231
|
+
formatStatusPill(connection.kind),
|
|
232
|
+
connection.kind === "remote" ? connection.baseUrl ?? "" : connection.mode ?? "local"
|
|
233
|
+
].join(" "));
|
|
234
|
+
return [formatSection("Rig servers", `${rows.length} available`), `${pc.bold(pad("ALIAS", aliasWidth))} ${pc.bold("KIND")} ${pc.bold("TARGET")}`, ...lines, "", ...formatNextSteps(["Select one: `rig server use <alias|local>`"])].join(`
|
|
235
|
+
`);
|
|
236
|
+
}
|
|
237
|
+
function formatConnectionStatus(selected, connections) {
|
|
238
|
+
const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
|
|
239
|
+
const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
|
|
240
|
+
return [
|
|
241
|
+
formatSection("Rig server", "selected for this repo"),
|
|
242
|
+
`${pc.dim("\u2502")} ${pc.dim("selected ")} ${pc.bold(selected)}`,
|
|
243
|
+
`${pc.dim("\u2502")} ${pc.dim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
|
|
244
|
+
`${pc.dim("\u2502")} ${pc.dim("target ")} ${target ?? "not configured"}`,
|
|
245
|
+
"",
|
|
246
|
+
...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
|
|
247
|
+
].join(`
|
|
248
|
+
`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// packages/cli/src/commands/connect.ts
|
|
252
|
+
function usageName(options) {
|
|
253
|
+
return `rig ${options.group}`;
|
|
254
|
+
}
|
|
255
|
+
function parseConnection(alias, value, options) {
|
|
256
|
+
if (alias === "local" && !value)
|
|
257
|
+
return { kind: "local", mode: "auto" };
|
|
258
|
+
if (!value)
|
|
259
|
+
throw new CliError(`Missing remote server URL. Usage: ${usageName(options)} add <alias> <url>`, 1);
|
|
260
|
+
let parsed;
|
|
261
|
+
try {
|
|
262
|
+
parsed = new URL(value);
|
|
263
|
+
} catch {
|
|
264
|
+
throw new CliError(`Invalid Rig server URL: ${value}`, 1, { hint: "Pass a full http(s) URL, e.g. `rig server add prod https://rig.example.com`." });
|
|
265
|
+
}
|
|
266
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
267
|
+
throw new CliError("Rig remote server URL must be http(s).", 1, { hint: "Use an http:// or https:// URL, e.g. `rig server add prod https://rig.example.com`." });
|
|
268
|
+
}
|
|
269
|
+
return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
|
|
270
|
+
}
|
|
271
|
+
function printJsonOrText(context, payload, text) {
|
|
272
|
+
if (context.outputMode === "json") {
|
|
273
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
274
|
+
} else {
|
|
275
|
+
console.log(text);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async function promptForConnectionAlias(context) {
|
|
279
|
+
const state = readGlobalConnections();
|
|
280
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
281
|
+
const options = [
|
|
282
|
+
{ value: "local", label: "local", hint: "Use/start a local Rig server" },
|
|
283
|
+
...Object.entries(state.connections).map(([alias, connection]) => ({
|
|
284
|
+
value: alias,
|
|
285
|
+
label: alias,
|
|
286
|
+
hint: connection.kind === "remote" ? connection.baseUrl : "local"
|
|
287
|
+
}))
|
|
288
|
+
].filter((option, index, all) => all.findIndex((candidate) => candidate.value === option.value) === index);
|
|
289
|
+
const answer = await select({
|
|
290
|
+
message: "Select Rig server for this repo",
|
|
291
|
+
initialValue: repo?.selected ?? "local",
|
|
292
|
+
options
|
|
293
|
+
});
|
|
294
|
+
if (isCancel(answer)) {
|
|
295
|
+
cancel("No server selected.");
|
|
296
|
+
throw new CliError("No server selected.", 3);
|
|
297
|
+
}
|
|
298
|
+
return String(answer);
|
|
299
|
+
}
|
|
300
|
+
async function executeConnectionCommand(context, args, options) {
|
|
301
|
+
const [command, ...rest] = args;
|
|
302
|
+
switch (command ?? "status") {
|
|
303
|
+
case "list": {
|
|
304
|
+
requireNoExtraArgs(rest, `${usageName(options)} list`);
|
|
305
|
+
const state = readGlobalConnections();
|
|
306
|
+
printJsonOrText(context, state, formatConnectionList(state.connections));
|
|
307
|
+
return { ok: true, group: options.group, command: "list", details: state };
|
|
308
|
+
}
|
|
309
|
+
case "add": {
|
|
310
|
+
const [alias, url, ...extra] = rest;
|
|
311
|
+
if (!alias)
|
|
312
|
+
throw new CliError(`Missing alias. Usage: ${usageName(options)} add <alias> <url>`, 1);
|
|
313
|
+
requireNoExtraArgs(extra, `${usageName(options)} add <alias> <url>`);
|
|
314
|
+
const connection = parseConnection(alias, url, options);
|
|
315
|
+
const state = upsertGlobalConnection(alias, connection);
|
|
316
|
+
printJsonOrText(context, { alias, connection }, formatSuccessCard("Rig server saved", [
|
|
317
|
+
["alias", alias],
|
|
318
|
+
["target", connection.kind === "remote" ? connection.baseUrl : "local"],
|
|
319
|
+
["next", `${usageName(options)} use ${alias}`]
|
|
320
|
+
]));
|
|
321
|
+
return { ok: true, group: options.group, command: "add", details: { alias, connection, count: Object.keys(state.connections).length } };
|
|
322
|
+
}
|
|
323
|
+
case "use": {
|
|
324
|
+
let [alias, ...extra] = rest;
|
|
325
|
+
requireNoExtraArgs(extra, `${usageName(options)} use <alias|local>`);
|
|
326
|
+
if (!alias && options.interactiveUse && context.outputMode === "text" && process.stdin.isTTY) {
|
|
327
|
+
alias = await promptForConnectionAlias(context);
|
|
328
|
+
}
|
|
329
|
+
if (!alias)
|
|
330
|
+
throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
|
|
331
|
+
if (alias !== "local") {
|
|
332
|
+
const state = readGlobalConnections();
|
|
333
|
+
if (!state.connections[alias])
|
|
334
|
+
throw new CliError(`Unknown Rig server: ${alias}`, 1, { hint: "Run `rig server list` to see saved aliases, or save one with `rig server add <alias> <url>`." });
|
|
335
|
+
}
|
|
336
|
+
const repoState = { selected: alias, linkedAt: new Date().toISOString() };
|
|
337
|
+
writeRepoConnection(context.projectRoot, repoState);
|
|
338
|
+
printJsonOrText(context, repoState, formatSuccessCard("Rig server selected", [
|
|
339
|
+
["selected", alias],
|
|
340
|
+
["scope", "this repo"],
|
|
341
|
+
["next", "rig task list"]
|
|
342
|
+
]));
|
|
343
|
+
return { ok: true, group: options.group, command: "use", details: repoState };
|
|
344
|
+
}
|
|
345
|
+
case "status": {
|
|
346
|
+
requireNoExtraArgs(rest, `${usageName(options)} status`);
|
|
347
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
348
|
+
const global = readGlobalConnections();
|
|
349
|
+
const details = { selected: repo?.selected ?? "local", repo, connections: global.connections };
|
|
350
|
+
printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections));
|
|
351
|
+
return { ok: true, group: options.group, command: "status", details };
|
|
352
|
+
}
|
|
353
|
+
default:
|
|
354
|
+
throw new CliError(`Unknown ${options.group} command: ${String(command)}
|
|
355
|
+
Usage: ${usageName(options)} <list|add|use|status>`, 1);
|
|
147
356
|
}
|
|
148
|
-
return { alias: repo.selected, connection };
|
|
149
357
|
}
|
|
150
358
|
|
|
151
359
|
// packages/cli/src/commands/_server-client.ts
|
|
152
|
-
|
|
360
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
361
|
+
import { resolve as resolve2 } from "path";
|
|
362
|
+
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
363
|
+
var scopedGitHubBearerTokens = new Map;
|
|
364
|
+
var serverPhaseListener = null;
|
|
365
|
+
function reportServerPhase(label) {
|
|
366
|
+
serverPhaseListener?.(label);
|
|
367
|
+
}
|
|
153
368
|
function cleanToken(value) {
|
|
154
369
|
const trimmed = value?.trim();
|
|
155
370
|
return trimmed ? trimmed : null;
|
|
@@ -166,49 +381,80 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
166
381
|
}
|
|
167
382
|
}
|
|
168
383
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
169
|
-
|
|
170
|
-
|
|
384
|
+
const scopedKey = resolve2(projectRoot);
|
|
385
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
386
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
171
387
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
172
|
-
if (privateSession)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
return
|
|
388
|
+
if (privateSession)
|
|
389
|
+
return privateSession;
|
|
390
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
391
|
+
}
|
|
392
|
+
function readStoredGitHubAuthToken(projectRoot) {
|
|
393
|
+
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
394
|
+
if (!existsSync2(path))
|
|
395
|
+
return null;
|
|
396
|
+
try {
|
|
397
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
398
|
+
return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
|
|
399
|
+
} catch {
|
|
400
|
+
return null;
|
|
180
401
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
185
|
-
});
|
|
186
|
-
cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
|
|
187
|
-
return cachedGitHubBearerToken;
|
|
402
|
+
}
|
|
403
|
+
function readLocalConnectionFallbackToken(projectRoot) {
|
|
404
|
+
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
188
405
|
}
|
|
189
406
|
async function ensureServerForCli(projectRoot) {
|
|
190
407
|
try {
|
|
191
408
|
const selected = resolveSelectedConnection(projectRoot);
|
|
192
409
|
if (selected?.connection.kind === "remote") {
|
|
410
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
411
|
+
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
412
|
+
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
193
413
|
return {
|
|
194
414
|
baseUrl: selected.connection.baseUrl,
|
|
195
|
-
authToken
|
|
196
|
-
connectionKind: "remote"
|
|
415
|
+
authToken,
|
|
416
|
+
connectionKind: "remote",
|
|
417
|
+
serverProjectRoot
|
|
197
418
|
};
|
|
198
419
|
}
|
|
420
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
199
421
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
200
422
|
return {
|
|
201
423
|
baseUrl: connection.baseUrl,
|
|
202
|
-
authToken: connection.authToken,
|
|
203
|
-
connectionKind: "local"
|
|
424
|
+
authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
|
|
425
|
+
connectionKind: "local",
|
|
426
|
+
serverProjectRoot: resolve2(projectRoot)
|
|
204
427
|
};
|
|
205
428
|
} catch (error) {
|
|
206
429
|
if (error instanceof Error) {
|
|
207
|
-
throw new
|
|
430
|
+
throw new CliError(error.message, 1);
|
|
208
431
|
}
|
|
209
432
|
throw error;
|
|
210
433
|
}
|
|
211
434
|
}
|
|
435
|
+
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
436
|
+
const repo = readRepoConnection(projectRoot);
|
|
437
|
+
const slug = repo?.project?.trim();
|
|
438
|
+
if (!slug)
|
|
439
|
+
return null;
|
|
440
|
+
try {
|
|
441
|
+
const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
|
|
442
|
+
headers: mergeHeaders(undefined, authToken)
|
|
443
|
+
});
|
|
444
|
+
if (!response.ok)
|
|
445
|
+
return null;
|
|
446
|
+
const payload = await response.json();
|
|
447
|
+
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
448
|
+
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
449
|
+
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
450
|
+
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
451
|
+
if (path)
|
|
452
|
+
writeRepoServerProjectRoot(projectRoot, path);
|
|
453
|
+
return path;
|
|
454
|
+
} catch {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
212
458
|
function mergeHeaders(headers, authToken) {
|
|
213
459
|
const merged = new Headers(headers);
|
|
214
460
|
if (authToken) {
|
|
@@ -231,12 +477,65 @@ function diagnosticMessage(payload) {
|
|
|
231
477
|
});
|
|
232
478
|
return messages.length > 0 ? messages.join("; ") : null;
|
|
233
479
|
}
|
|
480
|
+
var serverReachabilityCache = new Map;
|
|
481
|
+
async function probeServerReachability(baseUrl, authToken) {
|
|
482
|
+
try {
|
|
483
|
+
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
|
|
484
|
+
headers: mergeHeaders(undefined, authToken),
|
|
485
|
+
signal: AbortSignal.timeout(1500)
|
|
486
|
+
});
|
|
487
|
+
return response.ok;
|
|
488
|
+
} catch {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function cachedServerReachability(projectRoot, baseUrl, authToken) {
|
|
493
|
+
const key = resolve2(projectRoot);
|
|
494
|
+
const cached = serverReachabilityCache.get(key);
|
|
495
|
+
if (cached)
|
|
496
|
+
return cached;
|
|
497
|
+
const probe = probeServerReachability(baseUrl, authToken);
|
|
498
|
+
serverReachabilityCache.set(key, probe);
|
|
499
|
+
return probe;
|
|
500
|
+
}
|
|
501
|
+
function describeSelectedServer(projectRoot, server) {
|
|
502
|
+
try {
|
|
503
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
504
|
+
if (selected) {
|
|
505
|
+
return {
|
|
506
|
+
alias: selected.alias,
|
|
507
|
+
target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
} catch {}
|
|
511
|
+
return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
|
|
512
|
+
}
|
|
513
|
+
async function buildServerFailureContext(projectRoot, server) {
|
|
514
|
+
const { alias, target } = describeSelectedServer(projectRoot, server);
|
|
515
|
+
const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
|
|
516
|
+
const reachability = reachable ? "server is reachable" : "server is unreachable";
|
|
517
|
+
return {
|
|
518
|
+
contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
|
|
519
|
+
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
520
|
+
};
|
|
521
|
+
}
|
|
234
522
|
async function requestServerJson(context, pathname, init = {}) {
|
|
235
523
|
const server = await ensureServerForCli(context.projectRoot);
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
headers
|
|
239
|
-
});
|
|
524
|
+
const headers = mergeHeaders(init.headers, server.authToken);
|
|
525
|
+
if (server.serverProjectRoot)
|
|
526
|
+
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
527
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
528
|
+
let response;
|
|
529
|
+
try {
|
|
530
|
+
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
531
|
+
...init,
|
|
532
|
+
headers
|
|
533
|
+
});
|
|
534
|
+
} catch (error) {
|
|
535
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
536
|
+
throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
|
|
537
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
538
|
+
}
|
|
240
539
|
const text = await response.text();
|
|
241
540
|
const payload = text.trim().length > 0 ? (() => {
|
|
242
541
|
try {
|
|
@@ -248,10 +547,23 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
248
547
|
if (!response.ok) {
|
|
249
548
|
const diagnostics = diagnosticMessage(payload);
|
|
250
549
|
const detail = diagnostics ?? (text || response.statusText);
|
|
251
|
-
|
|
550
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
551
|
+
throw new CliError(`Rig server request failed (${response.status}): ${detail}
|
|
552
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
252
553
|
}
|
|
253
554
|
return payload;
|
|
254
555
|
}
|
|
556
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
557
|
+
"created",
|
|
558
|
+
"preparing",
|
|
559
|
+
"running",
|
|
560
|
+
"validating",
|
|
561
|
+
"reviewing",
|
|
562
|
+
"stopped",
|
|
563
|
+
"failed",
|
|
564
|
+
"needs-attention",
|
|
565
|
+
"needs_attention"
|
|
566
|
+
]);
|
|
255
567
|
async function submitTaskRunViaServer(context, input) {
|
|
256
568
|
const isTaskRun = Boolean(input.taskId);
|
|
257
569
|
const endpoint = isTaskRun ? "/api/runs/task" : "/api/runs/adhoc";
|
|
@@ -275,18 +587,21 @@ async function submitTaskRunViaServer(context, input) {
|
|
|
275
587
|
})
|
|
276
588
|
});
|
|
277
589
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
278
|
-
throw new
|
|
590
|
+
throw new CliError("Rig server returned an invalid run submission payload.", 1, { hint: "Check `rig server status` and retry; `rig run list` shows whether the run was created anyway." });
|
|
279
591
|
}
|
|
280
592
|
const runId = payload.runId;
|
|
281
593
|
if (typeof runId !== "string" || runId.trim().length === 0) {
|
|
282
|
-
throw new
|
|
594
|
+
throw new CliError("Rig server returned no runId for the submitted run.", 1, { hint: "Check `rig run list` \u2014 the run may still have been created; otherwise retry the submission." });
|
|
283
595
|
}
|
|
284
596
|
return { runId };
|
|
285
597
|
}
|
|
286
598
|
|
|
287
599
|
// packages/cli/src/commands/server.ts
|
|
288
600
|
async function executeServer(context, args, options) {
|
|
289
|
-
const [command = "
|
|
601
|
+
const [command = "status", ...rest] = args;
|
|
602
|
+
if (["status", "list", "add", "use"].includes(command)) {
|
|
603
|
+
return executeConnectionCommand(context, [command, ...rest], { group: "server", interactiveUse: true });
|
|
604
|
+
}
|
|
290
605
|
switch (command) {
|
|
291
606
|
case "start": {
|
|
292
607
|
let pending = rest;
|
|
@@ -298,8 +613,9 @@ async function executeServer(context, args, options) {
|
|
|
298
613
|
pending = pollResult.rest;
|
|
299
614
|
const authTokenResult = takeOption(pending, "--auth-token");
|
|
300
615
|
pending = authTokenResult.rest;
|
|
301
|
-
requireNoExtraArgs(pending, "
|
|
302
|
-
const
|
|
616
|
+
requireNoExtraArgs(pending, "rig server start [--host <host>] [--port <n>] [--poll-ms <n>] [--auth-token <token>]");
|
|
617
|
+
const serverCommand = resolveRigServerCommand(context.projectRoot);
|
|
618
|
+
const commandParts = [serverCommand.command, ...serverCommand.commandArgs, "start"];
|
|
303
619
|
if (hostResult.value) {
|
|
304
620
|
commandParts.push("--host", hostResult.value);
|
|
305
621
|
}
|
|
@@ -316,13 +632,14 @@ async function executeServer(context, args, options) {
|
|
|
316
632
|
return { ok: true, group: "server", command };
|
|
317
633
|
}
|
|
318
634
|
case "events":
|
|
319
|
-
throw new
|
|
635
|
+
throw new CliError("server events is not supported by the CLI wrapper. Use `rig inspector stream` for bounded event streaming, or start the server with `rig server start`.", 2);
|
|
320
636
|
case "notify-test": {
|
|
321
637
|
let pending = rest;
|
|
322
638
|
const eventResult = takeOption(pending, "--event");
|
|
323
639
|
pending = eventResult.rest;
|
|
324
|
-
requireNoExtraArgs(pending, "
|
|
325
|
-
const
|
|
640
|
+
requireNoExtraArgs(pending, "rig server notify-test [--event <type>]");
|
|
641
|
+
const serverCommand = resolveRigServerCommand(context.projectRoot);
|
|
642
|
+
const commandParts = [serverCommand.command, ...serverCommand.commandArgs, "notify-test"];
|
|
326
643
|
if (eventResult.value) {
|
|
327
644
|
commandParts.push("--event", eventResult.value);
|
|
328
645
|
}
|
|
@@ -349,9 +666,9 @@ async function executeServer(context, args, options) {
|
|
|
349
666
|
pending = dirtyBaselineResult.rest;
|
|
350
667
|
const prResult = takeOption(pending, "--pr");
|
|
351
668
|
pending = prResult.rest;
|
|
352
|
-
requireNoExtraArgs(pending, "
|
|
669
|
+
requireNoExtraArgs(pending, "rig --run-id <run-id> server task-run [--task <id>] [--title <text>] [--runtime-adapter claude-code|codex|pi] [--model <model>] [--runtime-mode <mode>] [--interaction-mode <mode>] [--initial-prompt <text>] [--dirty-baseline head|dirty-snapshot] [--pr auto|ask|off]");
|
|
353
670
|
if (!taskResult.value && !initialPromptResult.value && !titleResult.value) {
|
|
354
|
-
throw new
|
|
671
|
+
throw new CliError("server task-run requires either --task <id> or --initial-prompt/--title for an ad hoc run.", 2);
|
|
355
672
|
}
|
|
356
673
|
const input = {
|
|
357
674
|
runId: context.runId,
|
|
@@ -378,7 +695,7 @@ async function executeServer(context, args, options) {
|
|
|
378
695
|
};
|
|
379
696
|
}
|
|
380
697
|
default:
|
|
381
|
-
throw new
|
|
698
|
+
throw new CliError(`Unknown server command: ${command}`, 1, { hint: "Run `rig server --help` \u2014 primary commands are status|list|add|use|start." });
|
|
382
699
|
}
|
|
383
700
|
}
|
|
384
701
|
export {
|