@h-rig/cli 0.0.6-alpha.10 → 0.0.6-alpha.100

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 (120) hide show
  1. package/README.md +18 -19
  2. package/dist/bin/build-rig-binaries.d.ts +2 -0
  3. package/dist/bin/build-rig-binaries.js +22 -10
  4. package/dist/bin/rig.d.ts +72 -0
  5. package/dist/bin/rig.js +15683 -7361
  6. package/dist/config/rig-default-config.yml +5 -0
  7. package/dist/src/app/drone-ui.d.ts +34 -0
  8. package/dist/src/app/drone-ui.js +278 -0
  9. package/dist/src/commands/_async-ui.d.ts +10 -0
  10. package/dist/src/commands/_async-ui.js +121 -0
  11. package/dist/src/commands/_cli-format.d.ts +56 -0
  12. package/dist/src/commands/_cli-format.js +319 -0
  13. package/dist/src/commands/_config-env.d.ts +18 -0
  14. package/dist/src/commands/_config-env.js +22 -0
  15. package/dist/src/commands/_connection-state.d.ts +54 -0
  16. package/dist/src/commands/_connection-state.js +75 -11
  17. package/dist/src/commands/_doctor-checks.d.ts +46 -0
  18. package/dist/src/commands/_doctor-checks.js +100 -267
  19. package/dist/src/commands/_help-catalog.d.ts +29 -0
  20. package/dist/src/commands/_help-catalog.js +146 -0
  21. package/dist/src/commands/_inprocess-services.d.ts +33 -0
  22. package/dist/src/commands/_inprocess-services.js +102 -0
  23. package/dist/src/commands/_json-output.d.ts +11 -0
  24. package/dist/src/commands/_json-output.js +60 -0
  25. package/dist/src/commands/_lazy-reconcile.d.ts +34 -0
  26. package/dist/src/commands/_lazy-reconcile.js +102 -0
  27. package/dist/src/commands/_parsers.d.ts +15 -0
  28. package/dist/src/commands/_parsers.js +18 -11
  29. package/dist/src/commands/_paths.d.ts +11 -0
  30. package/dist/src/commands/_paths.js +1 -1
  31. package/dist/src/commands/_pi-frontend.d.ts +35 -0
  32. package/dist/src/commands/_pi-frontend.js +64 -0
  33. package/dist/src/commands/_pi-install.d.ts +42 -0
  34. package/dist/src/commands/_pi-install.js +19 -36
  35. package/dist/src/commands/_policy.d.ts +8 -0
  36. package/dist/src/commands/_policy.js +67 -19
  37. package/dist/src/commands/_probes.d.ts +1 -0
  38. package/dist/src/commands/_run-bridge.d.ts +114 -0
  39. package/dist/src/commands/_run-bridge.js +387 -0
  40. package/dist/src/commands/_run-diagnostics.d.ts +9 -0
  41. package/dist/src/commands/_run-diagnostics.js +51 -0
  42. package/dist/src/commands/_run-driver-helpers.d.ts +26 -0
  43. package/dist/src/commands/_run-driver-helpers.js +79 -230
  44. package/dist/src/commands/_run-projection.d.ts +50 -0
  45. package/dist/src/commands/_run-projection.js +349 -0
  46. package/dist/src/commands/_run-subcommands.d.ts +3 -0
  47. package/dist/src/commands/_run-subcommands.js +31 -0
  48. package/dist/src/commands/_spinner.d.ts +25 -0
  49. package/dist/src/commands/_spinner.js +65 -0
  50. package/dist/src/commands/agent.d.ts +3 -0
  51. package/dist/src/commands/agent.js +8690 -236
  52. package/dist/src/commands/config.d.ts +3 -0
  53. package/dist/src/commands/config.js +181 -0
  54. package/dist/src/commands/dist.d.ts +28 -0
  55. package/dist/src/commands/dist.js +44 -29
  56. package/dist/src/commands/doctor.d.ts +3 -0
  57. package/dist/src/commands/doctor.js +219 -268
  58. package/dist/src/commands/github.d.ts +3 -0
  59. package/dist/src/commands/github.js +236 -188
  60. package/dist/src/commands/inbox.d.ts +28 -0
  61. package/dist/src/commands/inbox.js +530 -111
  62. package/dist/src/commands/init.d.ts +64 -0
  63. package/dist/src/commands/init.js +912 -705
  64. package/dist/src/commands/inspect.d.ts +20 -0
  65. package/dist/src/commands/inspect.js +709 -119
  66. package/dist/src/commands/pi.d.ts +3 -0
  67. package/dist/src/commands/pi.js +177 -0
  68. package/dist/src/commands/plugin.d.ts +16 -0
  69. package/dist/src/commands/plugin.js +576 -29
  70. package/dist/src/commands/profile-and-review.d.ts +4 -0
  71. package/dist/src/commands/profile-and-review.js +112 -67
  72. package/dist/src/commands/queue.d.ts +3 -0
  73. package/dist/src/commands/queue.js +20 -20
  74. package/dist/src/commands/remote.d.ts +3 -0
  75. package/dist/src/commands/remote.js +883 -50
  76. package/dist/src/commands/repo-git-harness.d.ts +5 -0
  77. package/dist/src/commands/repo-git-harness.js +78 -28
  78. package/dist/src/commands/run.d.ts +21 -0
  79. package/dist/src/commands/run.js +17731 -661
  80. package/dist/src/commands/server.d.ts +3 -0
  81. package/dist/src/commands/server.js +166 -303
  82. package/dist/src/commands/setup.d.ts +16 -0
  83. package/dist/src/commands/setup.js +244 -299
  84. package/dist/src/commands/stats.d.ts +15 -0
  85. package/dist/src/commands/stats.js +632 -0
  86. package/dist/src/commands/task-run-driver.d.ts +94 -0
  87. package/dist/src/commands/task-run-driver.js +116 -1961
  88. package/dist/src/commands/task.d.ts +35 -0
  89. package/dist/src/commands/task.js +676 -1201
  90. package/dist/src/commands/test.d.ts +3 -0
  91. package/dist/src/commands/test.js +15 -8
  92. package/dist/src/commands/triage.d.ts +11 -0
  93. package/dist/src/commands/triage.js +227 -0
  94. package/dist/src/commands/workspace.d.ts +3 -0
  95. package/dist/src/commands/workspace.js +18 -11
  96. package/dist/src/commands.d.ts +13 -0
  97. package/dist/src/commands.js +16812 -8045
  98. package/dist/src/index.d.ts +4 -0
  99. package/dist/src/index.js +16817 -8377
  100. package/dist/src/launcher.d.ts +61 -0
  101. package/dist/src/launcher.js +77 -11
  102. package/dist/src/operator-cli.d.ts +2 -0
  103. package/dist/src/operator-cli.js +17867 -0
  104. package/dist/src/operator-entry.d.ts +1 -0
  105. package/dist/src/operator-entry.js +3 -0
  106. package/dist/src/runner.d.ts +47 -0
  107. package/dist/src/runner.js +16 -22
  108. package/dist/src/withMutedConsole.d.ts +2 -0
  109. package/package.json +21 -8
  110. package/dist/src/commands/_authority-runs.js +0 -111
  111. package/dist/src/commands/_operator-view.js +0 -340
  112. package/dist/src/commands/_preflight.js +0 -478
  113. package/dist/src/commands/_server-client.js +0 -382
  114. package/dist/src/commands/_snapshot-upload.js +0 -331
  115. package/dist/src/commands/_task-picker.js +0 -48
  116. package/dist/src/commands/browser.js +0 -890
  117. package/dist/src/commands/connect.js +0 -180
  118. package/dist/src/commands/inspector.js +0 -256
  119. package/dist/src/commands/task-report-bug.js +0 -1083
  120. package/dist/src/report-bug.js +0 -260
