@h-rig/cli 0.0.6-alpha.13 → 0.0.6-alpha.15

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.
@@ -1,8 +1,8 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/task.ts
3
3
  import { readFileSync as readFileSync4 } from "fs";
4
- import { spawnSync as spawnSync2 } from "child_process";
5
- import { createInterface as createInterface3 } from "readline/promises";
4
+ import { spawnSync } from "child_process";
5
+ import { createInterface as createInterface2 } from "readline/promises";
6
6
  import { resolve as resolve4 } from "path";
7
7
 
8
8
  // packages/cli/src/runner.ts
@@ -188,11 +188,10 @@ function resolveSelectedConnection(projectRoot, options = {}) {
188
188
  }
189
189
 
190
190
  // packages/cli/src/commands/_server-client.ts
191
- import { spawnSync } from "child_process";
192
191
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
193
192
  import { resolve as resolve2 } from "path";
194
193
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
195
- var cachedGitHubBearerToken;
194
+ var scopedGitHubBearerTokens = new Map;
196
195
  function cleanToken(value) {
197
196
  const trimmed = value?.trim();
198
197
  return trimmed ? trimmed : null;
@@ -209,25 +208,13 @@ function readPrivateRemoteSessionToken(projectRoot) {
209
208
  }
210
209
  }
211
210
  function readGitHubBearerTokenForRemote(projectRoot) {
212
- if (cachedGitHubBearerToken !== undefined)
213
- return cachedGitHubBearerToken;
211
+ const scopedKey = resolve2(projectRoot);
212
+ if (scopedGitHubBearerTokens.has(scopedKey))
213
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
214
214
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
215
- if (privateSession) {
216
- cachedGitHubBearerToken = privateSession;
217
- return cachedGitHubBearerToken;
218
- }
219
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
220
- if (envToken) {
221
- cachedGitHubBearerToken = envToken;
222
- return cachedGitHubBearerToken;
223
- }
224
- const result = spawnSync("gh", ["auth", "token"], {
225
- encoding: "utf8",
226
- timeout: 5000,
227
- stdio: ["ignore", "pipe", "ignore"]
228
- });
229
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
230
- return cachedGitHubBearerToken;
215
+ if (privateSession)
216
+ return privateSession;
217
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
231
218
  }
