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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/build-rig-binaries.d.ts +2 -0
  3. package/dist/bin/rig.d.ts +2 -0
  4. package/dist/bin/rig.js +11052 -6668
  5. package/dist/src/app/board.d.ts +23 -0
  6. package/dist/src/app/board.js +1786 -0
  7. package/dist/src/app/drone-ui.d.ts +37 -0
  8. package/dist/src/app/drone-ui.js +294 -0
  9. package/dist/src/app/theme.d.ts +47 -0
  10. package/dist/src/app/theme.js +150 -0
  11. package/dist/src/commands/_async-ui.d.ts +13 -0
  12. package/dist/src/commands/_async-ui.js +223 -0
  13. package/dist/src/commands/_authority-runs.d.ts +22 -0
  14. package/dist/src/commands/_authority-runs.js +2 -3
  15. package/dist/src/commands/_cli-format.d.ts +49 -0
  16. package/dist/src/commands/_cli-format.js +447 -0
  17. package/dist/src/commands/_connection-state.d.ts +44 -0
  18. package/dist/src/commands/_connection-state.js +30 -11
  19. package/dist/src/commands/_doctor-checks.d.ts +52 -0
  20. package/dist/src/commands/_doctor-checks.js +180 -44
  21. package/dist/src/commands/_help-catalog.d.ts +51 -0
  22. package/dist/src/commands/_help-catalog.js +442 -0
  23. package/dist/src/commands/_json-output.d.ts +11 -0
  24. package/dist/src/commands/_json-output.js +56 -0
  25. package/dist/src/commands/_operator-surface.d.ts +34 -0
  26. package/dist/src/commands/_operator-surface.js +220 -0
  27. package/dist/src/commands/_operator-view.d.ts +30 -0
  28. package/dist/src/commands/_operator-view.js +803 -73
  29. package/dist/src/commands/_parsers.d.ts +15 -0
  30. package/dist/src/commands/_parsers.js +18 -11
  31. package/dist/src/commands/_paths.d.ts +11 -0
  32. package/dist/src/commands/_pi-frontend.d.ts +27 -0
  33. package/dist/src/commands/_pi-frontend.js +742 -0
  34. package/dist/src/commands/_pi-install.d.ts +42 -0
  35. package/dist/src/commands/_pi-install.js +5 -4
  36. package/dist/src/commands/_policy.d.ts +8 -0
  37. package/dist/src/commands/_policy.js +12 -5
  38. package/dist/src/commands/_preflight.d.ts +22 -0
  39. package/dist/src/commands/_preflight.js +190 -128
  40. package/dist/src/commands/_probes.d.ts +1 -0
  41. package/dist/src/commands/_run-driver-helpers.d.ts +99 -0
  42. package/dist/src/commands/_run-driver-helpers.js +75 -22
  43. package/dist/src/commands/_run-replay.d.ts +24 -0
  44. package/dist/src/commands/_run-replay.js +142 -0
  45. package/dist/src/commands/_server-client.d.ts +186 -0
  46. package/dist/src/commands/_server-client.js +360 -61
  47. package/dist/src/commands/_snapshot-upload.d.ts +39 -0
  48. package/dist/src/commands/_snapshot-upload.js +163 -39
  49. package/dist/src/commands/_spinner.d.ts +25 -0
  50. package/dist/src/commands/_spinner.js +65 -0
  51. package/dist/src/commands/_task-picker.d.ts +9 -0
  52. package/dist/src/commands/_task-picker.js +172 -19
  53. package/dist/src/commands/agent.d.ts +3 -0
  54. package/dist/src/commands/agent.js +39 -20
  55. package/dist/src/commands/browser.d.ts +65 -0
  56. package/dist/src/commands/browser.js +334 -51
  57. package/dist/src/commands/connect.d.ts +7 -0
  58. package/dist/src/commands/connect.js +272 -33
  59. package/dist/src/commands/dist.d.ts +28 -0
  60. package/dist/src/commands/dist.js +19 -12
  61. package/dist/src/commands/doctor.d.ts +3 -0
  62. package/dist/src/commands/doctor.js +378 -45
  63. package/dist/src/commands/github.d.ts +3 -0
  64. package/dist/src/commands/github.js +375 -53
  65. package/dist/src/commands/inbox.d.ts +30 -0
  66. package/dist/src/commands/inbox.js +760 -70
  67. package/dist/src/commands/init.d.ts +74 -0
  68. package/dist/src/commands/init.js +963 -119
  69. package/dist/src/commands/inspect.d.ts +3 -0
  70. package/dist/src/commands/inspect.js +588 -32
  71. package/dist/src/commands/inspector.d.ts +3 -0
  72. package/dist/src/commands/inspector.js +20 -13
  73. package/dist/src/commands/pi.d.ts +3 -0
  74. package/dist/src/commands/pi.js +177 -0
  75. package/dist/src/commands/plugin.d.ts +16 -0
  76. package/dist/src/commands/plugin.js +95 -27
  77. package/dist/src/commands/profile-and-review.d.ts +4 -0
  78. package/dist/src/commands/profile-and-review.js +26 -19
  79. package/dist/src/commands/queue.d.ts +3 -0
  80. package/dist/src/commands/queue.js +32 -12
  81. package/dist/src/commands/remote.d.ts +3 -0
  82. package/dist/src/commands/remote.js +43 -36
  83. package/dist/src/commands/repo-git-harness.d.ts +5 -0
  84. package/dist/src/commands/repo-git-harness.js +22 -15
  85. package/dist/src/commands/run.d.ts +3 -0
  86. package/dist/src/commands/run.js +1379 -159
  87. package/dist/src/commands/server.d.ts +7 -0
  88. package/dist/src/commands/server.js +502 -57
  89. package/dist/src/commands/setup.d.ts +16 -0
  90. package/dist/src/commands/setup.js +390 -63
  91. package/dist/src/commands/stats.d.ts +12 -0
  92. package/dist/src/commands/stats.js +1051 -0
  93. package/dist/src/commands/task-report-bug.d.ts +19 -0
  94. package/dist/src/commands/task-report-bug.js +260 -62
  95. package/dist/src/commands/task-run-driver.d.ts +132 -0
  96. package/dist/src/commands/task-run-driver.js +886 -130
  97. package/dist/src/commands/task.d.ts +14 -0
  98. package/dist/src/commands/task.js +1644 -314
  99. package/dist/src/commands/test.d.ts +3 -0
  100. package/dist/src/commands/test.js +15 -8
  101. package/dist/src/commands/workspace.d.ts +3 -0
  102. package/dist/src/commands/workspace.js +18 -11
  103. package/dist/src/commands.d.ts +29 -0
  104. package/dist/src/commands.js +10967 -6637
  105. package/dist/src/index.d.ts +4 -0
  106. package/dist/src/index.js +10168 -5787
  107. package/dist/src/launcher.d.ts +61 -0
  108. package/dist/src/launcher.js +77 -13
  109. package/dist/src/report-bug.d.ts +44 -0
  110. package/dist/src/report-bug.js +3 -3
  111. package/dist/src/runner.d.ts +47 -0
  112. package/dist/src/runner.js +16 -22
  113. package/dist/src/withMutedConsole.d.ts +2 -0
  114. package/package.json +13 -6