@@ -0,0 +1,15 @@
1
+ import type { RigStatsData } from "@rig/contracts";
2
+ import type { CommandOutcome } from "@rig/runtime";
3
+ import { type RunnerContext } from "../runner";
4
+ import { listRunProjections } from "./_run-projection";
5
+ type StatsCommandDeps = {
6
+ listRuns?: typeof listRunProjections;
7
+ };
8
+ export declare function parseSinceOption(value: string | undefined, now?: Date): Date | null;
9
+ export declare function computeRigStats(projectRoot: string, options?: {
10
+ since?: Date | null;
11
+ listRuns?: typeof listRunProjections;
12
+ }): Promise<RigStatsData>;
13
+ export declare function formatStatsRows(stats: RigStatsData): Array<[string, unknown]>;
14
+ export declare function executeStats(context: RunnerContext, args: string[], deps?: StatsCommandDeps): Promise<CommandOutcome>;
15
+ export {};
@@ -0,0 +1,632 @@
1
+ // @bun
2
+ // packages/cli/src/runner.ts
3
+ import { EventBus } from "@rig/runtime/control-plane/runtime/events";
4
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
5
+ import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
6
+ import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
7
+
8
+ class CliError extends RuntimeCliError {
9
+ hint;
10
+ constructor(message, exitCode = 1, options = {}) {
11
+ super(message, exitCode);
12
+ if (options.hint?.trim()) {
13
+ this.hint = options.hint.trim();
14
+ }
15
+ }
16
+ }
17
+ function takeOption(args, option) {
18
+ const rest = [];
19
+ let value;
20
+ for (let index = 0;index < args.length; index += 1) {
21
+ const current = args[index];
22
+ if (current === option) {
23
+ const next = args[index + 1];
24
+ if (!next || next.startsWith("-")) {
25
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
26
+ }
27
+ value = next;
28
+ index += 1;
29
+ continue;
30
+ }
31
+ if (current !== undefined) {
32
+ rest.push(current);
33
+ }
34
+ }
35
+ return { value, rest };
36
+ }
37
+ function requireNoExtraArgs(args, usage) {
38
+ if (args.length > 0) {
39
+ throw new CliError(`Unexpected arguments: ${args.join(" ")}
40
+ Usage: ${usage}`);
41
+ }
42
+ }
43
+
44
+ // packages/cli/src/commands/_cli-format.ts
45
+ import pc from "picocolors";
46
+ var dim = pc.dim;
47
+ var faintBar = pc.dim("\u2502");
48
+ var accent = pc.cyan;
49
+ function compactValue(value) {
50
+ if (value === null || value === undefined)
51
+ return "\u2014";
52
+ if (typeof value === "string")
53
+ return value;
54
+ if (typeof value === "number" || typeof value === "boolean")
55
+ return String(value);
56
+ return JSON.stringify(value);
57
+ }
58
+ function printFormattedOutput(message) {
59
+ console.log(message);
60
+ }
61
+ function formatStatsTable(rows) {
62
+ return rows.map(([label, value]) => `${faintBar} ${dim(label.padEnd(20))} ${compactValue(value)}`).join(`
63
+ `);
64
+ }
65
+
66
+ // packages/cli/src/commands/_help-catalog.ts
67
+ import pc2 from "picocolors";
68
+ import {
69
+ ADVANCED_COMMANDS,
70
+ ADVANCED_GROUPS,
71
+ ALL_GROUPS,
72
+ helpCatalog,
73
+ TOP_LEVEL_SECTIONS
74
+ } from "@rig/contracts";
75
+ function heading(title) {
76
+ return pc2.bold(pc2.cyan(title));
77
+ }
78
+ function commandLine(command, description) {
79
+ const commandColumn = command.length >= 38 ? `${command} ` : command.padEnd(38);
80
+ return `${pc2.dim("\u2502")} ${pc2.bold(commandColumn)} ${description}`;
81
+ }
82
+ function renderCommandBlock(commands) {
83
+ return commands.map((entry) => commandLine(entry.command, entry.description)).join(`
84
+ `);
85
+ }
86
+ function renderGroup(group) {
87
+ const lines = [
88
+ `${heading(`rig ${group.name}`)} \u2014 ${group.summary}`,
89
+ "",
90
+ pc2.bold("Usage"),
91
+ ...group.usage.map((line) => ` ${line}`),
92
+ "",
93
+ pc2.bold("Commands"),
94
+ ...group.commands.map((entry) => commandLine(entry.command, entry.description))
95
+ ];
96
+ if (group.examples?.length) {
97
+ lines.push("", pc2.bold("Examples"), ...group.examples.map((line) => ` ${pc2.dim("$")} ${line}`));
98
+ }
99
+ if (group.next?.length) {
100
+ lines.push("", pc2.bold("Next steps"), ...group.next.map((line) => ` ${pc2.dim("\u203A")} ${line}`));
101
+ }
102
+ if (group.advanced?.length) {
103
+ lines.push("", pc2.bold("Compatibility / advanced"), ...group.advanced.map((line) => ` ${pc2.dim("\u203A")} ${line}`));
104
+ }
105
+ return lines.join(`
106
+ `);
107
+ }
108
+ function renderTopLevelHelp() {
109
+ return [
110
+ `${heading("rig")} ${pc2.dim("\u2014 Rig Cockpit for autonomous coding agents")}`,
111
+ pc2.dim("The loop: bare `rig` \u2192 Cockpit \u2192 Server target \u2192 Tasks \u2192 Task detail \u2192 Dispatch/Attach; every run uses OMP collaboration as the session substrate."),
112
+ "",
113
+ ...TOP_LEVEL_SECTIONS.flatMap((section) => [
114
+ `${pc2.bold(pc2.magenta(`\u25C7 ${section.title}`))} \u2014 ${pc2.dim(section.subtitle)}`,
115
+ renderCommandBlock(section.commands),
116
+ ""
117
+ ]),
118
+ pc2.dim("More: `rig help --advanced` for fenced legacy/diagnostic commands; `rig <group> --help` for per-group compatibility help; `rig --version` for the installed version."),
119
+ "",
120
+ pc2.bold("Root options"),
121
+ commandLine("--workspace <path>", "Open Rig Cockpit for a workspace instead of the current directory."),
122
+ commandLine("--json", "Compatibility flag for fenced legacy/diagnostic subcommands."),
123
+ commandLine("--dry-run", "Compatibility flag for fenced legacy/diagnostic subcommands.")
124
+ ].join(`
125
+ `).trimEnd();
126
+ }
127
+ function renderGroupHelp(groupName) {
128
+ const group = ALL_GROUPS.find((candidate) => candidate.name === groupName);
129
+ return group ? renderGroup(group) : null;
130
+ }
131
+ function printGroupHelpDocument(groupName) {
132
+ console.log(renderGroupHelp(groupName) ?? renderTopLevelHelp());
133
+ }
134
+
135
+ // packages/cli/src/commands/_run-projection.ts
136
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
137
+ import { resolve as resolve2 } from "path";
138
+ import {
139
+ foldRunSessionEntries,
140
+ sessionIdFromSessionFile as sessionIdFromSessionFile2
141
+ } from "@rig/contracts";
142
+ import { listAgentRuntimes as listAgentRuntimes2 } from "@rig/runtime/control-plane/runtime/isolation";
143
+
144
+ // packages/cli/src/commands/_run-bridge.ts
145
+ import { existsSync, readdirSync, readFileSync } from "fs";
146
+ import { dirname, resolve } from "path";
147
+ import { CUSTOM_TYPE_FOR, sessionIdFromSessionFile } from "@rig/contracts";
148
+ import { createPluginHost } from "@rig/core";
149
+ import { loadConfig } from "@rig/core/load-config";
150
+ import { resolveOwnerNamespaceKey, resolveRegistryBaseUrl, resolveRegistrySecret, resolveSelectedRemote } from "@rig/runtime/control-plane/remote";
151
+ import { listAgentRuntimes } from "@rig/runtime/control-plane/runtime/isolation";
152
+ import { createRegistryClient } from "@rig/relay-registry";
153
+ import {
154
+ listActiveCollabSessions as ompListActiveCollabSessions
155
+ } from "@oh-my-pi/pi-coding-agent/collab/api";
156
+ function registrySecret(env = process.env) {
157
+ return resolveRegistrySecret(process.cwd(), env) ?? null;
158
+ }
159
+ function registryBaseUrl(env = process.env) {
160
+ return resolveRegistryBaseUrl(process.cwd(), env);
161
+ }
162
+ function collabFromRegistryEntry(entry) {
163
+ return {
164
+ sessionId: entry.roomId,
165
+ sessionPath: "",
166
+ cwd: "",
167
+ title: entry.title,
168
+ joinLink: entry.joinLink,
169
+ webLink: entry.webLink,
170
+ relayUrl: entry.relayUrl,
171
+ owner: entry.owner,
172
+ selectedRepo: entry.repo,
173
+ startedAt: entry.startedAt,
174
+ updatedAt: entry.heartbeatAt,
175
+ ...entry.pid === undefined ? {} : { pid: entry.pid },
176
+ stale: entry.stale
177
+ };
178
+ }
179
+ function isRecord(value) {
180
+ return typeof value === "object" && value !== null;
181
+ }
182
+ function readLocalRunCollab(runtime) {
183
+ if (!runtime.sessionDir || !existsSync(runtime.sessionDir))
184
+ return null;
185
+ let files;
186
+ try {
187
+ files = readdirSync(runtime.sessionDir).filter((f) => f.endsWith(".jsonl")).sort().reverse();
188
+ } catch {
189
+ return null;
190
+ }
191
+ for (const file of files) {
192
+ let lines;
193
+ try {
194
+ lines = readFileSync(resolve(runtime.sessionDir, file), "utf8").split(`
195
+ `);
196
+ } catch {
197
+ continue;
198
+ }
199
+ let host = null;
200
+ for (const line of lines) {
201
+ if (!line.includes("collab-host-started"))
202
+ continue;
203
+ let entry;
204
+ try {
205
+ entry = JSON.parse(line);
206
+ } catch {
207
+ continue;
208
+ }
209
+ if (!isRecord(entry) || entry.customType !== CUSTOM_TYPE_FOR["timeline-entry"] || !isRecord(entry.data))
210
+ continue;
211
+ const payload = isRecord(entry.data.payload) ? entry.data.payload : entry.data;
212
+ if (payload.type !== "collab-host-started")
213
+ continue;
214
+ host = {
215
+ roomId: typeof payload.roomId === "string" ? payload.roomId : undefined,
216
+ joinLink: typeof payload.joinLink === "string" ? payload.joinLink : undefined,
217
+ webLink: typeof payload.webLink === "string" ? payload.webLink : undefined,
218
+ relayUrl: typeof payload.relayUrl === "string" ? payload.relayUrl : undefined,
219
+ at: typeof entry.data.at === "string" ? entry.data.at : undefined
220
+ };
221
+ }
222
+ if (!host?.joinLink)
223
+ continue;
224
+ const at = host.at ?? "";
225
+ const sessionId = host.roomId ?? sessionIdFromSessionFile(resolve(runtime.sessionDir, file)) ?? runtime.id;
226
+ return {
227
+ sessionId,
228
+ sessionPath: resolve(runtime.sessionDir, file),
229
+ cwd: runtime.workspaceDir,
230
+ joinLink: host.joinLink,
231
+ webLink: host.webLink ?? "",
232
+ relayUrl: host.relayUrl ?? "",
233
+ title: runtime.taskId ? `[${runtime.taskId}] Rig run ${sessionId}` : `Rig run ${sessionId}`,
234
+ startedAt: at,
235
+ updatedAt: at,
236
+ stale: false
237
+ };
238
+ }
239
+ return null;
240
+ }
241
+ async function listLocalRunCollabSessions(projectRoot) {
242
+ let runtimes;
243
+ try {
244
+ runtimes = await listAgentRuntimes(projectRoot);
245
+ } catch {
246
+ return [];
247
+ }
248
+ const out = [];
249
+ for (const runtime of runtimes) {
250
+ const collab = readLocalRunCollab(runtime);
251
+ if (collab)
252
+ out.push(collab);
253
+ }
254
+ return out;
255
+ }
256
+ async function listActiveRunCollab(filter) {
257
+ return defaultListActiveCollabSessions(filter);
258
+ }
259
+ async function defaultListActiveCollabSessions(filter) {
260
+ const projectRoot = process.cwd();
261
+ const local = await listLocalRunCollabSessions(projectRoot);
262
+ const namespaceKey = filter.namespaceKey ?? resolveOwnerNamespaceKey(projectRoot);
263
+ const secret = registrySecret();
264
+ const remote = namespaceKey && secret ? (await createRegistryClient({ baseUrl: registryBaseUrl(), namespaceKey, secret }).listRoomsByOwner(filter)).map(collabFromRegistryEntry) : await ompListActiveCollabSessions(filter);
265
+ const seen = new Set(local.map((c) => c.sessionId));
266
+ return [...local, ...remote.filter((c) => !seen.has(c.sessionId))];
267
+ }
268
+
269
+ // packages/cli/src/commands/_run-diagnostics.ts
270
+ function normalizeString(value) {
271
+ if (typeof value !== "string")
272
+ return null;
273
+ const normalized = value.replace(/\s+/g, " ").trim();
274
+ return normalized.length > 0 ? normalized : null;
275
+ }
276
+ function isGenericRunFailure(value) {
277
+ const text = normalizeString(value);
278
+ return Boolean(text && /^Task run failed \([^)]*\)$/i.test(text));
279
+ }
280
+ function appendCandidate(candidates, value) {
281
+ const text = normalizeString(value);
282
+ if (text && !candidates.includes(text))
283
+ candidates.push(text);
284
+ }
285
+ function categorizeUsefulRunError(lines) {
286
+ const taskSourceFailure = lines.find((line) => /failed to update task source/i.test(line));
287
+ if (taskSourceFailure)
288
+ return `Task source update failed: ${taskSourceFailure}`;
289
+ const moduleFailure = lines.find((line) => /cannot find module/i.test(line));
290
+ if (moduleFailure)
291
+ return `Runtime module resolution failed: ${moduleFailure}`;
292
+ const providerFailure = lines.find((line) => /no api key found|unauthorized|authentication failed|invalid api key/i.test(line));
293
+ if (providerFailure)
294
+ return `Provider authentication failed: ${providerFailure}`;
295
+ return null;
296
+ }
297
+ function summarizeUsefulRunError(projection) {
298
+ if (projection.status !== "failed")
299
+ return null;
300
+ const candidates = [];
301
+ for (const anomaly of projection.anomalies) {
302
+ appendCandidate(candidates, `Journal anomaly (${anomaly.kind}): ${anomaly.detail}`);
303
+ }
304
+ for (const phase of projection.closeoutPhases) {
305
+ if (phase.outcome === "failed")
306
+ appendCandidate(candidates, phase.detail);
307
+ }
308
+ for (const entry of projection.statusHistory) {
309
+ appendCandidate(candidates, entry.reason);
310
+ }
311
+ appendCandidate(candidates, projection.record.statusDetail);
312
+ appendCandidate(candidates, projection.record.errorText);
313
+ const nonGeneric = candidates.filter((candidate) => !isGenericRunFailure(candidate));
314
+ return categorizeUsefulRunError(nonGeneric) ?? nonGeneric.at(-1) ?? normalizeString(projection.record.errorText);
315
+ }
316
+
317
+ // packages/cli/src/commands/_run-projection.ts
318
+ var EMPTY_PROJECTION = foldRunSessionEntries([], "");
319
+ function stringOrNull(value) {
320
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
321
+ }
322
+ function newestSessionFile(sessionDir) {
323
+ if (!sessionDir || !existsSync2(sessionDir))
324
+ return null;
325
+ try {
326
+ const files = readdirSync2(sessionDir).filter((file) => file.endsWith(".jsonl")).sort().reverse();
327
+ return files[0] ? resolve2(sessionDir, files[0]) : null;
328
+ } catch {
329
+ return null;
330
+ }
331
+ }
332
+ function readSessionRunEntries(sessionPath) {
333
+ if (!sessionPath || !existsSync2(sessionPath))
334
+ return [];
335
+ let raw;
336
+ try {
337
+ raw = readFileSync2(sessionPath, "utf8");
338
+ } catch {
339
+ return [];
340
+ }
341
+ const entries = [];
342
+ for (const line of raw.split(`
343
+ `)) {
344
+ if (!line.trim())
345
+ continue;
346
+ let parsed;
347
+ try {
348
+ parsed = JSON.parse(line);
349
+ } catch {
350
+ continue;
351
+ }
352
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
353
+ continue;
354
+ entries.push({
355
+ type: "type" in parsed && typeof parsed.type === "string" ? parsed.type : "",
356
+ ..."customType" in parsed && typeof parsed.customType === "string" ? { customType: parsed.customType } : {},
357
+ ..."data" in parsed ? { data: parsed.data } : {}
358
+ });
359
+ }
360
+ return entries;
361
+ }
362
+ function recordFromProjection(runId, projection, presence) {
363
+ const record = projection.record;
364
+ const status = projection.status ?? (presence.stale ? "stale" : presence.live ? "running" : "unknown");
365
+ const taskId = stringOrNull(record.taskId) ?? presence.fallbackTaskId ?? null;
366
+ const title = stringOrNull(record.title) ?? stringOrNull(presence.title) ?? (taskId ? `Run ${taskId}` : `Rig run ${runId}`);
367
+ return {
368
+ runId,
369
+ taskId,
370
+ title,
371
+ status,
372
+ source: presence.source,
373
+ live: presence.live,
374
+ stale: presence.stale,
375
+ startedAt: stringOrNull(record.startedAt) ?? presence.startedAt ?? null,
376
+ updatedAt: stringOrNull(record.updatedAt) ?? presence.updatedAt ?? projection.lastEventAt ?? null,
377
+ completedAt: stringOrNull(record.completedAt),
378
+ joinLink: presence.joinLink ?? null,
379
+ webLink: presence.webLink ?? null,
380
+ relayUrl: presence.relayUrl ?? null,
381
+ sessionPath: stringOrNull(record.sessionPath) ?? presence.sessionPath ?? null,
382
+ prUrl: stringOrNull(record.prUrl),
383
+ worktreePath: stringOrNull(record.worktreePath),
384
+ pendingApprovals: projection.pendingApprovals.length,
385
+ pendingInputs: projection.pendingUserInputs.length,
386
+ steeringCount: projection.steeringCount,
387
+ stallCount: projection.stallCount,
388
+ errorSummary: summarizeUsefulRunError(projection),
389
+ projection
390
+ };
391
+ }
392
+ function recordFromLiveCollab(collab) {
393
+ const runId = collab.sessionId;
394
+ const sessionPath = stringOrNull(collab.sessionPath);
395
+ const projection = sessionPath ? foldRunSessionEntries(readSessionRunEntries(sessionPath), runId) : EMPTY_PROJECTION;
396
+ return recordFromProjection(runId, projection, {
397
+ source: sessionPath ? "local" : "remote",
398
+ live: !collab.stale,
399
+ stale: Boolean(collab.stale),
400
+ title: collab.title,
401
+ joinLink: stringOrNull(collab.joinLink),
402
+ webLink: stringOrNull(collab.webLink),
403
+ relayUrl: stringOrNull(collab.relayUrl),
404
+ sessionPath,
405
+ startedAt: stringOrNull(collab.startedAt),
406
+ updatedAt: stringOrNull(collab.updatedAt)
407
+ });
408
+ }
409
+ function recordFromRuntime(runtime) {
410
+ const sessionPath = newestSessionFile(runtime.sessionDir ?? "");
411
+ if (!sessionPath)
412
+ return null;
413
+ const projection = foldRunSessionEntries(readSessionRunEntries(sessionPath), runtime.id);
414
+ if (projection.lastSeq === 0)
415
+ return null;
416
+ return recordFromProjection(sessionIdFromSessionFile2(sessionPath) ?? runtime.id, projection, {
417
+ source: "local",
418
+ live: false,
419
+ stale: false,
420
+ sessionPath,
421
+ fallbackTaskId: stringOrNull(runtime.taskId)
422
+ });
423
+ }
424
+ function sortByRecency(records) {
425
+ return [...records].sort((a, b) => {
426
+ const at = Date.parse(b.updatedAt ?? b.startedAt ?? "") || 0;
427
+ const bt = Date.parse(a.updatedAt ?? a.startedAt ?? "") || 0;
428
+ return at - bt;
429
+ });
430
+ }
431
+ async function listRunProjections(projectRoot, filter = {}) {
432
+ const byRunId = new Map;
433
+ let live = [];
434
+ try {
435
+ live = await listActiveRunCollab(filter);
436
+ } catch {
437
+ live = [];
438
+ }
439
+ for (const collab of live) {
440
+ const record = recordFromLiveCollab(collab);
441
+ byRunId.set(record.runId, record);
442
+ }
443
+ let runtimes = [];
444
+ try {
445
+ runtimes = await listAgentRuntimes2(projectRoot);
446
+ } catch {
447
+ runtimes = [];
448
+ }
449
+ for (const runtime of runtimes) {
450
+ const record = recordFromRuntime(runtime);
451
+ if (!record)
452
+ continue;
453
+ const sessionId = sessionIdFromSessionFile2(record.sessionPath);
454
+ const liveMatch = sessionId ? byRunId.get(sessionId) : undefined;
455
+ if (liveMatch) {
456
+ if (liveMatch.source === "remote" || !liveMatch.sessionPath) {
457
+ byRunId.set(liveMatch.runId, { ...liveMatch, sessionPath: record.sessionPath, source: "local" });
458
+ }
459
+ continue;
460
+ }
461
+ if (byRunId.has(runtime.id))
462
+ continue;
463
+ byRunId.set(record.runId, record);
464
+ }
465
+ return sortByRecency([...byRunId.values()]);
466
+ }
467
+
468
+ // packages/cli/src/commands/stats.ts
469
+ var DAY_MS = 24 * 60 * 60 * 1000;
470
+ function parseSinceOption(value, now = new Date) {
471
+ if (!value?.trim())
472
+ return null;
473
+ const trimmed = value.trim();
474
+ const relative = trimmed.match(/^(\d+)d$/i);
475
+ if (relative?.[1]) {
476
+ const days = Number.parseInt(relative[1], 10);
477
+ if (!Number.isFinite(days) || days <= 0) {
478
+ throw new CliError(`Invalid --since value: ${value}`, 2, {
479
+ hint: "Use an ISO date or a relative window like `7d`."
480
+ });
481
+ }
482
+ return new Date(now.getTime() - days * DAY_MS);
483
+ }
484
+ const parsed = Date.parse(trimmed);
485
+ if (!Number.isFinite(parsed)) {
486
+ throw new CliError(`Invalid --since value: ${value}`, 2, {
487
+ hint: "Use an ISO date or a relative window like `7d`."
488
+ });
489
+ }
490
+ return new Date(parsed);
491
+ }
492
+ function parseTimestamp(value) {
493
+ if (!value)
494
+ return null;
495
+ const parsed = Date.parse(value);
496
+ return Number.isFinite(parsed) ? parsed : null;
497
+ }
498
+ function median(values) {
499
+ if (values.length === 0)
500
+ return null;
501
+ const sorted = [...values].sort((left, right) => left - right);
502
+ const middle = Math.floor(sorted.length / 2);
503
+ return sorted.length % 2 === 1 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
504
+ }
505
+ function rate(part, total) {
506
+ return total === 0 ? null : part / total;
507
+ }
508
+ function isNeedsAttention(run) {
509
+ return run.status === "needs-attention" || run.pendingApprovals + run.pendingInputs > 0 || run.stallCount > 0;
510
+ }
511
+ function completedDuration(run) {
512
+ if (run.status !== "completed")
513
+ return null;
514
+ const startedAt = parseTimestamp(run.startedAt);
515
+ const completedAt = parseTimestamp(run.completedAt);
516
+ if (startedAt === null || completedAt === null || completedAt < startedAt)
517
+ return null;
518
+ return completedAt - startedAt;
519
+ }
520
+ async function computeRigStats(projectRoot, options = {}) {
521
+ const sinceMs = options.since ? options.since.getTime() : null;
522
+ const allRuns = await (options.listRuns ?? listRunProjections)(projectRoot);
523
+ const runs = allRuns.filter((run) => {
524
+ if (sinceMs === null)
525
+ return true;
526
+ const startedAt = parseTimestamp(run.startedAt);
527
+ return startedAt !== null && startedAt >= sinceMs;
528
+ });
529
+ const statusCounts = {};
530
+ const completionDurations = [];
531
+ let completedRuns = 0;
532
+ let failedRuns = 0;
533
+ let needsAttentionRuns = 0;
534
+ let steeringTotal = 0;
535
+ let stallTotal = 0;
536
+ let approvalsPending = 0;
537
+ for (const run of runs) {
538
+ const status = run.status || "unknown";
539
+ statusCounts[status] = (statusCounts[status] ?? 0) + 1;
540
+ if (status === "completed")
541
+ completedRuns += 1;
542
+ if (status === "failed")
543
+ failedRuns += 1;
544
+ if (isNeedsAttention(run))
545
+ needsAttentionRuns += 1;
546
+ const duration = completedDuration(run);
547
+ if (duration !== null)
548
+ completionDurations.push(duration);
549
+ steeringTotal += run.steeringCount;
550
+ stallTotal += run.stallCount;
551
+ approvalsPending += run.pendingApprovals;
552
+ }
553
+ const totalRuns = runs.length;
554
+ return {
555
+ since: options.since ? options.since.toISOString() : null,
556
+ totalRuns,
557
+ statusCounts,
558
+ completedRuns,
559
+ failedRuns,
560
+ needsAttentionRuns,
561
+ completionRate: rate(completedRuns, totalRuns),
562
+ failureRate: rate(failedRuns, totalRuns),
563
+ needsAttentionRate: rate(needsAttentionRuns, totalRuns),
564
+ medianCompletionMs: median(completionDurations),
565
+ steeringTotal,
566
+ steeringPerRun: totalRuns === 0 ? null : steeringTotal / totalRuns,
567
+ stallTotal,
568
+ approvalsRequested: approvalsPending,
569
+ approvalsApproved: 0,
570
+ approvalsRejected: 0,
571
+ approvalsPending
572
+ };
573
+ }
574
+ function formatPercent(value) {
575
+ return value === null ? "\u2014" : `${Math.round(value * 100)}%`;
576
+ }
577
+ function formatDuration(ms) {
578
+ if (ms === null)
579
+ return "\u2014";
580
+ const seconds = Math.round(ms / 1000);
581
+ if (seconds < 60)
582
+ return `${seconds}s`;
583
+ const minutes = Math.round(ms / 60000);
584
+ if (minutes < 60)
585
+ return `${minutes}m`;
586
+ const hours = Math.floor(minutes / 60);
587
+ const remainder = minutes % 60;
588
+ return remainder === 0 ? `${hours}h` : `${hours}h ${remainder}m`;
589
+ }
590
+ function formatStatsRows(stats) {
591
+ return [
592
+ ["window", stats.since ? `since ${stats.since}` : "all runs"],
593
+ ["total runs", stats.totalRuns],
594
+ ["completed", `${stats.completedRuns} (${formatPercent(stats.completionRate)})`],
595
+ ["failed", `${stats.failedRuns} (${formatPercent(stats.failureRate)})`],
596
+ ["needs attention", `${stats.needsAttentionRuns} (${formatPercent(stats.needsAttentionRate)})`],
597
+ ["median run time", formatDuration(stats.medianCompletionMs)],
598
+ ["steering", stats.steeringTotal],
599
+ ["stalls", stats.stallTotal],
600
+ ["pending approvals", stats.approvalsPending]
601
+ ];
602
+ }
603
+ async function executeStats(context, args, deps = {}) {
604
+ const [first = "show", ...rest] = args;
605
+ const command = first.startsWith("-") ? "show" : first;
606
+ const commandArgs = first.startsWith("-") ? args : rest;
607
+ if (command === "help") {
608
+ printGroupHelpDocument("stats");
609
+ return { ok: true, group: "stats", command };
610
+ }
611
+ if (command !== "show") {
612
+ throw new CliError(`Unknown stats command: ${command}`, 1, {
613
+ hint: "Run `rig stats --help` \u2014 the default command is `show`."
614
+ });
615
+ }
616
+ const sinceResult = takeOption(commandArgs, "--since");
617
+ requireNoExtraArgs(sinceResult.rest, "rig stats [show] [--since <7d|30d|ISO date>]");
618
+ const details = await computeRigStats(context.projectRoot, {
619
+ since: parseSinceOption(sinceResult.value),
620
+ listRuns: deps.listRuns
621
+ });
622
+ if (context.outputMode === "text") {
623
+ printFormattedOutput(formatStatsTable(formatStatsRows(details)));
624
+ }
625
+ return { ok: true, group: "stats", command, details };
626
+ }
627
+ export {
628
+ parseSinceOption,
629
+ formatStatsRows,
630
+ executeStats,
631
+ computeRigStats
632
+ };