@h-rig/cli 0.0.6-alpha.66 → 0.0.6-alpha.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2730,6 +2730,9 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
2730
2730
  return;
2731
2731
  writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
2732
2732
  }
2733
+ function isRemoteConnectionSelected(projectRoot) {
2734
+ return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
2735
+ }
2733
2736
 
2734
2737
  // packages/cli/src/commands/_server-client.ts
2735
2738
  init_runner();
@@ -2737,6 +2740,15 @@ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
2737
2740
  import { resolve as resolve10 } from "path";
2738
2741
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
2739
2742
  var scopedGitHubBearerTokens = new Map;
2743
+ var serverPhaseListener = null;
2744
+ function setServerPhaseListener(listener) {
2745
+ const previous = serverPhaseListener;
2746
+ serverPhaseListener = listener;
2747
+ return previous;
2748
+ }
2749
+ function reportServerPhase(label) {
2750
+ serverPhaseListener?.(label);
2751
+ }
2740
2752
  function cleanToken(value) {
2741
2753
  const trimmed = value?.trim();
2742
2754
  return trimmed ? trimmed : null;
@@ -2783,6 +2795,7 @@ async function ensureServerForCli(projectRoot) {
2783
2795
  try {
2784
2796
  const selected = resolveSelectedConnection(projectRoot);
2785
2797
  if (selected?.connection.kind === "remote") {
2798
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
2786
2799
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
2787
2800
  const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
2788
2801
  return {
@@ -2792,6 +2805,7 @@ async function ensureServerForCli(projectRoot) {
2792
2805
  serverProjectRoot
2793
2806
  };
2794
2807
  }
2808
+ reportServerPhase("Starting local Rig server\u2026");
2795
2809
  const connection = await ensureLocalRigServerConnection(projectRoot);
2796
2810
  return {
2797
2811
  baseUrl: connection.baseUrl,
@@ -2908,6 +2922,7 @@ async function requestServerJson(context, pathname, init = {}) {
2908
2922
  const headers = mergeHeaders(init.headers, server.authToken);
2909
2923
  if (server.serverProjectRoot)
2910
2924
  headers.set("x-rig-project-root", server.serverProjectRoot);
2925
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
2911
2926
  let response;
2912
2927
  try {
2913
2928
  response = await fetch(`${server.baseUrl}${pathname}`, {
@@ -4428,6 +4443,127 @@ function formatConnectionStatus(selected, connections) {
4428
4443
  `);
4429
4444
  }
4430
4445
 
4446
+ // packages/cli/src/commands/_async-ui.ts
4447
+ import pc4 from "picocolors";
4448
+
4449
+ // packages/cli/src/commands/_spinner.ts
4450
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
4451
+ function createTtySpinner(input) {
4452
+ const output = input.output ?? process.stdout;
4453
+ const isTty = output.isTTY === true;
4454
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
4455
+ let label = input.label;
4456
+ let frame = 0;
4457
+ let paused = false;
4458
+ let stopped = false;
4459
+ let lastPrintedLabel = "";
4460
+ const render = () => {
4461
+ if (stopped || paused)
4462
+ return;
4463
+ if (!isTty) {
4464
+ if (label !== lastPrintedLabel) {
4465
+ output.write(`${label}
4466
+ `);
4467
+ lastPrintedLabel = label;
4468
+ }
4469
+ return;
4470
+ }
4471
+ frame = (frame + 1) % frames.length;
4472
+ const glyph = frames[frame] ?? frames[0] ?? "";
4473
+ output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
4474
+ };
4475
+ const clearLine = () => {
4476
+ if (isTty)
4477
+ output.write("\r\x1B[2K");
4478
+ };
4479
+ render();
4480
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
4481
+ return {
4482
+ setLabel(next) {
4483
+ label = next;
4484
+ render();
4485
+ },
4486
+ pause() {
4487
+ paused = true;
4488
+ clearLine();
4489
+ },
4490
+ resume() {
4491
+ if (stopped)
4492
+ return;
4493
+ paused = false;
4494
+ render();
4495
+ },
4496
+ stop(finalLine) {
4497
+ if (stopped)
4498
+ return;
4499
+ stopped = true;
4500
+ if (timer)
4501
+ clearInterval(timer);
4502
+ clearLine();
4503
+ if (finalLine)
4504
+ output.write(`${finalLine}
4505
+ `);
4506
+ }
4507
+ };
4508
+ }
4509
+
4510
+ // packages/cli/src/commands/_async-ui.ts
4511
+ var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
4512
+ var DONE_SYMBOL = pc4.green("\u25C7");
4513
+ var FAIL_SYMBOL = pc4.red("\u25A0");
4514
+ var activeUpdate = null;
4515
+ async function withSpinner(label, work, options = {}) {
4516
+ if (options.outputMode === "json") {
4517
+ return work(() => {});
4518
+ }
4519
+ if (activeUpdate) {
4520
+ const outer = activeUpdate;
4521
+ outer(label);
4522
+ return work(outer);
4523
+ }
4524
+ const output = options.output ?? process.stderr;
4525
+ const isTty = output.isTTY === true;
4526
+ let lastLabel = label;
4527
+ if (!isTty) {
4528
+ output.write(`${label}
4529
+ `);
4530
+ const update2 = (next) => {
4531
+ lastLabel = next;
4532
+ };
4533
+ activeUpdate = update2;
4534
+ const previousListener2 = setServerPhaseListener(update2);
4535
+ try {
4536
+ return await work(update2);
4537
+ } finally {
4538
+ activeUpdate = null;
4539
+ setServerPhaseListener(previousListener2);
4540
+ }
4541
+ }
4542
+ const spinner2 = createTtySpinner({
4543
+ label,
4544
+ output,
4545
+ frames: CLACK_SPINNER_FRAMES,
4546
+ styleFrame: (frame) => pc4.magenta(frame)
4547
+ });
4548
+ const update = (next) => {
4549
+ lastLabel = next;
4550
+ spinner2.setLabel(next);
4551
+ };
4552
+ activeUpdate = update;
4553
+ const previousListener = setServerPhaseListener(update);
4554
+ try {
4555
+ const result = await work(update);
4556
+ spinner2.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
4557
+ return result;
4558
+ } catch (error) {
4559
+ spinner2.stop(`${FAIL_SYMBOL} ${lastLabel}`);
4560
+ throw error;
4561
+ } finally {
4562
+ activeUpdate = null;
4563
+ setServerPhaseListener(previousListener);
4564
+ }
4565
+ }
4566
+
4431
4567
  // packages/cli/src/commands/inbox.ts
4432
4568
  async function listInboxRecords(context, kind, filters) {
4433
4569
  const params = new URLSearchParams;
@@ -4532,7 +4668,7 @@ async function executeInbox(context, args) {
4532
4668
  const task = takeOption(pending, "--task");
4533
4669
  pending = task.rest;
4534
4670
  requireNoExtraArgs(pending, "rig inbox approvals [--run <id>] [--task <id>]");
4535
- const approvals = await listInboxRecords(context, "approvals", { run: run.value, task: task.value });
4671
+ const approvals = await withSpinner("Reading approvals from server\u2026", () => listInboxRecords(context, "approvals", { run: run.value, task: task.value }), { outputMode: context.outputMode });
4536
4672
  renderList(context, "approvals", approvals);
4537
4673
  return { ok: true, group: "inbox", command, details: { approvals } };
4538
4674
  }
@@ -4543,7 +4679,7 @@ async function executeInbox(context, args) {
4543
4679
  const task = takeOption(pending, "--task");
4544
4680
  pending = task.rest;
4545
4681
  requireNoExtraArgs(pending, "rig inbox inputs [--run <id>] [--task <id>]");
4546
- const requests = await listInboxRecords(context, "inputs", { run: run.value, task: task.value });
4682
+ const requests = await withSpinner("Reading input requests from server\u2026", () => listInboxRecords(context, "inputs", { run: run.value, task: task.value }), { outputMode: context.outputMode });
4547
4683
  renderList(context, "inputs", requests);
4548
4684
  return { ok: true, group: "inbox", command, details: { requests } };
4549
4685
  }
@@ -4564,7 +4700,7 @@ async function executeInbox(context, args) {
4564
4700
  if (decision.value !== "approve" && decision.value !== "reject") {
4565
4701
  throw new CliError("decision must be approve or reject.", 1, { hint: "Re-run as `rig inbox approve --run <id> --request <id> --decision approve|reject`." });
4566
4702
  }
4567
- const result = await requestServerJson(context, "/api/inbox/approvals/resolve", {
4703
+ const result = await withSpinner(`Resolving approval ${request.value}\u2026`, () => requestServerJson(context, "/api/inbox/approvals/resolve", {
4568
4704
  method: "POST",
4569
4705
  headers: { "content-type": "application/json" },
4570
4706
  body: JSON.stringify({
@@ -4573,7 +4709,7 @@ async function executeInbox(context, args) {
4573
4709
  decision: decision.value,
4574
4710
  note: note4.value ?? null
4575
4711
  })
4576
- });
4712
+ }), { outputMode: context.outputMode });
4577
4713
  return { ok: true, group: "inbox", command, details: { result } };
4578
4714
  }
4579
4715
  case "respond": {
@@ -4607,7 +4743,7 @@ async function executeInbox(context, args) {
4607
4743
  const [key, ...restValue] = entry.split("=");
4608
4744
  return [key, restValue.join("=")];
4609
4745
  }));
4610
- const result = await requestServerJson(context, "/api/inbox/inputs/respond", {
4746
+ const result = await withSpinner(`Sending response for ${request.value}\u2026`, () => requestServerJson(context, "/api/inbox/inputs/respond", {
4611
4747
  method: "POST",
4612
4748
  headers: { "content-type": "application/json" },
4613
4749
  body: JSON.stringify({
@@ -4615,7 +4751,7 @@ async function executeInbox(context, args) {
4615
4751
  requestId: request.value,
4616
4752
  answers: parsedAnswers
4617
4753
  })
4618
- });
4754
+ }), { outputMode: context.outputMode });
4619
4755
  return { ok: true, group: "inbox", command, details: { result } };
4620
4756
  }
4621
4757
  case "watch": {
@@ -5032,7 +5168,10 @@ async function runRigDoctorChecks(options) {
5032
5168
  const bunVersion = options.bunVersion ?? Bun.version;
5033
5169
  const request = options.requestJson ?? ((pathname, init) => requestServerJson({ projectRoot }, pathname, init));
5034
5170
  const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
5171
+ const progress = options.onProgress ?? (() => {});
5172
+ progress("Checking local toolchain\u2026");
5035
5173
  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`)."));
5174
+ progress("Loading rig.config\u2026");
5036
5175
  const loadedConfig = await loadConfig(projectRoot).catch(() => null);
5037
5176
  const config = loadedConfig ?? loadFallbackConfig(projectRoot);
5038
5177
  const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync10(resolve16(projectRoot, name)));
@@ -5051,6 +5190,7 @@ async function runRigDoctorChecks(options) {
5051
5190
  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));
5052
5191
  let server = null;
5053
5192
  try {
5193
+ progress("Connecting to the selected Rig server\u2026");
5054
5194
  server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
5055
5195
  checks.push(check("server", "Rig server reachable", "pass", `${server.connectionKind} ${server.baseUrl}`));
5056
5196
  } catch (error) {
@@ -5058,18 +5198,21 @@ async function runRigDoctorChecks(options) {
5058
5198
  }
5059
5199
  if (server || options.requestJson) {
5060
5200
  try {
5201
+ progress("Checking server status\u2026");
5061
5202
  const status = await request("/api/server/status");
5062
5203
  checks.push(check("server-status", "server project status", "pass", JSON.stringify(status).slice(0, 180)));
5063
5204
  } catch (error) {
5064
5205
  checks.push(check("server-status", "server project status", "fail", errorMessage(error), "Run `rig doctor` after the selected server is reachable."));
5065
5206
  }
5066
5207
  try {
5208
+ progress("Checking GitHub auth\u2026");
5067
5209
  const auth = await request("/api/github/auth/status");
5068
5210
  checks.push(isAuthenticated2(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>`."));
5069
5211
  } catch (error) {
5070
5212
  checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
5071
5213
  }
5072
5214
  try {
5215
+ progress("Checking GitHub repo permissions\u2026");
5073
5216
  const permissions = await request("/api/github/repo/permissions");
5074
5217
  const allowed = permissionAllowsPr2(permissions);
5075
5218
  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."));
@@ -5077,6 +5220,7 @@ async function runRigDoctorChecks(options) {
5077
5220
  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."));
5078
5221
  }
5079
5222
  try {
5223
+ progress("Checking GitHub issue labels\u2026");
5080
5224
  const labels = await request("/api/workspace/task-labels");
5081
5225
  const ready = labelsReady(labels);
5082
5226
  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."));
@@ -5084,6 +5228,7 @@ async function runRigDoctorChecks(options) {
5084
5228
  checks.push(check("task-labels", "GitHub issue labels", "warn", errorMessage(error), "Run `rig init`/`rig doctor` after label setup is wired on the server."));
5085
5229
  }
5086
5230
  try {
5231
+ progress("Checking task projection\u2026");
5087
5232
  const projection = await request("/api/workspace/task-projection");
5088
5233
  checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
5089
5234
  } catch (error) {
@@ -5092,6 +5237,7 @@ async function runRigDoctorChecks(options) {
5092
5237
  const slug = projectStatusSlug(projectRoot, config);
5093
5238
  if (slug) {
5094
5239
  try {
5240
+ progress("Checking server project checkout\u2026");
5095
5241
  const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
5096
5242
  checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
5097
5243
  } catch (error) {
@@ -5106,6 +5252,7 @@ async function runRigDoctorChecks(options) {
5106
5252
  }
5107
5253
  checks.push(githubProjectsCheck(config));
5108
5254
  checks.push(prMergeCheck(config));
5255
+ progress("Checking Pi installation\u2026");
5109
5256
  const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
5110
5257
  ok: false,
5111
5258
  label: "pi/pi-rig checks",
@@ -6028,7 +6175,7 @@ async function executeGithub(context, args) {
6028
6175
  case "status": {
6029
6176
  if (rest.length > 0)
6030
6177
  throw new CliError("Usage: rig github auth status", 1);
6031
- const status = await getGitHubAuthStatusViaServer(context);
6178
+ const status = await withSpinner("Checking GitHub auth on the server\u2026", () => getGitHubAuthStatusViaServer(context), { outputMode: context.outputMode });
6032
6179
  printPayload(context, status, `GitHub auth: ${status.authenticated ? "authenticated" : "unauthenticated"}`);
6033
6180
  return { ok: true, group: "github", command: "auth status", details: status };
6034
6181
  }
@@ -6039,14 +6186,15 @@ async function executeGithub(context, args) {
6039
6186
  const token = parsed.value?.trim();
6040
6187
  if (!token)
6041
6188
  throw new CliError("Missing --token value.", 1, { hint: "Re-run as `rig github auth token --token <token>`." });
6042
- const result = await postGitHubTokenViaServer(context, token);
6189
+ const result = await withSpinner("Storing GitHub token on the server\u2026", () => postGitHubTokenViaServer(context, token), { outputMode: context.outputMode });
6043
6190
  printPayload(context, result, "GitHub token stored on the selected Rig server.");
6044
6191
  return { ok: true, group: "github", command: "auth token", details: result };
6045
6192
  }
6046
6193
  case "import-gh": {
6047
6194
  if (rest.length > 0)
6048
6195
  throw new CliError("Usage: rig github auth import-gh", 1);
6049
- const result = await postGitHubTokenViaServer(context, readGhToken());
6196
+ const importedToken = readGhToken();
6197
+ const result = await withSpinner("Storing GitHub token on the server\u2026", () => postGitHubTokenViaServer(context, importedToken), { outputMode: context.outputMode });
6050
6198
  printPayload(context, result, "GitHub token imported from gh and stored on the selected Rig server.");
6051
6199
  return { ok: true, group: "github", command: "auth import-gh", details: result };
6052
6200
  }
@@ -6059,7 +6207,7 @@ async function executeGithub(context, args) {
6059
6207
  init_runner();
6060
6208
  async function executeDoctor(context, args) {
6061
6209
  requireNoExtraArgs(args, "rig doctor");
6062
- const checks = await runRigDoctorChecks({ projectRoot: context.projectRoot });
6210
+ const checks = await withSpinner("Running doctor checks\u2026", (update) => runRigDoctorChecks({ projectRoot: context.projectRoot, onProgress: update }), { outputMode: context.outputMode });
6063
6211
  if (context.outputMode === "text") {
6064
6212
  console.log(formatDoctorChecks(checks));
6065
6213
  }
@@ -6366,13 +6514,19 @@ async function executeInspect(context, args) {
6366
6514
  const requiredTask = requireTask(task, "rig inspect logs --task <task-id>");
6367
6515
  const latestRun = listAuthorityRuns(context.projectRoot).map((entry) => readAuthorityRun3(context.projectRoot, entry.runId)).filter((run) => Boolean(run)).filter((run) => run.taskId === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
6368
6516
  if (!latestRun) {
6369
- const serverRuns = await listRunsViaServer(context, { limit: 200 }).catch(() => []);
6370
- const serverRun = serverRuns.filter((run) => String(run.taskId ?? "") === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
6371
- if (!serverRun || typeof serverRun.runId !== "string") {
6517
+ const fallback = await withSpinner(`Finding runs for ${requiredTask} on the server\u2026`, async (update) => {
6518
+ const serverRuns = await listRunsViaServer(context, { limit: 200 }).catch(() => []);
6519
+ const serverRun = serverRuns.filter((run) => String(run.taskId ?? "") === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
6520
+ if (!serverRun || typeof serverRun.runId !== "string")
6521
+ return null;
6522
+ update(`Reading logs for run ${serverRun.runId}\u2026`);
6523
+ const page = await getRunLogsViaServer(context, serverRun.runId, { limit: 500 });
6524
+ return { runId: serverRun.runId, page };
6525
+ }, { outputMode: context.outputMode });
6526
+ if (!fallback) {
6372
6527
  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`." });
6373
6528
  }
6374
- const page = await getRunLogsViaServer(context, serverRun.runId, { limit: 500 });
6375
- const entries = Array.isArray(page.entries) ? page.entries : [];
6529
+ const entries = Array.isArray(fallback.page.entries) ? fallback.page.entries : [];
6376
6530
  if (context.outputMode === "text") {
6377
6531
  for (const entry of entries) {
6378
6532
  const record = entry && typeof entry === "object" ? entry : {};
@@ -6381,9 +6535,9 @@ async function executeInspect(context, args) {
6381
6535
  console.log([title, detail].filter(Boolean).join(" \u2014 "));
6382
6536
  }
6383
6537
  if (entries.length === 0)
6384
- console.log(`(no log entries for run ${serverRun.runId})`);
6538
+ console.log(`(no log entries for run ${fallback.runId})`);
6385
6539
  }
6386
- return { ok: true, group: "inspect", command, details: { task: requiredTask, runId: serverRun.runId, source: "server", entries } };
6540
+ return { ok: true, group: "inspect", command, details: { task: requiredTask, runId: fallback.runId, source: "server", entries } };
6387
6541
  }
6388
6542
  const logsPath = resolve19(resolveAuthorityRunDir2(context.projectRoot, latestRun.runId), "logs.jsonl");
6389
6543
  if (!existsSync12(logsPath)) {
@@ -6441,7 +6595,7 @@ async function executeInspect(context, args) {
6441
6595
  const { value: task, rest: remaining } = takeOption(rest, "--task");
6442
6596
  requireNoExtraArgs(remaining, "rig inspect diff [--task <task-id>]");
6443
6597
  if (task) {
6444
- const files = changedFilesForTask(context.projectRoot, task, false);
6598
+ const files = await withSpinner(`Computing changed files for ${task}\u2026`, async () => changedFilesForTask(context.projectRoot, task, false), { outputMode: context.outputMode });
6445
6599
  for (const file of files) {
6446
6600
  console.log(file);
6447
6601
  }
@@ -7529,7 +7683,12 @@ async function attachRunBundledPiFrontend(context, input) {
7529
7683
  };
7530
7684
  let detached = false;
7531
7685
  try {
7532
- await runPiMain([], {
7686
+ await runPiMain([
7687
+ "--no-extensions",
7688
+ "--no-skills",
7689
+ "--no-prompt-templates",
7690
+ "--no-context-files"
7691
+ ], {
7533
7692
  extensionFactories: [piRigExtensionFactory]
7534
7693
  });
7535
7694
  detached = true;
@@ -7594,8 +7753,9 @@ async function readOperatorSnapshot(context, runId, options = {}) {
7594
7753
  }
7595
7754
  async function attachRunOperatorView(context, input) {
7596
7755
  let steered = false;
7597
- if (input.message?.trim()) {
7598
- await sendRunPiPromptViaServer(context, input.runId, input.message.trim(), "steer").catch(() => steerRunViaServer(context, input.runId, input.message.trim()));
7756
+ const attachMessage = input.message?.trim();
7757
+ if (attachMessage) {
7758
+ await withSpinner("Queueing steering message\u2026", () => sendRunPiPromptViaServer(context, input.runId, attachMessage, "steer").catch(() => steerRunViaServer(context, input.runId, attachMessage)), { outputMode: context.outputMode });
7599
7759
  steered = true;
7600
7760
  }
7601
7761
  if (input.follow && !input.once && input.interactive !== false && context.outputMode === "text" && Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
@@ -7605,7 +7765,7 @@ async function attachRunOperatorView(context, input) {
7605
7765
  });
7606
7766
  }
7607
7767
  const surface = createOperatorSurface({ interactive: input.interactive !== false });
7608
- let snapshot = await readOperatorSnapshot(context, input.runId);
7768
+ let snapshot = await withSpinner(`Connecting to run ${input.runId}\u2026`, () => readOperatorSnapshot(context, input.runId), { outputMode: context.outputMode });
7609
7769
  if (context.outputMode === "text") {
7610
7770
  surface.renderSnapshot(snapshot);
7611
7771
  surface.renderTimeline(snapshot.timeline);
@@ -7656,9 +7816,6 @@ function normalizeRemoteRunDetails(payload) {
7656
7816
  };
7657
7817
  }
7658
7818
  var REMOTE_TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged"]);
7659
- function isRemoteConnectionSelected(projectRoot) {
7660
- return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
7661
- }
7662
7819
  async function listRunsForSelectedConnection(context, options = {}) {
7663
7820
  if (isRemoteConnectionSelected(context.projectRoot)) {
7664
7821
  return { runs: await listRunsViaServer(context, options), source: "server" };
@@ -7739,7 +7896,7 @@ async function executeRun(context, args) {
7739
7896
  switch (command) {
7740
7897
  case "list": {
7741
7898
  requireNoExtraArgs(rest, "rig run list");
7742
- const { runs, source } = await listRunsForSelectedConnection(context, { limit: 100 });
7899
+ const { runs, source } = isRemoteConnectionSelected(context.projectRoot) ? await withSpinner("Reading runs from server\u2026", () => listRunsForSelectedConnection(context, { limit: 100 }), { outputMode: context.outputMode }) : await listRunsForSelectedConnection(context, { limit: 100 });
7743
7900
  if (context.outputMode === "text") {
7744
7901
  printFormattedOutput(formatRunList(runs, { source }));
7745
7902
  await printPendingInboxFooter(context);
@@ -7812,7 +7969,7 @@ async function executeRun(context, args) {
7812
7969
  if (!runId) {
7813
7970
  throw new CliError("run show requires a run id.", 1, { hint: "Run `rig run list` to find run ids, then `rig run show <run-id>`." });
7814
7971
  }
7815
- const record = readAuthorityRun5(context.projectRoot, runId) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, runId).catch(() => ({})));
7972
+ const record = readAuthorityRun5(context.projectRoot, runId) ?? normalizeRemoteRunDetails(await withSpinner(`Reading run ${runId} from server\u2026`, () => getRunDetailsViaServer(context, runId).catch(() => ({})), { outputMode: context.outputMode }));
7816
7973
  if (!record) {
7817
7974
  throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig run list` to see recorded runs." });
7818
7975
  }
@@ -7833,7 +7990,8 @@ async function executeRun(context, args) {
7833
7990
  }
7834
7991
  const renderer = createPiRunStreamRenderer();
7835
7992
  let cursor = null;
7836
- const page = await getRunTimelineViaServer(context, run.value, { limit: 500 });
7993
+ const timelineRunId = run.value;
7994
+ const page = await withSpinner(`Reading timeline for ${timelineRunId}\u2026`, () => getRunTimelineViaServer(context, timelineRunId, { limit: 500 }), { outputMode: context.outputMode });
7837
7995
  const events = Array.isArray(page.entries) ? page.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
7838
7996
  cursor = typeof page.nextCursor === "string" ? page.nextCursor : null;
7839
7997
  if (context.outputMode === "text") {
@@ -7910,8 +8068,9 @@ async function executeRun(context, args) {
7910
8068
  throw new CliError("run attach requires a run id.", 2, { hint: "Run `rig run list` to find run ids, then `rig run attach <run-id> --follow`." });
7911
8069
  }
7912
8070
  let steered = false;
7913
- if (messageOption.value?.trim()) {
7914
- await steerRunViaServer(context, runId, messageOption.value.trim());
8071
+ const steerMessage = messageOption.value?.trim();
8072
+ if (steerMessage) {
8073
+ await withSpinner("Queueing steering message\u2026", () => steerRunViaServer(context, runId, steerMessage), { outputMode: context.outputMode });
7915
8074
  steered = true;
7916
8075
  }
7917
8076
  const attached = await attachRunOperatorView(context, {
@@ -7931,7 +8090,7 @@ async function executeRun(context, args) {
7931
8090
  }
7932
8091
  return { ok: true, group: "run", command };
7933
8092
  }
7934
- const summary = isRemoteConnectionSelected(context.projectRoot) ? buildServerRunStatus(await listRunsViaServer(context, { limit: 100 })) : runStatus(context.projectRoot, runtimeContext);
8093
+ const summary = isRemoteConnectionSelected(context.projectRoot) ? buildServerRunStatus(await withSpinner("Reading run status from server\u2026", () => listRunsViaServer(context, { limit: 100 }), { outputMode: context.outputMode })) : runStatus(context.projectRoot, runtimeContext);
7935
8094
  const activeRuns = Array.isArray(summary.activeRuns) ? summary.activeRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
7936
8095
  const recentRuns = Array.isArray(summary.recentRuns) ? summary.recentRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
7937
8096
  if (context.outputMode === "text") {
@@ -8008,28 +8167,38 @@ async function executeRun(context, args) {
8008
8167
  };
8009
8168
  }
8010
8169
  case "resume": {
8011
- requireNoExtraArgs(rest, "rig run resume");
8170
+ let pending = rest;
8171
+ const runOpt = takeOption(pending, "--run");
8172
+ pending = runOpt.rest;
8173
+ const positional = pending[0] && !pending[0].startsWith("-") ? pending.shift() : undefined;
8174
+ requireNoExtraArgs(pending, "rig run resume [<run-id>] [--run <id>]");
8175
+ const targetRunId = runOpt.value ?? positional ?? null;
8012
8176
  if (context.dryRun) {
8013
8177
  if (context.outputMode === "text") {
8014
8178
  console.log("[dry-run] rig run resume");
8015
8179
  }
8016
8180
  return { ok: true, group: "run", command };
8017
8181
  }
8018
- const resumed = await runResume(context.projectRoot, runtimeContext);
8182
+ const resumed = await runResume(context.projectRoot, runtimeContext, targetRunId);
8019
8183
  if (context.outputMode === "text") {
8020
8184
  console.log(`Resumed run: ${resumed.runId}`);
8021
8185
  }
8022
8186
  return { ok: true, group: "run", command, details: resumed };
8023
8187
  }
8024
8188
  case "restart": {
8025
- requireNoExtraArgs(rest, "rig run restart");
8189
+ let pending = rest;
8190
+ const runOpt = takeOption(pending, "--run");
8191
+ pending = runOpt.rest;
8192
+ const positional = pending[0] && !pending[0].startsWith("-") ? pending.shift() : undefined;
8193
+ requireNoExtraArgs(pending, "rig run restart [<run-id>] [--run <id>]");
8194
+ const targetRunId = runOpt.value ?? positional ?? null;
8026
8195
  if (context.dryRun) {
8027
8196
  if (context.outputMode === "text") {
8028
8197
  console.log("[dry-run] rig run restart");
8029
8198
  }
8030
8199
  return { ok: true, group: "run", command };
8031
8200
  }
8032
- const restarted = await runRestart(context.projectRoot, runtimeContext);
8201
+ const restarted = await runRestart(context.projectRoot, runtimeContext, targetRunId);
8033
8202
  if (context.outputMode === "text") {
8034
8203
  console.log(`Restarted run: ${restarted.runId}`);
8035
8204
  }
@@ -8056,7 +8225,8 @@ async function executeRun(context, args) {
8056
8225
  }
8057
8226
  return { ok: true, group: "run", command, details: { runId, dryRun: true } };
8058
8227
  }
8059
- await steerRunViaServer(context, runId, message2.trim());
8228
+ const trimmedMessage = message2.trim();
8229
+ await withSpinner("Queueing steering message\u2026", () => steerRunViaServer(context, runId, trimmedMessage), { outputMode: context.outputMode });
8060
8230
  if (context.outputMode === "text") {
8061
8231
  console.log(`Steering message queued for ${runId}.`);
8062
8232
  }
@@ -8077,7 +8247,7 @@ async function executeRun(context, args) {
8077
8247
  };
8078
8248
  }
8079
8249
  if (runId) {
8080
- const stopped = await stopRunViaServer(context, runId);
8250
+ const stopped = await withSpinner(`Requesting stop for ${runId}\u2026`, () => stopRunViaServer(context, runId), { outputMode: context.outputMode });
8081
8251
  if (context.outputMode === "text")
8082
8252
  console.log(`Stop requested: ${runId}`);
8083
8253
  return { ok: true, group: "run", command, details: stopped };
@@ -8323,15 +8493,12 @@ async function executeServer(context, args, options) {
8323
8493
 
8324
8494
  // packages/cli/src/commands/stats.ts
8325
8495
  init_runner();
8326
- import pc5 from "picocolors";
8327
- import {
8328
- listAuthorityRuns as listAuthorityRuns3,
8329
- projectRunFromJournal
8330
- } from "@rig/runtime/control-plane/authority-files";
8496
+ import pc6 from "picocolors";
8497
+ import { computeRigStats } from "@rig/runtime/control-plane/native/run-stats";
8331
8498
 
8332
8499
  // packages/cli/src/commands/_help-catalog.ts
8333
8500
  import { intro as intro3, log as log4, note as note4, outro as outro3 } from "@clack/prompts";
8334
- import pc4 from "picocolors";
8501
+ import pc5 from "picocolors";
8335
8502
  var TOP_LEVEL_SECTIONS = [
8336
8503
  {
8337
8504
  title: "Start here",
@@ -8619,13 +8786,13 @@ var ADVANCED_COMMANDS = [
8619
8786
  ];
8620
8787
  var ALL_GROUPS = [...PRIMARY_GROUPS, ...ADVANCED_GROUPS];
8621
8788
  function heading(title) {
8622
- return pc4.bold(pc4.cyan(title));
8789
+ return pc5.bold(pc5.cyan(title));
8623
8790
  }
8624
8791
  function renderRigBanner(version) {
8625
- const m = (s) => pc4.bold(pc4.magenta(s));
8626
- const c = (s) => pc4.bold(pc4.cyan(s));
8627
- const y = (s) => pc4.yellow(s);
8628
- const d = (s) => pc4.dim(s);
8792
+ const m = (s) => pc5.bold(pc5.magenta(s));
8793
+ const c = (s) => pc5.bold(pc5.cyan(s));
8794
+ const y = (s) => pc5.yellow(s);
8795
+ const d = (s) => pc5.dim(s);
8629
8796
  const lines = [
8630
8797
  m(" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ") + d(" \u2591\u2592\u2593\u2588 "),
8631
8798
  m(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D ") + d("\u2593\u2592\u2591"),
@@ -8634,7 +8801,7 @@ function renderRigBanner(version) {
8634
8801
  y(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D") + d(" \u2592\u2591"),
8635
8802
  y(" \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D "),
8636
8803
  "",
8637
- ` ${c("\u25E2\u25E4")} ${pc4.bold("the control rig for autonomous coding agents")} ${m("//")} ${d("you are the operator")}`,
8804
+ ` ${c("\u25E2\u25E4")} ${pc5.bold("the control rig for autonomous coding agents")} ${m("//")} ${d("you are the operator")}`,
8638
8805
  version ? ` ${d(`v${version} \xB7 jack in: rig task run --next`)}` : ` ${d("jack in: rig task run --next")}`
8639
8806
  ];
8640
8807
  return lines.join(`
@@ -8642,7 +8809,7 @@ function renderRigBanner(version) {
8642
8809
  }
8643
8810
  function commandLine(command, description) {
8644
8811
  const commandColumn = command.length >= 38 ? `${command} ` : command.padEnd(38);
8645
- return `${pc4.dim("\u2502")} ${pc4.bold(commandColumn)} ${description}`;
8812
+ return `${pc5.dim("\u2502")} ${pc5.bold(commandColumn)} ${description}`;
8646
8813
  }
8647
8814
  function renderCommandBlock(commands) {
8648
8815
  return commands.map((entry) => commandLine(entry.command, entry.description)).join(`
@@ -8652,37 +8819,37 @@ function renderGroup(group) {
8652
8819
  const lines = [
8653
8820
  `${heading(`rig ${group.name}`)} \u2014 ${group.summary}`,
8654
8821
  "",
8655
- pc4.bold("Usage"),
8822
+ pc5.bold("Usage"),
8656
8823
  ...group.usage.map((line) => ` ${line}`),
8657
8824
  "",
8658
- pc4.bold("Commands"),
8825
+ pc5.bold("Commands"),
8659
8826
  ...group.commands.map((entry) => commandLine(entry.command, entry.description))
8660
8827
  ];
8661
8828
  if (group.examples?.length) {
8662
- lines.push("", pc4.bold("Examples"), ...group.examples.map((line) => ` ${pc4.dim("$")} ${line}`));
8829
+ lines.push("", pc5.bold("Examples"), ...group.examples.map((line) => ` ${pc5.dim("$")} ${line}`));
8663
8830
  }
8664
8831
  if (group.next?.length) {
8665
- lines.push("", pc4.bold("Next steps"), ...group.next.map((line) => ` ${pc4.dim("\u203A")} ${line}`));
8832
+ lines.push("", pc5.bold("Next steps"), ...group.next.map((line) => ` ${pc5.dim("\u203A")} ${line}`));
8666
8833
  }
8667
8834
  if (group.advanced?.length) {
8668
- lines.push("", pc4.bold("Compatibility / advanced"), ...group.advanced.map((line) => ` ${pc4.dim("\u203A")} ${line}`));
8835
+ lines.push("", pc5.bold("Compatibility / advanced"), ...group.advanced.map((line) => ` ${pc5.dim("\u203A")} ${line}`));
8669
8836
  }
8670
8837
  return lines.join(`
8671
8838
  `);
8672
8839
  }
8673
8840
  function renderTopLevelHelp() {
8674
8841
  return [
8675
- `${heading("rig")} ${pc4.dim("\u2014 server-owned task/run control plane for Pi-backed engineering work")}`,
8676
- pc4.dim("Current path: select a server, choose a task, submit a run, attach with native Pi, clear inbox gates."),
8842
+ `${heading("rig")} ${pc5.dim("\u2014 server-owned task/run control plane for Pi-backed engineering work")}`,
8843
+ pc5.dim("Current path: select a server, choose a task, submit a run, attach with native Pi, clear inbox gates."),
8677
8844
  "",
8678
8845
  ...TOP_LEVEL_SECTIONS.flatMap((section) => [
8679
- `${pc4.bold(pc4.magenta(`\u25C7 ${section.title}`))} \u2014 ${pc4.dim(section.subtitle)}`,
8846
+ `${pc5.bold(pc5.magenta(`\u25C7 ${section.title}`))} \u2014 ${pc5.dim(section.subtitle)}`,
8680
8847
  renderCommandBlock(section.commands),
8681
8848
  ""
8682
8849
  ]),
8683
- pc4.dim("More: `rig help --advanced` for dev/compatibility commands; `rig <group> --help` for rich per-group help; `rig --version` for the installed version."),
8850
+ pc5.dim("More: `rig help --advanced` for dev/compatibility commands; `rig <group> --help` for rich per-group help; `rig --version` for the installed version."),
8684
8851
  "",
8685
- pc4.bold("Global options"),
8852
+ pc5.bold("Global options"),
8686
8853
  commandLine("--project <path>", "Use a project root instead of auto-discovery."),
8687
8854
  commandLine("--json", "Emit structured output for scripts/agents."),
8688
8855
  commandLine("--dry-run", "Print the command plan without mutating state.")
@@ -8693,16 +8860,16 @@ function renderAdvancedHelp() {
8693
8860
  return [
8694
8861
  `${heading("rig advanced")} \u2014 compatibility, diagnostics, and internal surfaces`,
8695
8862
  "",
8696
- pc4.bold("Primary groups"),
8863
+ pc5.bold("Primary groups"),
8697
8864
  " init, server, task, run, inbox, repo, plugin, inspect, stats, doctor, github",
8698
8865
  "",
8699
- pc4.bold("Advanced commands"),
8866
+ pc5.bold("Advanced commands"),
8700
8867
  ...ADVANCED_COMMANDS.map((entry) => commandLine(entry.command, entry.description)),
8701
8868
  "",
8702
- pc4.bold("Advanced groups"),
8869
+ pc5.bold("Advanced groups"),
8703
8870
  ...ADVANCED_GROUPS.map((group) => commandLine(group.name, group.summary)),
8704
8871
  "",
8705
- pc4.dim("All groups remain callable. Prefer `rig server`, `rig task`, `rig run`, and `rig inbox` for day-to-day work.")
8872
+ pc5.dim("All groups remain callable. Prefer `rig server`, `rig task`, `rig run`, and `rig inbox` for day-to-day work.")
8706
8873
  ].join(`
8707
8874
  `);
8708
8875
  }
@@ -8816,90 +8983,6 @@ function parseSinceOption(value, now = new Date) {
8816
8983
  }
8817
8984
  return new Date(parsed);
8818
8985
  }
8819
- function median(values) {
8820
- if (values.length === 0)
8821
- return null;
8822
- const sorted = [...values].sort((left, right) => left - right);
8823
- const middle = Math.floor(sorted.length / 2);
8824
- const upper = sorted[middle];
8825
- if (sorted.length % 2 === 1)
8826
- return upper;
8827
- return (sorted[middle - 1] + upper) / 2;
8828
- }
8829
- function rate(part, total) {
8830
- if (total === 0)
8831
- return null;
8832
- return part / total;
8833
- }
8834
- function parseTimestamp(value) {
8835
- if (!value)
8836
- return null;
8837
- const parsed = Date.parse(value);
8838
- return Number.isFinite(parsed) ? parsed : null;
8839
- }
8840
- function computeRigStats(projectRoot, options = {}) {
8841
- const since = options.since ?? null;
8842
- const sinceMs = since ? since.getTime() : null;
8843
- const runs = listAuthorityRuns3(projectRoot).filter((run) => {
8844
- if (sinceMs === null)
8845
- return true;
8846
- const createdAt = parseTimestamp(run.createdAt) ?? parseTimestamp(run.updatedAt);
8847
- return createdAt !== null && createdAt >= sinceMs;
8848
- });
8849
- const statusCounts = {};
8850
- const completionDurations = [];
8851
- let steeringTotal = 0;
8852
- let stallTotal = 0;
8853
- let approvalsApproved = 0;
8854
- let approvalsRejected = 0;
8855
- let approvalsPending = 0;
8856
- for (const run of runs) {
8857
- const status = run.status ?? "unknown";
8858
- statusCounts[status] = (statusCounts[status] ?? 0) + 1;
8859
- if (status === "completed") {
8860
- const createdAt = parseTimestamp(run.createdAt);
8861
- const completedAt = parseTimestamp(run.completedAt);
8862
- if (createdAt !== null && completedAt !== null && completedAt >= createdAt) {
8863
- completionDurations.push(completedAt - createdAt);
8864
- }
8865
- }
8866
- const projection = projectRunFromJournal(projectRoot, run.runId);
8867
- if (!projection)
8868
- continue;
8869
- steeringTotal += projection.steeringCount;
8870
- stallTotal += projection.stallCount;
8871
- approvalsPending += projection.pendingApprovals.length;
8872
- for (const resolved of projection.resolvedApprovals) {
8873
- if (resolved.decision === "approve")
8874
- approvalsApproved += 1;
8875
- else
8876
- approvalsRejected += 1;
8877
- }
8878
- }
8879
- const totalRuns = runs.length;
8880
- const completedRuns = statusCounts["completed"] ?? 0;
8881
- const failedRuns = statusCounts["failed"] ?? 0;
8882
- const needsAttentionRuns = statusCounts["needs-attention"] ?? 0;
8883
- return {
8884
- since: since ? since.toISOString() : null,
8885
- totalRuns,
8886
- statusCounts,
8887
- completedRuns,
8888
- failedRuns,
8889
- needsAttentionRuns,
8890
- completionRate: rate(completedRuns, totalRuns),
8891
- failureRate: rate(failedRuns, totalRuns),
8892
- needsAttentionRate: rate(needsAttentionRuns, totalRuns),
8893
- medianCompletionMs: median(completionDurations),
8894
- steeringTotal,
8895
- steeringPerRun: totalRuns === 0 ? null : steeringTotal / totalRuns,
8896
- stallTotal,
8897
- approvalsRequested: approvalsApproved + approvalsRejected + approvalsPending,
8898
- approvalsApproved,
8899
- approvalsRejected,
8900
- approvalsPending
8901
- };
8902
- }
8903
8986
  function formatPercent(value) {
8904
8987
  if (value === null)
8905
8988
  return "\u2014";
@@ -8934,11 +9017,11 @@ function formatStatsReport(stats) {
8934
9017
  const keyWidth = Math.max(...rows.map(([key]) => key.length));
8935
9018
  const lines = [
8936
9019
  formatSection("Fleet stats", window),
8937
- ...rows.map(([key, value]) => `${pc5.dim("\u2502")} ${pc5.dim(key.padEnd(keyWidth + 2))} ${value}`)
9020
+ ...rows.map(([key, value]) => `${pc6.dim("\u2502")} ${pc6.dim(key.padEnd(keyWidth + 2))} ${value}`)
8938
9021
  ];
8939
9022
  const otherStatuses = Object.entries(stats.statusCounts).filter(([status]) => !["completed", "failed", "needs-attention"].includes(status)).sort(([, left], [, right]) => right - left);
8940
9023
  if (otherStatuses.length > 0) {
8941
- lines.push(`${pc5.dim("\u2502")} ${pc5.dim("other".padEnd(keyWidth + 2))} ${otherStatuses.map(([status, count]) => `${status}:${count}`).join(" ")}`);
9024
+ lines.push(`${pc6.dim("\u2502")} ${pc6.dim("other".padEnd(keyWidth + 2))} ${otherStatuses.map(([status, count]) => `${status}:${count}`).join(" ")}`);
8942
9025
  }
8943
9026
  lines.push("", ...formatNextSteps([
8944
9027
  "Inspect a run: `rig run list` then `rig run show <run-id>`",
@@ -8957,7 +9040,8 @@ async function executeStats(context, args) {
8957
9040
  const sinceResult = takeOption(pending, "--since");
8958
9041
  requireNoExtraArgs(sinceResult.rest, "rig stats [show] [--since <7d|30d|ISO date>]");
8959
9042
  const since = parseSinceOption(sinceResult.value);
8960
- const stats = computeRigStats(context.projectRoot, { since });
9043
+ const remoteStats = isRemoteConnectionSelected(context.projectRoot);
9044
+ 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 });
8961
9045
  if (context.outputMode === "text") {
8962
9046
  printFormattedOutput(formatStatsReport(stats));
8963
9047
  }
@@ -9212,7 +9296,7 @@ async function executeTask(context, args, options) {
9212
9296
  pending = rawResult.rest;
9213
9297
  const { filters, rest: remaining } = parseTaskFilters(pending);
9214
9298
  requireNoExtraArgs(remaining, "rig task list [--raw] [--assignee <login|me|@me>] [--state open|closed] [--status <status>] [--limit <n>]");
9215
- const tasks = await listWorkspaceTasksViaServer(context, filters);
9299
+ const tasks = await withSpinner("Reading tasks from server\u2026", () => listWorkspaceTasksViaServer(context, filters), { outputMode: context.outputMode });
9216
9300
  if (context.outputMode === "text") {
9217
9301
  const renderedTasks = rawResult.value ? tasks.map((task) => summarizeTask(task, { raw: true })) : tasks.map((task) => summarizeTask(task));
9218
9302
  printFormattedOutput(formatTaskList(renderedTasks, { raw: rawResult.value }));
@@ -9236,7 +9320,7 @@ async function executeTask(context, args, options) {
9236
9320
  const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
9237
9321
  if (!taskId3)
9238
9322
  throw new CliError("task show requires a task id.", 2, { hint: "Run `rig task list` to find ids, then `rig task show <id>`." });
9239
- const task = await getWorkspaceTaskViaServer(context, taskId3);
9323
+ const task = await withSpinner(`Reading task ${taskId3} from server\u2026`, () => getWorkspaceTaskViaServer(context, taskId3), { outputMode: context.outputMode });
9240
9324
  if (!task)
9241
9325
  throw new CliError(`Task not found: ${taskId3}`, 3, { hint: "Run `rig task list` to see available tasks." });
9242
9326
  const summary = summarizeTask(task, { raw: true });
@@ -9248,7 +9332,7 @@ async function executeTask(context, args, options) {
9248
9332
  case "next": {
9249
9333
  const { filters, rest: remaining } = parseTaskFilters(rest);
9250
9334
  requireNoExtraArgs(remaining, "rig task next [--assignee <login|me|@me>] [--state open|closed] [--status <status>] [--limit <n>]");
9251
- const selected = await selectNextWorkspaceTaskViaServer(context, filters);
9335
+ const selected = await withSpinner("Selecting next ready task\u2026", () => selectNextWorkspaceTaskViaServer(context, filters), { outputMode: context.outputMode });
9252
9336
  if (context.outputMode === "text") {
9253
9337
  if (selected.task) {
9254
9338
  printFormattedOutput(formatTaskCard(summarizeTask(selected.task, { raw: true }), { title: "Selected task", selected: true }));
@@ -9398,7 +9482,7 @@ async function executeTask(context, args, options) {
9398
9482
  let selectedTask = null;
9399
9483
  let selectedTaskId = normalizeTaskRunTaskId(taskResult.value) ?? positionalTaskId;
9400
9484
  if (nextResult.value) {
9401
- const selected = await selectNextWorkspaceTaskViaServer(context, filterResult.filters);
9485
+ const selected = await withSpinner("Selecting next ready task\u2026", () => selectNextWorkspaceTaskViaServer(context, filterResult.filters), { outputMode: context.outputMode });
9402
9486
  selectedTask = selected.task;
9403
9487
  selectedTaskId = selected.task ? readTaskId(selected.task) : null;
9404
9488
  if (!selectedTaskId) {
@@ -9409,7 +9493,7 @@ async function executeTask(context, args, options) {
9409
9493
  if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
9410
9494
  throw new CliError("task run requires an interactive terminal to pick a task; pass --next, --task <id>, or --initial-prompt/--title for an ad hoc run.", 2);
9411
9495
  }
9412
- const tasks = await listWorkspaceTasksViaServer(context, filterResult.filters);
9496
+ const tasks = await withSpinner("Reading tasks from server\u2026", () => listWorkspaceTasksViaServer(context, filterResult.filters), { outputMode: context.outputMode });
9413
9497
  selectedTask = await selectTaskWithTextPicker(tasks);
9414
9498
  selectedTaskId = selectedTask ? readTaskId(selectedTask) : null;
9415
9499
  if (!selectedTaskId) {
@@ -9419,13 +9503,13 @@ async function executeTask(context, args, options) {
9419
9503
  await runProjectMainSyncPreflight(context, { disabled: skipProjectSyncResult.value });
9420
9504
  const projectDefaults = await loadTaskRunProjectDefaults(context.projectRoot);
9421
9505
  const runtimeAdapter = normalizeRuntimeAdapter(runtimeAdapterResult.value ?? projectDefaults.runtimeAdapter);
9422
- await runFastTaskRunPreflight(context, {
9506
+ await withSpinner("Running task-run preflight\u2026", () => runFastTaskRunPreflight(context, {
9423
9507
  taskId: selectedTaskId,
9424
9508
  runtimeAdapter
9425
- });
9509
+ }), { outputMode: context.outputMode });
9426
9510
  const dirtyBaseline = await resolveDirtyBaselineForTaskRun(context, dirtyBaselineResult.value);
9427
9511
  const prMode = noPrResult.value ? "off" : normalizePrMode(prResult.value) ?? projectDefaults.prMode;
9428
- const submitted = await submitTaskRunViaServer(context, {
9512
+ const submitted = await withSpinner("Dispatching run\u2026", () => submitTaskRunViaServer(context, {
9429
9513
  runId: context.runId,
9430
9514
  taskId: selectedTaskId ?? undefined,
9431
9515
  title: titleResult.value ?? undefined,
@@ -9436,7 +9520,7 @@ async function executeTask(context, args, options) {
9436
9520
  initialPrompt: initialPromptResult.value ?? undefined,
9437
9521
  baselineMode: dirtyBaseline.mode,
9438
9522
  prMode
9439
- });
9523
+ }), { outputMode: context.outputMode });
9440
9524
  let attachDetails = null;
9441
9525
  if (!detachResult.value && context.outputMode === "text") {
9442
9526
  printFormattedOutput(formatSubmittedRun({
@@ -11614,7 +11698,7 @@ async function executeSetup(context, args) {
11614
11698
  case "check":
11615
11699
  requireNoExtraArgs(rest, `rig setup ${command}`);
11616
11700
  {
11617
- const checks = await withMutedConsole(context.outputMode === "json", () => runSetupCheck(context.projectRoot));
11701
+ const checks = await withMutedConsole(context.outputMode === "json", () => runSetupCheck(context.projectRoot, context.outputMode));
11618
11702
  return { ok: true, group: "setup", command, details: { checks, failures: countDoctorFailures(checks) } };
11619
11703
  }
11620
11704
  case "setup":
@@ -11623,7 +11707,7 @@ async function executeSetup(context, args) {
11623
11707
  return { ok: true, group: "setup", command };
11624
11708
  case "preflight":
11625
11709
  requireNoExtraArgs(rest, "rig setup preflight");
11626
- await withMutedConsole(context.outputMode === "json", () => runSetupPreflight(context.projectRoot));
11710
+ await withMutedConsole(context.outputMode === "json", () => runSetupPreflight(context.projectRoot, context.outputMode));
11627
11711
  return { ok: true, group: "setup", command };
11628
11712
  default:
11629
11713
  throw new CliError(`Unknown setup command: ${command}`, 1, { hint: "Run `rig setup --help` \u2014 commands are bootstrap|check|preflight." });
@@ -11644,8 +11728,8 @@ function runSetupInit(projectRoot) {
11644
11728
  }
11645
11729
  console.log("Harness directories ready.");
11646
11730
  }
11647
- async function runSetupCheck(projectRoot) {
11648
- const doctorChecks = await runRigDoctorChecks({ projectRoot });
11731
+ async function runSetupCheck(projectRoot, outputMode = "text") {
11732
+ const doctorChecks = await withSpinner("Running setup checks\u2026", (update) => runRigDoctorChecks({ projectRoot, onProgress: update }), { outputMode });
11649
11733
  console.log(formatDoctorChecks(doctorChecks));
11650
11734
  const failures = countDoctorFailures(doctorChecks);
11651
11735
  if (failures > 0) {
@@ -11653,8 +11737,8 @@ async function runSetupCheck(projectRoot) {
11653
11737
  }
11654
11738
  return doctorChecks;
11655
11739
  }
11656
- async function runSetupPreflight(projectRoot) {
11657
- await runSetupCheck(projectRoot);
11740
+ async function runSetupPreflight(projectRoot, outputMode = "text") {
11741
+ await runSetupCheck(projectRoot, outputMode);
11658
11742
  const validationRoot = resolve23(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
11659
11743
  if (existsSync15(validationRoot)) {
11660
11744
  const validators = readdirSync3(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());