@@ -1,12 +1,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,133 @@ 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/app/drone-ui.ts
73
+ import { ProcessTerminal, TUI, Text, Input, SelectList, matchesKey } from "@earendil-works/pi-tui";
74
+
75
+ // packages/cli/src/app/theme.ts
76
+ var RIG_PALETTE = {
77
+ ink: "#f2f3f6",
78
+ ink2: "#aeb0ba",
79
+ ink3: "#6c6e79",
80
+ ink4: "#44464f",
81
+ accent: "#ccff4d",
82
+ accentDim: "#a9d63f",
83
+ cyan: "#56d8ff",
84
+ red: "#ff5d5d",
85
+ yellow: "#ffd24d"
86
+ };
87
+ function hexToRgb(hex) {
88
+ const value = hex.replace("#", "");
89
+ return [
90
+ Number.parseInt(value.slice(0, 2), 16),
91
+ Number.parseInt(value.slice(2, 4), 16),
92
+ Number.parseInt(value.slice(4, 6), 16)
93
+ ];
94
+ }
95
+ function fg(hex) {
96
+ const [r, g, b] = hexToRgb(hex);
97
+ return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
98
+ }
99
+ var ink = fg(RIG_PALETTE.ink);
100
+ var ink2 = fg(RIG_PALETTE.ink2);
101
+ var ink3 = fg(RIG_PALETTE.ink3);
102
+ var ink4 = fg(RIG_PALETTE.ink4);
103
+ var accent = fg(RIG_PALETTE.accent);
104
+ var accentDim = fg(RIG_PALETTE.accentDim);
105
+ var cyan = fg(RIG_PALETTE.cyan);
106
+ var red = fg(RIG_PALETTE.red);
107
+ var yellow = fg(RIG_PALETTE.yellow);
108
+ function bold(text) {
109
+ return `\x1B[1m${text}\x1B[22m`;
110
+ }
111
+ var DRONE_ART = [
112
+ " .-=-. .-=-. ",
113
+ " ( !!! ) ( !!! ) ",
114
+ " '-=-'._ _.'-=-' ",
115
+ " '._ _.' ",
116
+ " '=$$$$$$$=.' ",
117
+ " =$$$$$$$$$$$= ",
118
+ " $$$@@@@@@@@@@$$$ ",
119
+ " $$$@@ @@$$$ ",
120
+ " $$@ ? @$$$ ",
121
+ " $$$@ '-' @$$$ ",
122
+ " $$$@@ @@$$$ ",
123
+ " $$$@@@@@@@@@@$$$ ",
124
+ " =$$$$$$$$$$$= ",
125
+ " '=$$$$$$$=.' ",
126
+ " _.' '._ ",
127
+ " .-=-.' '.-=-. ",
128
+ " ( !!! ) ( !!! ) ",
129
+ " '-=-' '-=-' "
130
+ ];
131
+ var EYE_FRAMES = ["@", "o", "."];
132
+ var DRONE_WIDTH = DRONE_ART[0].length;
133
+ var DRONE_HEIGHT = DRONE_ART.length;
134
+ var MICRO_BLADES = ["---", "\\\\\\", "|||", "///"];
135
+ function microDroneFrame(tick) {
136
+ const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
137
+ const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
138
+ return `(${blade})${eye}(${blade})`;
139
+ }
140
+ var MICRO_DRONE_FRAMES = Array.from({ length: 12 }, (_, index) => microDroneFrame(index));
141
+
142
+ // packages/cli/src/app/drone-ui.ts
143
+ var isTty = () => Boolean(process.stdout.isTTY);
144
+ function droneCancel(text) {
145
+ console.log(` ${red("\u2716")} ${ink3(text)}`);
146
+ }
147
+ var SELECT_THEME = {
148
+ selectedPrefix: (text) => accent(text),
149
+ selectedText: (text) => bold(ink(text)),
150
+ description: (text) => ink3(text),
151
+ scrollInfo: (text) => ink4(text),
152
+ noMatch: (text) => ink3(text)
153
+ };
154
+ async function runMiniTui(build) {
155
+ const terminal = new ProcessTerminal;
156
+ const tui = new TUI(terminal);
157
+ let settled = false;
158
+ return await new Promise((resolve) => {
159
+ const finish = (result) => {
160
+ if (settled)
161
+ return;
162
+ settled = true;
163
+ tui.stop();
164
+ resolve(result);
165
+ };
166
+ build(tui, finish);
167
+ tui.start();
168
+ });
169
+ }
170
+ async function droneSelect(input) {
171
+ if (!isTty() || input.options.length === 0) {
172
+ return input.initialValue ?? input.options[0]?.value ?? null;
173
+ }
174
+ return runMiniTui((tui, finish) => {
175
+ tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}`));
176
+ const items = input.options.map((option) => ({
177
+ value: option.value,
178
+ label: option.label,
179
+ ...option.hint ? { description: option.hint } : {}
180
+ }));
181
+ const list = new SelectList(items, Math.min(items.length, 12), SELECT_THEME);
182
+ const initialIndex = input.initialValue ? items.findIndex((item) => item.value === input.initialValue) : -1;
183
+ if (initialIndex > 0)
184
+ list.setSelectedIndex(initialIndex);
185
+ list.onSelect = (item) => finish(item.value);
186
+ list.onCancel = () => finish(null);
187
+ tui.addChild(list);
188
+ tui.addChild(new Text(ink4(" \u2191\u2193 navigate \xB7 enter select \xB7 esc cancel")));
189
+ tui.setFocus(list);
190
+ tui.addInputListener((data) => {
191
+ if (matchesKey(data, "ctrl+c") || matchesKey(data, "escape")) {
192
+ finish(null);
193
+ return { consume: true };
194
+ }
195
+ return;
196
+ });
197
+ });
198
+ }
68
199
 
69
200
  // packages/cli/src/commands/_connection-state.ts
70
201
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -88,9 +219,14 @@ function readJsonFile(path) {
88
219
  try {
89
220
  return JSON.parse(readFileSync(path, "utf8"));
90
221
  } catch (error) {
91
- throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
222
+ 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
223
  }
93
224
  }
225
+ function writeJsonFile(path, value) {
226
+ mkdirSync(dirname(path), { recursive: true });
227
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
228
+ `, "utf8");
229
+ }
94
230
  function normalizeConnection(value) {
95
231
  if (!value || typeof value !== "object" || Array.isArray(value))
96
232
  return null;
@@ -120,6 +256,18 @@ function readGlobalConnections(options = {}) {
120
256
  }
121
257
  return { connections };
122
258
  }
