@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.
- package/README.md +18 -19
- package/dist/bin/build-rig-binaries.js +22 -10
- package/dist/bin/rig.d.ts +71 -1
- package/dist/bin/rig.js +15078 -11169
- package/dist/config/rig-default-config.yml +5 -0
- package/dist/src/app/drone-ui.d.ts +11 -14
- package/dist/src/app/drone-ui.js +70 -86
- package/dist/src/commands/_async-ui.d.ts +1 -4
- package/dist/src/commands/_async-ui.js +9 -111
- package/dist/src/commands/_cli-format.d.ts +16 -9
- package/dist/src/commands/_cli-format.js +167 -295
- package/dist/src/commands/_connection-state.d.ts +11 -1
- package/dist/src/commands/_connection-state.js +50 -5
- package/dist/src/commands/_doctor-checks.d.ts +0 -6
- package/dist/src/commands/_doctor-checks.js +79 -382
- package/dist/src/commands/_help-catalog.d.ts +1 -1
- package/dist/src/commands/_help-catalog.js +217 -157
- package/dist/src/commands/_inprocess-services.d.ts +33 -0
- package/dist/src/commands/_inprocess-services.js +102 -0
- package/dist/src/commands/_json-output.js +4 -0
- package/dist/src/commands/_lazy-reconcile.d.ts +34 -0
- package/dist/src/commands/_lazy-reconcile.js +102 -0
- package/dist/src/commands/_paths.js +1 -1
- package/dist/src/commands/_pi-frontend.d.ts +18 -10
- package/dist/src/commands/_pi-frontend.js +37 -715
- package/dist/src/commands/_pi-install.js +18 -36
- package/dist/src/commands/_policy.d.ts +1 -1
- package/dist/src/commands/_policy.js +56 -15
- package/dist/src/commands/_run-bridge.d.ts +114 -0
- package/dist/src/commands/_run-bridge.js +387 -0
- package/dist/src/commands/_run-diagnostics.d.ts +9 -0
- package/dist/src/commands/_run-diagnostics.js +51 -0
- package/dist/src/commands/_run-driver-helpers.d.ts +8 -81
- package/dist/src/commands/_run-driver-helpers.js +79 -283
- package/dist/src/commands/_run-projection.d.ts +50 -0
- package/dist/src/commands/_run-projection.js +349 -0
- package/dist/src/commands/_run-subcommands.d.ts +3 -0
- package/dist/src/commands/_run-subcommands.js +31 -0
- package/dist/src/commands/_spinner.js +1 -1
- package/dist/src/commands/agent.d.ts +1 -1
- package/dist/src/commands/agent.js +8559 -239
- package/dist/src/commands/dist.d.ts +1 -1
- package/dist/src/commands/dist.js +27 -19
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +93 -475
- package/dist/src/commands/github.d.ts +1 -1
- package/dist/src/commands/github.js +113 -387
- package/dist/src/commands/inbox.d.ts +22 -24
- package/dist/src/commands/inbox.js +420 -691
- package/dist/src/commands/init.d.ts +6 -16
- package/dist/src/commands/init.js +334 -971
- package/dist/src/commands/inspect.d.ts +19 -2
- package/dist/src/commands/inspect.js +644 -610
- package/dist/src/commands/pi.d.ts +1 -1
- package/dist/src/commands/plugin.d.ts +1 -1
- package/dist/src/commands/plugin.js +486 -7
- package/dist/src/commands/profile-and-review.d.ts +1 -1
- package/dist/src/commands/profile-and-review.js +94 -56
- package/dist/src/commands/queue.js +1 -21
- package/dist/src/commands/remote.d.ts +1 -1
- package/dist/src/commands/remote.js +837 -14
- package/dist/src/commands/repo-git-harness.d.ts +1 -1
- package/dist/src/commands/repo-git-harness.js +57 -14
- package/dist/src/commands/run.d.ts +20 -2
- package/dist/src/commands/run.js +17579 -1759
- package/dist/src/commands/server.d.ts +2 -6
- package/dist/src/commands/server.js +141 -723
- package/dist/src/commands/setup.d.ts +1 -1
- package/dist/src/commands/setup.js +102 -484
- package/dist/src/commands/stats.d.ts +13 -10
- package/dist/src/commands/stats.js +689 -761
- package/dist/src/commands/task-run-driver.d.ts +50 -88
- package/dist/src/commands/task-run-driver.js +116 -2717
- package/dist/src/commands/task.d.ts +34 -13
- package/dist/src/commands/task.js +668 -2523
- package/dist/src/commands/test.d.ts +1 -1
- package/dist/src/commands/triage.d.ts +11 -0
- package/dist/src/commands/triage.js +227 -0
- package/dist/src/commands/workspace.d.ts +1 -1
- package/dist/src/commands.d.ts +0 -16
- package/dist/src/commands.js +16657 -12250
- package/dist/src/index.js +16528 -12497
- package/dist/src/launcher.js +4 -0
- package/dist/src/operator-cli.d.ts +2 -0
- package/dist/src/operator-cli.js +17837 -0
- package/dist/src/operator-entry.d.ts +1 -0
- package/dist/src/operator-entry.js +3 -0
- package/package.json +18 -12
- package/dist/src/app/board.d.ts +0 -23
- package/dist/src/app/board.js +0 -1786
- package/dist/src/app/theme.d.ts +0 -47
- package/dist/src/app/theme.js +0 -150
- package/dist/src/commands/_authority-runs.d.ts +0 -22
- package/dist/src/commands/_authority-runs.js +0 -110
- package/dist/src/commands/_operator-surface.d.ts +0 -34
- package/dist/src/commands/_operator-surface.js +0 -220
- package/dist/src/commands/_operator-view.d.ts +0 -30
- package/dist/src/commands/_operator-view.js +0 -1070
- package/dist/src/commands/_preflight.d.ts +0 -22
- package/dist/src/commands/_preflight.js +0 -540
- package/dist/src/commands/_run-replay.d.ts +0 -24
- package/dist/src/commands/_run-replay.js +0 -142
- package/dist/src/commands/_server-client.d.ts +0 -186
- package/dist/src/commands/_server-client.js +0 -681
- package/dist/src/commands/_snapshot-upload.d.ts +0 -39
- package/dist/src/commands/_snapshot-upload.js +0 -455
- package/dist/src/commands/_task-picker.d.ts +0 -9
- package/dist/src/commands/_task-picker.js +0 -201
- package/dist/src/commands/browser.d.ts +0 -65
- package/dist/src/commands/browser.js +0 -1173
- package/dist/src/commands/connect.d.ts +0 -7
- package/dist/src/commands/connect.js +0 -419
- package/dist/src/commands/inspector.d.ts +0 -3
- package/dist/src/commands/inspector.js +0 -263
- package/dist/src/commands/task-report-bug.d.ts +0 -19
- package/dist/src/commands/task-report-bug.js +0 -1281
- package/dist/src/report-bug.d.ts +0 -44
- 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
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
return;
|
|
522
|
-
|
|
523
|
-
|
|
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
|
|
526
|
-
|
|
58
|
+
function printFormattedOutput(message) {
|
|
59
|
+
console.log(message);
|
|
527
60
|
}
|
|
528
|
-
function
|
|
529
|
-
|
|
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
|
|
67
|
+
import pc2 from "picocolors";
|
|
593
68
|
var TOP_LEVEL_SECTIONS = [
|
|
594
69
|
{
|
|
595
|
-
title: "
|
|
596
|
-
subtitle: "
|
|
70
|
+
title: "Open Rig",
|
|
71
|
+
subtitle: "normal UX \u2014 bare rig opens the bundled Rig Cockpit inside OMP",
|
|
597
72
|
commands: [
|
|
598
|
-
{ command: "rig
|
|
599
|
-
{ command: "
|
|
600
|
-
{ command: "
|
|
601
|
-
{ command: "
|
|
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: "
|
|
608
|
-
subtitle: "
|
|
80
|
+
title: "Server",
|
|
81
|
+
subtitle: "select execution placement before reading or dispatching work",
|
|
609
82
|
commands: [
|
|
610
|
-
{ command: "
|
|
611
|
-
{ command: "
|
|
612
|
-
{ command: "
|
|
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: "
|
|
617
|
-
subtitle: "
|
|
89
|
+
title: "Tasks",
|
|
90
|
+
subtitle: "configured task source on the selected target feeds task detail",
|
|
618
91
|
commands: [
|
|
619
|
-
{ command: "
|
|
620
|
-
{ command: "
|
|
621
|
-
{ command: "
|
|
622
|
-
{ command: "rig
|
|
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: "
|
|
628
|
-
subtitle: "
|
|
99
|
+
title: "Runs",
|
|
100
|
+
subtitle: "dispatched Rig runs attach through collaborative OMP sessions",
|
|
629
101
|
commands: [
|
|
630
|
-
{ command: "
|
|
631
|
-
{ command: "rig
|
|
632
|
-
{ command: "
|
|
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: "
|
|
637
|
-
subtitle: "
|
|
108
|
+
title: "Inbox + Doctor",
|
|
109
|
+
subtitle: "approvals, user prompts, and health checks live inside Rig/OMP",
|
|
638
110
|
commands: [
|
|
639
|
-
{ command: "
|
|
640
|
-
{ command: "rig
|
|
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
|
|
118
|
+
subtitle: "plugins and OMP extensions shape the worker environment",
|
|
646
119
|
commands: [
|
|
647
|
-
{ command: "rig
|
|
648
|
-
{ 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: "
|
|
656
|
-
usage: ["rig server <status|list|add|
|
|
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
|
|
659
|
-
{ command: "
|
|
660
|
-
{ command: "
|
|
661
|
-
{ command: "
|
|
662
|
-
{ command: "
|
|
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
|
|
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: [
|
|
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,
|
|
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
|
|
680
|
-
{ command: "next [filters]", description: "Render the next
|
|
681
|
-
{ command: "show <id>|--task <id> [--raw]", description: "Show a
|
|
682
|
-
{ command: "run [#<issue>|<task-id>|--
|
|
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
|
|
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: "
|
|
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
|
|
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: ["
|
|
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
|
|
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
|
|
706
|
-
{ command: "status", description: "Render active
|
|
707
|
-
{ command: "
|
|
708
|
-
{ command: "
|
|
709
|
-
{ command: "
|
|
710
|
-
{ command: "
|
|
711
|
-
{ command: "
|
|
712
|
-
{ command: "
|
|
713
|
-
{ command: "
|
|
714
|
-
{ command: "
|
|
715
|
-
{ command: "
|
|
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
|
|
719
|
-
"rig run
|
|
720
|
-
"rig run
|
|
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
|
-
"
|
|
726
|
-
"
|
|
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|
|
|
206
|
+
usage: ["rig inbox <approvals|inputs|approve|respond> [options]"],
|
|
736
207
|
commands: [
|
|
737
|
-
{ command: "approvals [--run <id>] [--task <id>]", description: "List pending
|
|
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
|
|
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: ["
|
|
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: "
|
|
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 --
|
|
228
|
+
"rig stats --json"
|
|
760
229
|
],
|
|
761
|
-
next: ["Inspect outliers with `rig run list` and `rig run show <run-id>`."
|
|
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,
|
|
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
|
|
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: "
|
|
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: "
|
|
805
|
-
{ command: "init --demo", description: "
|
|
806
|
-
{ command: "init --yes", description: "
|
|
807
|
-
{ command: "init --server remote --remote-url <url>", description: "
|
|
808
|
-
{ command: "init --repair", description: "
|
|
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 --
|
|
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: ["
|
|
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: "
|
|
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: "
|
|
824
|
-
{ command: "check", description: "
|
|
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
|
|
827
|
-
next: ["
|
|
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
|
|
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: "
|
|
835
|
-
{ command: "auth import-gh", description: "
|
|
836
|
-
{ command: "auth token --token <token>", description: "
|
|
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
|
|
839
|
-
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
|
-
{
|
|
844
|
-
|
|
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
|
|
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: ["
|
|
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
|
-
{
|
|
882
|
-
|
|
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
|
-
{
|
|
885
|
-
|
|
886
|
-
|
|
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
|
|
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 `${
|
|
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
|
-
|
|
437
|
+
pc2.bold("Usage"),
|
|
908
438
|
...group.usage.map((line) => ` ${line}`),
|
|
909
439
|
"",
|
|
910
|
-
|
|
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("",
|
|
444
|
+
lines.push("", pc2.bold("Examples"), ...group.examples.map((line) => ` ${pc2.dim("$")} ${line}`));
|
|
915
445
|
}
|
|
916
446
|
if (group.next?.length) {
|
|
917
|
-
lines.push("",
|
|
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("",
|
|
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")} ${
|
|
928
|
-
|
|
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
|
-
`${
|
|
461
|
+
`${pc2.bold(pc2.magenta(`\u25C7 ${section.title}`))} \u2014 ${pc2.dim(section.subtitle)}`,
|
|
932
462
|
renderCommandBlock(section.commands),
|
|
933
463
|
""
|
|
934
464
|
]),
|
|
935
|
-
|
|
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
|
-
|
|
938
|
-
commandLine("--
|
|
939
|
-
commandLine("--json", "
|
|
940
|
-
commandLine("--dry-run", "
|
|
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
|
-
|
|
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: ${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
981
|
-
const minutes =
|
|
929
|
+
return `${seconds}s`;
|
|
930
|
+
const minutes = Math.round(ms / 60000);
|
|
982
931
|
if (minutes < 60)
|
|
983
|
-
return `${
|
|
932
|
+
return `${minutes}m`;
|
|
984
933
|
const hours = Math.floor(minutes / 60);
|
|
985
|
-
|
|
934
|
+
const remainder = minutes % 60;
|
|
935
|
+
return remainder === 0 ? `${hours}h` : `${hours}h ${remainder}m`;
|
|
986
936
|
}
|
|
987
|
-
function
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
["runs",
|
|
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
|
|
994
|
-
["median time", formatDuration(stats.medianCompletionMs)],
|
|
995
|
-
["steering",
|
|
996
|
-
["stalls",
|
|
997
|
-
["approvals",
|
|
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
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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
|
-
|
|
976
|
+
formatStatsRows,
|
|
1049
977
|
executeStats,
|
|
1050
978
|
computeRigStats
|
|
1051
979
|
};
|