@h-rig/cli 0.0.6-alpha.7 → 0.0.6-alpha.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin/rig.js +4507 -1506
- package/dist/src/commands/_async-ui.js +152 -0
- package/dist/src/commands/_authority-runs.js +2 -3
- package/dist/src/commands/_cli-format.js +369 -0
- package/dist/src/commands/_connection-state.js +30 -11
- package/dist/src/commands/_doctor-checks.js +177 -43
- package/dist/src/commands/_help-catalog.js +485 -0
- package/dist/src/commands/_json-output.js +56 -0
- package/dist/src/commands/_operator-surface.js +220 -0
- package/dist/src/commands/_operator-view.js +595 -72
- package/dist/src/commands/_parsers.js +18 -11
- package/dist/src/commands/_pi-frontend.js +411 -0
- package/dist/src/commands/_pi-install.js +4 -3
- package/dist/src/commands/_policy.js +12 -5
- package/dist/src/commands/_preflight.js +187 -127
- package/dist/src/commands/_run-driver-helpers.js +75 -22
- package/dist/src/commands/_run-replay.js +142 -0
- package/dist/src/commands/_server-client.js +343 -60
- package/dist/src/commands/_snapshot-upload.js +160 -38
- package/dist/src/commands/_spinner.js +65 -0
- package/dist/src/commands/_task-picker.js +44 -16
- package/dist/src/commands/agent.js +39 -20
- package/dist/src/commands/browser.js +28 -21
- package/dist/src/commands/connect.js +146 -33
- package/dist/src/commands/dist.js +19 -12
- package/dist/src/commands/doctor.js +304 -44
- package/dist/src/commands/github.js +301 -52
- package/dist/src/commands/inbox.js +679 -72
- package/dist/src/commands/init.js +622 -118
- package/dist/src/commands/inspect.js +515 -32
- package/dist/src/commands/inspector.js +20 -13
- package/dist/src/commands/pi.js +177 -0
- package/dist/src/commands/plugin.js +95 -27
- package/dist/src/commands/profile-and-review.js +26 -19
- package/dist/src/commands/queue.js +32 -12
- package/dist/src/commands/remote.js +43 -36
- package/dist/src/commands/repo-git-harness.js +22 -15
- package/dist/src/commands/run.js +1162 -158
- package/dist/src/commands/server.js +373 -56
- package/dist/src/commands/setup.js +316 -62
- package/dist/src/commands/stats.js +1030 -0
- package/dist/src/commands/task-report-bug.js +29 -22
- package/dist/src/commands/task-run-driver.js +862 -129
- package/dist/src/commands/task.js +1423 -311
- package/dist/src/commands/test.js +15 -8
- package/dist/src/commands/workspace.js +18 -11
- package/dist/src/commands.js +4446 -1499
- package/dist/src/index.js +4502 -1504
- package/dist/src/launcher.js +77 -13
- package/dist/src/report-bug.js +3 -3
- package/dist/src/runner.js +16 -22
- package/package.json +10 -5
|
@@ -10,12 +10,19 @@ import pc2 from "picocolors";
|
|
|
10
10
|
|
|
11
11
|
// packages/cli/src/runner.ts
|
|
12
12
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
13
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
13
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
14
14
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
15
|
-
import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
16
|
-
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
17
15
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
18
|
-
|
|
16
|
+
|
|
17
|
+
class CliError extends RuntimeCliError {
|
|
18
|
+
hint;
|
|
19
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
20
|
+
super(message, exitCode);
|
|
21
|
+
if (options.hint?.trim()) {
|
|
22
|
+
this.hint = options.hint.trim();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
19
26
|
function takeFlag(args, flag) {
|
|
20
27
|
const rest = [];
|
|
21
28
|
let value = false;
|
|
@@ -36,7 +43,7 @@ function takeOption(args, option) {
|
|
|
36
43
|
if (current === option) {
|
|
37
44
|
const next = args[index + 1];
|
|
38
45
|
if (!next || next.startsWith("-")) {
|
|
39
|
-
throw new CliError(`Missing value for ${option}`);
|
|
46
|
+
throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
|
|
40
47
|
}
|
|
41
48
|
value = next;
|
|
42
49
|
index += 1;
|
|
@@ -95,7 +102,7 @@ async function promptBugConfirm(message, initialValue) {
|
|
|
95
102
|
function unwrapClackPrompt(result) {
|
|
96
103
|
if (clack.isCancel(result)) {
|
|
97
104
|
clack.cancel("Bug report cancelled.");
|
|
98
|
-
throw new
|
|
105
|
+
throw new CliError("Bug report cancelled by user.");
|
|
99
106
|
}
|
|
100
107
|
return result;
|
|
101
108
|
}
|
|
@@ -351,7 +358,7 @@ async function executeBrowser(context, args) {
|
|
|
351
358
|
return { ok: true, group: "browser", command: "help" };
|
|
352
359
|
}
|
|
353
360
|
if (command === "explain") {
|
|
354
|
-
requireNoExtraArgs(rest, "
|
|
361
|
+
requireNoExtraArgs(rest, "rig browser explain");
|
|
355
362
|
console.log(browserAgentUsageText());
|
|
356
363
|
return { ok: true, group: "browser", command: "explain" };
|
|
357
364
|
}
|
|
@@ -361,7 +368,7 @@ async function executeBrowser(context, args) {
|
|
|
361
368
|
if (command === "app" || command === "hp-next") {
|
|
362
369
|
const appSlug = command === "hp-next" ? "hp-next" : process.env.RIG_BROWSER_APP?.trim() || "";
|
|
363
370
|
if (!appSlug) {
|
|
364
|
-
throw new
|
|
371
|
+
throw new CliError(`rig browser app: set RIG_BROWSER_APP=<app-slug> to select which project app scripts to invoke.
|
|
365
372
|
` + `Scripts are invoked as 'bun run app:<mode>:browser:<app-slug>'.
|
|
366
373
|
|
|
367
374
|
${browserHelpText()}`);
|
|
@@ -373,11 +380,11 @@ ${browserHelpText()}`);
|
|
|
373
380
|
}
|
|
374
381
|
const modes = ["dev", "start", "check", "e2e", "reset"];
|
|
375
382
|
if (!modes.includes(subcommand)) {
|
|
376
|
-
throw new
|
|
383
|
+
throw new CliError(`Unknown browser ${command} command: ${subcommand}. Valid modes: ${modes.join(", ")}.
|
|
377
384
|
|
|
378
385
|
${browserHelpText()}`);
|
|
379
386
|
}
|
|
380
|
-
requireNoExtraArgs(appRest, `
|
|
387
|
+
requireNoExtraArgs(appRest, `rig browser ${command} ${subcommand}`);
|
|
381
388
|
await context.runCommand(["bun", "run", `app:${subcommand}:browser:${appSlug}`]);
|
|
382
389
|
return { ok: true, group: "browser", command: `${command}-${subcommand}` };
|
|
383
390
|
}
|
|
@@ -389,13 +396,13 @@ ${browserHelpText()}`);
|
|
|
389
396
|
};
|
|
390
397
|
const packageScript = packageScripts[command];
|
|
391
398
|
if (packageScript) {
|
|
392
|
-
requireNoExtraArgs(rest, `
|
|
399
|
+
requireNoExtraArgs(rest, `rig browser ${command}`);
|
|
393
400
|
await context.runCommand(["bun", "run", "--filter=@rig/browser", packageScript]);
|
|
394
401
|
return { ok: true, group: "browser", command };
|
|
395
402
|
}
|
|
396
|
-
throw new
|
|
403
|
+
throw new CliError(`Unknown browser command: ${command}
|
|
397
404
|
|
|
398
|
-
${browserHelpText()}`);
|
|
405
|
+
${browserHelpText()}`, 1, { hint: "Run `rig browser help` for the full browser command reference." });
|
|
399
406
|
}
|
|
400
407
|
async function executeBrowserDemo(context, args) {
|
|
401
408
|
const [maybeHelp] = args;
|
|
@@ -416,13 +423,13 @@ async function executeBrowserDemo(context, args) {
|
|
|
416
423
|
pending = keepOpenFlag.rest;
|
|
417
424
|
const noBuildFlag = takeFlag(pending, "--no-build");
|
|
418
425
|
pending = noBuildFlag.rest;
|
|
419
|
-
requireNoExtraArgs(pending, "
|
|
426
|
+
requireNoExtraArgs(pending, "rig browser demo [--port <n>] [--profile <name>] [--state-dir <path>] [--target-url <url>] [--keep-open] [--no-build]");
|
|
420
427
|
if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
421
|
-
throw new
|
|
428
|
+
throw new CliError("rig browser demo requires an interactive TTY in text mode.", 1, { hint: "Run `rig browser demo` from an interactive terminal (no --json, no piped stdin)." });
|
|
422
429
|
}
|
|
423
430
|
const port = portResult.value ? Number.parseInt(portResult.value, 10) : undefined;
|
|
424
431
|
if (port !== undefined && (!Number.isFinite(port) || port <= 0)) {
|
|
425
|
-
throw new
|
|
432
|
+
throw new CliError(`Invalid --port value: ${portResult.value}`, 1, { hint: "Pass a positive port number, e.g. `--port 9222`." });
|
|
426
433
|
}
|
|
427
434
|
const runtime = buildBrowserDemoRuntime(context.projectRoot, {
|
|
428
435
|
...port !== undefined ? { port } : {},
|
|
@@ -577,7 +584,7 @@ function runBrowserDemoBuild(projectRoot) {
|
|
|
577
584
|
if (result.exitCode !== 0) {
|
|
578
585
|
const details = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join(`
|
|
579
586
|
`);
|
|
580
|
-
throw new
|
|
587
|
+
throw new CliError(`Browser demo build failed.${details ? `
|
|
581
588
|
${details}` : ""}`, result.exitCode);
|
|
582
589
|
}
|
|
583
590
|
}
|
|
@@ -632,7 +639,7 @@ function readBrowserDemoCommandLine(command) {
|
|
|
632
639
|
cleanup();
|
|
633
640
|
stdout.write(`
|
|
634
641
|
`);
|
|
635
|
-
rejectInput(new
|
|
642
|
+
rejectInput(new CliError("Browser demo cancelled."));
|
|
636
643
|
return;
|
|
637
644
|
}
|
|
638
645
|
if (key.name === "return" || key.name === "enter") {
|
|
@@ -698,7 +705,7 @@ async function waitForBrowserDemoReady(runtime, child) {
|
|
|
698
705
|
return { targets: nextTargets, pageTarget: nextPageTarget };
|
|
699
706
|
}, child, "CDP demo page target");
|
|
700
707
|
if (!version.Browser) {
|
|
701
|
-
throw new
|
|
708
|
+
throw new CliError("Browser demo reached CDP but /json/version did not include Browser metadata.");
|
|
702
709
|
}
|
|
703
710
|
return { version, targets, pageTarget };
|
|
704
711
|
}
|
|
@@ -711,14 +718,14 @@ async function waitForBrowserDemo(check, child, label, timeoutMs = 18000) {
|
|
|
711
718
|
let lastError = null;
|
|
712
719
|
for (;; ) {
|
|
713
720
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
714
|
-
throw new
|
|
721
|
+
throw new CliError(`Rig Browser exited before ${label} became ready.`);
|
|
715
722
|
}
|
|
716
723
|
try {
|
|
717
724
|
return await check();
|
|
718
725
|
} catch (error) {
|
|
719
726
|
lastError = error;
|
|
720
727
|
if (Date.now() - startedAt > timeoutMs) {
|
|
721
|
-
throw new
|
|
728
|
+
throw new CliError(`${label} did not become ready within ${timeoutMs}ms: ${String(lastError)}`);
|
|
722
729
|
}
|
|
723
730
|
await new Promise((resolveDelay) => setTimeout(resolveDelay, 150));
|
|
724
731
|
}
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// packages/cli/src/commands/connect.ts
|
|
3
|
+
import { cancel, isCancel, select } from "@clack/prompts";
|
|
4
|
+
|
|
2
5
|
// packages/cli/src/runner.ts
|
|
3
6
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
4
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
7
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
5
8
|
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
9
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
9
|
-
|
|
10
|
+
|
|
11
|
+
class CliError extends RuntimeCliError {
|
|
12
|
+
hint;
|
|
13
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
14
|
+
super(message, exitCode);
|
|
15
|
+
if (options.hint?.trim()) {
|
|
16
|
+
this.hint = options.hint.trim();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
10
20
|
function requireNoExtraArgs(args, usage) {
|
|
11
21
|
if (args.length > 0) {
|
|
12
22
|
throw new CliError(`Unexpected arguments: ${args.join(" ")}
|
|
@@ -36,7 +46,7 @@ function readJsonFile(path) {
|
|
|
36
46
|
try {
|
|
37
47
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
38
48
|
} catch (error) {
|
|
39
|
-
throw new
|
|
49
|
+
throw new CliError(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1, { hint: "Fix or delete that file, then re-select a server with `rig server use <alias|local>`." });
|
|
40
50
|
}
|
|
41
51
|
}
|
|
42
52
|
function writeJsonFile(path, value) {
|
|
@@ -79,7 +89,7 @@ function writeGlobalConnections(state, options = {}) {
|
|
|
79
89
|
function upsertGlobalConnection(alias, connection, options = {}) {
|
|
80
90
|
const cleanAlias = alias.trim();
|
|
81
91
|
if (!cleanAlias)
|
|
82
|
-
throw new
|
|
92
|
+
throw new CliError("Connection alias is required.", 1, { hint: "Save a server with `rig server add <alias> <url>`." });
|
|
83
93
|
const state = readGlobalConnections(options);
|
|
84
94
|
state.connections[cleanAlias] = connection;
|
|
85
95
|
writeGlobalConnections(state, options);
|
|
@@ -96,27 +106,98 @@ function readRepoConnection(projectRoot) {
|
|
|
96
106
|
return {
|
|
97
107
|
selected,
|
|
98
108
|
project: typeof record.project === "string" ? record.project : undefined,
|
|
99
|
-
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
|
|
109
|
+
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
110
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
100
111
|
};
|
|
101
112
|
}
|
|
102
113
|
function writeRepoConnection(projectRoot, state) {
|
|
103
114
|
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
104
115
|
}
|
|
105
116
|
|
|
117
|
+
// packages/cli/src/commands/_cli-format.ts
|
|
118
|
+
import { log, note } from "@clack/prompts";
|
|
119
|
+
import pc from "picocolors";
|
|
120
|
+
function truncate(value, width) {
|
|
121
|
+
if (value.length <= width)
|
|
122
|
+
return value;
|
|
123
|
+
if (width <= 1)
|
|
124
|
+
return "\u2026";
|
|
125
|
+
return `${value.slice(0, width - 1)}\u2026`;
|
|
126
|
+
}
|
|
127
|
+
function pad(value, width) {
|
|
128
|
+
return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
|
|
129
|
+
}
|
|
130
|
+
function statusColor(status) {
|
|
131
|
+
const normalized = status.toLowerCase();
|
|
132
|
+
if (["completed", "merged", "closed", "done", "accepted", "pass", "selected", "approved"].includes(normalized))
|
|
133
|
+
return pc.green;
|
|
134
|
+
if (["failed", "needs_attention", "needs-attention", "blocked", "error", "rejected"].includes(normalized))
|
|
135
|
+
return pc.red;
|
|
136
|
+
if (["running", "reviewing", "validating", "in_progress", "in-progress", "remote"].includes(normalized))
|
|
137
|
+
return pc.cyan;
|
|
138
|
+
if (["ready", "open", "queued", "created", "preparing", "local", "pending"].includes(normalized))
|
|
139
|
+
return pc.yellow;
|
|
140
|
+
return pc.dim;
|
|
141
|
+
}
|
|
142
|
+
function formatStatusPill(status) {
|
|
143
|
+
const label = status || "unknown";
|
|
144
|
+
return statusColor(label)(`\u25CF ${label}`);
|
|
145
|
+
}
|
|
146
|
+
function formatSection(title, subtitle) {
|
|
147
|
+
return `${pc.bold(pc.cyan("\u25C6"))} ${pc.bold(title)}${subtitle ? pc.dim(` \u2014 ${subtitle}`) : ""}`;
|
|
148
|
+
}
|
|
149
|
+
function formatSuccessCard(title, rows = []) {
|
|
150
|
+
const body = rows.filter(([, value]) => value !== undefined && value !== null && String(value).length > 0).map(([key, value]) => `${pc.dim("\u2502")} ${pc.dim(key.padEnd(12))} ${value}`);
|
|
151
|
+
return [formatSection(title), ...body].join(`
|
|
152
|
+
`);
|
|
153
|
+
}
|
|
154
|
+
function formatNextSteps(steps) {
|
|
155
|
+
if (steps.length === 0)
|
|
156
|
+
return [];
|
|
157
|
+
return [pc.bold("Next"), ...steps.map((step) => `${pc.dim("\u203A")} ${step}`)];
|
|
158
|
+
}
|
|
159
|
+
function formatConnectionList(connections) {
|
|
160
|
+
const rows = [["local", { kind: "local", mode: "auto" }], ...Object.entries(connections)];
|
|
161
|
+
const aliasWidth = Math.min(24, Math.max(5, ...rows.map(([alias]) => alias.length)));
|
|
162
|
+
const lines = rows.map(([alias, connection]) => [
|
|
163
|
+
pc.bold(pad(truncate(alias, aliasWidth), aliasWidth)),
|
|
164
|
+
formatStatusPill(connection.kind),
|
|
165
|
+
connection.kind === "remote" ? connection.baseUrl ?? "" : connection.mode ?? "local"
|
|
166
|
+
].join(" "));
|
|
167
|
+
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(`
|
|
168
|
+
`);
|
|
169
|
+
}
|
|
170
|
+
function formatConnectionStatus(selected, connections) {
|
|
171
|
+
const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
|
|
172
|
+
const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
|
|
173
|
+
return [
|
|
174
|
+
formatSection("Rig server", "selected for this repo"),
|
|
175
|
+
`${pc.dim("\u2502")} ${pc.dim("selected ")} ${pc.bold(selected)}`,
|
|
176
|
+
`${pc.dim("\u2502")} ${pc.dim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
|
|
177
|
+
`${pc.dim("\u2502")} ${pc.dim("target ")} ${target ?? "not configured"}`,
|
|
178
|
+
"",
|
|
179
|
+
...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
|
|
180
|
+
].join(`
|
|
181
|
+
`);
|
|
182
|
+
}
|
|
183
|
+
|
|
106
184
|
// packages/cli/src/commands/connect.ts
|
|
107
|
-
function
|
|
185
|
+
function usageName(options) {
|
|
186
|
+
return `rig ${options.group}`;
|
|
187
|
+
}
|
|
188
|
+
function parseConnection(alias, value, options) {
|
|
108
189
|
if (alias === "local" && !value)
|
|
109
190
|
return { kind: "local", mode: "auto" };
|
|
110
191
|
if (!value)
|
|
111
|
-
throw new
|
|
192
|
+
throw new CliError(`Missing remote server URL. Usage: ${usageName(options)} add <alias> <url>`, 1);
|
|
112
193
|
let parsed;
|
|
113
194
|
try {
|
|
114
195
|
parsed = new URL(value);
|
|
115
196
|
} catch {
|
|
116
|
-
throw new
|
|
197
|
+
throw new CliError(`Invalid Rig server URL: ${value}`, 1, { hint: "Pass a full http(s) URL, e.g. `rig server add prod https://rig.example.com`." });
|
|
117
198
|
}
|
|
118
199
|
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
119
|
-
throw new
|
|
200
|
+
throw new CliError("Rig remote server URL must be http(s).", 1, { hint: "Use an http:// or https:// URL, e.g. `rig server add prod https://rig.example.com`." });
|
|
120
201
|
}
|
|
121
202
|
return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
|
|
122
203
|
}
|
|
@@ -127,54 +208,86 @@ function printJsonOrText(context, payload, text) {
|
|
|
127
208
|
console.log(text);
|
|
128
209
|
}
|
|
129
210
|
}
|
|
130
|
-
async function
|
|
211
|
+
async function promptForConnectionAlias(context) {
|
|
212
|
+
const state = readGlobalConnections();
|
|
213
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
214
|
+
const options = [
|
|
215
|
+
{ value: "local", label: "local", hint: "Use/start a local Rig server" },
|
|
216
|
+
...Object.entries(state.connections).map(([alias, connection]) => ({
|
|
217
|
+
value: alias,
|
|
218
|
+
label: alias,
|
|
219
|
+
hint: connection.kind === "remote" ? connection.baseUrl : "local"
|
|
220
|
+
}))
|
|
221
|
+
].filter((option, index, all) => all.findIndex((candidate) => candidate.value === option.value) === index);
|
|
222
|
+
const answer = await select({
|
|
223
|
+
message: "Select Rig server for this repo",
|
|
224
|
+
initialValue: repo?.selected ?? "local",
|
|
225
|
+
options
|
|
226
|
+
});
|
|
227
|
+
if (isCancel(answer)) {
|
|
228
|
+
cancel("No server selected.");
|
|
229
|
+
throw new CliError("No server selected.", 3);
|
|
230
|
+
}
|
|
231
|
+
return String(answer);
|
|
232
|
+
}
|
|
233
|
+
async function executeConnectionCommand(context, args, options) {
|
|
131
234
|
const [command, ...rest] = args;
|
|
132
235
|
switch (command ?? "status") {
|
|
133
236
|
case "list": {
|
|
134
|
-
requireNoExtraArgs(rest,
|
|
237
|
+
requireNoExtraArgs(rest, `${usageName(options)} list`);
|
|
135
238
|
const state = readGlobalConnections();
|
|
136
|
-
printJsonOrText(context, state,
|
|
137
|
-
|
|
138
|
-
return { ok: true, group: "connect", command: "list", details: state };
|
|
239
|
+
printJsonOrText(context, state, formatConnectionList(state.connections));
|
|
240
|
+
return { ok: true, group: options.group, command: "list", details: state };
|
|
139
241
|
}
|
|
140
242
|
case "add": {
|
|
141
243
|
const [alias, url, ...extra] = rest;
|
|
142
244
|
if (!alias)
|
|
143
|
-
throw new
|
|
144
|
-
requireNoExtraArgs(extra,
|
|
145
|
-
const connection = parseConnection(alias, url);
|
|
245
|
+
throw new CliError(`Missing alias. Usage: ${usageName(options)} add <alias> <url>`, 1);
|
|
246
|
+
requireNoExtraArgs(extra, `${usageName(options)} add <alias> <url>`);
|
|
247
|
+
const connection = parseConnection(alias, url, options);
|
|
146
248
|
const state = upsertGlobalConnection(alias, connection);
|
|
147
|
-
printJsonOrText(context, { alias, connection },
|
|
148
|
-
|
|
249
|
+
printJsonOrText(context, { alias, connection }, formatSuccessCard("Rig server saved", [
|
|
250
|
+
["alias", alias],
|
|
251
|
+
["target", connection.kind === "remote" ? connection.baseUrl : "local"],
|
|
252
|
+
["next", `${usageName(options)} use ${alias}`]
|
|
253
|
+
]));
|
|
254
|
+
return { ok: true, group: options.group, command: "add", details: { alias, connection, count: Object.keys(state.connections).length } };
|
|
149
255
|
}
|
|
150
256
|
case "use": {
|
|
151
|
-
|
|
257
|
+
let [alias, ...extra] = rest;
|
|
258
|
+
requireNoExtraArgs(extra, `${usageName(options)} use <alias|local>`);
|
|
259
|
+
if (!alias && options.interactiveUse && context.outputMode === "text" && process.stdin.isTTY) {
|
|
260
|
+
alias = await promptForConnectionAlias(context);
|
|
261
|
+
}
|
|
152
262
|
if (!alias)
|
|
153
|
-
throw new
|
|
154
|
-
requireNoExtraArgs(extra, "rig connect use <alias|local>");
|
|
263
|
+
throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
|
|
155
264
|
if (alias !== "local") {
|
|
156
265
|
const state = readGlobalConnections();
|
|
157
266
|
if (!state.connections[alias])
|
|
158
|
-
throw new
|
|
267
|
+
throw new CliError(`Unknown Rig server: ${alias}`, 1, { hint: "Run `rig server list` to see saved aliases, or save one with `rig server add <alias> <url>`." });
|
|
159
268
|
}
|
|
160
269
|
const repoState = { selected: alias, linkedAt: new Date().toISOString() };
|
|
161
270
|
writeRepoConnection(context.projectRoot, repoState);
|
|
162
|
-
printJsonOrText(context, repoState,
|
|
163
|
-
|
|
271
|
+
printJsonOrText(context, repoState, formatSuccessCard("Rig server selected", [
|
|
272
|
+
["selected", alias],
|
|
273
|
+
["scope", "this repo"],
|
|
274
|
+
["next", "rig task list"]
|
|
275
|
+
]));
|
|
276
|
+
return { ok: true, group: options.group, command: "use", details: repoState };
|
|
164
277
|
}
|
|
165
278
|
case "status": {
|
|
166
|
-
requireNoExtraArgs(rest,
|
|
279
|
+
requireNoExtraArgs(rest, `${usageName(options)} status`);
|
|
167
280
|
const repo = readRepoConnection(context.projectRoot);
|
|
168
281
|
const global = readGlobalConnections();
|
|
169
282
|
const details = { selected: repo?.selected ?? "local", repo, connections: global.connections };
|
|
170
|
-
printJsonOrText(context, details,
|
|
171
|
-
return { ok: true, group:
|
|
283
|
+
printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections));
|
|
284
|
+
return { ok: true, group: options.group, command: "status", details };
|
|
172
285
|
}
|
|
173
286
|
default:
|
|
174
|
-
throw new
|
|
175
|
-
Usage:
|
|
287
|
+
throw new CliError(`Unknown ${options.group} command: ${String(command)}
|
|
288
|
+
Usage: ${usageName(options)} <list|add|use|status>`, 1);
|
|
176
289
|
}
|
|
177
290
|
}
|
|
178
291
|
export {
|
|
179
|
-
|
|
292
|
+
executeConnectionCommand
|
|
180
293
|
};
|
|
@@ -17,12 +17,19 @@ import { resolve as resolve3 } from "path";
|
|
|
17
17
|
|
|
18
18
|
// packages/cli/src/runner.ts
|
|
19
19
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
20
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
20
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
21
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
22
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
25
|
-
|
|
23
|
+
|
|
24
|
+
class CliError extends RuntimeCliError {
|
|
25
|
+
hint;
|
|
26
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
27
|
+
super(message, exitCode);
|
|
28
|
+
if (options.hint?.trim()) {
|
|
29
|
+
this.hint = options.hint.trim();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
26
33
|
function takeOption(args, option) {
|
|
27
34
|
const rest = [];
|
|
28
35
|
let value;
|
|
@@ -31,7 +38,7 @@ function takeOption(args, option) {
|
|
|
31
38
|
if (current === option) {
|
|
32
39
|
const next = args[index + 1];
|
|
33
40
|
if (!next || next.startsWith("-")) {
|
|
34
|
-
throw new CliError(`Missing value for ${option}`);
|
|
41
|
+
throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
|
|
35
42
|
}
|
|
36
43
|
value = next;
|
|
37
44
|
index += 1;
|
|
@@ -67,7 +74,7 @@ function parseInstallScope(value) {
|
|
|
67
74
|
if (value === "system") {
|
|
68
75
|
return "system";
|
|
69
76
|
}
|
|
70
|
-
throw new
|
|
77
|
+
throw new CliError(`Invalid --scope value: ${value}. Use user|system.`);
|
|
71
78
|
}
|
|
72
79
|
function resolveInstallDir(scope, explicitPath) {
|
|
73
80
|
if (explicitPath) {
|
|
@@ -185,7 +192,7 @@ async function executeDist(context, args) {
|
|
|
185
192
|
switch (command) {
|
|
186
193
|
case "build": {
|
|
187
194
|
const { value: outputDir, rest: pending } = takeOption(rest, "--output-dir");
|
|
188
|
-
requireNoExtraArgs(pending, "
|
|
195
|
+
requireNoExtraArgs(pending, "rig dist build [--output-dir <dir>]");
|
|
189
196
|
const commandParts = ["bun", "run", "packages/cli/bin/build-rig-binaries.ts"];
|
|
190
197
|
if (outputDir) {
|
|
191
198
|
commandParts.push("--output-dir", outputDir);
|
|
@@ -199,7 +206,7 @@ async function executeDist(context, args) {
|
|
|
199
206
|
pending = scopeResult.rest;
|
|
200
207
|
const pathResult = takeOption(pending, "--path");
|
|
201
208
|
pending = pathResult.rest;
|
|
202
|
-
requireNoExtraArgs(pending, "
|
|
209
|
+
requireNoExtraArgs(pending, "rig dist install [--scope user|system] [--path <dir>]");
|
|
203
210
|
const scope = parseInstallScope(scopeResult.value);
|
|
204
211
|
const installDir = resolveInstallDir(scope, pathResult.value);
|
|
205
212
|
mkdirSync(installDir, { recursive: true });
|
|
@@ -211,7 +218,7 @@ async function executeDist(context, args) {
|
|
|
211
218
|
source = resolve3(buildDir, "bin", "rig");
|
|
212
219
|
}
|
|
213
220
|
if (!existsSync(source)) {
|
|
214
|
-
throw new
|
|
221
|
+
throw new CliError(`Unable to locate rig binary at ${source}.`, 2, { hint: "Build it first with `rig dist build`, then re-run `rig dist install`." });
|
|
215
222
|
}
|
|
216
223
|
const installedPath = resolve3(installDir, "rig");
|
|
217
224
|
if (existsSync(installedPath)) {
|
|
@@ -242,7 +249,7 @@ async function executeDist(context, args) {
|
|
|
242
249
|
};
|
|
243
250
|
}
|
|
244
251
|
case "doctor": {
|
|
245
|
-
requireNoExtraArgs(rest, "
|
|
252
|
+
requireNoExtraArgs(rest, "rig dist doctor");
|
|
246
253
|
const details = await runDistDoctor(context.projectRoot);
|
|
247
254
|
if (context.outputMode === "text") {
|
|
248
255
|
console.log(`bun: ${details.bun.available ? `ok (${details.bun.version})` : "missing"}`);
|
|
@@ -253,7 +260,7 @@ async function executeDist(context, args) {
|
|
|
253
260
|
return { ok: true, group: "dist", command, details };
|
|
254
261
|
}
|
|
255
262
|
case "rebuild-agent": {
|
|
256
|
-
requireNoExtraArgs(rest, "
|
|
263
|
+
requireNoExtraArgs(rest, "rig dist rebuild-agent");
|
|
257
264
|
const fp = await computeRuntimeImageFingerprint(context.projectRoot);
|
|
258
265
|
const currentId = computeRuntimeImageId(fp);
|
|
259
266
|
const imagesDir = resolve3(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
|
|
@@ -393,7 +400,7 @@ async function executeDist(context, args) {
|
|
|
393
400
|
return { ok: true, group: "dist", command, details: { imageId: currentId, count: targets.length } };
|
|
394
401
|
}
|
|
395
402
|
default:
|
|
396
|
-
throw new
|
|
403
|
+
throw new CliError(`Unknown dist command: ${command}`, 1, { hint: "Run `rig dist --help` \u2014 commands are build|install|doctor." });
|
|
397
404
|
}
|
|
398
405
|
}
|
|
399
406
|
export {
|