@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.
@@ -121,6 +121,15 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
121
121
 
122
122
  // packages/cli/src/commands/_server-client.ts
123
123
  var scopedGitHubBearerTokens = new Map;
124
+ var serverPhaseListener = null;
125
+ function setServerPhaseListener(listener) {
126
+ const previous = serverPhaseListener;
127
+ serverPhaseListener = listener;
128
+ return previous;
129
+ }
130
+ function reportServerPhase(label) {
131
+ serverPhaseListener?.(label);
132
+ }
124
133
  function cleanToken(value) {
125
134
  const trimmed = value?.trim();
126
135
  return trimmed ? trimmed : null;
@@ -167,6 +176,7 @@ async function ensureServerForCli(projectRoot) {
167
176
  try {
168
177
  const selected = resolveSelectedConnection(projectRoot);
169
178
  if (selected?.connection.kind === "remote") {
179
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
170
180
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
171
181
  const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
172
182
  return {
@@ -176,6 +186,7 @@ async function ensureServerForCli(projectRoot) {
176
186
  serverProjectRoot
177
187
  };
178
188
  }
189
+ reportServerPhase("Starting local Rig server\u2026");
179
190
  const connection = await ensureLocalRigServerConnection(projectRoot);
180
191
  return {
181
192
  baseUrl: connection.baseUrl,
@@ -292,6 +303,7 @@ async function requestServerJson(context, pathname, init = {}) {
292
303
  const headers = mergeHeaders(init.headers, server.authToken);
293
304
  if (server.serverProjectRoot)
294
305
  headers.set("x-rig-project-root", server.serverProjectRoot);
306
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
295
307
  let response;
296
308
  try {
297
309
  response = await fetch(`${server.baseUrl}${pathname}`, {
@@ -469,6 +481,38 @@ async function updateWorkspaceTaskViaServer(context, input) {
469
481
  });
470
482
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
471
483
  }
484
+ var RESUMABLE_RUN_STATUSES = new Set([
485
+ "created",
486
+ "preparing",
487
+ "running",
488
+ "validating",
489
+ "reviewing",
490
+ "stopped",
491
+ "failed",
492
+ "needs-attention",
493
+ "needs_attention"
494
+ ]);
495
+ async function resumeRunViaServer(context, runId, options) {
496
+ let targetRunId = runId?.trim() || null;
497
+ if (!targetRunId) {
498
+ const candidates = (await listRunsViaServer(context)).filter((run) => RESUMABLE_RUN_STATUSES.has(String(run.status ?? ""))).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")));
499
+ targetRunId = typeof candidates[0]?.runId === "string" ? candidates[0].runId : null;
500
+ }
501
+ if (!targetRunId) {
502
+ throw new CliError(options.restart ? "No run is available to restart." : "No resumable run is available.", 2, { hint: "List runs with `rig run list`, then pass an explicit id: `rig run resume <run-id>`." });
503
+ }
504
+ const payload = await requestServerJson(context, "/api/runs/resume", {
505
+ method: "POST",
506
+ headers: { "content-type": "application/json" },
507
+ body: JSON.stringify({ runId: targetRunId, createdAt: new Date().toISOString(), restart: options.restart })
508
+ });
509
+ const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
510
+ if (record.ok === false) {
511
+ const message = typeof record.error === "string" && record.error.trim() ? record.error : "run resume failed";
512
+ throw new CliError(`${options.restart ? "restart" : "resume"} failed for ${targetRunId}: ${message}`, 1);
513
+ }
514
+ return { ok: true, runId: targetRunId, ...record };
515
+ }
472
516
  async function stopRunViaServer(context, runId) {
473
517
  const payload = await requestServerJson(context, "/api/runs/stop", {
474
518
  method: "POST",
@@ -588,11 +632,13 @@ export {
588
632
  submitTaskRunViaServer,
589
633
  stopRunViaServer,
590
634
  steerRunViaServer,
635
+ setServerPhaseListener,
591
636
  setGitHubBearerTokenForCurrentProcess,
592
637
  sendRunPiShellViaServer,
593
638
  sendRunPiPromptViaServer,
594
639
  selectNextWorkspaceTaskViaServer,
595
640
  runRunPiCommandViaServer,
641
+ resumeRunViaServer,
596
642
  respondRunPiExtensionUiViaServer,
597
643
  requestServerJson,
598
644
  registerProjectViaServer,
@@ -125,6 +125,10 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
125
125
 
126
126
  // packages/cli/src/commands/_server-client.ts
127
127
  var scopedGitHubBearerTokens = new Map;
128
+ var serverPhaseListener = null;
129
+ function reportServerPhase(label) {
130
+ serverPhaseListener?.(label);
131
+ }
128
132
  function cleanToken(value) {
129
133
  const trimmed = value?.trim();
130
134
  return trimmed ? trimmed : null;
@@ -167,6 +171,7 @@ async function ensureServerForCli(projectRoot) {
167
171
  try {
168
172
  const selected = resolveSelectedConnection(projectRoot);
169
173
  if (selected?.connection.kind === "remote") {
174
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
170
175
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
171
176
  const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
172
177
  return {
@@ -176,6 +181,7 @@ async function ensureServerForCli(projectRoot) {
176
181
  serverProjectRoot
177
182
  };
178
183
  }
184
+ reportServerPhase("Starting local Rig server\u2026");
179
185
  const connection = await ensureLocalRigServerConnection(projectRoot);
180
186
  return {
181
187
  baseUrl: connection.baseUrl,
@@ -282,6 +288,7 @@ async function requestServerJson(context, pathname, init = {}) {
282
288
  const headers = mergeHeaders(init.headers, server.authToken);
283
289
  if (server.serverProjectRoot)
284
290
  headers.set("x-rig-project-root", server.serverProjectRoot);
291
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
285
292
  let response;
286
293
  try {
287
294
  response = await fetch(`${server.baseUrl}${pathname}`, {
@@ -310,6 +317,17 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
310
317
  }
311
318
  return payload;
312
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
+ ]);
313
331
 
314
332
  // packages/cli/src/commands/_snapshot-upload.ts
315
333
  var UPLOADED_SNAPSHOT_PR_MARKER = "<!-- rig:uploaded-snapshot -->";
@@ -4,6 +4,7 @@ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834"
4
4
  function createTtySpinner(input) {
5
5
  const output = input.output ?? process.stdout;
6
6
  const isTty = output.isTTY === true;
7
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
7
8
  let label = input.label;
8
9
  let frame = 0;
9
10
  let paused = false;
@@ -20,8 +21,9 @@ function createTtySpinner(input) {
20
21
  }
21
22
  return;
22
23
  }
23
- frame = (frame + 1) % SPINNER_FRAMES.length;
24
- output.write(`\r\x1B[2K${SPINNER_FRAMES[frame]} ${label}`);
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}`);
25
27
  };
26
28
  const clearLine = () => {
27
29
  if (isTty)
@@ -229,6 +229,17 @@ import { ensureProjectMainFreshBeforeRun } from "@rig/runtime/control-plane/proj
229
229
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
230
230
  var scopedGitHubBearerTokens = new Map;
231
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
+ ]);
232
243
 
233
244
  // packages/cli/src/commands/_preflight.ts
234
245
  async function runProjectMainSyncPreflight(context, options) {
@@ -130,6 +130,15 @@ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
130
130
  import { resolve as resolve2 } from "path";
131
131
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
132
132
  var scopedGitHubBearerTokens = new Map;
133
+ var serverPhaseListener = null;
134
+ function setServerPhaseListener(listener) {
135
+ const previous = serverPhaseListener;
136
+ serverPhaseListener = listener;
137
+ return previous;
138
+ }
139
+ function reportServerPhase(label) {
140
+ serverPhaseListener?.(label);
141
+ }
133
142
  function cleanToken(value) {
134
143
  const trimmed = value?.trim();
135
144
  return trimmed ? trimmed : null;
@@ -172,6 +181,7 @@ async function ensureServerForCli(projectRoot) {
172
181
  try {
173
182
  const selected = resolveSelectedConnection(projectRoot);
174
183
  if (selected?.connection.kind === "remote") {
184
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
175
185
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
176
186
  const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
177
187
  return {
@@ -181,6 +191,7 @@ async function ensureServerForCli(projectRoot) {
181
191
  serverProjectRoot
182
192
  };
183
193
  }
194
+ reportServerPhase("Starting local Rig server\u2026");
184
195
  const connection = await ensureLocalRigServerConnection(projectRoot);
185
196
  return {
186
197
  baseUrl: connection.baseUrl,
@@ -287,6 +298,7 @@ async function requestServerJson(context, pathname, init = {}) {
287
298
  const headers = mergeHeaders(init.headers, server.authToken);
288
299
  if (server.serverProjectRoot)
289
300
  headers.set("x-rig-project-root", server.serverProjectRoot);
301
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
290
302
  let response;
291
303
  try {
292
304
  response = await fetch(`${server.baseUrl}${pathname}`, {
@@ -315,6 +327,17 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
315
327
  }
316
328
  return payload;
317
329
  }
330
+ var RESUMABLE_RUN_STATUSES = new Set([
331
+ "created",
332
+ "preparing",
333
+ "running",
334
+ "validating",
335
+ "reviewing",
336
+ "stopped",
337
+ "failed",
338
+ "needs-attention",
339
+ "needs_attention"
340
+ ]);
318
341
 
319
342
  // packages/cli/src/commands/_parsers.ts
320
343
  async function loadRigConfigOrNull(projectRoot) {
@@ -518,7 +541,10 @@ async function runRigDoctorChecks(options) {
518
541
  const bunVersion = options.bunVersion ?? Bun.version;
519
542
  const request = options.requestJson ?? ((pathname, init) => requestServerJson({ projectRoot }, pathname, init));
520
543
  const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
544
+ const progress = options.onProgress ?? (() => {});
545
+ progress("Checking local toolchain\u2026");
521
546
  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`)."));
547
+ progress("Loading rig.config\u2026");
522
548
  const loadedConfig = await loadConfig(projectRoot).catch(() => null);
523
549
  const config = loadedConfig ?? loadFallbackConfig(projectRoot);
524
550
  const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve4(projectRoot, name)));
@@ -537,6 +563,7 @@ async function runRigDoctorChecks(options) {
537
563
  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));
538
564
  let server = null;
539
565
  try {
566
+ progress("Connecting to the selected Rig server\u2026");
540
567
  server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
541
568
  checks.push(check("server", "Rig server reachable", "pass", `${server.connectionKind} ${server.baseUrl}`));
542
569
  } catch (error) {
@@ -544,18 +571,21 @@ async function runRigDoctorChecks(options) {
544
571
  }
545
572
  if (server || options.requestJson) {
546
573
  try {
574
+ progress("Checking server status\u2026");
547
575
  const status = await request("/api/server/status");
548
576
  checks.push(check("server-status", "server project status", "pass", JSON.stringify(status).slice(0, 180)));
549
577
  } catch (error) {
550
578
  checks.push(check("server-status", "server project status", "fail", errorMessage(error), "Run `rig doctor` after the selected server is reachable."));
551
579
  }
552
580
  try {
581
+ progress("Checking GitHub auth\u2026");
553
582
  const auth = await request("/api/github/auth/status");
554
583
  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>`."));
555
584
  } catch (error) {
556
585
  checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
557
586
  }
558
587
  try {
588
+ progress("Checking GitHub repo permissions\u2026");
559
589
  const permissions = await request("/api/github/repo/permissions");
560
590
  const allowed = permissionAllowsPr(permissions);
561
591
  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."));
@@ -563,6 +593,7 @@ async function runRigDoctorChecks(options) {
563
593
  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."));
564
594
  }
565
595
  try {
596
+ progress("Checking GitHub issue labels\u2026");
566
597
  const labels = await request("/api/workspace/task-labels");
567
598
  const ready = labelsReady(labels);
568
599
  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."));
@@ -570,6 +601,7 @@ async function runRigDoctorChecks(options) {
570
601
  checks.push(check("task-labels", "GitHub issue labels", "warn", errorMessage(error), "Run `rig init`/`rig doctor` after label setup is wired on the server."));
571
602
  }
572
603
  try {
604
+ progress("Checking task projection\u2026");
573
605
  const projection = await request("/api/workspace/task-projection");
574
606
  checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
575
607
  } catch (error) {
@@ -578,6 +610,7 @@ async function runRigDoctorChecks(options) {
578
610
  const slug = projectStatusSlug(projectRoot, config);
579
611
  if (slug) {
580
612
  try {
613
+ progress("Checking server project checkout\u2026");
581
614
  const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
582
615
  checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
583
616
  } catch (error) {
@@ -592,6 +625,7 @@ async function runRigDoctorChecks(options) {
592
625
  }
593
626
  checks.push(githubProjectsCheck(config));
594
627
  checks.push(prMergeCheck(config));
628
+ progress("Checking Pi installation\u2026");
595
629
  const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
596
630
  ok: false,
597
631
  label: "pi/pi-rig checks",
@@ -615,10 +649,131 @@ function countDoctorFailures(checks) {
615
649
  return checks.filter((entry) => entry.status === "fail").length;
616
650
  }
617
651
 
652
+ // packages/cli/src/commands/_async-ui.ts
653
+ import pc from "picocolors";
654
+
655
+ // packages/cli/src/commands/_spinner.ts
656
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
657
+ function createTtySpinner(input) {
658
+ const output = input.output ?? process.stdout;
659
+ const isTty = output.isTTY === true;
660
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
661
+ let label = input.label;
662
+ let frame = 0;
663
+ let paused = false;
664
+ let stopped = false;
665
+ let lastPrintedLabel = "";
666
+ const render = () => {
667
+ if (stopped || paused)
668
+ return;
669
+ if (!isTty) {
670
+ if (label !== lastPrintedLabel) {
671
+ output.write(`${label}
672
+ `);
673
+ lastPrintedLabel = label;
674
+ }
675
+ return;
676
+ }
677
+ frame = (frame + 1) % frames.length;
678
+ const glyph = frames[frame] ?? frames[0] ?? "";
679
+ output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
680
+ };
681
+ const clearLine = () => {
682
+ if (isTty)
683
+ output.write("\r\x1B[2K");
684
+ };
685
+ render();
686
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
687
+ return {
688
+ setLabel(next) {
689
+ label = next;
690
+ render();
691
+ },
692
+ pause() {
693
+ paused = true;
694
+ clearLine();
695
+ },
696
+ resume() {
697
+ if (stopped)
698
+ return;
699
+ paused = false;
700
+ render();
701
+ },
702
+ stop(finalLine) {
703
+ if (stopped)
704
+ return;
705
+ stopped = true;
706
+ if (timer)
707
+ clearInterval(timer);
708
+ clearLine();
709
+ if (finalLine)
710
+ output.write(`${finalLine}
711
+ `);
712
+ }
713
+ };
714
+ }
715
+
716
+ // packages/cli/src/commands/_async-ui.ts
717
+ var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
718
+ var DONE_SYMBOL = pc.green("\u25C7");
719
+ var FAIL_SYMBOL = pc.red("\u25A0");
720
+ var activeUpdate = null;
721
+ async function withSpinner(label, work, options = {}) {
722
+ if (options.outputMode === "json") {
723
+ return work(() => {});
724
+ }
725
+ if (activeUpdate) {
726
+ const outer = activeUpdate;
727
+ outer(label);
728
+ return work(outer);
729
+ }
730
+ const output = options.output ?? process.stderr;
731
+ const isTty = output.isTTY === true;
732
+ let lastLabel = label;
733
+ if (!isTty) {
734
+ output.write(`${label}
735
+ `);
736
+ const update2 = (next) => {
737
+ lastLabel = next;
738
+ };
739
+ activeUpdate = update2;
740
+ const previousListener2 = setServerPhaseListener(update2);
741
+ try {
742
+ return await work(update2);
743
+ } finally {
744
+ activeUpdate = null;
745
+ setServerPhaseListener(previousListener2);
746
+ }
747
+ }
748
+ const spinner = createTtySpinner({
749
+ label,
750
+ output,
751
+ frames: CLACK_SPINNER_FRAMES,
752
+ styleFrame: (frame) => pc.magenta(frame)
753
+ });
754
+ const update = (next) => {
755
+ lastLabel = next;
756
+ spinner.setLabel(next);
757
+ };
758
+ activeUpdate = update;
759
+ const previousListener = setServerPhaseListener(update);
760
+ try {
761
+ const result = await work(update);
762
+ spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
763
+ return result;
764
+ } catch (error) {
765
+ spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
766
+ throw error;
767
+ } finally {
768
+ activeUpdate = null;
769
+ setServerPhaseListener(previousListener);
770
+ }
771
+ }
772
+
618
773
  // packages/cli/src/commands/doctor.ts
619
774
  async function executeDoctor(context, args) {
620
775
  requireNoExtraArgs(args, "rig doctor");
621
- const checks = await runRigDoctorChecks({ projectRoot: context.projectRoot });
776
+ const checks = await withSpinner("Running doctor checks\u2026", (update) => runRigDoctorChecks({ projectRoot: context.projectRoot, onProgress: update }), { outputMode: context.outputMode });
622
777
  if (context.outputMode === "text") {
623
778
  console.log(formatDoctorChecks(checks));
624
779
  }
@@ -142,6 +142,15 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
142
142
 
143
143
  // packages/cli/src/commands/_server-client.ts
144
144
  var scopedGitHubBearerTokens = new Map;
145
+ var serverPhaseListener = null;
146
+ function setServerPhaseListener(listener) {
147
+ const previous = serverPhaseListener;
148
+ serverPhaseListener = listener;
149
+ return previous;
150
+ }
151
+ function reportServerPhase(label) {
152
+ serverPhaseListener?.(label);
153
+ }
145
154
  function cleanToken(value) {
146
155
  const trimmed = value?.trim();
147
156
  return trimmed ? trimmed : null;
@@ -184,6 +193,7 @@ async function ensureServerForCli(projectRoot) {
184
193
  try {
185
194
  const selected = resolveSelectedConnection(projectRoot);
186
195
  if (selected?.connection.kind === "remote") {
196
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
187
197
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
188
198
  const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
189
199
  return {
@@ -193,6 +203,7 @@ async function ensureServerForCli(projectRoot) {
193
203
  serverProjectRoot
194
204
  };
195
205
  }
206
+ reportServerPhase("Starting local Rig server\u2026");
196
207
  const connection = await ensureLocalRigServerConnection(projectRoot);
197
208
  return {
198
209
  baseUrl: connection.baseUrl,
@@ -299,6 +310,7 @@ async function requestServerJson(context, pathname, init = {}) {
299
310
  const headers = mergeHeaders(init.headers, server.authToken);
300
311
  if (server.serverProjectRoot)
301
312
  headers.set("x-rig-project-root", server.serverProjectRoot);
313
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
302
314
  let response;
303
315
  try {
304
316
  response = await fetch(`${server.baseUrl}${pathname}`, {
@@ -339,6 +351,138 @@ async function postGitHubTokenViaServer(context, token, options = {}) {
339
351
  });
340
352
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
341
353
  }
354
+ var RESUMABLE_RUN_STATUSES = new Set([
355
+ "created",
356
+ "preparing",
357
+ "running",
358
+ "validating",
359
+ "reviewing",
360
+ "stopped",
361
+ "failed",
362
+ "needs-attention",
363
+ "needs_attention"
364
+ ]);
365
+
366
+ // packages/cli/src/commands/_async-ui.ts
367
+ import pc from "picocolors";
368
+
369
+ // packages/cli/src/commands/_spinner.ts
370
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
371
+ function createTtySpinner(input) {
372
+ const output = input.output ?? process.stdout;
373
+ const isTty = output.isTTY === true;
374
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
375
+ let label = input.label;
376
+ let frame = 0;
377
+ let paused = false;
378
+ let stopped = false;
379
+ let lastPrintedLabel = "";
380
+ const render = () => {
381
+ if (stopped || paused)
382
+ return;
383
+ if (!isTty) {
384
+ if (label !== lastPrintedLabel) {
385
+ output.write(`${label}
386
+ `);
387
+ lastPrintedLabel = label;
388
+ }
389
+ return;
390
+ }
391
+ frame = (frame + 1) % frames.length;
392
+ const glyph = frames[frame] ?? frames[0] ?? "";
393
+ output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
394
+ };
395
+ const clearLine = () => {
396
+ if (isTty)
397
+ output.write("\r\x1B[2K");
398
+ };
399
+ render();
400
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
401
+ return {
402
+ setLabel(next) {
403
+ label = next;
404
+ render();
405
+ },
406
+ pause() {
407
+ paused = true;
408
+ clearLine();
409
+ },
410
+ resume() {
411
+ if (stopped)
412
+ return;
413
+ paused = false;
414
+ render();
415
+ },
416
+ stop(finalLine) {
417
+ if (stopped)
418
+ return;
419
+ stopped = true;
420
+ if (timer)
421
+ clearInterval(timer);
422
+ clearLine();
423
+ if (finalLine)
424
+ output.write(`${finalLine}
425
+ `);
426
+ }
427
+ };
428
+ }
429
+
430
+ // packages/cli/src/commands/_async-ui.ts
431
+ var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
432
+ var DONE_SYMBOL = pc.green("\u25C7");
433
+ var FAIL_SYMBOL = pc.red("\u25A0");
434
+ var activeUpdate = null;
435
+ async function withSpinner(label, work, options = {}) {
436
+ if (options.outputMode === "json") {
437
+ return work(() => {});
438
+ }
439
+ if (activeUpdate) {
440
+ const outer = activeUpdate;
441
+ outer(label);
442
+ return work(outer);
443
+ }
444
+ const output = options.output ?? process.stderr;
445
+ const isTty = output.isTTY === true;
446
+ let lastLabel = label;
447
+ if (!isTty) {
448
+ output.write(`${label}
449
+ `);
450
+ const update2 = (next) => {
451
+ lastLabel = next;
452
+ };
453
+ activeUpdate = update2;
454
+ const previousListener2 = setServerPhaseListener(update2);
455
+ try {
456
+ return await work(update2);
457
+ } finally {
458
+ activeUpdate = null;
459
+ setServerPhaseListener(previousListener2);
460
+ }
461
+ }
462
+ const spinner = createTtySpinner({
463
+ label,
464
+ output,
465
+ frames: CLACK_SPINNER_FRAMES,
466
+ styleFrame: (frame) => pc.magenta(frame)
467
+ });
468
+ const update = (next) => {
469
+ lastLabel = next;
470
+ spinner.setLabel(next);
471
+ };
472
+ activeUpdate = update;
473
+ const previousListener = setServerPhaseListener(update);
474
+ try {
475
+ const result = await work(update);
476
+ spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
477
+ return result;
478
+ } catch (error) {
479
+ spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
480
+ throw error;
481
+ } finally {
482
+ activeUpdate = null;
483
+ setServerPhaseListener(previousListener);
484
+ }
485
+ }
342
486
 
343
487
  // packages/cli/src/commands/github.ts
344
488
  function printPayload(context, payload, fallback) {
@@ -367,7 +511,7 @@ async function executeGithub(context, args) {
367
511
  case "status": {
368
512
  if (rest.length > 0)
369
513
  throw new CliError("Usage: rig github auth status", 1);
370
- const status = await getGitHubAuthStatusViaServer(context);
514
+ const status = await withSpinner("Checking GitHub auth on the server\u2026", () => getGitHubAuthStatusViaServer(context), { outputMode: context.outputMode });
371
515
  printPayload(context, status, `GitHub auth: ${status.authenticated ? "authenticated" : "unauthenticated"}`);
372
516
  return { ok: true, group: "github", command: "auth status", details: status };
373
517
  }
@@ -378,14 +522,15 @@ async function executeGithub(context, args) {
378
522
  const token = parsed.value?.trim();
379
523
  if (!token)
380
524
  throw new CliError("Missing --token value.", 1, { hint: "Re-run as `rig github auth token --token <token>`." });
381
- const result = await postGitHubTokenViaServer(context, token);
525
+ const result = await withSpinner("Storing GitHub token on the server\u2026", () => postGitHubTokenViaServer(context, token), { outputMode: context.outputMode });
382
526
  printPayload(context, result, "GitHub token stored on the selected Rig server.");
383
527
  return { ok: true, group: "github", command: "auth token", details: result };
384
528
  }
385
529
  case "import-gh": {
386
530
  if (rest.length > 0)
387
531
  throw new CliError("Usage: rig github auth import-gh", 1);
388
- const result = await postGitHubTokenViaServer(context, readGhToken());
532
+ const importedToken = readGhToken();
533
+ const result = await withSpinner("Storing GitHub token on the server\u2026", () => postGitHubTokenViaServer(context, importedToken), { outputMode: context.outputMode });
389
534
  printPayload(context, result, "GitHub token imported from gh and stored on the selected Rig server.");
390
535
  return { ok: true, group: "github", command: "auth import-gh", details: result };
391
536
  }