@h-rig/cli 0.0.6-alpha.9 → 0.0.6-alpha.90

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 (114) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/build-rig-binaries.d.ts +2 -0
  3. package/dist/bin/rig.d.ts +2 -0
  4. package/dist/bin/rig.js +11052 -6668
  5. package/dist/src/app/board.d.ts +23 -0
  6. package/dist/src/app/board.js +1786 -0
  7. package/dist/src/app/drone-ui.d.ts +37 -0
  8. package/dist/src/app/drone-ui.js +294 -0
  9. package/dist/src/app/theme.d.ts +47 -0
  10. package/dist/src/app/theme.js +150 -0
  11. package/dist/src/commands/_async-ui.d.ts +13 -0
  12. package/dist/src/commands/_async-ui.js +223 -0
  13. package/dist/src/commands/_authority-runs.d.ts +22 -0
  14. package/dist/src/commands/_authority-runs.js +2 -3
  15. package/dist/src/commands/_cli-format.d.ts +49 -0
  16. package/dist/src/commands/_cli-format.js +447 -0
  17. package/dist/src/commands/_connection-state.d.ts +44 -0
  18. package/dist/src/commands/_connection-state.js +30 -11
  19. package/dist/src/commands/_doctor-checks.d.ts +52 -0
  20. package/dist/src/commands/_doctor-checks.js +180 -44
  21. package/dist/src/commands/_help-catalog.d.ts +51 -0
  22. package/dist/src/commands/_help-catalog.js +442 -0
  23. package/dist/src/commands/_json-output.d.ts +11 -0
  24. package/dist/src/commands/_json-output.js +56 -0
  25. package/dist/src/commands/_operator-surface.d.ts +34 -0
  26. package/dist/src/commands/_operator-surface.js +220 -0
  27. package/dist/src/commands/_operator-view.d.ts +30 -0
  28. package/dist/src/commands/_operator-view.js +803 -73
  29. package/dist/src/commands/_parsers.d.ts +15 -0
  30. package/dist/src/commands/_parsers.js +18 -11
  31. package/dist/src/commands/_paths.d.ts +11 -0
  32. package/dist/src/commands/_pi-frontend.d.ts +27 -0
  33. package/dist/src/commands/_pi-frontend.js +742 -0
  34. package/dist/src/commands/_pi-install.d.ts +42 -0
  35. package/dist/src/commands/_pi-install.js +5 -4
  36. package/dist/src/commands/_policy.d.ts +8 -0
  37. package/dist/src/commands/_policy.js +12 -5
  38. package/dist/src/commands/_preflight.d.ts +22 -0
  39. package/dist/src/commands/_preflight.js +190 -128
  40. package/dist/src/commands/_probes.d.ts +1 -0
  41. package/dist/src/commands/_run-driver-helpers.d.ts +99 -0
  42. package/dist/src/commands/_run-driver-helpers.js +75 -22
  43. package/dist/src/commands/_run-replay.d.ts +24 -0
  44. package/dist/src/commands/_run-replay.js +142 -0
  45. package/dist/src/commands/_server-client.d.ts +186 -0
  46. package/dist/src/commands/_server-client.js +360 -61
  47. package/dist/src/commands/_snapshot-upload.d.ts +39 -0
  48. package/dist/src/commands/_snapshot-upload.js +163 -39
  49. package/dist/src/commands/_spinner.d.ts +25 -0
  50. package/dist/src/commands/_spinner.js +65 -0
  51. package/dist/src/commands/_task-picker.d.ts +9 -0
  52. package/dist/src/commands/_task-picker.js +172 -19
  53. package/dist/src/commands/agent.d.ts +3 -0
  54. package/dist/src/commands/agent.js +39 -20
  55. package/dist/src/commands/browser.d.ts +65 -0
  56. package/dist/src/commands/browser.js +334 -51
  57. package/dist/src/commands/connect.d.ts +7 -0
  58. package/dist/src/commands/connect.js +272 -33
  59. package/dist/src/commands/dist.d.ts +28 -0
  60. package/dist/src/commands/dist.js +19 -12
  61. package/dist/src/commands/doctor.d.ts +3 -0
  62. package/dist/src/commands/doctor.js +378 -45
  63. package/dist/src/commands/github.d.ts +3 -0
  64. package/dist/src/commands/github.js +375 -53
  65. package/dist/src/commands/inbox.d.ts +30 -0
  66. package/dist/src/commands/inbox.js +760 -70
  67. package/dist/src/commands/init.d.ts +74 -0
  68. package/dist/src/commands/init.js +963 -119
  69. package/dist/src/commands/inspect.d.ts +3 -0
  70. package/dist/src/commands/inspect.js +588 -32
  71. package/dist/src/commands/inspector.d.ts +3 -0
  72. package/dist/src/commands/inspector.js +20 -13
  73. package/dist/src/commands/pi.d.ts +3 -0
  74. package/dist/src/commands/pi.js +177 -0
  75. package/dist/src/commands/plugin.d.ts +16 -0
  76. package/dist/src/commands/plugin.js +95 -27
  77. package/dist/src/commands/profile-and-review.d.ts +4 -0
  78. package/dist/src/commands/profile-and-review.js +26 -19
  79. package/dist/src/commands/queue.d.ts +3 -0
  80. package/dist/src/commands/queue.js +32 -12
  81. package/dist/src/commands/remote.d.ts +3 -0
  82. package/dist/src/commands/remote.js +43 -36
  83. package/dist/src/commands/repo-git-harness.d.ts +5 -0
  84. package/dist/src/commands/repo-git-harness.js +22 -15
  85. package/dist/src/commands/run.d.ts +3 -0
  86. package/dist/src/commands/run.js +1379 -159
  87. package/dist/src/commands/server.d.ts +7 -0
  88. package/dist/src/commands/server.js +502 -57
  89. package/dist/src/commands/setup.d.ts +16 -0
  90. package/dist/src/commands/setup.js +390 -63
  91. package/dist/src/commands/stats.d.ts +12 -0
  92. package/dist/src/commands/stats.js +1051 -0
  93. package/dist/src/commands/task-report-bug.d.ts +19 -0
  94. package/dist/src/commands/task-report-bug.js +260 -62
  95. package/dist/src/commands/task-run-driver.d.ts +132 -0
  96. package/dist/src/commands/task-run-driver.js +886 -130
  97. package/dist/src/commands/task.d.ts +14 -0
  98. package/dist/src/commands/task.js +1644 -314
  99. package/dist/src/commands/test.d.ts +3 -0
  100. package/dist/src/commands/test.js +15 -8
  101. package/dist/src/commands/workspace.d.ts +3 -0
  102. package/dist/src/commands/workspace.js +18 -11
  103. package/dist/src/commands.d.ts +29 -0
  104. package/dist/src/commands.js +10967 -6637
  105. package/dist/src/index.d.ts +4 -0
  106. package/dist/src/index.js +10168 -5787
  107. package/dist/src/launcher.d.ts +61 -0
  108. package/dist/src/launcher.js +77 -13
  109. package/dist/src/report-bug.d.ts +44 -0
  110. package/dist/src/report-bug.js +3 -3
  111. package/dist/src/runner.d.ts +47 -0
  112. package/dist/src/runner.js +16 -22
  113. package/dist/src/withMutedConsole.d.ts +2 -0
  114. package/package.json +13 -6
