@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,19 +1,344 @@
1
1
  // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
2
17
  var __require = import.meta.require;
3
18
 
19
+ // packages/cli/src/app/theme.ts
20
+ function hexToRgb(hex) {
21
+ const value = hex.replace("#", "");
22
+ return [
23
+ Number.parseInt(value.slice(0, 2), 16),
24
+ Number.parseInt(value.slice(2, 4), 16),
25
+ Number.parseInt(value.slice(4, 6), 16)
26
+ ];
27
+ }
28
+ function fg(hex) {
29
+ const [r, g, b] = hexToRgb(hex);
30
+ return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
31
+ }
32
+ function bold(text) {
33
+ return `\x1B[1m${text}\x1B[22m`;
34
+ }
35
+ function microDroneFrame(tick) {
36
+ const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
37
+ const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
38
+ return `(${blade})${eye}(${blade})`;
39
+ }
40
+ function renderMicroDroneFrame(tick) {
41
+ const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
42
+ const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
43
+ return `${ink4("(")}${cyan(blade)}${ink4(")")}${bold(accent(eye))}${ink4("(")}${cyan(blade)}${ink4(")")}`;
44
+ }
45
+ var RIG_PALETTE, ink, ink2, ink3, ink4, accent, accentDim, cyan, red, yellow, DRONE_ART, EYE_FRAMES, DRONE_WIDTH, DRONE_HEIGHT, MICRO_BLADES, MICRO_DRONE_FRAMES;
46
+ var init_theme = __esm(() => {
47
+ RIG_PALETTE = {
48
+ ink: "#f2f3f6",
49
+ ink2: "#aeb0ba",
50
+ ink3: "#6c6e79",
51
+ ink4: "#44464f",
52
+ accent: "#ccff4d",
53
+ accentDim: "#a9d63f",
54
+ cyan: "#56d8ff",
55
+ red: "#ff5d5d",
56
+ yellow: "#ffd24d"
57
+ };
58
+ ink = fg(RIG_PALETTE.ink);
59
+ ink2 = fg(RIG_PALETTE.ink2);
60
+ ink3 = fg(RIG_PALETTE.ink3);
61
+ ink4 = fg(RIG_PALETTE.ink4);
62
+ accent = fg(RIG_PALETTE.accent);
63
+ accentDim = fg(RIG_PALETTE.accentDim);
64
+ cyan = fg(RIG_PALETTE.cyan);
65
+ red = fg(RIG_PALETTE.red);
66
+ yellow = fg(RIG_PALETTE.yellow);
67
+ DRONE_ART = [
68
+ " .-=-. .-=-. ",
69
+ " ( !!! ) ( !!! ) ",
70
+ " '-=-'._ _.'-=-' ",
71
+ " '._ _.' ",
72
+ " '=$$$$$$$=.' ",
73
+ " =$$$$$$$$$$$= ",
74
+ " $$$@@@@@@@@@@$$$ ",
75
+ " $$$@@ @@$$$ ",
76
+ " $$@ ? @$$$ ",
77
+ " $$$@ '-' @$$$ ",
78
+ " $$$@@ @@$$$ ",
79
+ " $$$@@@@@@@@@@$$$ ",
80
+ " =$$$$$$$$$$$= ",
81
+ " '=$$$$$$$=.' ",
82
+ " _.' '._ ",
83
+ " .-=-.' '.-=-. ",
84
+ " ( !!! ) ( !!! ) ",
85
+ " '-=-' '-=-' "
86
+ ];
87
+ EYE_FRAMES = ["@", "o", "."];
88
+ DRONE_WIDTH = DRONE_ART[0].length;
89
+ DRONE_HEIGHT = DRONE_ART.length;
90
+ MICRO_BLADES = ["---", "\\\\\\", "|||", "///"];
91
+ MICRO_DRONE_FRAMES = Array.from({ length: 12 }, (_, index) => microDroneFrame(index));
92
+ });
93
+
94
+ // packages/cli/src/commands/_spinner.ts
95
+ function createTtySpinner(input) {
96
+ const output = input.output ?? process.stdout;
97
+ const isTty = output.isTTY === true;
98
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
99
+ let label = input.label;
100
+ let frame = 0;
101
+ let paused = false;
102
+ let stopped = false;
103
+ let lastPrintedLabel = "";
104
+ const render = () => {
105
+ if (stopped || paused)
106
+ return;
107
+ if (!isTty) {
108
+ if (label !== lastPrintedLabel) {
109
+ output.write(`${label}
110
+ `);
111
+ lastPrintedLabel = label;
112
+ }
113
+ return;
114
+ }
115
+ frame = (frame + 1) % frames.length;
116
+ const glyph = frames[frame] ?? frames[0] ?? "";
117
+ output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
118
+ };
119
+ const clearLine = () => {
120
+ if (isTty)
121
+ output.write("\r\x1B[2K");
122
+ };
123
+ render();
124
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
125
+ return {
126
+ setLabel(next) {
127
+ label = next;
128
+ render();
129
+ },
130
+ pause() {
131
+ paused = true;
132
+ clearLine();
133
+ },
134
+ resume() {
135
+ if (stopped)
136
+ return;
137
+ paused = false;
138
+ render();
139
+ },
140
+ stop(finalLine) {
141
+ if (stopped)
142
+ return;
143
+ stopped = true;
144
+ if (timer)
145
+ clearInterval(timer);
146
+ clearLine();
147
+ if (finalLine)
148
+ output.write(`${finalLine}
149
+ `);
150
+ }
151
+ };
152
+ }
153
+ var SPINNER_FRAMES;
154
+ var init__spinner = __esm(() => {
155
+ SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
156
+ });
157
+
158
+ // packages/cli/src/app/drone-ui.ts
159
+ var exports_drone_ui = {};
160
+ __export(exports_drone_ui, {
161
+ droneWarn: () => droneWarn,
162
+ droneText: () => droneText,
163
+ droneStep: () => droneStep,
164
+ droneSpinner: () => droneSpinner,
165
+ droneSelect: () => droneSelect,
166
+ droneOutro: () => droneOutro,
167
+ droneNote: () => droneNote,
168
+ droneIntro: () => droneIntro,
169
+ droneInfo: () => droneInfo,
170
+ droneError: () => droneError,
171
+ droneConfirm: () => droneConfirm,
172
+ droneCancel: () => droneCancel
173
+ });
174
+ import { ProcessTerminal, TUI, Text, Input, SelectList, matchesKey } from "@earendil-works/pi-tui";
175
+ function hairline(width = Math.min(process.stdout.columns ?? 80, 100)) {
176
+ return ink4("\u2500".repeat(Math.max(10, width)));
177
+ }
178
+ function droneIntro(title, subtitle) {
179
+ console.log("");
180
+ console.log(` ${accent("\u258D")}${bold(ink(title))}${subtitle ? ink3(` \u2014 ${subtitle}`) : ""}`);
181
+ console.log(hairline());
182
+ }
183
+ function droneOutro(text) {
184
+ console.log(hairline());
185
+ console.log(` ${accent("\u25C6")} ${ink2(text)}`);
186
+ console.log("");
187
+ }
188
+ function droneNote(message, title) {
189
+ if (title)
190
+ console.log(` ${accentDim("\u25C7")} ${bold(ink2(title))}`);
191
+ for (const line of message.split(`
192
+ `)) {
193
+ console.log(` ${ink4("\u2502")} ${line}`);
194
+ }
195
+ }
196
+ function droneStep(text) {
197
+ console.log(` ${accent("\u203A")} ${ink(text)}`);
198
+ }
199
+ function droneInfo(text) {
200
+ console.log(` ${cyan("\xB7")} ${ink2(text)}`);
201
+ }
202
+ function droneWarn(text) {
203
+ console.log(` ${yellow("\u25B2")} ${ink2(text)}`);
204
+ }
205
+ function droneError(text) {
206
+ console.log(` ${red("\u2716")} ${ink2(text)}`);
207
+ }
208
+ function droneCancel(text) {
209
+ console.log(` ${red("\u2716")} ${ink3(text)}`);
210
+ }
211
+ function droneSpinner() {
212
+ let active = null;
213
+ return {
214
+ start(message) {
215
+ active = createTtySpinner({
216
+ label: message,
217
+ frames: MICRO_DRONE_FRAMES,
218
+ styleFrame: (frame) => renderMicroDroneFrame(Math.max(0, MICRO_DRONE_FRAMES.indexOf(frame)))
219
+ });
220
+ },
221
+ stop(message) {
222
+ active?.stop(message ? ` ${accent("\u25C6")} ${ink2(message)}` : undefined);
223
+ active = null;
224
+ },
225
+ error(message) {
226
+ active?.stop(message ? ` ${red("\u2716")} ${ink2(message)}` : undefined);
227
+ active = null;
228
+ }
229
+ };
230
+ }
231
+ async function runMiniTui(build) {
232
+ const terminal = new ProcessTerminal;
233
+ const tui = new TUI(terminal);
234
+ let settled = false;
235
+ return await new Promise((resolve6) => {
236
+ const finish = (result) => {
237
+ if (settled)
238
+ return;
239
+ settled = true;
240
+ tui.stop();
241
+ resolve6(result);
242
+ };
243
+ build(tui, finish);
244
+ tui.start();
245
+ });
246
+ }
247
+ async function droneSelect(input) {
248
+ if (!isTty() || input.options.length === 0) {
249
+ return input.initialValue ?? input.options[0]?.value ?? null;
250
+ }
251
+ return runMiniTui((tui, finish) => {
252
+ tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}`));
253
+ const items = input.options.map((option) => ({
254
+ value: option.value,
255
+ label: option.label,
256
+ ...option.hint ? { description: option.hint } : {}
257
+ }));
258
+ const list = new SelectList(items, Math.min(items.length, 12), SELECT_THEME);
259
+ const initialIndex = input.initialValue ? items.findIndex((item) => item.value === input.initialValue) : -1;
260
+ if (initialIndex > 0)
261
+ list.setSelectedIndex(initialIndex);
262
+ list.onSelect = (item) => finish(item.value);
263
+ list.onCancel = () => finish(null);
264
+ tui.addChild(list);
265
+ tui.addChild(new Text(ink4(" \u2191\u2193 navigate \xB7 enter select \xB7 esc cancel")));
266
+ tui.setFocus(list);
267
+ tui.addInputListener((data) => {
268
+ if (matchesKey(data, "ctrl+c") || matchesKey(data, "escape")) {
269
+ finish(null);
270
+ return { consume: true };
271
+ }
272
+ return;
273
+ });
274
+ });
275
+ }
276
+ async function droneText(input) {
277
+ if (!isTty())
278
+ return input.initialValue ?? null;
279
+ return runMiniTui((tui, finish) => {
280
+ tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}${input.placeholder ? ink4(` (${input.placeholder})`) : ""}`));
281
+ const field = new Input;
282
+ if (input.initialValue)
283
+ field.setValue(input.initialValue);
284
+ field.onSubmit = (value) => finish(value);
285
+ field.onEscape = () => finish(null);
286
+ tui.addChild(field);
287
+ tui.addChild(new Text(ink4(" enter submit \xB7 esc cancel")));
288
+ tui.setFocus(field);
289
+ tui.addInputListener((data) => {
290
+ if (matchesKey(data, "ctrl+c")) {
291
+ finish(null);
292
+ return { consume: true };
293
+ }
294
+ return;
295
+ });
296
+ });
297
+ }
298
+ async function droneConfirm(input) {
299
+ const answer = await droneSelect({
300
+ message: input.message,
301
+ options: [
302
+ { value: "yes", label: "Yes" },
303
+ { value: "no", label: "No" }
304
+ ],
305
+ initialValue: input.initialValue === false ? "no" : "yes"
306
+ });
307
+ return answer === null ? null : answer === "yes";
308
+ }
309
+ var isTty = () => Boolean(process.stdout.isTTY), SELECT_THEME;
310
+ var init_drone_ui = __esm(() => {
311
+ init_theme();
312
+ init__spinner();
313
+ SELECT_THEME = {
314
+ selectedPrefix: (text) => accent(text),
315
+ selectedText: (text) => bold(ink(text)),
316
+ description: (text) => ink3(text),
317
+ scrollInfo: (text) => ink4(text),
318
+ noMatch: (text) => ink3(text)
319
+ };
320
+ });
321
+
4
322
  // packages/cli/src/commands/init.ts
