@h-rig/cli-surface-plugin 0.0.6-alpha.146

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 (94) hide show
  1. package/README.md +1 -0
  2. package/dist/src/app/drone-ui.d.ts +34 -0
  3. package/dist/src/app/drone-ui.js +278 -0
  4. package/dist/src/commands/_async-ui.d.ts +10 -0
  5. package/dist/src/commands/_async-ui.js +121 -0
  6. package/dist/src/commands/_cli-format.d.ts +56 -0
  7. package/dist/src/commands/_cli-format.js +332 -0
  8. package/dist/src/commands/_connection-state.d.ts +54 -0
  9. package/dist/src/commands/_connection-state.js +187 -0
  10. package/dist/src/commands/_doctor-checks.d.ts +9 -0
  11. package/dist/src/commands/_doctor-checks.js +24 -0
  12. package/dist/src/commands/_help-catalog.d.ts +29 -0
  13. package/dist/src/commands/_help-catalog.js +157 -0
  14. package/dist/src/commands/_inprocess-services.d.ts +33 -0
  15. package/dist/src/commands/_inprocess-services.js +102 -0
  16. package/dist/src/commands/_json-output.d.ts +11 -0
  17. package/dist/src/commands/_json-output.js +54 -0
  18. package/dist/src/commands/_parsers.d.ts +15 -0
  19. package/dist/src/commands/_parsers.js +114 -0
  20. package/dist/src/commands/_paths.d.ts +11 -0
  21. package/dist/src/commands/_paths.js +50 -0
  22. package/dist/src/commands/_pi-frontend.d.ts +35 -0
  23. package/dist/src/commands/_pi-frontend.js +64 -0
  24. package/dist/src/commands/_pi-install.d.ts +42 -0
  25. package/dist/src/commands/_pi-install.js +167 -0
  26. package/dist/src/commands/_policy.d.ts +8 -0
  27. package/dist/src/commands/_policy.js +138 -0
  28. package/dist/src/commands/_probes.d.ts +1 -0
  29. package/dist/src/commands/_probes.js +13 -0
  30. package/dist/src/commands/_run-driver-helpers.d.ts +26 -0
  31. package/dist/src/commands/_run-driver-helpers.js +132 -0
  32. package/dist/src/commands/_run-subcommands.d.ts +3 -0
  33. package/dist/src/commands/_run-subcommands.js +31 -0
  34. package/dist/src/commands/_spinner.d.ts +25 -0
  35. package/dist/src/commands/_spinner.js +65 -0
  36. package/dist/src/commands/agent.d.ts +3 -0
  37. package/dist/src/commands/agent.js +322 -0
  38. package/dist/src/commands/config.d.ts +3 -0
  39. package/dist/src/commands/config.js +193 -0
  40. package/dist/src/commands/dist.d.ts +28 -0
  41. package/dist/src/commands/dist.js +435 -0
  42. package/dist/src/commands/doctor.d.ts +3 -0
  43. package/dist/src/commands/doctor.js +171 -0
  44. package/dist/src/commands/github.d.ts +3 -0
  45. package/dist/src/commands/github.js +342 -0
  46. package/dist/src/commands/inbox.d.ts +19 -0
  47. package/dist/src/commands/inbox.js +241 -0
  48. package/dist/src/commands/init.d.ts +64 -0
  49. package/dist/src/commands/init.js +1449 -0
  50. package/dist/src/commands/inspect.d.ts +20 -0
  51. package/dist/src/commands/inspect.js +337 -0
  52. package/dist/src/commands/pi.d.ts +3 -0
  53. package/dist/src/commands/pi.js +177 -0
  54. package/dist/src/commands/plugin.d.ts +20 -0
  55. package/dist/src/commands/plugin.js +238 -0
  56. package/dist/src/commands/profile-and-review.d.ts +4 -0
  57. package/dist/src/commands/profile-and-review.js +223 -0
  58. package/dist/src/commands/queue.d.ts +3 -0
  59. package/dist/src/commands/queue.js +197 -0
  60. package/dist/src/commands/remote.d.ts +3 -0
  61. package/dist/src/commands/remote.js +516 -0
  62. package/dist/src/commands/repo-git-harness.d.ts +5 -0
  63. package/dist/src/commands/repo-git-harness.js +282 -0
  64. package/dist/src/commands/run.d.ts +22 -0
  65. package/dist/src/commands/run.js +645 -0
  66. package/dist/src/commands/server.d.ts +3 -0
  67. package/dist/src/commands/server.js +155 -0
  68. package/dist/src/commands/setup.d.ts +16 -0
  69. package/dist/src/commands/setup.js +356 -0
  70. package/dist/src/commands/stats.d.ts +11 -0
  71. package/dist/src/commands/stats.js +219 -0
  72. package/dist/src/commands/task-run-driver.d.ts +93 -0
  73. package/dist/src/commands/task-run-driver.js +136 -0
  74. package/dist/src/commands/task.d.ts +46 -0
  75. package/dist/src/commands/task.js +555 -0
  76. package/dist/src/commands/test.d.ts +3 -0
  77. package/dist/src/commands/test.js +46 -0
  78. package/dist/src/commands/triage.d.ts +11 -0
  79. package/dist/src/commands/triage.js +224 -0
  80. package/dist/src/commands/workspace.d.ts +3 -0
  81. package/dist/src/commands/workspace.js +130 -0
  82. package/dist/src/kernel-dispatch.d.ts +15 -0
  83. package/dist/src/kernel-dispatch.js +16 -0
  84. package/dist/src/plugin.d.ts +3 -0
  85. package/dist/src/plugin.js +5440 -0
  86. package/dist/src/rig-config-package-deps.d.ts +10 -0
  87. package/dist/src/rig-config-package-deps.js +272 -0
  88. package/dist/src/runner.d.ts +47 -0
  89. package/dist/src/runner.js +267 -0
  90. package/dist/src/version.d.ts +8 -0
  91. package/dist/src/version.js +47 -0
  92. package/dist/src/withMutedConsole.d.ts +2 -0
  93. package/dist/src/withMutedConsole.js +42 -0
  94. package/package.json +34 -0
