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

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 { iterateServerSentEvents } from "@rig/client";
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;
@@ -53,11 +62,11 @@ import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
53
62
  // packages/cli/src/commands/_parsers.ts
54
63
  function parseRequiredPositiveInt(value, option) {
55
64
  if (!value) {
56
- throw new CliError2(`Missing value for ${option}.`);
65
+ throw new CliError(`Missing value for ${option}.`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} 10\`.` });
57
66
  }
58
67
  const parsed = Number.parseInt(value, 10);
59
68
  if (!Number.isFinite(parsed) || parsed <= 0) {
60
- throw new CliError2(`Invalid ${option} value: ${value}`);
69
+ throw new CliError(`Invalid ${option} value: ${value}`, 1, { hint: `Pass a positive integer, e.g. \`${option} 10\`.` });
61
70
  }
62
71
  return parsed;
63
72
  }
@@ -106,7 +115,7 @@ async function executeInspector(context, args) {
106
115
  const seconds = secondsResult.value ? parseRequiredPositiveInt(secondsResult.value, "--seconds") : null;
107
116
  const pollMs = pollMsResult.value ? parseRequiredPositiveInt(pollMsResult.value, "--poll-ms") : null;
108
117
  if (context.outputMode === "json" && !onceResult.value && !seconds) {
109
- throw new CliError2("--json inspector stream requires --once or --seconds <n>.", 2);
118
+ throw new CliError("--json inspector stream requires --once or --seconds <n>.", 2, { hint: "Re-run as `rig inspector stream --once --json` or `rig inspector stream --seconds 10 --json`." });
110
119
  }
111
120
  const connection = await ensureLocalRigServerConnection(context.projectRoot);
112
121
  const streamUrl = new URL("/api/inspector/stream", connection.baseUrl);
@@ -133,7 +142,7 @@ async function executeInspector(context, args) {
133
142
  signal: controller.signal
134
143
  });
135
144
  if (!response.ok) {
136
- throw new CliError2(`Inspector stream request failed (${response.status}).`, response.status);
145
+ throw new CliError(`Inspector stream request failed (${response.status}).`, response.status);
137
146
  }
138
147
  let received = 0;
139
148
  let lastEvent = null;
@@ -210,7 +219,7 @@ async function executeInspector(context, args) {
210
219
  })
211
220
  });
212
221
  if (!response.ok) {
213
- throw new CliError2(`Upstream drift scan request failed (${response.status}).`, response.status);
222
+ throw new CliError(`Upstream drift scan request failed (${response.status}).`, response.status);
214
223
  }
215
224
  const result = await response.json();
216
225
  if (context.outputMode === "text") {
@@ -246,7 +255,7 @@ async function executeInspector(context, args) {
246
255
  };
247
256
  }
248
257
  default:
249
- throw new CliError2(`Unknown inspector command: ${command}`);
258
+ throw new CliError(`Unknown inspector command: ${command}`, 1, { hint: "Run `rig inspector --help` \u2014 commands are stream|scan-upstream-drift." });
250
259
  }
251
260
  }