259
+ function writeGlobalConnections(state, options = {}) {
260
+ writeJsonFile(resolveGlobalConnectionsPath(options.env ?? process.env), state);
261
+ }
262
+ function upsertGlobalConnection(alias, connection, options = {}) {
263
+ const cleanAlias = alias.trim();
264
+ if (!cleanAlias)
265
+ throw new CliError("Connection alias is required.", 1, { hint: "Save a server with `rig server add <alias> <url>`." });
266
+ const state = readGlobalConnections(options);
267
+ state.connections[cleanAlias] = connection;
268
+ writeGlobalConnections(state, options);
269
+ return state;
270
+ }
123
271
  function readRepoConnection(projectRoot) {
124
272
  const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
125
273
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
@@ -131,25 +279,218 @@ function readRepoConnection(projectRoot) {
131
279
  return {
132
280
  selected,
133
281
  project: typeof record.project === "string" ? record.project : undefined,
134
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
282
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
283
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
135
284
  };
136
285
  }
286
+ function writeRepoConnection(projectRoot, state) {
287
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
288
+ }
137
289
  function resolveSelectedConnection(projectRoot, options = {}) {
138
290
  const repo = readRepoConnection(projectRoot);
139
291
  if (!repo)
140
292
  return null;
141
293
  if (repo.selected === "local")
142
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
294
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
143
295
  const global = readGlobalConnections(options);
144
296
  const connection = global.connections[repo.selected];
145
297
  if (!connection) {
146
- throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
298
+ throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
299
+ }
300
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
301
+ }
302
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
303
+ const repo = readRepoConnection(projectRoot);
304
+ if (!repo)
305
+ return;
306
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
307
+ }
308
+
309
+ // packages/cli/src/commands/_cli-format.ts
310
+ import pc from "picocolors";
311
+ var themeDim = (value) => ink3(value);
312
+ var themeFaint = (value) => ink4(value);
313
+ function truncate(value, width) {
314
+ if (value.length <= width)
315
+ return value;
316
+ if (width <= 1)
317
+ return "\u2026";
318
+ return `${value.slice(0, width - 1)}\u2026`;
319
+ }
320
+ function pad(value, width) {
321
+ return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
322
+ }
323
+ function statusColor(status) {
324
+ const normalized = status.toLowerCase();
325
+ if (["completed", "merged", "closed", "done", "accepted", "pass", "selected", "approved", "running"].includes(normalized))
326
+ return accent;
327
+ if (["failed", "needs_attention", "needs-attention", "blocked", "error", "rejected"].includes(normalized))
328
+ return red;
329
+ if (["reviewing", "validating", "in_progress", "in-progress", "remote", "preparing", "closing-out"].includes(normalized))
330
+ return cyan;
331
+ if (["ready", "open", "queued", "created", "local", "pending"].includes(normalized))
332
+ return yellow;
333
+ return themeDim;
334
+ }
335
+ function formatStatusPill(status) {
336
+ const label = status || "unknown";
337
+ return statusColor(label)(`\u25CF ${label}`);
338
+ }
339
+ function formatSection(title, subtitle) {
340
+ return `${pc.bold(accent("\u25C6"))} ${pc.bold(title)}${subtitle ? themeDim(` \u2014 ${subtitle}`) : ""}`;
341
+ }
342
+ function formatSuccessCard(title, rows = []) {
343
+ const body = rows.filter(([, value]) => value !== undefined && value !== null && String(value).length > 0).map(([key, value]) => `${themeFaint("\u2502")} ${themeDim(key.padEnd(12))} ${value}`);
344
+ return [formatSection(title), ...body].join(`
345
+ `);
346
+ }
347
+ function formatNextSteps(steps) {
348
+ if (steps.length === 0)
349
+ return [];
350
+ return [pc.bold("Next"), ...steps.map((step) => `${accent("\u203A")} ${step}`)];
351
+ }
352
+ function formatConnectionList(connections) {
353
+ const rows = [["local", { kind: "local", mode: "auto" }], ...Object.entries(connections)];
354
+ const aliasWidth = Math.min(24, Math.max(5, ...rows.map(([alias]) => alias.length)));
355
+ const lines = rows.map(([alias, connection]) => [
356
+ pc.bold(pad(truncate(alias, aliasWidth), aliasWidth)),
357
+ formatStatusPill(connection.kind),
358
+ connection.kind === "remote" ? connection.baseUrl ?? "" : connection.mode ?? "local"
359
+ ].join(" "));
360
+ 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(`
361
+ `);
362
+ }
363
+ function formatConnectionStatus(selected, connections) {
364
+ const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
365
+ const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
366
+ return [
367
+ formatSection("Rig server", "selected for this repo"),
368
+ `${themeFaint("\u2502")} ${themeDim("selected ")} ${pc.bold(selected)}`,
369
+ `${themeFaint("\u2502")} ${themeDim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
370
+ `${themeFaint("\u2502")} ${themeDim("target ")} ${target ?? "not configured"}`,
371
+ "",
372
+ ...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
373
+ ].join(`
374
+ `);
375
+ }
376
+
377
+ // packages/cli/src/commands/connect.ts
378
+ function usageName(options) {
379
+ return `rig ${options.group}`;
380
+ }
381
+ function parseConnection(alias, value, options) {
382
+ if (alias === "local" && !value)
383
+ return { kind: "local", mode: "auto" };
384
+ if (!value)
385
+ throw new CliError(`Missing remote server URL. Usage: ${usageName(options)} add <alias> <url>`, 1);
386
+ let parsed;
387
+ try {
388
+ parsed = new URL(value);
389
+ } catch {
390
+ 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`." });
391
+ }
392
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
393
+ 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`." });
394
+ }
395
+ return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
396
+ }
397
+ function printJsonOrText(context, payload, text) {
398
+ if (context.outputMode === "json") {
399
+ console.log(JSON.stringify(payload, null, 2));
400
+ } else {
401
+ console.log(text);
402
+ }
403
+ }
404
+ async function promptForConnectionAlias(context) {
405
+ const state = readGlobalConnections();
406
+ const repo = readRepoConnection(context.projectRoot);
407
+ const options = [
408
+ { value: "local", label: "local", hint: "Use/start a local Rig server" },
409
+ ...Object.entries(state.connections).map(([alias, connection]) => ({
410
+ value: alias,
411
+ label: alias,
412
+ hint: connection.kind === "remote" ? connection.baseUrl : "local"
413
+ }))
414
+ ].filter((option, index, all) => all.findIndex((candidate) => candidate.value === option.value) === index);
415
+ const answer = await droneSelect({
416
+ message: "Select Rig server for this repo",
417
+ initialValue: repo?.selected ?? "local",
418
+ options
419
+ });
420
+ if (answer === null) {
421
+ droneCancel("No server selected.");
422
+ throw new CliError("No server selected.", 3);
423
+ }
424
+ return String(answer);
425
+ }
426
+ async function executeConnectionCommand(context, args, options) {
427
+ const [command, ...rest] = args;
428
+ switch (command ?? "status") {
429
+ case "list": {
430
+ requireNoExtraArgs(rest, `${usageName(options)} list`);
431
+ const state = readGlobalConnections();
432
+ printJsonOrText(context, state, formatConnectionList(state.connections));
433
+ return { ok: true, group: options.group, command: "list", details: state };
434
+ }
435
+ case "add": {
436
+ const [alias, url, ...extra] = rest;
437
+ if (!alias)
438
+ throw new CliError(`Missing alias. Usage: ${usageName(options)} add <alias> <url>`, 1);
439
+ requireNoExtraArgs(extra, `${usageName(options)} add <alias> <url>`);
440
+ const connection = parseConnection(alias, url, options);
441
+ const state = upsertGlobalConnection(alias, connection);
442
+ printJsonOrText(context, { alias, connection }, formatSuccessCard("Rig server saved", [
443
+ ["alias", alias],
444
+ ["target", connection.kind === "remote" ? connection.baseUrl : "local"],
445
+ ["next", `${usageName(options)} use ${alias}`]
446
+ ]));
447
+ return { ok: true, group: options.group, command: "add", details: { alias, connection, count: Object.keys(state.connections).length } };
448
+ }
449
+ case "use": {
450
+ let [alias, ...extra] = rest;
451
+ requireNoExtraArgs(extra, `${usageName(options)} use <alias|local>`);
452
+ if (!alias && options.interactiveUse && context.outputMode === "text" && process.stdin.isTTY) {
453
+ alias = await promptForConnectionAlias(context);
454
+ }
455
+ if (!alias)
456
+ throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
457
+ if (alias !== "local") {
458
+ const state = readGlobalConnections();
459
+ if (!state.connections[alias])
460
+ 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>`." });
461
+ }
462
+ const repoState = { selected: alias, linkedAt: new Date().toISOString() };
463
+ writeRepoConnection(context.projectRoot, repoState);
464
+ printJsonOrText(context, repoState, formatSuccessCard("Rig server selected", [
465
+ ["selected", alias],
466
+ ["scope", "this repo"],
467
+ ["next", "rig task list"]
468
+ ]));
469
+ return { ok: true, group: options.group, command: "use", details: repoState };
470
+ }
471
+ case "status": {
472
+ requireNoExtraArgs(rest, `${usageName(options)} status`);
473
+ const repo = readRepoConnection(context.projectRoot);
474
+ const global = readGlobalConnections();
475
+ const details = { selected: repo?.selected ?? "local", repo, connections: global.connections };
476
+ printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections));
477
+ return { ok: true, group: options.group, command: "status", details };
478
+ }
479
+ default:
480
+ throw new CliError(`Unknown ${options.group} command: ${String(command)}
481
+ Usage: ${usageName(options)} <list|add|use|status>`, 1);
147
482
  }
