@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.
@@ -172,6 +172,10 @@ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
172
172
  import { resolve as resolve2 } from "path";
173
173
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
174
174
  var scopedGitHubBearerTokens = new Map;
175
+ var serverPhaseListener = null;
176
+ function reportServerPhase(label) {
177
+ serverPhaseListener?.(label);
178
+ }
175
179
  function cleanToken(value) {
176
180
  const trimmed = value?.trim();
177
181
  return trimmed ? trimmed : null;
@@ -218,6 +222,7 @@ async function ensureServerForCli(projectRoot) {
218
222
  try {
219
223
  const selected = resolveSelectedConnection(projectRoot);
220
224
  if (selected?.connection.kind === "remote") {
225
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
221
226
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
222
227
  const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
223
228
  return {
@@ -227,6 +232,7 @@ async function ensureServerForCli(projectRoot) {
227
232
  serverProjectRoot
228
233
  };
229
234
  }
235
+ reportServerPhase("Starting local Rig server\u2026");
230
236
  const connection = await ensureLocalRigServerConnection(projectRoot);
231
237
  return {
232
238
  baseUrl: connection.baseUrl,
@@ -333,6 +339,7 @@ async function requestServerJson(context, pathname, init = {}) {
333
339
  const headers = mergeHeaders(init.headers, server.authToken);
334
340
  if (server.serverProjectRoot)
335
341
  headers.set("x-rig-project-root", server.serverProjectRoot);
342
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
336
343
  let response;
337
344
  try {
338
345
  response = await fetch(`${server.baseUrl}${pathname}`, {
@@ -842,7 +849,10 @@ async function runRigDoctorChecks(options) {
842
849
  const bunVersion = options.bunVersion ?? Bun.version;
843
850
  const request = options.requestJson ?? ((pathname, init) => requestServerJson({ projectRoot }, pathname, init));
844
851
  const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
852
+ const progress = options.onProgress ?? (() => {});
853
+ progress("Checking local toolchain\u2026");
845
854
  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`)."));
855
+ progress("Loading rig.config\u2026");
846
856
  const loadedConfig = await loadConfig(projectRoot).catch(() => null);
847
857
  const config = loadedConfig ?? loadFallbackConfig(projectRoot);
848
858
  const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve5(projectRoot, name)));
@@ -861,6 +871,7 @@ async function runRigDoctorChecks(options) {
861
871
  checks.push(selected ? check("connection", "selected server connection", "pass", selected.connection.kind === "remote" ? selected.connection.baseUrl : "local auto") : check("connection", "selected server", repo ? "fail" : "warn", repo ? "selected alias is missing" : "will auto-start local server", repo ? "Run `rig server list` and `rig server use <alias|local>`." : undefined));
862
872
  let server = null;
863
873
  try {
874
+ progress("Connecting to the selected Rig server\u2026");
864
875
  server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
865
876
  checks.push(check("server", "Rig server reachable", "pass", `${server.connectionKind} ${server.baseUrl}`));
866
877
  } catch (error) {
@@ -868,18 +879,21 @@ async function runRigDoctorChecks(options) {
868
879
  }
869
880
  if (server || options.requestJson) {
870
881
  try {
882
+ progress("Checking server status\u2026");
871
883
  const status = await request("/api/server/status");
872
884
  checks.push(check("server-status", "server project status", "pass", JSON.stringify(status).slice(0, 180)));
873
885
  } catch (error) {
874
886
  checks.push(check("server-status", "server project status", "fail", errorMessage(error), "Run `rig doctor` after the selected server is reachable."));
875
887
  }
876
888
  try {
889
+ progress("Checking GitHub auth\u2026");
877
890
  const auth = await request("/api/github/auth/status");
878
891
  checks.push(isAuthenticated(auth) ? check("github-auth", "GitHub auth", "pass") : check("github-auth", "GitHub auth", "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
879
892
  } catch (error) {
880
893
  checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
881
894
  }
882
895
  try {
896
+ progress("Checking GitHub repo permissions\u2026");
883
897
  const permissions = await request("/api/github/repo/permissions");
884
898
  const allowed = permissionAllowsPr(permissions);
885
899
  checks.push(allowed === true ? check("github-repo-permissions", "GitHub repo PR permissions", "pass", JSON.stringify(permissions).slice(0, 180)) : allowed === false ? check("github-repo-permissions", "GitHub repo PR permissions", "fail", JSON.stringify(permissions).slice(0, 180), "Grant the selected GitHub token permission to push branches, open PRs, and merge according to repo rules.") : check("github-repo-permissions", "GitHub repo PR permissions", "warn", JSON.stringify(permissions).slice(0, 180), "Confirm the selected token can push branches and open PRs."));
@@ -887,6 +901,7 @@ async function runRigDoctorChecks(options) {
887
901
  checks.push(check("github-repo-permissions", "GitHub repo PR permissions", "warn", errorMessage(error), "Ensure the server exposes repo permission checks and the token can open PRs."));
888
902
  }
889
903
  try {
904
+ progress("Checking GitHub issue labels\u2026");
890
905
  const labels = await request("/api/workspace/task-labels");
891
906
  const ready = labelsReady(labels);
892
907
  checks.push(ready === false ? check("task-labels", "GitHub issue labels", "fail", JSON.stringify(labels).slice(0, 180), "Let Rig create required labels or create the configured lifecycle labels manually.") : check("task-labels", "GitHub issue labels", ready === true ? "pass" : "warn", JSON.stringify(labels).slice(0, 180), "Confirm required Rig lifecycle labels exist."));
@@ -894,6 +909,7 @@ async function runRigDoctorChecks(options) {
894
909
  checks.push(check("task-labels", "GitHub issue labels", "warn", errorMessage(error), "Run `rig init`/`rig doctor` after label setup is wired on the server."));
895
910
  }
896
911
  try {
912
+ progress("Checking task projection\u2026");
897
913
  const projection = await request("/api/workspace/task-projection");
898
914
  checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
899
915
  } catch (error) {
@@ -902,6 +918,7 @@ async function runRigDoctorChecks(options) {
902
918
  const slug = projectStatusSlug(projectRoot, config);
903
919
  if (slug) {
904
920
  try {
921
+ progress("Checking server project checkout\u2026");
905
922
  const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
906
923
  checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
907
924
  } catch (error) {
@@ -916,6 +933,7 @@ async function runRigDoctorChecks(options) {
916
933
  }
917
934
  checks.push(githubProjectsCheck(config));
918
935
  checks.push(prMergeCheck(config));
936
+ progress("Checking Pi installation\u2026");
919
937
  const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
920
938
  ok: false,
921
939
  label: "pi/pi-rig checks",
@@ -167,6 +167,15 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
167
167
 
168
168
  // packages/cli/src/commands/_server-client.ts
169
169
  var scopedGitHubBearerTokens = new Map;
170
+ var serverPhaseListener = null;
171
+ function setServerPhaseListener(listener) {
172
+ const previous = serverPhaseListener;
173
+ serverPhaseListener = listener;
174
+ return previous;
175
+ }
176
+ function reportServerPhase(label) {
177
+ serverPhaseListener?.(label);
178
+ }
170
179
  function cleanToken(value) {
171
180
  const trimmed = value?.trim();
172
181
  return trimmed ? trimmed : null;
@@ -209,6 +218,7 @@ async function ensureServerForCli(projectRoot) {
209
218
  try {
210
219
  const selected = resolveSelectedConnection(projectRoot);
211
220
  if (selected?.connection.kind === "remote") {
221
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
212
222
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
213
223
  const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
214
224
  return {
@@ -218,6 +228,7 @@ async function ensureServerForCli(projectRoot) {
218
228
  serverProjectRoot
219
229
  };
220
230
  }
231
+ reportServerPhase("Starting local Rig server\u2026");
221
232
  const connection = await ensureLocalRigServerConnection(projectRoot);
222
233
  return {
223
234
  baseUrl: connection.baseUrl,
@@ -324,6 +335,7 @@ async function requestServerJson(context, pathname, init = {}) {
324
335
  const headers = mergeHeaders(init.headers, server.authToken);
325
336
  if (server.serverProjectRoot)
326
337
  headers.set("x-rig-project-root", server.serverProjectRoot);
338
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
327
339
  let response;
328
340
  try {
329
341
  response = await fetch(`${server.baseUrl}${pathname}`, {
@@ -370,6 +382,127 @@ async function getRunLogsViaServer(context, runId, options = {}) {
370
382
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
371
383
  }
372
384
 
385
+ // packages/cli/src/commands/_async-ui.ts
386
+ import pc from "picocolors";
387
+
388
+ // packages/cli/src/commands/_spinner.ts
389
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
390
+ function createTtySpinner(input) {
391
+ const output = input.output ?? process.stdout;
392
+ const isTty = output.isTTY === true;
393
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
394
+ let label = input.label;
395
+ let frame = 0;
396
+ let paused = false;
397
+ let stopped = false;
398
+ let lastPrintedLabel = "";
399
+ const render = () => {
400
+ if (stopped || paused)
401
+ return;
402
+ if (!isTty) {
403
+ if (label !== lastPrintedLabel) {
404
+ output.write(`${label}
405
+ `);
406
+ lastPrintedLabel = label;
407
+ }
408
+ return;
409
+ }
410
+ frame = (frame + 1) % frames.length;
411
+ const glyph = frames[frame] ?? frames[0] ?? "";
412
+ output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
413
+ };
414
+ const clearLine = () => {
415
+ if (isTty)
416
+ output.write("\r\x1B[2K");
417
+ };
418
+ render();
419
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
420
+ return {
421
+ setLabel(next) {
422
+ label = next;
423
+ render();
424
+ },
425
+ pause() {
426
+ paused = true;
427
+ clearLine();
428
+ },
429
+ resume() {
430
+ if (stopped)
431
+ return;
432
+ paused = false;
433
+ render();
434
+ },
435
+ stop(finalLine) {
436
+ if (stopped)
437
+ return;
438
+ stopped = true;
439
+ if (timer)
440
+ clearInterval(timer);
441
+ clearLine();
442
+ if (finalLine)
443
+ output.write(`${finalLine}
444
+ `);
445
+ }
446
+ };
447
+ }
448
+
449
+ // packages/cli/src/commands/_async-ui.ts
450
+ var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
451
+ var DONE_SYMBOL = pc.green("\u25C7");
452
+ var FAIL_SYMBOL = pc.red("\u25A0");
453
+ var activeUpdate = null;
454
+ async function withSpinner(label, work, options = {}) {
455
+ if (options.outputMode === "json") {
456
+ return work(() => {});
457
+ }
458
+ if (activeUpdate) {
459
+ const outer = activeUpdate;
460
+ outer(label);
461
+ return work(outer);
462
+ }
463
+ const output = options.output ?? process.stderr;
464
+ const isTty = output.isTTY === true;
465
+ let lastLabel = label;
466
+ if (!isTty) {
467
+ output.write(`${label}
468
+ `);
469
+ const update2 = (next) => {
470
+ lastLabel = next;
471
+ };
472
+ activeUpdate = update2;
473
+ const previousListener2 = setServerPhaseListener(update2);
474
+ try {
475
+ return await work(update2);
476
+ } finally {
477
+ activeUpdate = null;
478
+ setServerPhaseListener(previousListener2);
479
+ }
480
+ }
481
+ const spinner = createTtySpinner({
482
+ label,
483
+ output,
484
+ frames: CLACK_SPINNER_FRAMES,
485
+ styleFrame: (frame) => pc.magenta(frame)
486
+ });
487
+ const update = (next) => {
488
+ lastLabel = next;
489
+ spinner.setLabel(next);
490
+ };
491
+ activeUpdate = update;
492
+ const previousListener = setServerPhaseListener(update);
493
+ try {
494
+ const result = await work(update);
495
+ spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
496
+ return result;
497
+ } catch (error) {
498
+ spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
499
+ throw error;
500
+ } finally {
501
+ activeUpdate = null;
502
+ setServerPhaseListener(previousListener);
503
+ }
504
+ }
505
+
373
506
  // packages/cli/src/commands/inspect.ts
374
507
  async function executeInspect(context, args) {
375
508
  const [command = "failures", ...rest] = args;
@@ -380,13 +513,19 @@ async function executeInspect(context, args) {
380
513
  const requiredTask = requireTask(task, "rig inspect logs --task <task-id>");
381
514
  const latestRun = listAuthorityRuns(context.projectRoot).map((entry) => readAuthorityRun(context.projectRoot, entry.runId)).filter((run) => Boolean(run)).filter((run) => run.taskId === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
382
515
  if (!latestRun) {
383
- const serverRuns = await listRunsViaServer(context, { limit: 200 }).catch(() => []);
384
- const serverRun = serverRuns.filter((run) => String(run.taskId ?? "") === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
385
- if (!serverRun || typeof serverRun.runId !== "string") {
516
+ const fallback = await withSpinner(`Finding runs for ${requiredTask} on the server\u2026`, async (update) => {
517
+ const serverRuns = await listRunsViaServer(context, { limit: 200 }).catch(() => []);
518
+ const serverRun = serverRuns.filter((run) => String(run.taskId ?? "") === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
519
+ if (!serverRun || typeof serverRun.runId !== "string")
520
+ return null;
521
+ update(`Reading logs for run ${serverRun.runId}\u2026`);
522
+ const page = await getRunLogsViaServer(context, serverRun.runId, { limit: 500 });
523
+ return { runId: serverRun.runId, page };
524
+ }, { outputMode: context.outputMode });
525
+ if (!fallback) {
386
526
  throw new CliError(`No runs found for ${requiredTask} (local or on the selected server).`, 1, { hint: "Start one with `rig task run --task <id>`, or check `rig run list`." });
387
527
  }
388
- const page = await getRunLogsViaServer(context, serverRun.runId, { limit: 500 });
389
- const entries = Array.isArray(page.entries) ? page.entries : [];
528
+ const entries = Array.isArray(fallback.page.entries) ? fallback.page.entries : [];
390
529
  if (context.outputMode === "text") {
391
530
  for (const entry of entries) {
392
531
  const record = entry && typeof entry === "object" ? entry : {};
@@ -395,9 +534,9 @@ async function executeInspect(context, args) {
395
534
  console.log([title, detail].filter(Boolean).join(" \u2014 "));
396
535
  }
397
536
  if (entries.length === 0)
398
- console.log(`(no log entries for run ${serverRun.runId})`);
537
+ console.log(`(no log entries for run ${fallback.runId})`);
399
538
  }
400
- return { ok: true, group: "inspect", command, details: { task: requiredTask, runId: serverRun.runId, source: "server", entries } };
539
+ return { ok: true, group: "inspect", command, details: { task: requiredTask, runId: fallback.runId, source: "server", entries } };
401
540
  }
402
541
  const logsPath = resolve3(resolveAuthorityRunDir(context.projectRoot, latestRun.runId), "logs.jsonl");
403
542
  if (!existsSync3(logsPath)) {
@@ -455,7 +594,7 @@ async function executeInspect(context, args) {
455
594
  const { value: task, rest: remaining } = takeOption(rest, "--task");
456
595
  requireNoExtraArgs(remaining, "rig inspect diff [--task <task-id>]");
457
596
  if (task) {
458
- const files = changedFilesForTask(context.projectRoot, task, false);
597
+ const files = await withSpinner(`Computing changed files for ${task}\u2026`, async () => changedFilesForTask(context.projectRoot, task, false), { outputMode: context.outputMode });
459
598
  for (const file of files) {
460
599
  console.log(file);
461
600
  }