@h-rig/cli-surface-plugin 0.0.6-alpha.146

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 (94) hide show
  1. package/README.md +1 -0
  2. package/dist/src/app/drone-ui.d.ts +34 -0
  3. package/dist/src/app/drone-ui.js +278 -0
  4. package/dist/src/commands/_async-ui.d.ts +10 -0
  5. package/dist/src/commands/_async-ui.js +121 -0
  6. package/dist/src/commands/_cli-format.d.ts +56 -0
  7. package/dist/src/commands/_cli-format.js +332 -0
  8. package/dist/src/commands/_connection-state.d.ts +54 -0
  9. package/dist/src/commands/_connection-state.js +187 -0
  10. package/dist/src/commands/_doctor-checks.d.ts +9 -0
  11. package/dist/src/commands/_doctor-checks.js +24 -0
  12. package/dist/src/commands/_help-catalog.d.ts +29 -0
  13. package/dist/src/commands/_help-catalog.js +157 -0
  14. package/dist/src/commands/_inprocess-services.d.ts +33 -0
  15. package/dist/src/commands/_inprocess-services.js +102 -0
  16. package/dist/src/commands/_json-output.d.ts +11 -0
  17. package/dist/src/commands/_json-output.js +54 -0
  18. package/dist/src/commands/_parsers.d.ts +15 -0
  19. package/dist/src/commands/_parsers.js +114 -0
  20. package/dist/src/commands/_paths.d.ts +11 -0
  21. package/dist/src/commands/_paths.js +50 -0
  22. package/dist/src/commands/_pi-frontend.d.ts +35 -0
  23. package/dist/src/commands/_pi-frontend.js +64 -0
  24. package/dist/src/commands/_pi-install.d.ts +42 -0
  25. package/dist/src/commands/_pi-install.js +167 -0
  26. package/dist/src/commands/_policy.d.ts +8 -0
  27. package/dist/src/commands/_policy.js +138 -0
  28. package/dist/src/commands/_probes.d.ts +1 -0
  29. package/dist/src/commands/_probes.js +13 -0
  30. package/dist/src/commands/_run-driver-helpers.d.ts +26 -0
  31. package/dist/src/commands/_run-driver-helpers.js +132 -0
  32. package/dist/src/commands/_run-subcommands.d.ts +3 -0
  33. package/dist/src/commands/_run-subcommands.js +31 -0
  34. package/dist/src/commands/_spinner.d.ts +25 -0
  35. package/dist/src/commands/_spinner.js +65 -0
  36. package/dist/src/commands/agent.d.ts +3 -0
  37. package/dist/src/commands/agent.js +322 -0
  38. package/dist/src/commands/config.d.ts +3 -0
  39. package/dist/src/commands/config.js +193 -0
  40. package/dist/src/commands/dist.d.ts +28 -0
  41. package/dist/src/commands/dist.js +435 -0
  42. package/dist/src/commands/doctor.d.ts +3 -0
  43. package/dist/src/commands/doctor.js +171 -0
  44. package/dist/src/commands/github.d.ts +3 -0
  45. package/dist/src/commands/github.js +342 -0
  46. package/dist/src/commands/inbox.d.ts +19 -0
  47. package/dist/src/commands/inbox.js +241 -0
  48. package/dist/src/commands/init.d.ts +64 -0
  49. package/dist/src/commands/init.js +1449 -0
  50. package/dist/src/commands/inspect.d.ts +20 -0
  51. package/dist/src/commands/inspect.js +337 -0
  52. package/dist/src/commands/pi.d.ts +3 -0
  53. package/dist/src/commands/pi.js +177 -0
  54. package/dist/src/commands/plugin.d.ts +20 -0
  55. package/dist/src/commands/plugin.js +238 -0
  56. package/dist/src/commands/profile-and-review.d.ts +4 -0
  57. package/dist/src/commands/profile-and-review.js +223 -0
  58. package/dist/src/commands/queue.d.ts +3 -0
  59. package/dist/src/commands/queue.js +197 -0
  60. package/dist/src/commands/remote.d.ts +3 -0
  61. package/dist/src/commands/remote.js +516 -0
  62. package/dist/src/commands/repo-git-harness.d.ts +5 -0
  63. package/dist/src/commands/repo-git-harness.js +282 -0
  64. package/dist/src/commands/run.d.ts +22 -0
  65. package/dist/src/commands/run.js +645 -0
  66. package/dist/src/commands/server.d.ts +3 -0
  67. package/dist/src/commands/server.js +155 -0
  68. package/dist/src/commands/setup.d.ts +16 -0
  69. package/dist/src/commands/setup.js +356 -0
  70. package/dist/src/commands/stats.d.ts +11 -0
  71. package/dist/src/commands/stats.js +219 -0
  72. package/dist/src/commands/task-run-driver.d.ts +93 -0
  73. package/dist/src/commands/task-run-driver.js +136 -0
  74. package/dist/src/commands/task.d.ts +46 -0
  75. package/dist/src/commands/task.js +555 -0
  76. package/dist/src/commands/test.d.ts +3 -0
  77. package/dist/src/commands/test.js +46 -0
  78. package/dist/src/commands/triage.d.ts +11 -0
  79. package/dist/src/commands/triage.js +224 -0
  80. package/dist/src/commands/workspace.d.ts +3 -0
  81. package/dist/src/commands/workspace.js +130 -0
  82. package/dist/src/kernel-dispatch.d.ts +15 -0
  83. package/dist/src/kernel-dispatch.js +16 -0
  84. package/dist/src/plugin.d.ts +3 -0
  85. package/dist/src/plugin.js +5440 -0
  86. package/dist/src/rig-config-package-deps.d.ts +10 -0
  87. package/dist/src/rig-config-package-deps.js +272 -0
  88. package/dist/src/runner.d.ts +47 -0
  89. package/dist/src/runner.js +267 -0
  90. package/dist/src/version.d.ts +8 -0
  91. package/dist/src/version.js +47 -0
  92. package/dist/src/withMutedConsole.d.ts +2 -0
  93. package/dist/src/withMutedConsole.js +42 -0
  94. package/package.json +34 -0
