@h-rig/cli 0.0.6-alpha.0

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 (47) hide show
  1. package/README.md +30 -0
  2. package/dist/bin/build-rig-binaries.js +107 -0
  3. package/dist/bin/rig.js +9330 -0
  4. package/dist/src/commands/_authority-runs.js +110 -0
  5. package/dist/src/commands/_connection-state.js +123 -0
  6. package/dist/src/commands/_doctor-checks.js +501 -0
  7. package/dist/src/commands/_operator-view.js +322 -0
  8. package/dist/src/commands/_parsers.js +107 -0
  9. package/dist/src/commands/_paths.js +50 -0
  10. package/dist/src/commands/_pi-install.js +184 -0
  11. package/dist/src/commands/_policy.js +79 -0
  12. package/dist/src/commands/_preflight.js +460 -0
  13. package/dist/src/commands/_probes.js +13 -0
  14. package/dist/src/commands/_run-driver-helpers.js +289 -0
  15. package/dist/src/commands/_server-client.js +364 -0
  16. package/dist/src/commands/_snapshot-upload.js +313 -0
  17. package/dist/src/commands/_task-picker.js +48 -0
  18. package/dist/src/commands/agent.js +497 -0
  19. package/dist/src/commands/browser.js +890 -0
  20. package/dist/src/commands/connect.js +180 -0
  21. package/dist/src/commands/dist.js +402 -0
  22. package/dist/src/commands/doctor.js +511 -0
  23. package/dist/src/commands/github.js +276 -0
  24. package/dist/src/commands/inbox.js +160 -0
  25. package/dist/src/commands/init.js +1254 -0
  26. package/dist/src/commands/inspect.js +174 -0
  27. package/dist/src/commands/inspector.js +256 -0
  28. package/dist/src/commands/plugin.js +167 -0
  29. package/dist/src/commands/profile-and-review.js +178 -0
  30. package/dist/src/commands/queue.js +197 -0
  31. package/dist/src/commands/remote.js +507 -0
  32. package/dist/src/commands/repo-git-harness.js +221 -0
  33. package/dist/src/commands/run.js +753 -0
  34. package/dist/src/commands/server.js +368 -0
  35. package/dist/src/commands/setup.js +681 -0
  36. package/dist/src/commands/task-report-bug.js +1083 -0
  37. package/dist/src/commands/task-run-driver.js +1933 -0
  38. package/dist/src/commands/task.js +1325 -0
  39. package/dist/src/commands/test.js +39 -0
  40. package/dist/src/commands/workspace.js +123 -0
  41. package/dist/src/commands.js +9012 -0
  42. package/dist/src/index.js +9348 -0
  43. package/dist/src/launcher.js +131 -0
  44. package/dist/src/report-bug.js +260 -0
  45. package/dist/src/runner.js +272 -0
  46. package/dist/src/withMutedConsole.js +42 -0
  47. package/package.json +31 -0