5
323
  import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
6
- import { spawnSync as spawnSync2 } from "child_process";
7
- import { resolve as resolve6 } from "path";
324
+ import { spawnSync } from "child_process";
325
+ import { basename, resolve as resolve6 } from "path";
8
326
 
9
327
  // packages/cli/src/runner.ts
10
328
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
11
- import { CliError } from "@rig/runtime/control-plane/errors";
329
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
12
330
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
13
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
14
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
15
331
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
16
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
332
+
333
+ class CliError extends RuntimeCliError {
334
+ hint;
335
+ constructor(message, exitCode = 1, options = {}) {
336
+ super(message, exitCode);
337
+ if (options.hint?.trim()) {
338
+ this.hint = options.hint.trim();
339
+ }
340
+ }
341
+ }
17
342
  function takeFlag(args, flag) {
18
343
  const rest = [];
19
344
  let value = false;
@@ -34,7 +359,7 @@ function takeOption(args, option) {
34
359
  if (current === option) {
35
360
  const next = args[index + 1];
36
361
  if (!next || next.startsWith("-")) {
37
- throw new CliError(`Missing value for ${option}`);
362
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
38
363
  }
39
364
  value = next;
40
365
  index += 1;
@@ -49,6 +374,7 @@ function takeOption(args, option) {
49
374
 
50
375
  // packages/cli/src/commands/init.ts
51
376
  import { buildRigInitConfigSource } from "@rig/core";
377
+ import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
52
378
 
53
379
  // packages/cli/src/commands/_connection-state.ts
54
380
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -72,7 +398,7 @@ function readJsonFile(path) {
72
398
  try {
73
399
  return JSON.parse(readFileSync(path, "utf8"));
74
400
  } catch (error) {
75
- throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
401
+ 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>`." });
76
402
  }
77
403
  }
78
404
  function writeJsonFile(path, value) {
@@ -115,7 +441,7 @@ function writeGlobalConnections(state, options = {}) {
115
441
  function upsertGlobalConnection(alias, connection, options = {}) {
116
442
  const cleanAlias = alias.trim();
117
443
  if (!cleanAlias)
118
- throw new CliError2("Connection alias is required.", 1);
444
+ throw new CliError("Connection alias is required.", 1, { hint: "Save a server with `rig server add <alias> <url>`." });
119
445
  const state = readGlobalConnections(options);
120
446
  state.connections[cleanAlias] = connection;
121
447
  writeGlobalConnections(state, options);
@@ -132,7 +458,8 @@ function readRepoConnection(projectRoot) {
132
458
  return {
133
459
  selected,
134
460
  project: typeof record.project === "string" ? record.project : undefined,
135
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
461
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
462
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
136
463
  };
137
464
  }
138
465
  function writeRepoConnection(projectRoot, state) {
@@ -143,27 +470,37 @@ function resolveSelectedConnection(projectRoot, options = {}) {
143
470
  if (!repo)
144
471
  return null;
145
472
  if (repo.selected === "local")
146
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
473
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
147
474
  const global = readGlobalConnections(options);
148
475
  const connection = global.connections[repo.selected];
149
476
  if (!connection) {
150
- throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
477
+ throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
151
478
  }
152
- return { alias: repo.selected, connection };
479
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
480
+ }
481
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
482
+ const repo = readRepoConnection(projectRoot);
483
+ if (!repo)
484
+ return;
485
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
153
486
  }
154
487
 
155
488
  // packages/cli/src/commands/_server-client.ts
156
- import { spawnSync } from "child_process";
157
489
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
158
490
  import { resolve as resolve2 } from "path";
159
491
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
160
- var cachedGitHubBearerToken;
492
+ var scopedGitHubBearerTokens = new Map;
493
+ var serverPhaseListener = null;
494
+ function reportServerPhase(label) {
495
+ serverPhaseListener?.(label);
496
+ }
161
497
  function cleanToken(value) {
162
498
  const trimmed = value?.trim();
163
499
  return trimmed ? trimmed : null;
164
500
  }
165
- function setGitHubBearerTokenForCurrentProcess(token) {
166
- cachedGitHubBearerToken = cleanToken(token ?? undefined);
501
+ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
502
+ const scopedKey = resolve2(projectRoot ?? process.cwd());
503
+ scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
167
504
  }
168
505
  function readPrivateRemoteSessionToken(projectRoot) {
169
506
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
@@ -177,49 +514,80 @@ function readPrivateRemoteSessionToken(projectRoot) {
177
514
  }
178
515
  }
179
516
  function readGitHubBearerTokenForRemote(projectRoot) {
180
- if (cachedGitHubBearerToken !== undefined)
181
- return cachedGitHubBearerToken;
517
+ const scopedKey = resolve2(projectRoot);
518
+ if (scopedGitHubBearerTokens.has(scopedKey))
519
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
182
520
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
183
- if (privateSession) {
184
- cachedGitHubBearerToken = privateSession;
185
- return cachedGitHubBearerToken;
186
- }
187
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
188
- if (envToken) {
189
- cachedGitHubBearerToken = envToken;
190
- return cachedGitHubBearerToken;
521
+ if (privateSession)
522
+ return privateSession;
523
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
524
+ }
525
+ function readStoredGitHubAuthToken(projectRoot) {
526
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
527
+ if (!existsSync2(path))
528
+ return null;
529
+ try {
530
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
531
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
532
+ } catch {
533
+ return null;
191
534
  }
192
- const result = spawnSync("gh", ["auth", "token"], {
193
- encoding: "utf8",
194
- timeout: 5000,
195
- stdio: ["ignore", "pipe", "ignore"]
196
- });
197
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
198
- return cachedGitHubBearerToken;
535
+ }
536
+ function readLocalConnectionFallbackToken(projectRoot) {
537
+ return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
199
538
  }
200
539
  async function ensureServerForCli(projectRoot) {
201
540
  try {
202
541
  const selected = resolveSelectedConnection(projectRoot);
203
542
  if (selected?.connection.kind === "remote") {
543
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
544
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
545
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
204
546
  return {
205
547
  baseUrl: selected.connection.baseUrl,
206
- authToken: readGitHubBearerTokenForRemote(projectRoot),
207
- connectionKind: "remote"
548
+ authToken,
549
+ connectionKind: "remote",
550
+ serverProjectRoot
208
551
  };
209
552
  }
553
+ reportServerPhase("Starting local Rig server\u2026");
210
554
  const connection = await ensureLocalRigServerConnection(projectRoot);
211
555
  return {
212
556
  baseUrl: connection.baseUrl,
213
- authToken: connection.authToken,
214
- connectionKind: "local"
557
+ authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
558
+ connectionKind: "local",
559
+ serverProjectRoot: resolve2(projectRoot)
215
560
  };
216
561
  } catch (error) {
217
562
  if (error instanceof Error) {
218
- throw new CliError2(error.message, 1);
563
+ throw new CliError(error.message, 1);
219
564
  }
220
565
  throw error;
221
566
  }
222
567
  }
568
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
569
+ const repo = readRepoConnection(projectRoot);
570
+ const slug = repo?.project?.trim();
571
+ if (!slug)
572
+ return null;
573
+ try {
574
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
575
+ headers: mergeHeaders(undefined, authToken)
576
+ });
577
+ if (!response.ok)
578
+ return null;
579
+ const payload = await response.json();
580
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
581
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
582
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
583
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
584
+ if (path)
585
+ writeRepoServerProjectRoot(projectRoot, path);
586
+ return path;
587
+ } catch {
588
+ return null;
589
+ }
590
+ }
223
591
  function mergeHeaders(headers, authToken) {
224
592
  const merged = new Headers(headers);
225
593
  if (authToken) {
@@ -242,12 +610,65 @@ function diagnosticMessage(payload) {
242
610
  });
243
611
  return messages.length > 0 ? messages.join("; ") : null;
244
612
  }
613
+ var serverReachabilityCache = new Map;
614
+ async function probeServerReachability(baseUrl, authToken) {
615
+ try {
616
+ const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
617
+ headers: mergeHeaders(undefined, authToken),
618
+ signal: AbortSignal.timeout(1500)
619
+ });
620
+ return response.ok;
621
+ } catch {
622
+ return false;
623
+ }
624
+ }
625
+ function cachedServerReachability(projectRoot, baseUrl, authToken) {
626
+ const key = resolve2(projectRoot);
627
+ const cached = serverReachabilityCache.get(key);
628
+ if (cached)
629
+ return cached;
630
+ const probe = probeServerReachability(baseUrl, authToken);
631
+ serverReachabilityCache.set(key, probe);
632
+ return probe;
633
+ }
634
+ function describeSelectedServer(projectRoot, server) {
635
+ try {
636
+ const selected = resolveSelectedConnection(projectRoot);
637
+ if (selected) {
638
+ return {
639
+ alias: selected.alias,
640
+ target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
641
+ };
642
+ }
643
+ } catch {}
644
+ return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
645
+ }
646
+ async function buildServerFailureContext(projectRoot, server) {
647
+ const { alias, target } = describeSelectedServer(projectRoot, server);
648
+ const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
649
+ const reachability = reachable ? "server is reachable" : "server is unreachable";
650
+ return {
651
+ contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
652
+ hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
653
+ };
654
+ }
245
655
  async function requestServerJson(context, pathname, init = {}) {
246
656
  const server = await ensureServerForCli(context.projectRoot);
247
- const response = await fetch(`${server.baseUrl}${pathname}`, {
248
- ...init,
249
- headers: mergeHeaders(init.headers, server.authToken)
250
- });
657
+ const headers = mergeHeaders(init.headers, server.authToken);
658
+ if (server.serverProjectRoot)
659
+ headers.set("x-rig-project-root", server.serverProjectRoot);
660
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
661
+ let response;
662
+ try {
663
+ response = await fetch(`${server.baseUrl}${pathname}`, {
664
+ ...init,
665
+ headers
666
+ });
667
+ } catch (error) {
668
+ const failure = await buildServerFailureContext(context.projectRoot, server);
669
+ throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
670
+ ${failure.contextLine}`, 1, { hint: failure.hint });
671
+ }
251
672
  const text = await response.text();
252
673
  const payload = text.trim().length > 0 ? (() => {
253
674
  try {
@@ -258,8 +679,12 @@ async function requestServerJson(context, pathname, init = {}) {
258
679
  })() : null;
259
680
  if (!response.ok) {
260
681
  const diagnostics = diagnosticMessage(payload);
261
- const detail = diagnostics ?? (text || response.statusText);
262
- throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
682
+ const rawDetail = diagnostics ?? (text || response.statusText);
683
+ const detail = diagnostics ? rawDetail : rawDetail.split(`
684
+ `).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 200) ?? response.statusText;
685
+ const failure = await buildServerFailureContext(context.projectRoot, server);
686
+ throw new CliError(`Rig server request failed (${response.status}): ${detail}
687
+ ${failure.contextLine}`, 1, { hint: failure.hint });
263
688
  }
264
689
  return payload;
265
690
  }
@@ -290,39 +715,75 @@ async function registerProjectViaServer(context, input) {
290
715
  function sleep(ms) {
291
716
  return new Promise((resolve3) => setTimeout(resolve3, ms));
292
717
  }
718
+ function isRetryableProjectRootSwitchError(error) {
719
+ if (!(error instanceof Error))
720
+ return false;
721
+ const message = error.message.toLowerCase();
722
+ return message.includes("rig server request failed (401): auth-required") || message.includes("rig server request failed (401): github-token-required") || message.includes("rig server request failed (502)") || message.includes("rig server request failed (503)") || message.includes("bad gateway") || message.includes("fetch failed") || message.includes("econnrefused") || message.includes("connection refused");
723
+ }
293
724
  async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
294
- const switched = await requestServerJson(context, "/api/server/project-root", {
295
- method: "POST",
296
- headers: { "content-type": "application/json" },
297
- body: JSON.stringify({ projectRoot })
298
- });
299
725
  const timeoutMs = options.timeoutMs ?? 30000;
300
726
  const pollMs = options.pollMs ?? 1000;
301
727
  const deadline = Date.now() + timeoutMs;
302
728
  let lastError;
729
+ let switched = null;
303
730
  while (Date.now() < deadline) {
304
731
  try {
305
- const status = await requestServerJson(context, "/api/server/status");
306
- if (status && typeof status === "object" && !Array.isArray(status)) {
307
- const record = status;
308
- if (record.projectRoot === projectRoot) {
309
- return { ok: true, switched, status: record };
310
- }
311
- lastError = `server projectRoot=${String(record.projectRoot ?? "unknown")}`;
312
- }
732
+ switched = await requestServerJson(context, "/api/server/project-root", {
733
+ method: "POST",
734
+ headers: { "content-type": "application/json" },
735
+ body: JSON.stringify({ projectRoot })
736
+ });
737
+ break;
313
738
  } catch (error) {
314
739
  lastError = error;
740
+ if (!isRetryableProjectRootSwitchError(error))
741
+ throw error;
742
+ await sleep(pollMs);
315
743
  }
316
- await sleep(pollMs);
317
744
  }
318
- throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
745
+ if (!switched) {
746
+ throw new CliError(`Rig server did not accept project-root switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no response")}).`, 1);
747
+ }
748
+ const record = switched && typeof switched === "object" && !Array.isArray(switched) ? switched : {};
749
+ if (record.ok === true) {
750
+ writeRepoServerProjectRoot(context.projectRoot, projectRoot);
751
+ return { ok: true, switched: record };
752
+ }
753
+ throw new CliError(`Rig server rejected project-root scope ${projectRoot}: ${String(record.message ?? record.error ?? "unknown error")}`, 1);
754
+ }
755
+ async function ensureTaskLabelsViaServer(context) {
756
+ const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
757
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
758
+ }
759
+ async function listGitHubProjectsViaServer(context, owner) {
760
+ const url = new URL("http://rig.local/api/github/projects");
761
+ url.searchParams.set("owner", owner);
762
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
763
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
764
+ }
765
+ async function getGitHubProjectStatusFieldViaServer(context, projectId) {
766
+ const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
767
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
319
768
  }
769
+ var RESUMABLE_RUN_STATUSES = new Set([
770
+ "created",
771
+ "preparing",
772
+ "running",
773
+ "validating",
774
+ "reviewing",
775
+ "stopped",
776
+ "failed",
777
+ "needs-attention",
778
+ "needs_attention"
779
+ ]);
320
780
 
321
781
  // packages/cli/src/commands/_pi-install.ts
322
782
  import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
323
783
  import { homedir as homedir2 } from "os";
324
784
  import { resolve as resolve3 } from "path";
325
- var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
785
+ var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
786
+ var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
326
787
  var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
327
788
  export { default } from '@rig/pi-rig';
328
789
  `;
@@ -350,7 +811,7 @@ function resolvePiHomeDir(inputHomeDir) {
350
811
  function piListContainsPiRig(output) {
351
812
  return output.split(/\r?\n/).some((line) => {
352
813
  const normalized = line.trim();
353
- return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
814
+ return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
354
815
  });
355
816
  }
356
817
  async function safeRun(runner, command, options) {
@@ -374,7 +835,7 @@ async function ensurePiBinaryAvailable(input) {
374
835
  }
375
836
  return { ok: true, detail: (current.stdout || current.stderr).trim() || undefined };
376
837
  }
377
- const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim() || "bunx @earendil-works/pi@latest install";
838
+ const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim() || "bunx @earendil-works/pi-coding-agent@latest install";
378
839
  const parts = splitInstallCommand(installCommand);
379
840
  if (parts.length === 0) {
380
841
  return { ok: false, error: (current.stderr || current.stdout).trim() || "pi --version failed" };
@@ -466,7 +927,7 @@ async function ensureRemotePiRigInstalled(input) {
466
927
  const payload = await input.requestJson("/api/pi-rig/install", {
467
928
  method: "POST",
468
929
  headers: { "content-type": "application/json" },
469
- body: JSON.stringify({ package: "@rig/pi-rig", scope: "global" })
930
+ body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
470
931
  });
471
932
  const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
472
933
  const piOk = record.piOk === true || record.ok === true;
@@ -719,7 +1180,10 @@ async function runRigDoctorChecks(options) {
719
1180
  const bunVersion = options.bunVersion ?? Bun.version;
720
1181
  const request = options.requestJson ?? ((pathname, init) => requestServerJson({ projectRoot }, pathname, init));
721
1182
  const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
1183
+ const progress = options.onProgress ?? (() => {});
1184
+ progress("Checking local toolchain\u2026");
722
1185
  checks.push(check("bun", `bun >= ${MIN_SUPPORTED_BUN_VERSION}`, isSupportedBunVersion(bunVersion) ? "pass" : "fail", `found ${bunVersion}`, `Install Bun ${MIN_SUPPORTED_BUN_VERSION} or newer.`), check("git", "git", which("git") ? "pass" : "fail", which("git") ?? undefined, "Install git and ensure it is on PATH."), check("jq", "jq", which("jq") ? "pass" : "warn", which("jq") ?? undefined, "Install jq (for example `brew install jq`)."));
1186
+ progress("Loading rig.config\u2026");
723
1187
  const loadedConfig = await loadConfig(projectRoot).catch(() => null);
724
1188
  const config = loadedConfig ?? loadFallbackConfig(projectRoot);
725
1189
  const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve5(projectRoot, name)));
@@ -727,7 +1191,7 @@ async function runRigDoctorChecks(options) {
727
1191
  const taskSourceKind = config?.taskSource?.kind;
728
1192
  checks.push(taskSourceKind ? check("task-source", "task source configured", "pass", taskSourceKind) : check("task-source", "task source configured", "fail", "missing taskSource", "Configure taskSource in rig.config.ts."));
729
1193
  const repo = readRepoConnection(projectRoot);
730
- checks.push(repo ? check("project-link", "repo selected Rig connection", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to link this checkout to a GitHub repo slug.") : check("project-link", "repo selected Rig connection", "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig connect use <alias|local>`."));
1194
+ checks.push(repo ? check("project-link", "repo selected Rig server", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to link this checkout to a GitHub repo slug.") : check("project-link", "repo selected Rig server", "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
731
1195
  const selected = (() => {
732
1196
  try {
733
1197
  return resolveSelectedConnection(projectRoot);
@@ -735,9 +1199,10 @@ async function runRigDoctorChecks(options) {
735
1199
  return null;
736
1200
  }
737
1201
  })();
738
- checks.push(selected ? check("connection", "selected server connection", "pass", selected.connection.kind === "remote" ? selected.connection.baseUrl : "local auto") : check("connection", "selected server connection", repo ? "fail" : "warn", repo ? "selected alias is missing" : "will auto-start local server", repo ? "Run `rig connect list` and `rig connect use <alias|local>`." : undefined));
1202
+ checks.push(selected ? check("connection", "selected server connection", "pass", selected.connection.kind === "remote" ? selected.connection.baseUrl : "local auto") : check("connection", "selected server", repo ? "fail" : "warn", repo ? "selected alias is missing" : "will auto-start local server", repo ? "Run `rig server list` and `rig server use <alias|local>`." : undefined));
739
1203
  let server = null;
740
1204
  try {
1205
+ progress("Connecting to the selected Rig server\u2026");
741
1206
  server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
742
1207
  checks.push(check("server", "Rig server reachable", "pass", `${server.connectionKind} ${server.baseUrl}`));
743
1208
  } catch (error) {
@@ -745,18 +1210,21 @@ async function runRigDoctorChecks(options) {
745
1210
  }
746
1211
  if (server || options.requestJson) {
747
1212
  try {
1213
+ progress("Checking server status\u2026");
748
1214
  const status = await request("/api/server/status");
749
1215
  checks.push(check("server-status", "server project status", "pass", JSON.stringify(status).slice(0, 180)));
750
1216
  } catch (error) {
751
1217
  checks.push(check("server-status", "server project status", "fail", errorMessage(error), "Run `rig doctor` after the selected server is reachable."));
752
1218
  }
753
1219
  try {
1220
+ progress("Checking GitHub auth\u2026");
754
1221
  const auth = await request("/api/github/auth/status");
755
1222
  checks.push(isAuthenticated(auth) ? check("github-auth", "GitHub auth", "pass") : check("github-auth", "GitHub auth", "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
756
1223
  } catch (error) {
757
1224
  checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
758
1225
  }
759
1226
  try {
1227
+ progress("Checking GitHub repo permissions\u2026");
760
1228
  const permissions = await request("/api/github/repo/permissions");
761
1229
  const allowed = permissionAllowsPr(permissions);
762
1230
  checks.push(allowed === true ? check("github-repo-permissions", "GitHub repo PR permissions", "pass", JSON.stringify(permissions).slice(0, 180)) : allowed === false ? check("github-repo-permissions", "GitHub repo PR permissions", "fail", JSON.stringify(permissions).slice(0, 180), "Grant the selected GitHub token permission to push branches, open PRs, and merge according to repo rules.") : check("github-repo-permissions", "GitHub repo PR permissions", "warn", JSON.stringify(permissions).slice(0, 180), "Confirm the selected token can push branches and open PRs."));
@@ -764,6 +1232,7 @@ async function runRigDoctorChecks(options) {
764
1232
  checks.push(check("github-repo-permissions", "GitHub repo PR permissions", "warn", errorMessage(error), "Ensure the server exposes repo permission checks and the token can open PRs."));
765
1233
  }
766
1234
  try {
1235
+ progress("Checking GitHub issue labels\u2026");
767
1236
  const labels = await request("/api/workspace/task-labels");
768
1237
  const ready = labelsReady(labels);
769
1238
  checks.push(ready === false ? check("task-labels", "GitHub issue labels", "fail", JSON.stringify(labels).slice(0, 180), "Let Rig create required labels or create the configured lifecycle labels manually.") : check("task-labels", "GitHub issue labels", ready === true ? "pass" : "warn", JSON.stringify(labels).slice(0, 180), "Confirm required Rig lifecycle labels exist."));
@@ -771,6 +1240,7 @@ async function runRigDoctorChecks(options) {
771
1240
  checks.push(check("task-labels", "GitHub issue labels", "warn", errorMessage(error), "Run `rig init`/`rig doctor` after label setup is wired on the server."));
772
1241
  }
773
1242
  try {
1243
+ progress("Checking task projection\u2026");
774
1244
  const projection = await request("/api/workspace/task-projection");
775
1245
  checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
776
1246
  } catch (error) {
@@ -779,6 +1249,7 @@ async function runRigDoctorChecks(options) {
779
1249
  const slug = projectStatusSlug(projectRoot, config);
780
1250
  if (slug) {
781
1251
  try {
1252
+ progress("Checking server project checkout\u2026");
782
1253
  const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
783
1254
  checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
784
1255
  } catch (error) {
@@ -793,6 +1264,7 @@ async function runRigDoctorChecks(options) {
793
1264
  }
794
1265
  checks.push(githubProjectsCheck(config));
795
1266
  checks.push(prMergeCheck(config));
1267
+ progress("Checking Pi installation\u2026");
796
1268
  const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
797
1269
  ok: false,
798
1270
  label: "pi/pi-rig checks",
@@ -809,6 +1281,7 @@ function countDoctorFailures(checks) {
809
1281
 
810
1282
  // packages/cli/src/commands/init.ts
811
1283
  var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
1284
+ var DEFAULT_REMOTE_RIG_URL = "https://where.rig-does.work";
812
1285
  var RIG_CONFIG_DEV_DEPENDENCIES = {
813
1286
  "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
814
1287
  "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
@@ -819,7 +1292,7 @@ function parseRepoSlugFromRemote(remoteUrl) {
819
1292
  return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
820
1293
  }
821
1294
  function detectOriginRepoSlug(projectRoot) {
822
- const result = spawnSync2("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
1295
+ const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
823
1296
  if (result.status !== 0)
824
1297
  return null;
825
1298
  return parseRepoSlugFromRemote(result.stdout.trim());
@@ -827,7 +1300,7 @@ function detectOriginRepoSlug(projectRoot) {
827
1300
  function parseRepoSlug(value) {
828
1301
  const match = value.trim().match(/^([^/\s]+)\/([^/\s]+)$/);
829
1302
  if (!match)
830
- throw new CliError2(`Invalid GitHub repo slug: ${value}. Expected owner/repo.`, 1);
1303
+ throw new CliError(`Invalid GitHub repo slug: ${value}. Expected owner/repo.`, 1, { hint: "Pass the slug as owner/repo, e.g. `rig init --repo acme/widgets`." });
831
1304
  return { owner: match[1], repo: match[2], slug: `${match[1]}/${match[2]}` };
832
1305
  }
833
1306
  function ensureRigPrivateDirs(projectRoot) {
@@ -875,11 +1348,14 @@ function applyGitHubProjectConfig(source, options) {
875
1348
  return source;
876
1349
  const projectId = JSON.stringify(options.githubProject);
877
1350
  const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
1351
+ const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
1352
+ statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
1353
+ `)},` : "";
878
1354
  return source.replace(` projects: { enabled: false },`, [
879
1355
  ` projects: {`,
880
1356
  ` enabled: true,`,
881
1357
  ` projectId: ${projectId},`,
882
- ` statusFieldId: ${statusFieldId},`,
1358
+ ` statusFieldId: ${statusFieldId},${statuses}`,
883
1359
  ` },`
884
1360
  ].join(`
885
1361
  `));
@@ -900,40 +1376,238 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
900
1376
  }
901
1377
  }
902
1378
  function detectGhLogin() {
903
- const result = spawnSync2("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
1379
+ const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
904
1380
  return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
905
1381
  }
906
1382
  function readGhAuthToken() {
907
- const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
1383
+ const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
908
1384
  if (result.status !== 0 || !result.stdout.trim()) {
909
- throw new CliError2(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1);
1385
+ throw new CliError(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1, { hint: "Sign in with `gh auth login`, or pass a token directly: `rig init --github-auth token --github-token <token>`." });
910
1386
  }
911
1387
  return result.stdout.trim();
912
1388
  }
1389
+ function refreshGhProjectScopesAndReadToken() {
1390
+ const result = spawnSync("gh", ["auth", "refresh", "--scopes", "read:project"], {
1391
+ encoding: "utf8",
1392
+ stdio: ["inherit", "pipe", "pipe"]
1393
+ });
1394
+ if (result.status !== 0)
1395
+ return null;
1396
+ try {
1397
+ return readGhAuthToken();
1398
+ } catch {
1399
+ return null;
1400
+ }
1401
+ }
1402
+ var DRONE_CANCEL = Symbol("drone-cancel");
913
1403
  async function loadClackPrompts() {
914
- return await import("@clack/prompts");
1404
+ const drone = await Promise.resolve().then(() => (init_drone_ui(), exports_drone_ui));
1405
+ return {
1406
+ intro: (message) => drone.droneIntro(message),
1407
+ outro: (message) => drone.droneOutro(message),
1408
+ cancel: (message) => drone.droneCancel(message),
1409
+ isCancel: (value) => value === DRONE_CANCEL,
1410
+ text: async (options) => {
1411
+ const value = await drone.droneText({
1412
+ message: options.message,
1413
+ ...options.placeholder ? { placeholder: options.placeholder } : {},
1414
+ ...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
1415
+ });
1416
+ return value === null ? DRONE_CANCEL : value;
1417
+ },
1418
+ select: async (options) => {
1419
+ const value = await drone.droneSelect({
1420
+ message: options.message,
1421
+ options: options.options,
1422
+ ...options.initialValue ? { initialValue: options.initialValue } : {}
1423
+ });
1424
+ return value === null ? DRONE_CANCEL : value;
1425
+ },
1426
+ confirm: async (options) => {
1427
+ const value = await drone.droneConfirm(options);
1428
+ return value === null ? DRONE_CANCEL : value;
1429
+ }
1430
+ };
915
1431
  }
916
1432
  async function promptRequiredText(prompts, options) {
917
1433
  const value = await prompts.text(options);
918
1434
  if (prompts.isCancel(value))
919
- throw new CliError2("Init cancelled.", 1);
1435
+ throw new CliError("Init cancelled.", 1);
920
1436
  const text = String(value ?? "").trim();
921
1437
  if (!text)
922
- throw new CliError2(`${options.message} is required.`, 1);
1438
+ throw new CliError(`${options.message} is required.`, 1);
923
1439
  return text;
924
1440
  }
925
1441
  async function promptOptionalText(prompts, options) {
926
1442
  const value = await prompts.text(options);
927
1443
  if (prompts.isCancel(value))
928
- throw new CliError2("Init cancelled.", 1);
1444
+ throw new CliError("Init cancelled.", 1);
929
1445
  return String(value ?? "").trim();
930
1446
  }
931
1447
  async function promptSelect(prompts, options) {
932
1448
  const value = await prompts.select(options);
933
1449
  if (prompts.isCancel(value))
934
- throw new CliError2("Init cancelled.", 1);
1450
+ throw new CliError("Init cancelled.", 1);
935
1451
  return String(value);
936
1452
  }
1453
+ function repoOwnerFromSlug(repoSlug) {
1454
+ return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
1455
+ }
1456
+ function recordArray(value, key) {
1457
+ if (!value || typeof value !== "object" || Array.isArray(value))
1458
+ return [];
1459
+ const raw = value[key];
1460
+ return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
1461
+ }
1462
+ async function listGitHubProjectsForInit(context, owner, token) {
1463
+ if (token?.trim()) {
1464
+ try {
1465
+ return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
1466
+ } catch (directError) {
1467
+ const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
1468
+ if (recordArray(serverPayload, "projects").length > 0)
1469
+ return serverPayload;
1470
+ return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
1471
+ }
1472
+ }
1473
+ return listGitHubProjectsViaServer(context, owner);
1474
+ }
1475
+ async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
1476
+ if (token?.trim()) {
1477
+ try {
1478
+ return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
1479
+ } catch (directError) {
1480
+ const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
1481
+ if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
1482
+ return serverPayload;
1483
+ }
1484
+ return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
1485
+ }
1486
+ }
1487
+ return getGitHubProjectStatusFieldViaServer(context, projectId);
1488
+ }
1489
+ var PROJECT_STATUS_PROMPTS = {
1490
+ running: "Running/In progress",
1491
+ prOpen: "PR open/review",
1492
+ ciFixing: "CI/review fixing",
1493
+ merging: "Merging",
1494
+ done: "Done",
1495
+ needsAttention: "Needs attention"
1496
+ };
1497
+ var DEFAULT_PROJECT_STATUS_OPTIONS = {
1498
+ running: "In Progress",
1499
+ prOpen: "In Review",
1500
+ ciFixing: "In Review",
1501
+ merging: "Merging",
1502
+ done: "Done",
1503
+ needsAttention: "Needs Attention"
1504
+ };
1505
+ async function promptManualProjectStatusMapping(prompts) {
1506
+ const statuses = {};
1507
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
1508
+ const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
1509
+ const value = await promptOptionalText(prompts, {
1510
+ message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
1511
+ placeholder: defaultLabel
1512
+ });
1513
+ statuses[key] = value || defaultLabel;
1514
+ }
1515
+ return statuses;
1516
+ }
1517
+ function projectScopeError(value) {
1518
+ const text = typeof value === "string" ? value : JSON.stringify(value ?? "");
1519
+ return /INSUFFICIENT_SCOPES|read:project|required scopes/i.test(text);
1520
+ }
1521
+ function optionName(option) {
1522
+ return String(option.name ?? option.label ?? option.id ?? "").trim();
1523
+ }
1524
+ function autoProjectStatusValue(options, key, label) {
1525
+ const candidates = [DEFAULT_PROJECT_STATUS_OPTIONS[key], label].filter((value) => Boolean(value)).map((value) => value.trim().toLowerCase());
1526
+ const match = options.find((option) => candidates.includes(optionName(option).toLowerCase()));
1527
+ if (!match)
1528
+ return null;
1529
+ return String(match.id ?? match.name);
1530
+ }
1531
+ async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken) {
1532
+ const projectChoice = await promptSelect(prompts, {
1533
+ message: "GitHub Projects status sync",
1534
+ initialValue: "select",
1535
+ options: [
1536
+ { value: "select", label: "Select accessible ProjectV2" },
1537
+ { value: "off", label: "Off" },
1538
+ { value: "manual", label: "Enter ProjectV2 ids manually" }
1539
+ ]
1540
+ });
1541
+ if (projectChoice === "off")
1542
+ return { githubProject: "off" };
1543
+ if (projectChoice === "manual") {
1544
+ return {
1545
+ githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
1546
+ githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
1547
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
1548
+ };
1549
+ }
1550
+ const owner = repoOwnerFromSlug(repoSlug);
1551
+ if (!owner)
1552
+ throw new CliError(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1, { hint: "Pass the slug as owner/repo, e.g. `rig init --repo acme/widgets`." });
1553
+ let activeToken = githubToken?.trim() || null;
1554
+ let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
1555
+ let projects = recordArray(projectsPayload, "projects");
1556
+ if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
1557
+ prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
1558
+ const refreshedToken = refreshProjectToken();
1559
+ if (refreshedToken) {
1560
+ activeToken = refreshedToken;
1561
+ projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
1562
+ projects = recordArray(projectsPayload, "projects");
1563
+ }
1564
+ }
1565
+ if (projects.length === 0) {
1566
+ const error = typeof projectsPayload.error === "string" ? ` (${String(projectsPayload.error).replace(/\s+/g, " ").slice(0, 240)})` : "";
1567
+ prompts.outro?.(`No accessible GitHub Projects were returned${error}; continuing with GitHub Projects status sync off.`);
1568
+ return { githubProject: "off", ...activeToken ? { githubToken: activeToken } : {} };
1569
+ }
1570
+ const selectedProjectId = await promptSelect(prompts, {
1571
+ message: "GitHub ProjectV2 project",
1572
+ options: [
1573
+ ...projects.map((project) => ({
1574
+ value: String(project.id),
1575
+ label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
1576
+ hint: typeof project.url === "string" ? project.url : undefined
1577
+ })),
1578
+ { value: "manual", label: "Enter ProjectV2 id manually" }
1579
+ ]
1580
+ });
1581
+ const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
1582
+ const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
1583
+ const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
1584
+ const rawField = fieldPayloadRecord.field;
1585
+ const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
1586
+ const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
1587
+ const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
1588
+ if (options.length === 0) {
1589
+ return {
1590
+ githubProject: projectId,
1591
+ githubProjectStatusField: fieldId,
1592
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts),
1593
+ ...activeToken ? { githubToken: activeToken } : {}
1594
+ };
1595
+ }
1596
+ const statuses = {};
1597
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
1598
+ const auto = autoProjectStatusValue(options, key, label);
1599
+ statuses[key] = auto ?? await promptSelect(prompts, {
1600
+ message: `Project status option for ${label}`,
1601
+ options: options.map((option) => ({ value: String(option.id ?? option.name), label: optionName(option) }))
1602
+ });
1603
+ }
1604
+ return {
1605
+ githubProject: projectId,
1606
+ githubProjectStatusField: fieldId,
1607
+ githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined,
1608
+ ...activeToken ? { githubToken: activeToken } : {}
1609
+ };
1610
+ }
937
1611
  function sleep2(ms) {
938
1612
  return new Promise((resolve7) => setTimeout(resolve7, ms));
939
1613
  }
@@ -947,12 +1621,29 @@ function apiSessionTokenFrom(payload) {
947
1621
  const token = payload.apiSessionToken;
948
1622
  return typeof token === "string" && token.trim() ? token.trim() : null;
949
1623
  }
1624
+ function cleanPayloadString(value) {
1625
+ return typeof value === "string" && value.trim() ? value.trim() : null;
1626
+ }
1627
+ function remoteGitHubAuthMetadata(payload) {
1628
+ if (!payload)
1629
+ return {};
1630
+ const userNamespace = payload.userNamespace && typeof payload.userNamespace === "object" && !Array.isArray(payload.userNamespace) ? payload.userNamespace : null;
1631
+ return {
1632
+ ...cleanPayloadString(payload.login) ? { login: cleanPayloadString(payload.login) } : {},
1633
+ ...cleanPayloadString(payload.userId) ? { userId: cleanPayloadString(payload.userId) } : {},
1634
+ ...cleanPayloadString(userNamespace?.key) ? { userNamespaceKey: cleanPayloadString(userNamespace?.key) } : {},
1635
+ ...cleanPayloadString(userNamespace?.root) ? { userNamespaceRoot: cleanPayloadString(userNamespace?.root) } : {},
1636
+ ...cleanPayloadString(userNamespace?.checkoutBaseDir) ? { checkoutBaseDir: cleanPayloadString(userNamespace?.checkoutBaseDir) } : {},
1637
+ ...cleanPayloadString(userNamespace?.snapshotBaseDir) ? { snapshotBaseDir: cleanPayloadString(userNamespace?.snapshotBaseDir) } : {}
1638
+ };
1639
+ }
950
1640
  function writeRemoteGitHubAuthState(projectRoot, input) {
951
1641
  writeFileSync2(resolve6(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
952
1642
  authenticated: true,
953
1643
  source: input.source,
954
1644
  storedOnServer: true,
955
1645
  selectedRepo: input.selectedRepo,
1646
+ ...remoteGitHubAuthMetadata(input.authPayload ?? null),
956
1647
  ...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
957
1648
  updatedAt: new Date().toISOString()
958
1649
  }, null, 2)}
@@ -983,18 +1674,149 @@ async function pollDeviceAuthUntilComplete(context, pollId, firstPayload) {
983
1674
  } while (Date.now() < deadline);
984
1675
  return last;
985
1676
  }
1677
+ function runLocalFilesInit(context, options) {
1678
+ const projectRoot = context.projectRoot;
1679
+ ensureRigPrivateDirs(projectRoot);
1680
+ ensureGitignoreEntries(projectRoot);
1681
+ writeRepoConnection(projectRoot, { selected: "local", linkedAt: new Date().toISOString() });
1682
+ const configTsPath = resolve6(projectRoot, "rig.config.ts");
1683
+ const configExists = existsSync5(configTsPath) || existsSync5(resolve6(projectRoot, "rig.config.json"));
1684
+ if (configExists && !options.repair) {
1685
+ if (context.outputMode !== "json")
1686
+ console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
1687
+ } else {
1688
+ const projectName = basename(projectRoot) || "rig-project";
1689
+ writeFileSync2(configTsPath, buildRigInitConfigSource({
1690
+ projectName,
1691
+ taskSource: { kind: "files", path: "tasks" },
1692
+ useStandardPlugin: true
1693
+ }), "utf-8");
1694
+ }
1695
+ ensureRigConfigPackageDependencies(projectRoot);
1696
+ const tasksDir = resolve6(projectRoot, "tasks");
1697
+ if (!existsSync5(tasksDir)) {
1698
+ mkdirSync2(tasksDir, { recursive: true });
1699
+ writeFileSync2(resolve6(tasksDir, "T-1.json"), `${JSON.stringify({ id: "T-1", title: "My first Rig task", body: "Describe the change you want an agent to make." }, null, 2)}
1700
+ `, "utf-8");
1701
+ }
1702
+ if (context.outputMode !== "json") {
1703
+ console.log("Initialized a local files-source Rig project (no GitHub).");
1704
+ console.log(" tasks live in tasks/*.json \xB7 server: local");
1705
+ console.log("Next: `rig task list`, then `rig task run T-1`.");
1706
+ console.log("To wire GitHub later: `rig init --repair --repo owner/repo --github-auth gh`.");
1707
+ }
1708
+ return { ok: true, group: "init", command: "init", details: { mode: "local-files", projectRoot, taskSourcePath: "tasks" } };
1709
+ }
1710
+ var DEMO_TASKS_RELATIVE_DIR = ".rig/demo-tasks";
1711
+ var DEMO_TASKS = [
1712
+ {
1713
+ id: "demo-1",
1714
+ title: "Add a hello CLI script",
1715
+ body: [
1716
+ "Create `scripts/hello.ts` that prints `Hello from Rig!` plus the current date,",
1717
+ "and add a `hello` script entry to package.json that runs it with bun.",
1718
+ "Keep it dependency-free."
1719
+ ].join(`
1720
+ `),
1721
+ status: "ready",
1722
+ labels: ["demo"]
1723
+ },
1724
+ {
1725
+ id: "demo-2",
1726
+ title: "Write a README section about this project",
1727
+ body: [
1728
+ "Add (or extend) README.md with a short `## What this is` section:",
1729
+ "two or three sentences describing the repository and how to run it.",
1730
+ "Plain prose, no badges."
1731
+ ].join(`
1732
+ `),
1733
+ status: "ready",
1734
+ labels: ["demo"]
1735
+ },
1736
+ {
1737
+ id: "demo-3",
1738
+ title: "Add a unit test for the hello script",
1739
+ body: [
1740
+ "Add `scripts/hello.test.ts` with a bun test that imports the greeting",
1741
+ "helper from the hello script and asserts it contains `Hello from Rig!`.",
1742
+ "Refactor the script to export that helper if needed."
1743
+ ].join(`
1744
+ `),
1745
+ status: "ready",
1746
+ labels: ["demo"]
1747
+ }
1748
+ ];
1749
+ function runDemoInit(context, options) {
1750
+ const projectRoot = context.projectRoot;
1751
+ ensureRigPrivateDirs(projectRoot);
1752
+ ensureGitignoreEntries(projectRoot);
1753
+ writeRepoConnection(projectRoot, { selected: "local", linkedAt: new Date().toISOString() });
1754
+ const configTsPath = resolve6(projectRoot, "rig.config.ts");
1755
+ const configExists = existsSync5(configTsPath) || existsSync5(resolve6(projectRoot, "rig.config.json"));
1756
+ let configWritten = false;
1757
+ if (configExists && !options.repair) {
1758
+ if (context.outputMode !== "json") {
1759
+ console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it for the demo.");
1760
+ }
1761
+ } else {
1762
+ writeFileSync2(configTsPath, buildRigInitConfigSource({
1763
+ projectName: basename(projectRoot) || "rig-demo",
1764
+ taskSource: { kind: "files", path: DEMO_TASKS_RELATIVE_DIR },
1765
+ useStandardPlugin: true
1766
+ }), "utf-8");
1767
+ configWritten = true;
1768
+ }
1769
+ ensureRigConfigPackageDependencies(projectRoot);
1770
+ const demoTasksDir = resolve6(projectRoot, DEMO_TASKS_RELATIVE_DIR);
1771
+ mkdirSync2(demoTasksDir, { recursive: true });
1772
+ const taskIds = [];
1773
+ for (const task of DEMO_TASKS) {
1774
+ const id = String(task.id);
1775
+ taskIds.push(id);
1776
+ const taskPath = resolve6(demoTasksDir, `${id}.json`);
1777
+ if (!existsSync5(taskPath)) {
1778
+ writeFileSync2(taskPath, `${JSON.stringify(task, null, 2)}
1779
+ `, "utf-8");
1780
+ }
1781
+ }
1782
+ if (context.outputMode !== "json") {
1783
+ console.log(`Demo Rig project ready (offline, no GitHub).`);
1784
+ console.log(` config: rig.config.ts (files task source -> ${DEMO_TASKS_RELATIVE_DIR}/)`);
1785
+ console.log(` tasks: ${taskIds.join(", ")}`);
1786
+ console.log("Next steps:");
1787
+ console.log(" 1. rig task list");
1788
+ console.log(" 2. rig task run --next");
1789
+ }
1790
+ return {
1791
+ ok: true,
1792
+ group: "init",
1793
+ command: "init",
1794
+ details: {
1795
+ mode: "demo",
1796
+ projectRoot,
1797
+ taskSourcePath: DEMO_TASKS_RELATIVE_DIR,
1798
+ demoTasksDir,
1799
+ tasks: taskIds,
1800
+ configWritten
1801
+ }
1802
+ };
1803
+ }
986
1804
  async function runControlPlaneInit(context, options) {
987
1805
  const projectRoot = context.projectRoot;
988
1806
  const detectedSlug = options.repoSlug ?? detectOriginRepoSlug(projectRoot);
989
1807
  if (!detectedSlug) {
990
- throw new CliError2("Could not detect GitHub repo slug from origin. Pass --repo owner/repo.", 1);
1808
+ const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
1809
+ if ((options.server ?? "local") === "local" && authMethod2 === "skip") {
1810
+ return runLocalFilesInit(context, options);
1811
+ }
1812
+ throw new CliError("Could not detect GitHub repo slug from origin. Pass --repo owner/repo \u2014 or run `rig init --yes --server local --github-auth skip` for a local files-source project without GitHub.", 1);
991
1813
  }
992
1814
  const repo = parseRepoSlug(detectedSlug);
993
1815
  const serverKind = options.server ?? "local";
994
1816
  const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : "remote");
995
1817
  if (serverKind === "remote") {
996
1818
  if (!options.remoteUrl)
997
- throw new CliError2("Missing --remote-url for --server remote.", 1);
1819
+ throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server`." });
998
1820
  upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: options.remoteUrl });
999
1821
  }
1000
1822
  writeRepoConnection(projectRoot, {
@@ -1039,18 +1861,19 @@ async function runControlPlaneInit(context, options) {
1039
1861
  const authMethod = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
1040
1862
  const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${options.remoteUrl ?? "the remote Rig server"}.` : null;
1041
1863
  if (remoteGhTokenWarning && !options.yes) {
1042
- throw new CliError2(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
1864
+ throw new CliError(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
1043
1865
  }
1044
1866
  const token = authMethod === "gh" && !options.githubToken ? readGhAuthToken() : options.githubToken?.trim();
1045
1867
  if (token) {
1046
1868
  githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
1047
1869
  const apiSessionToken = apiSessionTokenFrom(githubAuth);
1048
- setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
1870
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
1049
1871
  if (serverKind === "remote") {
1050
1872
  writeRemoteGitHubAuthState(projectRoot, {
1051
1873
  source: authMethod === "gh" ? "gh" : "init-token",
1052
1874
  selectedRepo: repo.slug,
1053
- apiSessionToken
1875
+ apiSessionToken,
1876
+ authPayload: githubAuth
1054
1877
  });
1055
1878
  }
1056
1879
  } else if (authMethod === "device") {
@@ -1069,9 +1892,9 @@ async function runControlPlaneInit(context, options) {
1069
1892
  if (completed) {
1070
1893
  const apiSessionToken = apiSessionTokenFrom(completed);
1071
1894
  if (apiSessionToken) {
1072
- setGitHubBearerTokenForCurrentProcess(apiSessionToken);
1895
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
1073
1896
  if (serverKind === "remote") {
1074
- writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken });
1897
+ writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
1075
1898
  }
1076
1899
  }
1077
1900
  deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
@@ -1089,19 +1912,25 @@ async function runControlPlaneInit(context, options) {
1089
1912
  Object.assign(checkout, preparedCheckout);
1090
1913
  }
1091
1914
  }
1915
+ const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
1916
+ if (serverKind === "remote" && checkoutPath && token) {
1917
+ githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
1918
+ const apiSessionToken = apiSessionTokenFrom(githubAuth);
1919
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
1920
+ writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
1921
+ }
1092
1922
  const registered = await registerProjectViaServer(context, {
1093
1923
  repoSlug: repo.slug,
1094
1924
  checkout
1095
1925
  });
1096
- const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
1097
1926
  const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
1098
- if (serverRootSwitch && token) {
1099
- githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath ?? undefined });
1100
- const apiSessionToken = apiSessionTokenFrom(githubAuth);
1101
- setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
1102
- writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken });
1103
- }
1104
1927
  const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
1928
+ const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
1929
+ ok: false,
1930
+ ready: false,
1931
+ labelsReady: false,
1932
+ error: error instanceof Error ? error.message : String(error)
1933
+ }));
1105
1934
  const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
1106
1935
  remote: true,
1107
1936
  pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
@@ -1132,6 +1961,7 @@ async function runControlPlaneInit(context, options) {
1132
1961
  githubAuth,
1133
1962
  deviceAuth,
1134
1963
  githubAuthWarning: remoteGhTokenWarning,
1964
+ labelSetup,
1135
1965
  pi,
1136
1966
  doctor
1137
1967
  };
@@ -1143,6 +1973,8 @@ async function runControlPlaneInit(context, options) {
1143
1973
  }
1144
1974
  function parseInitOptions(args) {
1145
1975
  let rest = [...args];
1976
+ const demo = takeFlag(rest, "--demo");
1977
+ rest = demo.rest;
1146
1978
  const yes = takeFlag(rest, "--yes");
1147
1979
  rest = yes.rest;
1148
1980
  const repair = takeFlag(rest, "--repair");
@@ -1172,6 +2004,7 @@ function parseInitOptions(args) {
1172
2004
  const ref = takeOption(rest, "--ref");
1173
2005
  rest = ref.rest;
1174
2006
  const options = {
2007
+ demo: demo.value,
1175
2008
  yes: yes.value,
1176
2009
  repair: repair.value,
1177
2010
  privateStateOnly: privateStateOnly.value,
@@ -1185,10 +2018,10 @@ function parseInitOptions(args) {
1185
2018
  githubProjectStatusField: githubProjectStatusField.value
1186
2019
  };
1187
2020
  if (server.value && options.server === undefined) {
1188
- throw new CliError2("--server must be local or remote.", 1);
2021
+ throw new CliError("--server must be local or remote.", 1);
1189
2022
  }
1190
2023
  if (githubAuth.value && !["gh", "token", "device", "skip"].includes(githubAuth.value)) {
1191
- throw new CliError2("--github-auth must be gh, token, device, or skip.", 1);
2024
+ throw new CliError("--github-auth must be gh, token, device, or skip.", 1);
1192
2025
  }
1193
2026
  if (remoteCheckout.value) {
1194
2027
  if (remoteCheckout.value === "managed-clone")
@@ -1199,10 +2032,10 @@ function parseInitOptions(args) {
1199
2032
  options.remoteCheckout = { kind: "uploaded-snapshot" };
1200
2033
  else if (remoteCheckout.value === "existing-path") {
1201
2034
  if (!existingPath.value)
1202
- throw new CliError2("--remote-checkout existing-path requires --existing-path <path>.", 1);
2035
+ throw new CliError("--remote-checkout existing-path requires --existing-path <path>.", 1);
1203
2036
  options.remoteCheckout = { kind: "existing-path", path: existingPath.value };
1204
2037
  } else {
1205
- throw new CliError2("--remote-checkout must be managed-clone, current-ref, uploaded-snapshot, or existing-path.", 1);
2038
+ throw new CliError("--remote-checkout must be managed-clone, current-ref, uploaded-snapshot, or existing-path.", 1);
1206
2039
  }
1207
2040
  }
1208
2041
  return { options, rest };
@@ -1238,12 +2071,13 @@ async function runInteractiveControlPlaneInit(context, prompts) {
1238
2071
  });
1239
2072
  const serverChoice = await promptSelect(prompts, {
1240
2073
  message: "Rig server",
2074
+ initialValue: "remote",
1241
2075
  options: [
1242
- { value: "local", label: "Local server", hint: "run on this machine" },
1243
- { value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" }
2076
+ { value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" },
2077
+ { value: "local", label: "Local server", hint: "run on this machine" }
1244
2078
  ]
1245
2079
  });
1246
- const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: "https://rig.example.com" }) : undefined;
2080
+ const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: DEFAULT_REMOTE_RIG_URL, initialValue: DEFAULT_REMOTE_RIG_URL }) : undefined;
1247
2081
  let remoteCheckout;
1248
2082
  if (serverChoice === "remote") {
1249
2083
  const checkout = await promptSelect(prompts, {
@@ -1275,38 +2109,35 @@ async function runInteractiveControlPlaneInit(context, prompts) {
1275
2109
  { value: "skip", label: "Skip for now" }
1276
2110
  ]
1277
2111
  });
2112
+ let remoteGhTokenConfirmed = false;
1278
2113
  if (serverChoice === "remote" && authMethod === "gh") {
1279
2114
  if (!prompts.confirm)
1280
- throw new CliError2("Remote gh-token import requires explicit confirmation.", 1);
2115
+ throw new CliError("Remote gh-token import requires explicit confirmation.", 1);
1281
2116
  const confirmed = await prompts.confirm({
1282
2117
  message: `This sends a GitHub token from this machine to ${remoteUrl}. Continue?`,
1283
- initialValue: false
2118
+ initialValue: true
1284
2119
  });
1285
2120
  if (prompts.isCancel(confirmed) || confirmed !== true) {
1286
- throw new CliError2("Remote gh-token import cancelled.", 1);
2121
+ throw new CliError("Remote gh-token import cancelled.", 1);
1287
2122
  }
2123
+ remoteGhTokenConfirmed = true;
1288
2124
  }
1289
- const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : undefined;
1290
- const projectChoice = await promptSelect(prompts, {
1291
- message: "GitHub Projects status sync",
1292
- options: [
1293
- { value: "off", label: "Off" },
1294
- { value: "configure", label: "Configure ProjectV2 status field" }
1295
- ]
1296
- });
1297
- const githubProject = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : "off";
1298
- const githubProjectStatusField = projectChoice === "configure" ? await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }) : undefined;
2125
+ const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
2126
+ const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
2127
+ const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
1299
2128
  const result = await runControlPlaneInit(context, {
1300
2129
  server: serverChoice,
1301
2130
  remoteUrl,
1302
2131
  repoSlug,
1303
- githubToken,
2132
+ githubToken: effectiveGithubToken,
1304
2133
  githubAuthMethod: authMethod,
1305
- githubProject,
1306
- githubProjectStatusField,
2134
+ githubProject: projectConfig.githubProject,
2135
+ githubProjectStatusField: projectConfig.githubProjectStatusField,
2136
+ githubProjectStatuses: projectConfig.githubProjectStatuses,
1307
2137
  remoteCheckout,
1308
2138
  repair,
1309
- privateStateOnly
2139
+ privateStateOnly,
2140
+ yes: remoteGhTokenConfirmed || undefined
1310
2141
  });
1311
2142
  const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
1312
2143
  const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
@@ -1316,19 +2147,32 @@ async function runInteractiveControlPlaneInit(context, prompts) {
1316
2147
  }
1317
2148
  async function executeInit(context, args) {
1318
2149
  const parsed = parseInitOptions(args);
2150
+ if (parsed.options.demo) {
2151
+ if (parsed.rest.length > 0) {
2152
+ throw new CliError(`Unexpected arguments: ${parsed.rest.join(" ")}
2153
+ Usage: rig init --demo [--yes] [--repair]`, 1, { hint: "Run `rig init --demo` (optionally with --repair to rewrite an existing rig.config)." });
2154
+ }
2155
+ return runDemoInit(context, parsed.options);
2156
+ }
1319
2157
  if (parsed.options.yes || parsed.options.server || parsed.options.repoSlug || parsed.options.githubToken || parsed.options.privateStateOnly || parsed.options.repair || parsed.options.githubAuthMethod || parsed.options.remoteCheckout) {
1320
2158
  if (parsed.rest.length > 0)
1321
- throw new CliError2(`Unexpected arguments: ${parsed.rest.join(" ")}
1322
- Usage: rig init [--server local|remote] [--remote-url <url>] [--repo owner/repo] [--github-auth gh|token|device|skip] [--github-token <token>] [--github-project off|<project-id>] [--remote-checkout managed-clone|current-ref|uploaded-snapshot|existing-path] [--yes]`, 1);
2159
+ throw new CliError(`Unexpected arguments: ${parsed.rest.join(" ")}
2160
+ Usage: rig init [--demo] [--server local|remote] [--remote-url <url>] [--repo owner/repo] [--github-auth gh|token|device|skip] [--github-token <token>] [--github-project off|<project-id>] [--remote-checkout managed-clone|current-ref|uploaded-snapshot|existing-path] [--yes]`, 1);
1323
2161
  return runControlPlaneInit(context, parsed.options);
1324
2162
  }
1325
2163
  if (parsed.rest.length > 0)
1326
- throw new CliError2(`Unexpected arguments: ${parsed.rest.join(" ")}
2164
+ throw new CliError(`Unexpected arguments: ${parsed.rest.join(" ")}
1327
2165
  Usage: rig init`, 1);
2166
+ if (!process.stdin.isTTY) {
2167
+ throw new CliError("rig init is interactive and needs a terminal. For scripts, pass flags: rig init --yes --server local --github-auth skip [--repo owner/repo].", 1);
2168
+ }
1328
2169
  return runInteractiveControlPlaneInit(context, await loadClackPrompts());
1329
2170
  }
1330
2171
  export {
1331
2172
  runInteractiveControlPlaneInit,
2173
+ runDemoInit,
1332
2174
  executeInit,
1333
- buildRigInitConfigSource
2175
+ buildRigInitConfigSource,
2176
+ DEMO_TASKS_RELATIVE_DIR,
2177
+ DEMO_TASKS
1334
2178
  };