@@ -0,0 +1,20 @@
1
+ import type { RunSessionCustomEntry } from "@rig/contracts";
2
+ import type { CommandOutcome } from "@rig/runtime/control-plane/runtime/types";
3
+ import type { TaskArtifactReadResult } from "@rig/runtime/control-plane/native/task-ops";
4
+ import type { RunRecord } from "@rig/client";
5
+ import { type RunnerContext } from "../runner";
6
+ type MaybePromise<T> = T | Promise<T>;
7
+ type InspectCommandDeps = {
8
+ getRunProjection?: (projectRoot: string, taskId: string) => Promise<RunRecord | null>;
9
+ listRunProjections?: (projectRoot: string) => Promise<RunRecord[]>;
10
+ readSessionRunEntries?: (sessionPath: string | null) => RunSessionCustomEntry[];
11
+ taskArtifactRead?: (projectRoot: string, filename: string, options?: {
12
+ taskId?: string;
13
+ maxBytes?: number;
14
+ }) => MaybePromise<TaskArtifactReadResult>;
15
+ taskArtifacts?: (projectRoot: string, taskId?: string) => MaybePromise<void>;
16
+ taskDeps?: (projectRoot: string, taskId?: string) => MaybePromise<void>;
17
+ changedFilesForTask?: (projectRoot: string, taskId: string, scoped: boolean) => string[];
18
+ };
19
+ export declare function executeInspect(context: RunnerContext, args: string[], deps?: InspectCommandDeps): Promise<CommandOutcome>;
20
+ export {};
@@ -0,0 +1,337 @@
1
+ // @bun
2
+ // packages/cli-surface-plugin/src/commands/inspect.ts
3
+ import {
4
+ changedFilesForTask,
5
+ extractRunLogs,
6
+ getRun,
7
+ listRuns,
8
+ runsForTask,
9
+ summarizeRunFailures,
10
+ taskArtifactRead,
11
+ taskArtifacts,
12
+ taskDeps
13
+ } from "@rig/client";
14
+
15
+ // packages/cli-surface-plugin/src/runner.ts
16
+ import { EventBus } from "@rig/runtime/control-plane/runtime/events";
17
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
18
+ import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
19
+ import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
20
+
21
+ class CliError extends RuntimeCliError {
22
+ hint;
23
+ constructor(message, exitCode = 1, options = {}) {
24
+ super(message, exitCode);
25
+ if (options.hint?.trim()) {
26
+ this.hint = options.hint.trim();
27
+ }
28
+ }
29
+ }
30
+ function takeOption(args, option) {
31
+ const rest = [];
32
+ let value;
33
+ for (let index = 0;index < args.length; index += 1) {
34
+ const current = args[index];
35
+ if (current === option) {
36
+ const next = args[index + 1];
37
+ if (!next || next.startsWith("-")) {
38
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
39
+ }
40
+ value = next;
41
+ index += 1;
42
+ continue;
43
+ }
44
+ if (current !== undefined) {
45
+ rest.push(current);
46
+ }
47
+ }
48
+ return { value, rest };
49
+ }
50
+ function requireNoExtraArgs(args, usage) {
51
+ if (args.length > 0) {
52
+ throw new CliError(`Unexpected arguments: ${args.join(" ")}
53
+ Usage: ${usage}`);
54
+ }
55
+ }
56
+ function requireTask(taskId, usage) {
57
+ if (!taskId) {
58
+ throw new CliError(`Missing --task option.
59
+ Usage: ${usage}`);
60
+ }
61
+ return taskId;
62
+ }
63
+
64
+ // packages/cli-surface-plugin/src/commands/_cli-format.ts
65
+ import pc from "picocolors";
66
+ import { runStatusColorRole, runStatusText, statusColorRole } from "@rig/client";
67
+ var dim = pc.dim;
68
+ var faintBar = pc.dim("\u2502");
69
+ var accent = pc.cyan;
70
+ function truncate(value, width) {
71
+ return value.length <= width ? value : `${value.slice(0, Math.max(0, width - 1))}\u2026`;
72
+ }
73
+ function pad(value, width) {
74
+ return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
75
+ }
76
+ function colorForRole(role) {
77
+ switch (role) {
78
+ case "success":
79
+ return pc.green;
80
+ case "action-yellow":
81
+ return pc.yellow;
82
+ case "active-cyan":
83
+ return pc.cyan;
84
+ case "failure":
85
+ return pc.red;
86
+ case "muted":
87
+ case "neutral":
88
+ return pc.dim;
89
+ }
90
+ }
91
+ function firstString(record, keys, fallback = "") {
92
+ for (const key of keys) {
93
+ const value = record[key];
94
+ if (typeof value === "string" && value.trim())
95
+ return value;
96
+ }
97
+ return fallback;
98
+ }
99
+ function runIdOf(run) {
100
+ return firstString(run, ["runId", "id"], "(unknown-run)");
101
+ }
102
+ function taskIdOf(run) {
103
+ return firstString(run, ["taskId", "task", "task_id"]);
104
+ }
105
+ function runTitleOf(run) {
106
+ return firstString(run, ["title", "summary", "name"], taskIdOf(run) || "(untitled)");
107
+ }
108
+ function runLikeStatusText(run) {
109
+ return runStatusText(run);
110
+ }
111
+ function runLikeStatusColor(run) {
112
+ return colorForRole(runStatusColorRole(run));
113
+ }
114
+ function printFormattedOutput(message) {
115
+ console.log(message);
116
+ }
117
+ function formatSection(title, subtitle) {
118
+ return `${pc.bold(accent("\u25C6"))} ${pc.bold(title)}${subtitle ? dim(` \u2014 ${subtitle}`) : ""}`;
119
+ }
120
+ function formatLegacyAutomationSurface() {
121
+ return [];
122
+ }
123
+ function formatRunList(runs, options = {}) {
124
+ if (runs.length === 0) {
125
+ return [
126
+ formatSection("Runs", "none recorded"),
127
+ options.source === "server" ? dim("No runs recorded on the selected server.") : dim("No runs recorded in local state."),
128
+ "",
129
+ ...formatLegacyAutomationSurface()
130
+ ].join(`
131
+ `);
132
+ }
133
+ const body = runs.map((run) => {
134
+ const row = run;
135
+ const runId = runIdOf(row);
136
+ const status = runLikeStatusText(run);
137
+ const runtime = firstString(row, ["runtime", "runtimeAdapter"], "");
138
+ return [
139
+ pc.bold(runId),
140
+ runLikeStatusColor(run)(pad(truncate(status, 12), 12)),
141
+ `${runTitleOf(row)}${runtime ? dim(` ${runtime}`) : ""}`
142
+ ].join(" ");
143
+ });
144
+ return [formatSection("Runs", options.source === "server" ? "selected server" : "local state"), ...body, "", ...formatLegacyAutomationSurface()].join(`
145
+ `);
146
+ }
147
+
148
+ // packages/cli-surface-plugin/src/commands/inspect.ts
149
+ var INSPECT_USAGE = "rig inspect <logs|artifact|artifacts|run-logs|runs|failures|graph|diff>";
150
+ function inspectHelpText() {
151
+ return [
152
+ `Usage: ${INSPECT_USAGE}`,
153
+ "",
154
+ "Commands:",
155
+ " logs --task <id> Print log lines from the latest projected run for a task.",
156
+ " artifact --task <id> --file <name>",
157
+ " Preview a single task artifact.",
158
+ " artifacts --task <id> List task artifacts.",
159
+ " run-logs --run <id> Print log lines from a run journal.",
160
+ " runs List all projected runs.",
161
+ " diff --task <id> List files changed by a task.",
162
+ " graph [--task <id>] Print the task dependency graph.",
163
+ " failures --task <id> Summarize failed runs, closeout failures, and journal anomalies."
164
+ ].join(`
165
+ `);
166
+ }
167
+ function printInspectHelp() {
168
+ printFormattedOutput(inspectHelpText());
169
+ }
170
+ function toRunLike(run) {
171
+ return {
172
+ runId: run.runId,
173
+ taskId: run.taskId,
174
+ title: run.title,
175
+ status: run.status,
176
+ source: run.source,
177
+ live: run.live,
178
+ stale: run.stale,
179
+ startedAt: run.startedAt,
180
+ updatedAt: run.updatedAt,
181
+ completedAt: run.completedAt,
182
+ joinLink: run.joinLink,
183
+ prUrl: run.prUrl,
184
+ pendingApprovals: run.pendingApprovals,
185
+ pendingInputs: run.pendingInputs,
186
+ steeringCount: run.steeringCount,
187
+ stallCount: run.stallCount
188
+ };
189
+ }
190
+ function requireRunId(value, usage) {
191
+ if (!value || !value.trim())
192
+ throw new CliError(`${usage} requires a run id.`, 2, { hint: "Run `rig inspect runs` to find run ids." });
193
+ return value.trim();
194
+ }
195
+ async function executeLogs(context, args, deps) {
196
+ const taskResult = takeOption(args, "--task");
197
+ requireNoExtraArgs(taskResult.rest, "rig inspect logs --task <id>");
198
+ const taskId = requireTask(taskResult.value, "rig inspect logs --task <id>");
199
+ const runs = await runsForTask(context.projectRoot, taskId, {
200
+ listRuns: deps.listRunProjections ?? listRuns,
201
+ getRun: deps.getRunProjection ?? getRun
202
+ });
203
+ const projection = runs[0];
204
+ if (!projection)
205
+ throw new CliError(`No projected runs found for task ${taskId}.`, 1, { hint: "Run `rig run list` to confirm the run journal is discoverable." });
206
+ const lines = extractRunLogs(projection, { readSessionRunEntries: deps.readSessionRunEntries });
207
+ if (context.outputMode === "text") {
208
+ if (lines.length === 0)
209
+ console.log(`No log entries found for task ${taskId}.`);
210
+ else
211
+ for (const line of lines)
212
+ console.log(line);
213
+ }
214
+ return { ok: true, group: "inspect", command: "logs", details: { taskId, runId: projection.runId, count: lines.length, lines } };
215
+ }
216
+ async function executeArtifact(context, args, deps) {
217
+ const taskResult = takeOption(args, "--task");
218
+ const fileResult = takeOption(taskResult.rest, "--file");
219
+ requireNoExtraArgs(fileResult.rest, "rig inspect artifact --task <id> --file <name>");
220
+ const taskId = requireTask(taskResult.value, "rig inspect artifact --task <id> --file <name>");
221
+ const filename = fileResult.value?.trim();
222
+ if (!filename)
223
+ throw new CliError("rig inspect artifact --task <id> --file <name> requires an artifact file name.", 2);
224
+ const preview = await (deps.taskArtifactRead ?? taskArtifactRead)(context.projectRoot, filename, { taskId, maxBytes: 64 * 1024 });
225
+ if (context.outputMode === "text") {
226
+ console.log(preview.contents);
227
+ if (preview.truncated)
228
+ console.log(`
229
+ [truncated at ${preview.maxBytes} bytes; artifact is ${preview.sizeBytes} bytes]`);
230
+ }
231
+ return { ok: true, group: "inspect", command: "artifact", details: { taskId, file: filename, ...preview } };
232
+ }
233
+ async function executeRunLogs(context, args, deps) {
234
+ const runResult = takeOption(args, "--run");
235
+ requireNoExtraArgs(runResult.rest, "rig inspect run-logs --run <id>");
236
+ const runId = requireRunId(runResult.value, "rig inspect run-logs --run <id>");
237
+ const projection = await (deps.getRunProjection ?? getRun)(context.projectRoot, runId);
238
+ if (!projection)
239
+ throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig inspect runs` to see runs." });
240
+ const lines = extractRunLogs(projection, { readSessionRunEntries: deps.readSessionRunEntries });
241
+ if (context.outputMode === "text") {
242
+ if (lines.length === 0)
243
+ console.log(`No log entries found for run ${projection.runId}.`);
244
+ else
245
+ for (const line of lines)
246
+ console.log(line);
247
+ }
248
+ return { ok: true, group: "inspect", command: "run-logs", details: { runId: projection.runId, taskId: projection.taskId, count: lines.length, lines } };
249
+ }
250
+ async function executeRuns(context, args, deps) {
251
+ requireNoExtraArgs(args, "rig inspect runs");
252
+ const runs = await (deps.listRunProjections ?? listRuns)(context.projectRoot);
253
+ const formatted = runs.map(toRunLike);
254
+ if (context.outputMode === "text")
255
+ printFormattedOutput(formatRunList(formatted));
256
+ return { ok: true, group: "inspect", command: "runs", details: { runs: formatted } };
257
+ }
258
+ async function executeArtifacts(context, args, deps) {
259
+ const taskResult = takeOption(args, "--task");
260
+ requireNoExtraArgs(taskResult.rest, "rig inspect artifacts --task <id>");
261
+ const taskId = requireTask(taskResult.value, "rig inspect artifacts --task <id>");
262
+ if (context.outputMode === "text")
263
+ await (deps.taskArtifacts ?? taskArtifacts)(context.projectRoot, taskId);
264
+ return { ok: true, group: "inspect", command: "artifacts", details: { taskId, printed: context.outputMode === "text" } };
265
+ }
266
+ async function executeDiff(context, args, deps) {
267
+ const taskResult = takeOption(args, "--task");
268
+ requireNoExtraArgs(taskResult.rest, "rig inspect diff --task <id>");
269
+ const taskId = requireTask(taskResult.value, "rig inspect diff --task <id>");
270
+ const files = (deps.changedFilesForTask ?? changedFilesForTask)(context.projectRoot, taskId, true);
271
+ if (context.outputMode === "text")
272
+ for (const file of files)
273
+ console.log(file);
274
+ return { ok: true, group: "inspect", command: "diff", details: { taskId, files } };
275
+ }
276
+ async function executeGraph(context, args, deps) {
277
+ const taskResult = takeOption(args, "--task");
278
+ requireNoExtraArgs(taskResult.rest, "rig inspect graph [--task <id>]");
279
+ if (context.outputMode === "text")
280
+ await (deps.taskDeps ?? taskDeps)(context.projectRoot, taskResult.value);
281
+ return { ok: true, group: "inspect", command: "graph", details: { taskId: taskResult.value ?? null, printed: context.outputMode === "text" } };
282
+ }
283
+ async function executeFailures(context, args, deps) {
284
+ const taskResult = takeOption(args, "--task");
285
+ requireNoExtraArgs(taskResult.rest, "rig inspect failures --task <id>");
286
+ const taskId = requireTask(taskResult.value, "rig inspect failures --task <id>");
287
+ const runs = await runsForTask(context.projectRoot, taskId, {
288
+ listRuns: deps.listRunProjections ?? listRuns,
289
+ getRun: deps.getRunProjection ?? getRun
290
+ });
291
+ const failures = runs.flatMap(summarizeRunFailures);
292
+ if (context.outputMode === "text") {
293
+ if (failures.length === 0)
294
+ console.log(`No failures recorded for task ${taskId}.`);
295
+ else
296
+ for (const failure of failures)
297
+ console.log(failure);
298
+ }
299
+ return { ok: true, group: "inspect", command: "failures", details: { taskId, count: failures.length, failures } };
300
+ }
301
+ function executeAudit(args) {
302
+ requireNoExtraArgs(args, "rig inspect audit");
303
+ throw new CliError("rig inspect audit cannot read the controlled-command audit trail yet: current code only writes audit JSONL and exposes no supported reader API.", 2, { hint: "Use the OMP session history for operator audit context until a controlled-bash audit reader is added." });
304
+ }
305
+ async function executeInspect(context, args, deps = {}) {
306
+ const [command = "failures", ...rest] = args;
307
+ if (command === "--help" || command === "-h" || command === "help") {
308
+ if (context.outputMode === "text")
309
+ printInspectHelp();
310
+ return { ok: true, group: "inspect", command: "help" };
311
+ }
312
+ switch (command) {
313
+ case "logs":
314
+ return executeLogs(context, rest, deps);
315
+ case "artifact":
316
+ return executeArtifact(context, rest, deps);
317
+ case "artifacts":
318
+ return executeArtifacts(context, rest, deps);
319
+ case "run-logs":
320
+ return executeRunLogs(context, rest, deps);
321
+ case "runs":
322
+ return executeRuns(context, rest, deps);
323
+ case "diff":
324
+ return executeDiff(context, rest, deps);
325
+ case "graph":
326
+ return executeGraph(context, rest, deps);
327
+ case "failures":
328
+ return executeFailures(context, rest, deps);
329
+ case "audit":
330
+ return executeAudit(rest);
331
+ default:
332
+ throw new CliError(`Unknown inspect command: ${command}`, 1, { hint: "Run `rig inspect --help` to list inspect commands." });
333
+ }
334
+ }
335
+ export {
336
+ executeInspect
337
+ };
@@ -0,0 +1,3 @@
1
+ import { type RunnerContext } from "../runner";
2
+ import type { CommandOutcome } from "@rig/runtime";
3
+ export declare function executePi(context: RunnerContext, args: string[]): Promise<CommandOutcome>;
@@ -0,0 +1,177 @@
1
+ // @bun
2
+ // packages/cli-surface-plugin/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-surface-plugin/src/runner.ts
8
+ import { EventBus } from "@rig/runtime/control-plane/runtime/events";
9
+ import { CliError as RuntimeCliError } 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
+
13
+ class CliError extends RuntimeCliError {
14
+ hint;
15
+ constructor(message, exitCode = 1, options = {}) {
16
+ super(message, exitCode);
17
+ if (options.hint?.trim()) {
18
+ this.hint = options.hint.trim();
19
+ }
20
+ }
21
+ }
22
+ function requireNoExtraArgs(args, usage) {
23
+ if (args.length > 0) {
24
+ throw new CliError(`Unexpected arguments: ${args.join(" ")}
25
+ Usage: ${usage}`);
26
+ }
27
+ }
28
+
29
+ // packages/cli-surface-plugin/src/commands/pi.ts
30
+ function settingsPath(root) {
31
+ return resolve(root, ".pi", "settings.json");
32
+ }
33
+ function userSettingsPath() {
34
+ return resolve(homedir(), ".pi", "agent", "settings.json");
35
+ }
36
+ function readJson(path, fallback) {
37
+ if (!existsSync(path))
38
+ return fallback;
39
+ try {
40
+ return JSON.parse(readFileSync(path, "utf-8"));
41
+ } catch {
42
+ return fallback;
43
+ }
44
+ }
45
+ function packageKey(entry) {
46
+ if (typeof entry === "string")
47
+ return entry;
48
+ if (entry && typeof entry === "object" && typeof entry.source === "string") {
49
+ return entry.source;
50
+ }
51
+ return JSON.stringify(entry);
52
+ }
53
+ function writeSettings(path, settings) {
54
+ mkdirSync(dirname(path), { recursive: true });
55
+ writeFileSync(path, `${JSON.stringify(settings, null, 2)}
56
+ `, "utf-8");
57
+ }
58
+ async function searchNpmForPiExtensions(term) {
59
+ const query = encodeURIComponent(term ? `${term} pi extension` : "pi extension");
60
+ const url = `https://registry.npmjs.org/-/v1/search?text=${query}&size=20`;
61
+ const response = await fetch(url);
62
+ if (!response.ok) {
63
+ throw new CliError(`npm registry search failed (${response.status}).`, 2, { hint: "Check network access to registry.npmjs.org, then retry `rig pi search <term>`." });
64
+ }
65
+ const payload = await response.json();
66
+ const results = [];
67
+ for (const entry of payload.objects ?? []) {
68
+ const pkg = entry.package;
69
+ if (!pkg?.name)
70
+ continue;
71
+ const keywords = (pkg.keywords ?? []).map((k) => k.toLowerCase());
72
+ 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");
73
+ if (!piLike)
74
+ continue;
75
+ results.push({ name: pkg.name, version: pkg.version ?? "", description: pkg.description ?? "" });
76
+ }
77
+ return results;
78
+ }
79
+ async function executePi(context, args) {
80
+ const [command = "list", ...rest] = args;
81
+ const projectSettingsPath = settingsPath(context.projectRoot);
82
+ const managedRecordPath = resolve(context.projectRoot, ".rig", "state", "pi-managed-packages.json");
83
+ switch (command) {
84
+ case "list": {
85
+ requireNoExtraArgs(rest, "rig pi list");
86
+ const project = readJson(projectSettingsPath, {});
87
+ const managed = new Set(readJson(managedRecordPath, []));
88
+ const user = readJson(userSettingsPath(), {});
89
+ const projectPackages = (Array.isArray(project.packages) ? project.packages : []).map((entry) => ({
90
+ source: packageKey(entry),
91
+ managedByRigConfig: managed.has(packageKey(entry))
92
+ }));
93
+ const userPackages = (Array.isArray(user.packages) ? user.packages : []).map(packageKey);
94
+ if (context.outputMode === "text") {
95
+ console.log("Project Pi packages (.pi/settings.json):");
96
+ if (projectPackages.length === 0)
97
+ console.log(" (none)");
98
+ for (const pkg of projectPackages) {
99
+ console.log(` ${pkg.source}${pkg.managedByRigConfig ? " [from rig.config runtime.pi.packages]" : ""}`);
100
+ }
101
+ console.log("User Pi packages (~/.pi/agent/settings.json):");
102
+ if (userPackages.length === 0)
103
+ console.log(" (none)");
104
+ for (const pkg of userPackages)
105
+ console.log(` ${pkg}`);
106
+ console.log("Add more: `rig pi add <npm-package>` \xB7 discover: `rig pi search <term>`");
107
+ }
108
+ return { ok: true, group: "pi", command, details: { projectPackages, userPackages } };
109
+ }
110
+ case "add": {
111
+ const [source, ...extra] = rest;
112
+ requireNoExtraArgs(extra, "rig pi add <package-source>");
113
+ if (!source) {
114
+ throw new CliError("Usage: rig pi add <package-source> (npm name, name@version, or git URL)", 2);
115
+ }
116
+ const settings = readJson(projectSettingsPath, {});
117
+ const packages = Array.isArray(settings.packages) ? settings.packages : [];
118
+ if (packages.some((entry) => packageKey(entry) === source)) {
119
+ throw new CliError(`"${source}" is already in ${projectSettingsPath}.`, 2, { hint: "Run `rig pi list` to see installed extensions." });
120
+ }
121
+ writeSettings(projectSettingsPath, { ...settings, packages: [...packages, source] });
122
+ if (context.outputMode === "text") {
123
+ console.log(`Added ${source} to ${projectSettingsPath}.`);
124
+ console.log("Pi installs missing packages automatically at the next session start (local and worker).");
125
+ }
126
+ return { ok: true, group: "pi", command, details: { source, settingsPath: projectSettingsPath } };
127
+ }
128
+ case "remove": {
129
+ const [source, ...extra] = rest;
130
+ requireNoExtraArgs(extra, "rig pi remove <package-source>");
131
+ if (!source) {
132
+ throw new CliError("Usage: rig pi remove <package-source>", 2);
133
+ }
134
+ const managed = new Set(readJson(managedRecordPath, []));
135
+ if (managed.has(source)) {
136
+ throw new CliError(`"${source}" is managed by rig.config.ts (runtime.pi.packages); remove it there instead.`, 2);
137
+ }
138
+ const settings = readJson(projectSettingsPath, {});
139
+ const packages = Array.isArray(settings.packages) ? settings.packages : [];
140
+ const next = packages.filter((entry) => packageKey(entry) !== source);
141
+ if (next.length === packages.length) {
142
+ throw new CliError(`"${source}" is not in ${projectSettingsPath}.`, 2, { hint: "Run `rig pi list` to see what is installed, then `rig pi remove <source>` with an exact entry." });
143
+ }
144
+ const nextSettings = { ...settings };
145
+ if (next.length > 0)
146
+ nextSettings.packages = next;
147
+ else
148
+ delete nextSettings.packages;
149
+ writeSettings(projectSettingsPath, nextSettings);
150
+ if (context.outputMode === "text") {
151
+ console.log(`Removed ${source} from ${projectSettingsPath}.`);
152
+ }
153
+ return { ok: true, group: "pi", command, details: { source } };
154
+ }
155
+ case "search": {
156
+ const term = rest.join(" ").trim();
157
+ const results = await searchNpmForPiExtensions(term);
158
+ if (context.outputMode === "text") {
159
+ if (results.length === 0) {
160
+ console.log(`No Pi extension packages found on npm${term ? ` for "${term}"` : ""}.`);
161
+ } else {
162
+ console.log(`Pi extension packages on npm${term ? ` matching "${term}"` : ""}:`);
163
+ for (const pkg of results) {
164
+ console.log(` ${pkg.name}@${pkg.version} ${pkg.description.slice(0, 80)}`);
165
+ }
166
+ console.log("Install one: `rig pi add <name>`");
167
+ }
168
+ }
169
+ return { ok: true, group: "pi", command, details: { term, results } };
170
+ }
171
+ default:
172
+ throw new CliError(`Unknown pi command: ${command}. Use list|add|remove|search.`, 1, { hint: "Run `rig pi --help` for usage." });
173
+ }
174
+ }
175
+ export {
176
+ executePi
177
+ };
@@ -0,0 +1,20 @@
1
+ import { type RunnerContext } from "../runner";
2
+ import type { CommandOutcome } from "@rig/runtime";
3
+ export declare function executePlugin(context: RunnerContext, args: string[]): Promise<CommandOutcome>;
4
+ /**
5
+ * Resolve a plugin CLI command by exact id ("my-plugin:deploy") or by the
6
+ * unambiguous local part after the namespace colon ("deploy").
7
+ */
8
+ export declare function resolvePluginCliCommand(commands: readonly {
9
+ id: string;
10
+ family?: string;
11
+ command?: string;
12
+ description?: string;
13
+ aliases?: readonly string[];
14
+ }[], requested: string): {
15
+ id: string;
16
+ family?: string;
17
+ command?: string;
18
+ description?: string;
19
+ aliases?: readonly string[];
20
+ } | undefined;