@h-rig/cli 0.0.6-alpha.90 → 0.0.6-alpha.92

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.
Files changed (118) hide show
  1. package/README.md +18 -19
  2. package/dist/bin/build-rig-binaries.js +22 -10
  3. package/dist/bin/rig.d.ts +71 -1
  4. package/dist/bin/rig.js +15078 -11169
  5. package/dist/config/rig-default-config.yml +5 -0
  6. package/dist/src/app/drone-ui.d.ts +11 -14
  7. package/dist/src/app/drone-ui.js +70 -86
  8. package/dist/src/commands/_async-ui.d.ts +1 -4
  9. package/dist/src/commands/_async-ui.js +9 -111
  10. package/dist/src/commands/_cli-format.d.ts +16 -9
  11. package/dist/src/commands/_cli-format.js +167 -295
  12. package/dist/src/commands/_connection-state.d.ts +11 -1
  13. package/dist/src/commands/_connection-state.js +50 -5
  14. package/dist/src/commands/_doctor-checks.d.ts +0 -6
  15. package/dist/src/commands/_doctor-checks.js +79 -382
  16. package/dist/src/commands/_help-catalog.d.ts +1 -1
  17. package/dist/src/commands/_help-catalog.js +217 -157
  18. package/dist/src/commands/_inprocess-services.d.ts +33 -0
  19. package/dist/src/commands/_inprocess-services.js +102 -0
  20. package/dist/src/commands/_json-output.js +4 -0
  21. package/dist/src/commands/_lazy-reconcile.d.ts +34 -0
  22. package/dist/src/commands/_lazy-reconcile.js +102 -0
  23. package/dist/src/commands/_paths.js +1 -1
  24. package/dist/src/commands/_pi-frontend.d.ts +18 -10
  25. package/dist/src/commands/_pi-frontend.js +37 -715
  26. package/dist/src/commands/_pi-install.js +18 -36
  27. package/dist/src/commands/_policy.d.ts +1 -1
  28. package/dist/src/commands/_policy.js +56 -15
  29. package/dist/src/commands/_run-bridge.d.ts +114 -0
  30. package/dist/src/commands/_run-bridge.js +387 -0
  31. package/dist/src/commands/_run-diagnostics.d.ts +9 -0
  32. package/dist/src/commands/_run-diagnostics.js +51 -0
  33. package/dist/src/commands/_run-driver-helpers.d.ts +8 -81
  34. package/dist/src/commands/_run-driver-helpers.js +79 -283
  35. package/dist/src/commands/_run-projection.d.ts +50 -0
  36. package/dist/src/commands/_run-projection.js +349 -0
  37. package/dist/src/commands/_run-subcommands.d.ts +3 -0
  38. package/dist/src/commands/_run-subcommands.js +31 -0
  39. package/dist/src/commands/_spinner.js +1 -1
  40. package/dist/src/commands/agent.d.ts +1 -1
  41. package/dist/src/commands/agent.js +8559 -239
  42. package/dist/src/commands/dist.d.ts +1 -1
  43. package/dist/src/commands/dist.js +27 -19
  44. package/dist/src/commands/doctor.d.ts +1 -1
  45. package/dist/src/commands/doctor.js +93 -475
  46. package/dist/src/commands/github.d.ts +1 -1
  47. package/dist/src/commands/github.js +113 -387
  48. package/dist/src/commands/inbox.d.ts +22 -24
  49. package/dist/src/commands/inbox.js +420 -691
  50. package/dist/src/commands/init.d.ts +6 -16
  51. package/dist/src/commands/init.js +334 -971
  52. package/dist/src/commands/inspect.d.ts +19 -2
  53. package/dist/src/commands/inspect.js +644 -610
  54. package/dist/src/commands/pi.d.ts +1 -1
  55. package/dist/src/commands/plugin.d.ts +1 -1
  56. package/dist/src/commands/plugin.js +486 -7
  57. package/dist/src/commands/profile-and-review.d.ts +1 -1
  58. package/dist/src/commands/profile-and-review.js +94 -56
  59. package/dist/src/commands/queue.js +1 -21
  60. package/dist/src/commands/remote.d.ts +1 -1
  61. package/dist/src/commands/remote.js +837 -14
  62. package/dist/src/commands/repo-git-harness.d.ts +1 -1
  63. package/dist/src/commands/repo-git-harness.js +57 -14
  64. package/dist/src/commands/run.d.ts +20 -2
  65. package/dist/src/commands/run.js +17579 -1759
  66. package/dist/src/commands/server.d.ts +2 -6
  67. package/dist/src/commands/server.js +141 -723
  68. package/dist/src/commands/setup.d.ts +1 -1
  69. package/dist/src/commands/setup.js +102 -484
  70. package/dist/src/commands/stats.d.ts +13 -10
  71. package/dist/src/commands/stats.js +689 -761
  72. package/dist/src/commands/task-run-driver.d.ts +50 -88
  73. package/dist/src/commands/task-run-driver.js +116 -2717
  74. package/dist/src/commands/task.d.ts +34 -13
  75. package/dist/src/commands/task.js +668 -2523
  76. package/dist/src/commands/test.d.ts +1 -1
  77. package/dist/src/commands/triage.d.ts +11 -0
  78. package/dist/src/commands/triage.js +227 -0
  79. package/dist/src/commands/workspace.d.ts +1 -1
  80. package/dist/src/commands.d.ts +0 -16
  81. package/dist/src/commands.js +16657 -12250
  82. package/dist/src/index.js +16528 -12497
  83. package/dist/src/launcher.js +4 -0
  84. package/dist/src/operator-cli.d.ts +2 -0
  85. package/dist/src/operator-cli.js +17837 -0
  86. package/dist/src/operator-entry.d.ts +1 -0
  87. package/dist/src/operator-entry.js +3 -0
  88. package/package.json +18 -12
  89. package/dist/src/app/board.d.ts +0 -23
  90. package/dist/src/app/board.js +0 -1786
  91. package/dist/src/app/theme.d.ts +0 -47
  92. package/dist/src/app/theme.js +0 -150
  93. package/dist/src/commands/_authority-runs.d.ts +0 -22
  94. package/dist/src/commands/_authority-runs.js +0 -110
  95. package/dist/src/commands/_operator-surface.d.ts +0 -34
  96. package/dist/src/commands/_operator-surface.js +0 -220
  97. package/dist/src/commands/_operator-view.d.ts +0 -30
  98. package/dist/src/commands/_operator-view.js +0 -1070
  99. package/dist/src/commands/_preflight.d.ts +0 -22
  100. package/dist/src/commands/_preflight.js +0 -540
  101. package/dist/src/commands/_run-replay.d.ts +0 -24
  102. package/dist/src/commands/_run-replay.js +0 -142
  103. package/dist/src/commands/_server-client.d.ts +0 -186
  104. package/dist/src/commands/_server-client.js +0 -681
  105. package/dist/src/commands/_snapshot-upload.d.ts +0 -39
  106. package/dist/src/commands/_snapshot-upload.js +0 -455
  107. package/dist/src/commands/_task-picker.d.ts +0 -9
  108. package/dist/src/commands/_task-picker.js +0 -201
  109. package/dist/src/commands/browser.d.ts +0 -65
  110. package/dist/src/commands/browser.js +0 -1173
  111. package/dist/src/commands/connect.d.ts +0 -7
  112. package/dist/src/commands/connect.js +0 -419
  113. package/dist/src/commands/inspector.d.ts +0 -3
  114. package/dist/src/commands/inspector.js +0 -263
  115. package/dist/src/commands/task-report-bug.d.ts +0 -19
  116. package/dist/src/commands/task-report-bug.js +0 -1281
  117. package/dist/src/report-bug.d.ts +0 -44
  118. package/dist/src/report-bug.js +0 -260
@@ -1,7 +1,12 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/inspect.ts
3
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
4
- import { resolve as resolve3 } from "path";
3
+ import { RIG_RUN_LOG_ENTRY } from "@rig/contracts";
4
+ import {
5
+ changedFilesForTask,
6
+ taskArtifactRead,
7
+ taskArtifacts,
8
+ taskDeps
9
+ } from "@rig/runtime/control-plane/native/task-ops";
5
10
 