148
- return { alias: repo.selected, connection };
149
483
  }
150
484
 
151
485
  // packages/cli/src/commands/_server-client.ts
152
- var cachedGitHubBearerToken;
486
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
487
+ import { resolve as resolve2 } from "path";
488
+ import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
489
+ var scopedGitHubBearerTokens = new Map;
490
+ var serverPhaseListener = null;
491
+ function reportServerPhase(label) {
492
+ serverPhaseListener?.(label);
493
+ }
153
494
  function cleanToken(value) {
154
495
  const trimmed = value?.trim();
155
496
  return trimmed ? trimmed : null;
@@ -166,49 +507,80 @@ function readPrivateRemoteSessionToken(projectRoot) {
166
507
  }
167
508
  }
168
509
  function readGitHubBearerTokenForRemote(projectRoot) {
169
- if (cachedGitHubBearerToken !== undefined)
170
- return cachedGitHubBearerToken;
510
+ const scopedKey = resolve2(projectRoot);
511
+ if (scopedGitHubBearerTokens.has(scopedKey))
512
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
171
513
  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;
514
+ if (privateSession)
515
+ return privateSession;
516
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
517
+ }
518
+ function readStoredGitHubAuthToken(projectRoot) {
519
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
520
+ if (!existsSync2(path))
521
+ return null;
522
+ try {
523
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
524
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
525
+ } catch {
526
+ return null;
180
527
  }
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;
528
+ }
529
+ function readLocalConnectionFallbackToken(projectRoot) {
530
+ return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
188
531
  }
