@h-rig/cli 0.0.6-alpha.7 → 0.0.6-alpha.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/rig.js +4507 -1506
  3. package/dist/src/commands/_async-ui.js +152 -0
  4. package/dist/src/commands/_authority-runs.js +2 -3
  5. package/dist/src/commands/_cli-format.js +369 -0
  6. package/dist/src/commands/_connection-state.js +30 -11
  7. package/dist/src/commands/_doctor-checks.js +177 -43
  8. package/dist/src/commands/_help-catalog.js +485 -0
  9. package/dist/src/commands/_json-output.js +56 -0
  10. package/dist/src/commands/_operator-surface.js +220 -0
  11. package/dist/src/commands/_operator-view.js +595 -72
  12. package/dist/src/commands/_parsers.js +18 -11
  13. package/dist/src/commands/_pi-frontend.js +411 -0
  14. package/dist/src/commands/_pi-install.js +4 -3
  15. package/dist/src/commands/_policy.js +12 -5
  16. package/dist/src/commands/_preflight.js +187 -127
  17. package/dist/src/commands/_run-driver-helpers.js +75 -22
  18. package/dist/src/commands/_run-replay.js +142 -0
  19. package/dist/src/commands/_server-client.js +343 -60
  20. package/dist/src/commands/_snapshot-upload.js +160 -38
  21. package/dist/src/commands/_spinner.js +65 -0
  22. package/dist/src/commands/_task-picker.js +44 -16
  23. package/dist/src/commands/agent.js +39 -20
  24. package/dist/src/commands/browser.js +28 -21
  25. package/dist/src/commands/connect.js +146 -33
  26. package/dist/src/commands/dist.js +19 -12
  27. package/dist/src/commands/doctor.js +304 -44
  28. package/dist/src/commands/github.js +301 -52
  29. package/dist/src/commands/inbox.js +679 -72
  30. package/dist/src/commands/init.js +622 -118
  31. package/dist/src/commands/inspect.js +515 -32
  32. package/dist/src/commands/inspector.js +20 -13
  33. package/dist/src/commands/pi.js +177 -0
  34. package/dist/src/commands/plugin.js +95 -27
  35. package/dist/src/commands/profile-and-review.js +26 -19
  36. package/dist/src/commands/queue.js +32 -12
  37. package/dist/src/commands/remote.js +43 -36
  38. package/dist/src/commands/repo-git-harness.js +22 -15
  39. package/dist/src/commands/run.js +1162 -158
  40. package/dist/src/commands/server.js +373 -56
  41. package/dist/src/commands/setup.js +316 -62
  42. package/dist/src/commands/stats.js +1030 -0
  43. package/dist/src/commands/task-report-bug.js +29 -22
  44. package/dist/src/commands/task-run-driver.js +862 -129
  45. package/dist/src/commands/task.js +1423 -311
  46. package/dist/src/commands/test.js +15 -8
  47. package/dist/src/commands/workspace.js +18 -11
  48. package/dist/src/commands.js +4446 -1499
  49. package/dist/src/index.js +4502 -1504
  50. package/dist/src/launcher.js +77 -13
  51. package/dist/src/report-bug.js +3 -3
  52. package/dist/src/runner.js +16 -22
  53. package/package.json +10 -5
