@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.
@@ -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 serverRuns = await listRunsViaServer(context, { limit: 200 }).catch(() => []);
384
- const serverRun = serverRuns.filter((run) => String(run.taskId ?? "") === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
385
- if (!serverRun || typeof serverRun.runId !== "string") {
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 page = await getRunLogsViaServer(context, serverRun.runId, { limit: 500 });
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 ${serverRun.runId})`);
548
+ console.log(`(no log entries for run ${fallback.runId})`);
399
549
  }
400
- return { ok: true, group: "inspect", command, details: { task: requiredTask, runId: serverRun.runId, source: "server", entries } };
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) {