189
532
  async function ensureServerForCli(projectRoot) {
190
533
  try {
191
534
  const selected = resolveSelectedConnection(projectRoot);
192
535
  if (selected?.connection.kind === "remote") {
536
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
537
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
538
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
193
539
  return {
194
540
  baseUrl: selected.connection.baseUrl,
195
- authToken: readGitHubBearerTokenForRemote(projectRoot),
196
- connectionKind: "remote"
541
+ authToken,
542
+ connectionKind: "remote",
543
+ serverProjectRoot
197
544
  };
198
545
  }
546
+ reportServerPhase("Starting local Rig server\u2026");
199
547
  const connection = await ensureLocalRigServerConnection(projectRoot);
200
548
  return {
201
549
  baseUrl: connection.baseUrl,
202
- authToken: connection.authToken,
203
- connectionKind: "local"
550
+ authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
551
+ connectionKind: "local",
552
+ serverProjectRoot: resolve2(projectRoot)
204
553
  };
205
554
  } catch (error) {
206
555
  if (error instanceof Error) {
207
- throw new CliError2(error.message, 1);
556
+ throw new CliError(error.message, 1);
208
557
  }
209
558
  throw error;
210
559
  }
211
560
  }
561
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
562
+ const repo = readRepoConnection(projectRoot);
563
+ const slug = repo?.project?.trim();
564
+ if (!slug)
565
+ return null;
566
+ try {
567
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
568
+ headers: mergeHeaders(undefined, authToken)
569
+ });
570
+ if (!response.ok)
571
+ return null;
572
+ const payload = await response.json();
573
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
574
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
575
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
576
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
577
+ if (path)
578
+ writeRepoServerProjectRoot(projectRoot, path);
579
+ return path;
580
+ } catch {
581
+ return null;
582
+ }
583
+ }
212
584
  function mergeHeaders(headers, authToken) {
213
585
  const merged = new Headers(headers);
214
586
  if (authToken) {
@@ -231,12 +603,65 @@ function diagnosticMessage(payload) {
231
603
  });
232
604
  return messages.length > 0 ? messages.join("; ") : null;
233
605
  }
