@h-rig/cli 0.0.6-alpha.27 → 0.0.6-alpha.280

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 +6 -28
  2. package/package.json +11 -28
  3. package/dist/bin/build-rig-binaries.js +0 -107
  4. package/dist/bin/rig.js +0 -11321
  5. package/dist/src/commands/_authority-runs.js +0 -111
  6. package/dist/src/commands/_cli-format.js +0 -164
  7. package/dist/src/commands/_connection-state.js +0 -123
  8. package/dist/src/commands/_doctor-checks.js +0 -507
  9. package/dist/src/commands/_help-catalog.js +0 -203
  10. package/dist/src/commands/_operator-surface.js +0 -204
  11. package/dist/src/commands/_operator-view.js +0 -1147
  12. package/dist/src/commands/_parsers.js +0 -107
  13. package/dist/src/commands/_paths.js +0 -50
  14. package/dist/src/commands/_pi-frontend.js +0 -843
  15. package/dist/src/commands/_pi-install.js +0 -185
  16. package/dist/src/commands/_pi-worker-bridge-extension.js +0 -761
  17. package/dist/src/commands/_policy.js +0 -79
  18. package/dist/src/commands/_preflight.js +0 -403
  19. package/dist/src/commands/_probes.js +0 -13
  20. package/dist/src/commands/_run-driver-helpers.js +0 -289
  21. package/dist/src/commands/_server-client.js +0 -514
  22. package/dist/src/commands/_snapshot-upload.js +0 -318
  23. package/dist/src/commands/_task-picker.js +0 -76
  24. package/dist/src/commands/agent.js +0 -499
  25. package/dist/src/commands/browser.js +0 -890
  26. package/dist/src/commands/connect.js +0 -288
  27. package/dist/src/commands/dist.js +0 -402
  28. package/dist/src/commands/doctor.js +0 -517
  29. package/dist/src/commands/github.js +0 -281
  30. package/dist/src/commands/inbox.js +0 -160
  31. package/dist/src/commands/init.js +0 -1563
  32. package/dist/src/commands/inspect.js +0 -174
  33. package/dist/src/commands/inspector.js +0 -256
  34. package/dist/src/commands/plugin.js +0 -167
  35. package/dist/src/commands/profile-and-review.js +0 -178
  36. package/dist/src/commands/queue.js +0 -198
  37. package/dist/src/commands/remote.js +0 -507
  38. package/dist/src/commands/repo-git-harness.js +0 -221
  39. package/dist/src/commands/run.js +0 -1688
  40. package/dist/src/commands/server.js +0 -571
  41. package/dist/src/commands/setup.js +0 -687
  42. package/dist/src/commands/task-report-bug.js +0 -1083
  43. package/dist/src/commands/task-run-driver.js +0 -2600
  44. package/dist/src/commands/task.js +0 -2200
  45. package/dist/src/commands/test.js +0 -39
  46. package/dist/src/commands/workspace.js +0 -123
  47. package/dist/src/commands.js +0 -11000
  48. package/dist/src/index.js +0 -11339
  49. package/dist/src/launcher.js +0 -133
  50. package/dist/src/report-bug.js +0 -260
  51. package/dist/src/runner.js +0 -273
  52. package/dist/src/withMutedConsole.js +0 -42