@@ -1,12 +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
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
7
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
8
6
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
9
- 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
+ }
10
17
  function takeOption(args, option) {
11
18
  const rest = [];
12
19
  let value;
@@ -15,7 +22,7 @@ function takeOption(args, option) {
15
22
  if (current === option) {
16
23
  const next = args[index + 1];
17
24
  if (!next || next.startsWith("-")) {
18
- 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>\`.` });
19
26
  }
20
27
  value = next;
21
28
  index += 1;
@@ -34,12 +41,14 @@ Usage: ${usage}`);
34
41
  }
35
42
  }
36
43
 
44
+ // packages/cli/src/commands/server.ts
45
+ import { resolveRigServerCommand } from "@rig/runtime/local-server";
46
+
37
47
  // packages/cli/src/commands/_authority-runs.ts
38
48
  import {
39
49
  readAuthorityRun,
40
50
  readJsonlFile,
41
- resolveAuthorityRunDir,
42
- writeJsonFile
51
+ writeAuthorityRunRecord
43
52
  } from "@rig/runtime/control-plane/authority-files";
44
53
 
45
54
  // packages/cli/src/commands/_paths.ts
@@ -60,11 +69,8 @@ function normalizeRuntimeAdapter(value) {
60
69
  return "claude-code";
61
70
  }
62
71
 
63
- // packages/cli/src/commands/_server-client.ts
64
- import { spawnSync } from "child_process";
65
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
66
- import { resolve as resolve2 } from "path";
67
- import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
72
+ // packages/cli/src/commands/connect.ts
73
+ import { cancel, isCancel, select } from "@clack/prompts";
68
74
 
69
75
  // packages/cli/src/commands/_connection-state.ts
70
76
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -88,9 +94,14 @@ function readJsonFile(path) {
88
94
  try {
89
95
  return JSON.parse(readFileSync(path, "utf8"));
90
96
  } catch (error) {
91
- throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
97
+ 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>`." });
92
98
  }
93
99
  }