232
219
  async function ensureServerForCli(projectRoot) {
233
220
  try {
@@ -347,6 +334,15 @@ async function getRunLogsViaServer(context, runId, options = {}) {
347
334
  const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
348
335
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
349
336
  }
337
+ async function getRunTimelineViaServer(context, runId, options = {}) {
338
+ const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
339
+ if (options.limit !== undefined)
340
+ url.searchParams.set("limit", String(options.limit));
341
+ if (options.cursor)
342
+ url.searchParams.set("cursor", options.cursor);
343
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
344
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
345
+ }
350
346
  async function stopRunViaServer(context, runId) {
351
347
  const payload = await requestServerJson(context, "/api/runs/stop", {
352
348
  method: "POST",
@@ -522,6 +518,9 @@ function permissionAllowsPr(payload) {
522
518
  }
523
519
  return null;
524
520
  }
521
+ function isNotFoundError(error) {
522
+ return /\b(404|not found)\b/i.test(message(error));
523
+ }
525
524
  function projectCheckoutReady(payload) {
526
525
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
527
526
  return null;
@@ -554,19 +553,33 @@ async function runFastTaskRunPreflight(context, options = {}) {
554
553
  const checks = [];
555
554
  const request = options.requestJson ?? ((pathname, init) => requestServerJson(context, pathname, init));
556
555
  const taskId = options.taskId?.trim() || null;
556
+ const requiresCurrentRunApi = Boolean(taskId);
557
+ const selectedServer = options.requestJson ? null : await ensureServerForCli(context.projectRoot).catch(() => null);
558
+ const allowLocalLegacyTaskRunCompatibility = selectedServer?.connectionKind === "local";
559
+ let legacyServerCompatibility = false;
557
560
  try {
558
561
  await request("/api/server/status");
559
562
  checks.push(preflightCheck("server", "Rig server reachable", "pass"));
560
563
  } catch (error) {
561
- checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
564
+ if (isNotFoundError(error)) {
565
+ try {
566
+ await request("/health");
567
+ legacyServerCompatibility = !requiresCurrentRunApi || allowLocalLegacyTaskRunCompatibility;
568
+ checks.push(requiresCurrentRunApi && !allowLocalLegacyTaskRunCompatibility ? preflightCheck("server", "Rig server reachable", "fail", "legacy /health endpoint only; current task-run APIs are required", "Upgrade/select the Rig server before launching a task run.") : preflightCheck("server", "Rig server reachable", "pass", allowLocalLegacyTaskRunCompatibility ? "local legacy /health endpoint; submit endpoint will be authoritative" : "legacy /health endpoint"));
569
+ } catch (healthError) {
570
+ checks.push(preflightCheck("server", "Rig server reachable", "fail", message(healthError), "Start or select a reachable Rig server."));
571
+ }
572
+ } else {
573
+ checks.push(preflightCheck("server", "Rig server reachable", "fail", message(error), "Start or select a reachable Rig server."));
574
+ }
562
575
  }
563
576
  const repo = readRepoConnection(context.projectRoot);
564
- checks.push(repo ? preflightCheck("project-link", "project linked to Rig connection", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to Rig connection", "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig connect use <alias|local>`."));
577
+ checks.push(repo ? preflightCheck("project-link", "project linked to Rig connection", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to Rig connection", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig connect use <alias|local>`."));
565
578
  try {
566
579
  const auth = await request("/api/github/auth/status");
567
- checks.push(isAuthenticated(auth) ? preflightCheck("github-auth", "GitHub auth valid", "pass") : preflightCheck("github-auth", "GitHub auth valid", "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
580
+ checks.push(isAuthenticated(auth) ? preflightCheck("github-auth", "GitHub auth valid", "pass") : preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
568
581
  } catch (error) {
569
- checks.push(preflightCheck("github-auth", "GitHub auth valid", "fail", message(error), "Fix GitHub auth on the selected Rig server."));
582
+ checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
570
583
  }
571
584
  try {
572
585
  const projection = await request("/api/workspace/task-projection");
@@ -594,9 +607,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
594
607
  try {
595
608
  const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
596
609
  const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
597
- checks.push(found ? preflightCheck("issue", "task/issue accessible", "pass", taskId) : preflightCheck("issue", "task/issue accessible", "fail", taskId, "Confirm the issue exists and matches the configured task filters."));
610
+ checks.push(found ? preflightCheck("issue", "task/issue accessible", "pass", taskId) : preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", taskId, "Confirm the issue exists and matches the configured task filters."));
598
611
  } catch (error) {
599
- checks.push(preflightCheck("issue", "task/issue accessible", "fail", message(error), "Fix the task source before launching a run."));
612
+ checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
600
613
  }
601
614
  try {
602
615
  const runs = await request("/api/runs?limit=200");
@@ -705,8 +718,135 @@ function withMutedConsole(mute, fn) {
705
718
  }
706
719
  }
707
720
 
708
- // packages/cli/src/commands/_task-picker.ts
709
- import { createInterface } from "readline/promises";
721
+ // packages/cli/src/commands/_operator-surface.ts
722
+ import { createInterface } from "readline";
723
+ import { createInterface as createPromptInterface } from "readline/promises";
724
+ var CANONICAL_STAGES = [
725
+ "Connect",
726
+ "GitHub/task sync",
727
+ "Prepare workspace",
728
+ "Launch Pi",
729
+ "Plan",
730
+ "Implement",
731
+ "Validate",
732
+ "Commit",
733
+ "Open PR",
734
+ "Review/CI",
735
+ "Merge",
736
+ "Complete"
737
+ ];
738
+ function logDetail(log) {
739
+ return typeof log.detail === "string" ? log.detail.trim() : "";
740
+ }
741
+ function entryId(entry, fallback) {
742
+ return typeof entry.id === "string" && entry.id.trim() ? entry.id : fallback;
743
+ }
744
+ function renderOperatorSnapshot(snapshot) {
745
+ const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
746
+ const runId = String(run.runId ?? run.id ?? "run");
747
+ const status = String(run.status ?? "unknown");
748
+ const logs = snapshot.logs ?? [];
749
+ const latestByStage = new Map;
750
+ for (const log of logs) {
751
+ const title = String(log.title ?? "").toLowerCase();
752
+ const stageName = String(log.stage ?? "").toLowerCase();
753
+ const stage = CANONICAL_STAGES.find((candidate) => candidate.toLowerCase() === title || candidate.toLowerCase() === stageName);
754
+ if (stage)
755
+ latestByStage.set(stage, log);
756
+ }
757
+ const stageLines = CANONICAL_STAGES.flatMap((stage) => {
758
+ const match = latestByStage.get(stage);
759
+ return match ? [`${stage}: ${String(match.status ?? status)}${logDetail(match) ? ` \u2014 ${logDetail(match)}` : ""}`] : [];
760
+ });
761
+ return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
762
+ `);
763
+ }
764
+ function createPiRunStreamRenderer(output = process.stdout) {
765
+ let lastSnapshot = "";
766
+ const assistantTextById = new Map;
767
+ const seenTimeline = new Set;
768
+ const seenLogs = new Set;
769
+ const writeLine = (line) => output.write(`${line}
770
+ `);
771
+ return {
772
+ renderSnapshot(snapshot) {
773
+ const rendered = renderOperatorSnapshot(snapshot);
774
+ if (rendered && rendered !== lastSnapshot) {
775
+ writeLine(rendered);
776
+ lastSnapshot = rendered;
777
+ }
778
+ },
779
+ renderTimeline(entries) {
780
+ for (const [index, entry] of entries.entries()) {
781
+ const id = entryId(entry, `timeline:${index}:${String(entry.cursor ?? "")}`);
782
+ if (entry.type === "assistant_message" && typeof entry.text === "string") {
783
+ const text = entry.text;
784
+ const previousText = assistantTextById.get(id) ?? "";
785
+ if (text.startsWith(previousText)) {
786
+ const delta = text.slice(previousText.length);
787
+ if (delta)
788
+ output.write(delta);
789
+ } else if (text.trim() && text !== previousText) {
790
+ writeLine(`
791
+ [Pi assistant]`);
792
+ output.write(text);
793
+ }
794
+ assistantTextById.set(id, text);
795
+ continue;
796
+ }
797
+ if (seenTimeline.has(id))
798
+ continue;
799
+ seenTimeline.add(id);
800
+ if (entry.type === "tool_execution_start" || entry.type === "tool_execution_update" || entry.type === "tool_execution_end" || entry.type === "mcp_tool_call") {
801
+ writeLine(`[Pi tool] ${String(entry.toolName ?? entry.name ?? entry.title ?? entry.type)} ${String(entry.status ?? entry.state ?? "")}`.trim());
802
+ continue;
803
+ }
804
+ if (entry.type === "timeline_warning") {
805
+ writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
806
+ }
807
+ }
808
+ },
809
+ renderLogs(entries) {
810
+ for (const [index, entry] of entries.entries()) {
811
+ const id = entryId(entry, `log:${index}:${String(entry.createdAt ?? "")}:${String(entry.title ?? "")}`);
812
+ if (seenLogs.has(id))
813
+ continue;
814
+ seenLogs.add(id);
815
+ const title = String(entry.title ?? "");
816
+ if (CANONICAL_STAGES.some((stage) => stage.toLowerCase() === title.toLowerCase()))
817
+ continue;
818
+ const detail = logDetail(entry);
819
+ if (!detail)
820
+ continue;
821
+ writeLine(`[${title || "Rig log"}] ${detail}`);
822
+ }
823
+ }
824
+ };
825
+ }
826
+ function createOperatorSurface(options = {}) {
827
+ const input = options.input ?? process.stdin;
828
+ const output = options.output ?? process.stdout;
829
+ const errorOutput = options.errorOutput ?? process.stderr;
830
+ const renderer = createPiRunStreamRenderer(output);
831
+ const writeLine = (line) => output.write(`${line}
832
+ `);
833
+ return {
834
+ mode: "pi-compatible-text",
835
+ ...renderer,
836
+ info: writeLine,
837
+ error: (message2) => errorOutput.write(`${message2}
838
+ `),
839
+ attachCommandInput(handler) {
840
+ if (options.interactive === false || !input.isTTY)
841
+ return null;
842
+ const rl = createInterface({ input, output: process.stdout, terminal: false });
843
+ rl.on("line", (line) => {
844
+ Promise.resolve(handler(line)).catch((error) => writeLine(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
845
+ });
846
+ return { close: () => rl.close() };
847
+ }
848
+ };
849
+ }
710
850
  function taskId(task) {
711
851
  return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
712
852
  }
@@ -719,6 +859,19 @@ function taskStatus(task) {
719
859
  function renderTaskPickerRows(tasks) {
720
860
  return tasks.map((task, index) => `${index + 1}. ${taskId(task)} \xB7 ${taskStatus(task)} \xB7 ${taskTitle(task)}`);
721
861
  }
862
+ async function promptForTaskSelection(question) {
863
+ const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
864
+ try {
865
+ return await rl.question(question);
866
+ } finally {
867
+ rl.close();
868
+ }
869
+ }
870
+
871
+ // packages/cli/src/commands/_task-picker.ts
872
+ function taskId2(task) {
873
+ return typeof task.id === "string" && task.id.trim() ? task.id : "<unknown>";
874
+ }
722
875
  async function selectTaskWithTextPicker(tasks, io = {}) {
723
876
  if (tasks.length === 0)
724
877
  return null;
@@ -728,17 +881,12 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
728
881
  if (!isTty) {
729
882
  throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
730
883
  }
731
- const prompt = io.prompt ?? (async (question) => {
732
- const rl = createInterface({ input: process.stdin, output: process.stdout });
733
- try {
734
- return await rl.question(question);
735
- } finally {
736
- rl.close();
737
- }
738
- });
739
- console.log("Select Rig task:");
884
+ const prompt = io.prompt ?? promptForTaskSelection;
885
+ const renderer = io.renderer ?? { writeLine: (line) => process.stdout.write(`${line}
886
+ `) };
887
+ renderer.writeLine("Select Rig task:");
740
888
  for (const row of renderTaskPickerRows(tasks))
741
- console.log(` ${row}`);
889
+ renderer.writeLine(` ${row}`);
742
890
  const answer = (await prompt(`Task [1-${tasks.length}] or id: `)).trim();
743
891
  if (!answer)
744
892
  return null;
@@ -746,38 +894,11 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
746
894
  const index = Number.parseInt(answer, 10) - 1;
747
895
  return tasks[index] ?? null;
748
896
  }
749
- return tasks.find((task) => taskId(task) === answer) ?? null;
897
+ return tasks.find((task) => taskId2(task) === answer) ?? null;
750
898
  }
751
899
 
752
900
  // packages/cli/src/commands/_operator-view.ts
753
- import { createInterface as createInterface2 } from "readline";
754
901
  var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
755
- var CANONICAL_STAGES = [
756
- "Connect",
757
- "GitHub/task sync",
758
- "Prepare workspace",
759
- "Launch Pi",
760
- "Plan",
761
- "Implement",
762
- "Validate",
763
- "Commit",
764
- "Open PR",
765
- "Review/CI",
766
- "Merge",
767
- "Complete"
768
- ];
769
- function renderOperatorSnapshot(snapshot) {
770
- const run = snapshot.run.run && typeof snapshot.run.run === "object" ? snapshot.run.run : snapshot.run;
771
- const runId = String(run.runId ?? run.id ?? "run");
772
- const status = String(run.status ?? "unknown");
773
- const logs = snapshot.logs ?? [];
774
- const stageLines = CANONICAL_STAGES.flatMap((stage) => {
775
- const match = logs.find((log) => String(log.title ?? "").toLowerCase() === stage.toLowerCase() || String(log.stage ?? "").toLowerCase() === stage.toLowerCase());
776
- return match ? [`${stage}: ${String(match.status ?? status)}`] : [];
777
- });
778
- return [`Rig run ${runId}: ${status}`, ...stageLines].join(`
779
- `);
780
- }
781
902
  function runStatusFromPayload(payload) {
782
903
  const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
783
904
  return String(run.status ?? "unknown").toLowerCase();
@@ -799,11 +920,22 @@ async function applyOperatorCommand(context, input, deps = {}) {
799
920
  await (deps.steer ?? steerRunViaServer)(context, input.runId, userMessage);
800
921
  return { action: "continue", message: "Steering message queued." };
801
922
  }
802
- async function readOperatorSnapshot(context, runId) {
923
+ async function readOperatorSnapshot(context, runId, options = {}) {
803
924
  const run = await getRunDetailsViaServer(context, runId);
804
925
  const logsPage = await getRunLogsViaServer(context, runId, { limit: 100 });
805
- const entries = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
806
- return { run, logs: entries, rendered: renderOperatorSnapshot({ run, logs: entries }) };
926
+ const timelinePage = await getRunTimelineViaServer(context, runId, { limit: 200, ...options.timelineCursor ? { cursor: options.timelineCursor } : {} }).catch((error) => ({
927
+ entries: [{
928
+ id: `timeline-unavailable:${runId}`,
929
+ type: "timeline_warning",
930
+ detail: `Selected Rig server did not provide run timeline events: ${error instanceof Error ? error.message : String(error)}`,
931
+ createdAt: new Date().toISOString()
932
+ }],
933
+ nextCursor: options.timelineCursor ?? null
934
+ }));
935
+ const logs = Array.isArray(logsPage.entries) ? logsPage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).toReversed() : [];
936
+ const timeline = Array.isArray(timelinePage.entries) ? timelinePage.entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
937
+ const timelineCursor = typeof timelinePage.nextCursor === "string" ? timelinePage.nextCursor : options.timelineCursor ?? null;
938
+ return { run, logs, timeline, timelineCursor, rendered: renderOperatorSnapshot({ run, logs, timeline }) };
807
939
  }
808
940
  async function attachRunOperatorView(context, input) {
809
941
  let steered = false;
@@ -811,40 +943,41 @@ async function attachRunOperatorView(context, input) {
811
943
  await steerRunViaServer(context, input.runId, input.message.trim());
812
944
  steered = true;
813
945
  }
946
+ const surface = createOperatorSurface({ interactive: input.interactive !== false });
814
947
  let snapshot = await readOperatorSnapshot(context, input.runId);
815
948
  if (context.outputMode === "text") {
816
- console.log(snapshot.rendered);
949
+ surface.renderSnapshot(snapshot);
950
+ surface.renderTimeline(snapshot.timeline);
951
+ surface.renderLogs(snapshot.logs);
817
952
  if (steered)
818
- console.log("Steering message queued.");
953
+ surface.info("Steering message queued.");
819
954
  }
820
955
  let detached = false;
821
- let rl = null;
956
+ let commandInput = null;
822
957
  if (input.follow && !input.once && context.outputMode === "text") {
823
958
  if (input.interactive !== false && process.stdin.isTTY) {
824
- console.log("Controls: /user <message>, /stop, /detach");
825
- rl = createInterface2({ input: process.stdin, output: process.stdout, terminal: false });
826
- rl.on("line", (line) => {
827
- applyOperatorCommand(context, { runId: input.runId, line }).then((result) => {
828
- if (result.message)
829
- console.log(result.message);
830
- if (result.action === "detach" || result.action === "stopped") {
831
- detached = true;
832
- rl?.close();
833
- }
834
- }).catch((error) => console.log(`Operator command failed: ${error instanceof Error ? error.message : String(error)}`));
959
+ surface.info("Controls: /user <message>, /stop, /detach");
960
+ commandInput = surface.attachCommandInput(async (line) => {
961
+ const result = await applyOperatorCommand(context, { runId: input.runId, line });
962
+ if (result.message)
963
+ surface.info(result.message);
964
+ if (result.action === "detach" || result.action === "stopped") {
965
+ detached = true;
966
+ commandInput?.close();
967
+ }
835
968
  });
836
969
  }
837
- let lastRendered = snapshot.rendered;
838
970
  const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
971
+ let timelineCursor = snapshot.timelineCursor;
839
972
  while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
840
973
  await Bun.sleep(pollMs);
841
- snapshot = await readOperatorSnapshot(context, input.runId);
842
- if (snapshot.rendered !== lastRendered) {
843
- console.log(snapshot.rendered);
844
- lastRendered = snapshot.rendered;
845
- }
974
+ snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
975
+ timelineCursor = snapshot.timelineCursor;
976
+ surface.renderSnapshot(snapshot);
977
+ surface.renderTimeline(snapshot.timeline);
978
+ surface.renderLogs(snapshot.logs);
846
979
  }
847
- rl?.close();
980
+ commandInput?.close();
848
981
  }
849
982
  return { ...snapshot, steered, detached };
850
983
  }
@@ -924,7 +1057,7 @@ function normalizePrMode(value) {
924
1057
  throw new CliError2("--pr must be auto, ask, or off.", 2);
925
1058
  }
926
1059
  function detectLocalDirtyState(projectRoot) {
927
- const result = spawnSync2("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
1060
+ const result = spawnSync("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
928
1061
  if (result.status !== 0)
929
1062
  return { dirty: false, modified: 0, untracked: 0, lines: [] };
930
1063
  const lines = result.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
@@ -958,7 +1091,7 @@ async function resolveDirtyBaselineForTaskRun(context, explicit) {
958
1091
  if (explicit)
959
1092
  return { mode: explicit, state };
960
1093
  if (context.outputMode === "text" && process.stdin.isTTY && process.stdout.isTTY) {
961
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
1094
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
962
1095
  try {
963
1096
  const answer = (await rl.question("Include current uncommitted changes in run baseline? [y/N] ")).trim().toLowerCase();
964
1097
  return { mode: answer === "y" || answer === "yes" ? "dirty-snapshot" : "head", state };
@@ -1043,12 +1176,12 @@ async function executeTask(context, args, options) {
1043
1176
  const positional = taskOption.rest.length > 0 && taskOption.rest[0] && !taskOption.rest[0].startsWith("-") ? taskOption.rest[0] : undefined;
1044
1177
  const remaining = positional ? taskOption.rest.slice(1) : taskOption.rest;
1045
1178
  requireNoExtraArgs(remaining, "bun run rig task show <id>|--task <id>");
1046
- const taskId2 = normalizeTaskRunTaskId(taskOption.value ?? positional);
1047
- if (!taskId2)
1179
+ const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
1180
+ if (!taskId3)
1048
1181
  throw new CliError2("task show requires a task id.", 2);
1049
- const task = await getWorkspaceTaskViaServer(context, taskId2);
1182
+ const task = await getWorkspaceTaskViaServer(context, taskId3);
1050
1183
  if (!task)
1051
- throw new CliError2(`Task not found: ${taskId2}`, 3);
1184
+ throw new CliError2(`Task not found: ${taskId3}`, 3);
1052
1185
  const summary = summarizeTask(task, { raw: true });
1053
1186
  if (context.outputMode === "text")
1054
1187
  console.log(JSON.stringify(summary, null, 2));