@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.
@@ -285,6 +285,15 @@ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
285
285
  import { resolve as resolve4 } from "path";
286
286
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
287
287
  var scopedGitHubBearerTokens = new Map;
288
+ var serverPhaseListener = null;
289
+ function setServerPhaseListener(listener) {
290
+ const previous = serverPhaseListener;
291
+ serverPhaseListener = listener;
292
+ return previous;
293
+ }
294
+ function reportServerPhase(label) {
295
+ serverPhaseListener?.(label);
296
+ }
288
297
  function cleanToken(value) {
289
298
  const trimmed = value?.trim();
290
299
  return trimmed ? trimmed : null;
@@ -327,6 +336,7 @@ async function ensureServerForCli(projectRoot) {
327
336
  try {
328
337
  const selected = resolveSelectedConnection(projectRoot);
329
338
  if (selected?.connection.kind === "remote") {
339
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
330
340
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
331
341
  const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
332
342
  return {
@@ -336,6 +346,7 @@ async function ensureServerForCli(projectRoot) {
336
346
  serverProjectRoot
337
347
  };
338
348
  }
349
+ reportServerPhase("Starting local Rig server\u2026");
339
350
  const connection = await ensureLocalRigServerConnection(projectRoot);
340
351
  return {
341
352
  baseUrl: connection.baseUrl,
@@ -442,6 +453,7 @@ async function requestServerJson(context, pathname, init = {}) {
442
453
  const headers = mergeHeaders(init.headers, server.authToken);
443
454
  if (server.serverProjectRoot)
444
455
  headers.set("x-rig-project-root", server.serverProjectRoot);
456
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
445
457
  let response;
446
458
  try {
447
459
  response = await fetch(`${server.baseUrl}${pathname}`, {
@@ -590,7 +602,10 @@ async function runRigDoctorChecks(options) {
590
602
  const bunVersion = options.bunVersion ?? Bun.version;
591
603
  const request = options.requestJson ?? ((pathname, init) => requestServerJson({ projectRoot }, pathname, init));
592
604
  const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
605
+ const progress = options.onProgress ?? (() => {});
606
+ progress("Checking local toolchain\u2026");
593
607
  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`)."));
608
+ progress("Loading rig.config\u2026");
594
609
  const loadedConfig = await loadConfig(projectRoot).catch(() => null);
595
610
  const config = loadedConfig ?? loadFallbackConfig(projectRoot);
596
611
  const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve5(projectRoot, name)));
@@ -609,6 +624,7 @@ async function runRigDoctorChecks(options) {
609
624
  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));
610
625
  let server = null;
611
626
  try {
627
+ progress("Connecting to the selected Rig server\u2026");
612
628
  server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
613
629
  checks.push(check("server", "Rig server reachable", "pass", `${server.connectionKind} ${server.baseUrl}`));
614
630
  } catch (error) {
@@ -616,18 +632,21 @@ async function runRigDoctorChecks(options) {
616
632
  }
617
633
  if (server || options.requestJson) {
618
634
  try {
635
+ progress("Checking server status\u2026");
619
636
  const status = await request("/api/server/status");
620
637
  checks.push(check("server-status", "server project status", "pass", JSON.stringify(status).slice(0, 180)));
621
638
  } catch (error) {
622
639
  checks.push(check("server-status", "server project status", "fail", errorMessage(error), "Run `rig doctor` after the selected server is reachable."));
623
640
  }
624
641
  try {
642
+ progress("Checking GitHub auth\u2026");
625
643
  const auth = await request("/api/github/auth/status");
626
644
  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>`."));
627
645
  } catch (error) {
628
646
  checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
629
647
  }
630
648
  try {
649
+ progress("Checking GitHub repo permissions\u2026");
631
650
  const permissions = await request("/api/github/repo/permissions");
632
651
  const allowed = permissionAllowsPr(permissions);
633
652
  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."));
@@ -635,6 +654,7 @@ async function runRigDoctorChecks(options) {
635
654
  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."));
636
655
  }
637
656
  try {
657
+ progress("Checking GitHub issue labels\u2026");
638
658
  const labels = await request("/api/workspace/task-labels");
639
659
  const ready = labelsReady(labels);
640
660
  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."));
@@ -642,6 +662,7 @@ async function runRigDoctorChecks(options) {
642
662
  checks.push(check("task-labels", "GitHub issue labels", "warn", errorMessage(error), "Run `rig init`/`rig doctor` after label setup is wired on the server."));
643
663
  }
644
664
  try {
665
+ progress("Checking task projection\u2026");
645
666
  const projection = await request("/api/workspace/task-projection");
646
667
  checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
647
668
  } catch (error) {
@@ -650,6 +671,7 @@ async function runRigDoctorChecks(options) {
650
671
  const slug = projectStatusSlug(projectRoot, config);
651
672
  if (slug) {
652
673
  try {
674
+ progress("Checking server project checkout\u2026");
653
675
  const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
654
676
  checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
655
677
  } catch (error) {
@@ -664,6 +686,7 @@ async function runRigDoctorChecks(options) {
664
686
  }
665
687
  checks.push(githubProjectsCheck(config));
666
688
  checks.push(prMergeCheck(config));
689
+ progress("Checking Pi installation\u2026");
667
690
  const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
668
691
  ok: false,
669
692
  label: "pi/pi-rig checks",
@@ -687,6 +710,127 @@ function countDoctorFailures(checks) {
687
710
  return checks.filter((entry) => entry.status === "fail").length;
688
711
  }
689
712
 
713
+ // packages/cli/src/commands/_async-ui.ts
714
+ import pc from "picocolors";
715
+
716
+ // packages/cli/src/commands/_spinner.ts
717
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
718
+ function createTtySpinner(input) {
719
+ const output = input.output ?? process.stdout;
720
+ const isTty = output.isTTY === true;
721
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
722
+ let label = input.label;
723
+ let frame = 0;
724
+ let paused = false;
725
+ let stopped = false;
726
+ let lastPrintedLabel = "";
727
+ const render = () => {
728
+ if (stopped || paused)
729
+ return;
730
+ if (!isTty) {
731
+ if (label !== lastPrintedLabel) {
732
+ output.write(`${label}
733
+ `);
734
+ lastPrintedLabel = label;
735
+ }
736
+ return;
737
+ }
738
+ frame = (frame + 1) % frames.length;
739
+ const glyph = frames[frame] ?? frames[0] ?? "";
740
+ output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
741
+ };
742
+ const clearLine = () => {
743
+ if (isTty)
744
+ output.write("\r\x1B[2K");
745
+ };
746
+ render();
747
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
748
+ return {
749
+ setLabel(next) {
750
+ label = next;
751
+ render();
752
+ },
753
+ pause() {
754
+ paused = true;
755
+ clearLine();
756
+ },
757
+ resume() {
758
+ if (stopped)
759
+ return;
760
+ paused = false;
761
+ render();
762
+ },
763
+ stop(finalLine) {
764
+ if (stopped)
765
+ return;
766
+ stopped = true;
767
+ if (timer)
768
+ clearInterval(timer);
769
+ clearLine();
770
+ if (finalLine)
771
+ output.write(`${finalLine}
772
+ `);
773
+ }
774
+ };
775
+ }
776
+
777
+ // packages/cli/src/commands/_async-ui.ts
778
+ var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
779
+ var DONE_SYMBOL = pc.green("\u25C7");
780
+ var FAIL_SYMBOL = pc.red("\u25A0");
781
+ var activeUpdate = null;
782
+ async function withSpinner(label, work, options = {}) {
783
+ if (options.outputMode === "json") {
784
+ return work(() => {});
785
+ }
786
+ if (activeUpdate) {
787
+ const outer = activeUpdate;
788
+ outer(label);
789
+ return work(outer);
790
+ }
791
+ const output = options.output ?? process.stderr;
792
+ const isTty = output.isTTY === true;
793
+ let lastLabel = label;
794
+ if (!isTty) {
795
+ output.write(`${label}
796
+ `);
797
+ const update2 = (next) => {
798
+ lastLabel = next;
799
+ };
800
+ activeUpdate = update2;
801
+ const previousListener2 = setServerPhaseListener(update2);
802
+ try {
803
+ return await work(update2);
804
+ } finally {
805
+ activeUpdate = null;
806
+ setServerPhaseListener(previousListener2);
807
+ }
808
+ }
809
+ const spinner = createTtySpinner({
810
+ label,
811
+ output,
812
+ frames: CLACK_SPINNER_FRAMES,
813
+ styleFrame: (frame) => pc.magenta(frame)
814
+ });
815
+ const update = (next) => {
816
+ lastLabel = next;
817
+ spinner.setLabel(next);
818
+ };
819
+ activeUpdate = update;
820
+ const previousListener = setServerPhaseListener(update);
821
+ try {
822
+ const result = await work(update);
823
+ spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
824
+ return result;
825
+ } catch (error) {
826
+ spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
827
+ throw error;
828
+ } finally {
829
+ activeUpdate = null;
830
+ setServerPhaseListener(previousListener);
831
+ }
832
+ }
833
+
690
834
  // packages/cli/src/commands/setup.ts
691
835
  async function executeSetup(context, args) {
692
836
  const [command = "check", ...rest] = args;
@@ -717,7 +861,7 @@ async function executeSetup(context, args) {
717
861
  case "check":
718
862
  requireNoExtraArgs(rest, `rig setup ${command}`);
719
863
  {
720
- const checks = await withMutedConsole(context.outputMode === "json", () => runSetupCheck(context.projectRoot));
864
+ const checks = await withMutedConsole(context.outputMode === "json", () => runSetupCheck(context.projectRoot, context.outputMode));
721
865
  return { ok: true, group: "setup", command, details: { checks, failures: countDoctorFailures(checks) } };
722
866
  }
723
867
  case "setup":
@@ -726,7 +870,7 @@ async function executeSetup(context, args) {
726
870
  return { ok: true, group: "setup", command };
727
871
  case "preflight":
728
872
  requireNoExtraArgs(rest, "rig setup preflight");
729
- await withMutedConsole(context.outputMode === "json", () => runSetupPreflight(context.projectRoot));
873
+ await withMutedConsole(context.outputMode === "json", () => runSetupPreflight(context.projectRoot, context.outputMode));
730
874
  return { ok: true, group: "setup", command };
731
875
  default:
732
876
  throw new CliError(`Unknown setup command: ${command}`, 1, { hint: "Run `rig setup --help` \u2014 commands are bootstrap|check|preflight." });
@@ -763,8 +907,8 @@ function runSetupInit(projectRoot) {
763
907
  }
764
908
  console.log("Harness directories ready.");
765
909
  }
766
- async function runSetupCheck(projectRoot) {
767
- const doctorChecks = await runRigDoctorChecks({ projectRoot });
910
+ async function runSetupCheck(projectRoot, outputMode = "text") {
911
+ const doctorChecks = await withSpinner("Running setup checks\u2026", (update) => runRigDoctorChecks({ projectRoot, onProgress: update }), { outputMode });
768
912
  console.log(formatDoctorChecks(doctorChecks));
769
913
  const failures = countDoctorFailures(doctorChecks);
770
914
  if (failures > 0) {
@@ -772,8 +916,8 @@ async function runSetupCheck(projectRoot) {
772
916
  }
773
917
  return doctorChecks;
774
918
  }
775
- async function runSetupPreflight(projectRoot) {
776
- await runSetupCheck(projectRoot);
919
+ async function runSetupPreflight(projectRoot, outputMode = "text") {
920
+ await runSetupCheck(projectRoot, outputMode);
777
921
  const validationRoot = resolve6(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
778
922
  if (existsSync5(validationRoot)) {
779
923
  const validators = readdirSync(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
@@ -1,6 +1,6 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/stats.ts
3
- import pc3 from "picocolors";
3
+ import pc4 from "picocolors";
4
4
 
5
5
  // packages/cli/src/runner.ts
6
6
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
@@ -152,6 +152,15 @@ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
152
152
  import { resolve as resolve2 } from "path";
153
153
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
154
154
  var scopedGitHubBearerTokens = new Map;
155
+ var serverPhaseListener = null;
156
+ function setServerPhaseListener(listener) {
157
+ const previous = serverPhaseListener;
158
+ serverPhaseListener = listener;
159
+ return previous;
160
+ }
161
+ function reportServerPhase(label) {
162
+ serverPhaseListener?.(label);
163
+ }
155
164
  function cleanToken(value) {
156
165
  const trimmed = value?.trim();
157
166
  return trimmed ? trimmed : null;
@@ -194,6 +203,7 @@ async function ensureServerForCli(projectRoot) {
194
203
  try {
195
204
  const selected = resolveSelectedConnection(projectRoot);
196
205
  if (selected?.connection.kind === "remote") {
206
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
197
207
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
198
208
  const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
199
209
  return {
@@ -203,6 +213,7 @@ async function ensureServerForCli(projectRoot) {
203
213
  serverProjectRoot
204
214
  };
205
215
  }
216
+ reportServerPhase("Starting local Rig server\u2026");
206
217
  const connection = await ensureLocalRigServerConnection(projectRoot);
207
218
  return {
208
219
  baseUrl: connection.baseUrl,
@@ -309,6 +320,7 @@ async function requestServerJson(context, pathname, init = {}) {
309
320
  const headers = mergeHeaders(init.headers, server.authToken);
310
321
  if (server.serverProjectRoot)
311
322
  headers.set("x-rig-project-root", server.serverProjectRoot);
323
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
312
324
  let response;
313
325
  try {
314
326
  response = await fetch(`${server.baseUrl}${pathname}`, {
@@ -363,9 +375,130 @@ function formatNextSteps(steps) {
363
375
  return [pc.bold("Next"), ...steps.map((step) => `${pc.dim("\u203A")} ${step}`)];
364
376
  }
365
377
 
378
+ // packages/cli/src/commands/_async-ui.ts
379
+ import pc2 from "picocolors";
380
+
381
+ // packages/cli/src/commands/_spinner.ts
382
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
383
+ function createTtySpinner(input) {
384
+ const output = input.output ?? process.stdout;
385
+ const isTty = output.isTTY === true;
386
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
387
+ let label = input.label;
388
+ let frame = 0;
389
+ let paused = false;
390
+ let stopped = false;
391
+ let lastPrintedLabel = "";
392
+ const render = () => {
393
+ if (stopped || paused)
394
+ return;
395
+ if (!isTty) {
396
+ if (label !== lastPrintedLabel) {
397
+ output.write(`${label}
398
+ `);
399
+ lastPrintedLabel = label;
400
+ }
401
+ return;
402
+ }
403
+ frame = (frame + 1) % frames.length;
404
+ const glyph = frames[frame] ?? frames[0] ?? "";
405
+ output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
406
+ };
407
+ const clearLine = () => {
408
+ if (isTty)
409
+ output.write("\r\x1B[2K");
410
+ };
411
+ render();
412
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
413
+ return {
414
+ setLabel(next) {
415
+ label = next;
416
+ render();
417
+ },
418
+ pause() {
419
+ paused = true;
420
+ clearLine();
421
+ },
422
+ resume() {
423
+ if (stopped)
424
+ return;
425
+ paused = false;
426
+ render();
427
+ },
428
+ stop(finalLine) {
429
+ if (stopped)
430
+ return;
431
+ stopped = true;
432
+ if (timer)
433
+ clearInterval(timer);
434
+ clearLine();
435
+ if (finalLine)
436
+ output.write(`${finalLine}
437
+ `);
438
+ }
439
+ };
440
+ }
441
+
442
+ // packages/cli/src/commands/_async-ui.ts
443
+ var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
444
+ var DONE_SYMBOL = pc2.green("\u25C7");
445
+ var FAIL_SYMBOL = pc2.red("\u25A0");
446
+ var activeUpdate = null;
447
+ async function withSpinner(label, work, options = {}) {
448
+ if (options.outputMode === "json") {
449
+ return work(() => {});
450
+ }
451
+ if (activeUpdate) {
452
+ const outer = activeUpdate;
453
+ outer(label);
454
+ return work(outer);
455
+ }
456
+ const output = options.output ?? process.stderr;
457
+ const isTty = output.isTTY === true;
458
+ let lastLabel = label;
459
+ if (!isTty) {
460
+ output.write(`${label}
461
+ `);
462
+ const update2 = (next) => {
463
+ lastLabel = next;
464
+ };
465
+ activeUpdate = update2;
466
+ const previousListener2 = setServerPhaseListener(update2);
467
+ try {
468
+ return await work(update2);
469
+ } finally {
470
+ activeUpdate = null;
471
+ setServerPhaseListener(previousListener2);
472
+ }
473
+ }
474
+ const spinner = createTtySpinner({
475
+ label,
476
+ output,
477
+ frames: CLACK_SPINNER_FRAMES,
478
+ styleFrame: (frame) => pc2.magenta(frame)
479
+ });
480
+ const update = (next) => {
481
+ lastLabel = next;
482
+ spinner.setLabel(next);
483
+ };
484
+ activeUpdate = update;
485
+ const previousListener = setServerPhaseListener(update);
486
+ try {
487
+ const result = await work(update);
488
+ spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
489
+ return result;
490
+ } catch (error) {
491
+ spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
492
+ throw error;
493
+ } finally {
494
+ activeUpdate = null;
495
+ setServerPhaseListener(previousListener);
496
+ }
497
+ }
498
+
366
499
  // packages/cli/src/commands/_help-catalog.ts
367
500
  import { intro, log as log2, note as note2, outro } from "@clack/prompts";
368
- import pc2 from "picocolors";
501
+ import pc3 from "picocolors";
369
502
  var TOP_LEVEL_SECTIONS = [
370
503
  {
371
504
  title: "Start here",
@@ -647,13 +780,13 @@ var ADVANCED_GROUPS = [
647
780
  ];
648
781
  var ALL_GROUPS = [...PRIMARY_GROUPS, ...ADVANCED_GROUPS];
649
782
  function heading(title) {
650
- return pc2.bold(pc2.cyan(title));
783
+ return pc3.bold(pc3.cyan(title));
651
784
  }
652
785
  function renderRigBanner(version) {
653
- const m = (s) => pc2.bold(pc2.magenta(s));
654
- const c = (s) => pc2.bold(pc2.cyan(s));
655
- const y = (s) => pc2.yellow(s);
656
- const d = (s) => pc2.dim(s);
786
+ const m = (s) => pc3.bold(pc3.magenta(s));
787
+ const c = (s) => pc3.bold(pc3.cyan(s));
788
+ const y = (s) => pc3.yellow(s);
789
+ const d = (s) => pc3.dim(s);
657
790
  const lines = [
658
791
  m(" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ") + d(" \u2591\u2592\u2593\u2588 "),
659
792
  m(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D ") + d("\u2593\u2592\u2591"),
@@ -662,7 +795,7 @@ function renderRigBanner(version) {
662
795
  y(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D") + d(" \u2592\u2591"),
663
796
  y(" \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D "),
664
797
  "",
665
- ` ${c("\u25E2\u25E4")} ${pc2.bold("the control rig for autonomous coding agents")} ${m("//")} ${d("you are the operator")}`,
798
+ ` ${c("\u25E2\u25E4")} ${pc3.bold("the control rig for autonomous coding agents")} ${m("//")} ${d("you are the operator")}`,
666
799
  version ? ` ${d(`v${version} \xB7 jack in: rig task run --next`)}` : ` ${d("jack in: rig task run --next")}`
667
800
  ];
668
801
  return lines.join(`
@@ -670,7 +803,7 @@ function renderRigBanner(version) {
670
803
  }
671
804
  function commandLine(command, description) {
672
805
  const commandColumn = command.length >= 38 ? `${command} ` : command.padEnd(38);
673
- return `${pc2.dim("\u2502")} ${pc2.bold(commandColumn)} ${description}`;
806
+ return `${pc3.dim("\u2502")} ${pc3.bold(commandColumn)} ${description}`;
674
807
  }
675
808
  function renderCommandBlock(commands) {
676
809
  return commands.map((entry) => commandLine(entry.command, entry.description)).join(`
@@ -680,37 +813,37 @@ function renderGroup(group) {
680
813
  const lines = [
681
814
  `${heading(`rig ${group.name}`)} \u2014 ${group.summary}`,
682
815
  "",
683
- pc2.bold("Usage"),
816
+ pc3.bold("Usage"),
684
817
  ...group.usage.map((line) => ` ${line}`),
685
818
  "",
686
- pc2.bold("Commands"),
819
+ pc3.bold("Commands"),
687
820
  ...group.commands.map((entry) => commandLine(entry.command, entry.description))
688
821
  ];
689
822
  if (group.examples?.length) {
690
- lines.push("", pc2.bold("Examples"), ...group.examples.map((line) => ` ${pc2.dim("$")} ${line}`));
823
+ lines.push("", pc3.bold("Examples"), ...group.examples.map((line) => ` ${pc3.dim("$")} ${line}`));
691
824
  }
692
825
  if (group.next?.length) {
693
- lines.push("", pc2.bold("Next steps"), ...group.next.map((line) => ` ${pc2.dim("\u203A")} ${line}`));
826
+ lines.push("", pc3.bold("Next steps"), ...group.next.map((line) => ` ${pc3.dim("\u203A")} ${line}`));
694
827
  }
695
828
  if (group.advanced?.length) {
696
- lines.push("", pc2.bold("Compatibility / advanced"), ...group.advanced.map((line) => ` ${pc2.dim("\u203A")} ${line}`));
829
+ lines.push("", pc3.bold("Compatibility / advanced"), ...group.advanced.map((line) => ` ${pc3.dim("\u203A")} ${line}`));
697
830
  }
698
831
  return lines.join(`
699
832
  `);
700
833
  }
701
834
  function renderTopLevelHelp() {
702
835
  return [
703
- `${heading("rig")} ${pc2.dim("\u2014 server-owned task/run control plane for Pi-backed engineering work")}`,
704
- pc2.dim("Current path: select a server, choose a task, submit a run, attach with native Pi, clear inbox gates."),
836
+ `${heading("rig")} ${pc3.dim("\u2014 server-owned task/run control plane for Pi-backed engineering work")}`,
837
+ pc3.dim("Current path: select a server, choose a task, submit a run, attach with native Pi, clear inbox gates."),
705
838
  "",
706
839
  ...TOP_LEVEL_SECTIONS.flatMap((section) => [
707
- `${pc2.bold(pc2.magenta(`\u25C7 ${section.title}`))} \u2014 ${pc2.dim(section.subtitle)}`,
840
+ `${pc3.bold(pc3.magenta(`\u25C7 ${section.title}`))} \u2014 ${pc3.dim(section.subtitle)}`,
708
841
  renderCommandBlock(section.commands),
709
842
  ""
710
843
  ]),
711
- pc2.dim("More: `rig help --advanced` for dev/compatibility commands; `rig <group> --help` for rich per-group help; `rig --version` for the installed version."),
844
+ pc3.dim("More: `rig help --advanced` for dev/compatibility commands; `rig <group> --help` for rich per-group help; `rig --version` for the installed version."),
712
845
  "",
713
- pc2.bold("Global options"),
846
+ pc3.bold("Global options"),
714
847
  commandLine("--project <path>", "Use a project root instead of auto-discovery."),
715
848
  commandLine("--json", "Emit structured output for scripts/agents."),
716
849
  commandLine("--dry-run", "Print the command plan without mutating state.")
@@ -834,11 +967,11 @@ function formatStatsReport(stats) {
834
967
  const keyWidth = Math.max(...rows.map(([key]) => key.length));
835
968
  const lines = [
836
969
  formatSection("Fleet stats", window),
837
- ...rows.map(([key, value]) => `${pc3.dim("\u2502")} ${pc3.dim(key.padEnd(keyWidth + 2))} ${value}`)
970
+ ...rows.map(([key, value]) => `${pc4.dim("\u2502")} ${pc4.dim(key.padEnd(keyWidth + 2))} ${value}`)
838
971
  ];
839
972
  const otherStatuses = Object.entries(stats.statusCounts).filter(([status]) => !["completed", "failed", "needs-attention"].includes(status)).sort(([, left], [, right]) => right - left);
840
973
  if (otherStatuses.length > 0) {
841
- lines.push(`${pc3.dim("\u2502")} ${pc3.dim("other".padEnd(keyWidth + 2))} ${otherStatuses.map(([status, count]) => `${status}:${count}`).join(" ")}`);
974
+ lines.push(`${pc4.dim("\u2502")} ${pc4.dim("other".padEnd(keyWidth + 2))} ${otherStatuses.map(([status, count]) => `${status}:${count}`).join(" ")}`);
842
975
  }
843
976
  lines.push("", ...formatNextSteps([
844
977
  "Inspect a run: `rig run list` then `rig run show <run-id>`",
@@ -857,7 +990,8 @@ async function executeStats(context, args) {
857
990
  const sinceResult = takeOption(pending, "--since");
858
991
  requireNoExtraArgs(sinceResult.rest, "rig stats [show] [--since <7d|30d|ISO date>]");
859
992
  const since = parseSinceOption(sinceResult.value);
860
- const stats = isRemoteConnectionSelected(context.projectRoot) ? await requestServerJson(context, `/api/stats${since ? `?since=${encodeURIComponent(since.toISOString())}` : ""}`) : computeRigStats(context.projectRoot, { since });
993
+ const remoteStats = isRemoteConnectionSelected(context.projectRoot);
994
+ const stats = await withSpinner(remoteStats ? "Computing fleet stats on the server\u2026" : "Scanning local run state\u2026", async () => remoteStats ? await requestServerJson(context, `/api/stats${since ? `?since=${encodeURIComponent(since.toISOString())}` : ""}`) : computeRigStats(context.projectRoot, { since }), { outputMode: context.outputMode });
861
995
  if (context.outputMode === "text") {
862
996
  printFormattedOutput(formatStatsReport(stats));
863
997
  }
@@ -543,6 +543,10 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
543
543
 
544
544
  // packages/cli/src/commands/_server-client.ts
545
545
  var scopedGitHubBearerTokens = new Map;
546
+ var serverPhaseListener = null;
547
+ function reportServerPhase(label) {
548
+ serverPhaseListener?.(label);
549
+ }
546
550
  function cleanToken(value) {
547
551
  const trimmed = value?.trim();
548
552
  return trimmed ? trimmed : null;
@@ -585,6 +589,7 @@ async function ensureServerForCli(projectRoot) {
585
589
  try {
586
590
  const selected = resolveSelectedConnection(projectRoot);
587
591
  if (selected?.connection.kind === "remote") {
592
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
588
593
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
589
594
  const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
590
595
  return {
@@ -594,6 +599,7 @@ async function ensureServerForCli(projectRoot) {
594
599
  serverProjectRoot
595
600
  };
596
601
  }
602
+ reportServerPhase("Starting local Rig server\u2026");
597
603
  const connection = await ensureLocalRigServerConnection(projectRoot);
598
604
  return {
599
605
  baseUrl: connection.baseUrl,
@@ -700,6 +706,7 @@ async function requestServerJson(context, pathname, init = {}) {
700
706
  const headers = mergeHeaders(init.headers, server.authToken);
701
707
  if (server.serverProjectRoot)
702
708
  headers.set("x-rig-project-root", server.serverProjectRoot);
709
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
703
710
  let response;
704
711
  try {
705
712
  response = await fetch(`${server.baseUrl}${pathname}`, {