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

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,4 @@
1
1
  // @bun
2
- // packages/cli/src/commands/stats.ts
3
- import pc4 from "picocolors";
4
-
5
2
  // packages/cli/src/runner.ts
6
3
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
7
4
  import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
@@ -44,731 +41,203 @@ Usage: ${usage}`);
44
41
  }
45
42
  }
46
43
 
47
- // packages/cli/src/commands/stats.ts
48
- import { computeRigStats } from "@rig/runtime/control-plane/native/run-stats";
49
-
50
- // packages/cli/src/commands/_connection-state.ts
51
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
52
- import { homedir } from "os";
53
- import { dirname, resolve } from "path";
54
- function resolveGlobalConnectionsPath(env = process.env) {
55
- const explicit = env.RIG_CONNECTIONS_FILE?.trim();
56
- if (explicit)
57
- return resolve(explicit);
58
- const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
59
- if (stateDir)
60
- return resolve(stateDir, "connections.json");
61
- return resolve(homedir(), ".rig", "connections.json");
62
- }
63
- function resolveRepoConnectionPath(projectRoot) {
64
- return resolve(projectRoot, ".rig", "state", "connection.json");
65
- }
66
- function readJsonFile(path) {
67
- if (!existsSync(path))
68
- return null;
69
- try {
70
- return JSON.parse(readFileSync(path, "utf8"));
71
- } catch (error) {
72
- 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>`." });
73
- }
74
- }
75
- function writeJsonFile(path, value) {
76
- mkdirSync(dirname(path), { recursive: true });
77
- writeFileSync(path, `${JSON.stringify(value, null, 2)}
78
- `, "utf8");
79
- }
80
- function normalizeConnection(value) {
81
- if (!value || typeof value !== "object" || Array.isArray(value))
82
- return null;
83
- const record = value;
84
- if (record.kind === "local")
85
- return { kind: "local", mode: "auto" };
86
- if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
87
- const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
88
- return { kind: "remote", baseUrl };
89
- }
90
- return null;
91
- }
92
- function readGlobalConnections(options = {}) {
93
- const path = resolveGlobalConnectionsPath(options.env ?? process.env);
94
- const payload = readJsonFile(path);
95
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
96
- return { connections: {} };
97
- }
98
- const rawConnections = payload.connections;
99
- const connections = {};
100
- if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
101
- for (const [alias, raw] of Object.entries(rawConnections)) {
102
- const connection = normalizeConnection(raw);
103
- if (connection)
104
- connections[alias] = connection;
105
- }
106
- }
107
- return { connections };
108
- }
109
- function readRepoConnection(projectRoot) {
110
- const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
111
- if (!payload || typeof payload !== "object" || Array.isArray(payload))
112
- return null;
113
- const record = payload;
114
- const selected = typeof record.selected === "string" ? record.selected.trim() : "";
115
- if (!selected)
116
- return null;
117
- return {
118
- selected,
119
- project: typeof record.project === "string" ? record.project : undefined,
120
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
121
- serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
122
- };
123
- }
124
- function writeRepoConnection(projectRoot, state) {
125
- writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
126
- }
127
- function resolveSelectedConnection(projectRoot, options = {}) {
128
- const repo = readRepoConnection(projectRoot);
129
- if (!repo)
130
- return null;
131
- if (repo.selected === "local")
132
- return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
133
- const global = readGlobalConnections(options);
134
- const connection = global.connections[repo.selected];
135
- if (!connection) {
136
- throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
137
- }
138
- return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
139
- }
140
- function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
141
- const repo = readRepoConnection(projectRoot);
142
- if (!repo)
143
- return;
144
- writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
145
- }
146
- function isRemoteConnectionSelected(projectRoot) {
147
- return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
148
- }
149
-
150
- // packages/cli/src/commands/_server-client.ts
151
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
152
- import { resolve as resolve2 } from "path";
153
- import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
154
- var scopedGitHubBearerTokens = new Map;
155
- var serverPhaseListener = null;
156
- function setServerPhaseListener(listener) {
157
- const previous = serverPhaseListener;
158
- serverPhaseListener = listener;
159
- return previous;
160
- }
161
- function reportServerPhase(label) {
162
- serverPhaseListener?.(label);
163
- }
164
- function cleanToken(value) {
165
- const trimmed = value?.trim();
166
- return trimmed ? trimmed : null;
167
- }
168
- function readPrivateRemoteSessionToken(projectRoot) {
169
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
170
- if (!existsSync2(path))
171
- return null;
172
- try {
173
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
174
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
175
- } catch {
176
- return null;
177
- }
178
- }
179
- function readGitHubBearerTokenForRemote(projectRoot) {
180
- const scopedKey = resolve2(projectRoot);
181
- if (scopedGitHubBearerTokens.has(scopedKey))
182
- return scopedGitHubBearerTokens.get(scopedKey) ?? null;
183
- const privateSession = readPrivateRemoteSessionToken(projectRoot);
184
- if (privateSession)
185
- return privateSession;
186
- return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
187
- }
188
- function readStoredGitHubAuthToken(projectRoot) {
189
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
190
- if (!existsSync2(path))
191
- return null;
192
- try {
193
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
194
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
195
- } catch {
196
- return null;
197
- }
198
- }
199
- function readLocalConnectionFallbackToken(projectRoot) {
200
- return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
201
- }
202
- async function ensureServerForCli(projectRoot) {
203
- try {
204
- const selected = resolveSelectedConnection(projectRoot);
205
- if (selected?.connection.kind === "remote") {
206
- reportServerPhase(`Connecting to ${selected.alias}\u2026`);
207
- const authToken = readGitHubBearerTokenForRemote(projectRoot);
208
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
209
- return {
210
- baseUrl: selected.connection.baseUrl,
211
- authToken,
212
- connectionKind: "remote",
213
- serverProjectRoot
214
- };
215
- }
216
- reportServerPhase("Starting local Rig server\u2026");
217
- const connection = await ensureLocalRigServerConnection(projectRoot);
218
- return {
219
- baseUrl: connection.baseUrl,
220
- authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
221
- connectionKind: "local",
222
- serverProjectRoot: resolve2(projectRoot)
223
- };
224
- } catch (error) {
225
- if (error instanceof Error) {
226
- throw new CliError(error.message, 1);
227
- }
228
- throw error;
229
- }
230
- }
231
- async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
232
- const repo = readRepoConnection(projectRoot);
233
- const slug = repo?.project?.trim();
234
- if (!slug)
235
- return null;
236
- try {
237
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
238
- headers: mergeHeaders(undefined, authToken)
239
- });
240
- if (!response.ok)
241
- return null;
242
- const payload = await response.json();
243
- const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
244
- const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
245
- const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
246
- const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
247
- if (path)
248
- writeRepoServerProjectRoot(projectRoot, path);
249
- return path;
250
- } catch {
251
- return null;
252
- }
253
- }
254
- function mergeHeaders(headers, authToken) {
255
- const merged = new Headers(headers);
256
- if (authToken) {
257
- merged.set("authorization", `Bearer ${authToken}`);
258
- }
259
- return merged;
260
- }
261
- function diagnosticMessage(payload) {
262
- if (!payload || typeof payload !== "object" || Array.isArray(payload))
263
- return null;
264
- const record = payload;
265
- const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
266
- const messages = diagnostics.flatMap((entry) => {
267
- if (!entry || typeof entry !== "object" || Array.isArray(entry))
268
- return [];
269
- const diagnostic = entry;
270
- const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
271
- const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
272
- return message ? [`${kind}: ${message}`] : [];
273
- });
274
- return messages.length > 0 ? messages.join("; ") : null;
275
- }
276
- var serverReachabilityCache = new Map;
277
- async function probeServerReachability(baseUrl, authToken) {
278
- try {
279
- const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
280
- headers: mergeHeaders(undefined, authToken),
281
- signal: AbortSignal.timeout(1500)
282
- });
283
- return response.ok;
284
- } catch {
285
- return false;
286
- }
287
- }
288
- function cachedServerReachability(projectRoot, baseUrl, authToken) {
289
- const key = resolve2(projectRoot);
290
- const cached = serverReachabilityCache.get(key);
291
- if (cached)
292
- return cached;
293
- const probe = probeServerReachability(baseUrl, authToken);
294
- serverReachabilityCache.set(key, probe);
295
- return probe;
296
- }
297
- function describeSelectedServer(projectRoot, server) {
298
- try {
299
- const selected = resolveSelectedConnection(projectRoot);
300
- if (selected) {
301
- return {
302
- alias: selected.alias,
303
- target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
304
- };
305
- }
306
- } catch {}
307
- return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
308
- }
309
- async function buildServerFailureContext(projectRoot, server) {
310
- const { alias, target } = describeSelectedServer(projectRoot, server);
311
- const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
312
- const reachability = reachable ? "server is reachable" : "server is unreachable";
313
- return {
314
- contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
315
- hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
316
- };
317
- }
318
- async function requestServerJson(context, pathname, init = {}) {
319
- const server = await ensureServerForCli(context.projectRoot);
320
- const headers = mergeHeaders(init.headers, server.authToken);
321
- if (server.serverProjectRoot)
322
- headers.set("x-rig-project-root", server.serverProjectRoot);
323
- reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
324
- let response;
325
- try {
326
- response = await fetch(`${server.baseUrl}${pathname}`, {
327
- ...init,
328
- headers
329
- });
330
- } catch (error) {
331
- const failure = await buildServerFailureContext(context.projectRoot, server);
332
- throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
333
- ${failure.contextLine}`, 1, { hint: failure.hint });
334
- }
335
- const text = await response.text();
336
- const payload = text.trim().length > 0 ? (() => {
337
- try {
338
- return JSON.parse(text);
339
- } catch {
340
- return null;
341
- }
342
- })() : null;
343
- if (!response.ok) {
344
- const diagnostics = diagnosticMessage(payload);
345
- const rawDetail = diagnostics ?? (text || response.statusText);
346
- const detail = diagnostics ? rawDetail : rawDetail.split(`
347
- `).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 200) ?? response.statusText;
348
- const failure = await buildServerFailureContext(context.projectRoot, server);
349
- throw new CliError(`Rig server request failed (${response.status}): ${detail}
350
- ${failure.contextLine}`, 1, { hint: failure.hint });
351
- }
352
- return payload;
353
- }
354
- var RESUMABLE_RUN_STATUSES = new Set([
355
- "created",
356
- "preparing",
357
- "running",
358
- "validating",
359
- "reviewing",
360
- "stopped",
361
- "failed",
362
- "needs-attention",
363
- "needs_attention"
364
- ]);
365
-
366
- // packages/cli/src/app/drone-ui.ts
367
- import { ProcessTerminal, TUI, Text, Input, SelectList, matchesKey } from "@earendil-works/pi-tui";
368
-
369
- // packages/cli/src/app/theme.ts
370
- var RIG_PALETTE = {
371
- ink: "#f2f3f6",
372
- ink2: "#aeb0ba",
373
- ink3: "#6c6e79",
374
- ink4: "#44464f",
375
- accent: "#ccff4d",
376
- accentDim: "#a9d63f",
377
- cyan: "#56d8ff",
378
- red: "#ff5d5d",
379
- yellow: "#ffd24d"
380
- };
381
- function hexToRgb(hex) {
382
- const value = hex.replace("#", "");
383
- return [
384
- Number.parseInt(value.slice(0, 2), 16),
385
- Number.parseInt(value.slice(2, 4), 16),
386
- Number.parseInt(value.slice(4, 6), 16)
387
- ];
388
- }
389
- function fg(hex) {
390
- const [r, g, b] = hexToRgb(hex);
391
- return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
392
- }
393
- var ink = fg(RIG_PALETTE.ink);
394
- var ink2 = fg(RIG_PALETTE.ink2);
395
- var ink3 = fg(RIG_PALETTE.ink3);
396
- var ink4 = fg(RIG_PALETTE.ink4);
397
- var accent = fg(RIG_PALETTE.accent);
398
- var accentDim = fg(RIG_PALETTE.accentDim);
399
- var cyan = fg(RIG_PALETTE.cyan);
400
- var red = fg(RIG_PALETTE.red);
401
- var yellow = fg(RIG_PALETTE.yellow);
402
- function bold(text) {
403
- return `\x1B[1m${text}\x1B[22m`;
404
- }
405
- var DRONE_ART = [
406
- " .-=-. .-=-. ",
407
- " ( !!! ) ( !!! ) ",
408
- " '-=-'._ _.'-=-' ",
409
- " '._ _.' ",
410
- " '=$$$$$$$=.' ",
411
- " =$$$$$$$$$$$= ",
412
- " $$$@@@@@@@@@@$$$ ",
413
- " $$$@@ @@$$$ ",
414
- " $$@ ? @$$$ ",
415
- " $$$@ '-' @$$$ ",
416
- " $$$@@ @@$$$ ",
417
- " $$$@@@@@@@@@@$$$ ",
418
- " =$$$$$$$$$$$= ",
419
- " '=$$$$$$$=.' ",
420
- " _.' '._ ",
421
- " .-=-.' '.-=-. ",
422
- " ( !!! ) ( !!! ) ",
423
- " '-=-' '-=-' "
424
- ];
425
- var EYE_FRAMES = ["@", "o", "."];
426
- var DRONE_WIDTH = DRONE_ART[0].length;
427
- var DRONE_HEIGHT = DRONE_ART.length;
428
- var MICRO_BLADES = ["---", "\\\\\\", "|||", "///"];
429
- function microDroneFrame(tick) {
430
- const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
431
- const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
432
- return `(${blade})${eye}(${blade})`;
433
- }
434
- var MICRO_DRONE_FRAMES = Array.from({ length: 12 }, (_, index) => microDroneFrame(index));
435
- function renderMicroDroneFrame(tick) {
436
- const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
437
- const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
438
- return `${ink4("(")}${cyan(blade)}${ink4(")")}${bold(accent(eye))}${ink4("(")}${cyan(blade)}${ink4(")")}`;
439
- }
440
-
441
- // packages/cli/src/commands/_spinner.ts
442
- var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
443
- function createTtySpinner(input) {
444
- const output = input.output ?? process.stdout;
445
- const isTty = output.isTTY === true;
446
- const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
447
- let label = input.label;
448
- let frame = 0;
449
- let paused = false;
450
- let stopped = false;
451
- let lastPrintedLabel = "";
452
- const render = () => {
453
- if (stopped || paused)
454
- return;
455
- if (!isTty) {
456
- if (label !== lastPrintedLabel) {
457
- output.write(`${label}
458
- `);
459
- lastPrintedLabel = label;
460
- }
461
- return;
462
- }
463
- frame = (frame + 1) % frames.length;
464
- const glyph = frames[frame] ?? frames[0] ?? "";
465
- output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
466
- };
467
- const clearLine = () => {
468
- if (isTty)
469
- output.write("\r\x1B[2K");
470
- };
471
- render();
472
- const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
473
- return {
474
- setLabel(next) {
475
- label = next;
476
- render();
477
- },
478
- pause() {
479
- paused = true;
480
- clearLine();
481
- },
482
- resume() {
483
- if (stopped)
484
- return;
485
- paused = false;
486
- render();
487
- },
488
- stop(finalLine) {
489
- if (stopped)
490
- return;
491
- stopped = true;
492
- if (timer)
493
- clearInterval(timer);
494
- clearLine();
495
- if (finalLine)
496
- output.write(`${finalLine}
497
- `);
498
- }
499
- };
500
- }
501
-
502
- // packages/cli/src/app/drone-ui.ts
503
- function droneNote(message, title) {
504
- if (title)
505
- console.log(` ${accentDim("\u25C7")} ${bold(ink2(title))}`);
506
- for (const line of message.split(`
507
- `)) {
508
- console.log(` ${ink4("\u2502")} ${line}`);
509
- }
510
- }
511
-
512
44
  // packages/cli/src/commands/_cli-format.ts
513
45
  import pc from "picocolors";
514
- var themeDim = (value) => ink3(value);
515
- function shouldUseClackOutput() {
516
- return Boolean(process.stdout.isTTY) && process.env.RIG_CLI_PLAIN_HELP !== "1";
517
- }
518
- function printFormattedOutput(message, options = {}) {
519
- if (!shouldUseClackOutput()) {
520
- console.log(message);
521
- return;
522
- }
523
- droneNote(message, options.title);
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);
524
57
  }
525
- function formatSection(title, subtitle) {
526
- return `${pc.bold(accent("\u25C6"))} ${pc.bold(title)}${subtitle ? themeDim(` \u2014 ${subtitle}`) : ""}`;
58
+ function printFormattedOutput(message) {
59
+ console.log(message);
527
60
  }
528
- function formatNextSteps(steps) {
529
- if (steps.length === 0)
530
- return [];
531
- return [pc.bold("Next"), ...steps.map((step) => `${accent("\u203A")} ${step}`)];
532
- }
533
-
534
- // packages/cli/src/commands/_async-ui.ts
535
- import pc2 from "picocolors";
536
- var DONE_SYMBOL = pc2.green("\u25C7");
537
- var FAIL_SYMBOL = pc2.red("\u25A0");
538
- var activeUpdate = null;
539
- async function withSpinner(label, work, options = {}) {
540
- if (options.outputMode === "json") {
541
- return work(() => {});
542
- }
543
- if (activeUpdate) {
544
- const outer = activeUpdate;
545
- outer(label);
546
- return work(outer);
547
- }
548
- const output = options.output ?? process.stderr;
549
- const isTty = output.isTTY === true;
550
- let lastLabel = label;
551
- if (!isTty) {
552
- output.write(`${label}
61
+ function formatStatsTable(rows) {
62
+ return rows.map(([label, value]) => `${faintBar} ${dim(label.padEnd(20))} ${compactValue(value)}`).join(`
553
63
  `);
554
- const update2 = (next) => {
555
- lastLabel = next;
556
- };
557
- activeUpdate = update2;
558
- const previousListener2 = setServerPhaseListener(update2);
559
- try {
560
- return await work(update2);
561
- } finally {
562
- activeUpdate = null;
563
- setServerPhaseListener(previousListener2);
564
- }
565
- }
566
- const spinner = createTtySpinner({
567
- label,
568
- output,
569
- frames: MICRO_DRONE_FRAMES,
570
- styleFrame: (frame) => renderMicroDroneFrame(Math.max(0, MICRO_DRONE_FRAMES.indexOf(frame)))
571
- });
572
- const update = (next) => {
573
- lastLabel = next;
574
- spinner.setLabel(next);
575
- };
576
- activeUpdate = update;
577
- const previousListener = setServerPhaseListener(update);
578
- try {
579
- const result = await work(update);
580
- spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
581
- return result;
582
- } catch (error) {
583
- spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
584
- throw error;
585
- } finally {
586
- activeUpdate = null;
587
- setServerPhaseListener(previousListener);
588
- }
589
64
  }
590
65
 
591
66
  // packages/cli/src/commands/_help-catalog.ts
592
- import pc3 from "picocolors";
67
+ import pc2 from "picocolors";
593
68
  var TOP_LEVEL_SECTIONS = [
594
69
  {
595
- title: "Pi console",
596
- subtitle: "the operator surface \u2014 every attach opens the run's FULL Pi session",
70
+ title: "Open Rig",
71
+ subtitle: "normal UX \u2014 bare rig opens the bundled Rig Cockpit inside OMP",
597
72
  commands: [
598
- { command: "rig run attach <run-id>", description: "Open the run's Pi console: complete transcript (live AND finished runs), worker turns streaming in live." },
599
- { command: "type a message", description: "Inside the console, plain text steers the worker mid-turn \u2014 it lands in the agent's context, not a local model." },
600
- { command: "!<command>", description: "Inside the console, runs shell in the WORKER's workspace; output streams back into the transcript." },
601
- { command: "/<command>", description: "The palette includes the WORKER session's slash commands ([worker]-tagged) next to /rig." },
602
- { command: "/rig abort | /rig stop", description: "Abort the worker's current turn, or stop the whole run, from inside the console." },
603
- { command: "rig run steer <id> --message <text>", description: "Steer without attaching \u2014 queue a message; the worker picks it up within seconds." }
73
+ { command: "rig", description: "Open Rig Cockpit with OMP collaboration substrate for the current workspace." },
74
+ { command: "rig --workspace <path>", description: "Open Rig Cockpit for another workspace." },
75
+ { command: "/rig", description: "Inside OMP, reopen the Rig Cockpit." },
76
+ { command: "rig join <link>", description: "Join an encrypted OMP collaborative session from another operator." }
604
77
  ]
605
78
  },
606
79
  {
607
- title: "Start here",
608
- subtitle: "one-time setup, pick a server",
80
+ title: "Server",
81
+ subtitle: "select execution placement before reading or dispatching work",
609
82
  commands: [
610
- { command: "rig init", description: "Wizard: config, GitHub auth, task source, server, Pi wiring." },
611
- { command: "rig server use <alias|local>", description: "Pick which Rig server owns this repo (`rig server list` to see them)." },
612
- { command: "rig server status", description: "Show the selected server for this repo." }
83
+ { command: "Cockpit Server", description: "Select/configure local or remote execution placement and the project-root link." },
84
+ { command: "Server target \u2192 Local", description: "Use local execution placement for the selected project." },
85
+ { command: "Server target \u2192 Remote", description: "Use remote execution placement with the server-host project root preserved." }
613
86
  ]
614
87
  },
615
88
  {
616
- title: "Work",
617
- subtitle: "find a task, put an agent on it, answer what it asks",
89
+ title: "Tasks",
90
+ subtitle: "configured task source on the selected target feeds task detail",
618
91
  commands: [
619
- { command: "rig task list", description: "What's on the board (from the selected source/server)." },
620
- { command: "rig task run --next", description: "Dispatch an agent on the next ready task; interactive mode drops you into its Pi console." },
621
- { command: "rig run status", description: "Active and recent runs at a glance." },
622
- { command: "rig run attach <id> --follow", description: "Same as plain attach \u2014 the Pi console is always the interactive surface." },
623
- { command: "rig inbox approvals", description: "Approvals workers are waiting on (then `rig inbox approve \u2026`)." }
92
+ { command: "Cockpit Tasks", description: "List tasks from the selected server target's configured task source." },
93
+ { command: "Task detail", description: "Review task id, title, body, source status, labels, and issue URL before dispatch." },
94
+ { command: "Task detail \u2192 Dispatch", description: "Submit the selected task run through the selected target, then attach the collaborative OMP session." },
95
+ { command: "rig triage", description: "Run the configured issueAnalysis pass once; use cron/launchd/CI to schedule continuous triage." }
624
96
  ]
625
97
  },
626
98
  {
627
- title: "Watch",
628
- subtitle: "fleet metrics and per-task forensics",
99
+ title: "Runs",
100
+ subtitle: "dispatched Rig runs attach through collaborative OMP sessions",
629
101
  commands: [
630
- { command: "rig stats [--since 7d]", description: "Fleet metrics: completion/failure rates, median run time, steering, stalls." },
631
- { command: "rig inspect logs --task <id>", description: "Latest run log for a task." },
632
- { command: "rig inspect diff --task <id>", description: "Changed files for a task." }
102
+ { command: "OMP Runs screen", description: "Open dispatched runs, collaboration links, cwd, identity, status, and inbox counts." },
103
+ { command: "rig join <link>", description: "Join a shared OMP session without changing execution placement." },
104
+ { command: "OMP collab guest view", description: "Watch the shared transcript live; write links can prompt the originating session." }
633
105
  ]
634
106
  },
635
107
  {
636
- title: "Unblock",
637
- subtitle: "diagnose wiring, fix auth",
108
+ title: "Inbox + Doctor",
109
+ subtitle: "approvals, user prompts, and health checks live inside Rig/OMP",
638
110
  commands: [
639
- { command: "rig doctor", description: "Check every part of the wiring \u2014 run this when anything feels off." },
640
- { command: "rig github auth status", description: "GitHub auth state on the selected server." }
111
+ { command: "OMP Inbox screen", description: "Review and answer pending approval/user-input workflow entries." },
112
+ { command: "/rig inbox", description: "Open the inbox screen from inside OMP." },
113
+ { command: "OMP Doctor screen", description: "Probe session discovery, collaboration registry, selected target, and workflow-entry health." }
641
114
  ]
642
115
  },
643
116
  {
644
117
  title: "Extend",
645
- subtitle: "plugins contribute validators, hooks, task sources, commands",
118
+ subtitle: "plugins and OMP extensions shape the worker environment",
646
119
  commands: [
647
- { command: "rig plugin list", description: "What the rig.config.ts plugins contribute." },
648
- { command: "rig plugin run <command-id>", description: "Execute a plugin-contributed CLI command." }
120
+ { command: "rig.config.ts plugins", description: "Register task sources, validators, hooks, skills, roles, and workflow metadata." },
121
+ { command: "runtime.pi.packages", description: "Declare team-wide OMP/Pi extension packages for sessions and workers." }
649
122
  ]
650
123
  }
651
124
  ];
652
125
  var PRIMARY_GROUPS = [
653
126
  {
654
127
  name: "server",
655
- summary: "Choose, inspect, and start the Rig server that owns tasks and runs.",
656
- usage: ["rig server <status|list|add|use|start> [options]"],
128
+ summary: "Select and configure local vs remote execution placement.",
129
+ usage: ["rig server <status|list|use|add|remove> [options]"],
657
130
  commands: [
658
- { command: "status", description: "Show the selected server for this repo.", primary: true },
659
- { command: "use local", description: "Switch this repo to the local Rig server.", primary: true },
660
- { command: "add <alias> <url>", description: "Save a remote Rig server URL.", primary: true },
661
- { command: "use <alias>", description: "Select a saved remote server alias.", primary: true },
662
- { command: "list", description: "List saved local/remote server aliases.", primary: true },
663
- { command: "start [--host <host>] [--port <n>]", description: "Start a local rig-server process." }
131
+ { command: "status", description: "Show the selected placement (local or remote alias) and task source.", primary: true },
132
+ { command: "list", description: "List local plus configured remote endpoints; marks the selected one.", primary: true },
133
+ { command: "use local|<alias>", description: "Select where runs execute \u2014 local, or a configured remote alias.", primary: true },
134
+ { command: "add --alias <a> --host <h> [--port <n>] [--token <t>]", description: "Add a remote SSH endpoint (the same store run dispatch resolves).", primary: true },
135
+ { command: "remove <alias>", description: "Remove a configured remote endpoint." }
664
136
  ],
665
137
  examples: [
666
138
  "rig server status",
667
- "rig server add prod https://where.rig-does.work",
668
- "rig server use prod",
669
- "rig server use local",
670
- "rig server start --port 3773"
139
+ "rig server add --alias prod --host prod.example.com --port 22",
140
+ "rig server use prod"
671
141
  ],
672
- next: ["Use `rig task list` to see server-owned work.", "Use `rig run list` or `rig run attach <id> --follow` to monitor runs."]
142
+ next: [
143
+ "Placement is read by run dispatch (`rig task run` / `rig run <task>`).",
144
+ "The same selection is available in-session on the bare `rig` board."
145
+ ]
673
146
  },
674
147
  {
675
148
  name: "task",
676
- summary: "Find work, start Pi-backed runs, and validate task results.",
677
- usage: ["rig task <list|next|show|run> [options]"],
149
+ summary: "Find work, dispatch task runs, and validate task results.",
150
+ usage: ["rig task <list|next|show|run|ready|validate> [options]"],
678
151
  commands: [
679
- { command: "list [--assignee <login|me|@me>] [--state open|closed]", description: "List tasks from the selected server/source.", primary: true },
680
- { command: "next [filters]", description: "Render the next matching task as a selected-task card.", primary: true },
681
- { command: "show <id>|--task <id> [--raw]", description: "Show a human task summary; --raw prints the full payload.", primary: true },
682
- { command: "run [#<issue>|<task-id>|--next|--task <id>]", description: "Submit a task run; interactive follows with bundled Pi.", primary: true },
152
+ { command: "list [--assignee <login|me|@me>] [--state open|closed]", description: "List tasks from the configured task source.", primary: true },
153
+ { command: "next [filters]", description: "Render the next ready task as a selected-task card.", primary: true },
154
+ { command: "show <id>|--task <id> [--raw]", description: "Show a task summary; --raw prints the full payload.", primary: true },
155
+ { command: "run [#<issue>|<task-id>|--task <id>]", description: "Dispatch the task as a detached run; attach with `rig run attach <run-id>`.", primary: true },
683
156
  { command: "ready", description: "List task IDs that are runnable now." },
684
- { command: "validate|verify [--task <id>]", description: "Run configured task checks/review gates." },
157
+ { command: "validate|verify [--task <id>]", description: "Run the task's configured checks / review gates." },
685
158
  { command: "details --task <id>", description: "Show full task info from the configured source." },
686
159
  { command: "reopen [--task <id> | --all] [--reason <text>]", description: "Reopen closed task(s) in the configured source." },
687
160
  { command: "artifacts|artifact-dir|artifact-write", description: "Inspect or write task artifacts." },
688
- { command: "report-bug", description: "Create a structured bug report/task." }
161
+ { command: "info [--task <id>]", description: "Show the active/selected task's full runtime info." },
162
+ { command: "scope [--files] [--task <id>]", description: "Show the task's scope (and changed files with --files)." },
163
+ { command: "deps [--task <id>]", description: "Show the task's dependency graph." },
164
+ { command: "status", description: "Show project task-tracker progress." },
165
+ { command: "lookup <id>", description: "Resolve a task id to its canonical record path." },
166
+ { command: "record <decision|failure> <text> [--task <id>]", description: "Append a decision or failure note to the task." }
689
167
  ],
690
168
  examples: [
691
169
  "rig task list --assignee @me --limit 20",
692
- "rig task next",
693
170
  "rig task show 123 --raw",
694
- "rig task run --next",
695
- "rig task run #123 --runtime-adapter pi",
696
- "rig task run --title 'Investigate deploy drift' --initial-prompt 'Check server health'"
171
+ "rig task run #123"
697
172
  ],
698
- next: ["Use `--detach` to submit without attaching.", "Use `rig run attach <run-id> --follow` to rejoin a live run."]
173
+ next: ["The same flow is available in-session: run bare `rig`, then Cockpit Tasks \u2192 Task detail \u2192 Dispatch."]
699
174
  },
700
175
  {
701
176
  name: "run",
702
- summary: "Observe, attach to, and control Rig runs. `attach` opens the Pi console \u2014 the full worker session, live.",
703
- usage: ["rig run <list|status|show|attach|steer|stop|resume|restart> [options]"],
177
+ summary: "Observe, attach to, and control runs over the OMP collab substrate.",
178
+ usage: ["rig run <list|status|start|start-serial|start-parallel|show|attach|steer|stop|resume|restart> [options]"],
704
179
  commands: [
705
- { command: "list", description: "List recent runs from the selected server or local state.", primary: true },
706
- { command: "status", description: "Render active and recent run groups.", primary: true },
707
- { command: "show <id>|--run <id> [--raw]", description: "Show a human run summary; --raw prints the full payload.", primary: true },
708
- { command: "attach <run-id>|--run <id>", description: "Open the run's Pi console \u2014 see 'Inside the console' below. Works on live AND finished runs.", primary: true },
709
- { command: "steer <run-id> --message <text>", description: "Queue a steering message into a live worker without attaching \u2014 delivered into the agent's context within seconds.", primary: true },
710
- { command: "stop [<run-id>|--run <id>]", description: "Request stop for one run or local active runs.", primary: true },
711
- { command: "resume [<run-id>]", description: "Resume an interrupted run on the selected server (defaults to the most recent resumable)." },
712
- { command: "restart [<run-id>]", description: "Re-dispatch a run from a clean runtime, reopening its Pi session where possible." },
713
- { command: "timeline --run <id> [--follow]", description: "Stream raw run timeline events (scripts; humans should attach)." },
714
- { command: "replay <run-id>|--run <id> [--with-session]", description: "Print the run's consolidated run.jsonl as a merged timeline; --with-session interleaves the Pi session log." },
715
- { command: "delete|cleanup", description: "Remove completed run records/artifacts." }
180
+ { command: "list", description: "List discoverable runs (relay-registry + on-disk OMP run journals).", primary: true },
181
+ { command: "status", description: "Render active vs recent runs from the run journals.", primary: true },
182
+ { command: "start [#<issue>|<task-id>|--next|--task <id>] [--title <t>]", description: "Dispatch one task as a detached run; attach with the printed run id.", primary: true },
183
+ { command: "start-parallel [--limit <n>]", description: "Dispatch all currently-ready tasks as detached runs (limited when requested).", primary: true },
184
+ { command: "start-serial", description: "Dispatch the next ready task only; run again after it finishes to continue serially.", primary: true },
185
+ { command: "show <id>|--run <id> [--raw]", description: "Show a run's folded journal projection; --raw prints JSON.", primary: true },
186
+ { command: "attach <run-id>|--run <id>", description: "Attach to a live run's collab session (resolves the join link).", primary: true },
187
+ { command: "resume [<run-id>]", description: "Re-dispatch the run's task as a fresh detached run." },
188
+ { command: "steer <run-id> --message <text>", description: "Steer a live run via its journal (local runs headless; remote runs attach to steer).", primary: true },
189
+ { command: "stop [<run-id>|--run <id>] [--reason <text>]", description: "Request graceful stop of a live run (local headless; remote attach to stop).", primary: true },
190
+ { command: "replay <run-id>|--run <id> [--with-session]", description: "Print the run's consolidated journal timeline." },
191
+ { command: "delete|cleanup", description: "Remove a finished run's live registry entry." }
716
192
  ],
717
193
  examples: [
718
- "rig run attach <run-id> # full session console \u2014 live mirror, steering, worker shell",
719
- "rig run steer <run-id> --message 'focus on the failing test first'",
720
- "rig run list",
721
- "rig run show <run-id>",
722
- "rig run stop <run-id>"
194
+ "rig run start --next",
195
+ "rig run start-parallel --limit 3",
196
+ "rig run attach <run-id>"
723
197
  ],
724
198
  next: [
725
- "Inside the console: the run's COMPLETE transcript loads on open (finished runs too), and new worker turns stream in live.",
726
- "Inside the console: plain text = steering into the worker's context \xB7 !<cmd> = shell in the WORKER workspace \xB7 /<cmd> includes the worker session's commands \xB7 /rig abort stops the current turn.",
727
- "The console is read-write but remote-only: nothing runs on your machine; the worker keeps running when you exit.",
728
- "Use `rig task run --next` to create a new run; interactive mode drops you straight into its console.",
729
- "Use `--json` when scripts need the full structured record."
199
+ "Attach to steer or stop a live run: rig run attach <run-id>.",
200
+ "The same runs appear in-session on the bare `rig` board."
730
201
  ]
731
202
  },
732
203
  {
733
204
  name: "inbox",
734
205
  summary: "Review approval and user-input requests that block worker runs.",
735
- usage: ["rig inbox <approvals|approve|inputs|respond> [options]"],
206
+ usage: ["rig inbox <approvals|inputs|approve|respond> [options]"],
736
207
  commands: [
737
- { command: "approvals [--run <id>] [--task <id>]", description: "List pending approvals.", primary: true },
738
- { command: "inputs [--run <id>] [--task <id>]", description: "List pending user-input requests.", primary: true },
739
- { command: "approve --run <id> --request <id> --decision approve|reject", description: "Resolve an approval request." },
740
- { command: "respond --run <id> --request <id> --answer key=value", description: "Answer a user-input request." }
208
+ { command: "approvals [--run <id>] [--task <id>]", description: "List pending approval requests across runs.", primary: true },
209
+ { command: "inputs [--run <id>] [--task <id>]", description: "List pending user-input requests across runs.", primary: true },
210
+ { command: "approve --run <id> --request <id> --decision approve|reject", description: "Resolve an approval \u2014 delivered over collab, so attach to resolve." },
211
+ { command: "respond --run <id> --request <id> --answer key=value", description: "Answer a user-input request \u2014 attach to resolve." }
741
212
  ],
742
213
  examples: [
743
214
  "rig inbox approvals",
744
- "rig inbox inputs --run <run-id>",
745
- "rig inbox approve --run <run-id> --request <request-id> --decision approve"
215
+ "rig inbox inputs --run <run-id>"
746
216
  ],
747
- next: ["Rejoin the run after resolving a block: `rig run attach <run-id> --follow`."]
217
+ next: ["Resolve a request by attaching to its run: rig run attach <run-id>."]
748
218
  },
749
219
  {
750
220
  name: "stats",
751
221
  summary: "Fleet metrics computed from on-disk run journals (no server required).",
752
222
  usage: ["rig stats [show] [--since <7d|30d|ISO date>]"],
753
223
  commands: [
754
- { command: "show [--since <window>]", description: "Total runs, completion/failure/needs-attention rates, median run time, steering, stalls, approvals.", primary: true }
224
+ { command: "show [--since <window>]", description: "Totals, completion/failure/needs-attention rates, run time, steering, stalls, approvals.", primary: true }
755
225
  ],
756
226
  examples: [
757
- "rig stats",
758
227
  "rig stats --since 7d",
759
- "rig stats --since 2026-06-01 --json"
228
+ "rig stats --json"
760
229
  ],
761
- next: ["Inspect outliers with `rig run list` and `rig run show <run-id>`.", "Use `--json` for the schema'd envelope (see docs/cli-json.md)."]
230
+ next: ["Inspect outliers with `rig run list` and `rig run show <run-id>`."]
762
231
  },
763
232
  {
764
233
  name: "inspect",
765
- summary: "Inspect logs, artifacts, graphs, failures for a task.",
234
+ summary: "Inspect logs, artifacts, failures, graph, and diffs for a task.",
766
235
  usage: ["rig inspect <logs|artifacts|failures|graph|audit|diff> --task <id>"],
767
236
  commands: [
768
- { command: "logs --task <id>", description: "Latest run log for a task (local or selected server).", primary: true },
237
+ { command: "logs --task <id>", description: "Latest run log for a task.", primary: true },
769
238
  { command: "artifacts --task <id>", description: "List the task's completion artifacts.", primary: true },
239
+ { command: "diff --task <id>", description: "List files changed by a task.", primary: true },
770
240
  { command: "failures --task <id>", description: "Recorded failures for a task.", primary: true },
771
- { command: "diff --task <id>", description: "Changed files for a task.", primary: true },
772
241
  { command: "graph", description: "Task dependency graph." },
773
242
  { command: "audit", description: "Controlled-command audit trail." }
774
243
  ],
@@ -798,72 +267,75 @@ var PRIMARY_GROUPS = [
798
267
  },
799
268
  {
800
269
  name: "init",
801
- summary: "Set up Rig for this repo: server, GitHub auth, checkout strategy, task source, and Pi wiring.",
270
+ summary: "Legacy automation-only config bootstrap/repair helper; not root onboarding.",
802
271
  usage: ["rig init [--yes] [--server local|remote] [--repo owner/repo] [--remote-url <url>]"],
803
272
  commands: [
804
- { command: "init", description: "Interactive setup wizard for a new or existing Rig repo.", primary: true },
805
- { command: "init --demo", description: "Offline demo project: files task source + 3 sample tasks, zero GitHub.", primary: true },
806
- { command: "init --yes", description: "Non-interactive setup using detected/default settings.", primary: true },
807
- { command: "init --server remote --remote-url <url>", description: "Link this repo to a remote Rig server.", primary: true },
808
- { command: "init --repair", description: "Repair missing private state without replacing project config." }
273
+ { command: "init", description: "Legacy/fenced setup wizard for compatibility projects; not onboarding.", primary: true },
274
+ { command: "init --demo", description: "Legacy/fenced offline demo project for compatibility tests.", primary: true },
275
+ { command: "init --yes", description: "Legacy/fenced non-interactive setup for old automation.", primary: true },
276
+ { command: "init --server remote --remote-url <url>", description: "Legacy/fenced remote server link setup.", primary: true },
277
+ { command: "init --repair", description: "Legacy/fenced repair of missing private compatibility state." }
809
278
  ],
810
279
  examples: [
811
- "rig init",
812
- "rig init --demo",
813
- "rig init --yes --repo humanity-org/humanwork",
814
- "rig init --server remote --remote-url https://where.rig-does.work --repo owner/repo"
280
+ "rig init --demo # legacy compatibility only",
281
+ "rig init --repair # legacy compatibility only"
815
282
  ],
816
- next: ["After init, run `rig server status`.", "Then use `rig task list` and `rig task run --next` for day-to-day work."]
283
+ next: ["For onboarding and normal UX, run bare `rig`, then use Cockpit Server/Tasks and dispatch from Task detail."]
817
284
  },
818
285
  {
819
286
  name: "doctor",
820
- summary: "Diagnostics for project/server/GitHub/Pi state.",
287
+ summary: "Diagnostic-only compatibility checks; normal health lives in the OMP Doctor screen.",
821
288
  usage: ["rig doctor"],
822
289
  commands: [
823
- { command: "doctor", description: "Run setup and runtime diagnostics.", primary: true },
824
- { command: "check", description: "Compatibility spelling for diagnostics." }
290
+ { command: "doctor", description: "Diagnostic/legacy-only: check compatibility setup/runtime state; not onboarding.", primary: true },
291
+ { command: "check", description: "Diagnostic/legacy-only spelling for compatibility checks." }
825
292
  ],
826
- examples: ["rig doctor", "rig doctor --json"],
827
- next: ["Use `rig server status` and `rig github auth status` to inspect common failure points."]
293
+ examples: ["rig doctor --json # legacy diagnostic automation only"],
294
+ next: ["For normal UX, use the OMP Doctor screen."]
828
295
  },
829
296
  {
830
297
  name: "github",
831
- summary: "GitHub auth helpers for the selected Rig server.",
298
+ summary: "Legacy automation-only GitHub auth helpers for selected compatibility servers.",
832
299
  usage: ["rig github auth <status|import-gh|token>"],
833
300
  commands: [
834
- { command: "auth status", description: "Show GitHub auth state.", primary: true },
835
- { command: "auth import-gh", description: "Import the current `gh` token into the selected server." },
836
- { command: "auth token --token <token>", description: "Store a token on the selected server." }
301
+ { command: "auth status", description: "Legacy automation-only: show GitHub auth state.", primary: true },
302
+ { command: "auth import-gh", description: "Legacy automation-only: import the current `gh` token into the selected server." },
303
+ { command: "auth token --token <token>", description: "Legacy automation-only: store a token on the selected server." }
837
304
  ],
838
- examples: ["rig github auth status", "rig github auth import-gh"],
839
- next: ["After auth is valid, use `rig task run --next`."]
305
+ examples: ["rig github auth status # legacy automation only"],
306
+ next: ["For normal UX, use bare `rig` and the OMP extension cockpit."]
840
307
  }
841
308
  ];
842
309
  var ADVANCED_GROUPS = [
843
- { name: "setup", summary: "Bootstrap/check local setup.", usage: ["rig setup <bootstrap|check|preflight>"], commands: [{ command: "bootstrap|check|preflight", description: "Setup helpers." }] },
844
- { name: "profile", summary: "Runtime profile/model defaults.", usage: ["rig profile <show|set>"], commands: [{ command: "show", description: "Show active profile." }] },
310
+ {
311
+ name: "setup",
312
+ summary: "Legacy automation-only setup bootstrap/check helpers.",
313
+ usage: ["rig setup <bootstrap|check|preflight>"],
314
+ commands: [
315
+ { command: "bootstrap", description: "Legacy automation-only: bootstrap local setup dependencies.", primary: true },
316
+ { command: "check", description: "Legacy automation-only: check local setup state (toolchain, deps, config).", primary: true },
317
+ { command: "preflight", description: "Legacy automation-only: run preflight checks before an old run.", primary: true }
318
+ ]
319
+ },
320
+ {
321
+ name: "profile",
322
+ summary: "Runtime profile/model defaults.",
323
+ usage: ["rig profile <show|set>"],
324
+ commands: [
325
+ { command: "show", description: "Show the active execution profile.", primary: true },
326
+ { command: "set [--model <model>] [--runtime <runtime>] [--plugin <plugin>]", description: "Set model/runtime/plugin profile defaults.", primary: true }
327
+ ]
328
+ },
845
329
  {
846
330
  name: "review",
847
331
  summary: "Inspect or change completion review gate policy.",
848
332
  usage: ["rig review <show|set>"],
849
333
  commands: [
850
334
  { command: "show", description: "Show current review gate settings." },
851
- { command: "set <off|advisory|required> [--provider greptile]", description: "Change review strictness/provider." }
335
+ { command: "set <off|advisory|required> [--provider <provider>]", description: "Change review strictness/provider (e.g. --provider greptile)." }
852
336
  ],
853
337
  examples: ["rig review show", "rig review set required --provider greptile"],
854
- next: ["Use `rig inbox approvals` for blocked run handoffs."]
855
- },
856
- {
857
- name: "browser",
858
- summary: "Browser/app diagnostics for browser-required tasks.",
859
- usage: ["rig browser <help|explain|demo|app|hp-next> [options]"],
860
- commands: [
861
- { command: "help", description: "Rich browser command help (canonical: `rig browser help`)." },
862
- { command: "explain", description: "Explain the browser-required task contract." },
863
- { command: "demo", description: "Run browser demo flows against a local page." },
864
- { command: "app", description: "Launch the Rig Browser workstation app." },
865
- { command: "hp-next <dev|check|e2e|reset>", description: "Drive the hp-next browser test harness." }
866
- ]
338
+ next: ["For normal workflow handoffs, use the OMP Inbox screen."]
867
339
  },
868
340
  {
869
341
  name: "pi",
@@ -878,23 +350,81 @@ var ADVANCED_GROUPS = [
878
350
  examples: ["rig pi search subagents", "rig pi add pi-subagents", "rig pi list"],
879
351
  next: ["Config-managed extensions: declare `runtime: { pi: { packages: [...] } }` in rig.config.ts \u2014 workers pick them up automatically."]
880
352
  },
881
- { name: "queue", summary: "Run task queues locally.", usage: ["rig queue run [options]"], commands: [{ command: "run", description: "Process queue work." }] },
882
- { name: "agent", summary: "Runtime agent workspace helpers.", usage: ["rig agent <list|prepare|run|cleanup>"], commands: [{ command: "list", description: "List prepared agents." }] },
353
+ {
354
+ name: "queue",
355
+ summary: "Run task queues locally.",
356
+ usage: ["rig queue run [--workers <n>] [--max-tasks <n>] [--action validate|verify|pipeline] [--isolation off|worktree] [--no-runtime-reuse] [--fail-fast] [--skip-project-sync]"],
357
+ commands: [
358
+ { command: "run [--workers <n>] [--max-tasks <n>] [--action validate|verify|pipeline] [--isolation off|worktree] [--no-runtime-reuse] [--fail-fast] [--skip-project-sync]", description: "Process queue work: drain matching tasks with N workers, optional per-task action and isolation.", primary: true }
359
+ ]
360
+ },
361
+ {
362
+ name: "agent",
363
+ summary: "Runtime agent workspace helpers.",
364
+ usage: ["rig agent <list|prepare|run|cleanup>"],
365
+ commands: [
366
+ { command: "list", description: "List prepared agent runtimes.", primary: true },
367
+ { command: "prepare [--id <id>] [--mode <mode>] [--task <task>]", description: "Prepare an isolated agent runtime workspace.", primary: true },
368
+ { command: "run [--id <id>] [--mode <mode>] [--task <task>] [--skip-project-sync]", description: "Prepare (if needed) and run an agent in its workspace.", primary: true },
369
+ { command: "cleanup [--all] [--id <id>]", description: "Remove prepared agent workspaces.", primary: true }
370
+ ]
371
+ },
883
372
  { name: "inspector", summary: "Event stream and drift scanners.", usage: ["rig inspector <stream|scan-upstream-drift>"], commands: [{ command: "stream", description: "Stream events." }] },
884
- { name: "dist", summary: "Build/install packaged Rig CLI.", usage: ["rig dist <build|install|doctor>"], commands: [{ command: "build", description: "Build distribution." }] },
885
- { name: "workspace", summary: "Workspace topology/service helpers.", usage: ["rig workspace <summary|topology|remote-hosts>"], commands: [{ command: "summary", description: "Show workspace summary." }] },
886
- { name: "remote", summary: "Compatibility remote orchestration controls.", usage: ["rig remote <status|watch|pause|resume|...>"], commands: [{ command: "status", description: "Show remote state." }] },
373
+ {
374
+ name: "dist",
375
+ summary: "Build/install packaged Rig CLI.",
376
+ usage: ["rig dist <build|install|doctor|rebuild-agent>"],
377
+ commands: [
378
+ { command: "build [--output-dir <dir>]", description: "Build the distributable Rig CLI.", primary: true },
379
+ { command: "install [--scope <scope>] [--path <path>]", description: "Install the built CLI to a scope/path.", primary: true },
380
+ { command: "doctor", description: "Diagnose the dist toolchain and install state.", primary: true },
381
+ { command: "rebuild-agent", description: "Rebuild the agent runtime image, pruning stale images.", primary: true }
382
+ ]
383
+ },
384
+ {
385
+ name: "workspace",
386
+ summary: "Workspace topology/service helpers.",
387
+ usage: ["rig workspace <summary|topology|remote-hosts|service-fabric>"],
388
+ commands: [
389
+ { command: "summary", description: "Show workspace summary.", primary: true },
390
+ { command: "topology", description: "List discovered workspace services.", primary: true },
391
+ { command: "remote-hosts", description: "List configured remote workspace hosts.", primary: true },
392
+ { command: "service-fabric <status|up|verify|down> [--service <name>]", description: "Inspect or mutate the workspace service fabric.", primary: true }
393
+ ]
394
+ },
395
+ {
396
+ name: "remote",
397
+ summary: "Legacy automation-only remote orchestration controls.",
398
+ usage: ["rig remote <status|tasks|watch|pause|resume|continue|stop|refresh|add-iterations|remove-iterations|endpoint ...>"],
399
+ commands: [
400
+ { command: "status [--remote <alias>]", description: "Legacy automation-only: show old remote orchestration state.", primary: true },
401
+ { command: "tasks [--remote <alias>]", description: "Legacy automation-only: list the old remote's tracked tasks.", primary: true },
402
+ { command: "watch [--remote <alias>] [--seconds <n>] [--event <type>]", description: "Legacy automation-only: stream old remote orchestration events.", primary: true },
403
+ { command: "pause [--remote <alias>]", description: "Legacy automation-only: pause old remote orchestration.", primary: true },
404
+ { command: "resume [--remote <alias>] [--max-workers <n>] [--max-iterations <n>] [--direct-merge]", description: "Legacy automation-only: resume old remote orchestration with optional tuning.", primary: true },
405
+ { command: "continue [--remote <alias>]", description: "Legacy automation-only: continue a paused old remote.", primary: true },
406
+ { command: "stop [--remote <alias>]", description: "Legacy automation-only: stop old remote orchestration.", primary: true },
407
+ { command: "refresh [--remote <alias>]", description: "Legacy automation-only: refresh old remote state.", primary: true },
408
+ { command: "add-iterations [--count <n>] [--remote <alias>]", description: "Legacy automation-only: grant an old remote more iterations.", primary: true },
409
+ { command: "remove-iterations [--count <n>] [--remote <alias>]", description: "Legacy automation-only: reduce an old remote's iteration budget.", primary: true },
410
+ { command: "endpoint list", description: "Legacy automation-only: list configured remote endpoints.", primary: true },
411
+ { command: "endpoint add --alias <alias> --host <host> --port <n> [--token <token>]", description: "Legacy automation-only: register a remote endpoint.", primary: true },
412
+ { command: "endpoint remove --alias <alias>", description: "Legacy automation-only: remove a remote endpoint.", primary: true },
413
+ { command: "endpoint test [--alias <alias>]", description: "Legacy diagnostic-only: test connectivity to a remote endpoint.", primary: true },
414
+ { command: "endpoint doctor", description: "Legacy diagnostic-only: diagnose remote endpoint configuration.", primary: true }
415
+ ]
416
+ },
887
417
  { name: "git", summary: "Pass through to Rig git-flow helper.", usage: ["rig git <args...>"], commands: [{ command: "<args...>", description: "Advanced git flow operations." }] },
888
418
  { name: "harness", summary: "Pass through to runtime harness CLI.", usage: ["rig harness <args...>"], commands: [{ command: "<args...>", description: "Advanced harness operations." }] },
889
419
  { name: "test", summary: "Project test wrappers.", usage: ["rig test <unit|e2e|all>"], commands: [{ command: "all", description: "Run configured project tests." }] }
890
420
  ];
891
421
  var ALL_GROUPS = [...PRIMARY_GROUPS, ...ADVANCED_GROUPS];
892
422
  function heading(title) {
893
- return pc3.bold(pc3.cyan(title));
423
+ return pc2.bold(pc2.cyan(title));
894
424
  }
895
425
  function commandLine(command, description) {
896
426
  const commandColumn = command.length >= 38 ? `${command} ` : command.padEnd(38);
897
- return `${pc3.dim("\u2502")} ${pc3.bold(commandColumn)} ${description}`;
427
+ return `${pc2.dim("\u2502")} ${pc2.bold(commandColumn)} ${description}`;
898
428
  }
899
429
  function renderCommandBlock(commands) {
900
430
  return commands.map((entry) => commandLine(entry.command, entry.description)).join(`
@@ -904,40 +434,40 @@ function renderGroup(group) {
904
434
  const lines = [
905
435
  `${heading(`rig ${group.name}`)} \u2014 ${group.summary}`,
906
436
  "",
907
- pc3.bold("Usage"),
437
+ pc2.bold("Usage"),
908
438
  ...group.usage.map((line) => ` ${line}`),
909
439
  "",
910
- pc3.bold("Commands"),
440
+ pc2.bold("Commands"),
911
441
  ...group.commands.map((entry) => commandLine(entry.command, entry.description))
912
442
  ];
913
443
  if (group.examples?.length) {
914
- lines.push("", pc3.bold("Examples"), ...group.examples.map((line) => ` ${pc3.dim("$")} ${line}`));
444
+ lines.push("", pc2.bold("Examples"), ...group.examples.map((line) => ` ${pc2.dim("$")} ${line}`));
915
445
  }
916
446
  if (group.next?.length) {
917
- lines.push("", pc3.bold("Next steps"), ...group.next.map((line) => ` ${pc3.dim("\u203A")} ${line}`));
447
+ lines.push("", pc2.bold("Next steps"), ...group.next.map((line) => ` ${pc2.dim("\u203A")} ${line}`));
918
448
  }
919
449
  if (group.advanced?.length) {
920
- lines.push("", pc3.bold("Compatibility / advanced"), ...group.advanced.map((line) => ` ${pc3.dim("\u203A")} ${line}`));
450
+ lines.push("", pc2.bold("Compatibility / advanced"), ...group.advanced.map((line) => ` ${pc2.dim("\u203A")} ${line}`));
921
451
  }
922
452
  return lines.join(`
923
453
  `);
924
454
  }
925
455
  function renderTopLevelHelp() {
926
456
  return [
927
- `${heading("rig")} ${pc3.dim("\u2014 server-owned task/run control plane for Pi-backed engineering work")}`,
928
- pc3.dim("The loop: pick a task, dispatch an agent, open its Pi console (`rig run attach <id>`) to watch and steer it live, clear inbox gates, merge."),
457
+ `${heading("rig")} ${pc2.dim("\u2014 Rig Cockpit for autonomous coding agents")}`,
458
+ 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."),
929
459
  "",
930
460
  ...TOP_LEVEL_SECTIONS.flatMap((section) => [
931
- `${pc3.bold(pc3.magenta(`\u25C7 ${section.title}`))} \u2014 ${pc3.dim(section.subtitle)}`,
461
+ `${pc2.bold(pc2.magenta(`\u25C7 ${section.title}`))} \u2014 ${pc2.dim(section.subtitle)}`,
932
462
  renderCommandBlock(section.commands),
933
463
  ""
934
464
  ]),
935
- pc3.dim("More: `rig help --advanced` for dev/compatibility commands; `rig <group> --help` for rich per-group help; `rig --version` for the installed version."),
465
+ 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."),
936
466
  "",
937
- pc3.bold("Global options"),
938
- commandLine("--project <path>", "Use a project root instead of auto-discovery."),
939
- commandLine("--json", "Emit structured output for scripts/agents."),
940
- commandLine("--dry-run", "Print the command plan without mutating state.")
467
+ pc2.bold("Root options"),
468
+ commandLine("--workspace <path>", "Open Rig Cockpit for a workspace instead of the current directory."),
469
+ commandLine("--json", "Compatibility flag for fenced legacy/diagnostic subcommands."),
470
+ commandLine("--dry-run", "Compatibility flag for fenced legacy/diagnostic subcommands.")
941
471
  ].join(`
942
472
  `).trimEnd();
943
473
  }
@@ -949,6 +479,339 @@ function printGroupHelpDocument(groupName) {
949
479
  console.log(renderGroupHelp(groupName) ?? renderTopLevelHelp());
950
480
  }
951
481
 
482
+ // packages/cli/src/commands/_run-projection.ts
483
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
484
+ import { resolve as resolve2 } from "path";
485
+ import {
486
+ foldRunSessionEntries,
487
+ sessionIdFromSessionFile as sessionIdFromSessionFile2
488
+ } from "@rig/contracts";
489
+ import { listAgentRuntimes as listAgentRuntimes2 } from "@rig/runtime/control-plane/runtime/isolation";
490
+
491
+ // packages/cli/src/commands/_run-bridge.ts
492
+ import { existsSync, readdirSync, readFileSync } from "fs";
493
+ import { dirname, resolve } from "path";
494
+ import { CUSTOM_TYPE_FOR, sessionIdFromSessionFile } from "@rig/contracts";
495
+ import { createPluginHost } from "@rig/core";
496
+ import { loadConfig } from "@rig/core/load-config";
497
+ import { resolveOwnerNamespaceKey, resolveRegistryBaseUrl, resolveRegistrySecret, resolveSelectedRemote } from "@rig/runtime/control-plane/remote";
498
+ import { listAgentRuntimes } from "@rig/runtime/control-plane/runtime/isolation";
499
+ import { createRegistryClient } from "@rig/relay-registry";
500
+ import {
501
+ listActiveCollabSessions as ompListActiveCollabSessions
502
+ } from "@oh-my-pi/pi-coding-agent/collab/api";
503
+ function registrySecret(env = process.env) {
504
+ return resolveRegistrySecret(process.cwd(), env) ?? null;
505
+ }
506
+ function registryBaseUrl(env = process.env) {
507
+ return resolveRegistryBaseUrl(process.cwd(), env);
508
+ }
509
+ function collabFromRegistryEntry(entry) {
510
+ return {
511
+ sessionId: entry.roomId,
512
+ sessionPath: "",
513
+ cwd: "",
514
+ title: entry.title,
515
+ joinLink: entry.joinLink,
516
+ webLink: entry.webLink,
517
+ relayUrl: entry.relayUrl,
518
+ owner: entry.owner,
519
+ selectedRepo: entry.repo,
520
+ startedAt: entry.startedAt,
521
+ updatedAt: entry.heartbeatAt,
522
+ ...entry.pid === undefined ? {} : { pid: entry.pid },
523
+ stale: entry.stale
524
+ };
525
+ }
526
+ function isRecord(value) {
527
+ return typeof value === "object" && value !== null;
528
+ }
529
+ function readLocalRunCollab(runtime) {
530
+ if (!runtime.sessionDir || !existsSync(runtime.sessionDir))
531
+ return null;
532
+ let files;
533
+ try {
534
+ files = readdirSync(runtime.sessionDir).filter((f) => f.endsWith(".jsonl")).sort().reverse();
535
+ } catch {
536
+ return null;
537
+ }
538
+ for (const file of files) {
539
+ let lines;
540
+ try {
541
+ lines = readFileSync(resolve(runtime.sessionDir, file), "utf8").split(`
542
+ `);
543
+ } catch {
544
+ continue;
545
+ }
546
+ let host = null;
547
+ for (const line of lines) {
548
+ if (!line.includes("collab-host-started"))
549
+ continue;
550
+ let entry;
551
+ try {
552
+ entry = JSON.parse(line);
553
+ } catch {
554
+ continue;
555
+ }
556
+ if (!isRecord(entry) || entry.customType !== CUSTOM_TYPE_FOR["timeline-entry"] || !isRecord(entry.data))
557
+ continue;
558
+ const payload = isRecord(entry.data.payload) ? entry.data.payload : entry.data;
559
+ if (payload.type !== "collab-host-started")
560
+ continue;
561
+ host = {
562
+ roomId: typeof payload.roomId === "string" ? payload.roomId : undefined,
563
+ joinLink: typeof payload.joinLink === "string" ? payload.joinLink : undefined,
564
+ webLink: typeof payload.webLink === "string" ? payload.webLink : undefined,
565
+ relayUrl: typeof payload.relayUrl === "string" ? payload.relayUrl : undefined,
566
+ at: typeof entry.data.at === "string" ? entry.data.at : undefined
567
+ };
568
+ }
569
+ if (!host?.joinLink)
570
+ continue;
571
+ const at = host.at ?? "";
572
+ const sessionId = host.roomId ?? sessionIdFromSessionFile(resolve(runtime.sessionDir, file)) ?? runtime.id;
573
+ return {
574
+ sessionId,
575
+ sessionPath: resolve(runtime.sessionDir, file),
576
+ cwd: runtime.workspaceDir,
577
+ joinLink: host.joinLink,
578
+ webLink: host.webLink ?? "",
579
+ relayUrl: host.relayUrl ?? "",
580
+ title: runtime.taskId ? `[${runtime.taskId}] Rig run ${sessionId}` : `Rig run ${sessionId}`,
581
+ startedAt: at,
582
+ updatedAt: at,
583
+ stale: false
584
+ };
585
+ }
586
+ return null;
587
+ }
588
+ async function listLocalRunCollabSessions(projectRoot) {
589
+ let runtimes;
590
+ try {
591
+ runtimes = await listAgentRuntimes(projectRoot);
592
+ } catch {
593
+ return [];
594
+ }
595
+ const out = [];
596
+ for (const runtime of runtimes) {
597
+ const collab = readLocalRunCollab(runtime);
598
+ if (collab)
599
+ out.push(collab);
600
+ }
601
+ return out;
602
+ }
603
+ async function listActiveRunCollab(filter) {
604
+ return defaultListActiveCollabSessions(filter);
605
+ }
606
+ async function defaultListActiveCollabSessions(filter) {
607
+ const projectRoot = process.cwd();
608
+ const local = await listLocalRunCollabSessions(projectRoot);
609
+ const namespaceKey = filter.namespaceKey ?? resolveOwnerNamespaceKey(projectRoot);
610
+ const secret = registrySecret();
611
+ const remote = namespaceKey && secret ? (await createRegistryClient({ baseUrl: registryBaseUrl(), namespaceKey, secret }).listRoomsByOwner(filter)).map(collabFromRegistryEntry) : await ompListActiveCollabSessions(filter);
612
+ const seen = new Set(local.map((c) => c.sessionId));
613
+ return [...local, ...remote.filter((c) => !seen.has(c.sessionId))];
614
+ }
615
+
616
+ // packages/cli/src/commands/_run-diagnostics.ts
617
+ function normalizeString(value) {
618
+ if (typeof value !== "string")
619
+ return null;
620
+ const normalized = value.replace(/\s+/g, " ").trim();
621
+ return normalized.length > 0 ? normalized : null;
622
+ }
623
+ function isGenericRunFailure(value) {
624
+ const text = normalizeString(value);
625
+ return Boolean(text && /^Task run failed \([^)]*\)$/i.test(text));
626
+ }
627
+ function appendCandidate(candidates, value) {
628
+ const text = normalizeString(value);
629
+ if (text && !candidates.includes(text))
630
+ candidates.push(text);
631
+ }
632
+ function categorizeUsefulRunError(lines) {
633
+ const taskSourceFailure = lines.find((line) => /failed to update task source/i.test(line));
634
+ if (taskSourceFailure)
635
+ return `Task source update failed: ${taskSourceFailure}`;
636
+ const moduleFailure = lines.find((line) => /cannot find module/i.test(line));
637
+ if (moduleFailure)
638
+ return `Runtime module resolution failed: ${moduleFailure}`;
639
+ const providerFailure = lines.find((line) => /no api key found|unauthorized|authentication failed|invalid api key/i.test(line));
640
+ if (providerFailure)
641
+ return `Provider authentication failed: ${providerFailure}`;
642
+ return null;
643
+ }
644
+ function summarizeUsefulRunError(projection) {
645
+ if (projection.status !== "failed")
646
+ return null;
647
+ const candidates = [];
648
+ for (const anomaly of projection.anomalies) {
649
+ appendCandidate(candidates, `Journal anomaly (${anomaly.kind}): ${anomaly.detail}`);
650
+ }
651
+ for (const phase of projection.closeoutPhases) {
652
+ if (phase.outcome === "failed")
653
+ appendCandidate(candidates, phase.detail);
654
+ }
655
+ for (const entry of projection.statusHistory) {
656
+ appendCandidate(candidates, entry.reason);
657
+ }
658
+ appendCandidate(candidates, projection.record.statusDetail);
659
+ appendCandidate(candidates, projection.record.errorText);
660
+ const nonGeneric = candidates.filter((candidate) => !isGenericRunFailure(candidate));
661
+ return categorizeUsefulRunError(nonGeneric) ?? nonGeneric.at(-1) ?? normalizeString(projection.record.errorText);
662
+ }
663
+
664
+ // packages/cli/src/commands/_run-projection.ts
665
+ var EMPTY_PROJECTION = foldRunSessionEntries([], "");
666
+ function stringOrNull(value) {
667
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
668
+ }
669
+ function newestSessionFile(sessionDir) {
670
+ if (!sessionDir || !existsSync2(sessionDir))
671
+ return null;
672
+ try {
673
+ const files = readdirSync2(sessionDir).filter((file) => file.endsWith(".jsonl")).sort().reverse();
674
+ return files[0] ? resolve2(sessionDir, files[0]) : null;
675
+ } catch {
676
+ return null;
677
+ }
678
+ }
679
+ function readSessionRunEntries(sessionPath) {
680
+ if (!sessionPath || !existsSync2(sessionPath))
681
+ return [];
682
+ let raw;
683
+ try {
684
+ raw = readFileSync2(sessionPath, "utf8");
685
+ } catch {
686
+ return [];
687
+ }
688
+ const entries = [];
689
+ for (const line of raw.split(`
690
+ `)) {
691
+ if (!line.trim())
692
+ continue;
693
+ let parsed;
694
+ try {
695
+ parsed = JSON.parse(line);
696
+ } catch {
697
+ continue;
698
+ }
699
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
700
+ continue;
701
+ entries.push({
702
+ type: "type" in parsed && typeof parsed.type === "string" ? parsed.type : "",
703
+ ..."customType" in parsed && typeof parsed.customType === "string" ? { customType: parsed.customType } : {},
704
+ ..."data" in parsed ? { data: parsed.data } : {}
705
+ });
706
+ }
707
+ return entries;
708
+ }
709
+ function recordFromProjection(runId, projection, presence) {
710
+ const record = projection.record;
711
+ const status = projection.status ?? (presence.stale ? "stale" : presence.live ? "running" : "unknown");
712
+ const taskId = stringOrNull(record.taskId) ?? presence.fallbackTaskId ?? null;
713
+ const title = stringOrNull(record.title) ?? stringOrNull(presence.title) ?? (taskId ? `Run ${taskId}` : `Rig run ${runId}`);
714
+ return {
715
+ runId,
716
+ taskId,
717
+ title,
718
+ status,
719
+ source: presence.source,
720
+ live: presence.live,
721
+ stale: presence.stale,
722
+ startedAt: stringOrNull(record.startedAt) ?? presence.startedAt ?? null,
723
+ updatedAt: stringOrNull(record.updatedAt) ?? presence.updatedAt ?? projection.lastEventAt ?? null,
724
+ completedAt: stringOrNull(record.completedAt),
725
+ joinLink: presence.joinLink ?? null,
726
+ webLink: presence.webLink ?? null,
727
+ relayUrl: presence.relayUrl ?? null,
728
+ sessionPath: stringOrNull(record.sessionPath) ?? presence.sessionPath ?? null,
729
+ prUrl: stringOrNull(record.prUrl),
730
+ worktreePath: stringOrNull(record.worktreePath),
731
+ pendingApprovals: projection.pendingApprovals.length,
732
+ pendingInputs: projection.pendingUserInputs.length,
733
+ steeringCount: projection.steeringCount,
734
+ stallCount: projection.stallCount,
735
+ errorSummary: summarizeUsefulRunError(projection),
736
+ projection
737
+ };
738
+ }
739
+ function recordFromLiveCollab(collab) {
740
+ const runId = collab.sessionId;
741
+ const sessionPath = stringOrNull(collab.sessionPath);
742
+ const projection = sessionPath ? foldRunSessionEntries(readSessionRunEntries(sessionPath), runId) : EMPTY_PROJECTION;
743
+ return recordFromProjection(runId, projection, {
744
+ source: sessionPath ? "local" : "remote",
745
+ live: !collab.stale,
746
+ stale: Boolean(collab.stale),
747
+ title: collab.title,
748
+ joinLink: stringOrNull(collab.joinLink),
749
+ webLink: stringOrNull(collab.webLink),
750
+ relayUrl: stringOrNull(collab.relayUrl),
751
+ sessionPath,
752
+ startedAt: stringOrNull(collab.startedAt),
753
+ updatedAt: stringOrNull(collab.updatedAt)
754
+ });
755
+ }
756
+ function recordFromRuntime(runtime) {
757
+ const sessionPath = newestSessionFile(runtime.sessionDir ?? "");
758
+ if (!sessionPath)
759
+ return null;
760
+ const projection = foldRunSessionEntries(readSessionRunEntries(sessionPath), runtime.id);
761
+ if (projection.lastSeq === 0)
762
+ return null;
763
+ return recordFromProjection(sessionIdFromSessionFile2(sessionPath) ?? runtime.id, projection, {
764
+ source: "local",
765
+ live: false,
766
+ stale: false,
767
+ sessionPath,
768
+ fallbackTaskId: stringOrNull(runtime.taskId)
769
+ });
770
+ }
771
+ function sortByRecency(records) {
772
+ return [...records].sort((a, b) => {
773
+ const at = Date.parse(b.updatedAt ?? b.startedAt ?? "") || 0;
774
+ const bt = Date.parse(a.updatedAt ?? a.startedAt ?? "") || 0;
775
+ return at - bt;
776
+ });
777
+ }
778
+ async function listRunProjections(projectRoot, filter = {}) {
779
+ const byRunId = new Map;
780
+ let live = [];
781
+ try {
782
+ live = await listActiveRunCollab(filter);
783
+ } catch {
784
+ live = [];
785
+ }
786
+ for (const collab of live) {
787
+ const record = recordFromLiveCollab(collab);
788
+ byRunId.set(record.runId, record);
789
+ }
790
+ let runtimes = [];
791
+ try {
792
+ runtimes = await listAgentRuntimes2(projectRoot);
793
+ } catch {
794
+ runtimes = [];
795
+ }
796
+ for (const runtime of runtimes) {
797
+ const record = recordFromRuntime(runtime);
798
+ if (!record)
799
+ continue;
800
+ const sessionId = sessionIdFromSessionFile2(record.sessionPath);
801
+ const liveMatch = sessionId ? byRunId.get(sessionId) : undefined;
802
+ if (liveMatch) {
803
+ if (liveMatch.source === "remote" || !liveMatch.sessionPath) {
804
+ byRunId.set(liveMatch.runId, { ...liveMatch, sessionPath: record.sessionPath, source: "local" });
805
+ }
806
+ continue;
807
+ }
808
+ if (byRunId.has(runtime.id))
809
+ continue;
810
+ byRunId.set(record.runId, record);
811
+ }
812
+ return sortByRecency([...byRunId.values()]);
813
+ }
814
+
952
815
  // packages/cli/src/commands/stats.ts
953
816
  var DAY_MS = 24 * 60 * 60 * 1000;
954
817
  function parseSinceOption(value, now = new Date) {
@@ -957,95 +820,160 @@ function parseSinceOption(value, now = new Date) {
957
820
  const trimmed = value.trim();
958
821
  const relative = trimmed.match(/^(\d+)d$/i);
959
822
  if (relative?.[1]) {
960
- return new Date(now.getTime() - Number.parseInt(relative[1], 10) * DAY_MS);
823
+ const days = Number.parseInt(relative[1], 10);
824
+ if (!Number.isFinite(days) || days <= 0) {
825
+ throw new CliError(`Invalid --since value: ${value}`, 2, {
826
+ hint: "Use an ISO date or a relative window like `7d`."
827
+ });
828
+ }
829
+ return new Date(now.getTime() - days * DAY_MS);
961
830
  }
962
831
  const parsed = Date.parse(trimmed);
963
832
  if (!Number.isFinite(parsed)) {
964
- throw new CliError(`Invalid --since value: ${trimmed}. Use <n>d (e.g. 7d, 30d) or an ISO date (e.g. 2026-06-01).`, 2, { hint: "Re-run with a valid window, e.g. `rig stats --since 7d` or `rig stats --since 2026-06-01`." });
833
+ throw new CliError(`Invalid --since value: ${value}`, 2, {
834
+ hint: "Use an ISO date or a relative window like `7d`."
835
+ });
965
836
  }
966
837
  return new Date(parsed);
967
838
  }
839
+ function parseTimestamp(value) {
840
+ if (!value)
841
+ return null;
842
+ const parsed = Date.parse(value);
843
+ return Number.isFinite(parsed) ? parsed : null;
844
+ }
845
+ function median(values) {
846
+ if (values.length === 0)
847
+ return null;
848
+ const sorted = [...values].sort((left, right) => left - right);
849
+ const middle = Math.floor(sorted.length / 2);
850
+ return sorted.length % 2 === 1 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
851
+ }
852
+ function rate(part, total) {
853
+ return total === 0 ? null : part / total;
854
+ }
855
+ function isNeedsAttention(run) {
856
+ return run.status === "needs-attention" || run.pendingApprovals + run.pendingInputs > 0 || run.stallCount > 0;
857
+ }
858
+ function completedDuration(run) {
859
+ if (run.status !== "completed")
860
+ return null;
861
+ const startedAt = parseTimestamp(run.startedAt);
862
+ const completedAt = parseTimestamp(run.completedAt);
863
+ if (startedAt === null || completedAt === null || completedAt < startedAt)
864
+ return null;
865
+ return completedAt - startedAt;
866
+ }
867
+ async function computeRigStats(projectRoot, options = {}) {
868
+ const sinceMs = options.since ? options.since.getTime() : null;
869
+ const allRuns = await (options.listRuns ?? listRunProjections)(projectRoot);
870
+ const runs = allRuns.filter((run) => {
871
+ if (sinceMs === null)
872
+ return true;
873
+ const startedAt = parseTimestamp(run.startedAt);
874
+ return startedAt !== null && startedAt >= sinceMs;
875
+ });
876
+ const statusCounts = {};
877
+ const completionDurations = [];
878
+ let completedRuns = 0;
879
+ let failedRuns = 0;
880
+ let needsAttentionRuns = 0;
881
+ let steeringTotal = 0;
882
+ let stallTotal = 0;
883
+ let approvalsPending = 0;
884
+ for (const run of runs) {
885
+ const status = run.status || "unknown";
886
+ statusCounts[status] = (statusCounts[status] ?? 0) + 1;
887
+ if (status === "completed")
888
+ completedRuns += 1;
889
+ if (status === "failed")
890
+ failedRuns += 1;
891
+ if (isNeedsAttention(run))
892
+ needsAttentionRuns += 1;
893
+ const duration = completedDuration(run);
894
+ if (duration !== null)
895
+ completionDurations.push(duration);
896
+ steeringTotal += run.steeringCount;
897
+ stallTotal += run.stallCount;
898
+ approvalsPending += run.pendingApprovals;
899
+ }
900
+ const totalRuns = runs.length;
901
+ return {
902
+ since: options.since ? options.since.toISOString() : null,
903
+ totalRuns,
904
+ statusCounts,
905
+ completedRuns,
906
+ failedRuns,
907
+ needsAttentionRuns,
908
+ completionRate: rate(completedRuns, totalRuns),
909
+ failureRate: rate(failedRuns, totalRuns),
910
+ needsAttentionRate: rate(needsAttentionRuns, totalRuns),
911
+ medianCompletionMs: median(completionDurations),
912
+ steeringTotal,
913
+ steeringPerRun: totalRuns === 0 ? null : steeringTotal / totalRuns,
914
+ stallTotal,
915
+ approvalsRequested: approvalsPending,
916
+ approvalsApproved: 0,
917
+ approvalsRejected: 0,
918
+ approvalsPending
919
+ };
920
+ }
968
921
  function formatPercent(value) {
969
- if (value === null)
970
- return "\u2014";
971
- return `${(value * 100).toFixed(1)}%`;
922
+ return value === null ? "\u2014" : `${Math.round(value * 100)}%`;
972
923
  }
973
924
  function formatDuration(ms) {
974
925
  if (ms === null)
975
926
  return "\u2014";
976
- if (ms < 1000)
977
- return `${Math.round(ms)}ms`;
978
- const seconds = ms / 1000;
927
+ const seconds = Math.round(ms / 1000);
979
928
  if (seconds < 60)
980
- return `${seconds.toFixed(0)}s`;
981
- const minutes = seconds / 60;
929
+ return `${seconds}s`;
930
+ const minutes = Math.round(ms / 60000);
982
931
  if (minutes < 60)
983
- return `${Math.floor(minutes)}m ${Math.round(seconds % 60)}s`;
932
+ return `${minutes}m`;
984
933
  const hours = Math.floor(minutes / 60);
985
- return `${hours}h ${Math.round(minutes % 60)}m`;
934
+ const remainder = minutes % 60;
935
+ return remainder === 0 ? `${hours}h` : `${hours}h ${remainder}m`;
986
936
  }
987
- function formatStatsReport(stats) {
988
- const window = stats.since ? `since ${stats.since}` : "all time";
989
- const rows = [
990
- ["runs", String(stats.totalRuns)],
937
+ function formatStatsRows(stats) {
938
+ return [
939
+ ["window", stats.since ? `since ${stats.since}` : "all runs"],
940
+ ["total runs", stats.totalRuns],
991
941
  ["completed", `${stats.completedRuns} (${formatPercent(stats.completionRate)})`],
992
942
  ["failed", `${stats.failedRuns} (${formatPercent(stats.failureRate)})`],
993
- ["needs attn", `${stats.needsAttentionRuns} (${formatPercent(stats.needsAttentionRate)})`],
994
- ["median time", formatDuration(stats.medianCompletionMs)],
995
- ["steering", `${stats.steeringTotal} total \xB7 ${stats.steeringPerRun === null ? "\u2014" : stats.steeringPerRun.toFixed(2)}/run`],
996
- ["stalls", String(stats.stallTotal)],
997
- ["approvals", `${stats.approvalsRequested} requested \xB7 ${stats.approvalsApproved} approved \xB7 ${stats.approvalsRejected} rejected \xB7 ${stats.approvalsPending} pending`]
943
+ ["needs attention", `${stats.needsAttentionRuns} (${formatPercent(stats.needsAttentionRate)})`],
944
+ ["median run time", formatDuration(stats.medianCompletionMs)],
945
+ ["steering", stats.steeringTotal],
946
+ ["stalls", stats.stallTotal],
947
+ ["pending approvals", stats.approvalsPending]
998
948
  ];
999
- const keyWidth = Math.max(...rows.map(([key]) => key.length));
1000
- const lines = [
1001
- formatSection("Fleet stats", window),
1002
- ...rows.map(([key, value]) => `${pc4.dim("\u2502")} ${pc4.dim(key.padEnd(keyWidth + 2))} ${value}`)
1003
- ];
1004
- const otherStatuses = Object.entries(stats.statusCounts).filter(([status]) => !["completed", "failed", "needs-attention"].includes(status)).sort(([, left], [, right]) => right - left);
1005
- if (otherStatuses.length > 0) {
1006
- lines.push(`${pc4.dim("\u2502")} ${pc4.dim("other".padEnd(keyWidth + 2))} ${otherStatuses.map(([status, count]) => `${status}:${count}`).join(" ")}`);
1007
- }
1008
- lines.push("", ...formatNextSteps([
1009
- "Inspect a run: `rig run list` then `rig run show <run-id>`",
1010
- "Narrow the window: `rig stats --since 7d`"
1011
- ]));
1012
- return lines.join(`
1013
- `);
1014
949
  }
1015
- async function executeStats(context, args) {
1016
- const [first, ...rest] = args;
1017
- const isImplicitShow = first === undefined || first.startsWith("-");
1018
- const command = isImplicitShow ? "show" : first;
1019
- const pending = isImplicitShow ? args : rest;
1020
- switch (command) {
1021
- case "show": {
1022
- const sinceResult = takeOption(pending, "--since");
1023
- requireNoExtraArgs(sinceResult.rest, "rig stats [show] [--since <7d|30d|ISO date>]");
1024
- const since = parseSinceOption(sinceResult.value);
1025
- const remoteStats = isRemoteConnectionSelected(context.projectRoot);
1026
- const stats = await withSpinner(remoteStats ? "Computing fleet stats on the server\u2026" : "Scanning local run state\u2026", async () => remoteStats ? await requestServerJson(context, `/api/stats${since ? `?since=${encodeURIComponent(since.toISOString())}` : ""}`) : computeRigStats(context.projectRoot, { since }), { outputMode: context.outputMode });
1027
- if (context.outputMode === "text") {
1028
- printFormattedOutput(formatStatsReport(stats));
1029
- }
1030
- return {
1031
- ok: true,
1032
- group: "stats",
1033
- command: "show",
1034
- details: stats
1035
- };
1036
- }
1037
- case "help": {
1038
- if (context.outputMode === "text")
1039
- printGroupHelpDocument("stats");
1040
- return { ok: true, group: "stats", command: "help" };
1041
- }
1042
- default:
1043
- throw new CliError(`Unknown stats command: ${command}. Use \`rig stats\` or \`rig stats show [--since <7d|30d|ISO date>]\`.`, 2, { hint: "Run `rig stats --since 7d` for the last week, or `rig stats --help` for usage." });
950
+ async function executeStats(context, args, deps = {}) {
951
+ const [first = "show", ...rest] = args;
952
+ const command = first.startsWith("-") ? "show" : first;
953
+ const commandArgs = first.startsWith("-") ? args : rest;
954
+ if (command === "help") {
955
+ printGroupHelpDocument("stats");
956
+ return { ok: true, group: "stats", command };
957
+ }
958
+ if (command !== "show") {
959
+ throw new CliError(`Unknown stats command: ${command}`, 1, {
960
+ hint: "Run `rig stats --help` \u2014 the default command is `show`."
961
+ });
962
+ }
963
+ const sinceResult = takeOption(commandArgs, "--since");
964
+ requireNoExtraArgs(sinceResult.rest, "rig stats [show] [--since <7d|30d|ISO date>]");
965
+ const details = await computeRigStats(context.projectRoot, {
966
+ since: parseSinceOption(sinceResult.value),
967
+ listRuns: deps.listRuns
968
+ });
969
+ if (context.outputMode === "text") {
970
+ printFormattedOutput(formatStatsTable(formatStatsRows(details)));
1044
971
  }
972
+ return { ok: true, group: "stats", command, details };
1045
973
  }
1046
974
  export {
1047
975
  parseSinceOption,
1048
- formatStatsReport,
976
+ formatStatsRows,
1049
977
  executeStats,
1050
978
  computeRigStats
1051
979
  };