606
+ var serverReachabilityCache = new Map;
607
+ async function probeServerReachability(baseUrl, authToken) {
608
+ try {
609
+ const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
610
+ headers: mergeHeaders(undefined, authToken),
611
+ signal: AbortSignal.timeout(1500)
612
+ });
613
+ return response.ok;
614
+ } catch {
615
+ return false;
616
+ }
617
+ }
618
+ function cachedServerReachability(projectRoot, baseUrl, authToken) {
619
+ const key = resolve2(projectRoot);
620
+ const cached = serverReachabilityCache.get(key);
621
+ if (cached)
622
+ return cached;
623
+ const probe = probeServerReachability(baseUrl, authToken);
624
+ serverReachabilityCache.set(key, probe);
625
+ return probe;
626
+ }
627
+ function describeSelectedServer(projectRoot, server) {
628
+ try {
629
+ const selected = resolveSelectedConnection(projectRoot);
630
+ if (selected) {
631
+ return {
632
+ alias: selected.alias,
633
+ target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
634
+ };
635
+ }
636
+ } catch {}
637
+ return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
638
+ }
639
+ async function buildServerFailureContext(projectRoot, server) {
640
+ const { alias, target } = describeSelectedServer(projectRoot, server);
641
+ const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
642
+ const reachability = reachable ? "server is reachable" : "server is unreachable";
643
+ return {
644
+ contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
645
+ hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
646
+ };
647
+ }
234
648
  async function requestServerJson(context, pathname, init = {}) {
235
649
  const server = await ensureServerForCli(context.projectRoot);
236
- const response = await fetch(`${server.baseUrl}${pathname}`, {
237
- ...init,
238
- headers: mergeHeaders(init.headers, server.authToken)
239
- });
650
+ const headers = mergeHeaders(init.headers, server.authToken);
651
+ if (server.serverProjectRoot)
652
+ headers.set("x-rig-project-root", server.serverProjectRoot);
653
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
654
+ let response;
655
+ try {
656
+ response = await fetch(`${server.baseUrl}${pathname}`, {
657
+ ...init,
658
+ headers
659
+ });
660
+ } catch (error) {
661
+ const failure = await buildServerFailureContext(context.projectRoot, server);
662
+ throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
663
+ ${failure.contextLine}`, 1, { hint: failure.hint });
664
+ }
240
665
  const text = await response.text();
241
666
  const payload = text.trim().length > 0 ? (() => {
242
667
  try {
@@ -247,11 +672,26 @@ async function requestServerJson(context, pathname, init = {}) {
247
672
  })() : null;
248
673
  if (!response.ok) {
249
674
  const diagnostics = diagnosticMessage(payload);
250
- const detail = diagnostics ?? (text || response.statusText);
251
- throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
675
+ const rawDetail = diagnostics ?? (text || response.statusText);
676
+ const detail = diagnostics ? rawDetail : rawDetail.split(`
677
+ `).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 200) ?? response.statusText;
678
+ const failure = await buildServerFailureContext(context.projectRoot, server);
679
+ throw new CliError(`Rig server request failed (${response.status}): ${detail}
680
+ ${failure.contextLine}`, 1, { hint: failure.hint });
252
681
  }
253
682
  return payload;
254
683
  }
684
+ var RESUMABLE_RUN_STATUSES = new Set([
685
+ "created",
686
+ "preparing",
687
+ "running",
688
+ "validating",
689
+ "reviewing",
690
+ "stopped",
691
+ "failed",
692
+ "needs-attention",
693
+ "needs_attention"
694
+ ]);
255
695
  async function submitTaskRunViaServer(context, input) {
256
696
  const isTaskRun = Boolean(input.taskId);
257
697
  const endpoint = isTaskRun ? "/api/runs/task" : "/api/runs/adhoc";
@@ -275,18 +715,21 @@ async function submitTaskRunViaServer(context, input) {
275
715
  })
276
716
  });
277
717
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
278
- throw new CliError2("Rig server returned an invalid run submission payload.", 1);
718
+ 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
719
  }
280
720
  const runId = payload.runId;
281
721
  if (typeof runId !== "string" || runId.trim().length === 0) {
282
- throw new CliError2("Rig server returned no runId for the submitted run.", 1);
722
+ 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
723
  }
284
724
  return { runId };
285
725
  }
286
726
 
287
727
  // packages/cli/src/commands/server.ts
288
728
  async function executeServer(context, args, options) {
289
- const [command = "start", ...rest] = args;
729
+ const [command = "status", ...rest] = args;
730
+ if (["status", "list", "add", "use"].includes(command)) {
731
+ return executeConnectionCommand(context, [command, ...rest], { group: "server", interactiveUse: true });
732
+ }
290
733
  switch (command) {
291
734
  case "start": {
292
735
  let pending = rest;
@@ -298,8 +741,9 @@ async function executeServer(context, args, options) {
298
741
  pending = pollResult.rest;
299
742
  const authTokenResult = takeOption(pending, "--auth-token");
300
743
  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"];
744
+ requireNoExtraArgs(pending, "rig server start [--host <host>] [--port <n>] [--poll-ms <n>] [--auth-token <token>]");
745
+ const serverCommand = resolveRigServerCommand(context.projectRoot);
746
+ const commandParts = [serverCommand.command, ...serverCommand.commandArgs, "start"];
303
747
  if (hostResult.value) {
304
748
  commandParts.push("--host", hostResult.value);
305
749
  }
@@ -316,13 +760,14 @@ async function executeServer(context, args, options) {
316
760
  return { ok: true, group: "server", command };
317
761
  }
318
762
  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);
763
+ 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
764
  case "notify-test": {
321
765
  let pending = rest;
322
766
  const eventResult = takeOption(pending, "--event");
323
767
  pending = eventResult.rest;
324
- requireNoExtraArgs(pending, "bun run rig server notify-test [--event <type>]");
325
- const commandParts = ["rig-server", "notify-test"];
768
+ requireNoExtraArgs(pending, "rig server notify-test [--event <type>]");
769
+ const serverCommand = resolveRigServerCommand(context.projectRoot);
770
+ const commandParts = [serverCommand.command, ...serverCommand.commandArgs, "notify-test"];
326
771
  if (eventResult.value) {
327
772
  commandParts.push("--event", eventResult.value);
328
773
  }
@@ -349,9 +794,9 @@ async function executeServer(context, args, options) {
349
794
  pending = dirtyBaselineResult.rest;
350
795
  const prResult = takeOption(pending, "--pr");
351
796
  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]");
797
+ 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
798
  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);
799
+ throw new CliError("server task-run requires either --task <id> or --initial-prompt/--title for an ad hoc run.", 2);
355
800
  }
356
801
  const input = {
357
802
  runId: context.runId,
@@ -378,7 +823,7 @@ async function executeServer(context, args, options) {
378
823
  };
379
824
  }
380
825
  default:
381
- throw new CliError2(`Unknown server command: ${command}`);
826
+ throw new CliError(`Unknown server command: ${command}`, 1, { hint: "Run `rig server --help` \u2014 primary commands are status|list|add|use|start." });
382
827
  }
383
828
  }
384
829
  export {