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