@h-rig/cli 0.0.6-alpha.64 → 0.0.6-alpha.66

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 (46) hide show
  1. package/dist/bin/rig.js +1349 -1599
  2. package/dist/src/commands/_connection-state.js +14 -5
  3. package/dist/src/commands/_doctor-checks.js +71 -11
  4. package/dist/src/commands/_help-catalog.js +99 -60
  5. package/dist/src/commands/_json-output.js +56 -0
  6. package/dist/src/commands/_operator-view.js +97 -784
  7. package/dist/src/commands/_parsers.js +18 -9
  8. package/dist/src/commands/_pi-frontend.js +96 -788
  9. package/dist/src/commands/_policy.js +12 -3
  10. package/dist/src/commands/_preflight.js +73 -13
  11. package/dist/src/commands/_run-driver-helpers.js +31 -5
  12. package/dist/src/commands/_run-replay.js +2 -2
  13. package/dist/src/commands/_server-client.js +76 -16
  14. package/dist/src/commands/_snapshot-upload.js +70 -10
  15. package/dist/src/commands/agent.js +21 -11
  16. package/dist/src/commands/browser.js +24 -15
  17. package/dist/src/commands/connect.js +22 -17
  18. package/dist/src/commands/dist.js +15 -6
  19. package/dist/src/commands/doctor.js +70 -10
  20. package/dist/src/commands/github.js +79 -19
  21. package/dist/src/commands/inbox.js +215 -131
  22. package/dist/src/commands/init.js +202 -35
  23. package/dist/src/commands/inspect.js +77 -17
  24. package/dist/src/commands/inspector.js +18 -9
  25. package/dist/src/commands/pi.js +18 -9
  26. package/dist/src/commands/plugin.js +19 -10
  27. package/dist/src/commands/profile-and-review.js +22 -13
  28. package/dist/src/commands/queue.js +19 -9
  29. package/dist/src/commands/remote.js +25 -16
  30. package/dist/src/commands/repo-git-harness.js +18 -9
  31. package/dist/src/commands/run.js +158 -805
  32. package/dist/src/commands/server.js +85 -25
  33. package/dist/src/commands/setup.js +73 -13
  34. package/dist/src/commands/stats.js +681 -0
  35. package/dist/src/commands/task-report-bug.js +24 -15
  36. package/dist/src/commands/task-run-driver.js +121 -49
  37. package/dist/src/commands/task.js +254 -893
  38. package/dist/src/commands/test.js +12 -3
  39. package/dist/src/commands/workspace.js +14 -5
  40. package/dist/src/commands.js +1339 -1650
  41. package/dist/src/index.js +1349 -1599
  42. package/dist/src/launcher.js +72 -10
  43. package/dist/src/runner.js +14 -5
  44. package/package.json +10 -7
  45. package/dist/src/commands/_pi-remote-session.js +0 -771
  46. package/dist/src/commands/_pi-worker-bridge-extension.js +0 -834
@@ -4,10 +4,19 @@ import { resolve as resolve2 } from "path";
4
4
 
5
5
  // packages/cli/src/runner.ts
