@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
|
@@ -4,18 +4,24 @@ import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
|
4
4
|
import { dirname as dirname2, resolve as resolve3, relative, sep } from "path";
|
|
5
5
|
|
|
6
6
|
// packages/cli/src/commands/_server-client.ts
|
|
7
|
-
import { spawnSync } from "child_process";
|
|
8
7
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
9
8
|
import { resolve as resolve2 } from "path";
|
|
10
9
|
|
|
11
10
|
// packages/cli/src/runner.ts
|
|
12
11
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
13
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
12
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
14
13
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
15
|
-
import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
16
|
-
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
17
14
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
18
|
-
|
|
15
|
+
|
|
16
|
+
class CliError extends RuntimeCliError {
|
|
17
|
+
hint;
|
|
18
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
19
|
+
super(message, exitCode);
|
|
20
|
+
if (options.hint?.trim()) {
|
|
21
|
+
this.hint = options.hint.trim();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
19
25
|
|
|
20
26
|
// packages/cli/src/commands/_server-client.ts
|
|
21
27
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
@@ -42,9 +48,14 @@ function readJsonFile(path) {
|
|
|
42
48
|
try {
|
|
43
49
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
44
50
|
} catch (error) {
|
|
45
|
-
throw new
|
|
51
|
+
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>`." });
|
|
46
52
|
}
|
|
47
53
|
}
|
|
54
|
+
function writeJsonFile(path, value) {
|
|
55
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
56
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
57
|
+
`, "utf8");
|
|
58
|
+
}
|
|
48
59
|
function normalizeConnection(value) {
|
|
49
60
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
50
61
|
return null;
|
|
@@ -85,25 +96,39 @@ function readRepoConnection(projectRoot) {
|
|
|
85
96
|
return {
|
|
86
97
|
selected,
|
|
87
98
|
project: typeof record.project === "string" ? record.project : undefined,
|
|
88
|
-
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
|
|
99
|
+
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
100
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
89
101
|
};
|
|
90
102
|
}
|
|
103
|
+
function writeRepoConnection(projectRoot, state) {
|
|
104
|
+
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
105
|
+
}
|
|
91
106
|
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
92
107
|
const repo = readRepoConnection(projectRoot);
|
|
93
108
|
if (!repo)
|
|
94
109
|
return null;
|
|
95
110
|
if (repo.selected === "local")
|
|
96
|
-
return { alias: "local", connection: { kind: "local", mode: "auto" } };
|
|
111
|
+
return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
|
|
97
112
|
const global = readGlobalConnections(options);
|
|
98
113
|
const connection = global.connections[repo.selected];
|
|
99
114
|
if (!connection) {
|
|
100
|
-
throw new
|
|
115
|
+
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
101
116
|
}
|
|
102
|
-
return { alias: repo.selected, connection };
|
|
117
|
+
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
118
|
+
}
|
|
119
|
+
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
120
|
+
const repo = readRepoConnection(projectRoot);
|
|
121
|
+
if (!repo)
|
|
122
|
+
return;
|
|
123
|
+
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
103
124
|
}
|
|
104
125
|
|
|
105
126
|
// packages/cli/src/commands/_server-client.ts
|
|
106
|
-
var
|
|
127
|
+
var scopedGitHubBearerTokens = new Map;
|
|
128
|
+
var serverPhaseListener = null;
|
|
129
|
+
function reportServerPhase(label) {
|
|
130
|
+
serverPhaseListener?.(label);
|
|
131
|
+
}
|
|
107
132
|
function cleanToken(value) {
|
|
108
133
|
const trimmed = value?.trim();
|
|
109
134
|
return trimmed ? trimmed : null;
|
|
@@ -120,49 +145,80 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
120
145
|
}
|
|
121
146
|
}
|
|
122
147
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
123
|
-
|
|
124
|
-
|
|
148
|
+
const scopedKey = resolve2(projectRoot);
|
|
149
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
150
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
125
151
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
126
|
-
if (privateSession)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return
|
|
152
|
+
if (privateSession)
|
|
153
|
+
return privateSession;
|
|
154
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
155
|
+
}
|
|
156
|
+
function readStoredGitHubAuthToken(projectRoot) {
|
|
157
|
+
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
158
|
+
if (!existsSync2(path))
|
|
159
|
+
return null;
|
|
160
|
+
try {
|
|
161
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
162
|
+
return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
|
|
163
|
+
} catch {
|
|
164
|
+
return null;
|
|
134
165
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
139
|
-
});
|
|
140
|
-
cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
|
|
141
|
-
return cachedGitHubBearerToken;
|
|
166
|
+
}
|
|
167
|
+
function readLocalConnectionFallbackToken(projectRoot) {
|
|
168
|
+
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
142
169
|
}
|
|
143
170
|
async function ensureServerForCli(projectRoot) {
|
|
144
171
|
try {
|
|
145
172
|
const selected = resolveSelectedConnection(projectRoot);
|
|
146
173
|
if (selected?.connection.kind === "remote") {
|
|
174
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
175
|
+
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
176
|
+
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
147
177
|
return {
|
|
148
178
|
baseUrl: selected.connection.baseUrl,
|
|
149
|
-
authToken
|
|
150
|
-
connectionKind: "remote"
|
|
179
|
+
authToken,
|
|
180
|
+
connectionKind: "remote",
|
|
181
|
+
serverProjectRoot
|
|
151
182
|
};
|
|
152
183
|
}
|
|
184
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
153
185
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
154
186
|
return {
|
|
155
187
|
baseUrl: connection.baseUrl,
|
|
156
|
-
authToken: connection.authToken,
|
|
157
|
-
connectionKind: "local"
|
|
188
|
+
authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
|
|
189
|
+
connectionKind: "local",
|
|
190
|
+
serverProjectRoot: resolve2(projectRoot)
|
|
158
191
|
};
|
|
159
192
|
} catch (error) {
|
|
160
193
|
if (error instanceof Error) {
|
|
161
|
-
throw new
|
|
194
|
+
throw new CliError(error.message, 1);
|
|
162
195
|
}
|
|
163
196
|
throw error;
|
|
164
197
|
}
|
|
165
198
|
}
|
|
199
|
+
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
200
|
+
const repo = readRepoConnection(projectRoot);
|
|
201
|
+
const slug = repo?.project?.trim();
|
|
202
|
+
if (!slug)
|
|
203
|
+
return null;
|
|
204
|
+
try {
|
|
205
|
+
const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
|
|
206
|
+
headers: mergeHeaders(undefined, authToken)
|
|
207
|
+
});
|
|
208
|
+
if (!response.ok)
|
|
209
|
+
return null;
|
|
210
|
+
const payload = await response.json();
|
|
211
|
+
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
212
|
+
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
213
|
+
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
214
|
+
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
215
|
+
if (path)
|
|
216
|
+
writeRepoServerProjectRoot(projectRoot, path);
|
|
217
|
+
return path;
|
|
218
|
+
} catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
166
222
|
function mergeHeaders(headers, authToken) {
|
|
167
223
|
const merged = new Headers(headers);
|
|
168
224
|
if (authToken) {
|
|
@@ -185,12 +241,65 @@ function diagnosticMessage(payload) {
|
|
|
185
241
|
});
|
|
186
242
|
return messages.length > 0 ? messages.join("; ") : null;
|
|
187
243
|
}
|
|
244
|
+
var serverReachabilityCache = new Map;
|
|
245
|
+
async function probeServerReachability(baseUrl, authToken) {
|
|
246
|
+
try {
|
|
247
|
+
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
|
|
248
|
+
headers: mergeHeaders(undefined, authToken),
|
|
249
|
+
signal: AbortSignal.timeout(1500)
|
|
250
|
+
});
|
|
251
|
+
return response.ok;
|
|
252
|
+
} catch {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function cachedServerReachability(projectRoot, baseUrl, authToken) {
|
|
257
|
+
const key = resolve2(projectRoot);
|
|
258
|
+
const cached = serverReachabilityCache.get(key);
|
|
259
|
+
if (cached)
|
|
260
|
+
return cached;
|
|
261
|
+
const probe = probeServerReachability(baseUrl, authToken);
|
|
262
|
+
serverReachabilityCache.set(key, probe);
|
|
263
|
+
return probe;
|
|
264
|
+
}
|
|
265
|
+
function describeSelectedServer(projectRoot, server) {
|
|
266
|
+
try {
|
|
267
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
268
|
+
if (selected) {
|
|
269
|
+
return {
|
|
270
|
+
alias: selected.alias,
|
|
271
|
+
target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
} catch {}
|
|
275
|
+
return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
|
|
276
|
+
}
|
|
277
|
+
async function buildServerFailureContext(projectRoot, server) {
|
|
278
|
+
const { alias, target } = describeSelectedServer(projectRoot, server);
|
|
279
|
+
const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
|
|
280
|
+
const reachability = reachable ? "server is reachable" : "server is unreachable";
|
|
281
|
+
return {
|
|
282
|
+
contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
|
|
283
|
+
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
284
|
+
};
|
|
285
|
+
}
|
|
188
286
|
async function requestServerJson(context, pathname, init = {}) {
|
|
189
287
|
const server = await ensureServerForCli(context.projectRoot);
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
headers
|
|
193
|
-
});
|
|
288
|
+
const headers = mergeHeaders(init.headers, server.authToken);
|
|
289
|
+
if (server.serverProjectRoot)
|
|
290
|
+
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
291
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
292
|
+
let response;
|
|
293
|
+
try {
|
|
294
|
+
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
295
|
+
...init,
|
|
296
|
+
headers
|
|
297
|
+
});
|
|
298
|
+
} catch (error) {
|
|
299
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
300
|
+
throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
|
|
301
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
302
|
+
}
|
|
194
303
|
const text = await response.text();
|
|
195
304
|
const payload = text.trim().length > 0 ? (() => {
|
|
196
305
|
try {
|
|
@@ -202,10 +311,23 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
202
311
|
if (!response.ok) {
|
|
203
312
|
const diagnostics = diagnosticMessage(payload);
|
|
204
313
|
const detail = diagnostics ?? (text || response.statusText);
|
|
205
|
-
|
|
314
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
315
|
+
throw new CliError(`Rig server request failed (${response.status}): ${detail}
|
|
316
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
206
317
|
}
|
|
207
318
|
return payload;
|
|
208
319
|
}
|
|
320
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
321
|
+
"created",
|
|
322
|
+
"preparing",
|
|
323
|
+
"running",
|
|
324
|
+
"validating",
|
|
325
|
+
"reviewing",
|
|
326
|
+
"stopped",
|
|
327
|
+
"failed",
|
|
328
|
+
"needs-attention",
|
|
329
|
+
"needs_attention"
|
|
330
|
+
]);
|
|
209
331
|
|
|
210
332
|
// packages/cli/src/commands/_snapshot-upload.ts
|
|
211
333
|
var UPLOADED_SNAPSHOT_PR_MARKER = "<!-- rig:uploaded-snapshot -->";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/commands/_spinner.ts
|
|
3
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4
|
+
function createTtySpinner(input) {
|
|
5
|
+
const output = input.output ?? process.stdout;
|
|
6
|
+
const isTty = output.isTTY === true;
|
|
7
|
+
const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
|
|
8
|
+
let label = input.label;
|
|
9
|
+
let frame = 0;
|
|
10
|
+
let paused = false;
|
|
11
|
+
let stopped = false;
|
|
12
|
+
let lastPrintedLabel = "";
|
|
13
|
+
const render = () => {
|
|
14
|
+
if (stopped || paused)
|
|
15
|
+
return;
|
|
16
|
+
if (!isTty) {
|
|
17
|
+
if (label !== lastPrintedLabel) {
|
|
18
|
+
output.write(`${label}
|
|
19
|
+
`);
|
|
20
|
+
lastPrintedLabel = label;
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
frame = (frame + 1) % frames.length;
|
|
25
|
+
const glyph = frames[frame] ?? frames[0] ?? "";
|
|
26
|
+
output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
|
|
27
|
+
};
|
|
28
|
+
const clearLine = () => {
|
|
29
|
+
if (isTty)
|
|
30
|
+
output.write("\r\x1B[2K");
|
|
31
|
+
};
|
|
32
|
+
render();
|
|
33
|
+
const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
|
|
34
|
+
return {
|
|
35
|
+
setLabel(next) {
|
|
36
|
+
label = next;
|
|
37
|
+
render();
|
|
38
|
+
},
|
|
39
|
+
pause() {
|
|
40
|
+
paused = true;
|
|
41
|
+
clearLine();
|
|
42
|
+
},
|
|
43
|
+
resume() {
|
|
44
|
+
if (stopped)
|
|
45
|
+
return;
|
|
46
|
+
paused = false;
|
|
47
|
+
render();
|
|
48
|
+
},
|
|
49
|
+
stop(finalLine) {
|
|
50
|
+
if (stopped)
|
|
51
|
+
return;
|
|
52
|
+
stopped = true;
|
|
53
|
+
if (timer)
|
|
54
|
+
clearInterval(timer);
|
|
55
|
+
clearLine();
|
|
56
|
+
if (finalLine)
|
|
57
|
+
output.write(`${finalLine}
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export {
|
|
63
|
+
createTtySpinner,
|
|
64
|
+
SPINNER_FRAMES
|
|
65
|
+
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/commands/_task-picker.ts
|
|
3
|
-
import {
|
|
3
|
+
import { cancel, isCancel, select } from "@clack/prompts";
|
|
4
|
+
|
|
5
|
+
// packages/cli/src/commands/_operator-surface.ts
|
|
6
|
+
import { createInterface as createPromptInterface } from "readline/promises";
|
|
4
7
|
function taskId(task) {
|
|
5
8
|
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
6
9
|
}
|
|
@@ -13,6 +16,19 @@ function taskStatus(task) {
|
|
|
13
16
|
function renderTaskPickerRows(tasks) {
|
|
14
17
|
return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
|
|
15
18
|
}
|
|
19
|
+
async function promptForTaskSelection(question) {
|
|
20
|
+
const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
|
|
21
|
+
try {
|
|
22
|
+
return await rl.question(question);
|
|
23
|
+
} finally {
|
|
24
|
+
rl.close();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// packages/cli/src/commands/_task-picker.ts
|
|
29
|
+
function taskId2(task) {
|
|
30
|
+
return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
|
|
31
|
+
}
|
|
16
32
|
async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
17
33
|
if (tasks.length === 0)
|
|
18
34
|
return null;
|
|
@@ -22,25 +38,37 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
22
38
|
if (!isTty) {
|
|
23
39
|
throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
|
|
24
40
|
}
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
41
|
+
if (io.prompt || io.renderer) {
|
|
42
|
+
const prompt = io.prompt ?? promptForTaskSelection;
|
|
43
|
+
const renderer = io.renderer ?? { writeLine: (line) => process.stdout.write(`${line}
|
|
44
|
+
`) };
|
|
45
|
+
renderer.writeLine("Select Rig task:");
|
|
46
|
+
for (const row of renderTaskPickerRows(tasks))
|
|
47
|
+
renderer.writeLine(` ${row}`);
|
|
48
|
+
const answer2 = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
|
|
49
|
+
if (!answer2)
|
|
50
|
+
return null;
|
|
51
|
+
if (/^\d+$/.test(answer2)) {
|
|
52
|
+
const index2 = Number.parseInt(answer2, 10) - 1;
|
|
53
|
+
return tasks[index2] ?? null;
|
|
31
54
|
}
|
|
55
|
+
return tasks.find((task) => taskId2(task) === answer2) ?? null;
|
|
56
|
+
}
|
|
57
|
+
const options = tasks.map((task, index2) => ({
|
|
58
|
+
value: `${index2}`,
|
|
59
|
+
label: `${taskId2(task)} \xB7 ${typeof task.title === "string" && task.title.trim() ? task.title.trim() : "Untitled task"}`,
|
|
60
|
+
hint: typeof task.status === "string" && task.status.trim() ? task.status.trim() : undefined
|
|
61
|
+
}));
|
|
62
|
+
const answer = await select({
|
|
63
|
+
message: "Select Rig task",
|
|
64
|
+
options
|
|
32
65
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
console.log(` ${row}`);
|
|
36
|
-
const answer = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
|
|
37
|
-
if (!answer)
|
|
66
|
+
if (isCancel(answer)) {
|
|
67
|
+
cancel("No task selected.");
|
|
38
68
|
return null;
|
|
39
|
-
if (/^\d+$/.test(answer)) {
|
|
40
|
-
const index = Number.parseInt(answer, 10) - 1;
|
|
41
|
-
return tasks[index] ?? null;
|
|
42
69
|
}
|
|
43
|
-
|
|
70
|
+
const index = Number.parseInt(String(answer), 10);
|
|
71
|
+
return Number.isFinite(index) ? tasks[index] ?? null : null;
|
|
44
72
|
}
|
|
45
73
|
export {
|
|
46
74
|
selectTaskWithTextPicker,
|
|
@@ -4,12 +4,19 @@ import { resolve as resolve2 } from "path";
|
|
|
4
4
|
|
|
5
5
|
// packages/cli/src/runner.ts
|
|
6
6
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
7
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
7
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
8
8
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
9
|
-
import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
10
|
-
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
11
9
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
12
|
-
|
|
10
|
+
|
|
11
|
+
class CliError extends RuntimeCliError {
|
|
12
|
+
hint;
|
|
13
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
14
|
+
super(message, exitCode);
|
|
15
|
+
if (options.hint?.trim()) {
|
|
16
|
+
this.hint = options.hint.trim();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
13
20
|
function takeFlag(args, flag) {
|
|
14
21
|
const rest = [];
|
|
15
22
|
let value = false;
|
|
@@ -30,7 +37,7 @@ function takeOption(args, option) {
|
|
|
30
37
|
if (current === option) {
|
|
31
38
|
const next = args[index + 1];
|
|
32
39
|
if (!next || next.startsWith("-")) {
|
|
33
|
-
throw new CliError(`Missing value for ${option}`);
|
|
40
|
+
throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
|
|
34
41
|
}
|
|
35
42
|
value = next;
|
|
36
43
|
index += 1;
|
|
@@ -64,8 +71,7 @@ import { resolve } from "path";
|
|
|
64
71
|
import {
|
|
65
72
|
readAuthorityRun,
|
|
66
73
|
readJsonlFile,
|
|
67
|
-
|
|
68
|
-
writeJsonFile
|
|
74
|
+
writeAuthorityRunRecord
|
|
69
75
|
} from "@rig/runtime/control-plane/authority-files";
|
|
70
76
|
|
|
71
77
|
// packages/cli/src/commands/_paths.ts
|
|
@@ -159,7 +165,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
|
|
|
159
165
|
} else if ("errorText" in next) {
|
|
160
166
|
delete next.errorText;
|
|
161
167
|
}
|
|
162
|
-
|
|
168
|
+
writeAuthorityRunRecord(projectRoot, input.runId, next);
|
|
163
169
|
return next;
|
|
164
170
|
}
|
|
165
171
|
|
|
@@ -213,7 +219,7 @@ function parseIsolationMode(value, allowOff) {
|
|
|
213
219
|
if (allowOff && value === "off") {
|
|
214
220
|
return value;
|
|
215
221
|
}
|
|
216
|
-
throw new
|
|
222
|
+
throw new CliError(`Invalid isolation mode: ${value}. Use ${allowOff ? "off|" : ""}worktree.`);
|
|
217
223
|
}
|
|
218
224
|
|
|
219
225
|
// packages/cli/src/commands/_preflight.ts
|
|
@@ -221,6 +227,19 @@ import { ensureProjectMainFreshBeforeRun } from "@rig/runtime/control-plane/proj
|
|
|
221
227
|
|
|
222
228
|
// packages/cli/src/commands/_server-client.ts
|
|
223
229
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
230
|
+
var scopedGitHubBearerTokens = new Map;
|
|
231
|
+
var serverReachabilityCache = new Map;
|
|
232
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
233
|
+
"created",
|
|
234
|
+
"preparing",
|
|
235
|
+
"running",
|
|
236
|
+
"validating",
|
|
237
|
+
"reviewing",
|
|
238
|
+
"stopped",
|
|
239
|
+
"failed",
|
|
240
|
+
"needs-attention",
|
|
241
|
+
"needs_attention"
|
|
242
|
+
]);
|
|
224
243
|
|
|
225
244
|
// packages/cli/src/commands/_preflight.ts
|
|
226
245
|
async function runProjectMainSyncPreflight(context, options) {
|
|
@@ -236,7 +255,7 @@ async function runProjectMainSyncPreflight(context, options) {
|
|
|
236
255
|
runBootstrap: async () => {
|
|
237
256
|
const bootstrap = await context.runCommand(["bun", "run", "bootstrap"]);
|
|
238
257
|
if (bootstrap.exitCode !== 0) {
|
|
239
|
-
throw new
|
|
258
|
+
throw new CliError(bootstrap.stderr || bootstrap.stdout || "bun run bootstrap failed during project pre-run sync", bootstrap.exitCode || 1);
|
|
240
259
|
}
|
|
241
260
|
}
|
|
242
261
|
});
|
|
@@ -289,7 +308,7 @@ async function executeAgent(context, args) {
|
|
|
289
308
|
const [command = "list", ...rest] = args;
|
|
290
309
|
switch (command) {
|
|
291
310
|
case "list": {
|
|
292
|
-
requireNoExtraArgs(rest, "
|
|
311
|
+
requireNoExtraArgs(rest, "rig agent list");
|
|
293
312
|
const runtimes = await listAgentRuntimes(context.projectRoot);
|
|
294
313
|
if (context.outputMode === "text") {
|
|
295
314
|
if (runtimes.length === 0) {
|
|
@@ -310,12 +329,12 @@ async function executeAgent(context, args) {
|
|
|
310
329
|
pending = modeResult.rest;
|
|
311
330
|
const taskResult = takeOption(pending, "--task");
|
|
312
331
|
pending = taskResult.rest;
|
|
313
|
-
requireNoExtraArgs(pending, "
|
|
332
|
+
requireNoExtraArgs(pending, "rig agent prepare --task <id> [--id <id>] [--mode worktree]");
|
|
314
333
|
const mode = parseIsolationMode(modeResult.value, false);
|
|
315
334
|
const id = idResult.value || agentId("agent");
|
|
316
335
|
const taskId = taskResult.value?.trim();
|
|
317
336
|
if (!taskId) {
|
|
318
|
-
throw new
|
|
337
|
+
throw new CliError("Usage: rig agent prepare --task <id> [--id <id>] [--mode worktree]");
|
|
319
338
|
}
|
|
320
339
|
const runtime = await withMutedConsole(context.outputMode === "json", () => ensureAgentRuntime({
|
|
321
340
|
projectRoot: context.projectRoot,
|
|
@@ -333,7 +352,7 @@ async function executeAgent(context, args) {
|
|
|
333
352
|
case "run": {
|
|
334
353
|
const { options, commandParts } = splitAtDoubleDash(rest);
|
|
335
354
|
if (commandParts.length === 0) {
|
|
336
|
-
throw new
|
|
355
|
+
throw new CliError("Usage: rig agent run [--id <id>] [--mode worktree] [--skip-project-sync] -- <command...>");
|
|
337
356
|
}
|
|
338
357
|
let pending = options;
|
|
339
358
|
const idResult = takeOption(pending, "--id");
|
|
@@ -344,12 +363,12 @@ async function executeAgent(context, args) {
|
|
|
344
363
|
pending = taskResult.rest;
|
|
345
364
|
const skipProjectSyncResult = takeFlag(pending, "--skip-project-sync");
|
|
346
365
|
pending = skipProjectSyncResult.rest;
|
|
347
|
-
requireNoExtraArgs(pending, "
|
|
366
|
+
requireNoExtraArgs(pending, "rig agent run --task <id> [--id <id>] [--mode worktree] [--skip-project-sync] -- <command...>");
|
|
348
367
|
const mode = parseIsolationMode(modeResult.value, false);
|
|
349
368
|
const id = idResult.value || agentId("agent-run");
|
|
350
369
|
const taskId = taskResult.value?.trim();
|
|
351
370
|
if (!taskId) {
|
|
352
|
-
throw new
|
|
371
|
+
throw new CliError("Usage: rig agent run --task <id> [--id <id>] [--mode worktree] [--skip-project-sync] -- <command...>");
|
|
353
372
|
}
|
|
354
373
|
await runProjectMainSyncPreflight(context, { disabled: skipProjectSyncResult.value });
|
|
355
374
|
const createdAt = new Date().toISOString();
|
|
@@ -408,7 +427,7 @@ async function executeAgent(context, args) {
|
|
|
408
427
|
pid: process.pid,
|
|
409
428
|
errorText: result.stderr ? result.stderr.trim() : `Agent runtime command failed (${result.exitCode})`
|
|
410
429
|
});
|
|
411
|
-
throw new
|
|
430
|
+
throw new CliError(`Agent runtime command failed (${result.exitCode}) in ${runtime.id}${result.stderr ? `
|
|
412
431
|
${result.stderr.trim()}` : ""}`, result.exitCode);
|
|
413
432
|
}
|
|
414
433
|
const completedAt = new Date().toISOString();
|
|
@@ -463,9 +482,9 @@ ${result.stderr.trim()}` : ""}`, result.exitCode);
|
|
|
463
482
|
pending = allResult.rest;
|
|
464
483
|
const idResult = takeOption(pending, "--id");
|
|
465
484
|
pending = idResult.rest;
|
|
466
|
-
requireNoExtraArgs(pending, "
|
|
485
|
+
requireNoExtraArgs(pending, "rig agent cleanup (--id <id> | --all)");
|
|
467
486
|
if (!allResult.value && !idResult.value) {
|
|
468
|
-
throw new
|
|
487
|
+
throw new CliError("Provide --id <id> or --all.", 1, { hint: "Run `rig agent list` to find agent ids." });
|
|
469
488
|
}
|
|
470
489
|
const runtimes = await listAgentRuntimes(context.projectRoot);
|
|
471
490
|
const targets = allResult.value ? runtimes.map((runtime) => runtime.id) : [idResult.value];
|
|
@@ -490,7 +509,7 @@ ${result.stderr.trim()}` : ""}`, result.exitCode);
|
|
|
490
509
|
};
|
|
491
510
|
}
|
|
492
511
|
default:
|
|
493
|
-
throw new
|
|
512
|
+
throw new CliError(`Unknown agent command: ${command}`, 1, { hint: "Run `rig agent --help` to list agent commands." });
|
|
494
513
|
}
|
|
495
514
|
}
|
|
496
515
|
export {
|