@@ -1,12 +1,147 @@
1
1
  // @bun
2
+ // packages/cli/src/app/drone-ui.ts
3
+ import { ProcessTerminal, TUI, Text, Input, SelectList, matchesKey } from "@earendil-works/pi-tui";
4
+
5
+ // packages/cli/src/app/theme.ts
6
+ var RIG_PALETTE = {
7
+ ink: "#f2f3f6",
8
+ ink2: "#aeb0ba",
9
+ ink3: "#6c6e79",
10
+ ink4: "#44464f",
11
+ accent: "#ccff4d",
12
+ accentDim: "#a9d63f",
13
+ cyan: "#56d8ff",
14
+ red: "#ff5d5d",
15
+ yellow: "#ffd24d"
16
+ };
17
+ function hexToRgb(hex) {
18
+ const value = hex.replace("#", "");
19
+ return [
20
+ Number.parseInt(value.slice(0, 2), 16),
21
+ Number.parseInt(value.slice(2, 4), 16),
22
+ Number.parseInt(value.slice(4, 6), 16)
23
+ ];
24
+ }
25
+ function fg(hex) {
26
+ const [r, g, b] = hexToRgb(hex);
27
+ return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
28
+ }
29
+ var ink = fg(RIG_PALETTE.ink);
30
+ var ink2 = fg(RIG_PALETTE.ink2);
31
+ var ink3 = fg(RIG_PALETTE.ink3);
32
+ var ink4 = fg(RIG_PALETTE.ink4);
33
+ var accent = fg(RIG_PALETTE.accent);
34
+ var accentDim = fg(RIG_PALETTE.accentDim);
35
+ var cyan = fg(RIG_PALETTE.cyan);
36
+ var red = fg(RIG_PALETTE.red);
37
+ var yellow = fg(RIG_PALETTE.yellow);
38
+ function bold(text) {
39
+ return `\x1B[1m${text}\x1B[22m`;
40
+ }
41
+ var DRONE_ART = [
42
+ " .-=-. .-=-. ",
43
+ " ( !!! ) ( !!! ) ",
44
+ " '-=-'._ _.'-=-' ",
45
+ " '._ _.' ",
46
+ " '=$$$$$$$=.' ",
47
+ " =$$$$$$$$$$$= ",
48
+ " $$$@@@@@@@@@@$$$ ",
49
+ " $$$@@ @@$$$ ",
50
+ " $$@ ? @$$$ ",
51
+ " $$$@ '-' @$$$ ",
52
+ " $$$@@ @@$$$ ",
53
+ " $$$@@@@@@@@@@$$$ ",
54
+ " =$$$$$$$$$$$= ",
55
+ " '=$$$$$$$=.' ",
56
+ " _.' '._ ",
57
+ " .-=-.' '.-=-. ",
58
+ " ( !!! ) ( !!! ) ",
59
+ " '-=-' '-=-' "
60
+ ];
61
+ var EYE_FRAMES = ["@", "o", "."];
62
+ var DRONE_WIDTH = DRONE_ART[0].length;
63
+ var DRONE_HEIGHT = DRONE_ART.length;
64
+ var MICRO_BLADES = ["---", "\\\\\\", "|||", "///"];
65
+ function microDroneFrame(tick) {
66
+ const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
67
+ const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
68
+ return `(${blade})${eye}(${blade})`;
69
+ }
70
+ var MICRO_DRONE_FRAMES = Array.from({ length: 12 }, (_, index) => microDroneFrame(index));
71
+
72
+ // packages/cli/src/app/drone-ui.ts
73
+ var isTty = () => Boolean(process.stdout.isTTY);
74
+ function droneCancel(text) {
75
+ console.log(` ${red("\u2716")} ${ink3(text)}`);
76
+ }
77
+ var SELECT_THEME = {
78
+ selectedPrefix: (text) => accent(text),
79
+ selectedText: (text) => bold(ink(text)),
80
+ description: (text) => ink3(text),
81
+ scrollInfo: (text) => ink4(text),
82
+ noMatch: (text) => ink3(text)
83
+ };
84
+ async function runMiniTui(build) {
85
+ const terminal = new ProcessTerminal;
86
+ const tui = new TUI(terminal);
87
+ let settled = false;
88
+ return await new Promise((resolve) => {
89
+ const finish = (result) => {
90
+ if (settled)
91
+ return;
92
+ settled = true;
93
+ tui.stop();
94
+ resolve(result);
95
+ };
96
+ build(tui, finish);
97
+ tui.start();
98
+ });
99
+ }
100
+ async function droneSelect(input) {
101
+ if (!isTty() || input.options.length === 0) {
102
+ return input.initialValue ?? input.options[0]?.value ?? null;
103
+ }
104
+ return runMiniTui((tui, finish) => {
105
+ tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}`));
106
+ const items = input.options.map((option) => ({
107
+ value: option.value,
108
+ label: option.label,
109
+ ...option.hint ? { description: option.hint } : {}
110
+ }));
111
+ const list = new SelectList(items, Math.min(items.length, 12), SELECT_THEME);
112
+ const initialIndex = input.initialValue ? items.findIndex((item) => item.value === input.initialValue) : -1;
113
+ if (initialIndex > 0)
114
+ list.setSelectedIndex(initialIndex);
115
+ list.onSelect = (item) => finish(item.value);
116
+ list.onCancel = () => finish(null);
117
+ tui.addChild(list);
118
+ tui.addChild(new Text(ink4(" \u2191\u2193 navigate \xB7 enter select \xB7 esc cancel")));
119
+ tui.setFocus(list);
120
+ tui.addInputListener((data) => {
121
+ if (matchesKey(data, "ctrl+c") || matchesKey(data, "escape")) {
122
+ finish(null);
123
+ return { consume: true };
124
+ }
125
+ return;
126
+ });
127
+ });
128
+ }
129
+
2
130
  // packages/cli/src/runner.ts
3
131
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
4
- import { CliError } from "@rig/runtime/control-plane/errors";
132
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
5
133
  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
134
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
9
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
135
+
136
+ class CliError extends RuntimeCliError {
137
+ hint;
138
+ constructor(message, exitCode = 1, options = {}) {
139
+ super(message, exitCode);
140
+ if (options.hint?.trim()) {
141
+ this.hint = options.hint.trim();
142
+ }
143
+ }
144
+ }
10
145
  function requireNoExtraArgs(args, usage) {
11
146
  if (args.length > 0) {
12
147
  throw new CliError(`Unexpected arguments: ${args.join(" ")}
@@ -36,7 +171,7 @@ function readJsonFile(path) {
36
171
  try {
37
172
  return JSON.parse(readFileSync(path, "utf8"));
38
173
  } catch (error) {
39
- throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
174
+ 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
175
  }
41
176
  }
42
177
  function writeJsonFile(path, value) {
@@ -79,7 +214,7 @@ function writeGlobalConnections(state, options = {}) {
79
214
  function upsertGlobalConnection(alias, connection, options = {}) {
80
215
  const cleanAlias = alias.trim();
81
216
  if (!cleanAlias)
82
- throw new CliError2("Connection alias is required.", 1);
217
+ throw new CliError("Connection alias is required.", 1, { hint: "Save a server with `rig server add <alias> <url>`." });
83
218
  const state = readGlobalConnections(options);
84
219
  state.connections[cleanAlias] = connection;
85
220
  writeGlobalConnections(state, options);
@@ -96,27 +231,99 @@ function readRepoConnection(projectRoot) {
96
231
  return {
97
232
  selected,
98
233
  project: typeof record.project === "string" ? record.project : undefined,
99
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
234
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
235
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
100
236
  };
101
237
  }
102
238
  function writeRepoConnection(projectRoot, state) {
103
239
  writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
104
240
  }
105
241
 
242
+ // packages/cli/src/commands/_cli-format.ts
243
+ import pc from "picocolors";
244
+ var themeDim = (value) => ink3(value);
245
+ var themeFaint = (value) => ink4(value);
246
+ function truncate(value, width) {
247
+ if (value.length <= width)
248
+ return value;
249
+ if (width <= 1)
250
+ return "\u2026";
251
+ return `${value.slice(0, width - 1)}\u2026`;
252
+ }
253
+ function pad(value, width) {
254
+ return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
255
+ }
256
+ function statusColor(status) {
257
+ const normalized = status.toLowerCase();
258
+ if (["completed", "merged", "closed", "done", "accepted", "pass", "selected", "approved", "running"].includes(normalized))
259
+ return accent;
260
+ if (["failed", "needs_attention", "needs-attention", "blocked", "error", "rejected"].includes(normalized))
261
+ return red;
262
+ if (["reviewing", "validating", "in_progress", "in-progress", "remote", "preparing", "closing-out"].includes(normalized))
263
+ return cyan;
264
+ if (["ready", "open", "queued", "created", "local", "pending"].includes(normalized))
265
+ return yellow;
266
+ return themeDim;
267
+ }
268
+ function formatStatusPill(status) {
269
+ const label = status || "unknown";
270
+ return statusColor(label)(`\u25CF ${label}`);
271
+ }
272
+ function formatSection(title, subtitle) {
273
+ return `${pc.bold(accent("\u25C6"))} ${pc.bold(title)}${subtitle ? themeDim(` \u2014 ${subtitle}`) : ""}`;
274
+ }
275
+ function formatSuccessCard(title, rows = []) {
276
+ const body = rows.filter(([, value]) => value !== undefined && value !== null && String(value).length > 0).map(([key, value]) => `${themeFaint("\u2502")} ${themeDim(key.padEnd(12))} ${value}`);
277
+ return [formatSection(title), ...body].join(`
278
+ `);
279
+ }
280
+ function formatNextSteps(steps) {
281
+ if (steps.length === 0)
282
+ return [];
283
+ return [pc.bold("Next"), ...steps.map((step) => `${accent("\u203A")} ${step}`)];
284
+ }
285
+ function formatConnectionList(connections) {
286
+ const rows = [["local", { kind: "local", mode: "auto" }], ...Object.entries(connections)];
287
+ const aliasWidth = Math.min(24, Math.max(5, ...rows.map(([alias]) => alias.length)));
288
+ const lines = rows.map(([alias, connection]) => [
289
+ pc.bold(pad(truncate(alias, aliasWidth), aliasWidth)),
290
+ formatStatusPill(connection.kind),
291
+ connection.kind === "remote" ? connection.baseUrl ?? "" : connection.mode ?? "local"
292
+ ].join(" "));
293
+ 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(`
294
+ `);
295
+ }
296
+ function formatConnectionStatus(selected, connections) {
297
+ const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
298
+ const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
299
+ return [
300
+ formatSection("Rig server", "selected for this repo"),
301
+ `${themeFaint("\u2502")} ${themeDim("selected ")} ${pc.bold(selected)}`,
302
+ `${themeFaint("\u2502")} ${themeDim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
303
+ `${themeFaint("\u2502")} ${themeDim("target ")} ${target ?? "not configured"}`,
304
+ "",
305
+ ...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
306
+ ].join(`
307
+ `);
308
+ }
309
+
106
310
  // packages/cli/src/commands/connect.ts
107
- function parseConnection(alias, value) {
311
+ function usageName(options) {
312
+ return `rig ${options.group}`;
313
+ }
314
+ function parseConnection(alias, value, options) {
108
315
  if (alias === "local" && !value)
109
316
  return { kind: "local", mode: "auto" };
110
317
  if (!value)
111
- throw new CliError2("Missing remote server URL. Usage: rig connect add <alias> <url>", 1);
318
+ throw new CliError(`Missing remote server URL. Usage: ${usageName(options)} add <alias> <url>`, 1);
112
319
  let parsed;
113
320
  try {
114
321
  parsed = new URL(value);
115
322
  } catch {
116
- throw new CliError2(`Invalid Rig server URL: ${value}`, 1);
323
+ 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
324
  }
118
325
  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
119
- throw new CliError2("Rig remote server URL must be http(s).", 1);
326
+ 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
327
  }
121
328
  return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
122
329
  }
@@ -127,54 +334,86 @@ function printJsonOrText(context, payload, text) {
127
334
  console.log(text);
128
335
  }
129
336
  }
130
- async function executeConnect(context, args) {
337
+ async function promptForConnectionAlias(context) {
338
+ const state = readGlobalConnections();
339
+ const repo = readRepoConnection(context.projectRoot);
340
+ const options = [
341
+ { value: "local", label: "local", hint: "Use/start a local Rig server" },
342
+ ...Object.entries(state.connections).map(([alias, connection]) => ({
343
+ value: alias,
344
+ label: alias,
345
+ hint: connection.kind === "remote" ? connection.baseUrl : "local"
346
+ }))
347
+ ].filter((option, index, all) => all.findIndex((candidate) => candidate.value === option.value) === index);
348
+ const answer = await droneSelect({
349
+ message: "Select Rig server for this repo",
350
+ initialValue: repo?.selected ?? "local",
351
+ options
352
+ });
353
+ if (answer === null) {
354
+ droneCancel("No server selected.");
355
+ throw new CliError("No server selected.", 3);
356
+ }
357
+ return String(answer);
358
+ }
359
+ async function executeConnectionCommand(context, args, options) {
131
360
  const [command, ...rest] = args;
132
361
  switch (command ?? "status") {
133
362
  case "list": {
134
- requireNoExtraArgs(rest, "rig connect list");
363
+ requireNoExtraArgs(rest, `${usageName(options)} list`);
135
364
  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 };
365
+ printJsonOrText(context, state, formatConnectionList(state.connections));
366
+ return { ok: true, group: options.group, command: "list", details: state };
139
367
  }
140
368
  case "add": {
141
369
  const [alias, url, ...extra] = rest;
142
370
  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);
371
+ throw new CliError(`Missing alias. Usage: ${usageName(options)} add <alias> <url>`, 1);
372
+ requireNoExtraArgs(extra, `${usageName(options)} add <alias> <url>`);
373
+ const connection = parseConnection(alias, url, options);
146
374
  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 } };
375
+ printJsonOrText(context, { alias, connection }, formatSuccessCard("Rig server saved", [
376
+ ["alias", alias],
377
+ ["target", connection.kind === "remote" ? connection.baseUrl : "local"],
378
+ ["next", `${usageName(options)} use ${alias}`]
379
+ ]));
380
+ return { ok: true, group: options.group, command: "add", details: { alias, connection, count: Object.keys(state.connections).length } };
149
381
  }
150
382
  case "use": {
151
- const [alias, ...extra] = rest;
383
+ let [alias, ...extra] = rest;
384
+ requireNoExtraArgs(extra, `${usageName(options)} use <alias|local>`);
385
+ if (!alias && options.interactiveUse && context.outputMode === "text" && process.stdin.isTTY) {
386
+ alias = await promptForConnectionAlias(context);
387
+ }
152
388
  if (!alias)
153
- throw new CliError2("Missing alias. Usage: rig connect use <alias|local>", 1);
154
- requireNoExtraArgs(extra, "rig connect use <alias|local>");
389
+ throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
155
390
  if (alias !== "local") {
156
391
  const state = readGlobalConnections();
157
392
  if (!state.connections[alias])
158
- throw new CliError2(`Unknown Rig connection: ${alias}`, 1);
393
+ 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
394
  }
160
395
  const repoState = { selected: alias, linkedAt: new Date().toISOString() };
161
396
  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 };
397
+ printJsonOrText(context, repoState, formatSuccessCard("Rig server selected", [
398
+ ["selected", alias],
399
+ ["scope", "this repo"],
400
+ ["next", "rig task list"]
401
+ ]));
402
+ return { ok: true, group: options.group, command: "use", details: repoState };
164
403
  }
165
404
  case "status": {
166
- requireNoExtraArgs(rest, "rig connect status");
405
+ requireNoExtraArgs(rest, `${usageName(options)} status`);
167
406
  const repo = readRepoConnection(context.projectRoot);
168
407
  const global = readGlobalConnections();
169
408
  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 };
409
+ printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections));
410
+ return { ok: true, group: options.group, command: "status", details };
172
411
  }
173
412
  default:
174
- throw new CliError2(`Unknown connect command: ${String(command)}
175
- Usage: rig connect <list|add|use|status>`, 1);
413
+ throw new CliError(`Unknown ${options.group} command: ${String(command)}
414
+ Usage: ${usageName(options)} <list|add|use|status>`, 1);
176
415
  }
177
416
  }
178
417
  export {
179
- executeConnect
418
+ executeConnectionCommand
180
419
  };
@@ -0,0 +1,28 @@
1
+ import { type RunnerContext } from "../runner";
2
+ import type { CommandOutcome } from "@rig/runtime/control-plane/runtime/types";
3
+ export type DistDoctorDetails = {
4
+ bun: {
5
+ available: boolean;
6
+ path: string | null;
7
+ version: string;
8
+ };
9
+ rig: {
10
+ onPath: boolean;
11
+ path: string | null;
12
+ runnable: boolean;
13
+ };
14
+ userBinDir: string;
15
+ userBinInPath: boolean;
16
+ latestDistBinary: string | null;
17
+ };
18
+ export type BuildTarget = {
19
+ source: string;
20
+ dest: string;
21
+ cwd: string;
22
+ };
23
+ export declare function collectRigValidatorBuildTargets(input: {
24
+ contextProjectRoot: string;
25
+ hostProjectRoot: string;
26
+ imageDir: string;
27
+ }): BuildTarget[];
28
+ export declare function executeDist(context: RunnerContext, args: string[]): Promise<CommandOutcome>;
@@ -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
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
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 CliError2(`Invalid --scope value: ${value}. Use user|system.`);
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, "bun run rig dist build [--output-dir <dir>]");
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, "bun run rig dist install [--scope user|system] [--path <dir>]");
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 CliError2(`Unable to locate rig binary at ${source}.`, 2);
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, "bun run rig dist doctor");
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, "bun run rig dist rebuild-agent");
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 CliError2(`Unknown dist command: ${command}`);
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 {
@@ -0,0 +1,3 @@
1
+ import type { CommandOutcome } from "@rig/runtime/control-plane/runtime/types";
2
+ import { type RunnerContext } from "../runner";
3
+ export declare function executeDoctor(context: RunnerContext, args: string[]): Promise<CommandOutcome>;