@@ -0,0 +1,1449 @@
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);
17
+
18
+ // packages/cli-surface-plugin/src/commands/_spinner.ts
19
+ function createTtySpinner(input) {
20
+ const output = input.output ?? process.stdout;
21
+ const isTty = output.isTTY === true;
22
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
23
+ let label = input.label;
24
+ let frame = 0;
25
+ let paused = false;
26
+ let stopped = false;
27
+ let lastPrintedLabel = "";
28
+ const render = () => {
29
+ if (stopped || paused)
30
+ return;
31
+ if (!isTty) {
32
+ if (label !== lastPrintedLabel) {
33
+ output.write(`${label}
34
+ `);
35
+ lastPrintedLabel = label;
36
+ }
37
+ return;
38
+ }
39
+ frame = (frame + 1) % frames.length;
40
+ const glyph = frames[frame] ?? frames[0] ?? "";
41
+ output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
42
+ };
43
+ const clearLine = () => {
44
+ if (isTty)
45
+ output.write("\r\x1B[2K");
46
+ };
47
+ render();
48
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 16) : null;
49
+ return {
50
+ setLabel(next) {
51
+ label = next;
52
+ render();
53
+ },
54
+ pause() {
55
+ paused = true;
56
+ clearLine();
57
+ },
58
+ resume() {
59
+ if (stopped)
60
+ return;
61
+ paused = false;
62
+ render();
63
+ },
64
+ stop(finalLine) {
65
+ if (stopped)
66
+ return;
67
+ stopped = true;
68
+ if (timer)
69
+ clearInterval(timer);
70
+ clearLine();
71
+ if (finalLine)
72
+ output.write(`${finalLine}
73
+ `);
74
+ }
75
+ };
76
+ }
77
+ var SPINNER_FRAMES;
78
+ var init__spinner = __esm(() => {
79
+ SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
80
+ });
81
+
82
+ // packages/cli-surface-plugin/src/app/drone-ui.ts
83
+ var exports_drone_ui = {};
84
+ __export(exports_drone_ui, {
85
+ droneWarn: () => droneWarn,
86
+ droneText: () => droneText,
87
+ droneStep: () => droneStep,
88
+ droneSpinner: () => droneSpinner,
89
+ droneSelect: () => droneSelect,
90
+ droneOutro: () => droneOutro,
91
+ droneNote: () => droneNote,
92
+ droneIntro: () => droneIntro,
93
+ droneInfo: () => droneInfo,
94
+ droneError: () => droneError,
95
+ droneConfirm: () => droneConfirm,
96
+ droneCancel: () => droneCancel
97
+ });
98
+ import { Input, matchesKey, ProcessTerminal, SelectList, Text, TUI } from "@oh-my-pi/pi-tui";
99
+ function hexToRgb(hex) {
100
+ const value = hex.charCodeAt(0) === 35 ? hex.slice(1) : hex;
101
+ return [
102
+ Number.parseInt(value.slice(0, 2), 16),
103
+ Number.parseInt(value.slice(2, 4), 16),
104
+ Number.parseInt(value.slice(4, 6), 16)
105
+ ];
106
+ }
107
+ function fg(hex) {
108
+ const [r, g, b] = hexToRgb(hex);
109
+ return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
110
+ }
111
+ function bold(text) {
112
+ return `\x1B[1m${text}\x1B[22m`;
113
+ }
114
+ function renderMicroDroneFrame(frame) {
115
+ const tick = Math.max(0, MICRO_DRONE_FRAMES.indexOf(frame));
116
+ const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
117
+ const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
118
+ return `${ink4("(")}${cyan(blade)}${ink4(")")}${bold(accent(eye))}${ink4("(")}${cyan(blade)}${ink4(")")}`;
119
+ }
120
+ function hairline(width = Math.min(process.stdout.columns ?? 80, 100)) {
121
+ return ink4("\u2500".repeat(Math.max(10, width)));
122
+ }
123
+ function droneIntro(title, subtitle) {
124
+ console.log("");
125
+ console.log(` ${accent("\u258D")}${bold(ink(title))}${subtitle ? ink3(` \u2014 ${subtitle}`) : ""}`);
126
+ console.log(hairline());
127
+ }
128
+ function droneOutro(text) {
129
+ console.log(hairline());
130
+ console.log(` ${accent("\u25C6")} ${ink2(text)}`);
131
+ console.log("");
132
+ }
133
+ function droneNote(message, title) {
134
+ if (title)
135
+ console.log(` ${accentDim("\u25C7")} ${bold(ink2(title))}`);
136
+ for (const line of message.split(`
137
+ `))
138
+ console.log(` ${ink4("\u2502")} ${line}`);
139
+ }
140
+ function droneStep(text) {
141
+ console.log(` ${accent("\u203A")} ${ink(text)}`);
142
+ }
143
+ function droneInfo(text) {
144
+ console.log(` ${cyan("\xB7")} ${ink2(text)}`);
145
+ }
146
+ function droneWarn(text) {
147
+ console.log(` ${yellow("\u25B2")} ${ink2(text)}`);
148
+ }
149
+ function droneError(text) {
150
+ console.log(` ${red("\u2716")} ${ink2(text)}`);
151
+ }
152
+ function droneCancel(text) {
153
+ console.log(` ${red("\u2716")} ${ink3(text)}`);
154
+ }
155
+ function droneSpinner() {
156
+ let active = null;
157
+ return {
158
+ start(message) {
159
+ active = createTtySpinner({
160
+ label: message,
161
+ frames: MICRO_DRONE_FRAMES,
162
+ styleFrame: renderMicroDroneFrame
163
+ });
164
+ },
165
+ stop(message) {
166
+ active?.stop(message ? ` ${accent("\u25C6")} ${ink2(message)}` : undefined);
167
+ active = null;
168
+ },
169
+ error(message) {
170
+ active?.stop(message ? ` ${red("\u2716")} ${ink2(message)}` : undefined);
171
+ active = null;
172
+ }
173
+ };
174
+ }
175
+ async function runMiniTui(build) {
176
+ const tui = new TUI(new ProcessTerminal);
177
+ let settled = false;
178
+ return await new Promise((resolve5) => {
179
+ const finish = (result) => {
180
+ if (settled)
181
+ return;
182
+ settled = true;
183
+ tui.stop();
184
+ resolve5(result);
185
+ };
186
+ build(tui, finish);
187
+ tui.start();
188
+ });
189
+ }
190
+ async function droneSelect(input) {
191
+ if (!process.stdout.isTTY || input.options.length === 0)
192
+ return input.initialValue ?? input.options[0]?.value ?? null;
193
+ return runMiniTui((tui, finish) => {
194
+ tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}`));
195
+ const items = input.options.map((option) => ({
196
+ value: option.value,
197
+ label: option.label,
198
+ ...option.hint ? { description: option.hint } : {}
199
+ }));
200
+ const list = new SelectList(items, Math.min(items.length, 12), DRONE_SELECT_THEME);
201
+ const initialIndex = input.initialValue ? items.findIndex((item) => item.value === input.initialValue) : -1;
202
+ if (initialIndex > 0)
203
+ list.setSelectedIndex(initialIndex);
204
+ list.onSelect = (item) => finish(item.value);
205
+ list.onCancel = () => finish(null);
206
+ tui.addChild(list);
207
+ tui.addChild(new Text(ink4(" \u2191\u2193 navigate \xB7 enter select \xB7 esc cancel")));
208
+ tui.setFocus(list);
209
+ tui.addInputListener((data) => {
210
+ if (matchesKey(data, "ctrl+c") || matchesKey(data, "escape")) {
211
+ finish(null);
212
+ return { consume: true };
213
+ }
214
+ return;
215
+ });
216
+ });
217
+ }
218
+ async function droneText(input) {
219
+ if (!process.stdout.isTTY)
220
+ return input.initialValue ?? null;
221
+ return runMiniTui((tui, finish) => {
222
+ tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}${input.placeholder ? ink4(` (${input.placeholder})`) : ""}`));
223
+ const field = new Input;
224
+ if (input.initialValue)
225
+ field.setValue(input.initialValue);
226
+ field.onSubmit = (value) => finish(value);
227
+ field.onEscape = () => finish(null);
228
+ tui.addChild(field);
229
+ tui.addChild(new Text(ink4(" enter submit \xB7 esc cancel")));
230
+ tui.setFocus(field);
231
+ tui.addInputListener((data) => {
232
+ if (matchesKey(data, "ctrl+c")) {
233
+ finish(null);
234
+ return { consume: true };
235
+ }
236
+ return;
237
+ });
238
+ });
239
+ }
240
+ async function droneConfirm(input) {
241
+ const answer = await droneSelect({
242
+ message: input.message,
243
+ options: [
244
+ { value: "yes", label: "Yes" },
245
+ { value: "no", label: "No" }
246
+ ],
247
+ initialValue: input.initialValue === false ? "no" : "yes"
248
+ });
249
+ return answer === null ? null : answer === "yes";
250
+ }
251
+ var PALETTE, ink, ink2, ink3, ink4, accent, accentDim, cyan, red, yellow, MICRO_BLADES, EYE_FRAMES, MICRO_DRONE_FRAMES, DRONE_SYMBOLS, DRONE_SELECT_THEME;
252
+ var init_drone_ui = __esm(() => {
253
+ init__spinner();
254
+ PALETTE = {
255
+ ink: "#f2f3f6",
256
+ ink2: "#aeb0ba",
257
+ ink3: "#6c6e79",
258
+ ink4: "#44464f",
259
+ accent: "#ccff4d",
260
+ accentDim: "#a9d63f",
261
+ cyan: "#56d8ff",
262
+ red: "#ff5d5d",
263
+ yellow: "#ffd24d"
264
+ };
265
+ ink = fg(PALETTE.ink);
266
+ ink2 = fg(PALETTE.ink2);
267
+ ink3 = fg(PALETTE.ink3);
268
+ ink4 = fg(PALETTE.ink4);
269
+ accent = fg(PALETTE.accent);
270
+ accentDim = fg(PALETTE.accentDim);
271
+ cyan = fg(PALETTE.cyan);
272
+ red = fg(PALETTE.red);
273
+ yellow = fg(PALETTE.yellow);
274
+ MICRO_BLADES = ["---", "\\\\\\", "|||", "///"];
275
+ EYE_FRAMES = ["@", "o", "."];
276
+ MICRO_DRONE_FRAMES = Array.from({ length: 12 }, (_, index) => {
277
+ const blade = MICRO_BLADES[index % MICRO_BLADES.length];
278
+ const eye = EYE_FRAMES[Math.floor(index / 2) % EYE_FRAMES.length];
279
+ return `(${blade})${eye}(${blade})`;
280
+ });
281
+ DRONE_SYMBOLS = {
282
+ cursor: "\u2192",
283
+ inputCursor: "\u258F",
284
+ boxRound: { topLeft: "\u256D", topRight: "\u256E", bottomLeft: "\u2570", bottomRight: "\u256F", horizontal: "\u2500", vertical: "\u2502" },
285
+ boxSharp: { topLeft: "\u250C", topRight: "\u2510", bottomLeft: "\u2514", bottomRight: "\u2518", horizontal: "\u2500", vertical: "\u2502", teeDown: "\u252C", teeUp: "\u2534", teeLeft: "\u2524", teeRight: "\u251C", cross: "\u253C" },
286
+ table: { topLeft: "\u250C", topRight: "\u2510", bottomLeft: "\u2514", bottomRight: "\u2518", horizontal: "\u2500", vertical: "\u2502", teeDown: "\u252C", teeUp: "\u2534", teeLeft: "\u2524", teeRight: "\u251C", cross: "\u253C" },
287
+ quoteBorder: "\u2502",
288
+ hrChar: "\u2500",
289
+ spinnerFrames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"]
290
+ };
291
+ DRONE_SELECT_THEME = {
292
+ selectedPrefix: (text) => accent(text),
293
+ selectedText: (text) => bold(ink(text)),
294
+ description: (text) => ink3(text),
295
+ scrollInfo: (text) => ink4(text),
296
+ noMatch: (text) => ink3(text),
297
+ symbols: DRONE_SYMBOLS,
298
+ hovered: (text) => bold(ink(text))
299
+ };
300
+ });
301
+
302
+ // packages/cli-surface-plugin/src/commands/init.ts
303
+ import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
304
+ import { spawnSync } from "child_process";
305
+ import { basename, resolve as resolve5 } from "path";
306
+
307
+ // packages/cli-surface-plugin/src/runner.ts
308
+ import { EventBus } from "@rig/runtime/control-plane/runtime/events";
309
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
310
+ import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
311
+ import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
312
+
313
+ class CliError extends RuntimeCliError {
314
+ hint;
315
+ constructor(message, exitCode = 1, options = {}) {
316
+ super(message, exitCode);
317
+ if (options.hint?.trim()) {
318
+ this.hint = options.hint.trim();
319
+ }
320
+ }
321
+ }
322
+ function takeFlag(args, flag) {
323
+ const rest = [];
324
+ let value = false;
325
+ for (const arg of args) {
326
+ if (arg === flag) {
327
+ value = true;
328
+ continue;
329
+ }
330
+ rest.push(arg);
331
+ }
332
+ return { value, rest };
333
+ }
334
+ function takeOption(args, option) {
335
+ const rest = [];
336
+ let value;
337
+ for (let index = 0;index < args.length; index += 1) {
338
+ const current = args[index];
339
+ if (current === option) {
340
+ const next = args[index + 1];
341
+ if (!next || next.startsWith("-")) {
342
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
343
+ }
344
+ value = next;
345
+ index += 1;
346
+ continue;
347
+ }
348
+ if (current !== undefined) {
349
+ rest.push(current);
350
+ }
351
+ }
352
+ return { value, rest };
353
+ }
354
+
355
+ // packages/cli-surface-plugin/src/commands/init.ts
356
+ import { buildRigInitConfigSource } from "@rig/core";
357
+
358
+ // packages/cli-surface-plugin/src/commands/_connection-state.ts
359
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
360
+ import { dirname, resolve } from "path";
361
+ function resolveRepoConnectionPath(projectRoot) {
362
+ return resolve(projectRoot, ".rig", "state", "connection.json");
363
+ }
364
+ function readJsonFile(path) {
365
+ if (!existsSync(path))
366
+ return null;
367
+ try {
368
+ return JSON.parse(readFileSync(path, "utf8"));
369
+ } catch (error) {
370
+ 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>`." });
371
+ }
372
+ }
373
+ function writeJsonFile(path, value) {
374
+ mkdirSync(dirname(path), { recursive: true });
375
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
376
+ `, "utf8");
377
+ }
378
+ function readRepoConnection(projectRoot) {
379
+ const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
380
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
381
+ return null;
382
+ const record = payload;
383
+ const selected = typeof record.selected === "string" ? record.selected.trim() : "";
384
+ if (!selected)
385
+ return null;
386
+ return {
387
+ selected,
388
+ project: typeof record.project === "string" ? record.project : undefined,
389
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
390
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined,
391
+ serverProjectRootAlias: typeof record.serverProjectRootAlias === "string" && record.serverProjectRootAlias.trim() ? record.serverProjectRootAlias.trim() : undefined,
392
+ serverProjectRootBaseUrl: typeof record.serverProjectRootBaseUrl === "string" && record.serverProjectRootBaseUrl.trim() ? record.serverProjectRootBaseUrl.trim().replace(/\/+$/, "") : undefined
393
+ };
394
+ }
395
+ function writeRepoConnection(projectRoot, state) {
396
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
397
+ }
398
+
399
+ // packages/cli-surface-plugin/src/commands/init.ts
400
+ import { upsertManagedRemoteEndpoint } from "@rig/runtime/control-plane/remote-config";
401
+ import { loadConfig } from "@rig/core/load-config";
402
+
403
+ // packages/cli-surface-plugin/src/commands/_inprocess-services.ts
404
+ import { resolve as resolve2 } from "path";
405
+ import {
406
+ beginGitHubDeviceFlow,
407
+ checkGitHubRepoPermissions,
408
+ createGitHubAuthStore,
409
+ listGitHubProjects,
410
+ pollGitHubDeviceFlow,
411
+ resolveGitHubAuthStatus,
412
+ resolveProjectStatusField,
413
+ saveGitHubTokenForProject
414
+ } from "@rig/runtime/control-plane/github/index";
415
+ var scopedGitHubBearerTokens = new Map;
416
+ function cleanToken(value) {
417
+ const trimmed = value?.trim();
418
+ return trimmed ? trimmed : null;
419
+ }
420
+ function cleanString(value) {
421
+ return typeof value === "string" && value.trim() ? value.trim() : null;
422
+ }
423
+ function oauthClientId() {
424
+ return cleanToken(process.env.RIG_GITHUB_OAUTH_CLIENT_ID);
425
+ }
426
+ function tokenForProject(projectRoot, override) {
427
+ const scoped = scopedGitHubBearerTokens.get(resolve2(projectRoot));
428
+ return cleanToken(override) ?? scoped ?? createGitHubAuthStore(projectRoot).readToken() ?? cleanToken(process.env.RIG_GITHUB_TOKEN);
429
+ }
430
+ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
431
+ scopedGitHubBearerTokens.set(resolve2(projectRoot ?? process.cwd()), cleanToken(token));
432
+ }
433
+ async function getGitHubAuthStatusInProcess(context) {
434
+ return { ok: true, ...resolveGitHubAuthStatus({ projectRoot: context.projectRoot, oauthConfigured: Boolean(oauthClientId()) }) };
435
+ }
436
+ async function postGitHubTokenInProcess(context, token, options = {}) {
437
+ const targetRoot = options.projectRoot?.trim() || context.projectRoot;
438
+ const result = await saveGitHubTokenForProject({
439
+ projectRoot: targetRoot,
440
+ token,
441
+ tokenSource: "manual-token",
442
+ selectedRepo: options.selectedRepo ?? null
443
+ });
444
+ const store = createGitHubAuthStore(targetRoot);
445
+ const session = store.createApiSession();
446
+ if (targetRoot !== context.projectRoot) {
447
+ store.copyToLocalProjectRoot(context.projectRoot);
448
+ }
449
+ return { ...result, authenticated: result.signedIn, apiSessionToken: session.token };
450
+ }
451
+ async function listGitHubProjectsInProcess(context, owner, options = {}) {
452
+ const token = tokenForProject(context.projectRoot, options.authToken);
453
+ if (!token)
454
+ return { ok: false, error: "missing-token", projects: [] };
455
+ const projects = await listGitHubProjects({ owner, token });
456
+ return { ok: true, projects };
457
+ }
458
+ async function getGitHubProjectStatusFieldInProcess(context, projectId, options = {}) {
459
+ const token = tokenForProject(context.projectRoot, options.authToken);
460
+ if (!token)
461
+ return { ok: false, error: "missing-token" };
462
+ const field = await resolveProjectStatusField({ projectId, token });
463
+ return { ok: true, field };
464
+ }
465
+ async function requestGitHubAuthJsonInProcess(context, pathname, init = {}) {
466
+ const method = (init.method ?? "GET").toUpperCase();
467
+ if (pathname === "/api/github/auth/status")
468
+ return getGitHubAuthStatusInProcess(context);
469
+ if (pathname === "/api/github/repo/permissions") {
470
+ return checkGitHubRepoPermissions({ projectRoot: context.projectRoot, oauthConfigured: Boolean(oauthClientId()) });
471
+ }
472
+ if (pathname === "/api/github/auth/device/start" && method === "POST") {
473
+ const clientId = oauthClientId();
474
+ if (!clientId)
475
+ return { ok: false, oauthConfigured: false, error: "RIG_GITHUB_OAUTH_CLIENT_ID is not configured." };
476
+ const body = init.body ? JSON.parse(String(init.body)) : {};
477
+ return beginGitHubDeviceFlow({
478
+ projectRoot: context.projectRoot,
479
+ clientId,
480
+ ...cleanString(body.scope) ? { scope: cleanString(body.scope) } : {},
481
+ selectedRepo: cleanString(body.repoSlug)
482
+ });
483
+ }
484
+ if (pathname === "/api/github/auth/device/poll" && method === "POST") {
485
+ const clientId = oauthClientId();
486
+ if (!clientId)
487
+ return { ok: false, oauthConfigured: false, error: "RIG_GITHUB_OAUTH_CLIENT_ID is not configured." };
488
+ const body = init.body ? JSON.parse(String(init.body)) : {};
489
+ const pollId = cleanString(body.pollId);
490
+ if (!pollId)
491
+ return { ok: false, status: "error", error: "pollId is required" };
492
+ return pollGitHubDeviceFlow({ projectRoot: context.projectRoot, clientId, pollId, selectedRepo: cleanString(body.repoSlug) });
493
+ }
494
+ throw new Error(`No in-process GitHub auth API for ${method} ${pathname}`);
495
+ }
496
+
497
+ // packages/cli-surface-plugin/src/commands/_pi-install.ts
498
+ import { existsSync as existsSync2 } from "fs";
499
+ import { homedir } from "os";
500
+ import { resolve as resolve3 } from "path";
501
+ var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
502
+ async function defaultCommandRunner(command, options = {}) {
503
+ const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
504
+ const [stdout, stderr, exitCode] = await Promise.all([
505
+ new Response(proc.stdout).text(),
506
+ new Response(proc.stderr).text(),
507
+ proc.exited
508
+ ]);
509
+ return { exitCode, stdout, stderr };
510
+ }
511
+ function resolvePiRigExtensionPath(homeDir) {
512
+ return resolve3(homeDir, ".pi", "agent", "extensions", "pi-rig");
513
+ }
514
+ function resolvePiRigPackageSource(projectRoot, exists = existsSync2) {
515
+ const localPackage = resolve3(projectRoot, "packages", "pi-rig");
516
+ if (exists(resolve3(localPackage, "package.json")))
517
+ return localPackage;
518
+ return `npm:${PI_RIG_PACKAGE_NAME}`;
519
+ }
520
+ function resolvePiHomeDir(inputHomeDir) {
521
+ return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir();
522
+ }
523
+ function piListContainsPiRig(output) {
524
+ return output.split(/\r?\n/).some((line) => {
525
+ const normalized = line.trim();
526
+ return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
527
+ });
528
+ }
529
+ async function safeRun(runner, command, options) {
530
+ try {
531
+ return await runner(command, options);
532
+ } catch (error) {
533
+ return { exitCode: 1, stdout: "", stderr: error instanceof Error ? error.message : String(error) };
534
+ }
535
+ }
536
+ function splitInstallCommand(value) {
537
+ return value.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((part) => part.replace(/^['"]|['"]$/g, "")) ?? [];
538
+ }
539
+ async function ensurePiBinaryAvailable(input) {
540
+ const current = await safeRun(input.runner, ["pi", "--version"]);
541
+ if (current.exitCode === 0) {
542
+ const updateCommand = process.env.RIG_PI_UPDATE_COMMAND?.trim();
543
+ if (updateCommand) {
544
+ const parts2 = splitInstallCommand(updateCommand);
545
+ if (parts2.length > 0)
546
+ await safeRun(input.runner, parts2, input.projectRoot ? { cwd: input.projectRoot } : undefined);
547
+ }
548
+ return { ok: true, detail: (current.stdout || current.stderr).trim() || undefined };
549
+ }
550
+ const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim();
551
+ if (!installCommand) {
552
+ return {
553
+ ok: false,
554
+ error: `${(current.stderr || current.stdout).trim() || "pi --version failed"}. Set RIG_PI_INSTALL_COMMAND to a supported installer or install Pi manually.`
555
+ };
556
+ }
557
+ const parts = splitInstallCommand(installCommand);
558
+ if (parts.length === 0) {
559
+ return { ok: false, error: (current.stderr || current.stdout).trim() || "pi --version failed" };
560
+ }
561
+ const install = await safeRun(input.runner, parts, input.projectRoot ? { cwd: input.projectRoot } : undefined);
562
+ if (install.exitCode !== 0) {
563
+ return { ok: false, installedOrUpdated: true, error: (install.stderr || install.stdout).trim() || `Pi install command failed (${install.exitCode})` };
564
+ }
565
+ const next = await safeRun(input.runner, ["pi", "--version"]);
566
+ return {
567
+ ok: next.exitCode === 0,
568
+ installedOrUpdated: true,
569
+ detail: (next.stdout || next.stderr).trim() || undefined,
570
+ ...next.exitCode === 0 ? {} : { error: (next.stderr || next.stdout).trim() || "pi --version failed after install" }
571
+ };
572
+ }
573
+ async function checkPiRigInstall(input = {}) {
574
+ const home = resolvePiHomeDir(input.homeDir);
575
+ const extensionPath = resolvePiRigExtensionPath(home);
576
+ if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
577
+ return {
578
+ extensionPath,
579
+ pi: { ok: true, label: "pi", detail: "fake-pi" },
580
+ piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
581
+ };
582
+ }
583
+ const runner = input.commandRunner ?? defaultCommandRunner;
584
+ const piResult = await safeRun(runner, ["pi", "--version"]);
585
+ const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
586
+ const hasPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
587
+ ${piListResult.stderr}`);
588
+ const hasLegacyBridgeScaffold = !hasPiRig && (input.exists ?? existsSync2)(extensionPath);
589
+ return {
590
+ extensionPath,
591
+ pi: {
592
+ ok: piResult.exitCode === 0,
593
+ label: "pi",
594
+ detail: (piResult.stdout || piResult.stderr).trim() || undefined,
595
+ hint: piResult.exitCode === 0 ? undefined : "Install Pi/OMP manually or set RIG_PI_INSTALL_COMMAND before verifying with bare `rig` / Cockpit \u2192 Doctor."
596
+ },
597
+ piRig: {
598
+ ok: hasPiRig,
599
+ label: "pi-rig global extension",
600
+ detail: hasPiRig ? piListResult.stdout.trim() || PI_RIG_PACKAGE_NAME : hasLegacyBridgeScaffold ? `legacy bridge scaffold at ${extensionPath}` : undefined,
601
+ hint: hasPiRig ? undefined : `Install the Rig OMP extension with \`pi install ${PI_RIG_PACKAGE_NAME}\`, then verify with bare \`rig\` / Cockpit \u2192 Doctor.`
602
+ }
603
+ };
604
+ }
605
+ async function ensurePiRigInstalled(input) {
606
+ const home = resolvePiHomeDir(input.homeDir);
607
+ if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
608
+ const status = await checkPiRigInstall({ homeDir: home, commandRunner: input.commandRunner });
609
+ return { ...status, installedPath: status.extensionPath };
610
+ }
611
+ const runner = input.commandRunner ?? defaultCommandRunner;
612
+ const piAvailable = await ensurePiBinaryAvailable({ runner, projectRoot: input.projectRoot });
613
+ if (!piAvailable.ok) {
614
+ throw new Error(`Pi install/update failed: ${piAvailable.error ?? "pi unavailable"}`);
615
+ }
616
+ const packageSource = resolvePiRigPackageSource(input.projectRoot);
617
+ const install = await runner(["pi", "install", packageSource], { cwd: input.projectRoot });
618
+ if (install.exitCode !== 0) {
619
+ throw new Error(`pi-rig install failed: ${(install.stderr || install.stdout).trim() || `exit ${install.exitCode}`}`);
620
+ }
621
+ const next = await checkPiRigInstall({ homeDir: home, commandRunner: runner });
622
+ return { ...next, installedPath: packageSource };
623
+ }
624
+
625
+ // packages/cli-surface-plugin/src/commands/_doctor-checks.ts
626
+ import { countDoctorFailures, runDoctorChecks } from "@rig/client";
627
+ var runRigDoctorChecks = runDoctorChecks;
628
+
629
+ // packages/cli-surface-plugin/src/version.ts
630
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
631
+ import { dirname as dirname2, resolve as resolve4 } from "path";
632
+ import { fileURLToPath } from "url";
633
+ import { readBuildConfig } from "@rig/runtime/build-time-config";
634
+ var SOURCE_CLI_VERSION = "0.0.0-alpha.1";
635
+ var DEV_CLI_VERSION = "0.0.0-dev";
636
+ function resolveInstalledCliVersion() {
637
+ const buildConfig = readBuildConfig();
638
+ const envVersion = process.env.RIG_CLI_VERSION?.trim();
639
+ const buildVersion = buildConfig.RIG_CLI_VERSION?.trim();
640
+ let version = envVersion || buildVersion || "";
641
+ if (!version) {
642
+ try {
643
+ const execPath = process.execPath || "";
644
+ const moduleDir = dirname2(fileURLToPath(import.meta.url));
645
+ const candidates = [
646
+ execPath ? resolve4(dirname2(execPath), "..", "manifest.json") : "",
647
+ resolve4(moduleDir, "..", "package.json"),
648
+ resolve4(moduleDir, "..", "..", "package.json"),
649
+ resolve4(process.cwd(), "packages/cli/package.json")
650
+ ].filter(Boolean);
651
+ for (const candidate of candidates) {
652
+ if (!existsSync3(candidate))
653
+ continue;
654
+ const parsed = JSON.parse(readFileSync2(candidate, "utf-8"));
655
+ const candidateVersion = parsed.version?.trim();
656
+ if (candidateVersion) {
657
+ version = candidateVersion;
658
+ break;
659
+ }
660
+ }
661
+ } catch {}
662
+ }
663
+ return version || DEV_CLI_VERSION;
664
+ }
665
+ function isPublishedCliVersion(version) {
666
+ const normalized = version.trim();
667
+ return normalized.length > 0 && normalized !== SOURCE_CLI_VERSION && normalized !== DEV_CLI_VERSION;
668
+ }
669
+
670
+ // packages/cli-surface-plugin/src/rig-config-package-deps.ts
671
+ var REQUIRED_RIG_CONFIG_PACKAGE_NAMES = ["core", "standard-plugin"];
672
+ function resolveRigConfigPackageVersion() {
673
+ const explicit = process.env.RIG_CONFIG_PACKAGE_VERSION?.trim();
674
+ if (explicit)
675
+ return explicit;
676
+ const installed = resolveInstalledCliVersion();
677
+ return isPublishedCliVersion(installed) ? installed : "latest";
678
+ }
679
+ function rigConfigDevDependencies() {
680
+ const version = resolveRigConfigPackageVersion();
681
+ return Object.fromEntries(REQUIRED_RIG_CONFIG_PACKAGE_NAMES.map((name) => [`@rig/${name}`, rigPackageSpec(name, version)]));
682
+ }
683
+ function rigPackageSpec(packageName, version = resolveRigConfigPackageVersion()) {
684
+ return `npm:@h-rig/${packageName}@${version}`;
685
+ }
686
+
687
+ // packages/cli-surface-plugin/src/commands/init.ts
688
+ function parseRepoSlugFromRemote(remoteUrl) {
689
+ const trimmed = remoteUrl.trim();
690
+ const gitHubMatch = trimmed.match(/github\.com[:/]([^/]+)\/([^/.]+)(?:\.git)?$/i);
691
+ return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
692
+ }
693
+ function detectOriginRepoSlug(projectRoot) {
694
+ const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
695
+ if (result.status !== 0)
696
+ return null;
697
+ return parseRepoSlugFromRemote(result.stdout.trim());
698
+ }
699
+ function parseRepoSlug(value) {
700
+ const match = value.trim().match(/^([^/\s]+)\/([^/\s]+)$/);
701
+ if (!match)
702
+ 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`." });
703
+ return { owner: match[1], repo: match[2], slug: `${match[1]}/${match[2]}` };
704
+ }
705
+ function ensureRigPrivateDirs(projectRoot) {
706
+ const rigDir = resolve5(projectRoot, ".rig");
707
+ mkdirSync2(resolve5(rigDir, "state"), { recursive: true });
708
+ mkdirSync2(resolve5(rigDir, "logs"), { recursive: true });
709
+ mkdirSync2(resolve5(rigDir, "runs"), { recursive: true });
710
+ mkdirSync2(resolve5(rigDir, "tmp"), { recursive: true });
711
+ mkdirSync2(resolve5(projectRoot, "artifacts"), { recursive: true });
712
+ const taskConfigPath = resolve5(rigDir, "task-config.json");
713
+ if (!existsSync4(taskConfigPath))
714
+ writeFileSync2(taskConfigPath, `{}
715
+ `, "utf-8");
716
+ }
717
+ function ensureGitignoreEntries(projectRoot) {
718
+ const path = resolve5(projectRoot, ".gitignore");
719
+ const existing = existsSync4(path) ? readFileSync3(path, "utf8") : "";
720
+ const entries = [".rig/state/", ".rig/logs/", ".rig/runs/", ".rig/tmp/"];
721
+ const missing = entries.filter((entry) => !existing.split(/\r?\n/).includes(entry));
722
+ if (missing.length === 0)
723
+ return;
724
+ const prefix = existing.length > 0 && !existing.endsWith(`
725
+ `) ? `
726
+ ` : "";
727
+ appendFileSync(path, `${prefix}${missing.join(`
728
+ `)}
729
+ `, "utf8");
730
+ }
731
+ function ensureRigConfigPackageDependencies(projectRoot) {
732
+ const path = resolve5(projectRoot, "package.json");
733
+ const existing = existsSync4(path) ? JSON.parse(readFileSync3(path, "utf8")) : {};
734
+ const devDependencies = existing.devDependencies && typeof existing.devDependencies === "object" && !Array.isArray(existing.devDependencies) ? { ...existing.devDependencies } : {};
735
+ for (const [name, spec] of Object.entries(rigConfigDevDependencies())) {
736
+ devDependencies[name] = spec;
737
+ }
738
+ const next = {
739
+ ...existsSync4(path) ? existing : { name: "rig-project", private: true },
740
+ devDependencies
741
+ };
742
+ writeFileSync2(path, `${JSON.stringify(next, null, 2)}
743
+ `, "utf8");
744
+ }
745
+ function applyGitHubProjectConfig(source, options) {
746
+ if (!options.githubProject || options.githubProject === "off")
747
+ return source;
748
+ const projectId = JSON.stringify(options.githubProject);
749
+ const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
750
+ const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
751
+ statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
752
+ `)},` : "";
753
+ return source.replace(` projects: { enabled: false },`, [
754
+ ` projects: {`,
755
+ ` enabled: true,`,
756
+ ` projectId: ${projectId},`,
757
+ ` statusFieldId: ${statusFieldId},${statuses}`,
758
+ ` },`
759
+ ].join(`
760
+ `));
761
+ }
762
+ function checkoutForInit(projectRoot, options) {
763
+ if (options.server === "remote") {
764
+ return {
765
+ kind: "remote",
766
+ strategy: "existing-path",
767
+ path: options.remoteCheckout?.trim() || null,
768
+ alias: options.remoteAlias ?? options.remoteHost ?? null
769
+ };
770
+ }
771
+ return { kind: "local", path: projectRoot };
772
+ }
773
+ function detectGhLogin() {
774
+ const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
775
+ return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
776
+ }
777
+ function readGhAuthToken() {
778
+ const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
779
+ if (result.status !== 0 || !result.stdout.trim()) {
780
+ 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>`." });
781
+ }
782
+ return result.stdout.trim();
783
+ }
784
+ function refreshGhProjectScopesAndReadToken() {
785
+ const result = spawnSync("gh", ["auth", "refresh", "--scopes", "read:project"], {
786
+ encoding: "utf8",
787
+ stdio: ["inherit", "pipe", "pipe"]
788
+ });
789
+ if (result.status !== 0)
790
+ return null;
791
+ try {
792
+ return readGhAuthToken();
793
+ } catch {
794
+ return null;
795
+ }
796
+ }
797
+ var DRONE_CANCEL = Symbol("drone-cancel");
798
+ async function loadClackPrompts() {
799
+ const drone = await Promise.resolve().then(() => (init_drone_ui(), exports_drone_ui));
800
+ return {
801
+ intro: (message) => drone.droneIntro(message),
802
+ outro: (message) => drone.droneOutro(message),
803
+ cancel: (message) => drone.droneCancel(message),
804
+ isCancel: (value) => value === DRONE_CANCEL,
805
+ text: async (options) => {
806
+ const prefill = (options.initialValue ?? options.defaultValue ?? "").trim();
807
+ const value = await drone.droneText({
808
+ message: options.message,
809
+ ...options.placeholder ? { placeholder: options.placeholder } : {},
810
+ ...prefill ? { initialValue: prefill } : {}
811
+ });
812
+ return value === null ? DRONE_CANCEL : value;
813
+ },
814
+ select: async (options) => {
815
+ const value = await drone.droneSelect({
816
+ message: options.message,
817
+ options: options.options,
818
+ ...options.initialValue ? { initialValue: options.initialValue } : {}
819
+ });
820
+ return value === null ? DRONE_CANCEL : value;
821
+ },
822
+ confirm: async (options) => {
823
+ const value = await drone.droneConfirm(options);
824
+ return value === null ? DRONE_CANCEL : value;
825
+ }
826
+ };
827
+ }
828
+ async function promptRequiredText(prompts, options) {
829
+ const value = await prompts.text(options);
830
+ if (prompts.isCancel(value))
831
+ throw new CliError("Init cancelled.", 1);
832
+ const text = String(value ?? "").trim();
833
+ if (!text)
834
+ throw new CliError(`${options.message} is required.`, 1);
835
+ return text;
836
+ }
837
+ async function promptOptionalText(prompts, options) {
838
+ const value = await prompts.text(options);
839
+ if (prompts.isCancel(value))
840
+ throw new CliError("Init cancelled.", 1);
841
+ return String(value ?? "").trim();
842
+ }
843
+ async function promptSelect(prompts, options) {
844
+ const value = await prompts.select(options);
845
+ if (prompts.isCancel(value))
846
+ throw new CliError("Init cancelled.", 1);
847
+ return String(value);
848
+ }
849
+ function repoOwnerFromSlug(repoSlug) {
850
+ return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
851
+ }
852
+ function recordArray(value, key) {
853
+ if (!value || typeof value !== "object" || Array.isArray(value))
854
+ return [];
855
+ const raw = value[key];
856
+ return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
857
+ }
858
+ async function listGitHubProjectsForInit(context, owner, token, serverBaseUrl) {
859
+ return listGitHubProjectsInProcess(context, owner, { authToken: token, baseUrl: serverBaseUrl });
860
+ }
861
+ async function getGitHubProjectStatusFieldForInit(context, projectId, token, serverBaseUrl) {
862
+ return getGitHubProjectStatusFieldInProcess(context, projectId, { authToken: token, baseUrl: serverBaseUrl });
863
+ }
864
+ var PROJECT_STATUS_PROMPTS = {
865
+ running: "Running/In progress",
866
+ prOpen: "PR open/review",
867
+ ciFixing: "CI/review fixing",
868
+ merging: "Merging",
869
+ done: "Done",
870
+ needsAttention: "Needs attention"
871
+ };
872
+ var DEFAULT_PROJECT_STATUS_OPTIONS = {
873
+ running: "In Progress",
874
+ prOpen: "In Review",
875
+ ciFixing: "In Review",
876
+ merging: "Merging",
877
+ done: "Done",
878
+ needsAttention: "Needs Attention"
879
+ };
880
+ async function promptManualProjectStatusMapping(prompts) {
881
+ const statuses = {};
882
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
883
+ const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
884
+ const value = await promptOptionalText(prompts, {
885
+ message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
886
+ placeholder: defaultLabel
887
+ });
888
+ statuses[key] = value || defaultLabel;
889
+ }
890
+ return statuses;
891
+ }
892
+ function projectScopeError(value) {
893
+ const text = typeof value === "string" ? value : JSON.stringify(value ?? "");
894
+ return /INSUFFICIENT_SCOPES|read:project|required scopes/i.test(text);
895
+ }
896
+ function optionName(option) {
897
+ return String(option.name ?? option.label ?? option.id ?? "").trim();
898
+ }
899
+ function autoProjectStatusValue(options, key, label) {
900
+ const candidates = [DEFAULT_PROJECT_STATUS_OPTIONS[key], label].filter((value) => Boolean(value)).map((value) => value.trim().toLowerCase());
901
+ const match = options.find((option) => candidates.includes(optionName(option).toLowerCase()));
902
+ if (!match)
903
+ return null;
904
+ return String(match.id ?? match.name);
905
+ }
906
+ async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken, serverBaseUrl) {
907
+ const projectChoice = await promptSelect(prompts, {
908
+ message: "GitHub Projects status sync",
909
+ initialValue: "select",
910
+ options: [
911
+ { value: "select", label: "Select accessible ProjectV2" },
912
+ { value: "off", label: "Off" },
913
+ { value: "manual", label: "Enter ProjectV2 ids manually" }
914
+ ]
915
+ });
916
+ if (projectChoice === "off")
917
+ return { githubProject: "off" };
918
+ if (projectChoice === "manual") {
919
+ return {
920
+ githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
921
+ githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
922
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
923
+ };
924
+ }
925
+ const owner = repoOwnerFromSlug(repoSlug);
926
+ if (!owner)
927
+ 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`." });
928
+ let activeToken = githubToken?.trim() || null;
929
+ let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken, serverBaseUrl).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
930
+ let projects = recordArray(projectsPayload, "projects");
931
+ if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
932
+ prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
933
+ const refreshedToken = refreshProjectToken();
934
+ if (refreshedToken) {
935
+ activeToken = refreshedToken;
936
+ projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken, serverBaseUrl).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
937
+ projects = recordArray(projectsPayload, "projects");
938
+ }
939
+ }
940
+ if (projects.length === 0) {
941
+ const error = typeof projectsPayload.error === "string" ? ` (${String(projectsPayload.error).replace(/\s+/g, " ").slice(0, 240)})` : "";
942
+ prompts.outro?.(`No accessible GitHub Projects were returned${error}; continuing with GitHub Projects status sync off.`);
943
+ return { githubProject: "off", ...activeToken ? { githubToken: activeToken } : {} };
944
+ }
945
+ const selectedProjectId = await promptSelect(prompts, {
946
+ message: "GitHub ProjectV2 project",
947
+ options: [
948
+ ...projects.map((project) => ({
949
+ value: String(project.id),
950
+ label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
951
+ hint: typeof project.url === "string" ? project.url : undefined
952
+ })),
953
+ { value: "manual", label: "Enter ProjectV2 id manually" }
954
+ ]
955
+ });
956
+ const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
957
+ const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken, serverBaseUrl).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
958
+ const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
959
+ const rawField = fieldPayloadRecord.field;
960
+ const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
961
+ const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
962
+ const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
963
+ if (options.length === 0) {
964
+ return {
965
+ githubProject: projectId,
966
+ githubProjectStatusField: fieldId,
967
+ githubProjectStatuses: await promptManualProjectStatusMapping(prompts),
968
+ ...activeToken ? { githubToken: activeToken } : {}
969
+ };
970
+ }
971
+ const statuses = {};
972
+ for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
973
+ const auto = autoProjectStatusValue(options, key, label);
974
+ statuses[key] = auto ?? await promptSelect(prompts, {
975
+ message: `Project status option for ${label}`,
976
+ options: options.map((option) => ({ value: String(option.id ?? option.name), label: optionName(option) }))
977
+ });
978
+ }
979
+ return {
980
+ githubProject: projectId,
981
+ githubProjectStatusField: fieldId,
982
+ githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined,
983
+ ...activeToken ? { githubToken: activeToken } : {}
984
+ };
985
+ }
986
+ function sleep(ms) {
987
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
988
+ }
989
+ function positiveIntFromEnv(name, fallback) {
990
+ const value = Number.parseInt(process.env[name] ?? "", 10);
991
+ return Number.isFinite(value) && value >= 0 ? value : fallback;
992
+ }
993
+ function apiSessionTokenFrom(payload) {
994
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
995
+ return null;
996
+ const token = payload.apiSessionToken;
997
+ return typeof token === "string" && token.trim() ? token.trim() : null;
998
+ }
999
+ async function pollDeviceAuthUntilComplete(context, pollId, firstPayload) {
1000
+ if (typeof pollId !== "string" || !pollId.trim())
1001
+ return null;
1002
+ const intervalSeconds = typeof firstPayload.interval === "number" && Number.isFinite(firstPayload.interval) && firstPayload.interval > 0 ? firstPayload.interval : 5;
1003
+ const timeoutMs = positiveIntFromEnv("RIG_DEVICE_AUTH_POLL_TIMEOUT_MS", 300000);
1004
+ const intervalMs = positiveIntFromEnv("RIG_DEVICE_AUTH_POLL_INTERVAL_MS", Math.max(1000, intervalSeconds * 1000));
1005
+ const deadline = Date.now() + timeoutMs;
1006
+ let last = null;
1007
+ do {
1008
+ const payload = await requestGitHubAuthJsonInProcess(context, "/api/github/auth/device/poll", {
1009
+ method: "POST",
1010
+ headers: { "content-type": "application/json" },
1011
+ body: JSON.stringify({ pollId })
1012
+ }).catch(() => null);
1013
+ last = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : null;
1014
+ const status = typeof last?.status === "string" ? last.status : null;
1015
+ if (status === "signed-in" || status === "expired" || status === "cancelled" || status === "failed") {
1016
+ return last;
1017
+ }
1018
+ if (timeoutMs <= 0)
1019
+ return last;
1020
+ await sleep(intervalMs);
1021
+ } while (Date.now() < deadline);
1022
+ return last;
1023
+ }
1024
+ function runLocalFilesInit(context, options) {
1025
+ const projectRoot = context.projectRoot;
1026
+ ensureRigPrivateDirs(projectRoot);
1027
+ ensureGitignoreEntries(projectRoot);
1028
+ writeRepoConnection(projectRoot, { selected: "local", linkedAt: new Date().toISOString() });
1029
+ const configTsPath = resolve5(projectRoot, "rig.config.ts");
1030
+ const configExists = existsSync4(configTsPath) || existsSync4(resolve5(projectRoot, "rig.config.json"));
1031
+ if (configExists && !options.repair) {
1032
+ if (context.outputMode !== "json")
1033
+ console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
1034
+ } else {
1035
+ const projectName = basename(projectRoot) || "rig-project";
1036
+ writeFileSync2(configTsPath, buildRigInitConfigSource({
1037
+ projectName,
1038
+ taskSource: { kind: "files", path: "tasks" },
1039
+ useStandardPlugin: true
1040
+ }), "utf-8");
1041
+ }
1042
+ ensureRigConfigPackageDependencies(projectRoot);
1043
+ const tasksDir = resolve5(projectRoot, "tasks");
1044
+ if (!existsSync4(tasksDir)) {
1045
+ mkdirSync2(tasksDir, { recursive: true });
1046
+ writeFileSync2(resolve5(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)}
1047
+ `, "utf-8");
1048
+ }
1049
+ if (context.outputMode !== "json") {
1050
+ console.log("Initialized a local files-source Rig project (no GitHub).");
1051
+ console.log(" tasks live in tasks/*.json \xB7 server: local");
1052
+ console.log("Next: `rig task list`, then `rig task run T-1`.");
1053
+ console.log("To wire GitHub later: `rig init --repair --repo owner/repo --github-auth gh`.");
1054
+ }
1055
+ return { ok: true, group: "init", command: "init", details: { mode: "local-files", projectRoot, taskSourcePath: "tasks" } };
1056
+ }
1057
+ var DEMO_TASKS_RELATIVE_DIR = ".rig/demo-tasks";
1058
+ var DEMO_TASKS = [
1059
+ {
1060
+ id: "demo-1",
1061
+ title: "Add a hello CLI script",
1062
+ body: [
1063
+ "Create `scripts/hello.ts` that prints `Hello from Rig!` plus the current date,",
1064
+ "and add a `hello` script entry to package.json that runs it with bun.",
1065
+ "Keep it dependency-free."
1066
+ ].join(`
1067
+ `),
1068
+ status: "ready",
1069
+ labels: ["demo"]
1070
+ },
1071
+ {
1072
+ id: "demo-2",
1073
+ title: "Write a README section about this project",
1074
+ body: [
1075
+ "Add (or extend) README.md with a short `## What this is` section:",
1076
+ "two or three sentences describing the repository and how to run it.",
1077
+ "Plain prose, no badges."
1078
+ ].join(`
1079
+ `),
1080
+ status: "ready",
1081
+ labels: ["demo"]
1082
+ },
1083
+ {
1084
+ id: "demo-3",
1085
+ title: "Add a unit test for the hello script",
1086
+ body: [
1087
+ "Add `scripts/hello.test.ts` with a bun test that imports the greeting",
1088
+ "helper from the hello script and asserts it contains `Hello from Rig!`.",
1089
+ "Refactor the script to export that helper if needed."
1090
+ ].join(`
1091
+ `),
1092
+ status: "ready",
1093
+ labels: ["demo"]
1094
+ }
1095
+ ];
1096
+ function runDemoInit(context, options) {
1097
+ const projectRoot = context.projectRoot;
1098
+ ensureRigPrivateDirs(projectRoot);
1099
+ ensureGitignoreEntries(projectRoot);
1100
+ writeRepoConnection(projectRoot, { selected: "local", linkedAt: new Date().toISOString() });
1101
+ const configTsPath = resolve5(projectRoot, "rig.config.ts");
1102
+ const configExists = existsSync4(configTsPath) || existsSync4(resolve5(projectRoot, "rig.config.json"));
1103
+ let configWritten = false;
1104
+ if (configExists && !options.repair) {
1105
+ if (context.outputMode !== "json") {
1106
+ console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it for the demo.");
1107
+ }
1108
+ } else {
1109
+ writeFileSync2(configTsPath, buildRigInitConfigSource({
1110
+ projectName: basename(projectRoot) || "rig-demo",
1111
+ taskSource: { kind: "files", path: DEMO_TASKS_RELATIVE_DIR },
1112
+ useStandardPlugin: true
1113
+ }), "utf-8");
1114
+ configWritten = true;
1115
+ }
1116
+ ensureRigConfigPackageDependencies(projectRoot);
1117
+ const demoTasksDir = resolve5(projectRoot, DEMO_TASKS_RELATIVE_DIR);
1118
+ mkdirSync2(demoTasksDir, { recursive: true });
1119
+ const taskIds = [];
1120
+ for (const task of DEMO_TASKS) {
1121
+ const id = String(task.id);
1122
+ taskIds.push(id);
1123
+ const taskPath = resolve5(demoTasksDir, `${id}.json`);
1124
+ if (!existsSync4(taskPath)) {
1125
+ writeFileSync2(taskPath, `${JSON.stringify(task, null, 2)}
1126
+ `, "utf-8");
1127
+ }
1128
+ }
1129
+ if (context.outputMode !== "json") {
1130
+ console.log(`Demo Rig project ready (offline, no GitHub).`);
1131
+ console.log(` config: rig.config.ts (files task source -> ${DEMO_TASKS_RELATIVE_DIR}/)`);
1132
+ console.log(` tasks: ${taskIds.join(", ")}`);
1133
+ console.log("Next steps:");
1134
+ console.log(" 1. run bare `rig`");
1135
+ console.log(" 2. use the OMP cockpit Start screen");
1136
+ }
1137
+ return {
1138
+ ok: true,
1139
+ group: "init",
1140
+ command: "init",
1141
+ details: {
1142
+ mode: "demo",
1143
+ projectRoot,
1144
+ taskSourcePath: DEMO_TASKS_RELATIVE_DIR,
1145
+ demoTasksDir,
1146
+ tasks: taskIds,
1147
+ configWritten
1148
+ }
1149
+ };
1150
+ }
1151
+ async function runControlPlaneInit(context, options) {
1152
+ const projectRoot = context.projectRoot;
1153
+ const existingRepoConnection = readRepoConnection(projectRoot);
1154
+ const detectedSlug = options.repoSlug ?? existingRepoConnection?.project ?? detectOriginRepoSlug(projectRoot);
1155
+ if (!detectedSlug) {
1156
+ const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
1157
+ if (authMethod2 === "skip") {
1158
+ return runLocalFilesInit(context, options);
1159
+ }
1160
+ throw new CliError("Could not detect GitHub repo slug from origin. Current UX starts from bare `rig`; pass --repo owner/repo or use `rig init --yes --github-auth skip` for a local files-source project without GitHub.", 1);
1161
+ }
1162
+ const repo = parseRepoSlug(detectedSlug);
1163
+ const placement = options.server ?? "local";
1164
+ let connectionAlias = "local";
1165
+ if (placement === "remote") {
1166
+ const host = (options.remoteHost ?? "").trim();
1167
+ const alias = (options.remoteAlias ?? host).trim();
1168
+ if (!host || !alias) {
1169
+ throw new CliError("Remote placement needs --remote-host <host>.", 1, {
1170
+ hint: "rig init --server remote --remote-host rig.example.com [--remote-alias prod] [--remote-port 22] [--remote-checkout /srv/repo]"
1171
+ });
1172
+ }
1173
+ upsertManagedRemoteEndpoint({ alias, host, port: options.remotePort ?? 22 }, undefined, projectRoot);
1174
+ connectionAlias = alias;
1175
+ }
1176
+ const remoteCheckoutPath = placement === "remote" ? options.remoteCheckout?.trim() || null : null;
1177
+ writeRepoConnection(projectRoot, {
1178
+ selected: connectionAlias,
1179
+ project: repo.slug,
1180
+ linkedAt: new Date().toISOString(),
1181
+ ...remoteCheckoutPath ? { serverProjectRoot: remoteCheckoutPath, serverProjectRootAlias: connectionAlias } : {}
1182
+ });
1183
+ ensureRigPrivateDirs(projectRoot);
1184
+ ensureGitignoreEntries(projectRoot);
1185
+ const configTsPath = resolve5(projectRoot, "rig.config.ts");
1186
+ const configJsonPath = resolve5(projectRoot, "rig.config.json");
1187
+ const configExists = existsSync4(configTsPath) || existsSync4(configJsonPath);
1188
+ if (!options.privateStateOnly) {
1189
+ if (configExists && !options.repair) {
1190
+ if (context.outputMode !== "json")
1191
+ console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
1192
+ } else {
1193
+ const source = applyGitHubProjectConfig(buildRigInitConfigSource({
1194
+ projectName: repo.slug,
1195
+ projectRepo: repo.slug,
1196
+ taskSource: { kind: "github-issues", owner: repo.owner, repo: repo.repo },
1197
+ useStandardPlugin: true
1198
+ }), options);
1199
+ writeFileSync2(configTsPath, source, "utf-8");
1200
+ }
1201
+ ensureRigConfigPackageDependencies(projectRoot);
1202
+ }
1203
+ writeFileSync2(resolve5(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
1204
+ `, "utf8");
1205
+ const checkout = checkoutForInit(projectRoot, options);
1206
+ let githubAuth = null;
1207
+ let deviceAuth = null;
1208
+ const authMethod = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
1209
+ const token = authMethod === "gh" && !options.githubToken ? readGhAuthToken() : options.githubToken?.trim();
1210
+ if (token) {
1211
+ githubAuth = await postGitHubTokenInProcess(context, token, { selectedRepo: repo.slug });
1212
+ const apiSessionToken = apiSessionTokenFrom(githubAuth);
1213
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
1214
+ } else if (authMethod === "device") {
1215
+ const payload = await requestGitHubAuthJsonInProcess(context, "/api/github/auth/device/start", {
1216
+ method: "POST",
1217
+ headers: { "content-type": "application/json" },
1218
+ body: JSON.stringify({ repoSlug: repo.slug })
1219
+ });
1220
+ deviceAuth = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
1221
+ if (context.outputMode !== "json") {
1222
+ const verificationUri = String(deviceAuth.verificationUri ?? deviceAuth.verification_uri ?? deviceAuth.verification_uri_complete ?? "the verification URL returned by GitHub");
1223
+ const userCode = String(deviceAuth.userCode ?? deviceAuth.user_code ?? "the returned user code");
1224
+ console.log(`GitHub device flow: open ${verificationUri} and enter ${userCode}. Waiting for authorization...`);
1225
+ }
1226
+ const completed = await pollDeviceAuthUntilComplete(context, deviceAuth.pollId, deviceAuth);
1227
+ if (completed) {
1228
+ const apiSessionToken = apiSessionTokenFrom(completed);
1229
+ if (apiSessionToken) {
1230
+ setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
1231
+ }
1232
+ deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
1233
+ }
1234
+ }
1235
+ const labelSetup = {
1236
+ ok: false,
1237
+ ready: false,
1238
+ labelsReady: false,
1239
+ skipped: true,
1240
+ reason: "retired-server-label-bootstrap"
1241
+ };
1242
+ const pi = await ensurePiRigInstalled({ projectRoot, homeDir: process.env.RIG_PI_HOME_DIR }).catch((error) => ({
1243
+ pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
1244
+ piRig: { ok: false, label: "pi-rig global extension", hint: "Local pi-rig installation failed." },
1245
+ extensionPath: null,
1246
+ installedPath: null
1247
+ }));
1248
+ const doctor = await runRigDoctorChecks({ projectRoot }).then((checks) => ({
1249
+ ok: countDoctorFailures(checks) === 0,
1250
+ failures: countDoctorFailures(checks),
1251
+ checks
1252
+ }));
1253
+ const details = {
1254
+ repoSlug: repo.slug,
1255
+ server: placement,
1256
+ connection: connectionAlias,
1257
+ githubProject: options.githubProject ?? "off",
1258
+ checkout,
1259
+ githubAuth,
1260
+ deviceAuth,
1261
+ labelSetup,
1262
+ pi,
1263
+ doctor
1264
+ };
1265
+ if (context.outputMode === "text")
1266
+ console.log(`Initialized Rig control-plane project ${repo.slug} (${placement} placement). Next: rig doctor && rig task list`);
1267
+ return { ok: true, group: "init", command: "init", details };
1268
+ }
1269
+ function parseInitOptions(args) {
1270
+ let rest = [...args];
1271
+ const demo = takeFlag(rest, "--demo");
1272
+ rest = demo.rest;
1273
+ const yes = takeFlag(rest, "--yes");
1274
+ rest = yes.rest;
1275
+ const repair = takeFlag(rest, "--repair");
1276
+ rest = repair.rest;
1277
+ const privateStateOnly = takeFlag(rest, "--private-state-only");
1278
+ rest = privateStateOnly.rest;
1279
+ const repoSlug = takeOption(rest, "--repo");
1280
+ rest = repoSlug.rest;
1281
+ const githubToken = takeOption(rest, "--github-token");
1282
+ rest = githubToken.rest;
1283
+ const githubProject = takeOption(rest, "--github-project");
1284
+ rest = githubProject.rest;
1285
+ const githubProjectStatusField = takeOption(rest, "--github-project-status-field");
1286
+ rest = githubProjectStatusField.rest;
1287
+ const githubAuth = takeOption(rest, "--github-auth");
1288
+ rest = githubAuth.rest;
1289
+ const server = takeOption(rest, "--server");
1290
+ rest = server.rest;
1291
+ const remoteHost = takeOption(rest, "--remote-host");
1292
+ rest = remoteHost.rest;
1293
+ const remoteUrl = takeOption(rest, "--remote-url");
1294
+ rest = remoteUrl.rest;
1295
+ const remoteAlias = takeOption(rest, "--remote-alias");
1296
+ rest = remoteAlias.rest;
1297
+ const remotePort = takeOption(rest, "--remote-port");
1298
+ rest = remotePort.rest;
1299
+ const remoteCheckout = takeOption(rest, "--remote-checkout");
1300
+ rest = remoteCheckout.rest;
1301
+ if (server.value && !["local", "remote"].includes(server.value)) {
1302
+ throw new CliError("--server must be local or remote.", 1);
1303
+ }
1304
+ if (githubAuth.value && !["gh", "token", "device", "skip"].includes(githubAuth.value)) {
1305
+ throw new CliError("--github-auth must be gh, token, device, or skip.", 1);
1306
+ }
1307
+ const portValue = remotePort.value ? Number(remotePort.value) : undefined;
1308
+ if (portValue !== undefined && !Number.isFinite(portValue)) {
1309
+ throw new CliError("--remote-port must be a number.", 1);
1310
+ }
1311
+ const hostFromUrl = remoteUrl.value ? remoteUrl.value.replace(/^[a-z]+:\/\//i, "").replace(/[/:].*$/, "").trim() : undefined;
1312
+ const options = {
1313
+ demo: demo.value,
1314
+ yes: yes.value,
1315
+ repair: repair.value,
1316
+ privateStateOnly: privateStateOnly.value,
1317
+ repoSlug: repoSlug.value,
1318
+ githubToken: githubToken.value,
1319
+ githubAuthMethod: githubAuth.value,
1320
+ githubProject: githubProject.value,
1321
+ githubProjectStatusField: githubProjectStatusField.value,
1322
+ server: server.value === "remote" ? "remote" : server.value === "local" ? "local" : undefined,
1323
+ remoteAlias: remoteAlias.value,
1324
+ remoteHost: remoteHost.value ?? hostFromUrl,
1325
+ remotePort: portValue,
1326
+ remoteCheckout: remoteCheckout.value
1327
+ };
1328
+ return { options, rest };
1329
+ }
1330
+ async function runInteractiveControlPlaneInit(context, prompts) {
1331
+ prompts.intro?.("Initialize a Rig control-plane project");
1332
+ const projectRoot = context.projectRoot;
1333
+ const existingConfig = existsSync4(resolve5(projectRoot, "rig.config.ts")) || existsSync4(resolve5(projectRoot, "rig.config.json"));
1334
+ let repair = false;
1335
+ let privateStateOnly = false;
1336
+ if (existingConfig) {
1337
+ const action = await promptSelect(prompts, {
1338
+ message: "rig.config already exists. What should rig init do?",
1339
+ options: [
1340
+ { value: "repair", label: "Verify/repair generated config" },
1341
+ { value: "reconfigure", label: "Reconfigure project and rewrite config" },
1342
+ { value: "private-state-only", label: "Leave config unchanged; update private connection/auth state only" },
1343
+ { value: "cancel", label: "Cancel" }
1344
+ ]
1345
+ });
1346
+ if (action === "cancel") {
1347
+ prompts.cancel?.("Init cancelled.");
1348
+ return { ok: false, group: "init", command: "init", details: { cancelled: true } };
1349
+ }
1350
+ repair = action === "repair" || action === "reconfigure";
1351
+ privateStateOnly = action === "private-state-only";
1352
+ }
1353
+ const detectedRepo = detectOriginRepoSlug(projectRoot) ?? undefined;
1354
+ const repoSlug = await promptRequiredText(prompts, {
1355
+ message: "GitHub repo slug",
1356
+ placeholder: "owner/repo",
1357
+ defaultValue: detectedRepo
1358
+ });
1359
+ const placement = await promptSelect(prompts, {
1360
+ message: "Execution placement",
1361
+ initialValue: "local",
1362
+ options: [
1363
+ { value: "local", label: "Local", hint: "runs execute on this machine (tmux)" },
1364
+ { value: "remote", label: "Remote", hint: "runs execute on an SSH host" }
1365
+ ]
1366
+ });
1367
+ let remoteAlias;
1368
+ let remoteHost;
1369
+ let remotePort;
1370
+ let remoteCheckout;
1371
+ if (placement === "remote") {
1372
+ let existingServer = {};
1373
+ if (existingConfig) {
1374
+ try {
1375
+ const cfg = await loadConfig(projectRoot);
1376
+ existingServer = cfg.runtime?.server ?? {};
1377
+ } catch {}
1378
+ }
1379
+ remoteHost = await promptRequiredText(prompts, { message: "Remote host (ssh)", placeholder: "rig.example.com", ...existingServer.sshTarget ? { defaultValue: existingServer.sshTarget } : {} });
1380
+ remoteAlias = await promptOptionalText(prompts, { message: "Alias for this remote", placeholder: "prod", initialValue: remoteHost }) || remoteHost;
1381
+ const portText = await promptOptionalText(prompts, { message: "SSH port", placeholder: "22", initialValue: "22" });
1382
+ remotePort = portText && Number.isFinite(Number(portText)) ? Number(portText) : 22;
1383
+ remoteCheckout = await promptOptionalText(prompts, { message: "Remote working directory (existing checkout path)", placeholder: "/srv/rig/<repo>", ...existingServer.checkout ? { defaultValue: existingServer.checkout } : {} }) || undefined;
1384
+ }
1385
+ const detectedGhLogin = detectGhLogin();
1386
+ const authMethod = await promptSelect(prompts, {
1387
+ message: `GitHub auth method${detectedGhLogin ? ` (detected gh login: ${detectedGhLogin})` : ""}`,
1388
+ options: [
1389
+ { value: "gh", label: "Import token from gh auth token", hint: "recommended for local" },
1390
+ { value: "device", label: "Start GitHub device flow" },
1391
+ { value: "token", label: "Paste token" },
1392
+ { value: "skip", label: "Skip for now" }
1393
+ ]
1394
+ });
1395
+ const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
1396
+ const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
1397
+ const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
1398
+ const result = await runControlPlaneInit(context, {
1399
+ repoSlug,
1400
+ githubToken: effectiveGithubToken,
1401
+ githubAuthMethod: authMethod,
1402
+ githubProject: projectConfig.githubProject,
1403
+ githubProjectStatusField: projectConfig.githubProjectStatusField,
1404
+ githubProjectStatuses: projectConfig.githubProjectStatuses,
1405
+ repair,
1406
+ privateStateOnly,
1407
+ server: placement,
1408
+ ...remoteAlias ? { remoteAlias } : {},
1409
+ ...remoteHost ? { remoteHost } : {},
1410
+ ...remotePort !== undefined ? { remotePort } : {},
1411
+ ...remoteCheckout ? { remoteCheckout } : {}
1412
+ });
1413
+ const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
1414
+ const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
1415
+ const deviceMessage = deviceAuth ? ` GitHub device flow: open ${String(deviceAuth.verificationUri ?? deviceAuth.verification_uri ?? deviceAuth.verification_uri_complete ?? "the verification URL returned by the server")} and enter ${String(deviceAuth.userCode ?? deviceAuth.user_code ?? "the returned user code")}.` : "";
1416
+ prompts.outro?.(`Rig project initialized (${placement} placement).${deviceMessage} Next: rig doctor && rig task list`);
1417
+ return result;
1418
+ }
1419
+ async function executeInit(context, args) {
1420
+ const parsed = parseInitOptions(args);
1421
+ if (parsed.options.demo) {
1422
+ if (parsed.rest.length > 0) {
1423
+ throw new CliError(`Unexpected arguments: ${parsed.rest.join(" ")}
1424
+ Usage: rig init --demo [--yes] [--repair]`, 1, { hint: "Run `rig init --demo` (optionally with --repair to rewrite an existing rig.config)." });
1425
+ }
1426
+ return runDemoInit(context, parsed.options);
1427
+ }
1428
+ if (parsed.options.yes || parsed.options.repoSlug || parsed.options.githubToken || parsed.options.privateStateOnly || parsed.options.repair || parsed.options.githubAuthMethod || parsed.options.server || parsed.options.remoteHost) {
1429
+ if (parsed.rest.length > 0)
1430
+ throw new CliError(`Unexpected arguments: ${parsed.rest.join(" ")}
1431
+ Usage: rig init [--demo] [--repo owner/repo] [--server local|remote] [--remote-host <host>] [--remote-alias <a>] [--remote-port <n>] [--remote-checkout <path>] [--github-auth gh|token|device|skip] [--github-token <token>] [--github-project off|<project-id>] [--yes]`, 1);
1432
+ return runControlPlaneInit(context, parsed.options);
1433
+ }
1434
+ if (parsed.rest.length > 0)
1435
+ throw new CliError(`Unexpected arguments: ${parsed.rest.join(" ")}
1436
+ Usage: rig init`, 1);
1437
+ if (!process.stdin.isTTY) {
1438
+ throw new CliError("rig init needs an interactive terminal. For scripts, pass flags: rig init --yes --repo owner/repo [--server local|remote --remote-host <host>] [--github-auth gh|token|device|skip].", 1);
1439
+ }
1440
+ return runInteractiveControlPlaneInit(context, await loadClackPrompts());
1441
+ }
1442
+ export {
1443
+ runInteractiveControlPlaneInit,
1444
+ runDemoInit,
1445
+ executeInit,
1446
+ buildRigInitConfigSource,
1447
+ DEMO_TASKS_RELATIVE_DIR,
1448
+ DEMO_TASKS
1449
+ };