@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.
@@ -194,6 +194,15 @@ function isRemoteConnectionSelected(projectRoot) {
194
194
 
195
195
  // packages/cli/src/commands/_server-client.ts
196
196
  var scopedGitHubBearerTokens = new Map;
197
+ var serverPhaseListener = null;
198
+ function setServerPhaseListener(listener) {
199
+ const previous = serverPhaseListener;
200
+ serverPhaseListener = listener;
201
+ return previous;
202
+ }
203
+ function reportServerPhase(label) {
204
+ serverPhaseListener?.(label);
205
+ }
197
206
  function cleanToken(value) {
198
207
  const trimmed = value?.trim();
199
208
  return trimmed ? trimmed : null;
@@ -236,6 +245,7 @@ async function ensureServerForCli(projectRoot) {
236
245
  try {
237
246
  const selected = resolveSelectedConnection(projectRoot);
238
247
  if (selected?.connection.kind === "remote") {
248
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
239
249
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
240
250
  const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
241
251
  return {
@@ -245,6 +255,7 @@ async function ensureServerForCli(projectRoot) {
245
255
  serverProjectRoot
246
256
  };
247
257
  }
258
+ reportServerPhase("Starting local Rig server\u2026");
248
259
  const connection = await ensureLocalRigServerConnection(projectRoot);
249
260
  return {
250
261
  baseUrl: connection.baseUrl,
@@ -351,6 +362,7 @@ async function requestServerJson(context, pathname, init = {}) {
351
362
  const headers = mergeHeaders(init.headers, server.authToken);
352
363
  if (server.serverProjectRoot)
353
364
  headers.set("x-rig-project-root", server.serverProjectRoot);
365
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
354
366
  let response;
355
367
  try {
356
368
  response = await fetch(`${server.baseUrl}${pathname}`, {
@@ -811,7 +823,12 @@ async function attachRunBundledPiFrontend(context, input) {
811
823
  };
812
824
  let detached = false;
813
825
  try {
814
- await runPiMain([], {
826
+ await runPiMain([
827
+ "--no-extensions",
828
+ "--no-skills",
829
+ "--no-prompt-templates",
830
+ "--no-context-files"
831
+ ], {
815
832
  extensionFactories: [piRigExtensionFactory]
816
833
  });
817
834
  detached = true;
@@ -834,6 +851,127 @@ async function attachRunBundledPiFrontend(context, input) {
834
851
  };
835
852
  }
836
853
 
854
+ // packages/cli/src/commands/_async-ui.ts
855
+ import pc from "picocolors";
856
+
857
+ // packages/cli/src/commands/_spinner.ts
858
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
859
+ function createTtySpinner(input) {
860
+ const output = input.output ?? process.stdout;
861
+ const isTty = output.isTTY === true;
862
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
863
+ let label = input.label;
864
+ let frame = 0;
865
+ let paused = false;
866
+ let stopped = false;
867
+ let lastPrintedLabel = "";
868
+ const render = () => {
869
+ if (stopped || paused)
870
+ return;
871
+ if (!isTty) {
872
+ if (label !== lastPrintedLabel) {
873
+ output.write(`${label}
874
+ `);
875
+ lastPrintedLabel = label;
876
+ }
877
+ return;
878
+ }
879
+ frame = (frame + 1) % frames.length;
880
+ const glyph = frames[frame] ?? frames[0] ?? "";
881
+ output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
882
+ };
883
+ const clearLine = () => {
884
+ if (isTty)
885
+ output.write("\r\x1B[2K");
886
+ };
887
+ render();
888
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
889
+ return {
890
+ setLabel(next) {
891
+ label = next;
892
+ render();
893
+ },
894
+ pause() {
895
+ paused = true;
896
+ clearLine();
897
+ },
898
+ resume() {
899
+ if (stopped)
900
+ return;
901
+ paused = false;
902
+ render();
903
+ },
904
+ stop(finalLine) {
905
+ if (stopped)
906
+ return;
907
+ stopped = true;
908
+ if (timer)
909
+ clearInterval(timer);
910
+ clearLine();
911
+ if (finalLine)
912
+ output.write(`${finalLine}
913
+ `);
914
+ }
915
+ };
916
+ }
917
+
918
+ // packages/cli/src/commands/_async-ui.ts
919
+ var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
920
+ var DONE_SYMBOL = pc.green("\u25C7");
921
+ var FAIL_SYMBOL = pc.red("\u25A0");
922
+ var activeUpdate = null;
923
+ async function withSpinner(label, work, options = {}) {
924
+ if (options.outputMode === "json") {
925
+ return work(() => {});
926
+ }
927
+ if (activeUpdate) {
928
+ const outer = activeUpdate;
929
+ outer(label);
930
+ return work(outer);
931
+ }
932
+ const output = options.output ?? process.stderr;
933
+ const isTty = output.isTTY === true;
934
+ let lastLabel = label;
935
+ if (!isTty) {
936
+ output.write(`${label}
937
+ `);
938
+ const update2 = (next) => {
939
+ lastLabel = next;
940
+ };
941
+ activeUpdate = update2;
942
+ const previousListener2 = setServerPhaseListener(update2);
943
+ try {
944
+ return await work(update2);
945
+ } finally {
946
+ activeUpdate = null;
947
+ setServerPhaseListener(previousListener2);
948
+ }
949
+ }
950
+ const spinner = createTtySpinner({
951
+ label,
952
+ output,
953
+ frames: CLACK_SPINNER_FRAMES,
954
+ styleFrame: (frame) => pc.magenta(frame)
955
+ });
956
+ const update = (next) => {
957
+ lastLabel = next;
958
+ spinner.setLabel(next);
959
+ };
960
+ activeUpdate = update;
961
+ const previousListener = setServerPhaseListener(update);
962
+ try {
963
+ const result = await work(update);
964
+ spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
965
+ return result;
966
+ } catch (error) {
967
+ spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
968
+ throw error;
969
+ } finally {
970
+ activeUpdate = null;
971
+ setServerPhaseListener(previousListener);
972
+ }
973
+ }
974
+
837
975
  // packages/cli/src/commands/_operator-view.ts
838
976
  var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
839
977
  function runStatusFromPayload(payload) {
@@ -876,8 +1014,9 @@ async function readOperatorSnapshot(context, runId, options = {}) {
876
1014
  }
877
1015
  async function attachRunOperatorView(context, input) {
878
1016
  let steered = false;
879
- if (input.message?.trim()) {
880
- await sendRunPiPromptViaServer(context, input.runId, input.message.trim(), "steer").catch(() => steerRunViaServer(context, input.runId, input.message.trim()));
1017
+ const attachMessage = input.message?.trim();
1018
+ if (attachMessage) {
1019
+ await withSpinner("Queueing steering message\u2026", () => sendRunPiPromptViaServer(context, input.runId, attachMessage, "steer").catch(() => steerRunViaServer(context, input.runId, attachMessage)), { outputMode: context.outputMode });
881
1020
  steered = true;
882
1021
  }
883
1022
  if (input.follow && !input.once && input.interactive !== false && context.outputMode === "text" && Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
@@ -887,7 +1026,7 @@ async function attachRunOperatorView(context, input) {
887
1026
  });
888
1027
  }
889
1028
  const surface = createOperatorSurface({ interactive: input.interactive !== false });
890
- let snapshot = await readOperatorSnapshot(context, input.runId);
1029
+ let snapshot = await withSpinner(`Connecting to run ${input.runId}\u2026`, () => readOperatorSnapshot(context, input.runId), { outputMode: context.outputMode });
891
1030
  if (context.outputMode === "text") {
892
1031
  surface.renderSnapshot(snapshot);
893
1032
  surface.renderTimeline(snapshot.timeline);
@@ -927,7 +1066,7 @@ async function attachRunOperatorView(context, input) {
927
1066
 
928
1067
  // packages/cli/src/commands/_cli-format.ts
929
1068
  import { log, note } from "@clack/prompts";
930
- import pc from "picocolors";
1069
+ import pc2 from "picocolors";
931
1070
  function stringField(record, key, fallback = "") {
932
1071
  const value = record[key];
933
1072
  return typeof value === "string" && value.trim() ? value.trim() : fallback;
@@ -949,14 +1088,14 @@ function pad(value, width) {
949
1088
  function statusColor(status) {
950
1089
  const normalized = status.toLowerCase();
951
1090
  if (["completed", "merged", "closed", "done", "accepted", "pass", "selected", "approved"].includes(normalized))
952
- return pc.green;
1091
+ return pc2.green;
953
1092
  if (["failed", "needs_attention", "needs-attention", "blocked", "error", "rejected"].includes(normalized))
954
- return pc.red;
1093
+ return pc2.red;
955
1094
  if (["running", "reviewing", "validating", "in_progress", "in-progress", "remote"].includes(normalized))
956
- return pc.cyan;
1095
+ return pc2.cyan;
957
1096
  if (["ready", "open", "queued", "created", "preparing", "local", "pending"].includes(normalized))
958
- return pc.yellow;
959
- return pc.dim;
1097
+ return pc2.yellow;
1098
+ return pc2.dim;
960
1099
  }
961
1100
  function compactDate(value) {
962
1101
  if (!value.trim())
@@ -1001,23 +1140,23 @@ function formatStatusPill(status) {
1001
1140
  return statusColor(label)(`\u25CF ${label}`);
1002
1141
  }
1003
1142
  function formatSection(title, subtitle) {
1004
- return `${pc.bold(pc.cyan("\u25C6"))} ${pc.bold(title)}${subtitle ? pc.dim(` \u2014 ${subtitle}`) : ""}`;
1143
+ return `${pc2.bold(pc2.cyan("\u25C6"))} ${pc2.bold(title)}${subtitle ? pc2.dim(` \u2014 ${subtitle}`) : ""}`;
1005
1144
  }
1006
1145
  function formatSuccessCard(title, rows = []) {
1007
- const body = rows.filter(([, value]) => value !== undefined && value !== null && String(value).length > 0).map(([key, value]) => `${pc.dim("\u2502")} ${pc.dim(key.padEnd(12))} ${value}`);
1146
+ const body = rows.filter(([, value]) => value !== undefined && value !== null && String(value).length > 0).map(([key, value]) => `${pc2.dim("\u2502")} ${pc2.dim(key.padEnd(12))} ${value}`);
1008
1147
  return [formatSection(title), ...body].join(`
1009
1148
  `);
1010
1149
  }
1011
1150
  function formatNextSteps(steps) {
1012
1151
  if (steps.length === 0)
1013
1152
  return [];
1014
- return [pc.bold("Next"), ...steps.map((step) => `${pc.dim("\u203A")} ${step}`)];
1153
+ return [pc2.bold("Next"), ...steps.map((step) => `${pc2.dim("\u203A")} ${step}`)];
1015
1154
  }
1016
1155
  function formatRunList(runs, options = {}) {
1017
1156
  if (runs.length === 0) {
1018
1157
  return [
1019
1158
  formatSection("Runs", "none recorded"),
1020
- options.source === "server" ? pc.dim("No runs recorded on the selected Rig server.") : pc.dim("No runs recorded in .rig/runs."),
1159
+ options.source === "server" ? pc2.dim("No runs recorded on the selected Rig server.") : pc2.dim("No runs recorded in .rig/runs."),
1021
1160
  "",
1022
1161
  ...formatNextSteps(["Start one: `rig task run --next`", "Check server: `rig server status`"])
1023
1162
  ].join(`
@@ -1033,11 +1172,11 @@ function formatRunList(runs, options = {}) {
1033
1172
  });
1034
1173
  const idWidth = Math.min(36, Math.max(6, ...rows.map((row) => row.runId.length)));
1035
1174
  const statusWidth = Math.min(16, Math.max(6, ...rows.map((row) => row.status.length)));
1036
- const header = `${pc.bold(pad("RUN", idWidth))} ${pc.bold(pad("STATUS", statusWidth))} ${pc.bold("TITLE")}`;
1175
+ const header = `${pc2.bold(pad("RUN", idWidth))} ${pc2.bold(pad("STATUS", statusWidth))} ${pc2.bold("TITLE")}`;
1037
1176
  const body = rows.map((row) => [
1038
- pc.bold(pad(truncate(row.runId, idWidth), idWidth)),
1177
+ pc2.bold(pad(truncate(row.runId, idWidth), idWidth)),
1039
1178
  statusColor(row.status)(pad(truncate(row.status, statusWidth), statusWidth)),
1040
- `${row.title}${row.runtime ? pc.dim(` ${row.runtime}`) : ""}`
1179
+ `${row.title}${row.runtime ? pc2.dim(` ${row.runtime}`) : ""}`
1041
1180
  ].join(" "));
1042
1181
  return [formatSection("Runs", options.source === "server" ? "selected server" : "local state"), header, ...body, "", ...formatNextSteps(["Follow live: `rig run attach <run-id> --follow`", "Details: `rig run show <run-id>`"])].join(`
1043
1182
  `);
@@ -1062,7 +1201,7 @@ function formatRunCard(run, options = {}) {
1062
1201
  const approvals = Array.isArray(merged.approvals) ? merged.approvals.length : null;
1063
1202
  const inputs = Array.isArray(merged.userInputs) ? merged.userInputs.length : null;
1064
1203
  const rows = [
1065
- ["run", pc.bold(runId)],
1204
+ ["run", pc2.bold(runId)],
1066
1205
  ["status", formatStatusPill(status)],
1067
1206
  ["task", taskId],
1068
1207
  ["title", title],
@@ -1088,17 +1227,17 @@ function formatRunStatus(summary, options = {}) {
1088
1227
  const activeRuns = summary.activeRuns ?? [];
1089
1228
  const recentRuns = summary.recentRuns ?? [];
1090
1229
  const lines = [formatSection("Run status", options.source === "server" ? "selected server" : "local state")];
1091
- lines.push("", pc.bold(`Active runs (${activeRuns.length})`));
1230
+ lines.push("", pc2.bold(`Active runs (${activeRuns.length})`));
1092
1231
  if (activeRuns.length === 0) {
1093
- lines.push(pc.dim("No active runs."));
1232
+ lines.push(pc2.dim("No active runs."));
1094
1233
  } else {
1095
1234
  for (const run of activeRuns) {
1096
1235
  lines.push(formatRunSummaryLine(run));
1097
1236
  }
1098
1237
  }
1099
- lines.push("", pc.bold(`Recent runs (${recentRuns.length})`));
1238
+ lines.push("", pc2.bold(`Recent runs (${recentRuns.length})`));
1100
1239
  if (recentRuns.length === 0) {
1101
- lines.push(pc.dim("No recent terminal runs."));
1240
+ lines.push(pc2.dim("No recent terminal runs."));
1102
1241
  } else {
1103
1242
  for (const run of recentRuns.slice(0, 10)) {
1104
1243
  lines.push(formatRunSummaryLine(run));
@@ -1116,7 +1255,7 @@ function formatRunSummaryLine(run) {
1116
1255
  const title = runTitleOf(record);
1117
1256
  const runtime = firstString(record, ["runtimeAdapter", "runtime", "adapter"]);
1118
1257
  const descriptor = [taskId, title].filter(Boolean).join(" \xB7 ");
1119
- return `${pc.dim("\u2502")} ${pc.bold(runId)} ${formatStatusPill(status)} ${descriptor}${runtime ? pc.dim(` ${runtime}`) : ""}`;
1258
+ return `${pc2.dim("\u2502")} ${pc2.bold(runId)} ${formatStatusPill(status)} ${descriptor}${runtime ? pc2.dim(` ${runtime}`) : ""}`;
1120
1259
  }
1121
1260
 
1122
1261
  // packages/cli/src/commands/inbox.ts
@@ -1250,7 +1389,7 @@ async function executeRun(context, args) {
1250
1389
  switch (command) {
1251
1390
  case "list": {
1252
1391
  requireNoExtraArgs(rest, "rig run list");
1253
- const { runs, source } = await listRunsForSelectedConnection(context, { limit: 100 });
1392
+ 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 });
1254
1393
  if (context.outputMode === "text") {
1255
1394
  printFormattedOutput(formatRunList(runs, { source }));
1256
1395
  await printPendingInboxFooter(context);
@@ -1323,7 +1462,7 @@ async function executeRun(context, args) {
1323
1462
  if (!runId) {
1324
1463
  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>`." });
1325
1464
  }
1326
- const record = readAuthorityRun2(context.projectRoot, runId) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, runId).catch(() => ({})));
1465
+ const record = readAuthorityRun2(context.projectRoot, runId) ?? normalizeRemoteRunDetails(await withSpinner(`Reading run ${runId} from server\u2026`, () => getRunDetailsViaServer(context, runId).catch(() => ({})), { outputMode: context.outputMode }));
1327
1466
  if (!record) {
1328
1467
  throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig run list` to see recorded runs." });
1329
1468
  }
@@ -1344,7 +1483,8 @@ async function executeRun(context, args) {
1344
1483
  }
1345
1484
  const renderer = createPiRunStreamRenderer();
1346
1485
  let cursor = null;
1347
- const page = await getRunTimelineViaServer(context, run.value, { limit: 500 });
1486
+ const timelineRunId = run.value;
1487
+ const page = await withSpinner(`Reading timeline for ${timelineRunId}\u2026`, () => getRunTimelineViaServer(context, timelineRunId, { limit: 500 }), { outputMode: context.outputMode });
1348
1488
  const events = Array.isArray(page.entries) ? page.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
1349
1489
  cursor = typeof page.nextCursor === "string" ? page.nextCursor : null;
1350
1490
  if (context.outputMode === "text") {
@@ -1421,8 +1561,9 @@ async function executeRun(context, args) {
1421
1561
  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`." });
1422
1562
  }
1423
1563
  let steered = false;
1424
- if (messageOption.value?.trim()) {
1425
- await steerRunViaServer(context, runId, messageOption.value.trim());
1564
+ const steerMessage = messageOption.value?.trim();
1565
+ if (steerMessage) {
1566
+ await withSpinner("Queueing steering message\u2026", () => steerRunViaServer(context, runId, steerMessage), { outputMode: context.outputMode });
1426
1567
  steered = true;
1427
1568
  }
1428
1569
  const attached = await attachRunOperatorView(context, {
@@ -1442,7 +1583,7 @@ async function executeRun(context, args) {
1442
1583
  }
1443
1584
  return { ok: true, group: "run", command };
1444
1585
  }
1445
- const summary = isRemoteConnectionSelected(context.projectRoot) ? buildServerRunStatus(await listRunsViaServer(context, { limit: 100 })) : runStatus(context.projectRoot, runtimeContext);
1586
+ 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);
1446
1587
  const activeRuns = Array.isArray(summary.activeRuns) ? summary.activeRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
1447
1588
  const recentRuns = Array.isArray(summary.recentRuns) ? summary.recentRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
1448
1589
  if (context.outputMode === "text") {
@@ -1577,7 +1718,8 @@ async function executeRun(context, args) {
1577
1718
  }
1578
1719
  return { ok: true, group: "run", command, details: { runId, dryRun: true } };
1579
1720
  }
1580
- await steerRunViaServer(context, runId, message.trim());
1721
+ const trimmedMessage = message.trim();
1722
+ await withSpinner("Queueing steering message\u2026", () => steerRunViaServer(context, runId, trimmedMessage), { outputMode: context.outputMode });
1581
1723
  if (context.outputMode === "text") {
1582
1724
  console.log(`Steering message queued for ${runId}.`);
1583
1725
  }
@@ -1598,7 +1740,7 @@ async function executeRun(context, args) {
1598
1740
  };
1599
1741
  }
1600
1742
  if (runId) {
1601
- const stopped = await stopRunViaServer(context, runId);
1743
+ const stopped = await withSpinner(`Requesting stop for ${runId}\u2026`, () => stopRunViaServer(context, runId), { outputMode: context.outputMode });
1602
1744
  if (context.outputMode === "text")
1603
1745
  console.log(`Stop requested: ${runId}`);
1604
1746
  return { ok: true, group: "run", command, details: stopped };
@@ -361,6 +361,10 @@ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
361
361
  import { resolve as resolve2 } from "path";
362
362
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
363
363
  var scopedGitHubBearerTokens = new Map;
364
+ var serverPhaseListener = null;
365
+ function reportServerPhase(label) {
366
+ serverPhaseListener?.(label);
367
+ }
364
368
  function cleanToken(value) {
365
369
  const trimmed = value?.trim();
366
370
  return trimmed ? trimmed : null;
@@ -403,6 +407,7 @@ async function ensureServerForCli(projectRoot) {
403
407
  try {
404
408
  const selected = resolveSelectedConnection(projectRoot);
405
409
  if (selected?.connection.kind === "remote") {
410
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
406
411
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
407
412
  const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
408
413
  return {
@@ -412,6 +417,7 @@ async function ensureServerForCli(projectRoot) {
412
417
  serverProjectRoot
413
418
  };
414
419
  }
420
+ reportServerPhase("Starting local Rig server\u2026");
415
421
  const connection = await ensureLocalRigServerConnection(projectRoot);
416
422
  return {
417
423
  baseUrl: connection.baseUrl,
@@ -518,6 +524,7 @@ async function requestServerJson(context, pathname, init = {}) {
518
524
  const headers = mergeHeaders(init.headers, server.authToken);
519
525
  if (server.serverProjectRoot)
520
526
  headers.set("x-rig-project-root", server.serverProjectRoot);
527
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
521
528
  let response;
522
529
  try {
523
530
  response = await fetch(`${server.baseUrl}${pathname}`, {