@h-rig/cli 0.0.6-alpha.6 → 0.0.6-alpha.61
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 +1 -1
- package/dist/bin/rig.js +5033 -1895
- package/dist/src/commands/_authority-runs.js +2 -3
- package/dist/src/commands/_cli-format.js +369 -0
- package/dist/src/commands/_connection-state.js +12 -6
- package/dist/src/commands/_doctor-checks.js +79 -34
- package/dist/src/commands/_help-catalog.js +446 -0
- package/dist/src/commands/_operator-surface.js +220 -0
- package/dist/src/commands/_operator-view.js +1124 -64
- package/dist/src/commands/_parsers.js +0 -2
- package/dist/src/commands/_pi-frontend.js +1080 -0
- package/dist/src/commands/_pi-install.js +4 -3
- package/dist/src/commands/_pi-remote-session.js +771 -0
- package/dist/src/commands/_pi-worker-bridge-extension.js +834 -0
- package/dist/src/commands/_policy.js +0 -2
- package/dist/src/commands/_preflight.js +98 -116
- package/dist/src/commands/_run-driver-helpers.js +46 -19
- package/dist/src/commands/_run-replay.js +142 -0
- package/dist/src/commands/_server-client.js +225 -48
- package/dist/src/commands/_snapshot-upload.js +74 -30
- package/dist/src/commands/_spinner.js +63 -0
- package/dist/src/commands/_task-picker.js +44 -16
- package/dist/src/commands/agent.js +10 -12
- package/dist/src/commands/browser.js +4 -6
- package/dist/src/commands/connect.js +134 -26
- package/dist/src/commands/dist.js +4 -6
- package/dist/src/commands/doctor.js +79 -34
- package/dist/src/commands/github.js +76 -32
- package/dist/src/commands/inbox.js +410 -31
- package/dist/src/commands/init.js +398 -90
- package/dist/src/commands/inspect.js +296 -23
- package/dist/src/commands/inspector.js +2 -4
- package/dist/src/commands/pi.js +168 -0
- package/dist/src/commands/plugin.js +81 -22
- package/dist/src/commands/profile-and-review.js +8 -10
- package/dist/src/commands/queue.js +2 -3
- package/dist/src/commands/remote.js +18 -20
- package/dist/src/commands/repo-git-harness.js +6 -8
- package/dist/src/commands/run.js +1600 -131
- package/dist/src/commands/server.js +281 -42
- package/dist/src/commands/setup.js +84 -45
- package/dist/src/commands/task-report-bug.js +5 -7
- package/dist/src/commands/task-run-driver.js +736 -93
- package/dist/src/commands/task.js +1863 -262
- package/dist/src/commands/test.js +3 -5
- package/dist/src/commands/workspace.js +4 -6
- package/dist/src/commands.js +5675 -2531
- package/dist/src/index.js +5654 -2519
- package/dist/src/launcher.js +5 -3
- package/dist/src/report-bug.js +3 -3
- package/dist/src/runner.js +5 -19
- package/package.json +7 -5
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/commands/inspect.ts
|
|
3
|
-
import { existsSync, readFileSync } from "fs";
|
|
4
|
-
import { resolve } from "path";
|
|
3
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
4
|
+
import { resolve as resolve3 } from "path";
|
|
5
5
|
|
|
6
6
|
// packages/cli/src/runner.ts
|
|
7
7
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
8
8
|
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
9
9
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
10
|
-
import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
11
|
-
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
12
10
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
13
11
|
import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
|
|
14
12
|
function takeOption(args, option) {
|
|
@@ -55,19 +53,294 @@ import {
|
|
|
55
53
|
import { changedFilesForTask } from "@rig/runtime/control-plane/native/task-ops";
|
|
56
54
|
import { resolveHarnessPaths, resolveMonorepoRoot, runCapture } from "@rig/runtime/control-plane/native/utils";
|
|
57
55
|
import { readTaskArtifactPreview } from "@rig/runtime/control-plane/native/workspace-ops";
|
|
56
|
+
|
|
57
|
+
// packages/cli/src/commands/_server-client.ts
|
|
58
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
59
|
+
import { resolve as resolve2 } from "path";
|
|
60
|
+
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
61
|
+
|
|
62
|
+
// packages/cli/src/commands/_connection-state.ts
|
|
63
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
64
|
+
import { homedir } from "os";
|
|
65
|
+
import { dirname, resolve } from "path";
|
|
66
|
+
function resolveGlobalConnectionsPath(env = process.env) {
|
|
67
|
+
const explicit = env.RIG_CONNECTIONS_FILE?.trim();
|
|
68
|
+
if (explicit)
|
|
69
|
+
return resolve(explicit);
|
|
70
|
+
const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
|
|
71
|
+
if (stateDir)
|
|
72
|
+
return resolve(stateDir, "connections.json");
|
|
73
|
+
return resolve(homedir(), ".rig", "connections.json");
|
|
74
|
+
}
|
|
75
|
+
function resolveRepoConnectionPath(projectRoot) {
|
|
76
|
+
return resolve(projectRoot, ".rig", "state", "connection.json");
|
|
77
|
+
}
|
|
78
|
+
function readJsonFile(path) {
|
|
79
|
+
if (!existsSync(path))
|
|
80
|
+
return null;
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function writeJsonFile(path, value) {
|
|
88
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
89
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
90
|
+
`, "utf8");
|
|
91
|
+
}
|
|
92
|
+
function normalizeConnection(value) {
|
|
93
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
94
|
+
return null;
|
|
95
|
+
const record = value;
|
|
96
|
+
if (record.kind === "local")
|
|
97
|
+
return { kind: "local", mode: "auto" };
|
|
98
|
+
if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
|
|
99
|
+
const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
|
|
100
|
+
return { kind: "remote", baseUrl };
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
function readGlobalConnections(options = {}) {
|
|
105
|
+
const path = resolveGlobalConnectionsPath(options.env ?? process.env);
|
|
106
|
+
const payload = readJsonFile(path);
|
|
107
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
108
|
+
return { connections: {} };
|
|
109
|
+
}
|
|
110
|
+
const rawConnections = payload.connections;
|
|
111
|
+
const connections = {};
|
|
112
|
+
if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
|
|
113
|
+
for (const [alias, raw] of Object.entries(rawConnections)) {
|
|
114
|
+
const connection = normalizeConnection(raw);
|
|
115
|
+
if (connection)
|
|
116
|
+
connections[alias] = connection;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { connections };
|
|
120
|
+
}
|
|
121
|
+
function readRepoConnection(projectRoot) {
|
|
122
|
+
const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
|
|
123
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
124
|
+
return null;
|
|
125
|
+
const record = payload;
|
|
126
|
+
const selected = typeof record.selected === "string" ? record.selected.trim() : "";
|
|
127
|
+
if (!selected)
|
|
128
|
+
return null;
|
|
129
|
+
return {
|
|
130
|
+
selected,
|
|
131
|
+
project: typeof record.project === "string" ? record.project : undefined,
|
|
132
|
+
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
133
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function writeRepoConnection(projectRoot, state) {
|
|
137
|
+
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
138
|
+
}
|
|
139
|
+
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
140
|
+
const repo = readRepoConnection(projectRoot);
|
|
141
|
+
if (!repo)
|
|
142
|
+
return null;
|
|
143
|
+
if (repo.selected === "local")
|
|
144
|
+
return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
|
|
145
|
+
const global = readGlobalConnections(options);
|
|
146
|
+
const connection = global.connections[repo.selected];
|
|
147
|
+
if (!connection) {
|
|
148
|
+
throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
149
|
+
}
|
|
150
|
+
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
151
|
+
}
|
|
152
|
+
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
153
|
+
const repo = readRepoConnection(projectRoot);
|
|
154
|
+
if (!repo)
|
|
155
|
+
return;
|
|
156
|
+
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// packages/cli/src/commands/_server-client.ts
|
|
160
|
+
var scopedGitHubBearerTokens = new Map;
|
|
161
|
+
function cleanToken(value) {
|
|
162
|
+
const trimmed = value?.trim();
|
|
163
|
+
return trimmed ? trimmed : null;
|
|
164
|
+
}
|
|
165
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
166
|
+
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
167
|
+
if (!existsSync2(path))
|
|
168
|
+
return null;
|
|
169
|
+
try {
|
|
170
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
171
|
+
return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
177
|
+
const scopedKey = resolve2(projectRoot);
|
|
178
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
179
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
180
|
+
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
181
|
+
if (privateSession)
|
|
182
|
+
return privateSession;
|
|
183
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
184
|
+
}
|
|
185
|
+
function readStoredGitHubAuthToken(projectRoot) {
|
|
186
|
+
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
187
|
+
if (!existsSync2(path))
|
|
188
|
+
return null;
|
|
189
|
+
try {
|
|
190
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
191
|
+
return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
|
|
192
|
+
} catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function readLocalConnectionFallbackToken(projectRoot) {
|
|
197
|
+
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
198
|
+
}
|
|
199
|
+
async function ensureServerForCli(projectRoot) {
|
|
200
|
+
try {
|
|
201
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
202
|
+
if (selected?.connection.kind === "remote") {
|
|
203
|
+
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
204
|
+
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
205
|
+
return {
|
|
206
|
+
baseUrl: selected.connection.baseUrl,
|
|
207
|
+
authToken,
|
|
208
|
+
connectionKind: "remote",
|
|
209
|
+
serverProjectRoot
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
213
|
+
return {
|
|
214
|
+
baseUrl: connection.baseUrl,
|
|
215
|
+
authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
|
|
216
|
+
connectionKind: "local",
|
|
217
|
+
serverProjectRoot: resolve2(projectRoot)
|
|
218
|
+
};
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (error instanceof Error) {
|
|
221
|
+
throw new CliError2(error.message, 1);
|
|
222
|
+
}
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
227
|
+
const repo = readRepoConnection(projectRoot);
|
|
228
|
+
const slug = repo?.project?.trim();
|
|
229
|
+
if (!slug)
|
|
230
|
+
return null;
|
|
231
|
+
try {
|
|
232
|
+
const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
|
|
233
|
+
headers: mergeHeaders(undefined, authToken)
|
|
234
|
+
});
|
|
235
|
+
if (!response.ok)
|
|
236
|
+
return null;
|
|
237
|
+
const payload = await response.json();
|
|
238
|
+
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
239
|
+
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
240
|
+
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
241
|
+
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
242
|
+
if (path)
|
|
243
|
+
writeRepoServerProjectRoot(projectRoot, path);
|
|
244
|
+
return path;
|
|
245
|
+
} catch {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function mergeHeaders(headers, authToken) {
|
|
250
|
+
const merged = new Headers(headers);
|
|
251
|
+
if (authToken) {
|
|
252
|
+
merged.set("authorization", `Bearer ${authToken}`);
|
|
253
|
+
}
|
|
254
|
+
return merged;
|
|
255
|
+
}
|
|
256
|
+
function diagnosticMessage(payload) {
|
|
257
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
258
|
+
return null;
|
|
259
|
+
const record = payload;
|
|
260
|
+
const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
|
|
261
|
+
const messages = diagnostics.flatMap((entry) => {
|
|
262
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
263
|
+
return [];
|
|
264
|
+
const diagnostic = entry;
|
|
265
|
+
const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
|
|
266
|
+
const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
|
|
267
|
+
return message ? [`${kind}: ${message}`] : [];
|
|
268
|
+
});
|
|
269
|
+
return messages.length > 0 ? messages.join("; ") : null;
|
|
270
|
+
}
|
|
271
|
+
async function requestServerJson(context, pathname, init = {}) {
|
|
272
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
273
|
+
const headers = mergeHeaders(init.headers, server.authToken);
|
|
274
|
+
if (server.serverProjectRoot)
|
|
275
|
+
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
276
|
+
const response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
277
|
+
...init,
|
|
278
|
+
headers
|
|
279
|
+
});
|
|
280
|
+
const text = await response.text();
|
|
281
|
+
const payload = text.trim().length > 0 ? (() => {
|
|
282
|
+
try {
|
|
283
|
+
return JSON.parse(text);
|
|
284
|
+
} catch {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
})() : null;
|
|
288
|
+
if (!response.ok) {
|
|
289
|
+
const diagnostics = diagnosticMessage(payload);
|
|
290
|
+
const detail = diagnostics ?? (text || response.statusText);
|
|
291
|
+
throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
|
|
292
|
+
}
|
|
293
|
+
return payload;
|
|
294
|
+
}
|
|
295
|
+
async function listRunsViaServer(context, options = {}) {
|
|
296
|
+
const url = new URL("http://rig.local/api/runs");
|
|
297
|
+
if (options.limit !== undefined)
|
|
298
|
+
url.searchParams.set("limit", String(options.limit));
|
|
299
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
300
|
+
const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
|
|
301
|
+
return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
|
|
302
|
+
}
|
|
303
|
+
async function getRunLogsViaServer(context, runId, options = {}) {
|
|
304
|
+
const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/logs`);
|
|
305
|
+
if (options.limit !== undefined)
|
|
306
|
+
url.searchParams.set("limit", String(options.limit));
|
|
307
|
+
if (options.cursor)
|
|
308
|
+
url.searchParams.set("cursor", options.cursor);
|
|
309
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
310
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// packages/cli/src/commands/inspect.ts
|
|
58
314
|
async function executeInspect(context, args) {
|
|
59
315
|
const [command = "failures", ...rest] = args;
|
|
60
316
|
switch (command) {
|
|
61
317
|
case "logs": {
|
|
62
318
|
const { value: task, rest: remaining } = takeOption(rest, "--task");
|
|
63
|
-
requireNoExtraArgs(remaining, "
|
|
64
|
-
const requiredTask = requireTask(task, "
|
|
319
|
+
requireNoExtraArgs(remaining, "rig inspect logs --task <task-id>");
|
|
320
|
+
const requiredTask = requireTask(task, "rig inspect logs --task <task-id>");
|
|
65
321
|
const latestRun = listAuthorityRuns(context.projectRoot).map((entry) => readAuthorityRun(context.projectRoot, entry.runId)).filter((run) => Boolean(run)).filter((run) => run.taskId === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
|
|
66
322
|
if (!latestRun) {
|
|
67
|
-
|
|
323
|
+
const serverRuns = await listRunsViaServer(context, { limit: 200 }).catch(() => []);
|
|
324
|
+
const serverRun = serverRuns.filter((run) => String(run.taskId ?? "") === requiredTask).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))[0];
|
|
325
|
+
if (!serverRun || typeof serverRun.runId !== "string") {
|
|
326
|
+
throw new CliError2(`No runs found for ${requiredTask} (local or on the selected server).`);
|
|
327
|
+
}
|
|
328
|
+
const page = await getRunLogsViaServer(context, serverRun.runId, { limit: 500 });
|
|
329
|
+
const entries = Array.isArray(page.entries) ? page.entries : [];
|
|
330
|
+
if (context.outputMode === "text") {
|
|
331
|
+
for (const entry of entries) {
|
|
332
|
+
const record = entry && typeof entry === "object" ? entry : {};
|
|
333
|
+
const title = String(record.title ?? "");
|
|
334
|
+
const detail = String(record.detail ?? "");
|
|
335
|
+
console.log([title, detail].filter(Boolean).join(" \u2014 "));
|
|
336
|
+
}
|
|
337
|
+
if (entries.length === 0)
|
|
338
|
+
console.log(`(no log entries for run ${serverRun.runId})`);
|
|
339
|
+
}
|
|
340
|
+
return { ok: true, group: "inspect", command, details: { task: requiredTask, runId: serverRun.runId, source: "server", entries } };
|
|
68
341
|
}
|
|
69
|
-
const logsPath =
|
|
70
|
-
if (!
|
|
342
|
+
const logsPath = resolve3(resolveAuthorityRunDir(context.projectRoot, latestRun.runId), "logs.jsonl");
|
|
343
|
+
if (!existsSync3(logsPath)) {
|
|
71
344
|
throw new CliError2(`No logs found for run ${latestRun.runId}.`);
|
|
72
345
|
}
|
|
73
346
|
await context.runCommand(["cat", logsPath]);
|
|
@@ -75,9 +348,9 @@ async function executeInspect(context, args) {
|
|
|
75
348
|
}
|
|
76
349
|
case "artifacts": {
|
|
77
350
|
const { value: task, rest: remaining } = takeOption(rest, "--task");
|
|
78
|
-
requireNoExtraArgs(remaining, "
|
|
79
|
-
const requiredTask = requireTask(task, "
|
|
80
|
-
const artifactRoot = resolveTaskArtifactDirs(context.projectRoot, requiredTask).find((path) =>
|
|
351
|
+
requireNoExtraArgs(remaining, "rig inspect artifacts --task <task-id>");
|
|
352
|
+
const requiredTask = requireTask(task, "rig inspect artifacts --task <task-id>");
|
|
353
|
+
const artifactRoot = resolveTaskArtifactDirs(context.projectRoot, requiredTask).find((path) => existsSync3(path));
|
|
81
354
|
if (!artifactRoot) {
|
|
82
355
|
throw new CliError2(`No artifacts found for ${requiredTask}.`);
|
|
83
356
|
}
|
|
@@ -90,8 +363,8 @@ async function executeInspect(context, args) {
|
|
|
90
363
|
previewPending = task.rest;
|
|
91
364
|
const file = takeOption(previewPending, "--file");
|
|
92
365
|
previewPending = file.rest;
|
|
93
|
-
requireNoExtraArgs(previewPending, "
|
|
94
|
-
const requiredTask = requireTask(task.value, "
|
|
366
|
+
requireNoExtraArgs(previewPending, "rig inspect artifact --task <task-id> --file <name>");
|
|
367
|
+
const requiredTask = requireTask(task.value, "rig inspect artifact --task <task-id> --file <name>");
|
|
95
368
|
if (!file.value) {
|
|
96
369
|
throw new CliError2("Missing --file for rig inspect artifact.");
|
|
97
370
|
}
|
|
@@ -120,7 +393,7 @@ async function executeInspect(context, args) {
|
|
|
120
393
|
}
|
|
121
394
|
case "diff": {
|
|
122
395
|
const { value: task, rest: remaining } = takeOption(rest, "--task");
|
|
123
|
-
requireNoExtraArgs(remaining, "
|
|
396
|
+
requireNoExtraArgs(remaining, "rig inspect diff [--task <task-id>]");
|
|
124
397
|
if (task) {
|
|
125
398
|
const files = changedFilesForTask(context.projectRoot, task, false);
|
|
126
399
|
for (const file of files) {
|
|
@@ -132,17 +405,17 @@ async function executeInspect(context, args) {
|
|
|
132
405
|
return { ok: true, group: "inspect", command, details: { task: task || null } };
|
|
133
406
|
}
|
|
134
407
|
case "failures": {
|
|
135
|
-
requireNoExtraArgs(rest, "
|
|
408
|
+
requireNoExtraArgs(rest, "rig inspect failures");
|
|
136
409
|
const failed = resolveHarnessPaths(context.projectRoot).failedApproachesPath;
|
|
137
|
-
if (!
|
|
410
|
+
if (!existsSync3(failed)) {
|
|
138
411
|
console.log("No failures recorded.");
|
|
139
412
|
} else {
|
|
140
|
-
process.stdout.write(
|
|
413
|
+
process.stdout.write(readFileSync3(failed, "utf-8"));
|
|
141
414
|
}
|
|
142
415
|
return { ok: true, group: "inspect", command };
|
|
143
416
|
}
|
|
144
417
|
case "graph":
|
|
145
|
-
requireNoExtraArgs(rest, "
|
|
418
|
+
requireNoExtraArgs(rest, "rig inspect graph");
|
|
146
419
|
{
|
|
147
420
|
const monorepoRoot = resolveMonorepoRoot(context.projectRoot);
|
|
148
421
|
const result = runCapture(["br", "--no-db", "list", "--pretty"], monorepoRoot);
|
|
@@ -153,12 +426,12 @@ async function executeInspect(context, args) {
|
|
|
153
426
|
}
|
|
154
427
|
return { ok: true, group: "inspect", command };
|
|
155
428
|
case "audit": {
|
|
156
|
-
requireNoExtraArgs(rest, "
|
|
157
|
-
const auditPath =
|
|
158
|
-
if (!
|
|
429
|
+
requireNoExtraArgs(rest, "rig inspect audit");
|
|
430
|
+
const auditPath = resolve3(resolveHarnessPaths(context.projectRoot).logsDir, "audit.jsonl");
|
|
431
|
+
if (!existsSync3(auditPath)) {
|
|
159
432
|
console.log("No audit log found.");
|
|
160
433
|
} else {
|
|
161
|
-
const lines =
|
|
434
|
+
const lines = readFileSync3(auditPath, "utf-8").split(/\r?\n/).filter(Boolean).slice(-20);
|
|
162
435
|
for (const line of lines) {
|
|
163
436
|
console.log(line);
|
|
164
437
|
}
|
|
@@ -6,8 +6,6 @@ import { iterateServerSentEvents } from "@rig/client";
|
|
|
6
6
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
7
7
|
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
8
8
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
9
|
-
import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
10
|
-
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
11
9
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
12
10
|
import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
|
|
13
11
|
function takeFlag(args, flag) {
|
|
@@ -104,7 +102,7 @@ async function executeInspector(context, args) {
|
|
|
104
102
|
pending = secondsResult.rest;
|
|
105
103
|
const pollMsResult = takeOption(pending, "--poll-ms");
|
|
106
104
|
pending = pollMsResult.rest;
|
|
107
|
-
requireNoExtraArgs(pending, "
|
|
105
|
+
requireNoExtraArgs(pending, "rig inspector stream [--once] [--seconds <n>] [--poll-ms <n>]");
|
|
108
106
|
const seconds = secondsResult.value ? parseRequiredPositiveInt(secondsResult.value, "--seconds") : null;
|
|
109
107
|
const pollMs = pollMsResult.value ? parseRequiredPositiveInt(pollMsResult.value, "--poll-ms") : null;
|
|
110
108
|
if (context.outputMode === "json" && !onceResult.value && !seconds) {
|
|
@@ -198,7 +196,7 @@ async function executeInspector(context, args) {
|
|
|
198
196
|
let pending = rest;
|
|
199
197
|
const scanIdResult = takeOption(pending, "--scan-id");
|
|
200
198
|
pending = scanIdResult.rest;
|
|
201
|
-
requireNoExtraArgs(pending, "
|
|
199
|
+
requireNoExtraArgs(pending, "rig inspector scan-upstream-drift [--scan-id <id>]");
|
|
202
200
|
const connection = await ensureLocalRigServerConnection(context.projectRoot);
|
|
203
201
|
const response = await fetch(new URL("/api/inspector/tools/invoke", connection.baseUrl), {
|
|
204
202
|
method: "POST",
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/commands/pi.ts
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { dirname, resolve } from "path";
|
|
6
|
+
|
|
7
|
+
// packages/cli/src/runner.ts
|
|
8
|
+
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
9
|
+
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
10
|
+
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
11
|
+
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
12
|
+
import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
|
|
13
|
+
function requireNoExtraArgs(args, usage) {
|
|
14
|
+
if (args.length > 0) {
|
|
15
|
+
throw new CliError(`Unexpected arguments: ${args.join(" ")}
|
|
16
|
+
Usage: ${usage}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// packages/cli/src/commands/pi.ts
|
|
21
|
+
function settingsPath(root) {
|
|
22
|
+
return resolve(root, ".pi", "settings.json");
|
|
23
|
+
}
|
|
24
|
+
function userSettingsPath() {
|
|
25
|
+
return resolve(homedir(), ".pi", "agent", "settings.json");
|
|
26
|
+
}
|
|
27
|
+
function readJson(path, fallback) {
|
|
28
|
+
if (!existsSync(path))
|
|
29
|
+
return fallback;
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
32
|
+
} catch {
|
|
33
|
+
return fallback;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function packageKey(entry) {
|
|
37
|
+
if (typeof entry === "string")
|
|
38
|
+
return entry;
|
|
39
|
+
if (entry && typeof entry === "object" && typeof entry.source === "string") {
|
|
40
|
+
return entry.source;
|
|
41
|
+
}
|
|
42
|
+
return JSON.stringify(entry);
|
|
43
|
+
}
|
|
44
|
+
function writeSettings(path, settings) {
|
|
45
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
46
|
+
writeFileSync(path, `${JSON.stringify(settings, null, 2)}
|
|
47
|
+
`, "utf-8");
|
|
48
|
+
}
|
|
49
|
+
async function searchNpmForPiExtensions(term) {
|
|
50
|
+
const query = encodeURIComponent(term ? `${term} pi extension` : "pi extension");
|
|
51
|
+
const url = `https://registry.npmjs.org/-/v1/search?text=${query}&size=20`;
|
|
52
|
+
const response = await fetch(url);
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
throw new CliError2(`npm registry search failed (${response.status}).`, 2);
|
|
55
|
+
}
|
|
56
|
+
const payload = await response.json();
|
|
57
|
+
const results = [];
|
|
58
|
+
for (const entry of payload.objects ?? []) {
|
|
59
|
+
const pkg = entry.package;
|
|
60
|
+
if (!pkg?.name)
|
|
61
|
+
continue;
|
|
62
|
+
const keywords = (pkg.keywords ?? []).map((k) => k.toLowerCase());
|
|
63
|
+
const piLike = pkg.name.startsWith("pi-") || pkg.name.includes("-pi") || keywords.includes("pi") || keywords.includes("pi-extension") || (pkg.description ?? "").toLowerCase().includes("pi extension") || (pkg.description ?? "").toLowerCase().includes("pi coding agent");
|
|
64
|
+
if (!piLike)
|
|
65
|
+
continue;
|
|
66
|
+
results.push({ name: pkg.name, version: pkg.version ?? "", description: pkg.description ?? "" });
|
|
67
|
+
}
|
|
68
|
+
return results;
|
|
69
|
+
}
|
|
70
|
+
async function executePi(context, args) {
|
|
71
|
+
const [command = "list", ...rest] = args;
|
|
72
|
+
const projectSettingsPath = settingsPath(context.projectRoot);
|
|
73
|
+
const managedRecordPath = resolve(context.projectRoot, ".rig", "state", "pi-managed-packages.json");
|
|
74
|
+
switch (command) {
|
|
75
|
+
case "list": {
|
|
76
|
+
requireNoExtraArgs(rest, "rig pi list");
|
|
77
|
+
const project = readJson(projectSettingsPath, {});
|
|
78
|
+
const managed = new Set(readJson(managedRecordPath, []));
|
|
79
|
+
const user = readJson(userSettingsPath(), {});
|
|
80
|
+
const projectPackages = (Array.isArray(project.packages) ? project.packages : []).map((entry) => ({
|
|
81
|
+
source: packageKey(entry),
|
|
82
|
+
managedByRigConfig: managed.has(packageKey(entry))
|
|
83
|
+
}));
|
|
84
|
+
const userPackages = (Array.isArray(user.packages) ? user.packages : []).map(packageKey);
|
|
85
|
+
if (context.outputMode === "text") {
|
|
86
|
+
console.log("Project Pi packages (.pi/settings.json):");
|
|
87
|
+
if (projectPackages.length === 0)
|
|
88
|
+
console.log(" (none)");
|
|
89
|
+
for (const pkg of projectPackages) {
|
|
90
|
+
console.log(` ${pkg.source}${pkg.managedByRigConfig ? " [from rig.config runtime.pi.packages]" : ""}`);
|
|
91
|
+
}
|
|
92
|
+
console.log("User Pi packages (~/.pi/agent/settings.json):");
|
|
93
|
+
if (userPackages.length === 0)
|
|
94
|
+
console.log(" (none)");
|
|
95
|
+
for (const pkg of userPackages)
|
|
96
|
+
console.log(` ${pkg}`);
|
|
97
|
+
console.log("Add more: `rig pi add <npm-package>` \xB7 discover: `rig pi search <term>`");
|
|
98
|
+
}
|
|
99
|
+
return { ok: true, group: "pi", command, details: { projectPackages, userPackages } };
|
|
100
|
+
}
|
|
101
|
+
case "add": {
|
|
102
|
+
const [source, ...extra] = rest;
|
|
103
|
+
requireNoExtraArgs(extra, "rig pi add <package-source>");
|
|
104
|
+
if (!source) {
|
|
105
|
+
throw new CliError2("Usage: rig pi add <package-source> (npm name, name@version, or git URL)", 2);
|
|
106
|
+
}
|
|
107
|
+
const settings = readJson(projectSettingsPath, {});
|
|
108
|
+
const packages = Array.isArray(settings.packages) ? settings.packages : [];
|
|
109
|
+
if (packages.some((entry) => packageKey(entry) === source)) {
|
|
110
|
+
throw new CliError2(`"${source}" is already in ${projectSettingsPath}.`, 2);
|
|
111
|
+
}
|
|
112
|
+
writeSettings(projectSettingsPath, { ...settings, packages: [...packages, source] });
|
|
113
|
+
if (context.outputMode === "text") {
|
|
114
|
+
console.log(`Added ${source} to ${projectSettingsPath}.`);
|
|
115
|
+
console.log("Pi installs missing packages automatically at the next session start (local and worker).");
|
|
116
|
+
}
|
|
117
|
+
return { ok: true, group: "pi", command, details: { source, settingsPath: projectSettingsPath } };
|
|
118
|
+
}
|
|
119
|
+
case "remove": {
|
|
120
|
+
const [source, ...extra] = rest;
|
|
121
|
+
requireNoExtraArgs(extra, "rig pi remove <package-source>");
|
|
122
|
+
if (!source) {
|
|
123
|
+
throw new CliError2("Usage: rig pi remove <package-source>", 2);
|
|
124
|
+
}
|
|
125
|
+
const managed = new Set(readJson(managedRecordPath, []));
|
|
126
|
+
if (managed.has(source)) {
|
|
127
|
+
throw new CliError2(`"${source}" is managed by rig.config.ts (runtime.pi.packages); remove it there instead.`, 2);
|
|
128
|
+
}
|
|
129
|
+
const settings = readJson(projectSettingsPath, {});
|
|
130
|
+
const packages = Array.isArray(settings.packages) ? settings.packages : [];
|
|
131
|
+
const next = packages.filter((entry) => packageKey(entry) !== source);
|
|
132
|
+
if (next.length === packages.length) {
|
|
133
|
+
throw new CliError2(`"${source}" is not in ${projectSettingsPath}.`, 2);
|
|
134
|
+
}
|
|
135
|
+
const nextSettings = { ...settings };
|
|
136
|
+
if (next.length > 0)
|
|
137
|
+
nextSettings.packages = next;
|
|
138
|
+
else
|
|
139
|
+
delete nextSettings.packages;
|
|
140
|
+
writeSettings(projectSettingsPath, nextSettings);
|
|
141
|
+
if (context.outputMode === "text") {
|
|
142
|
+
console.log(`Removed ${source} from ${projectSettingsPath}.`);
|
|
143
|
+
}
|
|
144
|
+
return { ok: true, group: "pi", command, details: { source } };
|
|
145
|
+
}
|
|
146
|
+
case "search": {
|
|
147
|
+
const term = rest.join(" ").trim();
|
|
148
|
+
const results = await searchNpmForPiExtensions(term);
|
|
149
|
+
if (context.outputMode === "text") {
|
|
150
|
+
if (results.length === 0) {
|
|
151
|
+
console.log(`No Pi extension packages found on npm${term ? ` for "${term}"` : ""}.`);
|
|
152
|
+
} else {
|
|
153
|
+
console.log(`Pi extension packages on npm${term ? ` matching "${term}"` : ""}:`);
|
|
154
|
+
for (const pkg of results) {
|
|
155
|
+
console.log(` ${pkg.name}@${pkg.version} ${pkg.description.slice(0, 80)}`);
|
|
156
|
+
}
|
|
157
|
+
console.log("Install one: `rig pi add <name>`");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { ok: true, group: "pi", command, details: { term, results } };
|
|
161
|
+
}
|
|
162
|
+
default:
|
|
163
|
+
throw new CliError2(`Unknown pi command: ${command}. Use list|add|remove|search.`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export {
|
|
167
|
+
executePi
|
|
168
|
+
};
|