6
6
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
7
- import { CliError } from "@rig/runtime/control-plane/errors";
7
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
8
8
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
9
9
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
10
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
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
+ }
11
20
  function takeFlag(args, flag) {
12
21
  const rest = [];
13
22
  let value = false;
@@ -28,7 +37,7 @@ function takeOption(args, option) {
28
37
  if (current === option) {
29
38
  const next = args[index + 1];
30
39
  if (!next || next.startsWith("-")) {
31
- throw new CliError(`Missing value for ${option}`);
40
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
32
41
  }
33
42
  value = next;
34
43
  index += 1;
@@ -210,7 +219,7 @@ function parseIsolationMode(value, allowOff) {
210
219
  if (allowOff && value === "off") {
211
220
  return value;
212
221
  }
213
- throw new CliError2(`Invalid isolation mode: ${value}. Use ${allowOff ? "off|" : ""}worktree.`);
222
+ throw new CliError(`Invalid isolation mode: ${value}. Use ${allowOff ? "off|" : ""}worktree.`);
214
223
  }
215
224
 
216
225
  // packages/cli/src/commands/_preflight.ts
@@ -219,6 +228,7 @@ import { ensureProjectMainFreshBeforeRun } from "@rig/runtime/control-plane/proj
219
228
  // packages/cli/src/commands/_server-client.ts
220
229
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
221
230
  var scopedGitHubBearerTokens = new Map;
231
+ var serverReachabilityCache = new Map;
222
232
 
223
233
  // packages/cli/src/commands/_preflight.ts
224
234
  async function runProjectMainSyncPreflight(context, options) {
@@ -234,7 +244,7 @@ async function runProjectMainSyncPreflight(context, options) {
234
244
  runBootstrap: async () => {
235
245
  const bootstrap = await context.runCommand(["bun", "run", "bootstrap"]);
236
246
  if (bootstrap.exitCode !== 0) {
237
- throw new CliError2(bootstrap.stderr || bootstrap.stdout || "bun run bootstrap failed during project pre-run sync", bootstrap.exitCode || 1);
247
+ throw new CliError(bootstrap.stderr || bootstrap.stdout || "bun run bootstrap failed during project pre-run sync", bootstrap.exitCode || 1);
238
248
  }
239
249
  }
240
250
  });
@@ -313,7 +323,7 @@ async function executeAgent(context, args) {
313
323
  const id = idResult.value || agentId("agent");
314
324
  const taskId = taskResult.value?.trim();
315
325
  if (!taskId) {
316
- throw new CliError2("Usage: rig agent prepare --task <id> [--id <id>] [--mode worktree]");
326
+ throw new CliError("Usage: rig agent prepare --task <id> [--id <id>] [--mode worktree]");
317
327
  }
318
328
  const runtime = await withMutedConsole(context.outputMode === "json", () => ensureAgentRuntime({
319
329
  projectRoot: context.projectRoot,
@@ -331,7 +341,7 @@ async function executeAgent(context, args) {
331
341
  case "run": {
332
342
  const { options, commandParts } = splitAtDoubleDash(rest);
333
343
  if (commandParts.length === 0) {
334
- throw new CliError2("Usage: rig agent run [--id <id>] [--mode worktree] [--skip-project-sync] -- <command...>");
344
+ throw new CliError("Usage: rig agent run [--id <id>] [--mode worktree] [--skip-project-sync] -- <command...>");
335
345
  }
336
346
  let pending = options;
337
347
  const idResult = takeOption(pending, "--id");
@@ -347,7 +357,7 @@ async function executeAgent(context, args) {
347
357
  const id = idResult.value || agentId("agent-run");
348
358
  const taskId = taskResult.value?.trim();
349
359
  if (!taskId) {
350
- throw new CliError2("Usage: rig agent run --task <id> [--id <id>] [--mode worktree] [--skip-project-sync] -- <command...>");
360
+ throw new CliError("Usage: rig agent run --task <id> [--id <id>] [--mode worktree] [--skip-project-sync] -- <command...>");
351
361
  }
352
362
  await runProjectMainSyncPreflight(context, { disabled: skipProjectSyncResult.value });
353
363
  const createdAt = new Date().toISOString();
@@ -406,7 +416,7 @@ async function executeAgent(context, args) {
406
416
  pid: process.pid,
407
417
  errorText: result.stderr ? result.stderr.trim() : `Agent runtime command failed (${result.exitCode})`
408
418
  });
409
- throw new CliError2(`Agent runtime command failed (${result.exitCode}) in ${runtime.id}${result.stderr ? `
419
+ throw new CliError(`Agent runtime command failed (${result.exitCode}) in ${runtime.id}${result.stderr ? `
410
420
  ${result.stderr.trim()}` : ""}`, result.exitCode);
411
421
  }
412
422
  const completedAt = new Date().toISOString();
@@ -463,7 +473,7 @@ ${result.stderr.trim()}` : ""}`, result.exitCode);
463
473
  pending = idResult.rest;
464
474
  requireNoExtraArgs(pending, "rig agent cleanup (--id <id> | --all)");
465
475
  if (!allResult.value && !idResult.value) {
466
- throw new CliError2("Provide --id <id> or --all.");
476
+ throw new CliError("Provide --id <id> or --all.", 1, { hint: "Run `rig agent list` to find agent ids." });
467
477
  }
468
478
  const runtimes = await listAgentRuntimes(context.projectRoot);
469
479
  const targets = allResult.value ? runtimes.map((runtime) => runtime.id) : [idResult.value];
@@ -488,7 +498,7 @@ ${result.stderr.trim()}` : ""}`, result.exitCode);
488
498
  };
489
499
  }
490
500
  default:
491
- throw new CliError2(`Unknown agent command: ${command}`);
501
+ throw new CliError(`Unknown agent command: ${command}`, 1, { hint: "Run `rig agent --help` to list agent commands." });
492
502
  }
493
503
  }
494
504
  export {
@@ -10,10 +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
15
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
16
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
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
+ }
17
26
  function takeFlag(args, flag) {
18
27
  const rest = [];
19
28
  let value = false;
@@ -34,7 +43,7 @@ function takeOption(args, option) {
34
43
  if (current === option) {
35
44
  const next = args[index + 1];
36
45
  if (!next || next.startsWith("-")) {
37
- 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>\`.` });
38
47
  }
39
48
  value = next;
40
49
  index += 1;
@@ -93,7 +102,7 @@ async function promptBugConfirm(message, initialValue) {
93
102
  function unwrapClackPrompt(result) {
94
103
  if (clack.isCancel(result)) {
95
104
  clack.cancel("Bug report cancelled.");
96
- throw new CliError2("Bug report cancelled by user.");
105
+ throw new CliError("Bug report cancelled by user.");
97
106
  }
98
107
  return result;
99
108
  }
@@ -359,7 +368,7 @@ async function executeBrowser(context, args) {
359
368
  if (command === "app" || command === "hp-next") {
360
369
  const appSlug = command === "hp-next" ? "hp-next" : process.env.RIG_BROWSER_APP?.trim() || "";
361
370
  if (!appSlug) {
362
- throw new CliError2(`rig browser app: set RIG_BROWSER_APP=<app-slug> to select which project app scripts to invoke.
371
+ throw new CliError(`rig browser app: set RIG_BROWSER_APP=<app-slug> to select which project app scripts to invoke.
363
372
  ` + `Scripts are invoked as 'bun run app:<mode>:browser:<app-slug>'.
364
373
 
365
374
  ${browserHelpText()}`);
@@ -371,7 +380,7 @@ ${browserHelpText()}`);
371
380
  }
372
381
  const modes = ["dev", "start", "check", "e2e", "reset"];
373
382
  if (!modes.includes(subcommand)) {
374
- throw new CliError2(`Unknown browser ${command} command: ${subcommand}. Valid modes: ${modes.join(", ")}.
383
+ throw new CliError(`Unknown browser ${command} command: ${subcommand}. Valid modes: ${modes.join(", ")}.
375
384
 
376
385
  ${browserHelpText()}`);
377
386
  }
@@ -391,9 +400,9 @@ ${browserHelpText()}`);
391
400
  await context.runCommand(["bun", "run", "--filter=@rig/browser", packageScript]);
392
401
  return { ok: true, group: "browser", command };
393
402
  }
394
- throw new CliError2(`Unknown browser command: ${command}
403
+ throw new CliError(`Unknown browser command: ${command}
395
404
 
396
- ${browserHelpText()}`);
405
+ ${browserHelpText()}`, 1, { hint: "Run `rig browser help` for the full browser command reference." });
397
406
  }
398
407
  async function executeBrowserDemo(context, args) {
399
408
  const [maybeHelp] = args;
@@ -416,11 +425,11 @@ async function executeBrowserDemo(context, args) {
416
425
  pending = noBuildFlag.rest;
417
426
  requireNoExtraArgs(pending, "rig browser demo [--port <n>] [--profile <name>] [--state-dir <path>] [--target-url <url>] [--keep-open] [--no-build]");
418
427
  if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
419
- throw new CliError2("rig browser demo requires an interactive TTY in text mode.");
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)." });
420
429
  }
421
430
  const port = portResult.value ? Number.parseInt(portResult.value, 10) : undefined;
422
431
  if (port !== undefined && (!Number.isFinite(port) || port <= 0)) {
423
- throw new CliError2(`Invalid --port value: ${portResult.value}`);
432
+ throw new CliError(`Invalid --port value: ${portResult.value}`, 1, { hint: "Pass a positive port number, e.g. `--port 9222`." });
424
433
  }
425
434
  const runtime = buildBrowserDemoRuntime(context.projectRoot, {
426
435
  ...port !== undefined ? { port } : {},
@@ -575,7 +584,7 @@ function runBrowserDemoBuild(projectRoot) {
575
584
  if (result.exitCode !== 0) {
576
585
  const details = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join(`
577
586
  `);
578
- throw new CliError2(`Browser demo build failed.${details ? `
587
+ throw new CliError(`Browser demo build failed.${details ? `
579
588
  ${details}` : ""}`, result.exitCode);
580
589
  }
581
590
  }
@@ -630,7 +639,7 @@ function readBrowserDemoCommandLine(command) {
630
639
  cleanup();
631
640
  stdout.write(`
632
641
  `);
633
- rejectInput(new CliError2("Browser demo cancelled."));
642
+ rejectInput(new CliError("Browser demo cancelled."));
634
643
  return;
635
644
  }
636
645
  if (key.name === "return" || key.name === "enter") {
@@ -696,7 +705,7 @@ async function waitForBrowserDemoReady(runtime, child) {
696
705
  return { targets: nextTargets, pageTarget: nextPageTarget };
697
706
  }, child, "CDP demo page target");
698
707
  if (!version.Browser) {
699
- throw new CliError2("Browser demo reached CDP but /json/version did not include Browser metadata.");
708
+ throw new CliError("Browser demo reached CDP but /json/version did not include Browser metadata.");
700
709
  }
701
710
  return { version, targets, pageTarget };
702
711
  }
@@ -709,14 +718,14 @@ async function waitForBrowserDemo(check, child, label, timeoutMs = 18000) {
709
718
  let lastError = null;
710
719
  for (;; ) {
711
720
  if (child.exitCode !== null || child.signalCode !== null) {
712
- throw new CliError2(`Rig Browser exited before ${label} became ready.`);
721
+ throw new CliError(`Rig Browser exited before ${label} became ready.`);
713
722
  }
714
723
  try {
715
724
  return await check();
716
725
  } catch (error) {
717
726
  lastError = error;
718
727
  if (Date.now() - startedAt > timeoutMs) {
719
- throw new CliError2(`${label} did not become ready within ${timeoutMs}ms: ${String(lastError)}`);
728
+ throw new CliError(`${label} did not become ready within ${timeoutMs}ms: ${String(lastError)}`);
720
729
  }
721
730
  await new Promise((resolveDelay) => setTimeout(resolveDelay, 150));
722
731
  }
@@ -4,10 +4,19 @@ import { cancel, isCancel, select } from "@clack/prompts";
4
4
 
5
5
  // packages/cli/src/runner.ts
6
6
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
7
- import { CliError } from "@rig/runtime/control-plane/errors";
7
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
8
8
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
9
9
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
10
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
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
+ }
11
20
  function requireNoExtraArgs(args, usage) {
12
21
  if (args.length > 0) {
13
22
  throw new CliError(`Unexpected arguments: ${args.join(" ")}
@@ -37,7 +46,7 @@ function readJsonFile(path) {
37
46
  try {
38
47
  return JSON.parse(readFileSync(path, "utf8"));
39
48
  } catch (error) {
40
- throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
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>`." });
41
50
  }
42
51
  }
43
52
  function writeJsonFile(path, value) {
@@ -80,7 +89,7 @@ function writeGlobalConnections(state, options = {}) {
80
89
  function upsertGlobalConnection(alias, connection, options = {}) {
81
90
  const cleanAlias = alias.trim();
82
91
  if (!cleanAlias)
83
- throw new CliError2("Connection alias is required.", 1);
92
+ throw new CliError("Connection alias is required.", 1, { hint: "Save a server with `rig server add <alias> <url>`." });
84
93
  const state = readGlobalConnections(options);
85
94
  state.connections[cleanAlias] = connection;
86
95
  writeGlobalConnections(state, options);
@@ -180,15 +189,15 @@ function parseConnection(alias, value, options) {
180
189
  if (alias === "local" && !value)
181
190
  return { kind: "local", mode: "auto" };
182
191
  if (!value)
183
- throw new CliError2(`Missing remote server URL. Usage: ${usageName(options)} add <alias> <url>`, 1);
192
+ throw new CliError(`Missing remote server URL. Usage: ${usageName(options)} add <alias> <url>`, 1);
184
193
  let parsed;
185
194
  try {
186
195
  parsed = new URL(value);
187
196
  } catch {
188
- throw new CliError2(`Invalid Rig server URL: ${value}`, 1);
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`." });
189
198
  }
190
199
  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
191
- throw new CliError2("Rig remote server URL must be http(s).", 1);
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`." });
192
201
  }
193
202
  return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
194
203
  }
@@ -217,7 +226,7 @@ async function promptForConnectionAlias(context) {
217
226
  });
218
227
  if (isCancel(answer)) {
219
228
  cancel("No server selected.");
220
- throw new CliError2("No server selected.", 3);
229
+ throw new CliError("No server selected.", 3);
221
230
  }
222
231
  return String(answer);
223
232
  }
@@ -233,7 +242,7 @@ async function executeConnectionCommand(context, args, options) {
233
242
  case "add": {
234
243
  const [alias, url, ...extra] = rest;
235
244
  if (!alias)
236
- throw new CliError2(`Missing alias. Usage: ${usageName(options)} add <alias> <url>`, 1);
245
+ throw new CliError(`Missing alias. Usage: ${usageName(options)} add <alias> <url>`, 1);
237
246
  requireNoExtraArgs(extra, `${usageName(options)} add <alias> <url>`);
238
247
  const connection = parseConnection(alias, url, options);
239
248
  const state = upsertGlobalConnection(alias, connection);
@@ -251,11 +260,11 @@ async function executeConnectionCommand(context, args, options) {
251
260
  alias = await promptForConnectionAlias(context);
252
261
  }
253
262
  if (!alias)
254
- throw new CliError2(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
263
+ throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
255
264
  if (alias !== "local") {
256
265
  const state = readGlobalConnections();
257
266
  if (!state.connections[alias])
258
- throw new CliError2(`Unknown Rig server: ${alias}`, 1);
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>`." });
259
268
  }
260
269
  const repoState = { selected: alias, linkedAt: new Date().toISOString() };
261
270
  writeRepoConnection(context.projectRoot, repoState);
@@ -275,14 +284,10 @@ async function executeConnectionCommand(context, args, options) {
275
284
  return { ok: true, group: options.group, command: "status", details };
276
285
  }
277
286
  default:
278
- throw new CliError2(`Unknown ${options.group} command: ${String(command)}
287
+ throw new CliError(`Unknown ${options.group} command: ${String(command)}
279
288
  Usage: ${usageName(options)} <list|add|use|status>`, 1);
280
289
  }
281
290
  }
282
- async function executeConnect(context, args) {
283
- return executeConnectionCommand(context, args, { group: "connect" });
284
- }
285
291
  export {
286
- executeConnectionCommand,
287
- executeConnect
292
+ executeConnectionCommand
288
293
  };
@@ -17,10 +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
22
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
23
- 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
+ }
24
33
  function takeOption(args, option) {
25
34
  const rest = [];
26
35
  let value;
@@ -29,7 +38,7 @@ function takeOption(args, option) {
29
38
  if (current === option) {
30
39
  const next = args[index + 1];
31
40
  if (!next || next.startsWith("-")) {
32
- 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>\`.` });
33
42
  }
34
43
  value = next;
35
44
  index += 1;
@@ -65,7 +74,7 @@ function parseInstallScope(value) {
65
74
  if (value === "system") {
66
75
  return "system";
67
76
  }
68
- throw new CliError2(`Invalid --scope value: ${value}. Use user|system.`);
77
+ throw new CliError(`Invalid --scope value: ${value}. Use user|system.`);
69
78
  }
70
79
  function resolveInstallDir(scope, explicitPath) {
71
80
  if (explicitPath) {
@@ -209,7 +218,7 @@ async function executeDist(context, args) {
209
218
  source = resolve3(buildDir, "bin", "rig");
210
219
  }
211
220
  if (!existsSync(source)) {
212
- 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`." });
213
222
  }
214
223
  const installedPath = resolve3(installDir, "rig");
215
224
  if (existsSync(installedPath)) {
@@ -391,7 +400,7 @@ async function executeDist(context, args) {
391
400
  return { ok: true, group: "dist", command, details: { imageId: currentId, count: targets.length } };
392
401
  }
393
402
  default:
394
- 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." });
395
404
  }
396
405
  }
397
406
  export {
@@ -3,10 +3,19 @@ var __require = import.meta.require;
3
3
 
4
4
  // packages/cli/src/runner.ts
5
5
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
6
- import { CliError } from "@rig/runtime/control-plane/errors";
6
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
7
7
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
8
8
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
9
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
9
+
10
+ class CliError extends RuntimeCliError {
11
+ hint;
12
+ constructor(message, exitCode = 1, options = {}) {
13
+ super(message, exitCode);
14
+ if (options.hint?.trim()) {
15
+ this.hint = options.hint.trim();
16
+ }
17
+ }
18
+ }
10
19
  function requireNoExtraArgs(args, usage) {
11
20
  if (args.length > 0) {
12
21
  throw new CliError(`Unexpected arguments: ${args.join(" ")}
@@ -41,7 +50,7 @@ function readJsonFile(path) {
41
50
  try {
42
51
  return JSON.parse(readFileSync(path, "utf8"));
43
52
  } catch (error) {
44
- throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
53
+ 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>`." });
45
54
  }
46
55
  }
47
56
  function writeJsonFile(path, value) {
@@ -105,7 +114,7 @@ function resolveSelectedConnection(projectRoot, options = {}) {
105
114
  const global = readGlobalConnections(options);
106
115
  const connection = global.connections[repo.selected];
107
116
  if (!connection) {
108
- throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
117
+ throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
109
118
  }
110
119
  return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
111
120
  }
@@ -181,7 +190,7 @@ async function ensureServerForCli(projectRoot) {
181
190
  };
182
191
  } catch (error) {
183
192
  if (error instanceof Error) {
184
- throw new CliError2(error.message, 1);
193
+ throw new CliError(error.message, 1);
185
194
  }
186
195
  throw error;
187
196
  }
@@ -231,15 +240,64 @@ function diagnosticMessage(payload) {
231
240
  });
232
241
  return messages.length > 0 ? messages.join("; ") : null;
233
242
  }
243
+ var serverReachabilityCache = new Map;
244
+ async function probeServerReachability(baseUrl, authToken) {
245
+ try {
246
+ const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
247
+ headers: mergeHeaders(undefined, authToken),
248
+ signal: AbortSignal.timeout(1500)
249
+ });
250
+ return response.ok;
251
+ } catch {
252
+ return false;
253
+ }
254
+ }
255
+ function cachedServerReachability(projectRoot, baseUrl, authToken) {
256
+ const key = resolve2(projectRoot);
257
+ const cached = serverReachabilityCache.get(key);
258
+ if (cached)
259
+ return cached;
260
+ const probe = probeServerReachability(baseUrl, authToken);
261
+ serverReachabilityCache.set(key, probe);
262
+ return probe;
263
+ }
264
+ function describeSelectedServer(projectRoot, server) {
265
+ try {
266
+ const selected = resolveSelectedConnection(projectRoot);
267
+ if (selected) {
268
+ return {
269
+ alias: selected.alias,
270
+ target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
271
+ };
272
+ }
273
+ } catch {}
274
+ return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
275
+ }
276
+ async function buildServerFailureContext(projectRoot, server) {
277
+ const { alias, target } = describeSelectedServer(projectRoot, server);
278
+ const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
279
+ const reachability = reachable ? "server is reachable" : "server is unreachable";
280
+ return {
281
+ contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
282
+ hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
283
+ };
284
+ }
234
285
  async function requestServerJson(context, pathname, init = {}) {
235
286
  const server = await ensureServerForCli(context.projectRoot);
236
287
  const headers = mergeHeaders(init.headers, server.authToken);
237
288
  if (server.serverProjectRoot)
238
289
  headers.set("x-rig-project-root", server.serverProjectRoot);
239
- const response = await fetch(`${server.baseUrl}${pathname}`, {
240
- ...init,
241
- headers
242
- });
290
+ let response;
291
+ try {
292
+ response = await fetch(`${server.baseUrl}${pathname}`, {
293
+ ...init,
294
+ headers
295
+ });
296
+ } catch (error) {
297
+ const failure = await buildServerFailureContext(context.projectRoot, server);
298
+ throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
299
+ ${failure.contextLine}`, 1, { hint: failure.hint });
300
+ }
243
301
  const text = await response.text();
244
302
  const payload = text.trim().length > 0 ? (() => {
245
303
  try {
@@ -251,7 +309,9 @@ async function requestServerJson(context, pathname, init = {}) {
251
309
  if (!response.ok) {
252
310
  const diagnostics = diagnosticMessage(payload);
253
311
  const detail = diagnostics ?? (text || response.statusText);
254
- throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
312
+ const failure = await buildServerFailureContext(context.projectRoot, server);
313
+ throw new CliError(`Rig server request failed (${response.status}): ${detail}
314
+ ${failure.contextLine}`, 1, { hint: failure.hint });
255
315
  }
256
316
  return payload;
257
317
  }