@h-rig/cli 0.0.6-alpha.90 → 0.0.6-alpha.92
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.
- package/README.md +18 -19
- package/dist/bin/build-rig-binaries.js +22 -10
- package/dist/bin/rig.d.ts +71 -1
- package/dist/bin/rig.js +15078 -11169
- package/dist/config/rig-default-config.yml +5 -0
- package/dist/src/app/drone-ui.d.ts +11 -14
- package/dist/src/app/drone-ui.js +70 -86
- package/dist/src/commands/_async-ui.d.ts +1 -4
- package/dist/src/commands/_async-ui.js +9 -111
- package/dist/src/commands/_cli-format.d.ts +16 -9
- package/dist/src/commands/_cli-format.js +167 -295
- package/dist/src/commands/_connection-state.d.ts +11 -1
- package/dist/src/commands/_connection-state.js +50 -5
- package/dist/src/commands/_doctor-checks.d.ts +0 -6
- package/dist/src/commands/_doctor-checks.js +79 -382
- package/dist/src/commands/_help-catalog.d.ts +1 -1
- package/dist/src/commands/_help-catalog.js +217 -157
- package/dist/src/commands/_inprocess-services.d.ts +33 -0
- package/dist/src/commands/_inprocess-services.js +102 -0
- package/dist/src/commands/_json-output.js +4 -0
- package/dist/src/commands/_lazy-reconcile.d.ts +34 -0
- package/dist/src/commands/_lazy-reconcile.js +102 -0
- package/dist/src/commands/_paths.js +1 -1
- package/dist/src/commands/_pi-frontend.d.ts +18 -10
- package/dist/src/commands/_pi-frontend.js +37 -715
- package/dist/src/commands/_pi-install.js +18 -36
- package/dist/src/commands/_policy.d.ts +1 -1
- package/dist/src/commands/_policy.js +56 -15
- package/dist/src/commands/_run-bridge.d.ts +114 -0
- package/dist/src/commands/_run-bridge.js +387 -0
- package/dist/src/commands/_run-diagnostics.d.ts +9 -0
- package/dist/src/commands/_run-diagnostics.js +51 -0
- package/dist/src/commands/_run-driver-helpers.d.ts +8 -81
- package/dist/src/commands/_run-driver-helpers.js +79 -283
- package/dist/src/commands/_run-projection.d.ts +50 -0
- package/dist/src/commands/_run-projection.js +349 -0
- package/dist/src/commands/_run-subcommands.d.ts +3 -0
- package/dist/src/commands/_run-subcommands.js +31 -0
- package/dist/src/commands/_spinner.js +1 -1
- package/dist/src/commands/agent.d.ts +1 -1
- package/dist/src/commands/agent.js +8559 -239
- package/dist/src/commands/dist.d.ts +1 -1
- package/dist/src/commands/dist.js +27 -19
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +93 -475
- package/dist/src/commands/github.d.ts +1 -1
- package/dist/src/commands/github.js +113 -387
- package/dist/src/commands/inbox.d.ts +22 -24
- package/dist/src/commands/inbox.js +420 -691
- package/dist/src/commands/init.d.ts +6 -16
- package/dist/src/commands/init.js +334 -971
- package/dist/src/commands/inspect.d.ts +19 -2
- package/dist/src/commands/inspect.js +644 -610
- package/dist/src/commands/pi.d.ts +1 -1
- package/dist/src/commands/plugin.d.ts +1 -1
- package/dist/src/commands/plugin.js +486 -7
- package/dist/src/commands/profile-and-review.d.ts +1 -1
- package/dist/src/commands/profile-and-review.js +94 -56
- package/dist/src/commands/queue.js +1 -21
- package/dist/src/commands/remote.d.ts +1 -1
- package/dist/src/commands/remote.js +837 -14
- package/dist/src/commands/repo-git-harness.d.ts +1 -1
- package/dist/src/commands/repo-git-harness.js +57 -14
- package/dist/src/commands/run.d.ts +20 -2
- package/dist/src/commands/run.js +17579 -1759
- package/dist/src/commands/server.d.ts +2 -6
- package/dist/src/commands/server.js +141 -723
- package/dist/src/commands/setup.d.ts +1 -1
- package/dist/src/commands/setup.js +102 -484
- package/dist/src/commands/stats.d.ts +13 -10
- package/dist/src/commands/stats.js +689 -761
- package/dist/src/commands/task-run-driver.d.ts +50 -88
- package/dist/src/commands/task-run-driver.js +116 -2717
- package/dist/src/commands/task.d.ts +34 -13
- package/dist/src/commands/task.js +668 -2523
- package/dist/src/commands/test.d.ts +1 -1
- package/dist/src/commands/triage.d.ts +11 -0
- package/dist/src/commands/triage.js +227 -0
- package/dist/src/commands/workspace.d.ts +1 -1
- package/dist/src/commands.d.ts +0 -16
- package/dist/src/commands.js +16657 -12250
- package/dist/src/index.js +16528 -12497
- package/dist/src/launcher.js +4 -0
- package/dist/src/operator-cli.d.ts +2 -0
- package/dist/src/operator-cli.js +17837 -0
- package/dist/src/operator-entry.d.ts +1 -0
- package/dist/src/operator-entry.js +3 -0
- package/package.json +18 -12
- package/dist/src/app/board.d.ts +0 -23
- package/dist/src/app/board.js +0 -1786
- package/dist/src/app/theme.d.ts +0 -47
- package/dist/src/app/theme.js +0 -150
- package/dist/src/commands/_authority-runs.d.ts +0 -22
- package/dist/src/commands/_authority-runs.js +0 -110
- package/dist/src/commands/_operator-surface.d.ts +0 -34
- package/dist/src/commands/_operator-surface.js +0 -220
- package/dist/src/commands/_operator-view.d.ts +0 -30
- package/dist/src/commands/_operator-view.js +0 -1070
- package/dist/src/commands/_preflight.d.ts +0 -22
- package/dist/src/commands/_preflight.js +0 -540
- package/dist/src/commands/_run-replay.d.ts +0 -24
- package/dist/src/commands/_run-replay.js +0 -142
- package/dist/src/commands/_server-client.d.ts +0 -186
- package/dist/src/commands/_server-client.js +0 -681
- package/dist/src/commands/_snapshot-upload.d.ts +0 -39
- package/dist/src/commands/_snapshot-upload.js +0 -455
- package/dist/src/commands/_task-picker.d.ts +0 -9
- package/dist/src/commands/_task-picker.js +0 -201
- package/dist/src/commands/browser.d.ts +0 -65
- package/dist/src/commands/browser.js +0 -1173
- package/dist/src/commands/connect.d.ts +0 -7
- package/dist/src/commands/connect.js +0 -419
- package/dist/src/commands/inspector.d.ts +0 -3
- package/dist/src/commands/inspector.js +0 -263
- package/dist/src/commands/task-report-bug.d.ts +0 -19
- package/dist/src/commands/task-report-bug.js +0 -1281
- package/dist/src/report-bug.d.ts +0 -44
- package/dist/src/report-bug.js +0 -260
|
@@ -16,81 +16,6 @@ var __export = (target, all) => {
|
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
17
|
var __require = import.meta.require;
|
|
18
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
19
|
// packages/cli/src/commands/_spinner.ts
|
|
95
20
|
function createTtySpinner(input) {
|
|
96
21
|
const output = input.output ?? process.stdout;
|
|
@@ -121,7 +46,7 @@ function createTtySpinner(input) {
|
|
|
121
46
|
output.write("\r\x1B[2K");
|
|
122
47
|
};
|
|
123
48
|
render();
|
|
124
|
-
const timer = isTty ? setInterval(render, input.intervalMs ??
|
|
49
|
+
const timer = isTty ? setInterval(render, input.intervalMs ?? 16) : null;
|
|
125
50
|
return {
|
|
126
51
|
setLabel(next) {
|
|
127
52
|
label = next;
|
|
@@ -171,7 +96,28 @@ __export(exports_drone_ui, {
|
|
|
171
96
|
droneConfirm: () => droneConfirm,
|
|
172
97
|
droneCancel: () => droneCancel
|
|
173
98
|
});
|
|
174
|
-
import {
|
|
99
|
+
import { Input, matchesKey, ProcessTerminal, SelectList, Text, TUI } from "@oh-my-pi/pi-tui";
|
|
100
|
+
function hexToRgb(hex) {
|
|
101
|
+
const value = hex.charCodeAt(0) === 35 ? hex.slice(1) : hex;
|
|
102
|
+
return [
|
|
103
|
+
Number.parseInt(value.slice(0, 2), 16),
|
|
104
|
+
Number.parseInt(value.slice(2, 4), 16),
|
|
105
|
+
Number.parseInt(value.slice(4, 6), 16)
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
function fg(hex) {
|
|
109
|
+
const [r, g, b] = hexToRgb(hex);
|
|
110
|
+
return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
|
|
111
|
+
}
|
|
112
|
+
function bold(text) {
|
|
113
|
+
return `\x1B[1m${text}\x1B[22m`;
|
|
114
|
+
}
|
|
115
|
+
function renderMicroDroneFrame(frame) {
|
|
116
|
+
const tick = Math.max(0, MICRO_DRONE_FRAMES.indexOf(frame));
|
|
117
|
+
const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
|
|
118
|
+
const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
|
|
119
|
+
return `${ink4("(")}${cyan(blade)}${ink4(")")}${bold(accent(eye))}${ink4("(")}${cyan(blade)}${ink4(")")}`;
|
|
120
|
+
}
|
|
175
121
|
function hairline(width = Math.min(process.stdout.columns ?? 80, 100)) {
|
|
176
122
|
return ink4("\u2500".repeat(Math.max(10, width)));
|
|
177
123
|
}
|
|
@@ -189,9 +135,8 @@ function droneNote(message, title) {
|
|
|
189
135
|
if (title)
|
|
190
136
|
console.log(` ${accentDim("\u25C7")} ${bold(ink2(title))}`);
|
|
191
137
|
for (const line of message.split(`
|
|
192
|
-
`))
|
|
138
|
+
`))
|
|
193
139
|
console.log(` ${ink4("\u2502")} ${line}`);
|
|
194
|
-
}
|
|
195
140
|
}
|
|
196
141
|
function droneStep(text) {
|
|
197
142
|
console.log(` ${accent("\u203A")} ${ink(text)}`);
|
|
@@ -215,7 +160,7 @@ function droneSpinner() {
|
|
|
215
160
|
active = createTtySpinner({
|
|
216
161
|
label: message,
|
|
217
162
|
frames: MICRO_DRONE_FRAMES,
|
|
218
|
-
styleFrame:
|
|
163
|
+
styleFrame: renderMicroDroneFrame
|
|
219
164
|
});
|
|
220
165
|
},
|
|
221
166
|
stop(message) {
|
|
@@ -229,25 +174,23 @@ function droneSpinner() {
|
|
|
229
174
|
};
|
|
230
175
|
}
|
|
231
176
|
async function runMiniTui(build) {
|
|
232
|
-
const
|
|
233
|
-
const tui = new TUI(terminal);
|
|
177
|
+
const tui = new TUI(new ProcessTerminal);
|
|
234
178
|
let settled = false;
|
|
235
|
-
return await new Promise((
|
|
179
|
+
return await new Promise((resolve5) => {
|
|
236
180
|
const finish = (result) => {
|
|
237
181
|
if (settled)
|
|
238
182
|
return;
|
|
239
183
|
settled = true;
|
|
240
184
|
tui.stop();
|
|
241
|
-
|
|
185
|
+
resolve5(result);
|
|
242
186
|
};
|
|
243
187
|
build(tui, finish);
|
|
244
188
|
tui.start();
|
|
245
189
|
});
|
|
246
190
|
}
|
|
247
191
|
async function droneSelect(input) {
|
|
248
|
-
if (!
|
|
192
|
+
if (!process.stdout.isTTY || input.options.length === 0)
|
|
249
193
|
return input.initialValue ?? input.options[0]?.value ?? null;
|
|
250
|
-
}
|
|
251
194
|
return runMiniTui((tui, finish) => {
|
|
252
195
|
tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}`));
|
|
253
196
|
const items = input.options.map((option) => ({
|
|
@@ -255,7 +198,7 @@ async function droneSelect(input) {
|
|
|
255
198
|
label: option.label,
|
|
256
199
|
...option.hint ? { description: option.hint } : {}
|
|
257
200
|
}));
|
|
258
|
-
const list = new SelectList(items, Math.min(items.length, 12),
|
|
201
|
+
const list = new SelectList(items, Math.min(items.length, 12), DRONE_SELECT_THEME);
|
|
259
202
|
const initialIndex = input.initialValue ? items.findIndex((item) => item.value === input.initialValue) : -1;
|
|
260
203
|
if (initialIndex > 0)
|
|
261
204
|
list.setSelectedIndex(initialIndex);
|
|
@@ -274,7 +217,7 @@ async function droneSelect(input) {
|
|
|
274
217
|
});
|
|
275
218
|
}
|
|
276
219
|
async function droneText(input) {
|
|
277
|
-
if (!
|
|
220
|
+
if (!process.stdout.isTTY)
|
|
278
221
|
return input.initialValue ?? null;
|
|
279
222
|
return runMiniTui((tui, finish) => {
|
|
280
223
|
tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}${input.placeholder ? ink4(` (${input.placeholder})`) : ""}`));
|
|
@@ -306,23 +249,61 @@ async function droneConfirm(input) {
|
|
|
306
249
|
});
|
|
307
250
|
return answer === null ? null : answer === "yes";
|
|
308
251
|
}
|
|
309
|
-
var
|
|
252
|
+
var PALETTE, ink, ink2, ink3, ink4, accent, accentDim, cyan, red, yellow, MICRO_BLADES, EYE_FRAMES, MICRO_DRONE_FRAMES, DRONE_SYMBOLS, DRONE_SELECT_THEME;
|
|
310
253
|
var init_drone_ui = __esm(() => {
|
|
311
|
-
init_theme();
|
|
312
254
|
init__spinner();
|
|
313
|
-
|
|
255
|
+
PALETTE = {
|
|
256
|
+
ink: "#f2f3f6",
|
|
257
|
+
ink2: "#aeb0ba",
|
|
258
|
+
ink3: "#6c6e79",
|
|
259
|
+
ink4: "#44464f",
|
|
260
|
+
accent: "#ccff4d",
|
|
261
|
+
accentDim: "#a9d63f",
|
|
262
|
+
cyan: "#56d8ff",
|
|
263
|
+
red: "#ff5d5d",
|
|
264
|
+
yellow: "#ffd24d"
|
|
265
|
+
};
|
|
266
|
+
ink = fg(PALETTE.ink);
|
|
267
|
+
ink2 = fg(PALETTE.ink2);
|
|
268
|
+
ink3 = fg(PALETTE.ink3);
|
|
269
|
+
ink4 = fg(PALETTE.ink4);
|
|
270
|
+
accent = fg(PALETTE.accent);
|
|
271
|
+
accentDim = fg(PALETTE.accentDim);
|
|
272
|
+
cyan = fg(PALETTE.cyan);
|
|
273
|
+
red = fg(PALETTE.red);
|
|
274
|
+
yellow = fg(PALETTE.yellow);
|
|
275
|
+
MICRO_BLADES = ["---", "\\\\\\", "|||", "///"];
|
|
276
|
+
EYE_FRAMES = ["@", "o", "."];
|
|
277
|
+
MICRO_DRONE_FRAMES = Array.from({ length: 12 }, (_, index) => {
|
|
278
|
+
const blade = MICRO_BLADES[index % MICRO_BLADES.length];
|
|
279
|
+
const eye = EYE_FRAMES[Math.floor(index / 2) % EYE_FRAMES.length];
|
|
280
|
+
return `(${blade})${eye}(${blade})`;
|
|
281
|
+
});
|
|
282
|
+
DRONE_SYMBOLS = {
|
|
283
|
+
cursor: "\u2192",
|
|
284
|
+
inputCursor: "\u258F",
|
|
285
|
+
boxRound: { topLeft: "\u256D", topRight: "\u256E", bottomLeft: "\u2570", bottomRight: "\u256F", horizontal: "\u2500", vertical: "\u2502" },
|
|
286
|
+
boxSharp: { topLeft: "\u250C", topRight: "\u2510", bottomLeft: "\u2514", bottomRight: "\u2518", horizontal: "\u2500", vertical: "\u2502", teeDown: "\u252C", teeUp: "\u2534", teeLeft: "\u2524", teeRight: "\u251C", cross: "\u253C" },
|
|
287
|
+
table: { topLeft: "\u250C", topRight: "\u2510", bottomLeft: "\u2514", bottomRight: "\u2518", horizontal: "\u2500", vertical: "\u2502", teeDown: "\u252C", teeUp: "\u2534", teeLeft: "\u2524", teeRight: "\u251C", cross: "\u253C" },
|
|
288
|
+
quoteBorder: "\u2502",
|
|
289
|
+
hrChar: "\u2500",
|
|
290
|
+
spinnerFrames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"]
|
|
291
|
+
};
|
|
292
|
+
DRONE_SELECT_THEME = {
|
|
314
293
|
selectedPrefix: (text) => accent(text),
|
|
315
294
|
selectedText: (text) => bold(ink(text)),
|
|
316
295
|
description: (text) => ink3(text),
|
|
317
296
|
scrollInfo: (text) => ink4(text),
|
|
318
|
-
noMatch: (text) => ink3(text)
|
|
297
|
+
noMatch: (text) => ink3(text),
|
|
298
|
+
symbols: DRONE_SYMBOLS,
|
|
299
|
+
hovered: (text) => bold(ink(text))
|
|
319
300
|
};
|
|
320
301
|
});
|
|
321
302
|
|
|
322
303
|
// packages/cli/src/commands/init.ts
|
|
323
|
-
import { appendFileSync, existsSync as
|
|
304
|
+
import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
324
305
|
import { spawnSync } from "child_process";
|
|
325
|
-
import { basename, resolve as
|
|
306
|
+
import { basename, resolve as resolve5 } from "path";
|
|
326
307
|
|
|
327
308
|
// packages/cli/src/runner.ts
|
|
328
309
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
@@ -374,21 +355,10 @@ function takeOption(args, option) {
|
|
|
374
355
|
|
|
375
356
|
// packages/cli/src/commands/init.ts
|
|
376
357
|
import { buildRigInitConfigSource } from "@rig/core";
|
|
377
|
-
import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
|
|
378
358
|
|
|
379
359
|
// packages/cli/src/commands/_connection-state.ts
|
|
380
360
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
381
|
-
import { homedir } from "os";
|
|
382
361
|
import { dirname, resolve } from "path";
|
|
383
|
-
function resolveGlobalConnectionsPath(env = process.env) {
|
|
384
|
-
const explicit = env.RIG_CONNECTIONS_FILE?.trim();
|
|
385
|
-
if (explicit)
|
|
386
|
-
return resolve(explicit);
|
|
387
|
-
const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
|
|
388
|
-
if (stateDir)
|
|
389
|
-
return resolve(stateDir, "connections.json");
|
|
390
|
-
return resolve(homedir(), ".rig", "connections.json");
|
|
391
|
-
}
|
|
392
362
|
function resolveRepoConnectionPath(projectRoot) {
|
|
393
363
|
return resolve(projectRoot, ".rig", "state", "connection.json");
|
|
394
364
|
}
|
|
@@ -406,47 +376,6 @@ function writeJsonFile(path, value) {
|
|
|
406
376
|
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
407
377
|
`, "utf8");
|
|
408
378
|
}
|
|
409
|
-
function normalizeConnection(value) {
|
|
410
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
411
|
-
return null;
|
|
412
|
-
const record = value;
|
|
413
|
-
if (record.kind === "local")
|
|
414
|
-
return { kind: "local", mode: "auto" };
|
|
415
|
-
if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
|
|
416
|
-
const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
|
|
417
|
-
return { kind: "remote", baseUrl };
|
|
418
|
-
}
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
function readGlobalConnections(options = {}) {
|
|
422
|
-
const path = resolveGlobalConnectionsPath(options.env ?? process.env);
|
|
423
|
-
const payload = readJsonFile(path);
|
|
424
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
425
|
-
return { connections: {} };
|
|
426
|
-
}
|
|
427
|
-
const rawConnections = payload.connections;
|
|
428
|
-
const connections = {};
|
|
429
|
-
if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
|
|
430
|
-
for (const [alias, raw] of Object.entries(rawConnections)) {
|
|
431
|
-
const connection = normalizeConnection(raw);
|
|
432
|
-
if (connection)
|
|
433
|
-
connections[alias] = connection;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
return { connections };
|
|
437
|
-
}
|
|
438
|
-
function writeGlobalConnections(state, options = {}) {
|
|
439
|
-
writeJsonFile(resolveGlobalConnectionsPath(options.env ?? process.env), state);
|
|
440
|
-
}
|
|
441
|
-
function upsertGlobalConnection(alias, connection, options = {}) {
|
|
442
|
-
const cleanAlias = alias.trim();
|
|
443
|
-
if (!cleanAlias)
|
|
444
|
-
throw new CliError("Connection alias is required.", 1, { hint: "Save a server with `rig server add <alias> <url>`." });
|
|
445
|
-
const state = readGlobalConnections(options);
|
|
446
|
-
state.connections[cleanAlias] = connection;
|
|
447
|
-
writeGlobalConnections(state, options);
|
|
448
|
-
return state;
|
|
449
|
-
}
|
|
450
379
|
function readRepoConnection(projectRoot) {
|
|
451
380
|
const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
|
|
452
381
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
@@ -459,334 +388,117 @@ function readRepoConnection(projectRoot) {
|
|
|
459
388
|
selected,
|
|
460
389
|
project: typeof record.project === "string" ? record.project : undefined,
|
|
461
390
|
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
462
|
-
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
391
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined,
|
|
392
|
+
serverProjectRootAlias: typeof record.serverProjectRootAlias === "string" && record.serverProjectRootAlias.trim() ? record.serverProjectRootAlias.trim() : undefined,
|
|
393
|
+
serverProjectRootBaseUrl: typeof record.serverProjectRootBaseUrl === "string" && record.serverProjectRootBaseUrl.trim() ? record.serverProjectRootBaseUrl.trim().replace(/\/+$/, "") : undefined
|
|
463
394
|
};
|
|
464
395
|
}
|
|
465
396
|
function writeRepoConnection(projectRoot, state) {
|
|
466
397
|
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
467
398
|
}
|
|
468
|
-
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
469
|
-
const repo = readRepoConnection(projectRoot);
|
|
470
|
-
if (!repo)
|
|
471
|
-
return null;
|
|
472
|
-
if (repo.selected === "local")
|
|
473
|
-
return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
|
|
474
|
-
const global = readGlobalConnections(options);
|
|
475
|
-
const connection = global.connections[repo.selected];
|
|
476
|
-
if (!connection) {
|
|
477
|
-
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
478
|
-
}
|
|
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 });
|
|
486
|
-
}
|
|
487
399
|
|
|
488
|
-
// packages/cli/src/commands/
|
|
489
|
-
import {
|
|
400
|
+
// packages/cli/src/commands/init.ts
|
|
401
|
+
import { upsertManagedRemoteEndpoint } from "@rig/runtime/control-plane/remote";
|
|
402
|
+
|
|
403
|
+
// packages/cli/src/commands/_inprocess-services.ts
|
|
490
404
|
import { resolve as resolve2 } from "path";
|
|
491
|
-
import {
|
|
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";
|
|
492
415
|
var scopedGitHubBearerTokens = new Map;
|
|
493
|
-
var serverPhaseListener = null;
|
|
494
|
-
function reportServerPhase(label) {
|
|
495
|
-
serverPhaseListener?.(label);
|
|
496
|
-
}
|
|
497
416
|
function cleanToken(value) {
|
|
498
417
|
const trimmed = value?.trim();
|
|
499
418
|
return trimmed ? trimmed : null;
|
|
500
419
|
}
|
|
501
|
-
function
|
|
502
|
-
|
|
503
|
-
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
504
|
-
}
|
|
505
|
-
function readPrivateRemoteSessionToken(projectRoot) {
|
|
506
|
-
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
507
|
-
if (!existsSync2(path))
|
|
508
|
-
return null;
|
|
509
|
-
try {
|
|
510
|
-
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
511
|
-
return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
|
|
512
|
-
} catch {
|
|
513
|
-
return null;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
517
|
-
const scopedKey = resolve2(projectRoot);
|
|
518
|
-
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
519
|
-
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
520
|
-
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
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;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
function readLocalConnectionFallbackToken(projectRoot) {
|
|
537
|
-
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
538
|
-
}
|
|
539
|
-
async function ensureServerForCli(projectRoot) {
|
|
540
|
-
try {
|
|
541
|
-
const selected = resolveSelectedConnection(projectRoot);
|
|
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);
|
|
546
|
-
return {
|
|
547
|
-
baseUrl: selected.connection.baseUrl,
|
|
548
|
-
authToken,
|
|
549
|
-
connectionKind: "remote",
|
|
550
|
-
serverProjectRoot
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
reportServerPhase("Starting local Rig server\u2026");
|
|
554
|
-
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
555
|
-
return {
|
|
556
|
-
baseUrl: connection.baseUrl,
|
|
557
|
-
authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
|
|
558
|
-
connectionKind: "local",
|
|
559
|
-
serverProjectRoot: resolve2(projectRoot)
|
|
560
|
-
};
|
|
561
|
-
} catch (error) {
|
|
562
|
-
if (error instanceof Error) {
|
|
563
|
-
throw new CliError(error.message, 1);
|
|
564
|
-
}
|
|
565
|
-
throw error;
|
|
566
|
-
}
|
|
420
|
+
function cleanString(value) {
|
|
421
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
567
422
|
}
|
|
568
|
-
|
|
569
|
-
|
|
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
|
-
}
|
|
423
|
+
function oauthClientId() {
|
|
424
|
+
return cleanToken(process.env.RIG_GITHUB_OAUTH_CLIENT_ID);
|
|
590
425
|
}
|
|
591
|
-
function
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
merged.set("authorization", `Bearer ${authToken}`);
|
|
595
|
-
}
|
|
596
|
-
return merged;
|
|
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);
|
|
597
429
|
}
|
|
598
|
-
function
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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
|
|
610
443
|
});
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
+
scope: cleanString(body.scope) ?? undefined,
|
|
481
|
+
selectedRepo: cleanString(body.repoSlug)
|
|
619
482
|
});
|
|
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
|
-
}
|
|
655
|
-
async function requestServerJson(context, pathname, init = {}) {
|
|
656
|
-
const server = await ensureServerForCli(context.projectRoot);
|
|
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
483
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
return
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
const diagnostics = diagnosticMessage(payload);
|
|
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 });
|
|
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) });
|
|
688
493
|
}
|
|
689
|
-
|
|
494
|
+
throw new Error(`No in-process GitHub auth API for ${method} ${pathname}`);
|
|
690
495
|
}
|
|
691
|
-
async function postGitHubTokenViaServer(context, token, options = {}) {
|
|
692
|
-
const payload = await requestServerJson(context, "/api/github/auth/token", {
|
|
693
|
-
method: "POST",
|
|
694
|
-
headers: { "content-type": "application/json" },
|
|
695
|
-
body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot })
|
|
696
|
-
});
|
|
697
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
698
|
-
}
|
|
699
|
-
async function prepareRemoteCheckoutViaServer(context, input) {
|
|
700
|
-
const payload = await requestServerJson(context, `/api/projects/${encodeURIComponent(input.repoSlug)}/prepare-checkout`, {
|
|
701
|
-
method: "POST",
|
|
702
|
-
headers: { "content-type": "application/json" },
|
|
703
|
-
body: JSON.stringify({ checkout: input.checkout, repoUrl: input.repoUrl, baseDir: input.baseDir })
|
|
704
|
-
});
|
|
705
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
706
|
-
}
|
|
707
|
-
async function registerProjectViaServer(context, input) {
|
|
708
|
-
const payload = await requestServerJson(context, "/api/projects", {
|
|
709
|
-
method: "POST",
|
|
710
|
-
headers: { "content-type": "application/json" },
|
|
711
|
-
body: JSON.stringify(input)
|
|
712
|
-
});
|
|
713
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
714
|
-
}
|
|
715
|
-
function sleep(ms) {
|
|
716
|
-
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
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
|
-
}
|
|
724
|
-
async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
|
|
725
|
-
const timeoutMs = options.timeoutMs ?? 30000;
|
|
726
|
-
const pollMs = options.pollMs ?? 1000;
|
|
727
|
-
const deadline = Date.now() + timeoutMs;
|
|
728
|
-
let lastError;
|
|
729
|
-
let switched = null;
|
|
730
|
-
while (Date.now() < deadline) {
|
|
731
|
-
try {
|
|
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;
|
|
738
|
-
} catch (error) {
|
|
739
|
-
lastError = error;
|
|
740
|
-
if (!isRetryableProjectRootSwitchError(error))
|
|
741
|
-
throw error;
|
|
742
|
-
await sleep(pollMs);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
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 : {};
|
|
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
|
-
]);
|
|
780
496
|
|
|
781
497
|
// packages/cli/src/commands/_pi-install.ts
|
|
782
|
-
import { existsSync as
|
|
783
|
-
import { homedir
|
|
498
|
+
import { existsSync as existsSync2 } from "fs";
|
|
499
|
+
import { homedir } from "os";
|
|
784
500
|
import { resolve as resolve3 } from "path";
|
|
785
501
|
var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
|
|
786
|
-
var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
787
|
-
var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
|
|
788
|
-
export { default } from '@rig/pi-rig';
|
|
789
|
-
`;
|
|
790
502
|
async function defaultCommandRunner(command, options = {}) {
|
|
791
503
|
const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
|
|
792
504
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
@@ -799,19 +511,19 @@ async function defaultCommandRunner(command, options = {}) {
|
|
|
799
511
|
function resolvePiRigExtensionPath(homeDir) {
|
|
800
512
|
return resolve3(homeDir, ".pi", "agent", "extensions", "pi-rig");
|
|
801
513
|
}
|
|
802
|
-
function resolvePiRigPackageSource(projectRoot, exists =
|
|
514
|
+
function resolvePiRigPackageSource(projectRoot, exists = existsSync2) {
|
|
803
515
|
const localPackage = resolve3(projectRoot, "packages", "pi-rig");
|
|
804
516
|
if (exists(resolve3(localPackage, "package.json")))
|
|
805
517
|
return localPackage;
|
|
806
518
|
return `npm:${PI_RIG_PACKAGE_NAME}`;
|
|
807
519
|
}
|
|
808
520
|
function resolvePiHomeDir(inputHomeDir) {
|
|
809
|
-
return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ??
|
|
521
|
+
return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir();
|
|
810
522
|
}
|
|
811
523
|
function piListContainsPiRig(output) {
|
|
812
524
|
return output.split(/\r?\n/).some((line) => {
|
|
813
525
|
const normalized = line.trim();
|
|
814
|
-
return normalized.includes(PI_RIG_PACKAGE_NAME) ||
|
|
526
|
+
return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
815
527
|
});
|
|
816
528
|
}
|
|
817
529
|
async function safeRun(runner, command, options) {
|
|
@@ -835,7 +547,13 @@ async function ensurePiBinaryAvailable(input) {
|
|
|
835
547
|
}
|
|
836
548
|
return { ok: true, detail: (current.stdout || current.stderr).trim() || undefined };
|
|
837
549
|
}
|
|
838
|
-
const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim()
|
|
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
|
+
}
|
|
839
557
|
const parts = splitInstallCommand(installCommand);
|
|
840
558
|
if (parts.length === 0) {
|
|
841
559
|
return { ok: false, error: (current.stderr || current.stdout).trim() || "pi --version failed" };
|
|
@@ -852,18 +570,6 @@ async function ensurePiBinaryAvailable(input) {
|
|
|
852
570
|
...next.exitCode === 0 ? {} : { error: (next.stderr || next.stdout).trim() || "pi --version failed after install" }
|
|
853
571
|
};
|
|
854
572
|
}
|
|
855
|
-
function removeManagedLegacyPiRigBridge(homeDir, exists = existsSync3) {
|
|
856
|
-
const extensionPath = resolvePiRigExtensionPath(homeDir);
|
|
857
|
-
const indexPath = resolve3(extensionPath, "index.ts");
|
|
858
|
-
if (!exists(indexPath))
|
|
859
|
-
return;
|
|
860
|
-
try {
|
|
861
|
-
const content = readFileSync3(indexPath, "utf8");
|
|
862
|
-
if (content === LEGACY_PI_RIG_MARKER || content.includes("Managed by Rig. Source package: @rig/pi-rig")) {
|
|
863
|
-
rmSync(extensionPath, { recursive: true, force: true });
|
|
864
|
-
}
|
|
865
|
-
} catch {}
|
|
866
|
-
}
|
|
867
573
|
async function checkPiRigInstall(input = {}) {
|
|
868
574
|
const home = resolvePiHomeDir(input.homeDir);
|
|
869
575
|
const extensionPath = resolvePiRigExtensionPath(home);
|
|
@@ -874,48 +580,40 @@ async function checkPiRigInstall(input = {}) {
|
|
|
874
580
|
piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
|
|
875
581
|
};
|
|
876
582
|
}
|
|
877
|
-
const exists = input.exists ?? existsSync3;
|
|
878
583
|
const runner = input.commandRunner ?? defaultCommandRunner;
|
|
879
584
|
const piResult = await safeRun(runner, ["pi", "--version"]);
|
|
880
585
|
const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
|
|
881
|
-
const
|
|
586
|
+
const hasPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
|
|
882
587
|
${piListResult.stderr}`);
|
|
883
|
-
const
|
|
884
|
-
const hasPiRig = listedPiRig;
|
|
588
|
+
const hasLegacyBridgeScaffold = !hasPiRig && (input.exists ?? existsSync2)(extensionPath);
|
|
885
589
|
return {
|
|
886
590
|
extensionPath,
|
|
887
591
|
pi: {
|
|
888
592
|
ok: piResult.exitCode === 0,
|
|
889
593
|
label: "pi",
|
|
890
594
|
detail: (piResult.stdout || piResult.stderr).trim() || undefined,
|
|
891
|
-
hint: piResult.exitCode === 0 ? undefined : "Install Pi or
|
|
595
|
+
hint: piResult.exitCode === 0 ? undefined : "Install Pi/OMP manually or set RIG_PI_INSTALL_COMMAND before verifying with bare `rig` / Cockpit \u2192 Doctor."
|
|
892
596
|
},
|
|
893
597
|
piRig: {
|
|
894
598
|
ok: hasPiRig,
|
|
895
599
|
label: "pi-rig global extension",
|
|
896
|
-
detail: hasPiRig ? piListResult.stdout.trim() || PI_RIG_PACKAGE_NAME :
|
|
897
|
-
hint: hasPiRig ? undefined :
|
|
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.`
|
|
898
602
|
}
|
|
899
603
|
};
|
|
900
604
|
}
|
|
901
605
|
async function ensurePiRigInstalled(input) {
|
|
902
606
|
const home = resolvePiHomeDir(input.homeDir);
|
|
903
607
|
if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
|
|
904
|
-
const
|
|
905
|
-
return { ...
|
|
608
|
+
const status = await checkPiRigInstall({ homeDir: home, commandRunner: input.commandRunner });
|
|
609
|
+
return { ...status, installedPath: status.extensionPath };
|
|
906
610
|
}
|
|
907
611
|
const runner = input.commandRunner ?? defaultCommandRunner;
|
|
908
612
|
const piAvailable = await ensurePiBinaryAvailable({ runner, projectRoot: input.projectRoot });
|
|
909
|
-
const status = piAvailable.ok ? await checkPiRigInstall({ homeDir: home, commandRunner: runner }) : {
|
|
910
|
-
extensionPath: resolvePiRigExtensionPath(home),
|
|
911
|
-
pi: { ok: false, label: "pi", detail: piAvailable.error, hint: "Install/update Pi with RIG_PI_INSTALL_COMMAND or install Pi manually." },
|
|
912
|
-
piRig: { ok: false, label: "pi-rig global extension", hint: "Pi is required before pi-rig can be installed." }
|
|
913
|
-
};
|
|
914
613
|
if (!piAvailable.ok) {
|
|
915
614
|
throw new Error(`Pi install/update failed: ${piAvailable.error ?? "pi unavailable"}`);
|
|
916
615
|
}
|
|
917
616
|
const packageSource = resolvePiRigPackageSource(input.projectRoot);
|
|
918
|
-
removeManagedLegacyPiRigBridge(home);
|
|
919
617
|
const install = await runner(["pi", "install", packageSource], { cwd: input.projectRoot });
|
|
920
618
|
if (install.exitCode !== 0) {
|
|
921
619
|
throw new Error(`pi-rig install failed: ${(install.stderr || install.stdout).trim() || `exit ${install.exitCode}`}`);
|
|
@@ -923,132 +621,14 @@ async function ensurePiRigInstalled(input) {
|
|
|
923
621
|
const next = await checkPiRigInstall({ homeDir: home, commandRunner: runner });
|
|
924
622
|
return { ...next, installedPath: packageSource };
|
|
925
623
|
}
|
|
926
|
-
async function ensureRemotePiRigInstalled(input) {
|
|
927
|
-
const payload = await input.requestJson("/api/pi-rig/install", {
|
|
928
|
-
method: "POST",
|
|
929
|
-
headers: { "content-type": "application/json" },
|
|
930
|
-
body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
|
|
931
|
-
});
|
|
932
|
-
const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
933
|
-
const piOk = record.piOk === true || record.ok === true;
|
|
934
|
-
const piRigOk = record.piRigOk === true || record.installed === true || record.ok === true;
|
|
935
|
-
const extensionPath = typeof record.extensionPath === "string" ? record.extensionPath : "remote:~/.pi/agent/extensions/pi-rig";
|
|
936
|
-
return {
|
|
937
|
-
remote: true,
|
|
938
|
-
extensionPath,
|
|
939
|
-
pi: {
|
|
940
|
-
ok: piOk,
|
|
941
|
-
label: "pi",
|
|
942
|
-
detail: typeof record.piVersion === "string" ? record.piVersion : undefined,
|
|
943
|
-
hint: piOk ? undefined : "Install/update Pi on the selected remote Rig server."
|
|
944
|
-
},
|
|
945
|
-
piRig: {
|
|
946
|
-
ok: piRigOk,
|
|
947
|
-
label: "pi-rig global extension",
|
|
948
|
-
detail: extensionPath,
|
|
949
|
-
hint: piRigOk ? undefined : "Install/enable pi-rig on the selected remote Rig server."
|
|
950
|
-
}
|
|
951
|
-
};
|
|
952
|
-
}
|
|
953
624
|
async function buildPiSetupChecks(input = {}) {
|
|
954
625
|
const status = await checkPiRigInstall(input);
|
|
955
626
|
return [status.pi, status.piRig];
|
|
956
627
|
}
|
|
957
628
|
|
|
958
|
-
// packages/cli/src/commands/_snapshot-upload.ts
|
|
959
|
-
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
960
|
-
import { dirname as dirname2, resolve as resolve4, relative, sep } from "path";
|
|
961
|
-
var SNAPSHOT_ARCHIVE_VERSION = 1;
|
|
962
|
-
var SNAPSHOT_ARCHIVE_CONTENT_TYPE = "application/vnd.rig.snapshot+json";
|
|
963
|
-
var DEFAULT_EXCLUDED_DIRECTORIES = new Set([
|
|
964
|
-
".git",
|
|
965
|
-
".rig",
|
|
966
|
-
"node_modules",
|
|
967
|
-
".turbo",
|
|
968
|
-
".next",
|
|
969
|
-
".cache",
|
|
970
|
-
"coverage",
|
|
971
|
-
"dist",
|
|
972
|
-
"build",
|
|
973
|
-
"out"
|
|
974
|
-
]);
|
|
975
|
-
function toPosixPath(path) {
|
|
976
|
-
return path.split(sep).join("/");
|
|
977
|
-
}
|
|
978
|
-
function assertManifestPath(root, relativePath) {
|
|
979
|
-
if (!relativePath || relativePath.startsWith("/") || relativePath.includes("\x00")) {
|
|
980
|
-
throw new Error(`Invalid snapshot path: ${relativePath}`);
|
|
981
|
-
}
|
|
982
|
-
const resolved = resolve4(root, relativePath);
|
|
983
|
-
const relativeToRoot = relative(root, resolved);
|
|
984
|
-
if (relativeToRoot.startsWith("..") || relativeToRoot === ".." || resolve4(relativeToRoot) === resolved) {
|
|
985
|
-
throw new Error(`Snapshot path escapes project root: ${relativePath}`);
|
|
986
|
-
}
|
|
987
|
-
return resolved;
|
|
988
|
-
}
|
|
989
|
-
async function buildSnapshotUploadManifest(projectRoot, options = {}) {
|
|
990
|
-
const root = resolve4(projectRoot);
|
|
991
|
-
const excludedDirectories = [...new Set([
|
|
992
|
-
...DEFAULT_EXCLUDED_DIRECTORIES,
|
|
993
|
-
...options.excludedDirectories ?? []
|
|
994
|
-
])];
|
|
995
|
-
const excludedSet = new Set(excludedDirectories);
|
|
996
|
-
const files = [];
|
|
997
|
-
async function visit(dir) {
|
|
998
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
999
|
-
for (const entry of entries) {
|
|
1000
|
-
if (entry.isDirectory() && excludedSet.has(entry.name))
|
|
1001
|
-
continue;
|
|
1002
|
-
const fullPath = resolve4(dir, entry.name);
|
|
1003
|
-
if (entry.isDirectory()) {
|
|
1004
|
-
await visit(fullPath);
|
|
1005
|
-
continue;
|
|
1006
|
-
}
|
|
1007
|
-
if (!entry.isFile())
|
|
1008
|
-
continue;
|
|
1009
|
-
files.push(toPosixPath(relative(root, fullPath)));
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
await visit(root);
|
|
1013
|
-
files.sort();
|
|
1014
|
-
return { root, files, excludedDirectories };
|
|
1015
|
-
}
|
|
1016
|
-
async function createSnapshotUploadArchive(projectRoot, options = {}) {
|
|
1017
|
-
const manifest = await buildSnapshotUploadManifest(projectRoot, options);
|
|
1018
|
-
const files = await Promise.all(manifest.files.map(async (path) => {
|
|
1019
|
-
const fullPath = assertManifestPath(manifest.root, path);
|
|
1020
|
-
return {
|
|
1021
|
-
path,
|
|
1022
|
-
contentBase64: (await readFile(fullPath)).toString("base64")
|
|
1023
|
-
};
|
|
1024
|
-
}));
|
|
1025
|
-
return {
|
|
1026
|
-
version: SNAPSHOT_ARCHIVE_VERSION,
|
|
1027
|
-
root: manifest.root,
|
|
1028
|
-
files,
|
|
1029
|
-
excludedDirectories: manifest.excludedDirectories,
|
|
1030
|
-
createdAt: (options.now?.() ?? new Date).toISOString()
|
|
1031
|
-
};
|
|
1032
|
-
}
|
|
1033
|
-
function encodeSnapshotUploadArchive(archive) {
|
|
1034
|
-
return Buffer.from(JSON.stringify(archive), "utf8").toString("base64");
|
|
1035
|
-
}
|
|
1036
|
-
async function uploadSnapshotArchiveViaServer(context, input) {
|
|
1037
|
-
const payload = await requestServerJson(context, `/api/projects/${encodeURIComponent(input.repoSlug)}/upload-snapshot`, {
|
|
1038
|
-
method: "POST",
|
|
1039
|
-
headers: { "content-type": "application/json" },
|
|
1040
|
-
body: JSON.stringify({
|
|
1041
|
-
archiveContentBase64: encodeSnapshotUploadArchive(input.archive),
|
|
1042
|
-
contentType: SNAPSHOT_ARCHIVE_CONTENT_TYPE,
|
|
1043
|
-
baseDir: input.baseDir
|
|
1044
|
-
})
|
|
1045
|
-
});
|
|
1046
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
629
|
// packages/cli/src/commands/_doctor-checks.ts
|
|
1050
|
-
import { existsSync as
|
|
1051
|
-
import { resolve as
|
|
630
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
631
|
+
import { resolve as resolve4 } from "path";
|
|
1052
632
|
import { isSupportedBunVersion, MIN_SUPPORTED_BUN_VERSION } from "@rig/runtime/control-plane/setup-version";
|
|
1053
633
|
|
|
1054
634
|
// packages/cli/src/commands/_parsers.ts
|
|
@@ -1080,31 +660,14 @@ function isAuthenticated(payload) {
|
|
|
1080
660
|
const record = payload;
|
|
1081
661
|
return record.signedIn === true || record.authenticated === true || record.status === "authenticated" || record.ok === true && typeof record.login === "string" && record.login.trim().length > 0;
|
|
1082
662
|
}
|
|
1083
|
-
function repoSlugFromConfig(config) {
|
|
1084
|
-
const project = config?.project;
|
|
1085
|
-
if (project && typeof project === "object" && !Array.isArray(project)) {
|
|
1086
|
-
const record = project;
|
|
1087
|
-
if (typeof record.repo === "string" && /^([^/\s]+)\/([^/\s]+)$/.test(record.repo))
|
|
1088
|
-
return record.repo;
|
|
1089
|
-
if (typeof record.name === "string" && /^([^/\s]+)\/([^/\s]+)$/.test(record.name))
|
|
1090
|
-
return record.name;
|
|
1091
|
-
}
|
|
1092
|
-
const taskSource = config?.taskSource;
|
|
1093
|
-
if (taskSource && typeof taskSource === "object" && !Array.isArray(taskSource)) {
|
|
1094
|
-
const source = taskSource;
|
|
1095
|
-
if (typeof source.owner === "string" && typeof source.repo === "string")
|
|
1096
|
-
return `${source.owner}/${source.repo}`;
|
|
1097
|
-
}
|
|
1098
|
-
return null;
|
|
1099
|
-
}
|
|
1100
663
|
function loadFallbackConfig(projectRoot) {
|
|
1101
664
|
const candidates = ["rig.config.ts", "rig.config.mts", "rig.config.json"];
|
|
1102
665
|
for (const name of candidates) {
|
|
1103
|
-
const path =
|
|
1104
|
-
if (!
|
|
666
|
+
const path = resolve4(projectRoot, name);
|
|
667
|
+
if (!existsSync3(path))
|
|
1105
668
|
continue;
|
|
1106
669
|
try {
|
|
1107
|
-
const source =
|
|
670
|
+
const source = readFileSync2(path, "utf8");
|
|
1108
671
|
if (name.endsWith(".json"))
|
|
1109
672
|
return JSON.parse(source);
|
|
1110
673
|
const owner = source.match(/owner\s*:\s*["']([^"']+)["']/)?.[1];
|
|
@@ -1123,14 +686,11 @@ function loadFallbackConfig(projectRoot) {
|
|
|
1123
686
|
}
|
|
1124
687
|
return null;
|
|
1125
688
|
}
|
|
1126
|
-
function projectStatusSlug(projectRoot, config) {
|
|
1127
|
-
return readRepoConnection(projectRoot)?.project ?? repoSlugFromConfig(config);
|
|
1128
|
-
}
|
|
1129
689
|
function githubProjectsCheck(config) {
|
|
1130
690
|
const github = config?.github;
|
|
1131
691
|
const projects = github?.projects;
|
|
1132
692
|
if (!projects?.enabled) {
|
|
1133
|
-
return check("github-projects", "GitHub Projects status sync", "warn", "disabled or not configured", "
|
|
693
|
+
return check("github-projects", "GitHub Projects status sync", "warn", "disabled or not configured", "Current OMP flow does not require GitHub Projects status sync; configure github.projects manually when Project status sync should be authoritative.");
|
|
1134
694
|
}
|
|
1135
695
|
if (projects.projectId && projects.statusFieldId) {
|
|
1136
696
|
return check("github-projects", "GitHub Projects status sync", "pass", `project ${projects.projectId}`);
|
|
@@ -1155,16 +715,6 @@ function permissionAllowsPr(payload) {
|
|
|
1155
715
|
}
|
|
1156
716
|
return null;
|
|
1157
717
|
}
|
|
1158
|
-
function labelsReady(payload) {
|
|
1159
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
1160
|
-
return null;
|
|
1161
|
-
const record = payload;
|
|
1162
|
-
if (record.ok === true || record.ready === true || record.labelsReady === true)
|
|
1163
|
-
return true;
|
|
1164
|
-
if (record.ok === false || record.ready === false || record.labelsReady === false)
|
|
1165
|
-
return false;
|
|
1166
|
-
return null;
|
|
1167
|
-
}
|
|
1168
718
|
function prMergeCheck(config) {
|
|
1169
719
|
const pr = config?.pr;
|
|
1170
720
|
const merge = config?.merge;
|
|
@@ -1178,7 +728,7 @@ async function runRigDoctorChecks(options) {
|
|
|
1178
728
|
const checks = [];
|
|
1179
729
|
const which = options.which ?? ((binary) => Bun.which(binary));
|
|
1180
730
|
const bunVersion = options.bunVersion ?? Bun.version;
|
|
1181
|
-
const request = options.requestJson ?? ((pathname, init) =>
|
|
731
|
+
const request = options.requestJson ?? ((pathname, init) => requestGitHubAuthJsonInProcess({ projectRoot }, pathname, init));
|
|
1182
732
|
const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
|
|
1183
733
|
const progress = options.onProgress ?? (() => {});
|
|
1184
734
|
progress("Checking local toolchain\u2026");
|
|
@@ -1186,78 +736,26 @@ async function runRigDoctorChecks(options) {
|
|
|
1186
736
|
progress("Loading rig.config\u2026");
|
|
1187
737
|
const loadedConfig = await loadConfig(projectRoot).catch(() => null);
|
|
1188
738
|
const config = loadedConfig ?? loadFallbackConfig(projectRoot);
|
|
1189
|
-
const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) =>
|
|
1190
|
-
checks.push(config ? check("config", "rig.config loadable", "pass") : check("config", "rig.config loadable", hasConfigFile ? "fail" : "fail", hasConfigFile ? "config file exists but failed to load" : "missing rig.config.ts/json", "
|
|
739
|
+
const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync3(resolve4(projectRoot, name)));
|
|
740
|
+
checks.push(config ? check("config", "rig.config loadable", "pass") : check("config", "rig.config loadable", hasConfigFile ? "fail" : "fail", hasConfigFile ? "config file exists but failed to load" : "missing rig.config.ts/json", "Create/fix rig.config.ts or use the OMP cockpit Start tab to scaffold one."));
|
|
1191
741
|
const taskSourceKind = config?.taskSource?.kind;
|
|
1192
742
|
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."));
|
|
1193
743
|
const repo = readRepoConnection(projectRoot);
|
|
1194
|
-
checks.push(repo ? check("project-link", "repo selected
|
|
1195
|
-
const selected = (() => {
|
|
1196
|
-
try {
|
|
1197
|
-
return resolveSelectedConnection(projectRoot);
|
|
1198
|
-
} catch {
|
|
1199
|
-
return null;
|
|
1200
|
-
}
|
|
1201
|
-
})();
|
|
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));
|
|
1203
|
-
let server = null;
|
|
744
|
+
checks.push(repo ? check("project-link", "repo selected for local setup", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --repo owner/repo` if this project should use GitHub Issues.") : check("project-link", "repo selected for local setup", "warn", "missing .rig/state/connection.json", "Current UX can run without this compatibility file; `rig init` recreates it for GitHub-backed projects."));
|
|
1204
745
|
try {
|
|
1205
|
-
progress("
|
|
1206
|
-
|
|
1207
|
-
checks.push(check("
|
|
746
|
+
progress("Checking GitHub auth\u2026");
|
|
747
|
+
const auth = await request("/api/github/auth/status");
|
|
748
|
+
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>`."));
|
|
1208
749
|
} catch (error) {
|
|
1209
|
-
checks.push(check("
|
|
750
|
+
checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig."));
|
|
1210
751
|
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
}
|
|
1219
|
-
try {
|
|
1220
|
-
progress("Checking GitHub auth\u2026");
|
|
1221
|
-
const auth = await request("/api/github/auth/status");
|
|
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>`."));
|
|
1223
|
-
} catch (error) {
|
|
1224
|
-
checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
|
|
1225
|
-
}
|
|
1226
|
-
try {
|
|
1227
|
-
progress("Checking GitHub repo permissions\u2026");
|
|
1228
|
-
const permissions = await request("/api/github/repo/permissions");
|
|
1229
|
-
const allowed = permissionAllowsPr(permissions);
|
|
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."));
|
|
1231
|
-
} catch (error) {
|
|
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."));
|
|
1233
|
-
}
|
|
1234
|
-
try {
|
|
1235
|
-
progress("Checking GitHub issue labels\u2026");
|
|
1236
|
-
const labels = await request("/api/workspace/task-labels");
|
|
1237
|
-
const ready = labelsReady(labels);
|
|
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."));
|
|
1239
|
-
} catch (error) {
|
|
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."));
|
|
1241
|
-
}
|
|
1242
|
-
try {
|
|
1243
|
-
progress("Checking task projection\u2026");
|
|
1244
|
-
const projection = await request("/api/workspace/task-projection");
|
|
1245
|
-
checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
|
|
1246
|
-
} catch (error) {
|
|
1247
|
-
checks.push(check("task-projection", "task projection", "warn", errorMessage(error), "Refresh task projection with `rig task list` or fix the task source."));
|
|
1248
|
-
}
|
|
1249
|
-
const slug = projectStatusSlug(projectRoot, config);
|
|
1250
|
-
if (slug) {
|
|
1251
|
-
try {
|
|
1252
|
-
progress("Checking server project checkout\u2026");
|
|
1253
|
-
const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
|
|
1254
|
-
checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
|
|
1255
|
-
} catch (error) {
|
|
1256
|
-
checks.push(check("remote-checkout", "server project checkout", "warn", errorMessage(error), "Run `rig init --yes --repo owner/repo` to register/link the server project checkout."));
|
|
1257
|
-
}
|
|
1258
|
-
} else {
|
|
1259
|
-
checks.push(check("remote-checkout", "server project checkout", "warn", "repo slug unknown", "Set project.repo or run `rig init --repo owner/repo`."));
|
|
1260
|
-
}
|
|
752
|
+
try {
|
|
753
|
+
progress("Checking GitHub repo permissions\u2026");
|
|
754
|
+
const permissions = await request("/api/github/repo/permissions");
|
|
755
|
+
const allowed = permissionAllowsPr(permissions);
|
|
756
|
+
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."));
|
|
757
|
+
} catch (error) {
|
|
758
|
+
checks.push(check("github-repo-permissions", "GitHub repo PR permissions", "warn", errorMessage(error), "Ensure the token can open PRs."));
|
|
1261
759
|
}
|
|
1262
760
|
if (taskSourceKind === "github-issues") {
|
|
1263
761
|
checks.push(check("gh", "gh CLI fallback", which("gh") ? "pass" : "warn", which("gh") ?? undefined, "Install gh for local/dev GitHub fallback operations."));
|
|
@@ -1271,7 +769,7 @@ async function runRigDoctorChecks(options) {
|
|
|
1271
769
|
hint: errorMessage(error)
|
|
1272
770
|
}]);
|
|
1273
771
|
for (const pi of piChecks) {
|
|
1274
|
-
checks.push(check(pi.label === "pi" ? "pi" : "pi-rig", pi.label, pi.ok ? "pass" : "warn", pi.detail, pi.hint ?? (pi.ok ? undefined : "
|
|
772
|
+
checks.push(check(pi.label === "pi" ? "pi" : "pi-rig", pi.label, pi.ok ? "pass" : "warn", pi.detail, pi.hint ?? (pi.ok ? undefined : "Install Pi/OMP and the Rig extension, then use bare `rig` or Cockpit \u2192 Doctor to verify.")));
|
|
1275
773
|
}
|
|
1276
774
|
return checks;
|
|
1277
775
|
}
|
|
@@ -1281,7 +779,6 @@ function countDoctorFailures(checks) {
|
|
|
1281
779
|
|
|
1282
780
|
// packages/cli/src/commands/init.ts
|
|
1283
781
|
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
|
|
1284
|
-
var DEFAULT_REMOTE_RIG_URL = "https://where.rig-does.work";
|
|
1285
782
|
var RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
1286
783
|
"@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
|
|
1287
784
|
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
|
|
@@ -1304,20 +801,20 @@ function parseRepoSlug(value) {
|
|
|
1304
801
|
return { owner: match[1], repo: match[2], slug: `${match[1]}/${match[2]}` };
|
|
1305
802
|
}
|
|
1306
803
|
function ensureRigPrivateDirs(projectRoot) {
|
|
1307
|
-
const rigDir =
|
|
1308
|
-
mkdirSync2(
|
|
1309
|
-
mkdirSync2(
|
|
1310
|
-
mkdirSync2(
|
|
1311
|
-
mkdirSync2(
|
|
1312
|
-
mkdirSync2(
|
|
1313
|
-
const taskConfigPath =
|
|
1314
|
-
if (!
|
|
804
|
+
const rigDir = resolve5(projectRoot, ".rig");
|
|
805
|
+
mkdirSync2(resolve5(rigDir, "state"), { recursive: true });
|
|
806
|
+
mkdirSync2(resolve5(rigDir, "logs"), { recursive: true });
|
|
807
|
+
mkdirSync2(resolve5(rigDir, "runs"), { recursive: true });
|
|
808
|
+
mkdirSync2(resolve5(rigDir, "tmp"), { recursive: true });
|
|
809
|
+
mkdirSync2(resolve5(projectRoot, "artifacts"), { recursive: true });
|
|
810
|
+
const taskConfigPath = resolve5(rigDir, "task-config.json");
|
|
811
|
+
if (!existsSync4(taskConfigPath))
|
|
1315
812
|
writeFileSync2(taskConfigPath, `{}
|
|
1316
813
|
`, "utf-8");
|
|
1317
814
|
}
|
|
1318
815
|
function ensureGitignoreEntries(projectRoot) {
|
|
1319
|
-
const path =
|
|
1320
|
-
const existing =
|
|
816
|
+
const path = resolve5(projectRoot, ".gitignore");
|
|
817
|
+
const existing = existsSync4(path) ? readFileSync3(path, "utf8") : "";
|
|
1321
818
|
const entries = [".rig/state/", ".rig/logs/", ".rig/runs/", ".rig/tmp/"];
|
|
1322
819
|
const missing = entries.filter((entry) => !existing.split(/\r?\n/).includes(entry));
|
|
1323
820
|
if (missing.length === 0)
|
|
@@ -1330,14 +827,14 @@ function ensureGitignoreEntries(projectRoot) {
|
|
|
1330
827
|
`, "utf8");
|
|
1331
828
|
}
|
|
1332
829
|
function ensureRigConfigPackageDependencies(projectRoot) {
|
|
1333
|
-
const path =
|
|
1334
|
-
const existing =
|
|
830
|
+
const path = resolve5(projectRoot, "package.json");
|
|
831
|
+
const existing = existsSync4(path) ? JSON.parse(readFileSync3(path, "utf8")) : {};
|
|
1335
832
|
const devDependencies = existing.devDependencies && typeof existing.devDependencies === "object" && !Array.isArray(existing.devDependencies) ? { ...existing.devDependencies } : {};
|
|
1336
833
|
for (const [name, spec] of Object.entries(RIG_CONFIG_DEV_DEPENDENCIES)) {
|
|
1337
834
|
devDependencies[name] = spec;
|
|
1338
835
|
}
|
|
1339
836
|
const next = {
|
|
1340
|
-
...
|
|
837
|
+
...existsSync4(path) ? existing : { name: "rig-project", private: true },
|
|
1341
838
|
devDependencies
|
|
1342
839
|
};
|
|
1343
840
|
writeFileSync2(path, `${JSON.stringify(next, null, 2)}
|
|
@@ -1360,20 +857,16 @@ function applyGitHubProjectConfig(source, options) {
|
|
|
1360
857
|
].join(`
|
|
1361
858
|
`));
|
|
1362
859
|
}
|
|
1363
|
-
function checkoutForInit(projectRoot,
|
|
1364
|
-
if (
|
|
1365
|
-
return {
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
return { kind: "current-ref", path: projectRoot, ...selected.ref ? { ref: selected.ref } : {} };
|
|
1372
|
-
case "uploaded-snapshot":
|
|
1373
|
-
return { kind: "uploaded-snapshot", path: projectRoot, source: "local-working-tree" };
|
|
1374
|
-
case "existing-path":
|
|
1375
|
-
return { kind: "existing-path", path: selected.path };
|
|
860
|
+
function checkoutForInit(projectRoot, options) {
|
|
861
|
+
if (options.server === "remote") {
|
|
862
|
+
return {
|
|
863
|
+
kind: "remote",
|
|
864
|
+
strategy: "existing-path",
|
|
865
|
+
path: options.remoteCheckout?.trim() || null,
|
|
866
|
+
alias: options.remoteAlias ?? options.remoteHost ?? null
|
|
867
|
+
};
|
|
1376
868
|
}
|
|
869
|
+
return { kind: "local", path: projectRoot };
|
|
1377
870
|
}
|
|
1378
871
|
function detectGhLogin() {
|
|
1379
872
|
const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -1459,32 +952,11 @@ function recordArray(value, key) {
|
|
|
1459
952
|
const raw = value[key];
|
|
1460
953
|
return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
1461
954
|
}
|
|
1462
|
-
async function listGitHubProjectsForInit(context, owner, token) {
|
|
1463
|
-
|
|
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);
|
|
955
|
+
async function listGitHubProjectsForInit(context, owner, token, serverBaseUrl) {
|
|
956
|
+
return listGitHubProjectsInProcess(context, owner, { authToken: token, baseUrl: serverBaseUrl });
|
|
1474
957
|
}
|
|
1475
|
-
async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
|
|
1476
|
-
|
|
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);
|
|
958
|
+
async function getGitHubProjectStatusFieldForInit(context, projectId, token, serverBaseUrl) {
|
|
959
|
+
return getGitHubProjectStatusFieldInProcess(context, projectId, { authToken: token, baseUrl: serverBaseUrl });
|
|
1488
960
|
}
|
|
1489
961
|
var PROJECT_STATUS_PROMPTS = {
|
|
1490
962
|
running: "Running/In progress",
|
|
@@ -1528,7 +1000,7 @@ function autoProjectStatusValue(options, key, label) {
|
|
|
1528
1000
|
return null;
|
|
1529
1001
|
return String(match.id ?? match.name);
|
|
1530
1002
|
}
|
|
1531
|
-
async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken) {
|
|
1003
|
+
async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken, serverBaseUrl) {
|
|
1532
1004
|
const projectChoice = await promptSelect(prompts, {
|
|
1533
1005
|
message: "GitHub Projects status sync",
|
|
1534
1006
|
initialValue: "select",
|
|
@@ -1551,14 +1023,14 @@ async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken
|
|
|
1551
1023
|
if (!owner)
|
|
1552
1024
|
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
1025
|
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: [] }));
|
|
1026
|
+
let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken, serverBaseUrl).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
1555
1027
|
let projects = recordArray(projectsPayload, "projects");
|
|
1556
1028
|
if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
|
|
1557
1029
|
prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
|
|
1558
1030
|
const refreshedToken = refreshProjectToken();
|
|
1559
1031
|
if (refreshedToken) {
|
|
1560
1032
|
activeToken = refreshedToken;
|
|
1561
|
-
projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
1033
|
+
projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken, serverBaseUrl).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
1562
1034
|
projects = recordArray(projectsPayload, "projects");
|
|
1563
1035
|
}
|
|
1564
1036
|
}
|
|
@@ -1579,7 +1051,7 @@ async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken
|
|
|
1579
1051
|
]
|
|
1580
1052
|
});
|
|
1581
1053
|
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) }));
|
|
1054
|
+
const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken, serverBaseUrl).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
1583
1055
|
const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
|
|
1584
1056
|
const rawField = fieldPayloadRecord.field;
|
|
1585
1057
|
const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
|
|
@@ -1608,8 +1080,8 @@ async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken
|
|
|
1608
1080
|
...activeToken ? { githubToken: activeToken } : {}
|
|
1609
1081
|
};
|
|
1610
1082
|
}
|
|
1611
|
-
function
|
|
1612
|
-
return new Promise((
|
|
1083
|
+
function sleep(ms) {
|
|
1084
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
1613
1085
|
}
|
|
1614
1086
|
function positiveIntFromEnv(name, fallback) {
|
|
1615
1087
|
const value = Number.parseInt(process.env[name] ?? "", 10);
|
|
@@ -1621,34 +1093,6 @@ function apiSessionTokenFrom(payload) {
|
|
|
1621
1093
|
const token = payload.apiSessionToken;
|
|
1622
1094
|
return typeof token === "string" && token.trim() ? token.trim() : null;
|
|
1623
1095
|
}
|
|
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
|
-
}
|
|
1640
|
-
function writeRemoteGitHubAuthState(projectRoot, input) {
|
|
1641
|
-
writeFileSync2(resolve6(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
|
|
1642
|
-
authenticated: true,
|
|
1643
|
-
source: input.source,
|
|
1644
|
-
storedOnServer: true,
|
|
1645
|
-
selectedRepo: input.selectedRepo,
|
|
1646
|
-
...remoteGitHubAuthMetadata(input.authPayload ?? null),
|
|
1647
|
-
...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
|
|
1648
|
-
updatedAt: new Date().toISOString()
|
|
1649
|
-
}, null, 2)}
|
|
1650
|
-
`, "utf8");
|
|
1651
|
-
}
|
|
1652
1096
|
async function pollDeviceAuthUntilComplete(context, pollId, firstPayload) {
|
|
1653
1097
|
if (typeof pollId !== "string" || !pollId.trim())
|
|
1654
1098
|
return null;
|
|
@@ -1658,7 +1102,7 @@ async function pollDeviceAuthUntilComplete(context, pollId, firstPayload) {
|
|
|
1658
1102
|
const deadline = Date.now() + timeoutMs;
|
|
1659
1103
|
let last = null;
|
|
1660
1104
|
do {
|
|
1661
|
-
const payload = await
|
|
1105
|
+
const payload = await requestGitHubAuthJsonInProcess(context, "/api/github/auth/device/poll", {
|
|
1662
1106
|
method: "POST",
|
|
1663
1107
|
headers: { "content-type": "application/json" },
|
|
1664
1108
|
body: JSON.stringify({ pollId })
|
|
@@ -1670,7 +1114,7 @@ async function pollDeviceAuthUntilComplete(context, pollId, firstPayload) {
|
|
|
1670
1114
|
}
|
|
1671
1115
|
if (timeoutMs <= 0)
|
|
1672
1116
|
return last;
|
|
1673
|
-
await
|
|
1117
|
+
await sleep(intervalMs);
|
|
1674
1118
|
} while (Date.now() < deadline);
|
|
1675
1119
|
return last;
|
|
1676
1120
|
}
|
|
@@ -1679,8 +1123,8 @@ function runLocalFilesInit(context, options) {
|
|
|
1679
1123
|
ensureRigPrivateDirs(projectRoot);
|
|
1680
1124
|
ensureGitignoreEntries(projectRoot);
|
|
1681
1125
|
writeRepoConnection(projectRoot, { selected: "local", linkedAt: new Date().toISOString() });
|
|
1682
|
-
const configTsPath =
|
|
1683
|
-
const configExists =
|
|
1126
|
+
const configTsPath = resolve5(projectRoot, "rig.config.ts");
|
|
1127
|
+
const configExists = existsSync4(configTsPath) || existsSync4(resolve5(projectRoot, "rig.config.json"));
|
|
1684
1128
|
if (configExists && !options.repair) {
|
|
1685
1129
|
if (context.outputMode !== "json")
|
|
1686
1130
|
console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
|
|
@@ -1693,10 +1137,10 @@ function runLocalFilesInit(context, options) {
|
|
|
1693
1137
|
}), "utf-8");
|
|
1694
1138
|
}
|
|
1695
1139
|
ensureRigConfigPackageDependencies(projectRoot);
|
|
1696
|
-
const tasksDir =
|
|
1697
|
-
if (!
|
|
1140
|
+
const tasksDir = resolve5(projectRoot, "tasks");
|
|
1141
|
+
if (!existsSync4(tasksDir)) {
|
|
1698
1142
|
mkdirSync2(tasksDir, { recursive: true });
|
|
1699
|
-
writeFileSync2(
|
|
1143
|
+
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)}
|
|
1700
1144
|
`, "utf-8");
|
|
1701
1145
|
}
|
|
1702
1146
|
if (context.outputMode !== "json") {
|
|
@@ -1751,8 +1195,8 @@ function runDemoInit(context, options) {
|
|
|
1751
1195
|
ensureRigPrivateDirs(projectRoot);
|
|
1752
1196
|
ensureGitignoreEntries(projectRoot);
|
|
1753
1197
|
writeRepoConnection(projectRoot, { selected: "local", linkedAt: new Date().toISOString() });
|
|
1754
|
-
const configTsPath =
|
|
1755
|
-
const configExists =
|
|
1198
|
+
const configTsPath = resolve5(projectRoot, "rig.config.ts");
|
|
1199
|
+
const configExists = existsSync4(configTsPath) || existsSync4(resolve5(projectRoot, "rig.config.json"));
|
|
1756
1200
|
let configWritten = false;
|
|
1757
1201
|
if (configExists && !options.repair) {
|
|
1758
1202
|
if (context.outputMode !== "json") {
|
|
@@ -1767,14 +1211,14 @@ function runDemoInit(context, options) {
|
|
|
1767
1211
|
configWritten = true;
|
|
1768
1212
|
}
|
|
1769
1213
|
ensureRigConfigPackageDependencies(projectRoot);
|
|
1770
|
-
const demoTasksDir =
|
|
1214
|
+
const demoTasksDir = resolve5(projectRoot, DEMO_TASKS_RELATIVE_DIR);
|
|
1771
1215
|
mkdirSync2(demoTasksDir, { recursive: true });
|
|
1772
1216
|
const taskIds = [];
|
|
1773
1217
|
for (const task of DEMO_TASKS) {
|
|
1774
1218
|
const id = String(task.id);
|
|
1775
1219
|
taskIds.push(id);
|
|
1776
|
-
const taskPath =
|
|
1777
|
-
if (!
|
|
1220
|
+
const taskPath = resolve5(demoTasksDir, `${id}.json`);
|
|
1221
|
+
if (!existsSync4(taskPath)) {
|
|
1778
1222
|
writeFileSync2(taskPath, `${JSON.stringify(task, null, 2)}
|
|
1779
1223
|
`, "utf-8");
|
|
1780
1224
|
}
|
|
@@ -1784,8 +1228,8 @@ function runDemoInit(context, options) {
|
|
|
1784
1228
|
console.log(` config: rig.config.ts (files task source -> ${DEMO_TASKS_RELATIVE_DIR}/)`);
|
|
1785
1229
|
console.log(` tasks: ${taskIds.join(", ")}`);
|
|
1786
1230
|
console.log("Next steps:");
|
|
1787
|
-
console.log(" 1.
|
|
1788
|
-
console.log(" 2.
|
|
1231
|
+
console.log(" 1. run bare `rig`");
|
|
1232
|
+
console.log(" 2. use the OMP cockpit Start screen");
|
|
1789
1233
|
}
|
|
1790
1234
|
return {
|
|
1791
1235
|
ok: true,
|
|
@@ -1803,32 +1247,41 @@ function runDemoInit(context, options) {
|
|
|
1803
1247
|
}
|
|
1804
1248
|
async function runControlPlaneInit(context, options) {
|
|
1805
1249
|
const projectRoot = context.projectRoot;
|
|
1806
|
-
const
|
|
1250
|
+
const existingRepoConnection = readRepoConnection(projectRoot);
|
|
1251
|
+
const detectedSlug = options.repoSlug ?? existingRepoConnection?.project ?? detectOriginRepoSlug(projectRoot);
|
|
1807
1252
|
if (!detectedSlug) {
|
|
1808
1253
|
const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
|
|
1809
|
-
if (
|
|
1254
|
+
if (authMethod2 === "skip") {
|
|
1810
1255
|
return runLocalFilesInit(context, options);
|
|
1811
1256
|
}
|
|
1812
|
-
throw new CliError("Could not detect GitHub repo slug from origin.
|
|
1257
|
+
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);
|
|
1813
1258
|
}
|
|
1814
1259
|
const repo = parseRepoSlug(detectedSlug);
|
|
1815
|
-
const
|
|
1816
|
-
|
|
1817
|
-
if (
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1260
|
+
const placement = options.server ?? "local";
|
|
1261
|
+
let connectionAlias = "local";
|
|
1262
|
+
if (placement === "remote") {
|
|
1263
|
+
const host = (options.remoteHost ?? "").trim();
|
|
1264
|
+
const alias = (options.remoteAlias ?? host).trim();
|
|
1265
|
+
if (!host || !alias) {
|
|
1266
|
+
throw new CliError("Remote placement needs --remote-host <host>.", 1, {
|
|
1267
|
+
hint: "rig init --server remote --remote-host rig.example.com [--remote-alias prod] [--remote-port 22] [--remote-checkout /srv/repo]"
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
upsertManagedRemoteEndpoint({ alias, host, port: options.remotePort ?? 22 }, undefined, projectRoot);
|
|
1271
|
+
connectionAlias = alias;
|
|
1821
1272
|
}
|
|
1273
|
+
const remoteCheckoutPath = placement === "remote" ? options.remoteCheckout?.trim() || null : null;
|
|
1822
1274
|
writeRepoConnection(projectRoot, {
|
|
1823
1275
|
selected: connectionAlias,
|
|
1824
1276
|
project: repo.slug,
|
|
1825
|
-
linkedAt: new Date().toISOString()
|
|
1277
|
+
linkedAt: new Date().toISOString(),
|
|
1278
|
+
...remoteCheckoutPath ? { serverProjectRoot: remoteCheckoutPath, serverProjectRootAlias: connectionAlias } : {}
|
|
1826
1279
|
});
|
|
1827
1280
|
ensureRigPrivateDirs(projectRoot);
|
|
1828
1281
|
ensureGitignoreEntries(projectRoot);
|
|
1829
|
-
const configTsPath =
|
|
1830
|
-
const configJsonPath =
|
|
1831
|
-
const configExists =
|
|
1282
|
+
const configTsPath = resolve5(projectRoot, "rig.config.ts");
|
|
1283
|
+
const configJsonPath = resolve5(projectRoot, "rig.config.json");
|
|
1284
|
+
const configExists = existsSync4(configTsPath) || existsSync4(configJsonPath);
|
|
1832
1285
|
if (!options.privateStateOnly) {
|
|
1833
1286
|
if (configExists && !options.repair) {
|
|
1834
1287
|
if (context.outputMode !== "json")
|
|
@@ -1844,47 +1297,26 @@ async function runControlPlaneInit(context, options) {
|
|
|
1844
1297
|
}
|
|
1845
1298
|
ensureRigConfigPackageDependencies(projectRoot);
|
|
1846
1299
|
}
|
|
1847
|
-
writeFileSync2(
|
|
1300
|
+
writeFileSync2(resolve5(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
|
|
1848
1301
|
`, "utf8");
|
|
1849
|
-
const checkout = checkoutForInit(projectRoot,
|
|
1850
|
-
let uploadedSnapshot = null;
|
|
1851
|
-
if (serverKind === "remote" && options.remoteCheckout?.kind === "uploaded-snapshot") {
|
|
1852
|
-
const archive = await createSnapshotUploadArchive(projectRoot);
|
|
1853
|
-
uploadedSnapshot = await uploadSnapshotArchiveViaServer(context, { repoSlug: repo.slug, archive });
|
|
1854
|
-
const uploadedCheckout = uploadedSnapshot.checkout;
|
|
1855
|
-
if (uploadedCheckout && typeof uploadedCheckout === "object" && !Array.isArray(uploadedCheckout)) {
|
|
1856
|
-
Object.assign(checkout, uploadedCheckout);
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1302
|
+
const checkout = checkoutForInit(projectRoot, options);
|
|
1859
1303
|
let githubAuth = null;
|
|
1860
1304
|
let deviceAuth = null;
|
|
1861
1305
|
const authMethod = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
|
|
1862
|
-
const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${options.remoteUrl ?? "the remote Rig server"}.` : null;
|
|
1863
|
-
if (remoteGhTokenWarning && !options.yes) {
|
|
1864
|
-
throw new CliError(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
|
|
1865
|
-
}
|
|
1866
1306
|
const token = authMethod === "gh" && !options.githubToken ? readGhAuthToken() : options.githubToken?.trim();
|
|
1867
1307
|
if (token) {
|
|
1868
|
-
githubAuth = await
|
|
1308
|
+
githubAuth = await postGitHubTokenInProcess(context, token, { selectedRepo: repo.slug });
|
|
1869
1309
|
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
1870
1310
|
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
1871
|
-
if (serverKind === "remote") {
|
|
1872
|
-
writeRemoteGitHubAuthState(projectRoot, {
|
|
1873
|
-
source: authMethod === "gh" ? "gh" : "init-token",
|
|
1874
|
-
selectedRepo: repo.slug,
|
|
1875
|
-
apiSessionToken,
|
|
1876
|
-
authPayload: githubAuth
|
|
1877
|
-
});
|
|
1878
|
-
}
|
|
1879
1311
|
} else if (authMethod === "device") {
|
|
1880
|
-
const payload = await
|
|
1312
|
+
const payload = await requestGitHubAuthJsonInProcess(context, "/api/github/auth/device/start", {
|
|
1881
1313
|
method: "POST",
|
|
1882
1314
|
headers: { "content-type": "application/json" },
|
|
1883
1315
|
body: JSON.stringify({ repoSlug: repo.slug })
|
|
1884
1316
|
});
|
|
1885
1317
|
deviceAuth = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
1886
1318
|
if (context.outputMode !== "json") {
|
|
1887
|
-
const verificationUri = String(deviceAuth.verificationUri ?? deviceAuth.verification_uri ?? deviceAuth.verification_uri_complete ?? "the verification URL returned by
|
|
1319
|
+
const verificationUri = String(deviceAuth.verificationUri ?? deviceAuth.verification_uri ?? deviceAuth.verification_uri_complete ?? "the verification URL returned by GitHub");
|
|
1888
1320
|
const userCode = String(deviceAuth.userCode ?? deviceAuth.user_code ?? "the returned user code");
|
|
1889
1321
|
console.log(`GitHub device flow: open ${verificationUri} and enter ${userCode}. Waiting for authorization...`);
|
|
1890
1322
|
}
|
|
@@ -1893,50 +1325,18 @@ async function runControlPlaneInit(context, options) {
|
|
|
1893
1325
|
const apiSessionToken = apiSessionTokenFrom(completed);
|
|
1894
1326
|
if (apiSessionToken) {
|
|
1895
1327
|
setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
|
|
1896
|
-
if (serverKind === "remote") {
|
|
1897
|
-
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
|
|
1898
|
-
}
|
|
1899
1328
|
}
|
|
1900
1329
|
deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
|
|
1901
1330
|
}
|
|
1902
1331
|
}
|
|
1903
|
-
|
|
1904
|
-
if (serverKind === "remote" && options.remoteCheckout?.kind !== "uploaded-snapshot") {
|
|
1905
|
-
remoteCheckoutPreparation = await prepareRemoteCheckoutViaServer(context, {
|
|
1906
|
-
repoSlug: repo.slug,
|
|
1907
|
-
checkout,
|
|
1908
|
-
repoUrl: `https://github.com/${repo.slug}.git`
|
|
1909
|
-
});
|
|
1910
|
-
const preparedCheckout = remoteCheckoutPreparation.checkout;
|
|
1911
|
-
if (preparedCheckout && typeof preparedCheckout === "object" && !Array.isArray(preparedCheckout)) {
|
|
1912
|
-
Object.assign(checkout, preparedCheckout);
|
|
1913
|
-
}
|
|
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
|
-
}
|
|
1922
|
-
const registered = await registerProjectViaServer(context, {
|
|
1923
|
-
repoSlug: repo.slug,
|
|
1924
|
-
checkout
|
|
1925
|
-
});
|
|
1926
|
-
const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
|
|
1927
|
-
const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
|
|
1928
|
-
const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
|
|
1332
|
+
const labelSetup = {
|
|
1929
1333
|
ok: false,
|
|
1930
1334
|
ready: false,
|
|
1931
1335
|
labelsReady: false,
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
|
|
1937
|
-
piRig: { ok: false, label: "pi-rig global extension", hint: "Remote server did not complete pi-rig installation." },
|
|
1938
|
-
extensionPath: "remote:~/.pi/agent/extensions/pi-rig"
|
|
1939
|
-
})) : await ensurePiRigInstalled({ projectRoot, homeDir: process.env.RIG_PI_HOME_DIR }).catch((error) => ({
|
|
1336
|
+
skipped: true,
|
|
1337
|
+
reason: "retired-server-label-bootstrap"
|
|
1338
|
+
};
|
|
1339
|
+
const pi = await ensurePiRigInstalled({ projectRoot, homeDir: process.env.RIG_PI_HOME_DIR }).catch((error) => ({
|
|
1940
1340
|
pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
|
|
1941
1341
|
piRig: { ok: false, label: "pi-rig global extension", hint: "Local pi-rig installation failed." },
|
|
1942
1342
|
extensionPath: null,
|
|
@@ -1949,18 +1349,12 @@ async function runControlPlaneInit(context, options) {
|
|
|
1949
1349
|
}));
|
|
1950
1350
|
const details = {
|
|
1951
1351
|
repoSlug: repo.slug,
|
|
1952
|
-
server:
|
|
1352
|
+
server: placement,
|
|
1953
1353
|
connection: connectionAlias,
|
|
1954
1354
|
githubProject: options.githubProject ?? "off",
|
|
1955
1355
|
checkout,
|
|
1956
|
-
remoteCheckoutPreparation,
|
|
1957
|
-
uploadedSnapshot,
|
|
1958
|
-
projectRegistration: registered,
|
|
1959
|
-
activeProjectRegistration,
|
|
1960
|
-
serverRootSwitch,
|
|
1961
1356
|
githubAuth,
|
|
1962
1357
|
deviceAuth,
|
|
1963
|
-
githubAuthWarning: remoteGhTokenWarning,
|
|
1964
1358
|
labelSetup,
|
|
1965
1359
|
pi,
|
|
1966
1360
|
doctor
|
|
@@ -1968,7 +1362,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
1968
1362
|
if (context.outputMode === "json")
|
|
1969
1363
|
console.log(JSON.stringify(details, null, 2));
|
|
1970
1364
|
else
|
|
1971
|
-
console.log(`Initialized Rig control-plane project ${repo.slug}. Next: rig doctor && rig task list`);
|
|
1365
|
+
console.log(`Initialized Rig control-plane project ${repo.slug} (${placement} placement). Next: rig doctor && rig task list`);
|
|
1972
1366
|
return { ok: true, group: "init", command: "init", details };
|
|
1973
1367
|
}
|
|
1974
1368
|
function parseInitOptions(args) {
|
|
@@ -1981,12 +1375,6 @@ function parseInitOptions(args) {
|
|
|
1981
1375
|
rest = repair.rest;
|
|
1982
1376
|
const privateStateOnly = takeFlag(rest, "--private-state-only");
|
|
1983
1377
|
rest = privateStateOnly.rest;
|
|
1984
|
-
const server = takeOption(rest, "--server");
|
|
1985
|
-
rest = server.rest;
|
|
1986
|
-
const remoteUrl = takeOption(rest, "--remote-url");
|
|
1987
|
-
rest = remoteUrl.rest;
|
|
1988
|
-
const connectionAlias = takeOption(rest, "--connection");
|
|
1989
|
-
rest = connectionAlias.rest;
|
|
1990
1378
|
const repoSlug = takeOption(rest, "--repo");
|
|
1991
1379
|
rest = repoSlug.rest;
|
|
1992
1380
|
const githubToken = takeOption(rest, "--github-token");
|
|
@@ -1997,53 +1385,51 @@ function parseInitOptions(args) {
|
|
|
1997
1385
|
rest = githubProjectStatusField.rest;
|
|
1998
1386
|
const githubAuth = takeOption(rest, "--github-auth");
|
|
1999
1387
|
rest = githubAuth.rest;
|
|
1388
|
+
const server = takeOption(rest, "--server");
|
|
1389
|
+
rest = server.rest;
|
|
1390
|
+
const remoteHost = takeOption(rest, "--remote-host");
|
|
1391
|
+
rest = remoteHost.rest;
|
|
1392
|
+
const remoteUrl = takeOption(rest, "--remote-url");
|
|
1393
|
+
rest = remoteUrl.rest;
|
|
1394
|
+
const remoteAlias = takeOption(rest, "--remote-alias");
|
|
1395
|
+
rest = remoteAlias.rest;
|
|
1396
|
+
const remotePort = takeOption(rest, "--remote-port");
|
|
1397
|
+
rest = remotePort.rest;
|
|
2000
1398
|
const remoteCheckout = takeOption(rest, "--remote-checkout");
|
|
2001
1399
|
rest = remoteCheckout.rest;
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
1400
|
+
if (server.value && !["local", "remote"].includes(server.value)) {
|
|
1401
|
+
throw new CliError("--server must be local or remote.", 1);
|
|
1402
|
+
}
|
|
1403
|
+
if (githubAuth.value && !["gh", "token", "device", "skip"].includes(githubAuth.value)) {
|
|
1404
|
+
throw new CliError("--github-auth must be gh, token, device, or skip.", 1);
|
|
1405
|
+
}
|
|
1406
|
+
const portValue = remotePort.value ? Number(remotePort.value) : undefined;
|
|
1407
|
+
if (portValue !== undefined && !Number.isFinite(portValue)) {
|
|
1408
|
+
throw new CliError("--remote-port must be a number.", 1);
|
|
1409
|
+
}
|
|
1410
|
+
const hostFromUrl = remoteUrl.value ? remoteUrl.value.replace(/^[a-z]+:\/\//i, "").replace(/[/:].*$/, "").trim() : undefined;
|
|
2006
1411
|
const options = {
|
|
2007
1412
|
demo: demo.value,
|
|
2008
1413
|
yes: yes.value,
|
|
2009
1414
|
repair: repair.value,
|
|
2010
1415
|
privateStateOnly: privateStateOnly.value,
|
|
2011
|
-
server: server.value === "remote" ? "remote" : server.value === "local" ? "local" : undefined,
|
|
2012
|
-
remoteUrl: remoteUrl.value,
|
|
2013
|
-
connectionAlias: connectionAlias.value,
|
|
2014
1416
|
repoSlug: repoSlug.value,
|
|
2015
1417
|
githubToken: githubToken.value,
|
|
2016
1418
|
githubAuthMethod: githubAuth.value,
|
|
2017
1419
|
githubProject: githubProject.value,
|
|
2018
|
-
githubProjectStatusField: githubProjectStatusField.value
|
|
1420
|
+
githubProjectStatusField: githubProjectStatusField.value,
|
|
1421
|
+
server: server.value === "remote" ? "remote" : server.value === "local" ? "local" : undefined,
|
|
1422
|
+
remoteAlias: remoteAlias.value,
|
|
1423
|
+
remoteHost: remoteHost.value ?? hostFromUrl,
|
|
1424
|
+
remotePort: portValue,
|
|
1425
|
+
remoteCheckout: remoteCheckout.value
|
|
2019
1426
|
};
|
|
2020
|
-
if (server.value && options.server === undefined) {
|
|
2021
|
-
throw new CliError("--server must be local or remote.", 1);
|
|
2022
|
-
}
|
|
2023
|
-
if (githubAuth.value && !["gh", "token", "device", "skip"].includes(githubAuth.value)) {
|
|
2024
|
-
throw new CliError("--github-auth must be gh, token, device, or skip.", 1);
|
|
2025
|
-
}
|
|
2026
|
-
if (remoteCheckout.value) {
|
|
2027
|
-
if (remoteCheckout.value === "managed-clone")
|
|
2028
|
-
options.remoteCheckout = { kind: "managed-clone" };
|
|
2029
|
-
else if (remoteCheckout.value === "current-ref")
|
|
2030
|
-
options.remoteCheckout = { kind: "current-ref", ref: ref.value };
|
|
2031
|
-
else if (remoteCheckout.value === "uploaded-snapshot")
|
|
2032
|
-
options.remoteCheckout = { kind: "uploaded-snapshot" };
|
|
2033
|
-
else if (remoteCheckout.value === "existing-path") {
|
|
2034
|
-
if (!existingPath.value)
|
|
2035
|
-
throw new CliError("--remote-checkout existing-path requires --existing-path <path>.", 1);
|
|
2036
|
-
options.remoteCheckout = { kind: "existing-path", path: existingPath.value };
|
|
2037
|
-
} else {
|
|
2038
|
-
throw new CliError("--remote-checkout must be managed-clone, current-ref, uploaded-snapshot, or existing-path.", 1);
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
1427
|
return { options, rest };
|
|
2042
1428
|
}
|
|
2043
1429
|
async function runInteractiveControlPlaneInit(context, prompts) {
|
|
2044
1430
|
prompts.intro?.("Initialize a Rig control-plane project");
|
|
2045
1431
|
const projectRoot = context.projectRoot;
|
|
2046
|
-
const existingConfig =
|
|
1432
|
+
const existingConfig = existsSync4(resolve5(projectRoot, "rig.config.ts")) || existsSync4(resolve5(projectRoot, "rig.config.json"));
|
|
2047
1433
|
let repair = false;
|
|
2048
1434
|
let privateStateOnly = false;
|
|
2049
1435
|
if (existingConfig) {
|
|
@@ -2069,80 +1455,57 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
2069
1455
|
placeholder: "owner/repo",
|
|
2070
1456
|
defaultValue: detectedRepo
|
|
2071
1457
|
});
|
|
2072
|
-
const
|
|
2073
|
-
message: "
|
|
2074
|
-
initialValue: "
|
|
1458
|
+
const placement = await promptSelect(prompts, {
|
|
1459
|
+
message: "Execution placement",
|
|
1460
|
+
initialValue: "local",
|
|
2075
1461
|
options: [
|
|
2076
|
-
{ value: "
|
|
2077
|
-
{ value: "
|
|
1462
|
+
{ value: "local", label: "Local", hint: "runs execute on this machine (tmux)" },
|
|
1463
|
+
{ value: "remote", label: "Remote", hint: "runs execute on an SSH host" }
|
|
2078
1464
|
]
|
|
2079
1465
|
});
|
|
2080
|
-
|
|
1466
|
+
let remoteAlias;
|
|
1467
|
+
let remoteHost;
|
|
1468
|
+
let remotePort;
|
|
2081
1469
|
let remoteCheckout;
|
|
2082
|
-
if (
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
{ value: "uploaded-snapshot", label: "Upload current working-tree snapshot" },
|
|
2089
|
-
{ value: "existing-path", label: "Use existing server path" }
|
|
2090
|
-
]
|
|
2091
|
-
});
|
|
2092
|
-
if (checkout === "existing-path") {
|
|
2093
|
-
remoteCheckout = { kind: "existing-path", path: await promptRequiredText(prompts, { message: "Existing server checkout path", placeholder: "/srv/rig/checkouts/repo" }) };
|
|
2094
|
-
} else if (checkout === "current-ref") {
|
|
2095
|
-
remoteCheckout = { kind: "current-ref", ref: await promptOptionalText(prompts, { message: "Branch/ref to clone (blank for current HEAD)", placeholder: "main" }) || undefined };
|
|
2096
|
-
} else if (checkout === "uploaded-snapshot") {
|
|
2097
|
-
remoteCheckout = { kind: "uploaded-snapshot" };
|
|
2098
|
-
} else {
|
|
2099
|
-
remoteCheckout = { kind: "managed-clone" };
|
|
2100
|
-
}
|
|
1470
|
+
if (placement === "remote") {
|
|
1471
|
+
remoteHost = await promptRequiredText(prompts, { message: "Remote host (ssh)", placeholder: "rig.example.com" });
|
|
1472
|
+
remoteAlias = await promptOptionalText(prompts, { message: "Alias for this remote", placeholder: "prod", initialValue: remoteHost }) || remoteHost;
|
|
1473
|
+
const portText = await promptOptionalText(prompts, { message: "SSH port", placeholder: "22", initialValue: "22" });
|
|
1474
|
+
remotePort = portText && Number.isFinite(Number(portText)) ? Number(portText) : 22;
|
|
1475
|
+
remoteCheckout = await promptOptionalText(prompts, { message: "Remote working directory (existing checkout path)", placeholder: "/srv/rig/<repo>" }) || undefined;
|
|
2101
1476
|
}
|
|
2102
1477
|
const detectedGhLogin = detectGhLogin();
|
|
2103
1478
|
const authMethod = await promptSelect(prompts, {
|
|
2104
1479
|
message: `GitHub auth method${detectedGhLogin ? ` (detected gh login: ${detectedGhLogin})` : ""}`,
|
|
2105
1480
|
options: [
|
|
2106
|
-
{ value: "gh", label: "Import token from gh auth token", hint:
|
|
2107
|
-
{ value: "device", label: "Start
|
|
1481
|
+
{ value: "gh", label: "Import token from gh auth token", hint: "recommended for local" },
|
|
1482
|
+
{ value: "device", label: "Start GitHub device flow" },
|
|
2108
1483
|
{ value: "token", label: "Paste token" },
|
|
2109
1484
|
{ value: "skip", label: "Skip for now" }
|
|
2110
1485
|
]
|
|
2111
1486
|
});
|
|
2112
|
-
let remoteGhTokenConfirmed = false;
|
|
2113
|
-
if (serverChoice === "remote" && authMethod === "gh") {
|
|
2114
|
-
if (!prompts.confirm)
|
|
2115
|
-
throw new CliError("Remote gh-token import requires explicit confirmation.", 1);
|
|
2116
|
-
const confirmed = await prompts.confirm({
|
|
2117
|
-
message: `This sends a GitHub token from this machine to ${remoteUrl}. Continue?`,
|
|
2118
|
-
initialValue: true
|
|
2119
|
-
});
|
|
2120
|
-
if (prompts.isCancel(confirmed) || confirmed !== true) {
|
|
2121
|
-
throw new CliError("Remote gh-token import cancelled.", 1);
|
|
2122
|
-
}
|
|
2123
|
-
remoteGhTokenConfirmed = true;
|
|
2124
|
-
}
|
|
2125
1487
|
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
|
|
2126
1488
|
const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
|
|
2127
1489
|
const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
|
|
2128
1490
|
const result = await runControlPlaneInit(context, {
|
|
2129
|
-
server: serverChoice,
|
|
2130
|
-
remoteUrl,
|
|
2131
1491
|
repoSlug,
|
|
2132
1492
|
githubToken: effectiveGithubToken,
|
|
2133
1493
|
githubAuthMethod: authMethod,
|
|
2134
1494
|
githubProject: projectConfig.githubProject,
|
|
2135
1495
|
githubProjectStatusField: projectConfig.githubProjectStatusField,
|
|
2136
1496
|
githubProjectStatuses: projectConfig.githubProjectStatuses,
|
|
2137
|
-
remoteCheckout,
|
|
2138
1497
|
repair,
|
|
2139
1498
|
privateStateOnly,
|
|
2140
|
-
|
|
1499
|
+
server: placement,
|
|
1500
|
+
...remoteAlias ? { remoteAlias } : {},
|
|
1501
|
+
...remoteHost ? { remoteHost } : {},
|
|
1502
|
+
...remotePort !== undefined ? { remotePort } : {},
|
|
1503
|
+
...remoteCheckout ? { remoteCheckout } : {}
|
|
2141
1504
|
});
|
|
2142
1505
|
const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
|
|
2143
1506
|
const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
|
|
2144
1507
|
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")}.` : "";
|
|
2145
|
-
prompts.outro?.(`Rig project initialized.${deviceMessage} Next: rig doctor && rig task list`);
|
|
1508
|
+
prompts.outro?.(`Rig project initialized (${placement} placement).${deviceMessage} Next: rig doctor && rig task list`);
|
|
2146
1509
|
return result;
|
|
2147
1510
|
}
|
|
2148
1511
|
async function executeInit(context, args) {
|
|
@@ -2154,17 +1517,17 @@ Usage: rig init --demo [--yes] [--repair]`, 1, { hint: "Run `rig init --demo` (o
|
|
|
2154
1517
|
}
|
|
2155
1518
|
return runDemoInit(context, parsed.options);
|
|
2156
1519
|
}
|
|
2157
|
-
if (parsed.options.yes || parsed.options.
|
|
1520
|
+
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) {
|
|
2158
1521
|
if (parsed.rest.length > 0)
|
|
2159
1522
|
throw new CliError(`Unexpected arguments: ${parsed.rest.join(" ")}
|
|
2160
|
-
Usage: rig init [--demo] [--server local|remote] [--remote-
|
|
1523
|
+
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);
|
|
2161
1524
|
return runControlPlaneInit(context, parsed.options);
|
|
2162
1525
|
}
|
|
2163
1526
|
if (parsed.rest.length > 0)
|
|
2164
1527
|
throw new CliError(`Unexpected arguments: ${parsed.rest.join(" ")}
|
|
2165
1528
|
Usage: rig init`, 1);
|
|
2166
1529
|
if (!process.stdin.isTTY) {
|
|
2167
|
-
throw new CliError("rig init
|
|
1530
|
+
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);
|
|
2168
1531
|
}
|
|
2169
1532
|
return runInteractiveControlPlaneInit(context, await loadClackPrompts());
|
|
2170
1533
|
}
|