@@ -1,288 +0,0 @@
1
- // @bun
2
- // packages/cli/src/commands/connect.ts
3
- import { cancel, isCancel, select } from "@clack/prompts";
4
-
5
- // packages/cli/src/runner.ts
6
- import { EventBus } from "@rig/runtime/control-plane/runtime/events";
7
- import { CliError } from "@rig/runtime/control-plane/errors";
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
- 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/_connection-state.ts
21
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
22
- import { homedir } from "os";
23
- import { dirname, resolve } from "path";
24
- function resolveGlobalConnectionsPath(env = process.env) {
25
- const explicit = env.RIG_CONNECTIONS_FILE?.trim();
26
- if (explicit)
27
- return resolve(explicit);
28
- const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
29
- if (stateDir)
30
- return resolve(stateDir, "connections.json");
31
- return resolve(homedir(), ".rig", "connections.json");
32
- }
33
- function resolveRepoConnectionPath(projectRoot) {
34
- return resolve(projectRoot, ".rig", "state", "connection.json");
35
- }
36
- function readJsonFile(path) {
37
- if (!existsSync(path))
38
- return null;
39
- try {
40
- return JSON.parse(readFileSync(path, "utf8"));
41
- } catch (error) {
42
- throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
43
- }
44
- }
45
- function writeJsonFile(path, value) {
46
- mkdirSync(dirname(path), { recursive: true });
47
- writeFileSync(path, `${JSON.stringify(value, null, 2)}
48
- `, "utf8");
49
- }
50
- function normalizeConnection(value) {
51
- if (!value || typeof value !== "object" || Array.isArray(value))
52
- return null;
53
- const record = value;
54
- if (record.kind === "local")
55
- return { kind: "local", mode: "auto" };
56
- if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
57
- const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
58
- return { kind: "remote", baseUrl };
59
- }
60
- return null;
61
- }
62
- function readGlobalConnections(options = {}) {
63
- const path = resolveGlobalConnectionsPath(options.env ?? process.env);
64
- const payload = readJsonFile(path);
65
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
66
- return { connections: {} };
67
- }
68
- const rawConnections = payload.connections;
69
- const connections = {};
70
- if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
71
- for (const [alias, raw] of Object.entries(rawConnections)) {
72
- const connection = normalizeConnection(raw);
73
- if (connection)
74
- connections[alias] = connection;
75
- }
76
- }
77
- return { connections };
78
- }
79
- function writeGlobalConnections(state, options = {}) {
80
- writeJsonFile(resolveGlobalConnectionsPath(options.env ?? process.env), state);
81
- }
82
- function upsertGlobalConnection(alias, connection, options = {}) {
83
- const cleanAlias = alias.trim();
84
- if (!cleanAlias)
85
- throw new CliError2("Connection alias is required.", 1);
86
- const state = readGlobalConnections(options);
87
- state.connections[cleanAlias] = connection;
88
- writeGlobalConnections(state, options);
89
- return state;
90
- }
91
- function readRepoConnection(projectRoot) {
92
- const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
93
- if (!payload || typeof payload !== "object" || Array.isArray(payload))
94
- return null;
95
- const record = payload;
96
- const selected = typeof record.selected === "string" ? record.selected.trim() : "";
97
- if (!selected)
98
- return null;
99
- return {
100
- selected,
101
- project: typeof record.project === "string" ? record.project : undefined,
102
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
103
- };
104
- }
105
- function writeRepoConnection(projectRoot, state) {
106
- writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
107
- }
108
-
109
- // packages/cli/src/commands/_cli-format.ts
110
- import pc from "picocolors";
111
- function truncate(value, width) {
112
- if (value.length <= width)
113
- return value;
114
- if (width <= 1)
115
- return "\u2026";
116
- return `${value.slice(0, width - 1)}\u2026`;
117
- }
118
- function pad(value, width) {
119
- return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
120
- }
121
- function statusColor(status) {
122
- const normalized = status.toLowerCase();
123
- if (["completed", "merged", "closed", "done", "accepted", "pass", "selected"].includes(normalized))
124
- return pc.green;
125
- if (["failed", "needs_attention", "needs-attention", "blocked", "error"].includes(normalized))
126
- return pc.red;
127
- if (["running", "reviewing", "validating", "in_progress", "in-progress", "remote"].includes(normalized))
128
- return pc.cyan;
129
- if (["ready", "open", "queued", "created", "preparing", "local"].includes(normalized))
130
- return pc.yellow;
131
- return pc.dim;
132
- }
133
- function formatStatusPill(status) {
134
- const label = status || "unknown";
135
- return statusColor(label)(`\u25CF ${label}`);
136
- }
137
- function formatSection(title, subtitle) {
138
- return `${pc.bold(pc.cyan("\u25C6"))} ${pc.bold(title)}${subtitle ? pc.dim(` \u2014 ${subtitle}`) : ""}`;
139
- }
140
- function formatSuccessCard(title, rows = []) {
141
- const body = rows.filter(([, value]) => value !== undefined && value !== null && String(value).length > 0).map(([key, value]) => `${pc.dim("\u2502")} ${pc.dim(key.padEnd(9))} ${value}`);
142
- return [formatSection(title), ...body].join(`
143
- `);
144
- }
145
- function formatNextSteps(steps) {
146
- if (steps.length === 0)
147
- return [];
148
- return [pc.bold("Next"), ...steps.map((step) => `${pc.dim("\u203A")} ${step}`)];
149
- }
150
- function formatConnectionList(connections) {
151
- const rows = [["local", { kind: "local", mode: "auto" }], ...Object.entries(connections)];
152
- const aliasWidth = Math.min(24, Math.max(5, ...rows.map(([alias]) => alias.length)));
153
- const lines = rows.map(([alias, connection]) => [
154
- pc.bold(pad(truncate(alias, aliasWidth), aliasWidth)),
155
- formatStatusPill(connection.kind),
156
- connection.kind === "remote" ? connection.baseUrl ?? "" : connection.mode ?? "local"
157
- ].join(" "));
158
- return [formatSection("Rig servers", `${rows.length} available`), `${pc.bold(pad("ALIAS", aliasWidth))} ${pc.bold("KIND")} ${pc.bold("TARGET")}`, ...lines, "", ...formatNextSteps(["Select one: `rig server use <alias|local>`"])].join(`
159
- `);
160
- }
161
- function formatConnectionStatus(selected, connections) {
162
- const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
163
- const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
164
- return [
165
- formatSection("Rig server", "selected for this repo"),
166
- `${pc.dim("\u2502")} ${pc.dim("selected ")} ${pc.bold(selected)}`,
167
- `${pc.dim("\u2502")} ${pc.dim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
168
- `${pc.dim("\u2502")} ${pc.dim("target ")} ${target ?? "not configured"}`,
169
- "",
170
- ...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
171
- ].join(`
172
- `);
173
- }
174
-
175
- // packages/cli/src/commands/connect.ts
176
- function usageName(options) {
177
- return `rig ${options.group}`;
178
- }
179
- function parseConnection(alias, value, options) {
180
- if (alias === "local" && !value)
181
- return { kind: "local", mode: "auto" };
182
- if (!value)
183
- throw new CliError2(`Missing remote server URL. Usage: ${usageName(options)} add <alias> <url>`, 1);
184
- let parsed;
185
- try {
186
- parsed = new URL(value);
187
- } catch {
188
- throw new CliError2(`Invalid Rig server URL: ${value}`, 1);
189
- }
190
- if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
191
- throw new CliError2("Rig remote server URL must be http(s).", 1);
192
- }
193
- return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
194
- }
195
- function printJsonOrText(context, payload, text) {
196
- if (context.outputMode === "json") {
197
- console.log(JSON.stringify(payload, null, 2));
198
- } else {
199
- console.log(text);
200
- }
201
- }
202
- async function promptForConnectionAlias(context) {
203
- const state = readGlobalConnections();
204
- const repo = readRepoConnection(context.projectRoot);
205
- const options = [
206
- { value: "local", label: "local", hint: "Use/start a local Rig server" },
207
- ...Object.entries(state.connections).map(([alias, connection]) => ({
208
- value: alias,
209
- label: alias,
210
- hint: connection.kind === "remote" ? connection.baseUrl : "local"
211
- }))
212
- ].filter((option, index, all) => all.findIndex((candidate) => candidate.value === option.value) === index);
213
- const answer = await select({
214
- message: "Select Rig server for this repo",
215
- initialValue: repo?.selected ?? "local",
216
- options
217
- });
218
- if (isCancel(answer)) {
219
- cancel("No server selected.");
220
- throw new CliError2("No server selected.", 3);
221
- }
222
- return String(answer);
223
- }
224
- async function executeConnectionCommand(context, args, options) {
225
- const [command, ...rest] = args;
226
- switch (command ?? "status") {
227
- case "list": {
228
- requireNoExtraArgs(rest, `${usageName(options)} list`);
229
- const state = readGlobalConnections();
230
- printJsonOrText(context, state, formatConnectionList(state.connections));
231
- return { ok: true, group: options.group, command: "list", details: state };
232
- }
233
- case "add": {
234
- const [alias, url, ...extra] = rest;
235
- if (!alias)
236
- throw new CliError2(`Missing alias. Usage: ${usageName(options)} add <alias> <url>`, 1);
237
- requireNoExtraArgs(extra, `${usageName(options)} add <alias> <url>`);
238
- const connection = parseConnection(alias, url, options);
239
- const state = upsertGlobalConnection(alias, connection);
240
- printJsonOrText(context, { alias, connection }, formatSuccessCard("Rig server saved", [
241
- ["alias", alias],
242
- ["target", connection.kind === "remote" ? connection.baseUrl : "local"],
243
- ["next", `${usageName(options)} use ${alias}`]
244
- ]));
245
- return { ok: true, group: options.group, command: "add", details: { alias, connection, count: Object.keys(state.connections).length } };
246
- }
247
- case "use": {
248
- let [alias, ...extra] = rest;
249
- requireNoExtraArgs(extra, `${usageName(options)} use <alias|local>`);
250
- if (!alias && options.interactiveUse && context.outputMode === "text" && process.stdin.isTTY) {
251
- alias = await promptForConnectionAlias(context);
252
- }
253
- if (!alias)
254
- throw new CliError2(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
255
- if (alias !== "local") {
256
- const state = readGlobalConnections();
257
- if (!state.connections[alias])
258
- throw new CliError2(`Unknown Rig server: ${alias}`, 1);
259
- }
260
- const repoState = { selected: alias, linkedAt: new Date().toISOString() };
261
- writeRepoConnection(context.projectRoot, repoState);
262
- printJsonOrText(context, repoState, formatSuccessCard("Rig server selected", [
263
- ["selected", alias],
264
- ["scope", "this repo"],
265
- ["next", "rig task list"]
266
- ]));
267
- return { ok: true, group: options.group, command: "use", details: repoState };
268
- }
269
- case "status": {
270
- requireNoExtraArgs(rest, `${usageName(options)} status`);
271
- const repo = readRepoConnection(context.projectRoot);
272
- const global = readGlobalConnections();
273
- const details = { selected: repo?.selected ?? "local", repo, connections: global.connections };
274
- printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections));
275
- return { ok: true, group: options.group, command: "status", details };
276
- }
277
- default:
278
- throw new CliError2(`Unknown ${options.group} command: ${String(command)}
279
- Usage: ${usageName(options)} <list|add|use|status>`, 1);
280
- }
281
- }
282
- async function executeConnect(context, args) {
283
- return executeConnectionCommand(context, args, { group: "connect" });
284
- }
285
- export {
286
- executeConnectionCommand,
287
- executeConnect
288
- };
@@ -1,402 +0,0 @@
1
- // @bun
2
- // packages/cli/src/commands/dist.ts
3
- import {
4
- chmodSync,
5
- copyFileSync,
6
- existsSync,
7
- mkdirSync,
8
- readdirSync,
9
- readlinkSync,
10
- rmSync,
11
- statSync,
12
- symlinkSync,
13
- unlinkSync
14
- } from "fs";
15
- import { homedir as homedir2 } from "os";
16
- import { resolve as resolve3 } from "path";
17
-
18
- // packages/cli/src/runner.ts
19
- import { EventBus } from "@rig/runtime/control-plane/runtime/events";
20
- import { CliError } from "@rig/runtime/control-plane/errors";
21
- import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
22
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
23
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
24
- import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
25
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
26
- function takeOption(args, option) {
27
- const rest = [];
28
- let value;
29
- for (let index = 0;index < args.length; index += 1) {
30
- const current = args[index];
31
- if (current === option) {
32
- const next = args[index + 1];
33
- if (!next || next.startsWith("-")) {
34
- throw new CliError(`Missing value for ${option}`);
35
- }
36
- value = next;
37
- index += 1;
38
- continue;
39
- }
40
- if (current !== undefined) {
41
- rest.push(current);
42
- }
43
- }
44
- return { value, rest };
45
- }
46
- function requireNoExtraArgs(args, usage) {
47
- if (args.length > 0) {
48
- throw new CliError(`Unexpected arguments: ${args.join(" ")}
49
- Usage: ${usage}`);
50
- }
51
- }
52
-
53
- // packages/cli/src/commands/dist.ts
54
- import { buildBinary as buildBinary2 } from "@rig/runtime/control-plane/runtime/isolation";
55
- import {
56
- computeRuntimeImageFingerprint,
57
- computeRuntimeImageId
58
- } from "@rig/runtime/control-plane/runtime/image/index";
59
-
60
- // packages/cli/src/commands/_parsers.ts
61
- import { homedir } from "os";
62
- import { resolve } from "path";
63
- function parseInstallScope(value) {
64
- if (!value || value === "user") {
65
- return "user";
66
- }
67
- if (value === "system") {
68
- return "system";
69
- }
70
- throw new CliError2(`Invalid --scope value: ${value}. Use user|system.`);
71
- }
72
- function resolveInstallDir(scope, explicitPath) {
73
- if (explicitPath) {
74
- return resolve(explicitPath);
75
- }
76
- if (scope === "system") {
77
- return "/usr/local/bin";
78
- }
79
- return resolve(homedir(), ".local/bin");
80
- }
81
-
82
- // packages/cli/src/commands/_paths.ts
83
- import { resolve as resolve2 } from "path";
84
- import { resolveMonorepoRoot } from "@rig/runtime/control-plane/native/utils";
85
- function resolveControlPlaneMonorepoRoot(projectRoot) {
86
- return resolveMonorepoRoot(projectRoot);
87
- }
88
- function resolveControlPlaneHostStateRoot(projectRoot) {
89
- return resolve2(projectRoot, ".rig");
90
- }
91
- function resolveControlPlaneHostBinDir(projectRoot) {
92
- return resolve2(resolveControlPlaneHostStateRoot(projectRoot), "bin");
93
- }
94
- function resolveControlPlaneHostDistDir(projectRoot) {
95
- return resolve2(resolveControlPlaneHostStateRoot(projectRoot), "dist");
96
- }
97
- function resolveControlPlaneMonorepoStateRoot(projectRoot) {
98
- return resolve2(resolveControlPlaneMonorepoRoot(projectRoot), ".rig");
99
- }
100
- function resolveControlPlaneMonorepoRuntimeDir(projectRoot) {
101
- return resolve2(resolveControlPlaneMonorepoStateRoot(projectRoot), "runtime");
102
- }
103
-
104
- // packages/cli/src/commands/_probes.ts
105
- async function runQuietBinaryProbe(binaryPath, args, cwd) {
106
- try {
107
- const run = await Bun.$`${binaryPath} ${args}`.cwd(cwd).quiet().nothrow();
108
- return run.exitCode === 0;
109
- } catch {
110
- return false;
111
- }
112
- }
113
-
114
- // packages/cli/src/commands/dist.ts
115
- function collectRigValidatorBuildTargets(input) {
116
- const validatorsRoot = resolve3(input.hostProjectRoot, "packages/runtime/src/control-plane/validators");
117
- if (!existsSync(validatorsRoot))
118
- return [];
119
- const targets = [];
120
- const categories = readdirSync(validatorsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
121
- for (const category of categories) {
122
- const categoryDir = resolve3(validatorsRoot, category.name);
123
- for (const entry of readdirSync(categoryDir, { withFileTypes: true })) {
124
- if (!entry.isFile() || !entry.name.endsWith(".ts"))
125
- continue;
126
- const check = entry.name.replace(/\.ts$/, "");
127
- if (!check || check === "index" || check === "shared")
128
- continue;
129
- targets.push({
130
- source: `packages/runtime/src/control-plane/validators/${category.name}/${entry.name}`,
131
- dest: resolve3(input.imageDir, `bin/validators/${category.name}-${check}`),
132
- cwd: input.hostProjectRoot
133
- });
134
- }
135
- }
136
- return targets;
137
- }
138
- async function findLatestDistBinary(projectRoot) {
139
- const distRoot = resolveControlPlaneHostDistDir(projectRoot);
140
- if (!existsSync(distRoot)) {
141
- return null;
142
- }
143
- const entries = readdirSync(distRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("rig-")).map((entry) => ({
144
- name: entry.name,
145
- mtimeMs: statSync(resolve3(distRoot, entry.name)).mtimeMs
146
- })).sort((a, b) => b.mtimeMs - a.mtimeMs || b.name.localeCompare(a.name));
147
- for (const { name } of entries) {
148
- const candidate = resolve3(distRoot, name, "bin", "rig");
149
- if (existsSync(candidate) && await isRunnableRigBinary(candidate, projectRoot)) {
150
- return candidate;
151
- }
152
- }
153
- return null;
154
- }
155
- async function isRunnableRigBinary(binaryPath, projectRoot) {
156
- return runQuietBinaryProbe(binaryPath, ["help"], projectRoot);
157
- }
158
- async function runDistDoctor(projectRoot) {
159
- const bunPath = Bun.which("bun");
160
- const rigPath = Bun.which("rig");
161
- const userBinDir = resolve3(homedir2(), ".local/bin");
162
- const userBinInPath = (process.env.PATH || "").split(":").filter(Boolean).includes(userBinDir);
163
- let rigRunnable = false;
164
- if (rigPath) {
165
- rigRunnable = await runQuietBinaryProbe(rigPath, ["help"], projectRoot);
166
- }
167
- return {
168
- bun: {
169
- available: Boolean(bunPath),
170
- path: bunPath || null,
171
- version: Bun.version
172
- },
173
- rig: {
174
- onPath: Boolean(rigPath),
175
- path: rigPath || null,
176
- runnable: rigRunnable
177
- },
178
- userBinDir,
179
- userBinInPath,
180
- latestDistBinary: await findLatestDistBinary(projectRoot)
181
- };
182
- }
183
- async function executeDist(context, args) {
184
- const [command = "build", ...rest] = args;
185
- switch (command) {
186
- case "build": {
187
- const { value: outputDir, rest: pending } = takeOption(rest, "--output-dir");
188
- requireNoExtraArgs(pending, "bun run rig dist build [--output-dir <dir>]");
189
- const commandParts = ["bun", "run", "packages/cli/bin/build-rig-binaries.ts"];
190
- if (outputDir) {
191
- commandParts.push("--output-dir", outputDir);
192
- }
193
- await context.runCommand(commandParts);
194
- return { ok: true, group: "dist", command, details: { outputDir: outputDir || null } };
195
- }
196
- case "install": {
197
- let pending = rest;
198
- const scopeResult = takeOption(pending, "--scope");
199
- pending = scopeResult.rest;
200
- const pathResult = takeOption(pending, "--path");
201
- pending = pathResult.rest;
202
- requireNoExtraArgs(pending, "bun run rig dist install [--scope user|system] [--path <dir>]");
203
- const scope = parseInstallScope(scopeResult.value);
204
- const installDir = resolveInstallDir(scope, pathResult.value);
205
- mkdirSync(installDir, { recursive: true });
206
- let source = await findLatestDistBinary(context.projectRoot);
207
- let buildDir = null;
208
- if (!source) {
209
- buildDir = resolve3(resolveControlPlaneHostDistDir(context.projectRoot), `rig-install-${Date.now()}`);
210
- await context.runCommand(["bun", "run", "packages/cli/bin/build-rig-binaries.ts", "--output-dir", buildDir]);
211
- source = resolve3(buildDir, "bin", "rig");
212
- }
213
- if (!existsSync(source)) {
214
- throw new CliError2(`Unable to locate rig binary at ${source}.`, 2);
215
- }
216
- const installedPath = resolve3(installDir, "rig");
217
- if (existsSync(installedPath)) {
218
- unlinkSync(installedPath);
219
- }
220
- copyFileSync(source, installedPath);
221
- chmodSync(installedPath, 493);
222
- const pathEntries = (process.env.PATH || "").split(":").filter(Boolean);
223
- const inPath = pathEntries.includes(installDir);
224
- if (context.outputMode === "text") {
225
- console.log(`Installed: ${installedPath}`);
226
- if (!inPath) {
227
- console.log(`PATH note: add ${installDir} to PATH to run 'rig' directly.`);
228
- }
229
- }
230
- return {
231
- ok: true,
232
- group: "dist",
233
- command,
234
- details: {
235
- scope,
236
- installDir,
237
- installedPath,
238
- sourcePath: source,
239
- builtNow: Boolean(buildDir),
240
- inPath
241
- }
242
- };
243
- }
244
- case "doctor": {
245
- requireNoExtraArgs(rest, "bun run rig dist doctor");
246
- const details = await runDistDoctor(context.projectRoot);
247
- if (context.outputMode === "text") {
248
- console.log(`bun: ${details.bun.available ? `ok (${details.bun.version})` : "missing"}`);
249
- console.log(`rig on PATH: ${details.rig.onPath ? details.rig.path : "missing"}`);
250
- console.log(`user bin dir: ${details.userBinDir} (${details.userBinInPath ? "in PATH" : "not in PATH"})`);
251
- console.log(`latest dist binary: ${details.latestDistBinary || "none"}`);
252
- }
253
- return { ok: true, group: "dist", command, details };
254
- }
255
- case "rebuild-agent": {
256
- requireNoExtraArgs(rest, "bun run rig dist rebuild-agent");
257
- const fp = await computeRuntimeImageFingerprint(context.projectRoot);
258
- const currentId = computeRuntimeImageId(fp);
259
- const imagesDir = resolve3(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
260
- mkdirSync(imagesDir, { recursive: true });
261
- let pruned = 0;
262
- for (const entry of readdirSync(imagesDir, { withFileTypes: true })) {
263
- if (entry.isDirectory() && entry.name !== currentId) {
264
- rmSync(resolve3(imagesDir, entry.name), { recursive: true, force: true });
265
- pruned++;
266
- }
267
- }
268
- if (pruned > 0 && context.outputMode === "text") {
269
- console.log(`Pruned ${pruned} stale image(s).`);
270
- }
271
- const imageDir = resolve3(imagesDir, currentId);
272
- mkdirSync(resolve3(imageDir, "bin/hooks"), { recursive: true });
273
- mkdirSync(resolve3(imageDir, "bin/plugins"), { recursive: true });
274
- mkdirSync(resolve3(imageDir, "bin/validators"), { recursive: true });
275
- const hookNames = [
276
- "scope-guard",
277
- "import-guard",
278
- "safety-guard",
279
- "test-integrity-guard",
280
- "audit-trail",
281
- "post-edit-lint",
282
- "completion-verification",
283
- "inject-context",
284
- "task-runtime-start"
285
- ];
286
- const targets = [];
287
- const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim() || context.projectRoot;
288
- targets.push({ source: "packages/runtime/bin/rig-agent.ts", dest: resolve3(imageDir, "bin/rig-agent"), cwd: hostProjectRoot });
289
- targets.push({ source: "packages/runtime/bin/rig-agent-dispatch.ts", dest: resolve3(resolveControlPlaneHostBinDir(context.projectRoot), "rig-agent"), cwd: hostProjectRoot });
290
- for (const hookName of hookNames) {
291
- const src = `packages/runtime/src/control-plane/hooks/${hookName}.ts`;
292
- targets.push({ source: src, dest: resolve3(imageDir, `bin/hooks/${hookName}`), cwd: hostProjectRoot });
293
- targets.push({ source: src, dest: resolve3(resolveControlPlaneHostBinDir(context.projectRoot), `hooks/${hookName}`), cwd: hostProjectRoot });
294
- }
295
- const pluginsDir = resolve3(context.projectRoot, "rig/plugins");
296
- const binPluginsDir = resolve3(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
297
- const validatorsRoot = resolve3(hostProjectRoot, "packages/runtime/src/control-plane/validators");
298
- const binValidatorsDir = resolve3(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
299
- mkdirSync(binPluginsDir, { recursive: true });
300
- mkdirSync(binValidatorsDir, { recursive: true });
301
- if (existsSync(pluginsDir)) {
302
- for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
303
- const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
304
- if (!m)
305
- continue;
306
- targets.push({ source: `rig/plugins/${entry.name}`, dest: resolve3(imageDir, `bin/plugins/${m[1]}`), cwd: context.projectRoot });
307
- }
308
- }
309
- targets.push(...collectRigValidatorBuildTargets({ contextProjectRoot: context.projectRoot, hostProjectRoot, imageDir }));
310
- for (const { source, dest, cwd } of targets) {
311
- if (context.outputMode === "text") {
312
- console.log(`Building: ${dest}`);
313
- }
314
- const isValidator = dest.includes("/bin/validators/");
315
- await buildBinary2(source, dest, cwd, isValidator ? { AGENT_BUN_PATH: Bun.which("bun") || "bun" } : { AGENT_PROJECT_ROOT: context.projectRoot });
316
- }
317
- if (existsSync(pluginsDir)) {
318
- for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
319
- const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
320
- if (!m)
321
- continue;
322
- const pluginName = m[1];
323
- const imageBin = resolve3(imageDir, `bin/plugins/${pluginName}`);
324
- if (!pluginName)
325
- continue;
326
- const symlinkPath = resolve3(binPluginsDir, pluginName);
327
- if (existsSync(imageBin)) {
328
- try {
329
- unlinkSync(symlinkPath);
330
- } catch {}
331
- symlinkSync(imageBin, symlinkPath);
332
- }
333
- }
334
- }
335
- if (existsSync(validatorsRoot)) {
336
- const categories = readdirSync(validatorsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
337
- for (const category of categories) {
338
- const categoryDir = resolve3(validatorsRoot, category.name);
339
- for (const entry of readdirSync(categoryDir, { withFileTypes: true })) {
340
- if (!entry.isFile() || !entry.name.endsWith(".ts"))
341
- continue;
342
- const check = entry.name.replace(/\.ts$/, "");
343
- if (!check || check === "index" || check === "shared")
344
- continue;
345
- const validatorName = `${category.name}-${check}`;
346
- const imageBin = resolve3(imageDir, `bin/validators/${validatorName}`);
347
- const symlinkPath = resolve3(binValidatorsDir, validatorName);
348
- if (existsSync(imageBin)) {
349
- try {
350
- unlinkSync(symlinkPath);
351
- } catch {}
352
- symlinkSync(imageBin, symlinkPath);
353
- }
354
- }
355
- }
356
- }
357
- const agentsDir = resolve3(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "agents");
358
- if (existsSync(agentsDir)) {
359
- let relinkCount = 0;
360
- for (const agentEntry of readdirSync(agentsDir, { withFileTypes: true })) {
361
- if (!agentEntry.isDirectory())
362
- continue;
363
- const agentBinDir = resolve3(agentsDir, agentEntry.name, "worktree", ".rig", "bin");
364
- if (!existsSync(agentBinDir))
365
- continue;
366
- const walkDir = (dir) => {
367
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
368
- const fullPath = resolve3(dir, entry.name);
369
- if (entry.isDirectory()) {
370
- walkDir(fullPath);
371
- } else if (entry.isSymbolicLink()) {
372
- const target = readlinkSync(fullPath);
373
- if (target.includes("/.rig/runtime/images/") && !target.includes(`/${currentId}/`)) {
374
- const newTarget = target.replace(/\/\.rig\/runtime\/images\/[^/]+\//, `/.rig/runtime/images/${currentId}/`);
375
- try {
376
- unlinkSync(fullPath);
377
- symlinkSync(newTarget, fullPath);
378
- relinkCount++;
379
- } catch {}
380
- }
381
- }
382
- }
383
- };
384
- walkDir(agentBinDir);
385
- }
386
- if (relinkCount > 0 && context.outputMode === "text") {
387
- console.log(`Re-linked ${relinkCount} worktree symlink(s) to image ${currentId}.`);
388
- }
389
- }
390
- if (context.outputMode === "text") {
391
- console.log(`Rebuilt ${targets.length} binaries into image ${currentId}.`);
392
- }
393
- return { ok: true, group: "dist", command, details: { imageId: currentId, count: targets.length } };
394
- }
395
- default:
396
- throw new CliError2(`Unknown dist command: ${command}`);
397
- }
398
- }
399
- export {
400
- executeDist,
401
- collectRigValidatorBuildTargets
402
- };