6
11
  // packages/cli/src/runner.ts
7
12
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
@@ -52,675 +57,704 @@ Usage: ${usage}`);
52
57
  return taskId;
53
58
  }
54
59
 
55
- // packages/cli/src/commands/inspect.ts
56
- import {
57
- listAuthorityRuns,
58
- readAuthorityRun,
59
- resolveAuthorityRunDir,
60
- resolveTaskArtifactDirs
61
- } from "@rig/runtime/control-plane/authority-files";
62
- import { changedFilesForTask } from "@rig/runtime/control-plane/native/task-ops";
63
- import { resolveHarnessPaths, resolveMonorepoRoot, runCapture } from "@rig/runtime/control-plane/native/utils";
64
- import { readTaskArtifactPreview } from "@rig/runtime/control-plane/native/workspace-ops";
65
-
66
- // packages/cli/src/commands/_server-client.ts
67
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
68
- import { resolve as resolve2 } from "path";
69
- import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
70
-
71
- // packages/cli/src/commands/_connection-state.ts
72
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
73
- import { homedir } from "os";
74
- import { dirname, resolve } from "path";
75
- function resolveGlobalConnectionsPath(env = process.env) {
76
- const explicit = env.RIG_CONNECTIONS_FILE?.trim();
77
- if (explicit)
78
- return resolve(explicit);
79
- const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
80
- if (stateDir)
81
- return resolve(stateDir, "connections.json");
82
- return resolve(homedir(), ".rig", "connections.json");
83
- }
84
- function resolveRepoConnectionPath(projectRoot) {
85
- return resolve(projectRoot, ".rig", "state", "connection.json");
86
- }
87
- function readJsonFile(path) {
88
- if (!existsSync(path))
89
- return null;
90
- try {
91
- return JSON.parse(readFileSync(path, "utf8"));
92
- } catch (error) {
93
- throw new CliError(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1, { hint: "Fix or delete that file, then re-select a server with `rig server use <alias|local>`." });
60
+ // packages/cli/src/commands/_cli-format.ts
61
+ import pc from "picocolors";
62
+ var dim = pc.dim;
63
+ var faintBar = pc.dim("\u2502");
64
+ var accent = pc.cyan;
65
+ function truncate(value, width) {
66
+ return value.length <= width ? value : `${value.slice(0, Math.max(0, width - 1))}\u2026`;
67
+ }
68
+ function pad(value, width) {
69
+ return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
70
+ }
71
+ function statusColor(status) {
72
+ const normalized = status.trim().toLowerCase();
73
+ if (["done", "completed", "ready", "healthy", "open", "approved"].includes(normalized))
74
+ return pc.green;
75
+ if (["failed", "error", "blocked", "rejected", "stopped"].includes(normalized))
76
+ return pc.red;
77
+ if (["running", "in_progress", "in-progress", "active", "booting"].includes(normalized))
78
+ return pc.cyan;
79
+ if (["pending", "queued", "created", "local"].includes(normalized))
80
+ return pc.yellow;
81
+ return pc.dim;
82
+ }
83
+ function firstString(record, keys, fallback = "") {
84
+ for (const key of keys) {
85
+ const value = record[key];
86
+ if (typeof value === "string" && value.trim())
87
+ return value;
94
88
  }
89
+ return fallback;
95
90
  }
96
- function writeJsonFile(path, value) {
97
- mkdirSync(dirname(path), { recursive: true });
98
- writeFileSync(path, `${JSON.stringify(value, null, 2)}
99
- `, "utf8");
91
+ function runIdOf(run) {
92
+ return firstString(run, ["runId", "id"], "(unknown-run)");
100
93
  }
101
- function normalizeConnection(value) {
102
- if (!value || typeof value !== "object" || Array.isArray(value))
103
- return null;
104
- const record = value;
105
- if (record.kind === "local")
106
- return { kind: "local", mode: "auto" };
107
- if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
108
- const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
109
- return { kind: "remote", baseUrl };
110
- }
111
- return null;
94
+ function taskIdOf(run) {
95
+ return firstString(run, ["taskId", "task", "task_id"]);
112
96
  }
113
- function readGlobalConnections(options = {}) {
114
- const path = resolveGlobalConnectionsPath(options.env ?? process.env);
115
- const payload = readJsonFile(path);
116
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
117
- return { connections: {} };
118
- }
119
- const rawConnections = payload.connections;
120
- const connections = {};
121
- if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
122
- for (const [alias, raw] of Object.entries(rawConnections)) {
123
- const connection = normalizeConnection(raw);
124
- if (connection)
125
- connections[alias] = connection;
126
- }
97
+ function runTitleOf(run) {
98
+ return firstString(run, ["title", "summary", "name"], taskIdOf(run) || "(untitled)");
99
+ }
100
+ function printFormattedOutput(message) {
101
+ console.log(message);
102
+ }
103
+ function formatSection(title, subtitle) {
104
+ return `${pc.bold(accent("\u25C6"))} ${pc.bold(title)}${subtitle ? dim(` \u2014 ${subtitle}`) : ""}`;
105
+ }
106
+ function formatLegacyAutomationSurface() {
107
+ return [];
108
+ }
109
+ function formatRunList(runs, options = {}) {
110
+ if (runs.length === 0) {
111
+ return [
112
+ formatSection("Runs", "none recorded"),
113
+ options.source === "server" ? dim("No runs recorded on the selected server.") : dim("No runs recorded in local state."),
114
+ "",
115
+ ...formatLegacyAutomationSurface()
116
+ ].join(`
117
+ `);
127
118
  }
128
- return { connections };
119
+ const body = runs.map((run) => {
120
+ const row = run;
121
+ const runId = runIdOf(row);
122
+ const status = firstString(row, ["status"], "unknown");
123
+ const runtime = firstString(row, ["runtime", "runtimeAdapter"], "");
124
+ return [
125
+ pc.bold(runId),
126
+ statusColor(status)(pad(truncate(status, 12), 12)),
127
+ `${runTitleOf(row)}${runtime ? dim(` ${runtime}`) : ""}`
128
+ ].join(" ");
129
+ });
130
+ return [formatSection("Runs", options.source === "server" ? "selected server" : "local state"), ...body, "", ...formatLegacyAutomationSurface()].join(`
131
+ `);
129
132
  }
130
- function readRepoConnection(projectRoot) {
131
- const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
132
- if (!payload || typeof payload !== "object" || Array.isArray(payload))
133
- return null;
134
- const record = payload;
135
- const selected = typeof record.selected === "string" ? record.selected.trim() : "";
136
- if (!selected)
137
- return null;
133
+
134
+ // packages/cli/src/commands/_run-projection.ts
135
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
136
+ import { resolve as resolve2 } from "path";
137
+ import {
138
+ foldRunSessionEntries,
139
+ sessionIdFromSessionFile as sessionIdFromSessionFile2
140
+ } from "@rig/contracts";
141
+ import { listAgentRuntimes as listAgentRuntimes2 } from "@rig/runtime/control-plane/runtime/isolation";
142
+
143
+ // packages/cli/src/commands/_run-bridge.ts
144
+ import { existsSync, readdirSync, readFileSync } from "fs";
145
+ import { dirname, resolve } from "path";
146
+ import { CUSTOM_TYPE_FOR, sessionIdFromSessionFile } from "@rig/contracts";
147
+ import { createPluginHost } from "@rig/core";
148
+ import { loadConfig } from "@rig/core/load-config";
149
+ import { resolveOwnerNamespaceKey, resolveRegistryBaseUrl, resolveRegistrySecret, resolveSelectedRemote } from "@rig/runtime/control-plane/remote";
150
+ import { listAgentRuntimes } from "@rig/runtime/control-plane/runtime/isolation";
151
+ import { createRegistryClient } from "@rig/relay-registry";
152
+ import {
153
+ listActiveCollabSessions as ompListActiveCollabSessions
154
+ } from "@oh-my-pi/pi-coding-agent/collab/api";
155
+ function registrySecret(env = process.env) {
156
+ return resolveRegistrySecret(process.cwd(), env) ?? null;
157
+ }
158
+ function registryBaseUrl(env = process.env) {
159
+ return resolveRegistryBaseUrl(process.cwd(), env);
160
+ }
161
+ function collabFromRegistryEntry(entry) {
138
162
  return {
139
- selected,
140
- project: typeof record.project === "string" ? record.project : undefined,
141
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
142
- serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
163
+ sessionId: entry.roomId,
164
+ sessionPath: "",
165
+ cwd: "",
166
+ title: entry.title,
167
+ joinLink: entry.joinLink,
168
+ webLink: entry.webLink,
169
+ relayUrl: entry.relayUrl,
170
+ owner: entry.owner,
171
+ selectedRepo: entry.repo,
172
+ startedAt: entry.startedAt,
173
+ updatedAt: entry.heartbeatAt,
174
+ ...entry.pid === undefined ? {} : { pid: entry.pid },
175
+ stale: entry.stale
143
176
  };
144
177
  }
145
- function writeRepoConnection(projectRoot, state) {
146
- writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
178
+ function isRecord(value) {
179
+ return typeof value === "object" && value !== null;
147
180
  }
148
- function resolveSelectedConnection(projectRoot, options = {}) {
149
- const repo = readRepoConnection(projectRoot);
150
- if (!repo)
181
+ function readLocalRunCollab(runtime) {
182
+ if (!runtime.sessionDir || !existsSync(runtime.sessionDir))
183
+ return null;
184
+ let files;
185
+ try {
186
+ files = readdirSync(runtime.sessionDir).filter((f) => f.endsWith(".jsonl")).sort().reverse();
187
+ } catch {
151
188
  return null;
152
- if (repo.selected === "local")
153
- return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
154
- const global = readGlobalConnections(options);
155
- const connection = global.connections[repo.selected];
156
- if (!connection) {
157
- throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
158
189
  }
159
- return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
190
+ for (const file of files) {
191
+ let lines;
192
+ try {
193
+ lines = readFileSync(resolve(runtime.sessionDir, file), "utf8").split(`
194
+ `);
195
+ } catch {
196
+ continue;
197
+ }
198
+ let host = null;
199
+ for (const line of lines) {
200
+ if (!line.includes("collab-host-started"))
201
+ continue;
202
+ let entry;
203
+ try {
204
+ entry = JSON.parse(line);
205
+ } catch {
206
+ continue;
207
+ }
208
+ if (!isRecord(entry) || entry.customType !== CUSTOM_TYPE_FOR["timeline-entry"] || !isRecord(entry.data))
209
+ continue;
210
+ const payload = isRecord(entry.data.payload) ? entry.data.payload : entry.data;
211
+ if (payload.type !== "collab-host-started")
212
+ continue;
213
+ host = {
214
+ roomId: typeof payload.roomId === "string" ? payload.roomId : undefined,
215
+ joinLink: typeof payload.joinLink === "string" ? payload.joinLink : undefined,
216
+ webLink: typeof payload.webLink === "string" ? payload.webLink : undefined,
217
+ relayUrl: typeof payload.relayUrl === "string" ? payload.relayUrl : undefined,
218
+ at: typeof entry.data.at === "string" ? entry.data.at : undefined
219
+ };
220
+ }
221
+ if (!host?.joinLink)
222
+ continue;
223
+ const at = host.at ?? "";
224
+ const sessionId = host.roomId ?? sessionIdFromSessionFile(resolve(runtime.sessionDir, file)) ?? runtime.id;
225
+ return {
226
+ sessionId,
227
+ sessionPath: resolve(runtime.sessionDir, file),
228
+ cwd: runtime.workspaceDir,
229
+ joinLink: host.joinLink,
230
+ webLink: host.webLink ?? "",
231
+ relayUrl: host.relayUrl ?? "",
232
+ title: runtime.taskId ? `[${runtime.taskId}] Rig run ${sessionId}` : `Rig run ${sessionId}`,
233
+ startedAt: at,
234
+ updatedAt: at,
235
+ stale: false
236
+ };
237
+ }
238
+ return null;
239
+ }
240
+ async function listLocalRunCollabSessions(projectRoot) {
241
+ let runtimes;
242
+ try {
243
+ runtimes = await listAgentRuntimes(projectRoot);
244
+ } catch {
245
+ return [];
246
+ }
247
+ const out = [];
248
+ for (const runtime of runtimes) {
249
+ const collab = readLocalRunCollab(runtime);
250
+ if (collab)
251
+ out.push(collab);
252
+ }
253
+ return out;
254
+ }
255
+ async function listActiveRunCollab(filter) {
256
+ return defaultListActiveCollabSessions(filter);
160
257
  }
161
- function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
162
- const repo = readRepoConnection(projectRoot);
163
- if (!repo)
164
- return;
165
- writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
258
+ async function defaultListActiveCollabSessions(filter) {
259
+ const projectRoot = process.cwd();
260
+ const local = await listLocalRunCollabSessions(projectRoot);
261
+ const namespaceKey = filter.namespaceKey ?? resolveOwnerNamespaceKey(projectRoot);
262
+ const secret = registrySecret();
263
+ const remote = namespaceKey && secret ? (await createRegistryClient({ baseUrl: registryBaseUrl(), namespaceKey, secret }).listRoomsByOwner(filter)).map(collabFromRegistryEntry) : await ompListActiveCollabSessions(filter);
264
+ const seen = new Set(local.map((c) => c.sessionId));
265
+ return [...local, ...remote.filter((c) => !seen.has(c.sessionId))];
166
266
  }
167
267
 
168
- // packages/cli/src/commands/_server-client.ts
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
- }
179
- function cleanToken(value) {
180
- const trimmed = value?.trim();
181
- return trimmed ? trimmed : null;
182
- }
183
- function readPrivateRemoteSessionToken(projectRoot) {
184
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
185
- if (!existsSync2(path))
268
+ // packages/cli/src/commands/_run-diagnostics.ts
269
+ function normalizeString(value) {
270
+ if (typeof value !== "string")
186
271
  return null;
187
- try {
188
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
189
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
190
- } catch {
272
+ const normalized = value.replace(/\s+/g, " ").trim();
273
+ return normalized.length > 0 ? normalized : null;
274
+ }
275
+ function isGenericRunFailure(value) {
276
+ const text = normalizeString(value);
277
+ return Boolean(text && /^Task run failed \([^)]*\)$/i.test(text));
278
+ }
279
+ function appendCandidate(candidates, value) {
280
+ const text = normalizeString(value);
281
+ if (text && !candidates.includes(text))
282
+ candidates.push(text);
283
+ }
284
+ function categorizeUsefulRunError(lines) {
285
+ const taskSourceFailure = lines.find((line) => /failed to update task source/i.test(line));
286
+ if (taskSourceFailure)
287
+ return `Task source update failed: ${taskSourceFailure}`;
288
+ const moduleFailure = lines.find((line) => /cannot find module/i.test(line));
289
+ if (moduleFailure)
290
+ return `Runtime module resolution failed: ${moduleFailure}`;
291
+ const providerFailure = lines.find((line) => /no api key found|unauthorized|authentication failed|invalid api key/i.test(line));
292
+ if (providerFailure)
293
+ return `Provider authentication failed: ${providerFailure}`;
294
+ return null;
295
+ }
296
+ function summarizeUsefulRunError(projection) {
297
+ if (projection.status !== "failed")
191
298
  return null;
299
+ const candidates = [];
300
+ for (const anomaly of projection.anomalies) {
301
+ appendCandidate(candidates, `Journal anomaly (${anomaly.kind}): ${anomaly.detail}`);
302
+ }
303
+ for (const phase of projection.closeoutPhases) {
304
+ if (phase.outcome === "failed")
305
+ appendCandidate(candidates, phase.detail);
192
306
  }
307
+ for (const entry of projection.statusHistory) {
308
+ appendCandidate(candidates, entry.reason);
309
+ }
310
+ appendCandidate(candidates, projection.record.statusDetail);
311
+ appendCandidate(candidates, projection.record.errorText);
312
+ const nonGeneric = candidates.filter((candidate) => !isGenericRunFailure(candidate));
313
+ return categorizeUsefulRunError(nonGeneric) ?? nonGeneric.at(-1) ?? normalizeString(projection.record.errorText);
193
314
  }
194
- function readGitHubBearerTokenForRemote(projectRoot) {
195
- const scopedKey = resolve2(projectRoot);
196
- if (scopedGitHubBearerTokens.has(scopedKey))
197
- return scopedGitHubBearerTokens.get(scopedKey) ?? null;
198
- const privateSession = readPrivateRemoteSessionToken(projectRoot);
199
- if (privateSession)
200
- return privateSession;
201
- return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
315
+
316
+ // packages/cli/src/commands/_run-projection.ts
317
+ var EMPTY_PROJECTION = foldRunSessionEntries([], "");
318
+ function stringOrNull(value) {
319
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
202
320
  }
203
- function readStoredGitHubAuthToken(projectRoot) {
204
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
205
- if (!existsSync2(path))
321
+ function newestSessionFile(sessionDir) {
322
+ if (!sessionDir || !existsSync2(sessionDir))
206
323
  return null;
207
324
  try {
208
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
209
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
325
+ const files = readdirSync2(sessionDir).filter((file) => file.endsWith(".jsonl")).sort().reverse();
326
+ return files[0] ? resolve2(sessionDir, files[0]) : null;
210
327
  } catch {
211
328
  return null;
212
329
  }
213
330
  }
214
- function readLocalConnectionFallbackToken(projectRoot) {
215
- return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
216
- }
217
- async function ensureServerForCli(projectRoot) {
331
+ function readSessionRunEntries(sessionPath) {
332
+ if (!sessionPath || !existsSync2(sessionPath))
333
+ return [];
334
+ let raw;
218
335
  try {
219
- const selected = resolveSelectedConnection(projectRoot);
220
- if (selected?.connection.kind === "remote") {
221
- reportServerPhase(`Connecting to ${selected.alias}\u2026`);
222
- const authToken = readGitHubBearerTokenForRemote(projectRoot);
223
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
224
- return {
225
- baseUrl: selected.connection.baseUrl,
226
- authToken,
227
- connectionKind: "remote",
228
- serverProjectRoot
229
- };
230
- }
231
- reportServerPhase("Starting local Rig server\u2026");
232
- const connection = await ensureLocalRigServerConnection(projectRoot);
233
- return {
234
- baseUrl: connection.baseUrl,
235
- authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
236
- connectionKind: "local",
237
- serverProjectRoot: resolve2(projectRoot)
238
- };
239
- } catch (error) {
240
- if (error instanceof Error) {
241
- throw new CliError(error.message, 1);
336
+ raw = readFileSync2(sessionPath, "utf8");
337
+ } catch {
338
+ return [];
339
+ }
340
+ const entries = [];
341
+ for (const line of raw.split(`
342
+ `)) {
343
+ if (!line.trim())
344
+ continue;
345
+ let parsed;
346
+ try {
347
+ parsed = JSON.parse(line);
348
+ } catch {
349
+ continue;
242
350
  }
243
- throw error;
351
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
352
+ continue;
353
+ entries.push({
354
+ type: "type" in parsed && typeof parsed.type === "string" ? parsed.type : "",
355
+ ..."customType" in parsed && typeof parsed.customType === "string" ? { customType: parsed.customType } : {},
356
+ ..."data" in parsed ? { data: parsed.data } : {}
357
+ });
244
358
  }
359
+ return entries;
245
360
  }
246
- async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
247
- const repo = readRepoConnection(projectRoot);
248
- const slug = repo?.project?.trim();
249
- if (!slug)
361
+ function recordFromProjection(runId, projection, presence) {
362
+ const record = projection.record;
363
+ const status = projection.status ?? (presence.stale ? "stale" : presence.live ? "running" : "unknown");
364
+ const taskId = stringOrNull(record.taskId) ?? presence.fallbackTaskId ?? null;
365
+ const title = stringOrNull(record.title) ?? stringOrNull(presence.title) ?? (taskId ? `Run ${taskId}` : `Rig run ${runId}`);
366
+ return {
367
+ runId,
368
+ taskId,
369
+ title,
370
+ status,
371
+ source: presence.source,
372
+ live: presence.live,
373
+ stale: presence.stale,
374
+ startedAt: stringOrNull(record.startedAt) ?? presence.startedAt ?? null,
375
+ updatedAt: stringOrNull(record.updatedAt) ?? presence.updatedAt ?? projection.lastEventAt ?? null,
376
+ completedAt: stringOrNull(record.completedAt),
377
+ joinLink: presence.joinLink ?? null,
378
+ webLink: presence.webLink ?? null,
379
+ relayUrl: presence.relayUrl ?? null,
380
+ sessionPath: stringOrNull(record.sessionPath) ?? presence.sessionPath ?? null,
381
+ prUrl: stringOrNull(record.prUrl),
382
+ worktreePath: stringOrNull(record.worktreePath),
383
+ pendingApprovals: projection.pendingApprovals.length,
384
+ pendingInputs: projection.pendingUserInputs.length,
385
+ steeringCount: projection.steeringCount,
386
+ stallCount: projection.stallCount,
387
+ errorSummary: summarizeUsefulRunError(projection),
388
+ projection
389
+ };
390
+ }
391
+ function recordFromLiveCollab(collab) {
392
+ const runId = collab.sessionId;
393
+ const sessionPath = stringOrNull(collab.sessionPath);
394
+ const projection = sessionPath ? foldRunSessionEntries(readSessionRunEntries(sessionPath), runId) : EMPTY_PROJECTION;
395
+ return recordFromProjection(runId, projection, {
396
+ source: sessionPath ? "local" : "remote",
397
+ live: !collab.stale,
398
+ stale: Boolean(collab.stale),
399
+ title: collab.title,
400
+ joinLink: stringOrNull(collab.joinLink),
401
+ webLink: stringOrNull(collab.webLink),
402
+ relayUrl: stringOrNull(collab.relayUrl),
403
+ sessionPath,
404
+ startedAt: stringOrNull(collab.startedAt),
405
+ updatedAt: stringOrNull(collab.updatedAt)
406
+ });
407
+ }
408
+ function recordFromRuntime(runtime) {
409
+ const sessionPath = newestSessionFile(runtime.sessionDir ?? "");
410
+ if (!sessionPath)
250
411
  return null;
412
+ const projection = foldRunSessionEntries(readSessionRunEntries(sessionPath), runtime.id);
413
+ if (projection.lastSeq === 0)
414
+ return null;
415
+ return recordFromProjection(sessionIdFromSessionFile2(sessionPath) ?? runtime.id, projection, {
416
+ source: "local",
417
+ live: false,
418
+ stale: false,
419
+ sessionPath,
420
+ fallbackTaskId: stringOrNull(runtime.taskId)
421
+ });
422
+ }
423
+ function sortByRecency(records) {
424
+ return [...records].sort((a, b) => {
425
+ const at = Date.parse(b.updatedAt ?? b.startedAt ?? "") || 0;
426
+ const bt = Date.parse(a.updatedAt ?? a.startedAt ?? "") || 0;
427
+ return at - bt;
428
+ });
429
+ }
430
+ async function listRunProjections(projectRoot, filter = {}) {
431
+ const byRunId = new Map;
432
+ let live = [];
251
433
  try {
252
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
253
- headers: mergeHeaders(undefined, authToken)
254
- });
255
- if (!response.ok)
256
- return null;
257
- const payload = await response.json();
258
- const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
259
- const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
260
- const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
261
- const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
262
- if (path)
263
- writeRepoServerProjectRoot(projectRoot, path);
264
- return path;
434
+ live = await listActiveRunCollab(filter);
265
435
  } catch {
266
- return null;
436
+ live = [];
437
+ }
438
+ for (const collab of live) {
439
+ const record = recordFromLiveCollab(collab);
440
+ byRunId.set(record.runId, record);
441
+ }
442
+ let runtimes = [];
443
+ try {
444
+ runtimes = await listAgentRuntimes2(projectRoot);
445
+ } catch {
446
+ runtimes = [];
267
447
  }
448
+ for (const runtime of runtimes) {
449
+ const record = recordFromRuntime(runtime);
450
+ if (!record)
451
+ continue;
452
+ const sessionId = sessionIdFromSessionFile2(record.sessionPath);
453
+ const liveMatch = sessionId ? byRunId.get(sessionId) : undefined;
454
+ if (liveMatch) {
455
+ if (liveMatch.source === "remote" || !liveMatch.sessionPath) {
456
+ byRunId.set(liveMatch.runId, { ...liveMatch, sessionPath: record.sessionPath, source: "local" });
457
+ }
458
+ continue;
459
+ }
460
+ if (byRunId.has(runtime.id))
461
+ continue;
462
+ byRunId.set(record.runId, record);
463
+ }
464
+ return sortByRecency([...byRunId.values()]);
465
+ }
466
+ async function getRunProjection(projectRoot, runId, filter = {}) {
467
+ const runs = await listRunProjections(projectRoot, filter);
468
+ return runs.find((run) => run.runId === runId) ?? runs.find((run) => run.runId.startsWith(runId)) ?? runs.find((run) => run.taskId === runId) ?? null;
268
469
  }
269
- function mergeHeaders(headers, authToken) {
270
- const merged = new Headers(headers);
271
- if (authToken) {
272
- merged.set("authorization", `Bearer ${authToken}`);
470
+
471
+ // packages/cli/src/commands/inspect.ts
472
+ var INSPECT_USAGE = "rig inspect <logs|artifact|artifacts|run-logs|runs|failures|graph|audit|diff>";
473
+ function printInspectHelp() {
474
+ console.log([
475
+ `Usage: ${INSPECT_USAGE}`,
476
+ "",
477
+ "Commands:",
478
+ " logs --task <id> Print log lines from the latest projected run for a task.",
479
+ " artifact --task <id> --file <name>",
480
+ " Preview a single task artifact.",
481
+ " artifacts --task <id> List task artifacts.",
482
+ " run-logs --run <id> Print log lines from a run journal.",
483
+ " runs List all projected runs.",
484
+ " diff --task <id> List files changed by a task.",
485
+ " graph [--task <id>] Print the task dependency graph.",
486
+ " failures --task <id> Summarize failed runs, closeout failures, and journal anomalies.",
487
+ " audit Controlled-command audit is not readable yet in this CLI."
488
+ ].join(`
489
+ `));
490
+ }
491
+ function asRecord(value) {
492
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
493
+ }
494
+ function firstString2(record, keys) {
495
+ for (const key of keys) {
496
+ const value = record[key];
497
+ if (typeof value === "string" && value.trim().length > 0) {
498
+ return value;
499
+ }
273
500
  }
274
- return merged;
501
+ return null;
275
502
  }
276
- function diagnosticMessage(payload) {
277
- if (!payload || typeof payload !== "object" || Array.isArray(payload))
503
+ function stringifyLogPayload(value) {
504
+ if (typeof value === "string")
505
+ return value;
506
+ if (typeof value === "number" || typeof value === "boolean")
507
+ return String(value);
508
+ const record = asRecord(value);
509
+ if (!record)
278
510
  return null;
279
- const record = payload;
280
- const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
281
- const messages = diagnostics.flatMap((entry) => {
282
- if (!entry || typeof entry !== "object" || Array.isArray(entry))
283
- return [];
284
- const diagnostic = entry;
285
- const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
286
- const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
287
- return message ? [`${kind}: ${message}`] : [];
288
- });
289
- return messages.length > 0 ? messages.join("; ") : null;
290
- }
291
- var serverReachabilityCache = new Map;
292
- async function probeServerReachability(baseUrl, authToken) {
511
+ const direct = firstString2(record, ["line", "message", "text", "content", "output", "summary", "detail"]);
512
+ if (direct)
513
+ return direct;
293
514
  try {
294
- const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
295
- headers: mergeHeaders(undefined, authToken),
296
- signal: AbortSignal.timeout(1500)
297
- });
298
- return response.ok;
515
+ return JSON.stringify(record);
299
516
  } catch {
300
- return false;
517
+ return String(record);
301
518
  }
302
519
  }
303
- function cachedServerReachability(projectRoot, baseUrl, authToken) {
304
- const key = resolve2(projectRoot);
305
- const cached = serverReachabilityCache.get(key);
306
- if (cached)
307
- return cached;
308
- const probe = probeServerReachability(baseUrl, authToken);
309
- serverReachabilityCache.set(key, probe);
310
- return probe;
311
- }
312
- function describeSelectedServer(projectRoot, server) {
313
- try {
314
- const selected = resolveSelectedConnection(projectRoot);
315
- if (selected) {
316
- return {
317
- alias: selected.alias,
318
- target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
319
- };
520
+ function logLineFromValue(value) {
521
+ const record = asRecord(value);
522
+ if (!record)
523
+ return stringifyLogPayload(value);
524
+ const direct = firstString2(record, ["line", "message", "text", "content", "output", "summary", "detail"]);
525
+ if (direct)
526
+ return direct;
527
+ if ("payload" in record)
528
+ return stringifyLogPayload(record.payload);
529
+ if ("data" in record)
530
+ return stringifyLogPayload(record.data);
531
+ return stringifyLogPayload(record);
532
+ }
533
+ function logLineFromSessionEntry(entry) {
534
+ if (entry.type !== "custom" || entry.customType !== RIG_RUN_LOG_ENTRY)
535
+ return null;
536
+ return logLineFromValue(entry.data);
537
+ }
538
+ function logLinesFromProjection(projection) {
539
+ const projected = projection;
540
+ const lines = [];
541
+ for (const key of ["logs", "logEntries", "journalLogs", "runLogs"]) {
542
+ const entries = projected[key];
543
+ if (!Array.isArray(entries))
544
+ continue;
545
+ for (const entry of entries) {
546
+ const line = logLineFromValue(entry);
547
+ if (line !== null)
548
+ lines.push(line);
320
549
  }
321
- } catch {}
322
- return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
550
+ }
551
+ return lines;
323
552
  }
324
- async function buildServerFailureContext(projectRoot, server) {
325
- const { alias, target } = describeSelectedServer(projectRoot, server);
326
- const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
327
- const reachability = reachable ? "server is reachable" : "server is unreachable";
553
+ function toRunLike(run) {
328
554
  return {
329
- contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
330
- hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
555
+ runId: run.runId,
556
+ taskId: run.taskId,
557
+ title: run.title,
558
+ status: run.status,
559
+ source: run.source,
560
+ live: run.live,
561
+ stale: run.stale,
562
+ startedAt: run.startedAt,
563
+ updatedAt: run.updatedAt,
564
+ completedAt: run.completedAt,
565
+ joinLink: run.joinLink,
566
+ prUrl: run.prUrl,
567
+ pendingApprovals: run.pendingApprovals,
568
+ pendingInputs: run.pendingInputs,
569
+ steeringCount: run.steeringCount,
570
+ stallCount: run.stallCount
331
571
  };
332
572
  }
333
- async function requestServerJson(context, pathname, init = {}) {
334
- const server = await ensureServerForCli(context.projectRoot);
335
- const headers = mergeHeaders(init.headers, server.authToken);
336
- if (server.serverProjectRoot)
337
- headers.set("x-rig-project-root", server.serverProjectRoot);
338
- reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
339
- let response;
340
- try {
341
- response = await fetch(`${server.baseUrl}${pathname}`, {
342
- ...init,
343
- headers
573
+ function requireRunId(value, usage) {
574
+ if (!value || !value.trim()) {
575
+ throw new CliError(`${usage} requires a run id.`, 2, { hint: "Run `rig inspect runs` to find run ids." });
576
+ }
577
+ return value.trim();
578
+ }
579
+ async function executeLogs(context, args, deps) {
580
+ const taskResult = takeOption(args, "--task");
581
+ requireNoExtraArgs(taskResult.rest, "rig inspect logs --task <id>");
582
+ const taskId = requireTask(taskResult.value, "rig inspect logs --task <id>");
583
+ const projection = await (deps.getRunProjection ?? getRunProjection)(context.projectRoot, taskId);
584
+ if (!projection) {
585
+ throw new CliError(`No projected runs found for task ${taskId}.`, 1, {
586
+ hint: "Run `rig run list` to confirm the run journal is discoverable."
344
587
  });
345
- } catch (error) {
346
- const failure = await buildServerFailureContext(context.projectRoot, server);
347
- throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
348
- ${failure.contextLine}`, 1, { hint: failure.hint });
349
588
  }
