@h-rig/cli 0.0.6-alpha.67 → 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.
@@ -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}`, {
@@ -518,7 +530,10 @@ async function runRigDoctorChecks(options) {
518
530
  const bunVersion = options.bunVersion ?? Bun.version;
519
531
  const request = options.requestJson ?? ((pathname, init) => requestServerJson({ projectRoot }, pathname, init));
520
532
  const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
533
+ const progress = options.onProgress ?? (() => {});
534
+ progress("Checking local toolchain\u2026");
521
535
  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`)."));
536
+ progress("Loading rig.config\u2026");
522
537
  const loadedConfig = await loadConfig(projectRoot).catch(() => null);
523
538
  const config = loadedConfig ?? loadFallbackConfig(projectRoot);
524
539
  const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve4(projectRoot, name)));
@@ -537,6 +552,7 @@ async function runRigDoctorChecks(options) {
537
552
  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
553
  let server = null;
539
554
  try {
555
+ progress("Connecting to the selected Rig server\u2026");
540
556
  server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
541
557
  checks.push(check("server", "Rig server reachable", "pass", `${server.connectionKind} ${server.baseUrl}`));
542
558
  } catch (error) {
@@ -544,18 +560,21 @@ async function runRigDoctorChecks(options) {
544
560
  }
545
561
  if (server || options.requestJson) {
546
562
  try {
563
+ progress("Checking server status\u2026");
547
564
  const status = await request("/api/server/status");
548
565
  checks.push(check("server-status", "server project status", "pass", JSON.stringify(status).slice(0, 180)));
549
566
  } catch (error) {
550
567
  checks.push(check("server-status", "server project status", "fail", errorMessage(error), "Run `rig doctor` after the selected server is reachable."));
551
568
  }
552
569
  try {
570
+ progress("Checking GitHub auth\u2026");
553
571
  const auth = await request("/api/github/auth/status");
554
572
  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
573
  } catch (error) {
556
574
  checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
557
575
  }
558
576
  try {
577
+ progress("Checking GitHub repo permissions\u2026");
559
578
  const permissions = await request("/api/github/repo/permissions");
560
579
  const allowed = permissionAllowsPr(permissions);
561
580
  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 +582,7 @@ async function runRigDoctorChecks(options) {
563
582
  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
583
  }
565
584
  try {
585
+ progress("Checking GitHub issue labels\u2026");
566
586
  const labels = await request("/api/workspace/task-labels");
567
587
  const ready = labelsReady(labels);
568
588
  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 +590,7 @@ async function runRigDoctorChecks(options) {
570
590
  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
591
  }
572
592
  try {
593
+ progress("Checking task projection\u2026");
573
594
  const projection = await request("/api/workspace/task-projection");
574
595
  checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
575
596
  } catch (error) {
@@ -578,6 +599,7 @@ async function runRigDoctorChecks(options) {
578
599
  const slug = projectStatusSlug(projectRoot, config);
579
600
  if (slug) {
580
601
  try {
602
+ progress("Checking server project checkout\u2026");
581
603
  const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
582
604
  checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
583
605
  } catch (error) {
@@ -592,6 +614,7 @@ async function runRigDoctorChecks(options) {
592
614
  }
593
615
  checks.push(githubProjectsCheck(config));
594
616
  checks.push(prMergeCheck(config));
617
+ progress("Checking Pi installation\u2026");
595
618
  const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
596
619
  ok: false,
597
620
  label: "pi/pi-rig checks",
@@ -615,10 +638,131 @@ function countDoctorFailures(checks) {
615
638
  return checks.filter((entry) => entry.status === "fail").length;
616
639
  }
617
640
 
641
+ // packages/cli/src/commands/_async-ui.ts
642
+ import pc from "picocolors";
643
+
644
+ // packages/cli/src/commands/_spinner.ts
645
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
646
+ function createTtySpinner(input) {
647
+ const output = input.output ?? process.stdout;
648
+ const isTty = output.isTTY === true;
649
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
650
+ let label = input.label;
651
+ let frame = 0;
652
+ let paused = false;
653
+ let stopped = false;
654
+ let lastPrintedLabel = "";
655
+ const render = () => {
656
+ if (stopped || paused)
657
+ return;
658
+ if (!isTty) {
659
+ if (label !== lastPrintedLabel) {
660
+ output.write(`${label}
661
+ `);
662
+ lastPrintedLabel = label;
663
+ }
664
+ return;
665
+ }
666
+ frame = (frame + 1) % frames.length;
667
+ const glyph = frames[frame] ?? frames[0] ?? "";
668
+ output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
669
+ };
670
+ const clearLine = () => {
671
+ if (isTty)
672
+ output.write("\r\x1B[2K");
673
+ };
674
+ render();
675
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
676
+ return {
677
+ setLabel(next) {
678
+ label = next;
679
+ render();
680
+ },
681
+ pause() {
682
+ paused = true;
683
+ clearLine();
684
+ },
685
+ resume() {
686
+ if (stopped)
687
+ return;
688
+ paused = false;
689
+ render();
690
+ },
691
+ stop(finalLine) {
692
+ if (stopped)
693
+ return;
694
+ stopped = true;
695
+ if (timer)
696
+ clearInterval(timer);
697
+ clearLine();
698
+ if (finalLine)
699
+ output.write(`${finalLine}
700
+ `);
701
+ }
702
+ };
703
+ }
704
+
705
+ // packages/cli/src/commands/_async-ui.ts
706
+ var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
707
+ var DONE_SYMBOL = pc.green("\u25C7");
708
+ var FAIL_SYMBOL = pc.red("\u25A0");
709
+ var activeUpdate = null;
710
+ async function withSpinner(label, work, options = {}) {
711
+ if (options.outputMode === "json") {
712
+ return work(() => {});
713
+ }
714
+ if (activeUpdate) {
715
+ const outer = activeUpdate;
716
+ outer(label);
717
+ return work(outer);
718
+ }
719
+ const output = options.output ?? process.stderr;
720
+ const isTty = output.isTTY === true;
721
+ let lastLabel = label;
722
+ if (!isTty) {
723
+ output.write(`${label}
724
+ `);
725
+ const update2 = (next) => {
726
+ lastLabel = next;
727
+ };
728
+ activeUpdate = update2;
729
+ const previousListener2 = setServerPhaseListener(update2);
730
+ try {
731
+ return await work(update2);
732
+ } finally {
733
+ activeUpdate = null;
734
+ setServerPhaseListener(previousListener2);
735
+ }
736
+ }
737
+ const spinner = createTtySpinner({
738
+ label,
739
+ output,
740
+ frames: CLACK_SPINNER_FRAMES,
741
+ styleFrame: (frame) => pc.magenta(frame)
742
+ });
743
+ const update = (next) => {
744
+ lastLabel = next;
745
+ spinner.setLabel(next);
746
+ };
747
+ activeUpdate = update;
748
+ const previousListener = setServerPhaseListener(update);
749
+ try {
750
+ const result = await work(update);
751
+ spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
752
+ return result;
753
+ } catch (error) {
754
+ spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
755
+ throw error;
756
+ } finally {
757
+ activeUpdate = null;
758
+ setServerPhaseListener(previousListener);
759
+ }
760
+ }
761
+
618
762
  // packages/cli/src/commands/doctor.ts
619
763
  async function executeDoctor(context, args) {
620
764
  requireNoExtraArgs(args, "rig doctor");
621
- const checks = await runRigDoctorChecks({ projectRoot: context.projectRoot });
765
+ const checks = await withSpinner("Running doctor checks\u2026", (update) => runRigDoctorChecks({ projectRoot: context.projectRoot, onProgress: update }), { outputMode: context.outputMode });
622
766
  if (context.outputMode === "text") {
623
767
  console.log(formatDoctorChecks(checks));
624
768
  }
@@ -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}`, {
@@ -340,6 +352,127 @@ async function postGitHubTokenViaServer(context, token, options = {}) {
340
352
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
341
353
  }
342
354
 
355
+ // packages/cli/src/commands/_async-ui.ts
356
+ import pc from "picocolors";
357
+
358
+ // packages/cli/src/commands/_spinner.ts
359
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
360
+ function createTtySpinner(input) {
361
+ const output = input.output ?? process.stdout;
362
+ const isTty = output.isTTY === true;
363
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
364
+ let label = input.label;
365
+ let frame = 0;
366
+ let paused = false;
367
+ let stopped = false;
368
+ let lastPrintedLabel = "";
369
+ const render = () => {
370
+ if (stopped || paused)
371
+ return;
372
+ if (!isTty) {
373
+ if (label !== lastPrintedLabel) {
374
+ output.write(`${label}
375
+ `);
376
+ lastPrintedLabel = label;
377
+ }
378
+ return;
379
+ }
380
+ frame = (frame + 1) % frames.length;
381
+ const glyph = frames[frame] ?? frames[0] ?? "";
382
+ output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
383
+ };
384
+ const clearLine = () => {
385
+ if (isTty)
386
+ output.write("\r\x1B[2K");
387
+ };
388
+ render();
389
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
390
+ return {
391
+ setLabel(next) {
392
+ label = next;
393
+ render();
394
+ },
395
+ pause() {
396
+ paused = true;
397
+ clearLine();
398
+ },
399
+ resume() {
400
+ if (stopped)
401
+ return;
402
+ paused = false;
403
+ render();
404
+ },
405
+ stop(finalLine) {
406
+ if (stopped)
407
+ return;
408
+ stopped = true;
409
+ if (timer)
410
+ clearInterval(timer);
411
+ clearLine();
412
+ if (finalLine)
413
+ output.write(`${finalLine}
414
+ `);
415
+ }
416
+ };
417
+ }
418
+
419
+ // packages/cli/src/commands/_async-ui.ts
420
+ var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
421
+ var DONE_SYMBOL = pc.green("\u25C7");
422
+ var FAIL_SYMBOL = pc.red("\u25A0");
423
+ var activeUpdate = null;
424
+ async function withSpinner(label, work, options = {}) {
425
+ if (options.outputMode === "json") {
426
+ return work(() => {});
427
+ }
428
+ if (activeUpdate) {
429
+ const outer = activeUpdate;
430
+ outer(label);
431
+ return work(outer);
432
+ }
433
+ const output = options.output ?? process.stderr;
434
+ const isTty = output.isTTY === true;
435
+ let lastLabel = label;
436
+ if (!isTty) {
437
+ output.write(`${label}
438
+ `);
439
+ const update2 = (next) => {
440
+ lastLabel = next;
441
+ };
442
+ activeUpdate = update2;
443
+ const previousListener2 = setServerPhaseListener(update2);
444
+ try {
445
+ return await work(update2);
446
+ } finally {
447
+ activeUpdate = null;
448
+ setServerPhaseListener(previousListener2);
449
+ }
450
+ }
451
+ const spinner = createTtySpinner({
452
+ label,
453
+ output,
454
+ frames: CLACK_SPINNER_FRAMES,
455
+ styleFrame: (frame) => pc.magenta(frame)
456
+ });
457
+ const update = (next) => {
458
+ lastLabel = next;
459
+ spinner.setLabel(next);
460
+ };
461
+ activeUpdate = update;
462
+ const previousListener = setServerPhaseListener(update);
463
+ try {
464
+ const result = await work(update);
465
+ spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
466
+ return result;
467
+ } catch (error) {
468
+ spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
469
+ throw error;
470
+ } finally {
471
+ activeUpdate = null;
472
+ setServerPhaseListener(previousListener);
473
+ }
474
+ }
475
+
343
476
  // packages/cli/src/commands/github.ts
344
477
  function printPayload(context, payload, fallback) {
345
478
  if (context.outputMode === "json")
@@ -367,7 +500,7 @@ async function executeGithub(context, args) {
367
500
  case "status": {
368
501
  if (rest.length > 0)
369
502
  throw new CliError("Usage: rig github auth status", 1);
370
- const status = await getGitHubAuthStatusViaServer(context);
503
+ const status = await withSpinner("Checking GitHub auth on the server\u2026", () => getGitHubAuthStatusViaServer(context), { outputMode: context.outputMode });
371
504
  printPayload(context, status, `GitHub auth: ${status.authenticated ? "authenticated" : "unauthenticated"}`);
372
505
  return { ok: true, group: "github", command: "auth status", details: status };
373
506
  }
@@ -378,14 +511,15 @@ async function executeGithub(context, args) {
378
511
  const token = parsed.value?.trim();
379
512
  if (!token)
380
513
  throw new CliError("Missing --token value.", 1, { hint: "Re-run as `rig github auth token --token <token>`." });
381
- const result = await postGitHubTokenViaServer(context, token);
514
+ const result = await withSpinner("Storing GitHub token on the server\u2026", () => postGitHubTokenViaServer(context, token), { outputMode: context.outputMode });
382
515
  printPayload(context, result, "GitHub token stored on the selected Rig server.");
383
516
  return { ok: true, group: "github", command: "auth token", details: result };
384
517
  }
385
518
  case "import-gh": {
386
519
  if (rest.length > 0)
387
520
  throw new CliError("Usage: rig github auth import-gh", 1);
388
- const result = await postGitHubTokenViaServer(context, readGhToken());
521
+ const importedToken = readGhToken();
522
+ const result = await withSpinner("Storing GitHub token on the server\u2026", () => postGitHubTokenViaServer(context, importedToken), { outputMode: context.outputMode });
389
523
  printPayload(context, result, "GitHub token imported from gh and stored on the selected Rig server.");
390
524
  return { ok: true, group: "github", command: "auth import-gh", details: result };
391
525
  }
@@ -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}`, {
@@ -413,6 +425,127 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
413
425
  return payload;
414
426
  }
415
427
 
428
+ // packages/cli/src/commands/_async-ui.ts
429
+ import pc2 from "picocolors";
430
+
431
+ // packages/cli/src/commands/_spinner.ts
432
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
433
+ function createTtySpinner(input) {
434
+ const output = input.output ?? process.stdout;
435
+ const isTty = output.isTTY === true;
436
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
437
+ let label = input.label;
438
+ let frame = 0;
439
+ let paused = false;
440
+ let stopped = false;
441
+ let lastPrintedLabel = "";
442
+ const render = () => {
443
+ if (stopped || paused)
444
+ return;
445
+ if (!isTty) {
446
+ if (label !== lastPrintedLabel) {
447
+ output.write(`${label}
448
+ `);
449
+ lastPrintedLabel = label;
450
+ }
451
+ return;
452
+ }
453
+ frame = (frame + 1) % frames.length;
454
+ const glyph = frames[frame] ?? frames[0] ?? "";
455
+ output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
456
+ };
457
+ const clearLine = () => {
458
+ if (isTty)
459
+ output.write("\r\x1B[2K");
460
+ };
461
+ render();
462
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
463
+ return {
464
+ setLabel(next) {
465
+ label = next;
466
+ render();
467
+ },
468
+ pause() {
469
+ paused = true;
470
+ clearLine();
471
+ },
472
+ resume() {
473
+ if (stopped)
474
+ return;
475
+ paused = false;
476
+ render();
477
+ },
478
+ stop(finalLine) {
479
+ if (stopped)
480
+ return;
481
+ stopped = true;
482
+ if (timer)
483
+ clearInterval(timer);
484
+ clearLine();
485
+ if (finalLine)
486
+ output.write(`${finalLine}
487
+ `);
488
+ }
489
+ };
490
+ }
491
+
492
+ // packages/cli/src/commands/_async-ui.ts
493
+ var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
494
+ var DONE_SYMBOL = pc2.green("\u25C7");
495
+ var FAIL_SYMBOL = pc2.red("\u25A0");
496
+ var activeUpdate = null;
497
+ async function withSpinner(label, work, options = {}) {
498
+ if (options.outputMode === "json") {
499
+ return work(() => {});
500
+ }
501
+ if (activeUpdate) {
502
+ const outer = activeUpdate;
503
+ outer(label);
504
+ return work(outer);
505
+ }
506
+ const output = options.output ?? process.stderr;
507
+ const isTty = output.isTTY === true;
508
+ let lastLabel = label;
509
+ if (!isTty) {
510
+ output.write(`${label}
511
+ `);
512
+ const update2 = (next) => {
513
+ lastLabel = next;
514
+ };
515
+ activeUpdate = update2;
516
+ const previousListener2 = setServerPhaseListener(update2);
517
+ try {
518
+ return await work(update2);
519
+ } finally {
520
+ activeUpdate = null;
521
+ setServerPhaseListener(previousListener2);
522
+ }
523
+ }
524
+ const spinner = createTtySpinner({
525
+ label,
526
+ output,
527
+ frames: CLACK_SPINNER_FRAMES,
528
+ styleFrame: (frame) => pc2.magenta(frame)
529
+ });
530
+ const update = (next) => {
531
+ lastLabel = next;
532
+ spinner.setLabel(next);
533
+ };
534
+ activeUpdate = update;
535
+ const previousListener = setServerPhaseListener(update);
536
+ try {
537
+ const result = await work(update);
538
+ spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
539
+ return result;
540
+ } catch (error) {
541
+ spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
542
+ throw error;
543
+ } finally {
544
+ activeUpdate = null;
545
+ setServerPhaseListener(previousListener);
546
+ }
547
+ }
548
+
416
549
  // packages/cli/src/commands/inbox.ts
417
550
  async function listInboxRecords(context, kind, filters) {
418
551
  const params = new URLSearchParams;
@@ -517,7 +650,7 @@ async function executeInbox(context, args) {
517
650
  const task = takeOption(pending, "--task");
518
651
  pending = task.rest;
519
652
  requireNoExtraArgs(pending, "rig inbox approvals [--run <id>] [--task <id>]");
520
- const approvals = await listInboxRecords(context, "approvals", { run: run.value, task: task.value });
653
+ const approvals = await withSpinner("Reading approvals from server\u2026", () => listInboxRecords(context, "approvals", { run: run.value, task: task.value }), { outputMode: context.outputMode });
521
654
  renderList(context, "approvals", approvals);
522
655
  return { ok: true, group: "inbox", command, details: { approvals } };
523
656
  }
@@ -528,7 +661,7 @@ async function executeInbox(context, args) {
528
661
  const task = takeOption(pending, "--task");
529
662
  pending = task.rest;
530
663
  requireNoExtraArgs(pending, "rig inbox inputs [--run <id>] [--task <id>]");
531
- const requests = await listInboxRecords(context, "inputs", { run: run.value, task: task.value });
664
+ const requests = await withSpinner("Reading input requests from server\u2026", () => listInboxRecords(context, "inputs", { run: run.value, task: task.value }), { outputMode: context.outputMode });
532
665
  renderList(context, "inputs", requests);
533
666
  return { ok: true, group: "inbox", command, details: { requests } };
534
667
  }
@@ -549,7 +682,7 @@ async function executeInbox(context, args) {
549
682
  if (decision.value !== "approve" && decision.value !== "reject") {
550
683
  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
684
  }
552
- const result = await requestServerJson(context, "/api/inbox/approvals/resolve", {
685
+ const result = await withSpinner(`Resolving approval ${request.value}\u2026`, () => requestServerJson(context, "/api/inbox/approvals/resolve", {
553
686
  method: "POST",
554
687
  headers: { "content-type": "application/json" },
555
688
  body: JSON.stringify({
@@ -558,7 +691,7 @@ async function executeInbox(context, args) {
558
691
  decision: decision.value,
559
692
  note: note2.value ?? null
560
693
  })
561
- });
694
+ }), { outputMode: context.outputMode });
562
695
  return { ok: true, group: "inbox", command, details: { result } };
563
696
  }
564
697
  case "respond": {
@@ -592,7 +725,7 @@ async function executeInbox(context, args) {
592
725
  const [key, ...restValue] = entry.split("=");
593
726
  return [key, restValue.join("=")];
594
727
  }));
595
- const result = await requestServerJson(context, "/api/inbox/inputs/respond", {
728
+ const result = await withSpinner(`Sending response for ${request.value}\u2026`, () => requestServerJson(context, "/api/inbox/inputs/respond", {
596
729
  method: "POST",
597
730
  headers: { "content-type": "application/json" },
598
731
  body: JSON.stringify({
@@ -600,7 +733,7 @@ async function executeInbox(context, args) {
600
733
  requestId: request.value,
601
734
  answers: parsedAnswers
602
735
  })
603
- });
736
+ }), { outputMode: context.outputMode });
604
737
  return { ok: true, group: "inbox", command, details: { result } };
605
738
  }
606
739
  case "watch": {