@h-rig/cli 0.0.6-alpha.5 → 0.0.6-alpha.50
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 +4072 -1204
- package/dist/src/commands/_cli-format.js +369 -0
- package/dist/src/commands/_connection-state.js +12 -6
- package/dist/src/commands/_doctor-checks.js +65 -34
- package/dist/src/commands/_help-catalog.js +445 -0
- package/dist/src/commands/_operator-surface.js +220 -0
- package/dist/src/commands/_operator-view.js +1109 -63
- package/dist/src/commands/_parsers.js +0 -2
- package/dist/src/commands/_pi-frontend.js +1066 -0
- package/dist/src/commands/_pi-install.js +4 -3
- package/dist/src/commands/_pi-remote-session.js +757 -0
- package/dist/src/commands/_pi-worker-bridge-extension.js +820 -0
- package/dist/src/commands/_policy.js +0 -2
- package/dist/src/commands/_preflight.js +84 -116
- package/dist/src/commands/_run-driver-helpers.js +2 -2
- package/dist/src/commands/_server-client.js +211 -48
- package/dist/src/commands/_snapshot-upload.js +60 -30
- package/dist/src/commands/_spinner.js +63 -0
- package/dist/src/commands/_task-picker.js +44 -16
- package/dist/src/commands/agent.js +8 -9
- package/dist/src/commands/browser.js +4 -6
- package/dist/src/commands/connect.js +134 -26
- package/dist/src/commands/dist.js +4 -6
- package/dist/src/commands/doctor.js +65 -34
- package/dist/src/commands/github.js +62 -32
- package/dist/src/commands/inbox.js +396 -31
- package/dist/src/commands/init.js +342 -88
- package/dist/src/commands/inspect.js +282 -23
- package/dist/src/commands/inspector.js +2 -4
- package/dist/src/commands/pi.js +168 -0
- package/dist/src/commands/plugin.js +81 -22
- package/dist/src/commands/profile-and-review.js +8 -10
- package/dist/src/commands/queue.js +2 -3
- package/dist/src/commands/remote.js +18 -20
- package/dist/src/commands/repo-git-harness.js +6 -8
- package/dist/src/commands/run.js +1407 -130
- package/dist/src/commands/server.js +266 -40
- package/dist/src/commands/setup.js +69 -44
- package/dist/src/commands/task-report-bug.js +5 -7
- package/dist/src/commands/task-run-driver.js +662 -70
- package/dist/src/commands/task.js +1846 -259
- package/dist/src/commands/test.js +3 -5
- package/dist/src/commands/workspace.js +4 -6
- package/dist/src/commands.js +4054 -1180
- package/dist/src/index.js +4065 -1200
- package/dist/src/launcher.js +5 -3
- package/dist/src/report-bug.js +3 -3
- package/dist/src/runner.js +5 -19
- package/package.json +7 -4
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
4
4
|
import { CliError } 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
|
import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
|
|
10
8
|
function takeOption(args, option) {
|
|
@@ -34,6 +32,9 @@ Usage: ${usage}`);
|
|
|
34
32
|
}
|
|
35
33
|
}
|
|
36
34
|
|
|
35
|
+
// packages/cli/src/commands/server.ts
|
|
36
|
+
import { resolveRigServerCommand } from "@rig/runtime/local-server";
|
|
37
|
+
|
|
37
38
|
// packages/cli/src/commands/_authority-runs.ts
|
|
38
39
|
import {
|
|
39
40
|
readAuthorityRun,
|
|
@@ -60,11 +61,8 @@ function normalizeRuntimeAdapter(value) {
|
|
|
60
61
|
return "claude-code";
|
|
61
62
|
}
|
|
62
63
|
|
|
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";
|
|
64
|
+
// packages/cli/src/commands/connect.ts
|
|
65
|
+
import { cancel, isCancel, select } from "@clack/prompts";
|
|
68
66
|
|
|
69
67
|
// packages/cli/src/commands/_connection-state.ts
|
|
70
68
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -91,6 +89,11 @@ function readJsonFile(path) {
|
|
|
91
89
|
throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
92
90
|
}
|
|
93
91
|
}
|
|
92
|
+
function writeJsonFile2(path, value) {
|
|
93
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
94
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
95
|
+
`, "utf8");
|
|
96
|
+
}
|
|
94
97
|
function normalizeConnection(value) {
|
|
95
98
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
96
99
|
return null;
|
|
@@ -120,6 +123,18 @@ function readGlobalConnections(options = {}) {
|
|
|
120
123
|
}
|
|
121
124
|
return { connections };
|
|
122
125
|
}
|
|
126
|
+
function writeGlobalConnections(state, options = {}) {
|
|
127
|
+
writeJsonFile2(resolveGlobalConnectionsPath(options.env ?? process.env), state);
|
|
128
|
+
}
|
|
129
|
+
function upsertGlobalConnection(alias, connection, options = {}) {
|
|
130
|
+
const cleanAlias = alias.trim();
|
|
131
|
+
if (!cleanAlias)
|
|
132
|
+
throw new CliError2("Connection alias is required.", 1);
|
|
133
|
+
const state = readGlobalConnections(options);
|
|
134
|
+
state.connections[cleanAlias] = connection;
|
|
135
|
+
writeGlobalConnections(state, options);
|
|
136
|
+
return state;
|
|
137
|
+
}
|
|
123
138
|
function readRepoConnection(projectRoot) {
|
|
124
139
|
const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
|
|
125
140
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
@@ -131,25 +146,213 @@ function readRepoConnection(projectRoot) {
|
|
|
131
146
|
return {
|
|
132
147
|
selected,
|
|
133
148
|
project: typeof record.project === "string" ? record.project : undefined,
|
|
134
|
-
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
|
|
149
|
+
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
150
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
135
151
|
};
|
|
136
152
|
}
|
|
153
|
+
function writeRepoConnection(projectRoot, state) {
|
|
154
|
+
writeJsonFile2(resolveRepoConnectionPath(projectRoot), state);
|
|
155
|
+
}
|
|
137
156
|
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
138
157
|
const repo = readRepoConnection(projectRoot);
|
|
139
158
|
if (!repo)
|
|
140
159
|
return null;
|
|
141
160
|
if (repo.selected === "local")
|
|
142
|
-
return { alias: "local", connection: { kind: "local", mode: "auto" } };
|
|
161
|
+
return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
|
|
143
162
|
const global = readGlobalConnections(options);
|
|
144
163
|
const connection = global.connections[repo.selected];
|
|
145
164
|
if (!connection) {
|
|
146
|
-
throw new CliError2(`Selected Rig
|
|
165
|
+
throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
166
|
+
}
|
|
167
|
+
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
168
|
+
}
|
|
169
|
+
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
170
|
+
const repo = readRepoConnection(projectRoot);
|
|
171
|
+
if (!repo)
|
|
172
|
+
return;
|
|
173
|
+
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// packages/cli/src/commands/_cli-format.ts
|
|
177
|
+
import { log, note } from "@clack/prompts";
|
|
178
|
+
import pc from "picocolors";
|
|
179
|
+
function truncate(value, width) {
|
|
180
|
+
if (value.length <= width)
|
|
181
|
+
return value;
|
|
182
|
+
if (width <= 1)
|
|
183
|
+
return "\u2026";
|
|
184
|
+
return `${value.slice(0, width - 1)}\u2026`;
|
|
185
|
+
}
|
|
186
|
+
function pad(value, width) {
|
|
187
|
+
return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
|
|
188
|
+
}
|
|
189
|
+
function statusColor(status) {
|
|
190
|
+
const normalized = status.toLowerCase();
|
|
191
|
+
if (["completed", "merged", "closed", "done", "accepted", "pass", "selected", "approved"].includes(normalized))
|
|
192
|
+
return pc.green;
|
|
193
|
+
if (["failed", "needs_attention", "needs-attention", "blocked", "error", "rejected"].includes(normalized))
|
|
194
|
+
return pc.red;
|
|
195
|
+
if (["running", "reviewing", "validating", "in_progress", "in-progress", "remote"].includes(normalized))
|
|
196
|
+
return pc.cyan;
|
|
197
|
+
if (["ready", "open", "queued", "created", "preparing", "local", "pending"].includes(normalized))
|
|
198
|
+
return pc.yellow;
|
|
199
|
+
return pc.dim;
|
|
200
|
+
}
|
|
201
|
+
function formatStatusPill(status) {
|
|
202
|
+
const label = status || "unknown";
|
|
203
|
+
return statusColor(label)(`\u25CF ${label}`);
|
|
204
|
+
}
|
|
205
|
+
function formatSection(title, subtitle) {
|
|
206
|
+
return `${pc.bold(pc.cyan("\u25C6"))} ${pc.bold(title)}${subtitle ? pc.dim(` \u2014 ${subtitle}`) : ""}`;
|
|
207
|
+
}
|
|
208
|
+
function formatSuccessCard(title, rows = []) {
|
|
209
|
+
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}`);
|
|
210
|
+
return [formatSection(title), ...body].join(`
|
|
211
|
+
`);
|
|
212
|
+
}
|
|
213
|
+
function formatNextSteps(steps) {
|
|
214
|
+
if (steps.length === 0)
|
|
215
|
+
return [];
|
|
216
|
+
return [pc.bold("Next"), ...steps.map((step) => `${pc.dim("\u203A")} ${step}`)];
|
|
217
|
+
}
|
|
218
|
+
function formatConnectionList(connections) {
|
|
219
|
+
const rows = [["local", { kind: "local", mode: "auto" }], ...Object.entries(connections)];
|
|
220
|
+
const aliasWidth = Math.min(24, Math.max(5, ...rows.map(([alias]) => alias.length)));
|
|
221
|
+
const lines = rows.map(([alias, connection]) => [
|
|
222
|
+
pc.bold(pad(truncate(alias, aliasWidth), aliasWidth)),
|
|
223
|
+
formatStatusPill(connection.kind),
|
|
224
|
+
connection.kind === "remote" ? connection.baseUrl ?? "" : connection.mode ?? "local"
|
|
225
|
+
].join(" "));
|
|
226
|
+
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(`
|
|
227
|
+
`);
|
|
228
|
+
}
|
|
229
|
+
function formatConnectionStatus(selected, connections) {
|
|
230
|
+
const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
|
|
231
|
+
const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
|
|
232
|
+
return [
|
|
233
|
+
formatSection("Rig server", "selected for this repo"),
|
|
234
|
+
`${pc.dim("\u2502")} ${pc.dim("selected ")} ${pc.bold(selected)}`,
|
|
235
|
+
`${pc.dim("\u2502")} ${pc.dim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
|
|
236
|
+
`${pc.dim("\u2502")} ${pc.dim("target ")} ${target ?? "not configured"}`,
|
|
237
|
+
"",
|
|
238
|
+
...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
|
|
239
|
+
].join(`
|
|
240
|
+
`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// packages/cli/src/commands/connect.ts
|
|
244
|
+
function usageName(options) {
|
|
245
|
+
return `rig ${options.group}`;
|
|
246
|
+
}
|
|
247
|
+
function parseConnection(alias, value, options) {
|
|
248
|
+
if (alias === "local" && !value)
|
|
249
|
+
return { kind: "local", mode: "auto" };
|
|
250
|
+
if (!value)
|
|
251
|
+
throw new CliError2(`Missing remote server URL. Usage: ${usageName(options)} add <alias> <url>`, 1);
|
|
252
|
+
let parsed;
|
|
253
|
+
try {
|
|
254
|
+
parsed = new URL(value);
|
|
255
|
+
} catch {
|
|
256
|
+
throw new CliError2(`Invalid Rig server URL: ${value}`, 1);
|
|
257
|
+
}
|
|
258
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
259
|
+
throw new CliError2("Rig remote server URL must be http(s).", 1);
|
|
260
|
+
}
|
|
261
|
+
return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
|
|
262
|
+
}
|
|
263
|
+
function printJsonOrText(context, payload, text) {
|
|
264
|
+
if (context.outputMode === "json") {
|
|
265
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
266
|
+
} else {
|
|
267
|
+
console.log(text);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
async function promptForConnectionAlias(context) {
|
|
271
|
+
const state = readGlobalConnections();
|
|
272
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
273
|
+
const options = [
|
|
274
|
+
{ value: "local", label: "local", hint: "Use/start a local Rig server" },
|
|
275
|
+
...Object.entries(state.connections).map(([alias, connection]) => ({
|
|
276
|
+
value: alias,
|
|
277
|
+
label: alias,
|
|
278
|
+
hint: connection.kind === "remote" ? connection.baseUrl : "local"
|
|
279
|
+
}))
|
|
280
|
+
].filter((option, index, all) => all.findIndex((candidate) => candidate.value === option.value) === index);
|
|
281
|
+
const answer = await select({
|
|
282
|
+
message: "Select Rig server for this repo",
|
|
283
|
+
initialValue: repo?.selected ?? "local",
|
|
284
|
+
options
|
|
285
|
+
});
|
|
286
|
+
if (isCancel(answer)) {
|
|
287
|
+
cancel("No server selected.");
|
|
288
|
+
throw new CliError2("No server selected.", 3);
|
|
289
|
+
}
|
|
290
|
+
return String(answer);
|
|
291
|
+
}
|
|
292
|
+
async function executeConnectionCommand(context, args, options) {
|
|
293
|
+
const [command, ...rest] = args;
|
|
294
|
+
switch (command ?? "status") {
|
|
295
|
+
case "list": {
|
|
296
|
+
requireNoExtraArgs(rest, `${usageName(options)} list`);
|
|
297
|
+
const state = readGlobalConnections();
|
|
298
|
+
printJsonOrText(context, state, formatConnectionList(state.connections));
|
|
299
|
+
return { ok: true, group: options.group, command: "list", details: state };
|
|
300
|
+
}
|
|
301
|
+
case "add": {
|
|
302
|
+
const [alias, url, ...extra] = rest;
|
|
303
|
+
if (!alias)
|
|
304
|
+
throw new CliError2(`Missing alias. Usage: ${usageName(options)} add <alias> <url>`, 1);
|
|
305
|
+
requireNoExtraArgs(extra, `${usageName(options)} add <alias> <url>`);
|
|
306
|
+
const connection = parseConnection(alias, url, options);
|
|
307
|
+
const state = upsertGlobalConnection(alias, connection);
|
|
308
|
+
printJsonOrText(context, { alias, connection }, formatSuccessCard("Rig server saved", [
|
|
309
|
+
["alias", alias],
|
|
310
|
+
["target", connection.kind === "remote" ? connection.baseUrl : "local"],
|
|
311
|
+
["next", `${usageName(options)} use ${alias}`]
|
|
312
|
+
]));
|
|
313
|
+
return { ok: true, group: options.group, command: "add", details: { alias, connection, count: Object.keys(state.connections).length } };
|
|
314
|
+
}
|
|
315
|
+
case "use": {
|
|
316
|
+
let [alias, ...extra] = rest;
|
|
317
|
+
requireNoExtraArgs(extra, `${usageName(options)} use <alias|local>`);
|
|
318
|
+
if (!alias && options.interactiveUse && context.outputMode === "text" && process.stdin.isTTY) {
|
|
319
|
+
alias = await promptForConnectionAlias(context);
|
|
320
|
+
}
|
|
321
|
+
if (!alias)
|
|
322
|
+
throw new CliError2(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
|
|
323
|
+
if (alias !== "local") {
|
|
324
|
+
const state = readGlobalConnections();
|
|
325
|
+
if (!state.connections[alias])
|
|
326
|
+
throw new CliError2(`Unknown Rig server: ${alias}`, 1);
|
|
327
|
+
}
|
|
328
|
+
const repoState = { selected: alias, linkedAt: new Date().toISOString() };
|
|
329
|
+
writeRepoConnection(context.projectRoot, repoState);
|
|
330
|
+
printJsonOrText(context, repoState, formatSuccessCard("Rig server selected", [
|
|
331
|
+
["selected", alias],
|
|
332
|
+
["scope", "this repo"],
|
|
333
|
+
["next", "rig task list"]
|
|
334
|
+
]));
|
|
335
|
+
return { ok: true, group: options.group, command: "use", details: repoState };
|
|
336
|
+
}
|
|
337
|
+
case "status": {
|
|
338
|
+
requireNoExtraArgs(rest, `${usageName(options)} status`);
|
|
339
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
340
|
+
const global = readGlobalConnections();
|
|
341
|
+
const details = { selected: repo?.selected ?? "local", repo, connections: global.connections };
|
|
342
|
+
printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections));
|
|
343
|
+
return { ok: true, group: options.group, command: "status", details };
|
|
344
|
+
}
|
|
345
|
+
default:
|
|
346
|
+
throw new CliError2(`Unknown ${options.group} command: ${String(command)}
|
|
347
|
+
Usage: ${usageName(options)} <list|add|use|status>`, 1);
|
|
147
348
|
}
|
|
148
|
-
return { alias: repo.selected, connection };
|
|
149
349
|
}
|
|
150
350
|
|
|
151
351
|
// packages/cli/src/commands/_server-client.ts
|
|
152
|
-
|
|
352
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
353
|
+
import { resolve as resolve2 } from "path";
|
|
354
|
+
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
355
|
+
var scopedGitHubBearerTokens = new Map;
|
|
153
356
|
function cleanToken(value) {
|
|
154
357
|
const trimmed = value?.trim();
|
|
155
358
|
return trimmed ? trimmed : null;
|
|
@@ -166,41 +369,33 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
166
369
|
}
|
|
167
370
|
}
|
|
168
371
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
169
|
-
|
|
170
|
-
|
|
372
|
+
const scopedKey = resolve2(projectRoot);
|
|
373
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
374
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
171
375
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
172
|
-
if (privateSession)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
|
|
177
|
-
if (envToken) {
|
|
178
|
-
cachedGitHubBearerToken = envToken;
|
|
179
|
-
return cachedGitHubBearerToken;
|
|
180
|
-
}
|
|
181
|
-
const result = spawnSync("gh", ["auth", "token"], {
|
|
182
|
-
encoding: "utf8",
|
|
183
|
-
timeout: 5000,
|
|
184
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
185
|
-
});
|
|
186
|
-
cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
|
|
187
|
-
return cachedGitHubBearerToken;
|
|
376
|
+
if (privateSession)
|
|
377
|
+
return privateSession;
|
|
378
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
188
379
|
}
|
|
189
380
|
async function ensureServerForCli(projectRoot) {
|
|
190
381
|
try {
|
|
191
382
|
const selected = resolveSelectedConnection(projectRoot);
|
|
192
383
|
if (selected?.connection.kind === "remote") {
|
|
384
|
+
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
385
|
+
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
193
386
|
return {
|
|
194
387
|
baseUrl: selected.connection.baseUrl,
|
|
195
|
-
authToken
|
|
196
|
-
connectionKind: "remote"
|
|
388
|
+
authToken,
|
|
389
|
+
connectionKind: "remote",
|
|
390
|
+
serverProjectRoot
|
|
197
391
|
};
|
|
198
392
|
}
|
|
199
393
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
200
394
|
return {
|
|
201
395
|
baseUrl: connection.baseUrl,
|
|
202
396
|
authToken: connection.authToken,
|
|
203
|
-
connectionKind: "local"
|
|
397
|
+
connectionKind: "local",
|
|
398
|
+
serverProjectRoot: resolve2(projectRoot)
|
|
204
399
|
};
|
|
205
400
|
} catch (error) {
|
|
206
401
|
if (error instanceof Error) {
|
|
@@ -209,6 +404,29 @@ async function ensureServerForCli(projectRoot) {
|
|
|
209
404
|
throw error;
|
|
210
405
|
}
|
|
211
406
|
}
|
|
407
|
+
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
408
|
+
const repo = readRepoConnection(projectRoot);
|
|
409
|
+
const slug = repo?.project?.trim();
|
|
410
|
+
if (!slug)
|
|
411
|
+
return null;
|
|
412
|
+
try {
|
|
413
|
+
const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
|
|
414
|
+
headers: mergeHeaders(undefined, authToken)
|
|
415
|
+
});
|
|
416
|
+
if (!response.ok)
|
|
417
|
+
return null;
|
|
418
|
+
const payload = await response.json();
|
|
419
|
+
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
420
|
+
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
421
|
+
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
422
|
+
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
423
|
+
if (path)
|
|
424
|
+
writeRepoServerProjectRoot(projectRoot, path);
|
|
425
|
+
return path;
|
|
426
|
+
} catch {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
212
430
|
function mergeHeaders(headers, authToken) {
|
|
213
431
|
const merged = new Headers(headers);
|
|
214
432
|
if (authToken) {
|
|
@@ -233,9 +451,12 @@ function diagnosticMessage(payload) {
|
|
|
233
451
|
}
|
|
234
452
|
async function requestServerJson(context, pathname, init = {}) {
|
|
235
453
|
const server = await ensureServerForCli(context.projectRoot);
|
|
454
|
+
const headers = mergeHeaders(init.headers, server.authToken);
|
|
455
|
+
if (server.serverProjectRoot)
|
|
456
|
+
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
236
457
|
const response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
237
458
|
...init,
|
|
238
|
-
headers
|
|
459
|
+
headers
|
|
239
460
|
});
|
|
240
461
|
const text = await response.text();
|
|
241
462
|
const payload = text.trim().length > 0 ? (() => {
|
|
@@ -286,7 +507,10 @@ async function submitTaskRunViaServer(context, input) {
|
|
|
286
507
|
|
|
287
508
|
// packages/cli/src/commands/server.ts
|
|
288
509
|
async function executeServer(context, args, options) {
|
|
289
|
-
const [command = "
|
|
510
|
+
const [command = "status", ...rest] = args;
|
|
511
|
+
if (["status", "list", "add", "use"].includes(command)) {
|
|
512
|
+
return executeConnectionCommand(context, [command, ...rest], { group: "server", interactiveUse: true });
|
|
513
|
+
}
|
|
290
514
|
switch (command) {
|
|
291
515
|
case "start": {
|
|
292
516
|
let pending = rest;
|
|
@@ -298,8 +522,9 @@ async function executeServer(context, args, options) {
|
|
|
298
522
|
pending = pollResult.rest;
|
|
299
523
|
const authTokenResult = takeOption(pending, "--auth-token");
|
|
300
524
|
pending = authTokenResult.rest;
|
|
301
|
-
requireNoExtraArgs(pending, "
|
|
302
|
-
const
|
|
525
|
+
requireNoExtraArgs(pending, "rig server start [--host <host>] [--port <n>] [--poll-ms <n>] [--auth-token <token>]");
|
|
526
|
+
const serverCommand = resolveRigServerCommand(context.projectRoot);
|
|
527
|
+
const commandParts = [serverCommand.command, ...serverCommand.commandArgs, "start"];
|
|
303
528
|
if (hostResult.value) {
|
|
304
529
|
commandParts.push("--host", hostResult.value);
|
|
305
530
|
}
|
|
@@ -321,8 +546,9 @@ async function executeServer(context, args, options) {
|
|
|
321
546
|
let pending = rest;
|
|
322
547
|
const eventResult = takeOption(pending, "--event");
|
|
323
548
|
pending = eventResult.rest;
|
|
324
|
-
requireNoExtraArgs(pending, "
|
|
325
|
-
const
|
|
549
|
+
requireNoExtraArgs(pending, "rig server notify-test [--event <type>]");
|
|
550
|
+
const serverCommand = resolveRigServerCommand(context.projectRoot);
|
|
551
|
+
const commandParts = [serverCommand.command, ...serverCommand.commandArgs, "notify-test"];
|
|
326
552
|
if (eventResult.value) {
|
|
327
553
|
commandParts.push("--event", eventResult.value);
|
|
328
554
|
}
|
|
@@ -349,7 +575,7 @@ async function executeServer(context, args, options) {
|
|
|
349
575
|
pending = dirtyBaselineResult.rest;
|
|
350
576
|
const prResult = takeOption(pending, "--pr");
|
|
351
577
|
pending = prResult.rest;
|
|
352
|
-
requireNoExtraArgs(pending, "
|
|
578
|
+
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
579
|
if (!taskResult.value && !initialPromptResult.value && !titleResult.value) {
|
|
354
580
|
throw new CliError2("server task-run requires either --task <id> or --initial-prompt/--title for an ad hoc run.", 2);
|
|
355
581
|
}
|
|
@@ -9,8 +9,6 @@ import { resolve as resolve6 } from "path";
|
|
|
9
9
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
10
10
|
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
11
11
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
12
|
-
import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
13
|
-
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
14
12
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
15
13
|
import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
|
|
16
14
|
function requireNoExtraArgs(args, usage) {
|
|
@@ -102,7 +100,8 @@ function resolveControlPlaneDefinitionRoot(projectRoot) {
|
|
|
102
100
|
import { existsSync, readFileSync, rmSync } from "fs";
|
|
103
101
|
import { homedir } from "os";
|
|
104
102
|
import { resolve as resolve2 } from "path";
|
|
105
|
-
var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
103
|
+
var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
|
|
104
|
+
var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
106
105
|
async function defaultCommandRunner(command, options = {}) {
|
|
107
106
|
const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
|
|
108
107
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
@@ -121,7 +120,7 @@ function resolvePiHomeDir(inputHomeDir) {
|
|
|
121
120
|
function piListContainsPiRig(output) {
|
|
122
121
|
return output.split(/\r?\n/).some((line) => {
|
|
123
122
|
const normalized = line.trim();
|
|
124
|
-
return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
123
|
+
return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
125
124
|
});
|
|
126
125
|
}
|
|
127
126
|
async function safeRun(runner, command, options) {
|
|
@@ -200,6 +199,11 @@ function readJsonFile(path) {
|
|
|
200
199
|
throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
201
200
|
}
|
|
202
201
|
}
|
|
202
|
+
function writeJsonFile(path, value) {
|
|
203
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
204
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
205
|
+
`, "utf8");
|
|
206
|
+
}
|
|
203
207
|
function normalizeConnection(value) {
|
|
204
208
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
205
209
|
return null;
|
|
@@ -240,29 +244,38 @@ function readRepoConnection(projectRoot) {
|
|
|
240
244
|
return {
|
|
241
245
|
selected,
|
|
242
246
|
project: typeof record.project === "string" ? record.project : undefined,
|
|
243
|
-
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
|
|
247
|
+
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
248
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
244
249
|
};
|
|
245
250
|
}
|
|
251
|
+
function writeRepoConnection(projectRoot, state) {
|
|
252
|
+
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
253
|
+
}
|
|
246
254
|
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
247
255
|
const repo = readRepoConnection(projectRoot);
|
|
248
256
|
if (!repo)
|
|
249
257
|
return null;
|
|
250
258
|
if (repo.selected === "local")
|
|
251
|
-
return { alias: "local", connection: { kind: "local", mode: "auto" } };
|
|
259
|
+
return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
|
|
252
260
|
const global = readGlobalConnections(options);
|
|
253
261
|
const connection = global.connections[repo.selected];
|
|
254
262
|
if (!connection) {
|
|
255
|
-
throw new CliError2(`Selected Rig
|
|
263
|
+
throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
256
264
|
}
|
|
257
|
-
return { alias: repo.selected, connection };
|
|
265
|
+
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
266
|
+
}
|
|
267
|
+
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
268
|
+
const repo = readRepoConnection(projectRoot);
|
|
269
|
+
if (!repo)
|
|
270
|
+
return;
|
|
271
|
+
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
258
272
|
}
|
|
259
273
|
|
|
260
274
|
// packages/cli/src/commands/_server-client.ts
|
|
261
|
-
import { spawnSync } from "child_process";
|
|
262
275
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
263
276
|
import { resolve as resolve4 } from "path";
|
|
264
277
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
265
|
-
var
|
|
278
|
+
var scopedGitHubBearerTokens = new Map;
|
|
266
279
|
function cleanToken(value) {
|
|
267
280
|
const trimmed = value?.trim();
|
|
268
281
|
return trimmed ? trimmed : null;
|
|
@@ -279,41 +292,33 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
279
292
|
}
|
|
280
293
|
}
|
|
281
294
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
282
|
-
|
|
283
|
-
|
|
295
|
+
const scopedKey = resolve4(projectRoot);
|
|
296
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
297
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
284
298
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
285
|
-
if (privateSession)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
|
|
290
|
-
if (envToken) {
|
|
291
|
-
cachedGitHubBearerToken = envToken;
|
|
292
|
-
return cachedGitHubBearerToken;
|
|
293
|
-
}
|
|
294
|
-
const result = spawnSync("gh", ["auth", "token"], {
|
|
295
|
-
encoding: "utf8",
|
|
296
|
-
timeout: 5000,
|
|
297
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
298
|
-
});
|
|
299
|
-
cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
|
|
300
|
-
return cachedGitHubBearerToken;
|
|
299
|
+
if (privateSession)
|
|
300
|
+
return privateSession;
|
|
301
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
301
302
|
}
|
|
302
303
|
async function ensureServerForCli(projectRoot) {
|
|
303
304
|
try {
|
|
304
305
|
const selected = resolveSelectedConnection(projectRoot);
|
|
305
306
|
if (selected?.connection.kind === "remote") {
|
|
307
|
+
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
308
|
+
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
306
309
|
return {
|
|
307
310
|
baseUrl: selected.connection.baseUrl,
|
|
308
|
-
authToken
|
|
309
|
-
connectionKind: "remote"
|
|
311
|
+
authToken,
|
|
312
|
+
connectionKind: "remote",
|
|
313
|
+
serverProjectRoot
|
|
310
314
|
};
|
|
311
315
|
}
|
|
312
316
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
313
317
|
return {
|
|
314
318
|
baseUrl: connection.baseUrl,
|
|
315
319
|
authToken: connection.authToken,
|
|
316
|
-
connectionKind: "local"
|
|
320
|
+
connectionKind: "local",
|
|
321
|
+
serverProjectRoot: resolve4(projectRoot)
|
|
317
322
|
};
|
|
318
323
|
} catch (error) {
|
|
319
324
|
if (error instanceof Error) {
|
|
@@ -322,6 +327,29 @@ async function ensureServerForCli(projectRoot) {
|
|
|
322
327
|
throw error;
|
|
323
328
|
}
|
|
324
329
|
}
|
|
330
|
+
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
331
|
+
const repo = readRepoConnection(projectRoot);
|
|
332
|
+
const slug = repo?.project?.trim();
|
|
333
|
+
if (!slug)
|
|
334
|
+
return null;
|
|
335
|
+
try {
|
|
336
|
+
const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
|
|
337
|
+
headers: mergeHeaders(undefined, authToken)
|
|
338
|
+
});
|
|
339
|
+
if (!response.ok)
|
|
340
|
+
return null;
|
|
341
|
+
const payload = await response.json();
|
|
342
|
+
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
343
|
+
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
344
|
+
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
345
|
+
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
346
|
+
if (path)
|
|
347
|
+
writeRepoServerProjectRoot(projectRoot, path);
|
|
348
|
+
return path;
|
|
349
|
+
} catch {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
325
353
|
function mergeHeaders(headers, authToken) {
|
|
326
354
|
const merged = new Headers(headers);
|
|
327
355
|
if (authToken) {
|
|
@@ -346,9 +374,12 @@ function diagnosticMessage(payload) {
|
|
|
346
374
|
}
|
|
347
375
|
async function requestServerJson(context, pathname, init = {}) {
|
|
348
376
|
const server = await ensureServerForCli(context.projectRoot);
|
|
377
|
+
const headers = mergeHeaders(init.headers, server.authToken);
|
|
378
|
+
if (server.serverProjectRoot)
|
|
379
|
+
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
349
380
|
const response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
350
381
|
...init,
|
|
351
|
-
headers
|
|
382
|
+
headers
|
|
352
383
|
});
|
|
353
384
|
const text = await response.text();
|
|
354
385
|
const payload = text.trim().length > 0 ? (() => {
|
|
@@ -493,7 +524,7 @@ async function runRigDoctorChecks(options) {
|
|
|
493
524
|
const taskSourceKind = config?.taskSource?.kind;
|
|
494
525
|
checks.push(taskSourceKind ? check("task-source", "task source configured", "pass", taskSourceKind) : check("task-source", "task source configured", "fail", "missing taskSource", "Configure taskSource in rig.config.ts."));
|
|
495
526
|
const repo = readRepoConnection(projectRoot);
|
|
496
|
-
checks.push(repo ? check("project-link", "repo selected Rig
|
|
527
|
+
checks.push(repo ? check("project-link", "repo selected Rig server", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to link this checkout to a GitHub repo slug.") : check("project-link", "repo selected Rig server", "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
|
|
497
528
|
const selected = (() => {
|
|
498
529
|
try {
|
|
499
530
|
return resolveSelectedConnection(projectRoot);
|
|
@@ -501,7 +532,7 @@ async function runRigDoctorChecks(options) {
|
|
|
501
532
|
return null;
|
|
502
533
|
}
|
|
503
534
|
})();
|
|
504
|
-
checks.push(selected ? check("connection", "selected server connection", "pass", selected.connection.kind === "remote" ? selected.connection.baseUrl : "local auto") : check("connection", "selected server
|
|
535
|
+
checks.push(selected ? check("connection", "selected server connection", "pass", selected.connection.kind === "remote" ? selected.connection.baseUrl : "local auto") : check("connection", "selected server", repo ? "fail" : "warn", repo ? "selected alias is missing" : "will auto-start local server", repo ? "Run `rig server list` and `rig server use <alias|local>`." : undefined));
|
|
505
536
|
let server = null;
|
|
506
537
|
try {
|
|
507
538
|
server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
|
|
@@ -587,7 +618,7 @@ async function executeSetup(context, args) {
|
|
|
587
618
|
const [command = "check", ...rest] = args;
|
|
588
619
|
switch (command) {
|
|
589
620
|
case "bootstrap":
|
|
590
|
-
requireNoExtraArgs(rest, "
|
|
621
|
+
requireNoExtraArgs(rest, "rig setup bootstrap");
|
|
591
622
|
{
|
|
592
623
|
const hostBash = Bun.which("bash") || "/bin/bash";
|
|
593
624
|
const env = { ...process.env };
|
|
@@ -610,25 +641,19 @@ async function executeSetup(context, args) {
|
|
|
610
641
|
}
|
|
611
642
|
return { ok: true, group: "setup", command };
|
|
612
643
|
case "check":
|
|
613
|
-
requireNoExtraArgs(rest, `
|
|
644
|
+
requireNoExtraArgs(rest, `rig setup ${command}`);
|
|
614
645
|
{
|
|
615
646
|
const checks = await withMutedConsole(context.outputMode === "json", () => runSetupCheck(context.projectRoot));
|
|
616
647
|
return { ok: true, group: "setup", command, details: { checks, failures: countDoctorFailures(checks) } };
|
|
617
648
|
}
|
|
618
649
|
case "setup":
|
|
619
|
-
requireNoExtraArgs(rest, "
|
|
650
|
+
requireNoExtraArgs(rest, "rig setup setup");
|
|
620
651
|
withMutedConsole(context.outputMode === "json", () => runSetupInit(context.projectRoot));
|
|
621
652
|
return { ok: true, group: "setup", command };
|
|
622
653
|
case "preflight":
|
|
623
|
-
requireNoExtraArgs(rest, "
|
|
654
|
+
requireNoExtraArgs(rest, "rig setup preflight");
|
|
624
655
|
await withMutedConsole(context.outputMode === "json", () => runSetupPreflight(context.projectRoot));
|
|
625
656
|
return { ok: true, group: "setup", command };
|
|
626
|
-
case "install-agent-shell":
|
|
627
|
-
requireNoExtraArgs(rest, "bun run rig setup install-agent-shell");
|
|
628
|
-
if (context.outputMode === "text") {
|
|
629
|
-
console.log("install-agent-shell is deprecated. Runtime shells now use compiled rig-agent directly.");
|
|
630
|
-
}
|
|
631
|
-
return { ok: true, group: "setup", command };
|
|
632
657
|
default:
|
|
633
658
|
throw new CliError2(`Unknown setup command: ${command}`);
|
|
634
659
|
}
|