@h-rig/cli 0.0.6-alpha.66 → 0.0.6-alpha.68
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 +250 -166
- package/dist/src/commands/_async-ui.js +141 -0
- package/dist/src/commands/_connection-state.js +5 -1
- package/dist/src/commands/_doctor-checks.js +18 -0
- package/dist/src/commands/_operator-view.js +143 -4
- package/dist/src/commands/_pi-frontend.js +13 -1
- package/dist/src/commands/_preflight.js +7 -0
- package/dist/src/commands/_server-client.js +13 -0
- package/dist/src/commands/_snapshot-upload.js +7 -0
- package/dist/src/commands/_spinner.js +4 -2
- package/dist/src/commands/doctor.js +145 -1
- package/dist/src/commands/github.js +137 -3
- package/dist/src/commands/inbox.js +139 -6
- package/dist/src/commands/init.js +18 -0
- package/dist/src/commands/inspect.js +147 -8
- package/dist/src/commands/run.js +190 -38
- package/dist/src/commands/server.js +7 -0
- package/dist/src/commands/setup.js +150 -6
- package/dist/src/commands/stats.js +448 -110
- package/dist/src/commands/task-run-driver.js +7 -0
- package/dist/src/commands/task.js +186 -47
- package/dist/src/commands.js +250 -166
- package/dist/src/index.js +250 -166
- package/package.json +8 -8
|
@@ -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}`, {
|
|
@@ -842,7 +849,10 @@ async function runRigDoctorChecks(options) {
|
|
|
842
849
|
const bunVersion = options.bunVersion ?? Bun.version;
|
|
843
850
|
const request = options.requestJson ?? ((pathname, init) => requestServerJson({ projectRoot }, pathname, init));
|
|
844
851
|
const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
|
|
852
|
+
const progress = options.onProgress ?? (() => {});
|
|
853
|
+
progress("Checking local toolchain\u2026");
|
|
845
854
|
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`)."));
|
|
855
|
+
progress("Loading rig.config\u2026");
|
|
846
856
|
const loadedConfig = await loadConfig(projectRoot).catch(() => null);
|
|
847
857
|
const config = loadedConfig ?? loadFallbackConfig(projectRoot);
|
|
848
858
|
const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve5(projectRoot, name)));
|
|
@@ -861,6 +871,7 @@ async function runRigDoctorChecks(options) {
|
|
|
861
871
|
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
872
|
let server = null;
|
|
863
873
|
try {
|
|
874
|
+
progress("Connecting to the selected Rig server\u2026");
|
|
864
875
|
server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
|
|
865
876
|
checks.push(check("server", "Rig server reachable", "pass", `${server.connectionKind} ${server.baseUrl}`));
|
|
866
877
|
} catch (error) {
|
|
@@ -868,18 +879,21 @@ async function runRigDoctorChecks(options) {
|
|
|
868
879
|
}
|
|
869
880
|
if (server || options.requestJson) {
|
|
870
881
|
try {
|
|
882
|
+
progress("Checking server status\u2026");
|
|
871
883
|
const status = await request("/api/server/status");
|
|
872
884
|
checks.push(check("server-status", "server project status", "pass", JSON.stringify(status).slice(0, 180)));
|
|
873
885
|
} catch (error) {
|
|
874
886
|
checks.push(check("server-status", "server project status", "fail", errorMessage(error), "Run `rig doctor` after the selected server is reachable."));
|
|
875
887
|
}
|
|
876
888
|
try {
|
|
889
|
+
progress("Checking GitHub auth\u2026");
|
|
877
890
|
const auth = await request("/api/github/auth/status");
|
|
878
891
|
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
892
|
} catch (error) {
|
|
880
893
|
checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
|
|
881
894
|
}
|
|
882
895
|
try {
|
|
896
|
+
progress("Checking GitHub repo permissions\u2026");
|
|
883
897
|
const permissions = await request("/api/github/repo/permissions");
|
|
884
898
|
const allowed = permissionAllowsPr(permissions);
|
|
885
899
|
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 +901,7 @@ async function runRigDoctorChecks(options) {
|
|
|
887
901
|
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
902
|
}
|
|
889
903
|
try {
|
|
904
|
+
progress("Checking GitHub issue labels\u2026");
|
|
890
905
|
const labels = await request("/api/workspace/task-labels");
|
|
891
906
|
const ready = labelsReady(labels);
|
|
892
907
|
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 +909,7 @@ async function runRigDoctorChecks(options) {
|
|
|
894
909
|
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
910
|
}
|
|
896
911
|
try {
|
|
912
|
+
progress("Checking task projection\u2026");
|
|
897
913
|
const projection = await request("/api/workspace/task-projection");
|
|
898
914
|
checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
|
|
899
915
|
} catch (error) {
|
|
@@ -902,6 +918,7 @@ async function runRigDoctorChecks(options) {
|
|
|
902
918
|
const slug = projectStatusSlug(projectRoot, config);
|
|
903
919
|
if (slug) {
|
|
904
920
|
try {
|
|
921
|
+
progress("Checking server project checkout\u2026");
|
|
905
922
|
const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
|
|
906
923
|
checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
|
|
907
924
|
} catch (error) {
|
|
@@ -916,6 +933,7 @@ async function runRigDoctorChecks(options) {
|
|
|
916
933
|
}
|
|
917
934
|
checks.push(githubProjectsCheck(config));
|
|
918
935
|
checks.push(prMergeCheck(config));
|
|
936
|
+
progress("Checking Pi installation\u2026");
|
|
919
937
|
const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
|
|
920
938
|
ok: false,
|
|
921
939
|
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}`, {
|
|
@@ -370,6 +382,127 @@ async function getRunLogsViaServer(context, runId, options = {}) {
|
|
|
370
382
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
371
383
|
}
|
|
372
384
|
|
|
385
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
386
|
+
import pc from "picocolors";
|
|
387
|
+
|
|
388
|
+
// packages/cli/src/commands/_spinner.ts
|
|
389
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
390
|
+
function createTtySpinner(input) {
|
|
391
|
+
const output = input.output ?? process.stdout;
|
|
392
|
+
const isTty = output.isTTY === true;
|
|
393
|
+
const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
|
|
394
|
+
let label = input.label;
|
|
395
|
+
let frame = 0;
|
|
396
|
+
let paused = false;
|
|
397
|
+
let stopped = false;
|
|
398
|
+
let lastPrintedLabel = "";
|
|
399
|
+
const render = () => {
|
|
400
|
+
if (stopped || paused)
|
|
401
|
+
return;
|
|
402
|
+
if (!isTty) {
|
|
403
|
+
if (label !== lastPrintedLabel) {
|
|
404
|
+
output.write(`${label}
|
|
405
|
+
`);
|
|
406
|
+
lastPrintedLabel = label;
|
|
407
|
+
}
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
frame = (frame + 1) % frames.length;
|
|
411
|
+
const glyph = frames[frame] ?? frames[0] ?? "";
|
|
412
|
+
output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
|
|
413
|
+
};
|
|
414
|
+
const clearLine = () => {
|
|
415
|
+
if (isTty)
|
|
416
|
+
output.write("\r\x1B[2K");
|
|
417
|
+
};
|
|
418
|
+
render();
|
|
419
|
+
const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
|
|
420
|
+
return {
|
|
421
|
+
setLabel(next) {
|
|
422
|
+
label = next;
|
|
423
|
+
render();
|
|
424
|
+
},
|
|
425
|
+
pause() {
|
|
426
|
+
paused = true;
|
|
427
|
+
clearLine();
|
|
428
|
+
},
|
|
429
|
+
resume() {
|
|
430
|
+
if (stopped)
|
|
431
|
+
return;
|
|
432
|
+
paused = false;
|
|
433
|
+
render();
|
|
434
|
+
},
|
|
435
|
+
stop(finalLine) {
|
|
436
|
+
if (stopped)
|
|
437
|
+
return;
|
|
438
|
+
stopped = true;
|
|
439
|
+
if (timer)
|
|
440
|
+
clearInterval(timer);
|
|
441
|
+
clearLine();
|
|
442
|
+
if (finalLine)
|
|
443
|
+
output.write(`${finalLine}
|
|
444
|
+
`);
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
450
|
+
var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
451
|
+
var DONE_SYMBOL = pc.green("\u25C7");
|
|
452
|
+
var FAIL_SYMBOL = pc.red("\u25A0");
|
|
453
|
+
var activeUpdate = null;
|
|
454
|
+
async function withSpinner(label, work, options = {}) {
|
|
455
|
+
if (options.outputMode === "json") {
|
|
456
|
+
return work(() => {});
|
|
457
|
+
}
|
|
458
|
+
if (activeUpdate) {
|
|
459
|
+
const outer = activeUpdate;
|
|
460
|
+
outer(label);
|
|
461
|
+
return work(outer);
|
|
462
|
+
}
|
|
463
|
+
const output = options.output ?? process.stderr;
|
|
464
|
+
const isTty = output.isTTY === true;
|
|
465
|
+
let lastLabel = label;
|
|
466
|
+
if (!isTty) {
|
|
467
|
+
output.write(`${label}
|
|
468
|
+
`);
|
|
469
|
+
const update2 = (next) => {
|
|
470
|
+
lastLabel = next;
|
|
471
|
+
};
|
|
472
|
+
activeUpdate = update2;
|
|
473
|
+
const previousListener2 = setServerPhaseListener(update2);
|
|
474
|
+
try {
|
|
475
|
+
return await work(update2);
|
|
476
|
+
} finally {
|
|
477
|
+
activeUpdate = null;
|
|
478
|
+
setServerPhaseListener(previousListener2);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const spinner = createTtySpinner({
|
|
482
|
+
label,
|
|
483
|
+
output,
|
|
484
|
+
frames: CLACK_SPINNER_FRAMES,
|
|
485
|
+
styleFrame: (frame) => pc.magenta(frame)
|
|
486
|
+
});
|
|
487
|
+
const update = (next) => {
|
|
488
|
+
lastLabel = next;
|
|
489
|
+
spinner.setLabel(next);
|
|
490
|
+
};
|
|
491
|
+
activeUpdate = update;
|
|
492
|
+
const previousListener = setServerPhaseListener(update);
|
|
493
|
+
try {
|
|
494
|
+
const result = await work(update);
|
|
495
|
+
spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
|
|
496
|
+
return result;
|
|
497
|
+
} catch (error) {
|
|
498
|
+
spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
|
|
499
|
+
throw error;
|
|
500
|
+
} finally {
|
|
501
|
+
activeUpdate = null;
|
|
502
|
+
setServerPhaseListener(previousListener);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
373
506
|
// packages/cli/src/commands/inspect.ts
|
|
374
507
|
async function executeInspect(context, args) {
|
|
375
508
|
const [command = "failures", ...rest] = args;
|
|
@@ -380,13 +513,19 @@ async function executeInspect(context, args) {
|
|
|
380
513
|
const requiredTask = requireTask(task, "rig inspect logs --task <task-id>");
|
|
381
514
|
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
515
|
if (!latestRun) {
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
516
|
+
const fallback = await withSpinner(`Finding runs for ${requiredTask} on the server\u2026`, async (update) => {
|
|
517
|
+
const serverRuns = await listRunsViaServer(context, { limit: 200 }).catch(() => []);
|
|
518
|
+
const serverRun = serverRuns.filter((run) => String(run.taskId ?? "") === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
|
|
519
|
+
if (!serverRun || typeof serverRun.runId !== "string")
|
|
520
|
+
return null;
|
|
521
|
+
update(`Reading logs for run ${serverRun.runId}\u2026`);
|
|
522
|
+
const page = await getRunLogsViaServer(context, serverRun.runId, { limit: 500 });
|
|
523
|
+
return { runId: serverRun.runId, page };
|
|
524
|
+
}, { outputMode: context.outputMode });
|
|
525
|
+
if (!fallback) {
|
|
386
526
|
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
527
|
}
|
|
388
|
-
const
|
|
389
|
-
const entries = Array.isArray(page.entries) ? page.entries : [];
|
|
528
|
+
const entries = Array.isArray(fallback.page.entries) ? fallback.page.entries : [];
|
|
390
529
|
if (context.outputMode === "text") {
|
|
391
530
|
for (const entry of entries) {
|
|
392
531
|
const record = entry && typeof entry === "object" ? entry : {};
|
|
@@ -395,9 +534,9 @@ async function executeInspect(context, args) {
|
|
|
395
534
|
console.log([title, detail].filter(Boolean).join(" \u2014 "));
|
|
396
535
|
}
|
|
397
536
|
if (entries.length === 0)
|
|
398
|
-
console.log(`(no log entries for run ${
|
|
537
|
+
console.log(`(no log entries for run ${fallback.runId})`);
|
|
399
538
|
}
|
|
400
|
-
return { ok: true, group: "inspect", command, details: { task: requiredTask, runId:
|
|
539
|
+
return { ok: true, group: "inspect", command, details: { task: requiredTask, runId: fallback.runId, source: "server", entries } };
|
|
401
540
|
}
|
|
402
541
|
const logsPath = resolve3(resolveAuthorityRunDir(context.projectRoot, latestRun.runId), "logs.jsonl");
|
|
403
542
|
if (!existsSync3(logsPath)) {
|
|
@@ -455,7 +594,7 @@ async function executeInspect(context, args) {
|
|
|
455
594
|
const { value: task, rest: remaining } = takeOption(rest, "--task");
|
|
456
595
|
requireNoExtraArgs(remaining, "rig inspect diff [--task <task-id>]");
|
|
457
596
|
if (task) {
|
|
458
|
-
const files = changedFilesForTask(context.projectRoot, task, false);
|
|
597
|
+
const files = await withSpinner(`Computing changed files for ${task}\u2026`, async () => changedFilesForTask(context.projectRoot, task, false), { outputMode: context.outputMode });
|
|
459
598
|
for (const file of files) {
|
|
460
599
|
console.log(file);
|
|
461
600
|
}
|