350
- const text = await response.text();
351
- const payload = text.trim().length > 0 ? (() => {
352
- try {
353
- return JSON.parse(text);
354
- } catch {
355
- return null;
356
- }
357
- })() : null;
358
- if (!response.ok) {
359
- const diagnostics = diagnosticMessage(payload);
360
- const rawDetail = diagnostics ?? (text || response.statusText);
361
- const detail = diagnostics ? rawDetail : rawDetail.split(`
362
- `).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 200) ?? response.statusText;
363
- const failure = await buildServerFailureContext(context.projectRoot, server);
364
- throw new CliError(`Rig server request failed (${response.status}): ${detail}
365
- ${failure.contextLine}`, 1, { hint: failure.hint });
366
- }
367
- return payload;
368
- }
369
- async function listRunsViaServer(context, options = {}) {
370
- const url = new URL("http://rig.local/api/runs");
371
- if (options.limit !== undefined)
372
- url.searchParams.set("limit", String(options.limit));
373
- const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
374
- const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
375
- return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
376
- }
377
- async function getRunLogsViaServer(context, runId, options = {}) {
378
- const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/logs`);
379
- if (options.limit !== undefined)
380
- url.searchParams.set("limit", String(options.limit));
381
- if (options.cursor)
382
- url.searchParams.set("cursor", options.cursor);
383
- const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
384
- return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
385
- }
386
- var RESUMABLE_RUN_STATUSES = new Set([
387
- "created",
388
- "preparing",
389
- "running",
390
- "validating",
391
- "reviewing",
392
- "stopped",
393
- "failed",
394
- "needs-attention",
395
- "needs_attention"
396
- ]);
397
-
398
- // packages/cli/src/commands/_async-ui.ts
399
- import pc from "picocolors";
400
-
401
- // packages/cli/src/commands/_spinner.ts
402
- var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
403
- function createTtySpinner(input) {
404
- const output = input.output ?? process.stdout;
405
- const isTty = output.isTTY === true;
406
- const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
407
- let label = input.label;
408
- let frame = 0;
409
- let paused = false;
410
- let stopped = false;
411
- let lastPrintedLabel = "";
412
- const render = () => {
413
- if (stopped || paused)
414
- return;
415
- if (!isTty) {
416
- if (label !== lastPrintedLabel) {
417
- output.write(`${label}
418
- `);
419
- lastPrintedLabel = label;
420
- }
421
- return;
589
+ const projectedLines = logLinesFromProjection(projection.projection);
590
+ const sessionLines = projectedLines.length > 0 ? [] : (deps.readSessionRunEntries ?? readSessionRunEntries)(projection.sessionPath).map(logLineFromSessionEntry).filter((line) => line !== null);
591
+ const lines = projectedLines.length > 0 ? projectedLines : sessionLines;
592
+ if (context.outputMode === "json") {
593
+ console.log(JSON.stringify({ taskId, runId: projection.runId, lines }, null, 2));
594
+ } else if (lines.length === 0) {
595
+ console.log(`No log entries found for task ${taskId}.`);
596
+ } else {
597
+ for (const line of lines)
598
+ console.log(line);
599
+ }
600
+ return { ok: true, group: "inspect", command: "logs", details: { taskId, runId: projection.runId, count: lines.length } };
601
+ }
602
+ async function executeArtifact(context, args, deps) {
603
+ const taskResult = takeOption(args, "--task");
604
+ const fileResult = takeOption(taskResult.rest, "--file");
605
+ requireNoExtraArgs(fileResult.rest, "rig inspect artifact --task <id> --file <name>");
606
+ const taskId = requireTask(taskResult.value, "rig inspect artifact --task <id> --file <name>");
607
+ const filename = fileResult.value?.trim();
608
+ if (!filename) {
609
+ throw new CliError("rig inspect artifact --task <id> --file <name> requires an artifact file name.", 2);
610
+ }
611
+ const preview = await (deps.taskArtifactRead ?? taskArtifactRead)(context.projectRoot, filename, {
612
+ taskId,
613
+ maxBytes: 64 * 1024
614
+ });
615
+ if (context.outputMode === "json") {
616
+ console.log(JSON.stringify({ taskId, file: filename, ...preview }, null, 2));
617
+ } else {
618
+ console.log(preview.contents);
619
+ if (preview.truncated) {
620
+ console.log(`
621
+ [truncated at ${preview.maxBytes} bytes; artifact is ${preview.sizeBytes} bytes]`);
422
622
  }
423
- frame = (frame + 1) % frames.length;
424
- const glyph = frames[frame] ?? frames[0] ?? "";
425
- output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
426
- };
427
- const clearLine = () => {
428
- if (isTty)
429
- output.write("\r\x1B[2K");
430
- };
431
- render();
432
- const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
623
+ }
433
624
  return {
434
- setLabel(next) {
435
- label = next;
436
- render();
437
- },
438
- pause() {
439
- paused = true;
440
- clearLine();
441
- },
442
- resume() {
443
- if (stopped)
444
- return;
445
- paused = false;
446
- render();
447
- },
448
- stop(finalLine) {
449
- if (stopped)
450
- return;
451
- stopped = true;
452
- if (timer)
453
- clearInterval(timer);
454
- clearLine();
455
- if (finalLine)
456
- output.write(`${finalLine}
457
- `);
458
- }
625
+ ok: true,
626
+ group: "inspect",
627
+ command: "artifact",
628
+ details: { taskId, file: filename, ...preview }
459
629
  };
460
630
  }
461
-
462
- // packages/cli/src/app/theme.ts
463
- var RIG_PALETTE = {
464
- ink: "#f2f3f6",
465
- ink2: "#aeb0ba",
466
- ink3: "#6c6e79",
467
- ink4: "#44464f",
468
- accent: "#ccff4d",
469
- accentDim: "#a9d63f",
470
- cyan: "#56d8ff",
471
- red: "#ff5d5d",
472
- yellow: "#ffd24d"
473
- };
474
- function hexToRgb(hex) {
475
- const value = hex.replace("#", "");
476
- return [
477
- Number.parseInt(value.slice(0, 2), 16),
478
- Number.parseInt(value.slice(2, 4), 16),
479
- Number.parseInt(value.slice(4, 6), 16)
480
- ];
481
- }
482
- function fg(hex) {
483
- const [r, g, b] = hexToRgb(hex);
484
- return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
485
- }
486
- var ink = fg(RIG_PALETTE.ink);
487
- var ink2 = fg(RIG_PALETTE.ink2);
488
- var ink3 = fg(RIG_PALETTE.ink3);
489
- var ink4 = fg(RIG_PALETTE.ink4);
490
- var accent = fg(RIG_PALETTE.accent);
491
- var accentDim = fg(RIG_PALETTE.accentDim);
492
- var cyan = fg(RIG_PALETTE.cyan);
493
- var red = fg(RIG_PALETTE.red);
494
- var yellow = fg(RIG_PALETTE.yellow);
495
- function bold(text) {
496
- return `\x1B[1m${text}\x1B[22m`;
497
- }
498
- var DRONE_ART = [
499
- " .-=-. .-=-. ",
500
- " ( !!! ) ( !!! ) ",
501
- " '-=-'._ _.'-=-' ",
502
- " '._ _.' ",
503
- " '=$$$$$$$=.' ",
504
- " =$$$$$$$$$$$= ",
505
- " $$$@@@@@@@@@@$$$ ",
506
- " $$$@@ @@$$$ ",
507
- " $$@ ? @$$$ ",
508
- " $$$@ '-' @$$$ ",
509
- " $$$@@ @@$$$ ",
510
- " $$$@@@@@@@@@@$$$ ",
511
- " =$$$$$$$$$$$= ",
512
- " '=$$$$$$$=.' ",
513
- " _.' '._ ",
514
- " .-=-.' '.-=-. ",
515
- " ( !!! ) ( !!! ) ",
516
- " '-=-' '-=-' "
517
- ];
518
- var EYE_FRAMES = ["@", "o", "."];
519
- var DRONE_WIDTH = DRONE_ART[0].length;
520
- var DRONE_HEIGHT = DRONE_ART.length;
521
- var MICRO_BLADES = ["---", "\\\\\\", "|||", "///"];
522
- function microDroneFrame(tick) {
523
- const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
524
- const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
525
- return `(${blade})${eye}(${blade})`;
526
- }
527
- var MICRO_DRONE_FRAMES = Array.from({ length: 12 }, (_, index) => microDroneFrame(index));
528
- function renderMicroDroneFrame(tick) {
529
- const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
530
- const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
531
- return `${ink4("(")}${cyan(blade)}${ink4(")")}${bold(accent(eye))}${ink4("(")}${cyan(blade)}${ink4(")")}`;
532
- }
533
-
534
- // packages/cli/src/commands/_async-ui.ts
535
- var DONE_SYMBOL = pc.green("\u25C7");
536
- var FAIL_SYMBOL = pc.red("\u25A0");
537
- var activeUpdate = null;
538
- async function withSpinner(label, work, options = {}) {
539
- if (options.outputMode === "json") {
540
- return work(() => {});
541
- }
542
- if (activeUpdate) {
543
- const outer = activeUpdate;
544
- outer(label);
545
- return work(outer);
546
- }
547
- const output = options.output ?? process.stderr;
548
- const isTty = output.isTTY === true;
549
- let lastLabel = label;
550
- if (!isTty) {
551
- output.write(`${label}
552
- `);
553
- const update2 = (next) => {
554
- lastLabel = next;
555
- };
556
- activeUpdate = update2;
557
- const previousListener2 = setServerPhaseListener(update2);
558
- try {
559
- return await work(update2);
560
- } finally {
561
- activeUpdate = null;
562
- setServerPhaseListener(previousListener2);
631
+ async function executeRunLogs(context, args, deps) {
632
+ const runResult = takeOption(args, "--run");
633
+ requireNoExtraArgs(runResult.rest, "rig inspect run-logs --run <id>");
634
+ const runId = requireRunId(runResult.value, "rig inspect run-logs --run <id>");
635
+ const projection = await (deps.getRunProjection ?? getRunProjection)(context.projectRoot, runId);
636
+ if (!projection) {
637
+ throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig inspect runs` to see runs." });
638
+ }
639
+ const sessionLines = (deps.readSessionRunEntries ?? readSessionRunEntries)(projection.sessionPath).map(logLineFromSessionEntry).filter((line) => line !== null);
640
+ const lines = sessionLines.length > 0 ? sessionLines : logLinesFromProjection(projection.projection);
641
+ if (context.outputMode === "json") {
642
+ console.log(JSON.stringify({ runId: projection.runId, taskId: projection.taskId, lines }, null, 2));
643
+ } else if (lines.length === 0) {
644
+ console.log(`No log entries found for run ${projection.runId}.`);
645
+ } else {
646
+ for (const line of lines)
647
+ console.log(line);
648
+ }
649
+ return { ok: true, group: "inspect", command: "run-logs", details: { runId: projection.runId, taskId: projection.taskId, count: lines.length } };
650
+ }
651
+ async function executeRuns(context, args, deps) {
652
+ requireNoExtraArgs(args, "rig inspect runs");
653
+ const runs = await (deps.listRunProjections ?? listRunProjections)(context.projectRoot);
654
+ const formatted = runs.map(toRunLike);
655
+ if (context.outputMode === "text") {
656
+ printFormattedOutput(formatRunList(formatted));
657
+ }
658
+ return { ok: true, group: "inspect", command: "runs", details: { runs: formatted } };
659
+ }
660
+ async function executeArtifacts(context, args, deps) {
661
+ const taskResult = takeOption(args, "--task");
662
+ requireNoExtraArgs(taskResult.rest, "rig inspect artifacts --task <id>");
663
+ const taskId = requireTask(taskResult.value, "rig inspect artifacts --task <id>");
664
+ await (deps.taskArtifacts ?? taskArtifacts)(context.projectRoot, taskId);
665
+ return { ok: true, group: "inspect", command: "artifacts", details: { taskId } };
666
+ }
667
+ async function executeDiff(context, args, deps) {
668
+ const taskResult = takeOption(args, "--task");
669
+ requireNoExtraArgs(taskResult.rest, "rig inspect diff --task <id>");
670
+ const taskId = requireTask(taskResult.value, "rig inspect diff --task <id>");
671
+ const files = (deps.changedFilesForTask ?? changedFilesForTask)(context.projectRoot, taskId, true);
672
+ if (context.outputMode === "json") {
673
+ console.log(JSON.stringify({ taskId, files }, null, 2));
674
+ } else {
675
+ for (const file of files)
676
+ console.log(file);
677
+ }
678
+ return { ok: true, group: "inspect", command: "diff", details: { taskId, files } };
679
+ }
680
+ async function executeGraph(context, args, deps) {
681
+ const taskResult = takeOption(args, "--task");
682
+ requireNoExtraArgs(taskResult.rest, "rig inspect graph [--task <id>]");
683
+ await (deps.taskDeps ?? taskDeps)(context.projectRoot, taskResult.value);
684
+ return { ok: true, group: "inspect", command: "graph", details: { taskId: taskResult.value ?? null } };
685
+ }
686
+ function summarizeProjectionFailures(run) {
687
+ const failures = [];
688
+ const projection = run.projection;
689
+ if (run.status === "failed" || projection.status === "failed") {
690
+ failures.push(`${run.runId}: run status failed`);
691
+ }
692
+ for (const phase of projection.closeoutPhases) {
693
+ if (phase.outcome === "failed") {
694
+ failures.push(`${run.runId}: closeout ${phase.phase} failed${phase.detail ? ` \u2014 ${phase.detail}` : ""}`);
563
695
  }
564
696
  }
565
- const spinner = createTtySpinner({
566
- label,
567
- output,
568
- frames: MICRO_DRONE_FRAMES,
569
- styleFrame: (frame) => renderMicroDroneFrame(Math.max(0, MICRO_DRONE_FRAMES.indexOf(frame)))
570
- });
571
- const update = (next) => {
572
- lastLabel = next;
573
- spinner.setLabel(next);
574
- };
575
- activeUpdate = update;
576
- const previousListener = setServerPhaseListener(update);
697
+ for (const anomaly of projection.anomalies) {
698
+ failures.push(`${run.runId}: anomaly ${anomaly.kind}${anomaly.detail ? ` \u2014 ${anomaly.detail}` : ""}`);
699
+ }
700
+ return failures;
701
+ }
702
+ async function runsForTask(context, taskId, deps) {
703
+ const listRuns = deps.listRunProjections ?? listRunProjections;
577
704
  try {
578
- const result = await work(update);
579
- spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
580
- return result;
581
- } catch (error) {
582
- spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
583
- throw error;
584
- } finally {
585
- activeUpdate = null;
586
- setServerPhaseListener(previousListener);
705
+ const runs = await listRuns(context.projectRoot);
706
+ const filtered = runs.filter((run2) => run2.taskId === taskId || run2.runId === taskId);
707
+ if (filtered.length > 0)
708
+ return filtered;
709
+ } catch {}
710
+ const run = await (deps.getRunProjection ?? getRunProjection)(context.projectRoot, taskId);
711
+ return run ? [run] : [];
712
+ }
713
+ async function executeFailures(context, args, deps) {
714
+ const taskResult = takeOption(args, "--task");
715
+ requireNoExtraArgs(taskResult.rest, "rig inspect failures --task <id>");
716
+ const taskId = requireTask(taskResult.value, "rig inspect failures --task <id>");
717
+ const runs = await runsForTask(context, taskId, deps);
718
+ const failures = runs.flatMap(summarizeProjectionFailures);
719
+ if (context.outputMode === "json") {
720
+ console.log(JSON.stringify({ taskId, failures }, null, 2));
721
+ } else if (failures.length === 0) {
722
+ console.log(`No failures recorded for task ${taskId}.`);
723
+ } else {
724
+ for (const failure of failures)
725
+ console.log(failure);
587
726
  }
727
+ return { ok: true, group: "inspect", command: "failures", details: { taskId, count: failures.length } };
588
728
  }
589
-
590
- // packages/cli/src/commands/inspect.ts
591
- async function executeInspect(context, args) {
729
+ function executeAudit(args) {
730
+ requireNoExtraArgs(args, "rig inspect audit");
731
+ throw new CliError("rig inspect audit cannot read the controlled-command audit trail yet: current code only writes audit JSONL and exposes no supported reader API.", 2, { hint: "Use the OMP session history for operator audit context until a controlled-bash audit reader is added." });
732
+ }
733
+ async function executeInspect(context, args, deps = {}) {
592
734
  const [command = "failures", ...rest] = args;
735
+ if (command === "--help" || command === "-h" || command === "help") {
736
+ printInspectHelp();
737
+ return { ok: true, group: "inspect", command: "help" };
738
+ }
593
739
  switch (command) {
594
- case "logs": {
595
- const { value: task, rest: remaining } = takeOption(rest, "--task");
596
- requireNoExtraArgs(remaining, "rig inspect logs --task <task-id>");
597
- const requiredTask = requireTask(task, "rig inspect logs --task <task-id>");
598
- 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];
599
- if (!latestRun) {
600
- const fallback = await withSpinner(`Finding runs for ${requiredTask} on the server\u2026`, async (update) => {
601
- const serverRuns = await listRunsViaServer(context, { limit: 200 }).catch(() => []);
602
- const serverRun = serverRuns.filter((run) => String(run.taskId ?? "") === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
603
- if (!serverRun || typeof serverRun.runId !== "string")
604
- return null;
605
- update(`Reading logs for run ${serverRun.runId}\u2026`);
606
- const page = await getRunLogsViaServer(context, serverRun.runId, { limit: 500 });
607
- return { runId: serverRun.runId, page };
608
- }, { outputMode: context.outputMode });
609
- if (!fallback) {
610
- 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`." });
611
- }
612
- const entries = Array.isArray(fallback.page.entries) ? fallback.page.entries : [];
613
- if (context.outputMode === "text") {
614
- for (const entry of entries) {
615
- const record = entry && typeof entry === "object" ? entry : {};
616
- const title = String(record.title ?? "");
617
- const detail = String(record.detail ?? "");
618
- console.log([title, detail].filter(Boolean).join(" \u2014 "));
619
- }
620
- if (entries.length === 0)
621
- console.log(`(no log entries for run ${fallback.runId})`);
622
- }
623
- return { ok: true, group: "inspect", command, details: { task: requiredTask, runId: fallback.runId, source: "server", entries } };
624
- }
625
- const logsPath = resolve3(resolveAuthorityRunDir(context.projectRoot, latestRun.runId), "logs.jsonl");
626
- if (!existsSync3(logsPath)) {
627
- throw new CliError(`No logs found for run ${latestRun.runId}.`, 1, { hint: `Try \`rig run show ${latestRun.runId}\` or \`rig run replay ${latestRun.runId}\`.` });
628
- }
629
- await context.runCommand(["cat", logsPath]);
630
- return { ok: true, group: "inspect", command, details: { task: requiredTask, runId: latestRun.runId } };
631
- }
632
- case "artifacts": {
633
- const { value: task, rest: remaining } = takeOption(rest, "--task");
634
- requireNoExtraArgs(remaining, "rig inspect artifacts --task <task-id>");
635
- const requiredTask = requireTask(task, "rig inspect artifacts --task <task-id>");
636
- const artifactRoot = resolveTaskArtifactDirs(context.projectRoot, requiredTask).find((path) => existsSync3(path));
637
- if (!artifactRoot) {
638
- throw new CliError(`No artifacts found for ${requiredTask}.`, 1, { hint: "Artifacts appear after a run completes; check `rig run list` for run state." });
639
- }
640
- await context.runCommand(["ls", "-la", artifactRoot]);
641
- return { ok: true, group: "inspect", command, details: { task: requiredTask } };
642
- }
643
- case "artifact": {
644
- let previewPending = rest;
645
- const task = takeOption(previewPending, "--task");
646
- previewPending = task.rest;
647
- const file = takeOption(previewPending, "--file");
648
- previewPending = file.rest;
649
- requireNoExtraArgs(previewPending, "rig inspect artifact --task <task-id> --file <name>");
650
- const requiredTask = requireTask(task.value, "rig inspect artifact --task <task-id> --file <name>");
651
- if (!file.value) {
652
- throw new CliError("Missing --file for rig inspect artifact.", 1, { hint: "List artifact files first: `rig inspect artifacts --task <id>`, then pass one with --file." });
653
- }
654
- const preview = readTaskArtifactPreview(context.projectRoot, requiredTask, file.value);
655
- if (context.outputMode === "text") {
656
- console.log(preview.contents);
657
- if (preview.truncated) {
658
- console.log(`
659
- [preview truncated at ${preview.maxBytes} bytes of ${preview.sizeBytes}]`);
660
- }
661
- }
662
- return {
663
- ok: true,
664
- group: "inspect",
665
- command,
666
- details: {
667
- task: requiredTask,
668
- fileName: file.value,
669
- path: preview.path,
670
- sizeBytes: preview.sizeBytes,
671
- truncated: preview.truncated,
672
- maxBytes: preview.maxBytes,
673
- contents: context.outputMode === "json" ? preview.contents : null
674
- }
675
- };
676
- }
677
- case "diff": {
678
- const { value: task, rest: remaining } = takeOption(rest, "--task");
679
- requireNoExtraArgs(remaining, "rig inspect diff [--task <task-id>]");
680
- if (task) {
681
- const files = await withSpinner(`Computing changed files for ${task}\u2026`, async () => changedFilesForTask(context.projectRoot, task, false), { outputMode: context.outputMode });
682
- for (const file of files) {
683
- console.log(file);
684
- }
685
- } else {
686
- await context.runCommand(["git", "diff", "--stat"]);
687
- }
688
- return { ok: true, group: "inspect", command, details: { task: task || null } };
689
- }
690
- case "failures": {
691
- requireNoExtraArgs(rest, "rig inspect failures");
692
- const failed = resolveHarnessPaths(context.projectRoot).failedApproachesPath;
693
- if (!existsSync3(failed)) {
694
- console.log("No failures recorded.");
695
- } else {
696
- process.stdout.write(readFileSync3(failed, "utf-8"));
697
- }
698
- return { ok: true, group: "inspect", command };
699
- }
740
+ case "logs":
741
+ return executeLogs(context, rest, deps);
742
+ case "artifact":
743
+ return executeArtifact(context, rest, deps);
744
+ case "artifacts":
745
+ return executeArtifacts(context, rest, deps);
746
+ case "run-logs":
747
+ return executeRunLogs(context, rest, deps);
748
+ case "runs":
749
+ return executeRuns(context, rest, deps);
750
+ case "diff":
751
+ return executeDiff(context, rest, deps);
700
752
  case "graph":
701
- requireNoExtraArgs(rest, "rig inspect graph");
702
- {
703
- const monorepoRoot = resolveMonorepoRoot(context.projectRoot);
704
- const result = runCapture(["br", "--no-db", "list", "--pretty"], monorepoRoot);
705
- if (result.exitCode !== 0) {
706
- throw new CliError(result.stderr || result.stdout || "Failed to inspect graph");
707
- }
708
- process.stdout.write(result.stdout);
709
- }
710
- return { ok: true, group: "inspect", command };
711
- case "audit": {
712
- requireNoExtraArgs(rest, "rig inspect audit");
713
- const auditPath = resolve3(resolveHarnessPaths(context.projectRoot).logsDir, "audit.jsonl");
714
- if (!existsSync3(auditPath)) {
715
- console.log("No audit log found.");
716
- } else {
717
- const lines = readFileSync3(auditPath, "utf-8").split(/\r?\n/).filter(Boolean).slice(-20);
718
- for (const line of lines) {
719
- console.log(line);
720
- }
721
- }
722
- return { ok: true, group: "inspect", command };
723
- }
753
+ return executeGraph(context, rest, deps);
754
+ case "failures":
755
+ return executeFailures(context, rest, deps);
756
+ case "audit":
757
+ return executeAudit(rest);
724
758
  default:
725
759
  throw new CliError(`Unknown inspect command: ${command}`, 1, { hint: "Run `rig inspect --help` to list inspect commands." });
726
760
  }