@h-rig/cli 0.0.6-alpha.67 → 0.0.6-alpha.69
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 +266 -75
- package/dist/src/commands/_async-ui.js +152 -0
- package/dist/src/commands/_doctor-checks.js +29 -0
- package/dist/src/commands/_operator-view.js +154 -4
- package/dist/src/commands/_pi-frontend.js +24 -1
- package/dist/src/commands/_preflight.js +18 -0
- package/dist/src/commands/_server-client.js +46 -0
- package/dist/src/commands/_snapshot-upload.js +18 -0
- package/dist/src/commands/_spinner.js +4 -2
- package/dist/src/commands/agent.js +11 -0
- package/dist/src/commands/doctor.js +156 -1
- package/dist/src/commands/github.js +148 -3
- package/dist/src/commands/inbox.js +150 -6
- package/dist/src/commands/init.js +29 -0
- package/dist/src/commands/inspect.js +158 -8
- package/dist/src/commands/queue.js +11 -0
- package/dist/src/commands/run.js +207 -35
- package/dist/src/commands/server.js +18 -0
- package/dist/src/commands/setup.js +161 -6
- package/dist/src/commands/stats.js +167 -22
- package/dist/src/commands/task-run-driver.js +18 -0
- package/dist/src/commands/task.js +197 -47
- package/dist/src/commands.js +266 -75
- package/dist/src/index.js +266 -75
- package/package.json +8 -8
|
@@ -227,6 +227,15 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
|
227
227
|
|
|
228
228
|
// packages/cli/src/commands/_server-client.ts
|
|
229
229
|
var scopedGitHubBearerTokens = new Map;
|
|
230
|
+
var serverPhaseListener = null;
|
|
231
|
+
function setServerPhaseListener(listener) {
|
|
232
|
+
const previous = serverPhaseListener;
|
|
233
|
+
serverPhaseListener = listener;
|
|
234
|
+
return previous;
|
|
235
|
+
}
|
|
236
|
+
function reportServerPhase(label) {
|
|
237
|
+
serverPhaseListener?.(label);
|
|
238
|
+
}
|
|
230
239
|
function cleanToken(value) {
|
|
231
240
|
const trimmed = value?.trim();
|
|
232
241
|
return trimmed ? trimmed : null;
|
|
@@ -269,6 +278,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
269
278
|
try {
|
|
270
279
|
const selected = resolveSelectedConnection(projectRoot);
|
|
271
280
|
if (selected?.connection.kind === "remote") {
|
|
281
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
272
282
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
273
283
|
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
274
284
|
return {
|
|
@@ -278,6 +288,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
278
288
|
serverProjectRoot
|
|
279
289
|
};
|
|
280
290
|
}
|
|
291
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
281
292
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
282
293
|
return {
|
|
283
294
|
baseUrl: connection.baseUrl,
|
|
@@ -384,6 +395,7 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
384
395
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
385
396
|
if (server.serverProjectRoot)
|
|
386
397
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
398
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
387
399
|
let response;
|
|
388
400
|
try {
|
|
389
401
|
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
@@ -412,6 +424,138 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
|
|
|
412
424
|
}
|
|
413
425
|
return payload;
|
|
414
426
|
}
|
|
427
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
428
|
+
"created",
|
|
429
|
+
"preparing",
|
|
430
|
+
"running",
|
|
431
|
+
"validating",
|
|
432
|
+
"reviewing",
|
|
433
|
+
"stopped",
|
|
434
|
+
"failed",
|
|
435
|
+
"needs-attention",
|
|
436
|
+
"needs_attention"
|
|
437
|
+
]);
|
|
438
|
+
|
|
439
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
440
|
+
import pc2 from "picocolors";
|
|
441
|
+
|
|
442
|
+
// packages/cli/src/commands/_spinner.ts
|
|
443
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
444
|
+
function createTtySpinner(input) {
|
|
445
|
+
const output = input.output ?? process.stdout;
|
|
446
|
+
const isTty = output.isTTY === true;
|
|
447
|
+
const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
|
|
448
|
+
let label = input.label;
|
|
449
|
+
let frame = 0;
|
|
450
|
+
let paused = false;
|
|
451
|
+
let stopped = false;
|
|
452
|
+
let lastPrintedLabel = "";
|
|
453
|
+
const render = () => {
|
|
454
|
+
if (stopped || paused)
|
|
455
|
+
return;
|
|
456
|
+
if (!isTty) {
|
|
457
|
+
if (label !== lastPrintedLabel) {
|
|
458
|
+
output.write(`${label}
|
|
459
|
+
`);
|
|
460
|
+
lastPrintedLabel = label;
|
|
461
|
+
}
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
frame = (frame + 1) % frames.length;
|
|
465
|
+
const glyph = frames[frame] ?? frames[0] ?? "";
|
|
466
|
+
output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
|
|
467
|
+
};
|
|
468
|
+
const clearLine = () => {
|
|
469
|
+
if (isTty)
|
|
470
|
+
output.write("\r\x1B[2K");
|
|
471
|
+
};
|
|
472
|
+
render();
|
|
473
|
+
const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
|
|
474
|
+
return {
|
|
475
|
+
setLabel(next) {
|
|
476
|
+
label = next;
|
|
477
|
+
render();
|
|
478
|
+
},
|
|
479
|
+
pause() {
|
|
480
|
+
paused = true;
|
|
481
|
+
clearLine();
|
|
482
|
+
},
|
|
483
|
+
resume() {
|
|
484
|
+
if (stopped)
|
|
485
|
+
return;
|
|
486
|
+
paused = false;
|
|
487
|
+
render();
|
|
488
|
+
},
|
|
489
|
+
stop(finalLine) {
|
|
490
|
+
if (stopped)
|
|
491
|
+
return;
|
|
492
|
+
stopped = true;
|
|
493
|
+
if (timer)
|
|
494
|
+
clearInterval(timer);
|
|
495
|
+
clearLine();
|
|
496
|
+
if (finalLine)
|
|
497
|
+
output.write(`${finalLine}
|
|
498
|
+
`);
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
504
|
+
var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
505
|
+
var DONE_SYMBOL = pc2.green("\u25C7");
|
|
506
|
+
var FAIL_SYMBOL = pc2.red("\u25A0");
|
|
507
|
+
var activeUpdate = null;
|
|
508
|
+
async function withSpinner(label, work, options = {}) {
|
|
509
|
+
if (options.outputMode === "json") {
|
|
510
|
+
return work(() => {});
|
|
511
|
+
}
|
|
512
|
+
if (activeUpdate) {
|
|
513
|
+
const outer = activeUpdate;
|
|
514
|
+
outer(label);
|
|
515
|
+
return work(outer);
|
|
516
|
+
}
|
|
517
|
+
const output = options.output ?? process.stderr;
|
|
518
|
+
const isTty = output.isTTY === true;
|
|
519
|
+
let lastLabel = label;
|
|
520
|
+
if (!isTty) {
|
|
521
|
+
output.write(`${label}
|
|
522
|
+
`);
|
|
523
|
+
const update2 = (next) => {
|
|
524
|
+
lastLabel = next;
|
|
525
|
+
};
|
|
526
|
+
activeUpdate = update2;
|
|
527
|
+
const previousListener2 = setServerPhaseListener(update2);
|
|
528
|
+
try {
|
|
529
|
+
return await work(update2);
|
|
530
|
+
} finally {
|
|
531
|
+
activeUpdate = null;
|
|
532
|
+
setServerPhaseListener(previousListener2);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
const spinner = createTtySpinner({
|
|
536
|
+
label,
|
|
537
|
+
output,
|
|
538
|
+
frames: CLACK_SPINNER_FRAMES,
|
|
539
|
+
styleFrame: (frame) => pc2.magenta(frame)
|
|
540
|
+
});
|
|
541
|
+
const update = (next) => {
|
|
542
|
+
lastLabel = next;
|
|
543
|
+
spinner.setLabel(next);
|
|
544
|
+
};
|
|
545
|
+
activeUpdate = update;
|
|
546
|
+
const previousListener = setServerPhaseListener(update);
|
|
547
|
+
try {
|
|
548
|
+
const result = await work(update);
|
|
549
|
+
spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
|
|
550
|
+
return result;
|
|
551
|
+
} catch (error) {
|
|
552
|
+
spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
|
|
553
|
+
throw error;
|
|
554
|
+
} finally {
|
|
555
|
+
activeUpdate = null;
|
|
556
|
+
setServerPhaseListener(previousListener);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
415
559
|
|
|
416
560
|
// packages/cli/src/commands/inbox.ts
|
|
417
561
|
async function listInboxRecords(context, kind, filters) {
|
|
@@ -517,7 +661,7 @@ async function executeInbox(context, args) {
|
|
|
517
661
|
const task = takeOption(pending, "--task");
|
|
518
662
|
pending = task.rest;
|
|
519
663
|
requireNoExtraArgs(pending, "rig inbox approvals [--run <id>] [--task <id>]");
|
|
520
|
-
const approvals = await listInboxRecords(context, "approvals", { run: run.value, task: task.value });
|
|
664
|
+
const approvals = await withSpinner("Reading approvals from server\u2026", () => listInboxRecords(context, "approvals", { run: run.value, task: task.value }), { outputMode: context.outputMode });
|
|
521
665
|
renderList(context, "approvals", approvals);
|
|
522
666
|
return { ok: true, group: "inbox", command, details: { approvals } };
|
|
523
667
|
}
|
|
@@ -528,7 +672,7 @@ async function executeInbox(context, args) {
|
|
|
528
672
|
const task = takeOption(pending, "--task");
|
|
529
673
|
pending = task.rest;
|
|
530
674
|
requireNoExtraArgs(pending, "rig inbox inputs [--run <id>] [--task <id>]");
|
|
531
|
-
const requests = await listInboxRecords(context, "inputs", { run: run.value, task: task.value });
|
|
675
|
+
const requests = await withSpinner("Reading input requests from server\u2026", () => listInboxRecords(context, "inputs", { run: run.value, task: task.value }), { outputMode: context.outputMode });
|
|
532
676
|
renderList(context, "inputs", requests);
|
|
533
677
|
return { ok: true, group: "inbox", command, details: { requests } };
|
|
534
678
|
}
|
|
@@ -549,7 +693,7 @@ async function executeInbox(context, args) {
|
|
|
549
693
|
if (decision.value !== "approve" && decision.value !== "reject") {
|
|
550
694
|
throw new CliError("decision must be approve or reject.", 1, { hint: "Re-run as `rig inbox approve --run <id> --request <id> --decision approve|reject`." });
|
|
551
695
|
}
|
|
552
|
-
const result = await requestServerJson(context, "/api/inbox/approvals/resolve", {
|
|
696
|
+
const result = await withSpinner(`Resolving approval ${request.value}\u2026`, () => requestServerJson(context, "/api/inbox/approvals/resolve", {
|
|
553
697
|
method: "POST",
|
|
554
698
|
headers: { "content-type": "application/json" },
|
|
555
699
|
body: JSON.stringify({
|
|
@@ -558,7 +702,7 @@ async function executeInbox(context, args) {
|
|
|
558
702
|
decision: decision.value,
|
|
559
703
|
note: note2.value ?? null
|
|
560
704
|
})
|
|
561
|
-
});
|
|
705
|
+
}), { outputMode: context.outputMode });
|
|
562
706
|
return { ok: true, group: "inbox", command, details: { result } };
|
|
563
707
|
}
|
|
564
708
|
case "respond": {
|
|
@@ -592,7 +736,7 @@ async function executeInbox(context, args) {
|
|
|
592
736
|
const [key, ...restValue] = entry.split("=");
|
|
593
737
|
return [key, restValue.join("=")];
|
|
594
738
|
}));
|
|
595
|
-
const result = await requestServerJson(context, "/api/inbox/inputs/respond", {
|
|
739
|
+
const result = await withSpinner(`Sending response for ${request.value}\u2026`, () => requestServerJson(context, "/api/inbox/inputs/respond", {
|
|
596
740
|
method: "POST",
|
|
597
741
|
headers: { "content-type": "application/json" },
|
|
598
742
|
body: JSON.stringify({
|
|
@@ -600,7 +744,7 @@ async function executeInbox(context, args) {
|
|
|
600
744
|
requestId: request.value,
|
|
601
745
|
answers: parsedAnswers
|
|
602
746
|
})
|
|
603
|
-
});
|
|
747
|
+
}), { outputMode: context.outputMode });
|
|
604
748
|
return { ok: true, group: "inbox", command, details: { result } };
|
|
605
749
|
}
|
|
606
750
|
case "watch": {
|
|
@@ -172,6 +172,10 @@ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
|
172
172
|
import { resolve as resolve2 } from "path";
|
|
173
173
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
174
174
|
var scopedGitHubBearerTokens = new Map;
|
|
175
|
+
var serverPhaseListener = null;
|
|
176
|
+
function reportServerPhase(label) {
|
|
177
|
+
serverPhaseListener?.(label);
|
|
178
|
+
}
|
|
175
179
|
function cleanToken(value) {
|
|
176
180
|
const trimmed = value?.trim();
|
|
177
181
|
return trimmed ? trimmed : null;
|
|
@@ -218,6 +222,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
218
222
|
try {
|
|
219
223
|
const selected = resolveSelectedConnection(projectRoot);
|
|
220
224
|
if (selected?.connection.kind === "remote") {
|
|
225
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
221
226
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
222
227
|
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
223
228
|
return {
|
|
@@ -227,6 +232,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
227
232
|
serverProjectRoot
|
|
228
233
|
};
|
|
229
234
|
}
|
|
235
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
230
236
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
231
237
|
return {
|
|
232
238
|
baseUrl: connection.baseUrl,
|
|
@@ -333,6 +339,7 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
333
339
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
334
340
|
if (server.serverProjectRoot)
|
|
335
341
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
342
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
336
343
|
let response;
|
|
337
344
|
try {
|
|
338
345
|
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
@@ -439,6 +446,17 @@ async function getGitHubProjectStatusFieldViaServer(context, projectId) {
|
|
|
439
446
|
const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
|
|
440
447
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
441
448
|
}
|
|
449
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
450
|
+
"created",
|
|
451
|
+
"preparing",
|
|
452
|
+
"running",
|
|
453
|
+
"validating",
|
|
454
|
+
"reviewing",
|
|
455
|
+
"stopped",
|
|
456
|
+
"failed",
|
|
457
|
+
"needs-attention",
|
|
458
|
+
"needs_attention"
|
|
459
|
+
]);
|
|
442
460
|
|
|
443
461
|
// packages/cli/src/commands/_pi-install.ts
|
|
444
462
|
import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
|
|
@@ -842,7 +860,10 @@ async function runRigDoctorChecks(options) {
|
|
|
842
860
|
const bunVersion = options.bunVersion ?? Bun.version;
|
|
843
861
|
const request = options.requestJson ?? ((pathname, init) => requestServerJson({ projectRoot }, pathname, init));
|
|
844
862
|
const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
|
|
863
|
+
const progress = options.onProgress ?? (() => {});
|
|
864
|
+
progress("Checking local toolchain\u2026");
|
|
845
865
|
checks.push(check("bun", `bun >= ${MIN_SUPPORTED_BUN_VERSION}`, isSupportedBunVersion(bunVersion) ? "pass" : "fail", `found ${bunVersion}`, `Install Bun ${MIN_SUPPORTED_BUN_VERSION} or newer.`), check("git", "git", which("git") ? "pass" : "fail", which("git") ?? undefined, "Install git and ensure it is on PATH."), check("jq", "jq", which("jq") ? "pass" : "warn", which("jq") ?? undefined, "Install jq (for example `brew install jq`)."));
|
|
866
|
+
progress("Loading rig.config\u2026");
|
|
846
867
|
const loadedConfig = await loadConfig(projectRoot).catch(() => null);
|
|
847
868
|
const config = loadedConfig ?? loadFallbackConfig(projectRoot);
|
|
848
869
|
const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve5(projectRoot, name)));
|
|
@@ -861,6 +882,7 @@ async function runRigDoctorChecks(options) {
|
|
|
861
882
|
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));
|
|
862
883
|
let server = null;
|
|
863
884
|
try {
|
|
885
|
+
progress("Connecting to the selected Rig server\u2026");
|
|
864
886
|
server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
|
|
865
887
|
checks.push(check("server", "Rig server reachable", "pass", `${server.connectionKind} ${server.baseUrl}`));
|
|
866
888
|
} catch (error) {
|
|
@@ -868,18 +890,21 @@ async function runRigDoctorChecks(options) {
|
|
|
868
890
|
}
|
|
869
891
|
if (server || options.requestJson) {
|
|
870
892
|
try {
|
|
893
|
+
progress("Checking server status\u2026");
|
|
871
894
|
const status = await request("/api/server/status");
|
|
872
895
|
checks.push(check("server-status", "server project status", "pass", JSON.stringify(status).slice(0, 180)));
|
|
873
896
|
} catch (error) {
|
|
874
897
|
checks.push(check("server-status", "server project status", "fail", errorMessage(error), "Run `rig doctor` after the selected server is reachable."));
|
|
875
898
|
}
|
|
876
899
|
try {
|
|
900
|
+
progress("Checking GitHub auth\u2026");
|
|
877
901
|
const auth = await request("/api/github/auth/status");
|
|
878
902
|
checks.push(isAuthenticated(auth) ? check("github-auth", "GitHub auth", "pass") : check("github-auth", "GitHub auth", "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
|
|
879
903
|
} catch (error) {
|
|
880
904
|
checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
|
|
881
905
|
}
|
|
882
906
|
try {
|
|
907
|
+
progress("Checking GitHub repo permissions\u2026");
|
|
883
908
|
const permissions = await request("/api/github/repo/permissions");
|
|
884
909
|
const allowed = permissionAllowsPr(permissions);
|
|
885
910
|
checks.push(allowed === true ? check("github-repo-permissions", "GitHub repo PR permissions", "pass", JSON.stringify(permissions).slice(0, 180)) : allowed === false ? check("github-repo-permissions", "GitHub repo PR permissions", "fail", JSON.stringify(permissions).slice(0, 180), "Grant the selected GitHub token permission to push branches, open PRs, and merge according to repo rules.") : check("github-repo-permissions", "GitHub repo PR permissions", "warn", JSON.stringify(permissions).slice(0, 180), "Confirm the selected token can push branches and open PRs."));
|
|
@@ -887,6 +912,7 @@ async function runRigDoctorChecks(options) {
|
|
|
887
912
|
checks.push(check("github-repo-permissions", "GitHub repo PR permissions", "warn", errorMessage(error), "Ensure the server exposes repo permission checks and the token can open PRs."));
|
|
888
913
|
}
|
|
889
914
|
try {
|
|
915
|
+
progress("Checking GitHub issue labels\u2026");
|
|
890
916
|
const labels = await request("/api/workspace/task-labels");
|
|
891
917
|
const ready = labelsReady(labels);
|
|
892
918
|
checks.push(ready === false ? check("task-labels", "GitHub issue labels", "fail", JSON.stringify(labels).slice(0, 180), "Let Rig create required labels or create the configured lifecycle labels manually.") : check("task-labels", "GitHub issue labels", ready === true ? "pass" : "warn", JSON.stringify(labels).slice(0, 180), "Confirm required Rig lifecycle labels exist."));
|
|
@@ -894,6 +920,7 @@ async function runRigDoctorChecks(options) {
|
|
|
894
920
|
checks.push(check("task-labels", "GitHub issue labels", "warn", errorMessage(error), "Run `rig init`/`rig doctor` after label setup is wired on the server."));
|
|
895
921
|
}
|
|
896
922
|
try {
|
|
923
|
+
progress("Checking task projection\u2026");
|
|
897
924
|
const projection = await request("/api/workspace/task-projection");
|
|
898
925
|
checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
|
|
899
926
|
} catch (error) {
|
|
@@ -902,6 +929,7 @@ async function runRigDoctorChecks(options) {
|
|
|
902
929
|
const slug = projectStatusSlug(projectRoot, config);
|
|
903
930
|
if (slug) {
|
|
904
931
|
try {
|
|
932
|
+
progress("Checking server project checkout\u2026");
|
|
905
933
|
const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
|
|
906
934
|
checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
|
|
907
935
|
} catch (error) {
|
|
@@ -916,6 +944,7 @@ async function runRigDoctorChecks(options) {
|
|
|
916
944
|
}
|
|
917
945
|
checks.push(githubProjectsCheck(config));
|
|
918
946
|
checks.push(prMergeCheck(config));
|
|
947
|
+
progress("Checking Pi installation\u2026");
|
|
919
948
|
const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
|
|
920
949
|
ok: false,
|
|
921
950
|
label: "pi/pi-rig checks",
|
|
@@ -167,6 +167,15 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
|
167
167
|
|
|
168
168
|
// packages/cli/src/commands/_server-client.ts
|
|
169
169
|
var scopedGitHubBearerTokens = new Map;
|
|
170
|
+
var serverPhaseListener = null;
|
|
171
|
+
function setServerPhaseListener(listener) {
|
|
172
|
+
const previous = serverPhaseListener;
|
|
173
|
+
serverPhaseListener = listener;
|
|
174
|
+
return previous;
|
|
175
|
+
}
|
|
176
|
+
function reportServerPhase(label) {
|
|
177
|
+
serverPhaseListener?.(label);
|
|
178
|
+
}
|
|
170
179
|
function cleanToken(value) {
|
|
171
180
|
const trimmed = value?.trim();
|
|
172
181
|
return trimmed ? trimmed : null;
|
|
@@ -209,6 +218,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
209
218
|
try {
|
|
210
219
|
const selected = resolveSelectedConnection(projectRoot);
|
|
211
220
|
if (selected?.connection.kind === "remote") {
|
|
221
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
212
222
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
213
223
|
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
214
224
|
return {
|
|
@@ -218,6 +228,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
218
228
|
serverProjectRoot
|
|
219
229
|
};
|
|
220
230
|
}
|
|
231
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
221
232
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
222
233
|
return {
|
|
223
234
|
baseUrl: connection.baseUrl,
|
|
@@ -324,6 +335,7 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
324
335
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
325
336
|
if (server.serverProjectRoot)
|
|
326
337
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
338
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
327
339
|
let response;
|
|
328
340
|
try {
|
|
329
341
|
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
@@ -369,6 +381,138 @@ async function getRunLogsViaServer(context, runId, options = {}) {
|
|
|
369
381
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
370
382
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
371
383
|
}
|
|
384
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
385
|
+
"created",
|
|
386
|
+
"preparing",
|
|
387
|
+
"running",
|
|
388
|
+
"validating",
|
|
389
|
+
"reviewing",
|
|
390
|
+
"stopped",
|
|
391
|
+
"failed",
|
|
392
|
+
"needs-attention",
|
|
393
|
+
"needs_attention"
|
|
394
|
+
]);
|
|
395
|
+
|
|
396
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
397
|
+
import pc from "picocolors";
|
|
398
|
+
|
|
399
|
+
// packages/cli/src/commands/_spinner.ts
|
|
400
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
401
|
+
function createTtySpinner(input) {
|
|
402
|
+
const output = input.output ?? process.stdout;
|
|
403
|
+
const isTty = output.isTTY === true;
|
|
404
|
+
const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
|
|
405
|
+
let label = input.label;
|
|
406
|
+
let frame = 0;
|
|
407
|
+
let paused = false;
|
|
408
|
+
let stopped = false;
|
|
409
|
+
let lastPrintedLabel = "";
|
|
410
|
+
const render = () => {
|
|
411
|
+
if (stopped || paused)
|
|
412
|
+
return;
|
|
413
|
+
if (!isTty) {
|
|
414
|
+
if (label !== lastPrintedLabel) {
|
|
415
|
+
output.write(`${label}
|
|
416
|
+
`);
|
|
417
|
+
lastPrintedLabel = label;
|
|
418
|
+
}
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
frame = (frame + 1) % frames.length;
|
|
422
|
+
const glyph = frames[frame] ?? frames[0] ?? "";
|
|
423
|
+
output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
|
|
424
|
+
};
|
|
425
|
+
const clearLine = () => {
|
|
426
|
+
if (isTty)
|
|
427
|
+
output.write("\r\x1B[2K");
|
|
428
|
+
};
|
|
429
|
+
render();
|
|
430
|
+
const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
|
|
431
|
+
return {
|
|
432
|
+
setLabel(next) {
|
|
433
|
+
label = next;
|
|
434
|
+
render();
|
|
435
|
+
},
|
|
436
|
+
pause() {
|
|
437
|
+
paused = true;
|
|
438
|
+
clearLine();
|
|
439
|
+
},
|
|
440
|
+
resume() {
|
|
441
|
+
if (stopped)
|
|
442
|
+
return;
|
|
443
|
+
paused = false;
|
|
444
|
+
render();
|
|
445
|
+
},
|
|
446
|
+
stop(finalLine) {
|
|
447
|
+
if (stopped)
|
|
448
|
+
return;
|
|
449
|
+
stopped = true;
|
|
450
|
+
if (timer)
|
|
451
|
+
clearInterval(timer);
|
|
452
|
+
clearLine();
|
|
453
|
+
if (finalLine)
|
|
454
|
+
output.write(`${finalLine}
|
|
455
|
+
`);
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
461
|
+
var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
462
|
+
var DONE_SYMBOL = pc.green("\u25C7");
|
|
463
|
+
var FAIL_SYMBOL = pc.red("\u25A0");
|
|
464
|
+
var activeUpdate = null;
|
|
465
|
+
async function withSpinner(label, work, options = {}) {
|
|
466
|
+
if (options.outputMode === "json") {
|
|
467
|
+
return work(() => {});
|
|
468
|
+
}
|
|
469
|
+
if (activeUpdate) {
|
|
470
|
+
const outer = activeUpdate;
|
|
471
|
+
outer(label);
|
|
472
|
+
return work(outer);
|
|
473
|
+
}
|
|
474
|
+
const output = options.output ?? process.stderr;
|
|
475
|
+
const isTty = output.isTTY === true;
|
|
476
|
+
let lastLabel = label;
|
|
477
|
+
if (!isTty) {
|
|
478
|
+
output.write(`${label}
|
|
479
|
+
`);
|
|
480
|
+
const update2 = (next) => {
|
|
481
|
+
lastLabel = next;
|
|
482
|
+
};
|
|
483
|
+
activeUpdate = update2;
|
|
484
|
+
const previousListener2 = setServerPhaseListener(update2);
|
|
485
|
+
try {
|
|
486
|
+
return await work(update2);
|
|
487
|
+
} finally {
|
|
488
|
+
activeUpdate = null;
|
|
489
|
+
setServerPhaseListener(previousListener2);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const spinner = createTtySpinner({
|
|
493
|
+
label,
|
|
494
|
+
output,
|
|
495
|
+
frames: CLACK_SPINNER_FRAMES,
|
|
496
|
+
styleFrame: (frame) => pc.magenta(frame)
|
|
497
|
+
});
|
|
498
|
+
const update = (next) => {
|
|
499
|
+
lastLabel = next;
|
|
500
|
+
spinner.setLabel(next);
|
|
501
|
+
};
|
|
502
|
+
activeUpdate = update;
|
|
503
|
+
const previousListener = setServerPhaseListener(update);
|
|
504
|
+
try {
|
|
505
|
+
const result = await work(update);
|
|
506
|
+
spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
|
|
507
|
+
return result;
|
|
508
|
+
} catch (error) {
|
|
509
|
+
spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
|
|
510
|
+
throw error;
|
|
511
|
+
} finally {
|
|
512
|
+
activeUpdate = null;
|
|
513
|
+
setServerPhaseListener(previousListener);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
372
516
|
|
|
373
517
|
// packages/cli/src/commands/inspect.ts
|
|
374
518
|
async function executeInspect(context, args) {
|
|
@@ -380,13 +524,19 @@ async function executeInspect(context, args) {
|
|
|
380
524
|
const requiredTask = requireTask(task, "rig inspect logs --task <task-id>");
|
|
381
525
|
const latestRun = listAuthorityRuns(context.projectRoot).map((entry) => readAuthorityRun(context.projectRoot, entry.runId)).filter((run) => Boolean(run)).filter((run) => run.taskId === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
|
|
382
526
|
if (!latestRun) {
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
527
|
+
const fallback = await withSpinner(`Finding runs for ${requiredTask} on the server\u2026`, async (update) => {
|
|
528
|
+
const serverRuns = await listRunsViaServer(context, { limit: 200 }).catch(() => []);
|
|
529
|
+
const serverRun = serverRuns.filter((run) => String(run.taskId ?? "") === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
|
|
530
|
+
if (!serverRun || typeof serverRun.runId !== "string")
|
|
531
|
+
return null;
|
|
532
|
+
update(`Reading logs for run ${serverRun.runId}\u2026`);
|
|
533
|
+
const page = await getRunLogsViaServer(context, serverRun.runId, { limit: 500 });
|
|
534
|
+
return { runId: serverRun.runId, page };
|
|
535
|
+
}, { outputMode: context.outputMode });
|
|
536
|
+
if (!fallback) {
|
|
386
537
|
throw new CliError(`No runs found for ${requiredTask} (local or on the selected server).`, 1, { hint: "Start one with `rig task run --task <id>`, or check `rig run list`." });
|
|
387
538
|
}
|
|
388
|
-
const
|
|
389
|
-
const entries = Array.isArray(page.entries) ? page.entries : [];
|
|
539
|
+
const entries = Array.isArray(fallback.page.entries) ? fallback.page.entries : [];
|
|
390
540
|
if (context.outputMode === "text") {
|
|
391
541
|
for (const entry of entries) {
|
|
392
542
|
const record = entry && typeof entry === "object" ? entry : {};
|
|
@@ -395,9 +545,9 @@ async function executeInspect(context, args) {
|
|
|
395
545
|
console.log([title, detail].filter(Boolean).join(" \u2014 "));
|
|
396
546
|
}
|
|
397
547
|
if (entries.length === 0)
|
|
398
|
-
console.log(`(no log entries for run ${
|
|
548
|
+
console.log(`(no log entries for run ${fallback.runId})`);
|
|
399
549
|
}
|
|
400
|
-
return { ok: true, group: "inspect", command, details: { task: requiredTask, runId:
|
|
550
|
+
return { ok: true, group: "inspect", command, details: { task: requiredTask, runId: fallback.runId, source: "server", entries } };
|
|
401
551
|
}
|
|
402
552
|
const logsPath = resolve3(resolveAuthorityRunDir(context.projectRoot, latestRun.runId), "logs.jsonl");
|
|
403
553
|
if (!existsSync3(logsPath)) {
|
|
@@ -455,7 +605,7 @@ async function executeInspect(context, args) {
|
|
|
455
605
|
const { value: task, rest: remaining } = takeOption(rest, "--task");
|
|
456
606
|
requireNoExtraArgs(remaining, "rig inspect diff [--task <task-id>]");
|
|
457
607
|
if (task) {
|
|
458
|
-
const files = changedFilesForTask(context.projectRoot, task, false);
|
|
608
|
+
const files = await withSpinner(`Computing changed files for ${task}\u2026`, async () => changedFilesForTask(context.projectRoot, task, false), { outputMode: context.outputMode });
|
|
459
609
|
for (const file of files) {
|
|
460
610
|
console.log(file);
|
|
461
611
|
}
|
|
@@ -99,6 +99,17 @@ import { ensureProjectMainFreshBeforeRun } from "@rig/runtime/control-plane/proj
|
|
|
99
99
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
100
100
|
var scopedGitHubBearerTokens = new Map;
|
|
101
101
|
var serverReachabilityCache = new Map;
|
|
102
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
103
|
+
"created",
|
|
104
|
+
"preparing",
|
|
105
|
+
"running",
|
|
106
|
+
"validating",
|
|
107
|
+
"reviewing",
|
|
108
|
+
"stopped",
|
|
109
|
+
"failed",
|
|
110
|
+
"needs-attention",
|
|
111
|
+
"needs_attention"
|
|
112
|
+
]);
|
|
102
113
|
|
|
103
114
|
// packages/cli/src/commands/_preflight.ts
|
|
104
115
|
async function runProjectMainSyncPreflight(context, options) {
|