252
261
  export {
@@ -6,10 +6,19 @@ import { dirname, resolve } from "path";
6
6
 
7
7
  // packages/cli/src/runner.ts
8
8
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
9
- import { CliError } from "@rig/runtime/control-plane/errors";
9
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
10
10
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
11
11
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
12
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
12
+
13
+ class CliError extends RuntimeCliError {
14
+ hint;
15
+ constructor(message, exitCode = 1, options = {}) {
16
+ super(message, exitCode);
17
+ if (options.hint?.trim()) {
18
+ this.hint = options.hint.trim();
19
+ }
20
+ }
21
+ }
13
22
  function requireNoExtraArgs(args, usage) {
14
23
  if (args.length > 0) {
15
24
  throw new CliError(`Unexpected arguments: ${args.join(" ")}
@@ -51,7 +60,7 @@ async function searchNpmForPiExtensions(term) {
51
60
  const url = `https://registry.npmjs.org/-/v1/search?text=${query}&size=20`;
52
61
  const response = await fetch(url);
53
62
  if (!response.ok) {
54
- throw new CliError2(`npm registry search failed (${response.status}).`, 2);
63
+ throw new CliError(`npm registry search failed (${response.status}).`, 2, { hint: "Check network access to registry.npmjs.org, then retry `rig pi search <term>`." });
55
64
  }
56
65
  const payload = await response.json();
57
66
  const results = [];
@@ -102,12 +111,12 @@ async function executePi(context, args) {
102
111
  const [source, ...extra] = rest;
103
112
  requireNoExtraArgs(extra, "rig pi add <package-source>");
104
113
  if (!source) {
105
- throw new CliError2("Usage: rig pi add <package-source> (npm name, name@version, or git URL)", 2);
114
+ throw new CliError("Usage: rig pi add <package-source> (npm name, name@version, or git URL)", 2);
106
115
  }
107
116
  const settings = readJson(projectSettingsPath, {});
108
117
  const packages = Array.isArray(settings.packages) ? settings.packages : [];
109
118
  if (packages.some((entry) => packageKey(entry) === source)) {
110
- throw new CliError2(`"${source}" is already in ${projectSettingsPath}.`, 2);
119
+ throw new CliError(`"${source}" is already in ${projectSettingsPath}.`, 2, { hint: "Run `rig pi list` to see installed extensions." });
111
120
  }
112
121
  writeSettings(projectSettingsPath, { ...settings, packages: [...packages, source] });
113
122
  if (context.outputMode === "text") {
@@ -120,17 +129,17 @@ async function executePi(context, args) {
120
129
  const [source, ...extra] = rest;
121
130
  requireNoExtraArgs(extra, "rig pi remove <package-source>");
122
131
  if (!source) {
123
- throw new CliError2("Usage: rig pi remove <package-source>", 2);
132
+ throw new CliError("Usage: rig pi remove <package-source>", 2);
124
133
  }
125
134
  const managed = new Set(readJson(managedRecordPath, []));
126
135
  if (managed.has(source)) {
127
- throw new CliError2(`"${source}" is managed by rig.config.ts (runtime.pi.packages); remove it there instead.`, 2);
136
+ throw new CliError(`"${source}" is managed by rig.config.ts (runtime.pi.packages); remove it there instead.`, 2);
128
137
  }
129
138
  const settings = readJson(projectSettingsPath, {});
130
139
  const packages = Array.isArray(settings.packages) ? settings.packages : [];
131
140
  const next = packages.filter((entry) => packageKey(entry) !== source);
132
141
  if (next.length === packages.length) {
133
- throw new CliError2(`"${source}" is not in ${projectSettingsPath}.`, 2);
142
+ throw new CliError(`"${source}" is not in ${projectSettingsPath}.`, 2, { hint: "Run `rig pi list` to see what is installed, then `rig pi remove <source>` with an exact entry." });
134
143
  }
135
144
  const nextSettings = { ...settings };
136
145
  if (next.length > 0)
@@ -160,7 +169,7 @@ async function executePi(context, args) {
160
169
  return { ok: true, group: "pi", command, details: { term, results } };
161
170
  }
162
171
  default:
163
- throw new CliError2(`Unknown pi command: ${command}. Use list|add|remove|search.`);
172
+ throw new CliError(`Unknown pi command: ${command}. Use list|add|remove|search.`, 1, { hint: "Run `rig pi --help` for usage." });
164
173
  }
165
174
  }
166
175
  export {
@@ -7,10 +7,19 @@ import { resolve } from "path";
7
7
 
8
8
  // packages/cli/src/runner.ts
9
9
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
10
- import { CliError } from "@rig/runtime/control-plane/errors";
10
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
11
11
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
12
12
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
13
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
13
+
14
+ class CliError extends RuntimeCliError {
15
+ hint;
16
+ constructor(message, exitCode = 1, options = {}) {
17
+ super(message, exitCode);
18
+ if (options.hint?.trim()) {
19
+ this.hint = options.hint.trim();
20
+ }
21
+ }
22
+ }
14
23
  function takeOption(args, option) {
15
24
  const rest = [];
16
25
  let value;
@@ -19,7 +28,7 @@ function takeOption(args, option) {
19
28
  if (current === option) {
20
29
  const next = args[index + 1];
21
30
  if (!next || next.startsWith("-")) {
22
- throw new CliError(`Missing value for ${option}`);
31
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
23
32
  }
24
33
  value = next;
25
34
  index += 1;
@@ -126,7 +135,7 @@ async function executePlugin(context, args) {
126
135
  const taskId = requireTask(task, "rig plugin validate --task <task-id>");
127
136
  const hostCtx = await buildPluginHostContext(context.projectRoot);
128
137
  if (!hostCtx) {
129
- throw new CliError2(`No rig.config found at ${context.projectRoot}. Run \`rig init\` to set up plugins.`, 2);
138
+ throw new CliError(`No rig.config found at ${context.projectRoot}. Run \`rig init\` to set up plugins.`, 2);
130
139
  }
131
140
  const validators = hostCtx.validatorRegistry.list();
132
141
  const results = [];
@@ -162,7 +171,7 @@ async function executePlugin(context, args) {
162
171
  }
163
172
  }
164
173
  if (failed > 0) {
165
- throw new CliError2(`Plugin validation failed for ${failed} validator(s).`, 2);
174
+ throw new CliError(`Plugin validation failed for ${failed} validator(s).`, 2, { hint: "Fix the reported validators, then re-run `rig plugin validate --task <id>`." });
166
175
  }
167
176
  return {
168
177
  ok: true,
@@ -179,16 +188,16 @@ async function executePlugin(context, args) {
179
188
  case "run": {
180
189
  const [commandId, ...commandArgs] = rest;
181
190
  if (!commandId) {
182
- throw new CliError2("Usage: rig plugin run <command-id> [args...]");
191
+ throw new CliError("Usage: rig plugin run <command-id> [args...]", 1, { hint: "Run `rig plugin list` to see plugin-contributed command ids." });
183
192
  }
184
193
  const hostCtx = await buildPluginHostContext(context.projectRoot);
185
194
  if (!hostCtx) {
186
- throw new CliError2(`No rig.config found at ${context.projectRoot}. Run \`rig init\` to set up plugins.`, 2);
195
+ throw new CliError(`No rig.config found at ${context.projectRoot}. Run \`rig init\` to set up plugins.`, 2);
187
196
  }
188
197
  const registration = resolvePluginCliCommand(hostCtx.pluginHost.listCliCommands(), commandId);
189
198
  if (!registration) {
190
199
  const available = hostCtx.pluginHost.listCliCommands().map((entry) => entry.id);
191
- throw new CliError2(available.length > 0 ? `Unknown plugin command "${commandId}". Available: ${available.join(", ")}` : `No plugin CLI commands are registered. Plugins contribute them via contributes.cliCommands.`, 2);
200
+ throw new CliError(available.length > 0 ? `Unknown plugin command "${commandId}". Available: ${available.join(", ")}` : `No plugin CLI commands are registered. Plugins contribute them via contributes.cliCommands.`, 2);
192
201
  }
193
202
  if (context.dryRun) {
194
203
  if (context.outputMode === "text") {
@@ -205,12 +214,12 @@ async function executePlugin(context, args) {
205
214
  });
206
215
  const exitCode = await proc.exited;
207
216
  if (exitCode !== 0) {
208
- throw new CliError2(`Plugin command "${registration.id}" exited with code ${exitCode}.`, exitCode);
217
+ throw new CliError(`Plugin command "${registration.id}" exited with code ${exitCode}.`, exitCode, { hint: "Run `rig plugin list` to inspect the contributing plugin, or re-run with --json for details." });
209
218
  }
210
219
  return { ok: true, group: "plugin", command, details: { id: registration.id, exitCode } };
211
220
  }
212
221
  default:
213
- throw new CliError2(`Unknown plugin command: ${command}`);
222
+ throw new CliError(`Unknown plugin command: ${command}`, 1, { hint: "Run `rig plugin --help` \u2014 commands are list|validate|run." });
214
223
  }
215
224
  }
216
225
  function resolvePluginCliCommand(commands, requested) {
@@ -1,10 +1,19 @@
1
1
  // @bun
2
2
  // packages/cli/src/runner.ts
3
3
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
4
- import { CliError } from "@rig/runtime/control-plane/errors";
4
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
5
5
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
6
6
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
7
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
7
+
8
+ class CliError extends RuntimeCliError {
9
+ hint;
10
+ constructor(message, exitCode = 1, options = {}) {
11
+ super(message, exitCode);
12
+ if (options.hint?.trim()) {
13
+ this.hint = options.hint.trim();
14
+ }
15
+ }
16
+ }
8
17
  function takeOption(args, option) {
9
18
  const rest = [];
10
19
  let value;
@@ -13,7 +22,7 @@ function takeOption(args, option) {
13
22
  if (current === option) {
14
23
  const next = args[index + 1];
15
24
  if (!next || next.startsWith("-")) {
16
- throw new CliError(`Missing value for ${option}`);
25
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
17
26
  }
18
27
  value = next;
19
28
  index += 1;
@@ -89,17 +98,17 @@ async function executeProfile(context, args) {
89
98
  return { ok: true, group: "profile", command };
90
99
  case "set": {
91
100
  if (rest.length === 0) {
92
- throw new CliError2("Usage: rig profile set <claude-code|codex-cli|pi> or set [--model ...] [--runtime ...] [--plugin ...]");
101
+ throw new CliError("Usage: rig profile set <claude-code|codex-cli|pi> or set [--model ...] [--runtime ...] [--plugin ...]");
93
102
  }
94
103
  const preset = rest[0];
95
104
  if (preset && !preset.startsWith("-")) {
96
105
  if (rest.length !== 1) {
97
- throw new CliError2("Usage: rig profile set <claude-code|codex-cli|pi>");
106
+ throw new CliError("Usage: rig profile set <claude-code|codex-cli|pi>");
98
107
  }
99
108
  try {
100
109
  await withMutedConsole(context.outputMode === "json", () => setProfile(context.projectRoot, { preset }));
101
110
  } catch (error) {
102
- throw new CliError2(error instanceof Error ? error.message : String(error), 2);
111
+ throw new CliError(error instanceof Error ? error.message : String(error), 2);
103
112
  }
104
113
  return { ok: true, group: "profile", command, details: { preset } };
105
114
  }
@@ -112,7 +121,7 @@ async function executeProfile(context, args) {
112
121
  pending = pluginResult.rest;
113
122
  requireNoExtraArgs(pending, "rig profile set [--model ...] [--runtime ...] [--plugin ...]");
114
123
  if (!modelResult.value && !runtimeResult.value && !pluginResult.value) {
115
- throw new CliError2("Provide at least one of --model, --runtime, or --plugin.");
124
+ throw new CliError("Provide at least one of --model, --runtime, or --plugin.", 1, { hint: "Example: `rig profile set --model claude-sonnet`. See current values with `rig profile show`." });
116
125
  }
117
126
  try {
118
127
  await withMutedConsole(context.outputMode === "json", () => setProfile(context.projectRoot, {
@@ -121,7 +130,7 @@ async function executeProfile(context, args) {
121
130
  plugin: pluginResult.value
122
131
  }));
123
132
  } catch (error) {
124
- throw new CliError2(error instanceof Error ? error.message : String(error), 2);
133
+ throw new CliError(error instanceof Error ? error.message : String(error), 2);
125
134
  }
126
135
  return {
127
136
  ok: true,
@@ -135,7 +144,7 @@ async function executeProfile(context, args) {
135
144
  };
136
145
  }
137
146
  default:
138
- throw new CliError2(`Unknown profile command: ${command}`);
147
+ throw new CliError(`Unknown profile command: ${command}`, 1, { hint: "Run `rig profile --help` \u2014 commands are show|set." });
139
148
  }
140
149
  }
141
150
  async function executeReview(context, args) {
@@ -147,11 +156,11 @@ async function executeReview(context, args) {
147
156
  return { ok: true, group: "review", command };
148
157
  case "set": {
149
158
  if (rest.length === 0) {
150
- throw new CliError2("Usage: rig review set <off|advisory|required> [--provider greptile]");
159
+ throw new CliError("Usage: rig review set <off|advisory|required> [--provider greptile]");
151
160
  }
152
161
  const mode = rest[0];
153
162
  if (!mode) {
154
- throw new CliError2("Usage: rig review set <off|advisory|required> [--provider greptile]");
163
+ throw new CliError("Usage: rig review set <off|advisory|required> [--provider greptile]");
155
164
  }
156
165
  let pending = rest.slice(1);
157
166
  const providerResult = takeOption(pending, "--provider");
@@ -162,12 +171,12 @@ async function executeReview(context, args) {
162
171
  return setReviewProfile(context.projectRoot, mode, providerResult.value);
163
172
  });
164
173
  } catch (error) {
165
- throw new CliError2(error instanceof Error ? error.message : String(error), 2);
174
+ throw new CliError(error instanceof Error ? error.message : String(error), 2);
166
175
  }
167
176
  return { ok: true, group: "review", command, details: { mode, provider: providerResult.value || null } };
168
177
  }
169
178
  default:
170
- throw new CliError2(`Unknown review command: ${command}`);
179
+ throw new CliError(`Unknown review command: ${command}`, 1, { hint: "Run `rig review --help` \u2014 commands are show|set." });
171
180
  }
172
181
  }
173
182
  export {
@@ -1,10 +1,19 @@
1
1
  // @bun
2
2
  // packages/cli/src/runner.ts
3
3
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
4
- import { CliError } from "@rig/runtime/control-plane/errors";
4
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
5
5
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
6
6
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
7
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
7
+
8
+ class CliError extends RuntimeCliError {
9
+ hint;
10
+ constructor(message, exitCode = 1, options = {}) {
11
+ super(message, exitCode);
12
+ if (options.hint?.trim()) {
13
+ this.hint = options.hint.trim();
14
+ }
15
+ }
16
+ }
8
17
  function takeFlag(args, flag) {
9
18
  const rest = [];
10
19
  let value = false;
@@ -25,7 +34,7 @@ function takeOption(args, option) {
25
34
  if (current === option) {
26
35
  const next = args[index + 1];
27
36
  if (!next || next.startsWith("-")) {
28
- throw new CliError(`Missing value for ${option}`);
37
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
29
38
  }
30
39
  value = next;
31
40
  index += 1;
@@ -54,7 +63,7 @@ function parsePositiveInt(value, option, fallback) {
54
63
  }
55
64
  const parsed = Number.parseInt(value, 10);
56
65
  if (!Number.isFinite(parsed) || parsed <= 0) {
57
- throw new CliError2(`Invalid ${option} value: ${value}`);
66
+ throw new CliError(`Invalid ${option} value: ${value}`, 1, { hint: `Pass a positive integer, e.g. \`${option} 10\`.` });
58
67
  }
59
68
  return parsed;
60
69
  }
@@ -68,7 +77,7 @@ function parseAction(value) {
68
77
  if (value === "pipeline") {
69
78
  return "pipeline";
70
79
  }
71
- throw new CliError2(`Invalid --action value: ${value}. Use validate, verify, or pipeline.`);
80
+ throw new CliError(`Invalid --action value: ${value}. Use validate, verify, or pipeline.`);
72
81
  }
73
82
  function parseIsolationMode(value, allowOff) {
74
83
  if (!value) {
@@ -80,7 +89,7 @@ function parseIsolationMode(value, allowOff) {
80
89
  if (allowOff && value === "off") {
81
90
  return value;
82
91
  }
83
- throw new CliError2(`Invalid isolation mode: ${value}. Use ${allowOff ? "off|" : ""}worktree.`);
92
+ throw new CliError(`Invalid isolation mode: ${value}. Use ${allowOff ? "off|" : ""}worktree.`);
84
93
  }
85
94
 
86
95
  // packages/cli/src/commands/_preflight.ts
@@ -89,6 +98,7 @@ import { ensureProjectMainFreshBeforeRun } from "@rig/runtime/control-plane/proj
89
98
  // packages/cli/src/commands/_server-client.ts
90
99
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
91
100
  var scopedGitHubBearerTokens = new Map;
101
+ var serverReachabilityCache = new Map;
92
102
 
93
103
  // packages/cli/src/commands/_preflight.ts
94
104
  async function runProjectMainSyncPreflight(context, options) {
@@ -104,7 +114,7 @@ async function runProjectMainSyncPreflight(context, options) {
104
114
  runBootstrap: async () => {
105
115
  const bootstrap = await context.runCommand(["bun", "run", "bootstrap"]);
106
116
  if (bootstrap.exitCode !== 0) {
107
- throw new CliError2(bootstrap.stderr || bootstrap.stdout || "bun run bootstrap failed during project pre-run sync", bootstrap.exitCode || 1);
117
+ throw new CliError(bootstrap.stderr || bootstrap.stdout || "bun run bootstrap failed during project pre-run sync", bootstrap.exitCode || 1);
108
118
  }
109
119
  }
110
120
  });
@@ -170,7 +180,7 @@ async function executeQueue(context, args) {
170
180
  console.log(`Failed: ${summary.failed}`);
171
181
  }
172
182
  if (summary.failed > 0) {
173
- throw new CliError2(`Queue finished with ${summary.failed} failed task(s).`, 3);
183
+ throw new CliError(`Queue finished with ${summary.failed} failed task(s).`, 3, { hint: "Inspect the failures with `rig inspect failures`, then re-run `rig queue run`." });
174
184
  }
175
185
  return {
176
186
  ok: true,
@@ -188,7 +198,7 @@ async function executeQueue(context, args) {
188
198
  };
189
199
  }
190
200
  default:
191
- throw new CliError2(`Unknown queue command: ${command}`);
201
+ throw new CliError(`Unknown queue command: ${command}`, 1, { hint: "Run `rig queue --help` \u2014 the primary command is `rig queue run`." });
192
202
  }
193
203
  }
194
204
  export {
@@ -1,10 +1,19 @@
1
1
  // @bun
2
2
  // packages/cli/src/runner.ts
3
3
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
4
- import { CliError } from "@rig/runtime/control-plane/errors";
4
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
5
5
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
6
6
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
7
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
7
+
8
+ class CliError extends RuntimeCliError {
9
+ hint;
10
+ constructor(message, exitCode = 1, options = {}) {
11
+ super(message, exitCode);
12
+ if (options.hint?.trim()) {
13
+ this.hint = options.hint.trim();
14
+ }
15
+ }
16
+ }
8
17
  function takeFlag(args, flag) {
9
18
  const rest = [];
10
19
  let value = false;
@@ -25,7 +34,7 @@ function takeOption(args, option) {
25
34
  if (current === option) {
26
35
  const next = args[index + 1];
27
36
  if (!next || next.startsWith("-")) {
28
- throw new CliError(`Missing value for ${option}`);
37
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
29
38
  }
30
39
  value = next;
31
40
  index += 1;
@@ -73,17 +82,17 @@ function parseOptionalPositiveInt(value, option) {
73
82
  }
74
83
  const parsed = Number.parseInt(value, 10);
75
84
  if (!Number.isFinite(parsed) || parsed <= 0) {
76
- throw new CliError2(`Invalid ${option} value: ${value}`);
85
+ throw new CliError(`Invalid ${option} value: ${value}`, 1, { hint: `Pass a positive integer, e.g. \`${option} 10\`.` });
77
86
  }
78
87
  return parsed;
79
88
  }
80
89
  function parseRequiredPositiveInt(value, option) {
81
90
  if (!value) {
82
- throw new CliError2(`Missing value for ${option}.`);
91
+ throw new CliError(`Missing value for ${option}.`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} 10\`.` });
83
92
  }
84
93
  const parsed = Number.parseInt(value, 10);
85
94
  if (!Number.isFinite(parsed) || parsed <= 0) {
86
- throw new CliError2(`Invalid ${option} value: ${value}`);
95
+ throw new CliError(`Invalid ${option} value: ${value}`, 1, { hint: `Pass a positive integer, e.g. \`${option} 10\`.` });
87
96
  }
88
97
  return parsed;
89
98
  }
@@ -158,7 +167,7 @@ async function executeRemote(context, args) {
158
167
  pending2 = token.rest;
159
168
  requireNoExtraArgs(pending2, "rig remote endpoint add --alias <a> --host <h> --port <n> --token <t>");
160
169
  if (!alias.value || !host.value || !token.value || !port.value) {
161
- throw new CliError2("remote endpoint add requires --alias, --host, --port, and --token.");
170
+ throw new CliError("remote endpoint add requires --alias, --host, --port, and --token.", 1, { hint: "Re-run as `rig remote endpoint add --alias <a> --host <h> --port <n> --token <t>`." });
162
171
  }
163
172
  const created = upsertManagedRemoteEndpoint({
164
173
  alias: alias.value,
@@ -185,7 +194,7 @@ async function executeRemote(context, args) {
185
194
  pending2 = token.rest;
186
195
  requireNoExtraArgs(pending2, "rig remote endpoint update --id <id> [--alias <a>] [--host <h>] [--port <n>] [--token <t>]");
187
196
  if (!endpointId.value && !alias.value) {
188
- throw new CliError2("remote endpoint update requires --id <id> or --alias <a>.");
197
+ throw new CliError("remote endpoint update requires --id <id> or --alias <a>.", 1, { hint: "List endpoints with `rig remote endpoint list`, then pass --id or --alias." });
189
198
  }
190
199
  const updated = updateManagedRemoteEndpointInAuthority(context.projectRoot, {
191
200
  endpointId: endpointId.value || undefined,
@@ -195,7 +204,7 @@ async function executeRemote(context, args) {
195
204
  token: token.value || undefined
196
205
  });
197
206
  if (!updated) {
198
- throw new CliError2("Remote endpoint not found.", 2);
207
+ throw new CliError("Remote endpoint not found.", 2, { hint: "Run `rig remote endpoint list` to see configured endpoints." });
199
208
  }
200
209
  if (context.outputMode === "text") {
201
210
  console.log(`Updated remote endpoint ${updated.alias} -> ${updated.host}:${updated.port}`);
@@ -208,11 +217,11 @@ async function executeRemote(context, args) {
208
217
  pending2 = alias.rest;
209
218
  requireNoExtraArgs(pending2, "rig remote endpoint remove --alias <a>");
210
219
  if (!alias.value) {
211
- throw new CliError2("remote endpoint remove requires --alias.");
220
+ throw new CliError("remote endpoint remove requires --alias.", 1, { hint: "Run `rig remote endpoint list` to find the alias, then `rig remote endpoint remove --alias <a>`." });
212
221
  }
213
222
  const removed = removeManagedRemoteEndpoint(alias.value, undefined, context.projectRoot);
214
223
  if (!removed) {
215
- throw new CliError2(`Remote endpoint alias not found: ${alias.value}`, 2);
224
+ throw new CliError(`Remote endpoint alias not found: ${alias.value}`, 2, { hint: "Run `rig remote endpoint list` to see configured endpoint aliases." });
216
225
  }
217
226
  if (context.outputMode === "text") {
218
227
  console.log(`Removed remote endpoint ${alias.value}`);
@@ -225,7 +234,7 @@ async function executeRemote(context, args) {
225
234
  pending2 = alias.rest;
226
235
  requireNoExtraArgs(pending2, "rig remote endpoint test --alias <a>");
227
236
  if (!alias.value) {
228
- throw new CliError2("remote endpoint test requires --alias.");
237
+ throw new CliError("remote endpoint test requires --alias.", 1, { hint: "Run `rig remote endpoint list` to find the alias, then `rig remote endpoint test --alias <a>`." });
229
238
  }
230
239
  const endpoint2 = resolveRemoteEndpoint({ remoteAlias: alias.value, projectRoot: context.projectRoot });
231
240
  const client = new RemoteWsClient(endpoint2);
@@ -270,7 +279,7 @@ async function executeRemote(context, args) {
270
279
  return { ok: true, group: "remote", command: "endpoint doctor", details: result };
271
280
  }
272
281
  default:
273
- throw new CliError2(`Unknown remote endpoint command: ${subcommand}`);
282
+ throw new CliError(`Unknown remote endpoint command: ${subcommand}`, 1, { hint: "Run `rig remote --help` for endpoint commands (list|add|update|remove|test)." });
274
283
  }
275
284
  }
276
285
  let pending = rest;
@@ -339,7 +348,7 @@ async function executeRemote(context, args) {
339
348
  const seconds = parseOptionalPositiveInt(secondsResult.value, "--seconds");
340
349
  const eventFilter = eventResult.value || undefined;
341
350
  if (context.outputMode === "json" && !seconds) {
342
- throw new CliError2("--json remote watch requires --seconds <n> to produce bounded output.", 2);
351
+ throw new CliError("--json remote watch requires --seconds <n> to produce bounded output.", 2, { hint: "Re-run as `rig remote watch --seconds 10 --json`." });
343
352
  }
344
353
  const captured = [];
345
354
  let dropped = 0;
@@ -491,11 +500,11 @@ async function executeRemote(context, args) {
491
500
  return { ok: true, group: "remote", command, details: toDetails(response) };
492
501
  }
493
502
  default:
494
- throw new CliError2(`Unknown remote command: ${command}`);
503
+ throw new CliError(`Unknown remote command: ${command}`, 1, { hint: "Run `rig remote --help` to list remote commands." });
495
504
  }
496
505
  } catch (error) {
497
506
  if (error instanceof RemoteCliError) {
498
- throw new CliError2(`[${error.code}] ${error.message}`, error.exitCode);
507
+ throw new CliError(`[${error.code}] ${error.message}`, error.exitCode);
499
508
  }
500
509
  throw error;
501
510
  }
@@ -1,10 +1,19 @@
1
1
  // @bun
2
2
  // packages/cli/src/runner.ts
3
3
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
4
- import { CliError } from "@rig/runtime/control-plane/errors";
4
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
5
5
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
6
6
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
7
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
7
+
8
+ class CliError extends RuntimeCliError {
9
+ hint;
10
+ constructor(message, exitCode = 1, options = {}) {
11
+ super(message, exitCode);
12
+ if (options.hint?.trim()) {
13
+ this.hint = options.hint.trim();
14
+ }
15
+ }
16
+ }
8
17
  function formatCommand(parts) {
9
18
  return parts.map((part) => /[^a-zA-Z0-9_./:-]/.test(part) ? JSON.stringify(part) : part).join(" ");
10
19
  }
@@ -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;
@@ -146,7 +155,7 @@ async function enforceNativeCommandPolicy(context, args, options) {
146
155
  appendControlledBashAudit(context, mode, command, args, matchedRuleIds, action === "block" ? "blocked" : action === "warn" ? "warn" : undefined);
147
156
  }
148
157
  if (action === "block") {
149
- throw new CliError2(`Policy blocked command: ${command}`, 126);
158
+ throw new CliError(`Policy blocked command: ${command}`, 126, { hint: "Review rig/policy/policy.json, or re-run with `--policy-mode observe` to log instead of block." });
150
159
  }
151
160
  }
152
161
 
@@ -167,12 +176,12 @@ async function executeRepo(context, args) {
167
176
  return { ok: true, group: "repo", command, details: { keepTaskStatus: keepTaskStatusFlag } };
168
177
  }
169
178
  default:
170
- throw new CliError2(`Unknown repo command: ${command}`);
179
+ throw new CliError(`Unknown repo command: ${command}`, 1, { hint: "Run `rig repo --help` \u2014 commands are sync|reset-baseline." });
171
180
  }
172
181
  }
173
182
  async function executeGit(context, args) {
174
183
  if (args.length === 0) {
175
- throw new CliError2("Usage: rig git <git-flow args...>");
184
+ throw new CliError("Usage: rig git <git-flow args...>");
176
185
  }
177
186
  await enforceNativeCommandPolicy(context, args, {
178
187
  commandPrefix: "rig-agent git",
@@ -187,13 +196,13 @@ async function executeGit(context, args) {
187
196
  try {
188
197
  await executeHarnessCommand(context.projectRoot, ["git", ...args]);
189
198
  } catch (error) {
190
- throw new CliError2(error instanceof Error ? error.message : String(error), 2);
199
+ throw new CliError(error instanceof Error ? error.message : String(error), 2);
191
200
  }
192
201
  return { ok: true, group: "git", command: args[0] ?? "git" };
193
202
  }
194
203
  async function executeHarness(context, args) {
195
204
  if (args.length === 0) {
196
- throw new CliError2("Usage: rig harness <harness args...>");
205
+ throw new CliError("Usage: rig harness <harness args...>");
197
206
  }
198
207
  await enforceNativeCommandPolicy(context, args, {
199
208
  commandPrefix: "rig-agent",
@@ -208,7 +217,7 @@ async function executeHarness(context, args) {
208
217
  try {
209
218
  await executeHarnessCommand(context.projectRoot, args);
210
219
  } catch (error) {
211
- throw new CliError2(error instanceof Error ? error.message : String(error), 2);
220
+ throw new CliError(error instanceof Error ? error.message : String(error), 2);
212
221
  }
213
222
  return { ok: true, group: "harness", command: args[0] ?? "harness" };
214
223
  }