@@ -0,0 +1,180 @@
1
+ // @bun
2
+ // packages/cli/src/runner.ts
3
+ import { EventBus } from "@rig/runtime/control-plane/runtime/events";
4
+ import { CliError } from "@rig/runtime/control-plane/errors";
5
+ import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
6
+ import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
7
+ import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
8
+ import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
9
+ import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
10
+ function requireNoExtraArgs(args, usage) {
11
+ if (args.length > 0) {
12
+ throw new CliError(`Unexpected arguments: ${args.join(" ")}
13
+ Usage: ${usage}`);
14
+ }
15
+ }
16
+
17
+ // packages/cli/src/commands/_connection-state.ts
18
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
19
+ import { homedir } from "os";
20
+ import { dirname, resolve } from "path";
21
+ function resolveGlobalConnectionsPath(env = process.env) {
22
+ const explicit = env.RIG_CONNECTIONS_FILE?.trim();
23
+ if (explicit)
24
+ return resolve(explicit);
25
+ const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
26
+ if (stateDir)
27
+ return resolve(stateDir, "connections.json");
28
+ return resolve(homedir(), ".rig", "connections.json");
29
+ }
30
+ function resolveRepoConnectionPath(projectRoot) {
31
+ return resolve(projectRoot, ".rig", "state", "connection.json");
32
+ }
33
+ function readJsonFile(path) {
34
+ if (!existsSync(path))
35
+ return null;
36
+ try {
37
+ return JSON.parse(readFileSync(path, "utf8"));
38
+ } catch (error) {
39
+ throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
40
+ }
41
+ }
42
+ function writeJsonFile(path, value) {
43
+ mkdirSync(dirname(path), { recursive: true });
44
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
45
+ `, "utf8");
46
+ }
47
+ function normalizeConnection(value) {
48
+ if (!value || typeof value !== "object" || Array.isArray(value))
49
+ return null;
50
+ const record = value;
51
+ if (record.kind === "local")
52
+ return { kind: "local", mode: "auto" };
53
+ if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
54
+ const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
55
+ return { kind: "remote", baseUrl };
56
+ }
57
+ return null;
58
+ }
59
+ function readGlobalConnections(options = {}) {
60
+ const path = resolveGlobalConnectionsPath(options.env ?? process.env);
61
+ const payload = readJsonFile(path);
62
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
63
+ return { connections: {} };
64
+ }
65
+ const rawConnections = payload.connections;
66
+ const connections = {};
67
+ if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
68
+ for (const [alias, raw] of Object.entries(rawConnections)) {
69
+ const connection = normalizeConnection(raw);
70
+ if (connection)
71
+ connections[alias] = connection;
72
+ }
73
+ }
74
+ return { connections };
75
+ }
76
+ function writeGlobalConnections(state, options = {}) {
77
+ writeJsonFile(resolveGlobalConnectionsPath(options.env ?? process.env), state);
78
+ }
79
+ function upsertGlobalConnection(alias, connection, options = {}) {
80
+ const cleanAlias = alias.trim();
81
+ if (!cleanAlias)
82
+ throw new CliError2("Connection alias is required.", 1);
83
+ const state = readGlobalConnections(options);
84
+ state.connections[cleanAlias] = connection;
85
+ writeGlobalConnections(state, options);
86
+ return state;
87
+ }
88
+ function readRepoConnection(projectRoot) {
89
+ const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
90
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
91
+ return null;
92
+ const record = payload;
93
+ const selected = typeof record.selected === "string" ? record.selected.trim() : "";
94
+ if (!selected)
95
+ return null;
96
+ return {
97
+ selected,
98
+ project: typeof record.project === "string" ? record.project : undefined,
99
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
100
+ };
101
+ }
102
+ function writeRepoConnection(projectRoot, state) {
103
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
104
+ }
105
+
106
+ // packages/cli/src/commands/connect.ts
107
+ function parseConnection(alias, value) {
108
+ if (alias === "local" && !value)
109
+ return { kind: "local", mode: "auto" };
110
+ if (!value)
111
+ throw new CliError2("Missing remote server URL. Usage: rig connect add <alias> <url>", 1);
112
+ let parsed;
113
+ try {
114
+ parsed = new URL(value);
115
+ } catch {
116
+ throw new CliError2(`Invalid Rig server URL: ${value}`, 1);
117
+ }
118
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
119
+ throw new CliError2("Rig remote server URL must be http(s).", 1);
120
+ }
121
+ return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
122
+ }
123
+ function printJsonOrText(context, payload, text) {
124
+ if (context.outputMode === "json") {
125
+ console.log(JSON.stringify(payload, null, 2));
126
+ } else {
127
+ console.log(text);
128
+ }
129
+ }
130
+ async function executeConnect(context, args) {
131
+ const [command, ...rest] = args;
132
+ switch (command ?? "status") {
133
+ case "list": {
134
+ requireNoExtraArgs(rest, "rig connect list");
135
+ const state = readGlobalConnections();
136
+ printJsonOrText(context, state, Object.entries(state.connections).map(([alias, connection]) => `${alias} ${connection.kind === "remote" ? connection.baseUrl : "local"}`).join(`
137
+ `) || "No Rig connections configured.");
138
+ return { ok: true, group: "connect", command: "list", details: state };
139
+ }
140
+ case "add": {
141
+ const [alias, url, ...extra] = rest;
142
+ if (!alias)
143
+ throw new CliError2("Missing alias. Usage: rig connect add <alias> <url>", 1);
144
+ requireNoExtraArgs(extra, "rig connect add <alias> <url>");
145
+ const connection = parseConnection(alias, url);
146
+ const state = upsertGlobalConnection(alias, connection);
147
+ printJsonOrText(context, { alias, connection }, `Added Rig connection ${alias}.`);
148
+ return { ok: true, group: "connect", command: "add", details: { alias, connection, count: Object.keys(state.connections).length } };
149
+ }
150
+ case "use": {
151
+ const [alias, ...extra] = rest;
152
+ if (!alias)
153
+ throw new CliError2("Missing alias. Usage: rig connect use <alias|local>", 1);
154
+ requireNoExtraArgs(extra, "rig connect use <alias|local>");
155
+ if (alias !== "local") {
156
+ const state = readGlobalConnections();
157
+ if (!state.connections[alias])
158
+ throw new CliError2(`Unknown Rig connection: ${alias}`, 1);
159
+ }
160
+ const repoState = { selected: alias, linkedAt: new Date().toISOString() };
161
+ writeRepoConnection(context.projectRoot, repoState);
162
+ printJsonOrText(context, repoState, `Selected Rig connection ${alias} for this repo.`);
163
+ return { ok: true, group: "connect", command: "use", details: repoState };
164
+ }
165
+ case "status": {
166
+ requireNoExtraArgs(rest, "rig connect status");
167
+ const repo = readRepoConnection(context.projectRoot);
168
+ const global = readGlobalConnections();
169
+ const details = { selected: repo?.selected ?? "local", repo, connections: global.connections };
170
+ printJsonOrText(context, details, `Selected Rig connection: ${details.selected}`);
171
+ return { ok: true, group: "connect", command: "status", details };
172
+ }
173
+ default:
174
+ throw new CliError2(`Unknown connect command: ${String(command)}
175
+ Usage: rig connect <list|add|use|status>`, 1);
176
+ }
177
+ }
178
+ export {
179
+ executeConnect
180
+ };
@@ -0,0 +1,402 @@
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
+ };