@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.
Files changed (52) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/rig.js +5033 -1895
  3. package/dist/src/commands/_authority-runs.js +2 -3
  4. package/dist/src/commands/_cli-format.js +369 -0
  5. package/dist/src/commands/_connection-state.js +12 -6
  6. package/dist/src/commands/_doctor-checks.js +79 -34
  7. package/dist/src/commands/_help-catalog.js +446 -0
  8. package/dist/src/commands/_operator-surface.js +220 -0
  9. package/dist/src/commands/_operator-view.js +1124 -64
  10. package/dist/src/commands/_parsers.js +0 -2
  11. package/dist/src/commands/_pi-frontend.js +1080 -0
  12. package/dist/src/commands/_pi-install.js +4 -3
  13. package/dist/src/commands/_pi-remote-session.js +771 -0
  14. package/dist/src/commands/_pi-worker-bridge-extension.js +834 -0
  15. package/dist/src/commands/_policy.js +0 -2
  16. package/dist/src/commands/_preflight.js +98 -116
  17. package/dist/src/commands/_run-driver-helpers.js +46 -19
  18. package/dist/src/commands/_run-replay.js +142 -0
  19. package/dist/src/commands/_server-client.js +225 -48
  20. package/dist/src/commands/_snapshot-upload.js +74 -30
  21. package/dist/src/commands/_spinner.js +63 -0
  22. package/dist/src/commands/_task-picker.js +44 -16
  23. package/dist/src/commands/agent.js +10 -12
  24. package/dist/src/commands/browser.js +4 -6
  25. package/dist/src/commands/connect.js +134 -26
  26. package/dist/src/commands/dist.js +4 -6
  27. package/dist/src/commands/doctor.js +79 -34
  28. package/dist/src/commands/github.js +76 -32
  29. package/dist/src/commands/inbox.js +410 -31
  30. package/dist/src/commands/init.js +398 -90
  31. package/dist/src/commands/inspect.js +296 -23
  32. package/dist/src/commands/inspector.js +2 -4
  33. package/dist/src/commands/pi.js +168 -0
  34. package/dist/src/commands/plugin.js +81 -22
  35. package/dist/src/commands/profile-and-review.js +8 -10
  36. package/dist/src/commands/queue.js +2 -3
  37. package/dist/src/commands/remote.js +18 -20
  38. package/dist/src/commands/repo-git-harness.js +6 -8
  39. package/dist/src/commands/run.js +1600 -131
  40. package/dist/src/commands/server.js +281 -42
  41. package/dist/src/commands/setup.js +84 -45
  42. package/dist/src/commands/task-report-bug.js +5 -7
  43. package/dist/src/commands/task-run-driver.js +736 -93
  44. package/dist/src/commands/task.js +1863 -262
  45. package/dist/src/commands/test.js +3 -5
  46. package/dist/src/commands/workspace.js +4 -6
  47. package/dist/src/commands.js +5675 -2531
  48. package/dist/src/index.js +5654 -2519
  49. package/dist/src/launcher.js +5 -3
  50. package/dist/src/report-bug.js +3 -3
  51. package/dist/src/runner.js +5 -19
  52. 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, "bun run rig inspect logs --task <beads-id>");
64
- const requiredTask = requireTask(task, "bun run rig inspect logs --task <beads-id>");
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
- throw new CliError2(`No runs found for ${requiredTask}.`);
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 = resolve(resolveAuthorityRunDir(context.projectRoot, latestRun.runId), "logs.jsonl");
70
- if (!existsSync(logsPath)) {
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, "bun run rig inspect artifacts --task <beads-id>");
79
- const requiredTask = requireTask(task, "bun run rig inspect artifacts --task <beads-id>");
80
- const artifactRoot = resolveTaskArtifactDirs(context.projectRoot, requiredTask).find((path) => existsSync(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, "bun run rig inspect artifact --task <beads-id> --file <name>");
94
- const requiredTask = requireTask(task.value, "bun run rig inspect artifact --task <beads-id> --file <name>");
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, "bun run rig inspect diff [--task <beads-id>]");
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, "bun run rig inspect failures");
408
+ requireNoExtraArgs(rest, "rig inspect failures");
136
409
  const failed = resolveHarnessPaths(context.projectRoot).failedApproachesPath;
137
- if (!existsSync(failed)) {
410
+ if (!existsSync3(failed)) {
138
411
  console.log("No failures recorded.");
139
412
  } else {
140
- process.stdout.write(readFileSync(failed, "utf-8"));
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, "bun run rig inspect graph");
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, "bun run rig inspect audit");
157
- const auditPath = resolve(resolveHarnessPaths(context.projectRoot).logsDir, "audit.jsonl");
158
- if (!existsSync(auditPath)) {
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 = readFileSync(auditPath, "utf-8").split(/\r?\n/).filter(Boolean).slice(-20);
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, "bun run rig inspector stream [--once] [--seconds <n>] [--poll-ms <n>]");
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, "bun run rig inspector scan-upstream-drift [--scan-id <id>]");
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
+ };