100
+ function writeJsonFile(path, value) {
101
+ mkdirSync(dirname(path), { recursive: true });
102
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
103
+ `, "utf8");
104
+ }
94
105
  function normalizeConnection(value) {
95
106
  if (!value || typeof value !== "object" || Array.isArray(value))
96
107
  return null;
@@ -120,6 +131,18 @@ function readGlobalConnections(options = {}) {
120
131
  }
121
132
  return { connections };
122
133
  }
134
+ function writeGlobalConnections(state, options = {}) {
135
+ writeJsonFile(resolveGlobalConnectionsPath(options.env ?? process.env), state);
136
+ }
137
+ function upsertGlobalConnection(alias, connection, options = {}) {
138
+ const cleanAlias = alias.trim();
139
+ if (!cleanAlias)
140
+ throw new CliError("Connection alias is required.", 1, { hint: "Save a server with `rig server add <alias> <url>`." });
141
+ const state = readGlobalConnections(options);
142
+ state.connections[cleanAlias] = connection;
143
+ writeGlobalConnections(state, options);
144
+ return state;
145
+ }
123
146
  function readRepoConnection(projectRoot) {
124
147
  const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
125
148
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
@@ -131,25 +154,217 @@ function readRepoConnection(projectRoot) {
131
154
  return {
132
155
  selected,
133
156
  project: typeof record.project === "string" ? record.project : undefined,
134
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
157
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
158
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
135
159
  };
136
160
  }
161
+ function writeRepoConnection(projectRoot, state) {
162
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
163
+ }
137
164
  function resolveSelectedConnection(projectRoot, options = {}) {
138
165
  const repo = readRepoConnection(projectRoot);
139
166
  if (!repo)
140
167
  return null;
141
168
  if (repo.selected === "local")
142
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
169
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
143
170
  const global = readGlobalConnections(options);
144
171
  const connection = global.connections[repo.selected];
145
172
  if (!connection) {
146
- throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
173
+ throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
174
+ }
175
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
176
+ }
177
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
178
+ const repo = readRepoConnection(projectRoot);
179
+ if (!repo)
180
+ return;
181
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
182
+ }
183
+
184
+ // packages/cli/src/commands/_cli-format.ts
185
+ import { log, note } from "@clack/prompts";
186
+ import pc from "picocolors";
187
+ function truncate(value, width) {
188
+ if (value.length <= width)
189
+ return value;
190
+ if (width <= 1)
191
+ return "\u2026";
192
+ return `${value.slice(0, width - 1)}\u2026`;
193
+ }
194
+ function pad(value, width) {
195
+ return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
196
+ }
197
+ function statusColor(status) {
198
+ const normalized = status.toLowerCase();
199
+ if (["completed", "merged", "closed", "done", "accepted", "pass", "selected", "approved"].includes(normalized))
200
+ return pc.green;
201
+ if (["failed", "needs_attention", "needs-attention", "blocked", "error", "rejected"].includes(normalized))
202
+ return pc.red;
203
+ if (["running", "reviewing", "validating", "in_progress", "in-progress", "remote"].includes(normalized))
204
+ return pc.cyan;
205
+ if (["ready", "open", "queued", "created", "preparing", "local", "pending"].includes(normalized))
206
+ return pc.yellow;
207
+ return pc.dim;
208
+ }
209
+ function formatStatusPill(status) {
210
+ const label = status || "unknown";
211
+ return statusColor(label)(`\u25CF ${label}`);
212
+ }
213
+ function formatSection(title, subtitle) {
214
+ return `${pc.bold(pc.cyan("\u25C6"))} ${pc.bold(title)}${subtitle ? pc.dim(` \u2014 ${subtitle}`) : ""}`;
215
+ }
216
+ function formatSuccessCard(title, rows = []) {
217
+ const body = rows.filter(([, value]) => value !== undefined && value !== null && String(value).length > 0).map(([key, value]) => `${pc.dim("\u2502")} ${pc.dim(key.padEnd(12))} ${value}`);
218
+ return [formatSection(title), ...body].join(`
219
+ `);
220
+ }
221
+ function formatNextSteps(steps) {
222
+ if (steps.length === 0)
223
+ return [];
224
+ return [pc.bold("Next"), ...steps.map((step) => `${pc.dim("\u203A")} ${step}`)];
225
+ }
226
+ function formatConnectionList(connections) {
227
+ const rows = [["local", { kind: "local", mode: "auto" }], ...Object.entries(connections)];
228
+ const aliasWidth = Math.min(24, Math.max(5, ...rows.map(([alias]) => alias.length)));
229
+ const lines = rows.map(([alias, connection]) => [
230
+ pc.bold(pad(truncate(alias, aliasWidth), aliasWidth)),
231
+ formatStatusPill(connection.kind),
232
+ connection.kind === "remote" ? connection.baseUrl ?? "" : connection.mode ?? "local"
233
+ ].join(" "));
234
+ 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(`
235
+ `);
236
+ }
237
+ function formatConnectionStatus(selected, connections) {
238
+ const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
239
+ const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
240
+ return [
241
+ formatSection("Rig server", "selected for this repo"),
242
+ `${pc.dim("\u2502")} ${pc.dim("selected ")} ${pc.bold(selected)}`,
243
+ `${pc.dim("\u2502")} ${pc.dim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
244
+ `${pc.dim("\u2502")} ${pc.dim("target ")} ${target ?? "not configured"}`,
245
+ "",
246
+ ...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
247
+ ].join(`
248
+ `);
249
+ }
250
+
251
+ // packages/cli/src/commands/connect.ts
252
+ function usageName(options) {
253
+ return `rig ${options.group}`;
254
+ }
255
+ function parseConnection(alias, value, options) {
256
+ if (alias === "local" && !value)
257
+ return { kind: "local", mode: "auto" };
258
+ if (!value)
259
+ throw new CliError(`Missing remote server URL. Usage: ${usageName(options)} add <alias> <url>`, 1);
260
+ let parsed;
261
+ try {
262
+ parsed = new URL(value);
263
+ } catch {
264
+ 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`." });
265
+ }
266
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
267
+ 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`." });
268
+ }
269
+ return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
270
+ }
271
+ function printJsonOrText(context, payload, text) {
272
+ if (context.outputMode === "json") {
273
+ console.log(JSON.stringify(payload, null, 2));
274
+ } else {
275
+ console.log(text);
276
+ }
277
+ }
278
+ async function promptForConnectionAlias(context) {
279
+ const state = readGlobalConnections();
280
+ const repo = readRepoConnection(context.projectRoot);
281
+ const options = [
282
+ { value: "local", label: "local", hint: "Use/start a local Rig server" },
283
+ ...Object.entries(state.connections).map(([alias, connection]) => ({
284
+ value: alias,
285
+ label: alias,
286
+ hint: connection.kind === "remote" ? connection.baseUrl : "local"
287
+ }))
288
+ ].filter((option, index, all) => all.findIndex((candidate) => candidate.value === option.value) === index);
289
+ const answer = await select({
290
+ message: "Select Rig server for this repo",
291
+ initialValue: repo?.selected ?? "local",
292
+ options
293
+ });
294
+ if (isCancel(answer)) {
295
+ cancel("No server selected.");
296
+ throw new CliError("No server selected.", 3);
297
+ }
298
+ return String(answer);
299
+ }
300
+ async function executeConnectionCommand(context, args, options) {
301
+ const [command, ...rest] = args;
302
+ switch (command ?? "status") {
303
+ case "list": {
304
+ requireNoExtraArgs(rest, `${usageName(options)} list`);
305
+ const state = readGlobalConnections();
306
+ printJsonOrText(context, state, formatConnectionList(state.connections));
307
+ return { ok: true, group: options.group, command: "list", details: state };
308
+ }
309
+ case "add": {
310
+ const [alias, url, ...extra] = rest;
311
+ if (!alias)
312
+ throw new CliError(`Missing alias. Usage: ${usageName(options)} add <alias> <url>`, 1);
313
+ requireNoExtraArgs(extra, `${usageName(options)} add <alias> <url>`);
314
+ const connection = parseConnection(alias, url, options);
315
+ const state = upsertGlobalConnection(alias, connection);
316
+ printJsonOrText(context, { alias, connection }, formatSuccessCard("Rig server saved", [
317
+ ["alias", alias],
318
+ ["target", connection.kind === "remote" ? connection.baseUrl : "local"],
319
+ ["next", `${usageName(options)} use ${alias}`]
320
+ ]));
321
+ return { ok: true, group: options.group, command: "add", details: { alias, connection, count: Object.keys(state.connections).length } };
322
+ }
323
+ case "use": {
324
+ let [alias, ...extra] = rest;
325
+ requireNoExtraArgs(extra, `${usageName(options)} use <alias|local>`);
326
+ if (!alias && options.interactiveUse && context.outputMode === "text" && process.stdin.isTTY) {
327
+ alias = await promptForConnectionAlias(context);
328
+ }
329
+ if (!alias)
330
+ throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
331
+ if (alias !== "local") {
332
+ const state = readGlobalConnections();
333
+ if (!state.connections[alias])
334
+ 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>`." });
335
+ }
336
+ const repoState = { selected: alias, linkedAt: new Date().toISOString() };
337
+ writeRepoConnection(context.projectRoot, repoState);
338
+ printJsonOrText(context, repoState, formatSuccessCard("Rig server selected", [
339
+ ["selected", alias],
340
+ ["scope", "this repo"],
341
+ ["next", "rig task list"]
342
+ ]));
343
+ return { ok: true, group: options.group, command: "use", details: repoState };
344
+ }
345
+ case "status": {
346
+ requireNoExtraArgs(rest, `${usageName(options)} status`);
347
+ const repo = readRepoConnection(context.projectRoot);
348
+ const global = readGlobalConnections();
349
+ const details = { selected: repo?.selected ?? "local", repo, connections: global.connections };
350
+ printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections));
351
+ return { ok: true, group: options.group, command: "status", details };
352
+ }
353
+ default:
354
+ throw new CliError(`Unknown ${options.group} command: ${String(command)}
355
+ Usage: ${usageName(options)} <list|add|use|status>`, 1);
147
356
  }
148
- return { alias: repo.selected, connection };
149
357
  }
150
358
 
151
359
  // packages/cli/src/commands/_server-client.ts
152
- var cachedGitHubBearerToken;
360
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
361
+ import { resolve as resolve2 } from "path";
362
+ import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
363
+ var scopedGitHubBearerTokens = new Map;
364
+ var serverPhaseListener = null;
365
+ function reportServerPhase(label) {
366
+ serverPhaseListener?.(label);
367
+ }
153
368
  function cleanToken(value) {
154
369
  const trimmed = value?.trim();
155
370
  return trimmed ? trimmed : null;
@@ -166,49 +381,80 @@ function readPrivateRemoteSessionToken(projectRoot) {
166
381
  }
167
382
  }
168
383
  function readGitHubBearerTokenForRemote(projectRoot) {
169
- if (cachedGitHubBearerToken !== undefined)
170
- return cachedGitHubBearerToken;
384
+ const scopedKey = resolve2(projectRoot);
385
+ if (scopedGitHubBearerTokens.has(scopedKey))
386
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
171
387
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
172
- if (privateSession) {
173
- cachedGitHubBearerToken = privateSession;
174
- return cachedGitHubBearerToken;
175
- }
176
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
177
- if (envToken) {
178
- cachedGitHubBearerToken = envToken;
179
- return cachedGitHubBearerToken;
388
+ if (privateSession)
389
+ return privateSession;
390
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
391
+ }
392
+ function readStoredGitHubAuthToken(projectRoot) {
393
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
394
+ if (!existsSync2(path))
395
+ return null;
396
+ try {
397
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
398
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
399
+ } catch {
400
+ return null;
180
401
  }
181
- const result = spawnSync("gh", ["auth", "token"], {
182
- encoding: "utf8",
183
- timeout: 5000,
184
- stdio: ["ignore", "pipe", "ignore"]
185
- });
186
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
187
- return cachedGitHubBearerToken;
402
+ }
403
+ function readLocalConnectionFallbackToken(projectRoot) {
404
+ return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
188
405
  }
189
406
  async function ensureServerForCli(projectRoot) {
190
407
  try {
191
408
  const selected = resolveSelectedConnection(projectRoot);
192
409
  if (selected?.connection.kind === "remote") {
410
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
411
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
412
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
193
413
  return {
194
414
  baseUrl: selected.connection.baseUrl,
195
- authToken: readGitHubBearerTokenForRemote(projectRoot),
196
- connectionKind: "remote"
415
+ authToken,
416
+ connectionKind: "remote",
417
+ serverProjectRoot
197
418
  };
198
419
  }
420
+ reportServerPhase("Starting local Rig server\u2026");
199
421
  const connection = await ensureLocalRigServerConnection(projectRoot);
200
422
  return {
201
423
  baseUrl: connection.baseUrl,
202
- authToken: connection.authToken,
203
- connectionKind: "local"
424
+ authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
425
+ connectionKind: "local",
426
+ serverProjectRoot: resolve2(projectRoot)
204
427
  };
205
428
  } catch (error) {
206
429
  if (error instanceof Error) {
207
- throw new CliError2(error.message, 1);
430
+ throw new CliError(error.message, 1);
208
431
  }
209
432
  throw error;
210
433
  }
211
434
  }
435
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
436
+ const repo = readRepoConnection(projectRoot);
437
+ const slug = repo?.project?.trim();
438
+ if (!slug)
439
+ return null;
440
+ try {
441
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
442
+ headers: mergeHeaders(undefined, authToken)
443
+ });
444
+ if (!response.ok)
445
+ return null;
446
+ const payload = await response.json();
447
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
448
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
449
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
450
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
451
+ if (path)
452
+ writeRepoServerProjectRoot(projectRoot, path);
453
+ return path;
454
+ } catch {
455
+ return null;
456
+ }
457
+ }
212
458
  function mergeHeaders(headers, authToken) {
213
459
  const merged = new Headers(headers);
214
460
  if (authToken) {
@@ -231,12 +477,65 @@ function diagnosticMessage(payload) {
231
477
  });
232
478
  return messages.length > 0 ? messages.join("; ") : null;
233
479
  }
480
+ var serverReachabilityCache = new Map;
481
+ async function probeServerReachability(baseUrl, authToken) {
482
+ try {
483
+ const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
484
+ headers: mergeHeaders(undefined, authToken),
485
+ signal: AbortSignal.timeout(1500)
486
+ });
487
+ return response.ok;
488
+ } catch {
489
+ return false;
490
+ }
491
+ }
492
+ function cachedServerReachability(projectRoot, baseUrl, authToken) {
493
+ const key = resolve2(projectRoot);
494
+ const cached = serverReachabilityCache.get(key);
495
+ if (cached)
496
+ return cached;
497
+ const probe = probeServerReachability(baseUrl, authToken);
498
+ serverReachabilityCache.set(key, probe);
499
+ return probe;
500
+ }
501
+ function describeSelectedServer(projectRoot, server) {
502
+ try {
503
+ const selected = resolveSelectedConnection(projectRoot);
504
+ if (selected) {
505
+ return {
506
+ alias: selected.alias,
507
+ target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
508
+ };
509
+ }
510
+ } catch {}
511
+ return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
512
+ }
513
+ async function buildServerFailureContext(projectRoot, server) {
514
+ const { alias, target } = describeSelectedServer(projectRoot, server);
515
+ const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
516
+ const reachability = reachable ? "server is reachable" : "server is unreachable";
517
+ return {
518
+ contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
519
+ hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
520
+ };
521
+ }
234
522
  async function requestServerJson(context, pathname, init = {}) {
235
523
  const server = await ensureServerForCli(context.projectRoot);
236
- const response = await fetch(`${server.baseUrl}${pathname}`, {
237
- ...init,
238
- headers: mergeHeaders(init.headers, server.authToken)
239
- });
524
+ const headers = mergeHeaders(init.headers, server.authToken);
525
+ if (server.serverProjectRoot)
526
+ headers.set("x-rig-project-root", server.serverProjectRoot);
527
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
528
+ let response;
529
+ try {
530
+ response = await fetch(`${server.baseUrl}${pathname}`, {
531
+ ...init,
532
+ headers
533
+ });
534
+ } catch (error) {
535
+ const failure = await buildServerFailureContext(context.projectRoot, server);
536
+ throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
537
+ ${failure.contextLine}`, 1, { hint: failure.hint });
538
+ }
240
539
  const text = await response.text();
241
540
  const payload = text.trim().length > 0 ? (() => {
242
541
  try {
@@ -248,10 +547,23 @@ async function requestServerJson(context, pathname, init = {}) {
248
547
  if (!response.ok) {
249
548
  const diagnostics = diagnosticMessage(payload);
250
549
  const detail = diagnostics ?? (text || response.statusText);
251
- throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
550
+ const failure = await buildServerFailureContext(context.projectRoot, server);
551
+ throw new CliError(`Rig server request failed (${response.status}): ${detail}
552
+ ${failure.contextLine}`, 1, { hint: failure.hint });
252
553
  }
253
554
  return payload;
254
555
  }
556
+ var RESUMABLE_RUN_STATUSES = new Set([
557
+ "created",
558
+ "preparing",
559
+ "running",
560
+ "validating",
561
+ "reviewing",
562
+ "stopped",
563
+ "failed",
564
+ "needs-attention",
565
+ "needs_attention"
566
+ ]);
255
567
  async function submitTaskRunViaServer(context, input) {
256
568
  const isTaskRun = Boolean(input.taskId);
257
569
  const endpoint = isTaskRun ? "/api/runs/task" : "/api/runs/adhoc";
@@ -275,18 +587,21 @@ async function submitTaskRunViaServer(context, input) {
275
587
  })
276
588
  });
277
589
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
278
- throw new CliError2("Rig server returned an invalid run submission payload.", 1);
590
+ throw new CliError("Rig server returned an invalid run submission payload.", 1, { hint: "Check `rig server status` and retry; `rig run list` shows whether the run was created anyway." });
279
591
  }
280
592
  const runId = payload.runId;
281
593
  if (typeof runId !== "string" || runId.trim().length === 0) {
282
- throw new CliError2("Rig server returned no runId for the submitted run.", 1);
594
+ throw new CliError("Rig server returned no runId for the submitted run.", 1, { hint: "Check `rig run list` \u2014 the run may still have been created; otherwise retry the submission." });
283
595
  }
284
596
  return { runId };
285
597
  }
286
598
 
287
599
  // packages/cli/src/commands/server.ts
288
600
  async function executeServer(context, args, options) {
289
- const [command = "start", ...rest] = args;
601
+ const [command = "status", ...rest] = args;
602
+ if (["status", "list", "add", "use"].includes(command)) {
603
+ return executeConnectionCommand(context, [command, ...rest], { group: "server", interactiveUse: true });
604
+ }
290
605
  switch (command) {
291
606
  case "start": {
292
607
  let pending = rest;
@@ -298,8 +613,9 @@ async function executeServer(context, args, options) {
298
613
  pending = pollResult.rest;
299
614
  const authTokenResult = takeOption(pending, "--auth-token");
300
615
  pending = authTokenResult.rest;
301
- requireNoExtraArgs(pending, "bun run rig server start [--host <host>] [--port <n>] [--poll-ms <n>] [--auth-token <token>]");
302
- const commandParts = ["rig-server", "start"];
616
+ requireNoExtraArgs(pending, "rig server start [--host <host>] [--port <n>] [--poll-ms <n>] [--auth-token <token>]");
617
+ const serverCommand = resolveRigServerCommand(context.projectRoot);
618
+ const commandParts = [serverCommand.command, ...serverCommand.commandArgs, "start"];
303
619
  if (hostResult.value) {
304
620
  commandParts.push("--host", hostResult.value);
305
621
  }
@@ -316,13 +632,14 @@ async function executeServer(context, args, options) {
316
632
  return { ok: true, group: "server", command };
317
633
  }
318
634
  case "events":
319
- throw new CliError2("server events is not supported by the CLI wrapper. Use `rig inspector stream` for bounded event streaming, or start the server with `rig server start`.", 2);
635
+ throw new CliError("server events is not supported by the CLI wrapper. Use `rig inspector stream` for bounded event streaming, or start the server with `rig server start`.", 2);
320
636
  case "notify-test": {
321
637
  let pending = rest;
322
638
  const eventResult = takeOption(pending, "--event");
323
639
  pending = eventResult.rest;
324
- requireNoExtraArgs(pending, "bun run rig server notify-test [--event <type>]");
325
- const commandParts = ["rig-server", "notify-test"];
640
+ requireNoExtraArgs(pending, "rig server notify-test [--event <type>]");
641
+ const serverCommand = resolveRigServerCommand(context.projectRoot);
642
+ const commandParts = [serverCommand.command, ...serverCommand.commandArgs, "notify-test"];
326
643
  if (eventResult.value) {
327
644
  commandParts.push("--event", eventResult.value);
328
645
  }
@@ -349,9 +666,9 @@ async function executeServer(context, args, options) {
349
666
  pending = dirtyBaselineResult.rest;
350
667
  const prResult = takeOption(pending, "--pr");
351
668
  pending = prResult.rest;
352
- requireNoExtraArgs(pending, "bun run rig --run-id <run-id> server task-run [--task <id>] [--title <text>] [--runtime-adapter claude-code|codex|pi] [--model <model>] [--runtime-mode <mode>] [--interaction-mode <mode>] [--initial-prompt <text>] [--dirty-baseline head|dirty-snapshot] [--pr auto|ask|off]");
669
+ requireNoExtraArgs(pending, "rig --run-id <run-id> server task-run [--task <id>] [--title <text>] [--runtime-adapter claude-code|codex|pi] [--model <model>] [--runtime-mode <mode>] [--interaction-mode <mode>] [--initial-prompt <text>] [--dirty-baseline head|dirty-snapshot] [--pr auto|ask|off]");
353
670
  if (!taskResult.value && !initialPromptResult.value && !titleResult.value) {
354
- throw new CliError2("server task-run requires either --task <id> or --initial-prompt/--title for an ad hoc run.", 2);
671
+ throw new CliError("server task-run requires either --task <id> or --initial-prompt/--title for an ad hoc run.", 2);
355
672
  }
356
673
  const input = {
357
674
  runId: context.runId,
@@ -378,7 +695,7 @@ async function executeServer(context, args, options) {
378
695
  };
379
696
  }
380
697
  default:
381
- throw new CliError2(`Unknown server command: ${command}`);
698
+ throw new CliError(`Unknown server command: ${command}`, 1, { hint: "Run `rig server --help` \u2014 primary commands are status|list|add|use|start." });
382
699
  }
383
700
  }
384
701
  export {