@h-rig/cli 0.0.6-alpha.7 → 0.0.6-alpha.71
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/rig.js +4507 -1506
- package/dist/src/commands/_async-ui.js +152 -0
- package/dist/src/commands/_authority-runs.js +2 -3
- package/dist/src/commands/_cli-format.js +369 -0
- package/dist/src/commands/_connection-state.js +30 -11
- package/dist/src/commands/_doctor-checks.js +177 -43
- package/dist/src/commands/_help-catalog.js +485 -0
- package/dist/src/commands/_json-output.js +56 -0
- package/dist/src/commands/_operator-surface.js +220 -0
- package/dist/src/commands/_operator-view.js +595 -72
- package/dist/src/commands/_parsers.js +18 -11
- package/dist/src/commands/_pi-frontend.js +411 -0
- package/dist/src/commands/_pi-install.js +4 -3
- package/dist/src/commands/_policy.js +12 -5
- package/dist/src/commands/_preflight.js +187 -127
- package/dist/src/commands/_run-driver-helpers.js +75 -22
- package/dist/src/commands/_run-replay.js +142 -0
- package/dist/src/commands/_server-client.js +343 -60
- package/dist/src/commands/_snapshot-upload.js +160 -38
- package/dist/src/commands/_spinner.js +65 -0
- package/dist/src/commands/_task-picker.js +44 -16
- package/dist/src/commands/agent.js +39 -20
- package/dist/src/commands/browser.js +28 -21
- package/dist/src/commands/connect.js +146 -33
- package/dist/src/commands/dist.js +19 -12
- package/dist/src/commands/doctor.js +304 -44
- package/dist/src/commands/github.js +301 -52
- package/dist/src/commands/inbox.js +679 -72
- package/dist/src/commands/init.js +622 -118
- package/dist/src/commands/inspect.js +515 -32
- package/dist/src/commands/inspector.js +20 -13
- package/dist/src/commands/pi.js +177 -0
- package/dist/src/commands/plugin.js +95 -27
- package/dist/src/commands/profile-and-review.js +26 -19
- package/dist/src/commands/queue.js +32 -12
- package/dist/src/commands/remote.js +43 -36
- package/dist/src/commands/repo-git-harness.js +22 -15
- package/dist/src/commands/run.js +1162 -158
- package/dist/src/commands/server.js +373 -56
- package/dist/src/commands/setup.js +316 -62
- package/dist/src/commands/stats.js +1030 -0
- package/dist/src/commands/task-report-bug.js +29 -22
- package/dist/src/commands/task-run-driver.js +862 -129
- package/dist/src/commands/task.js +1423 -311
- package/dist/src/commands/test.js +15 -8
- package/dist/src/commands/workspace.js +18 -11
- package/dist/src/commands.js +4446 -1499
- package/dist/src/index.js +4502 -1504
- package/dist/src/launcher.js +77 -13
- package/dist/src/report-bug.js +3 -3
- package/dist/src/runner.js +16 -22
- package/package.json +10 -5
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/commands/github.ts
|
|
3
|
-
import { spawnSync
|
|
3
|
+
import { spawnSync } from "child_process";
|
|
4
4
|
|
|
5
5
|
// packages/cli/src/runner.ts
|
|
6
6
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
7
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
7
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
8
8
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
9
|
-
import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
10
|
-
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
11
9
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
12
|
-
|
|
10
|
+
|
|
11
|
+
class CliError extends RuntimeCliError {
|
|
12
|
+
hint;
|
|
13
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
14
|
+
super(message, exitCode);
|
|
15
|
+
if (options.hint?.trim()) {
|
|
16
|
+
this.hint = options.hint.trim();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
13
20
|
function takeOption(args, option) {
|
|
14
21
|
const rest = [];
|
|
15
22
|
let value;
|
|
@@ -18,7 +25,7 @@ function takeOption(args, option) {
|
|
|
18
25
|
if (current === option) {
|
|
19
26
|
const next = args[index + 1];
|
|
20
27
|
if (!next || next.startsWith("-")) {
|
|
21
|
-
throw new CliError(`Missing value for ${option}`);
|
|
28
|
+
throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
|
|
22
29
|
}
|
|
23
30
|
value = next;
|
|
24
31
|
index += 1;
|
|
@@ -32,7 +39,6 @@ function takeOption(args, option) {
|
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
// packages/cli/src/commands/_server-client.ts
|
|
35
|
-
import { spawnSync } from "child_process";
|
|
36
42
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
37
43
|
import { resolve as resolve2 } from "path";
|
|
38
44
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
@@ -59,9 +65,14 @@ function readJsonFile(path) {
|
|
|
59
65
|
try {
|
|
60
66
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
61
67
|
} catch (error) {
|
|
62
|
-
throw new
|
|
68
|
+
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>`." });
|
|
63
69
|
}
|
|
64
70
|
}
|
|
71
|
+
function writeJsonFile(path, value) {
|
|
72
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
73
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
74
|
+
`, "utf8");
|
|
75
|
+
}
|
|
65
76
|
function normalizeConnection(value) {
|
|
66
77
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
67
78
|
return null;
|
|
@@ -102,25 +113,44 @@ function readRepoConnection(projectRoot) {
|
|
|
102
113
|
return {
|
|
103
114
|
selected,
|
|
104
115
|
project: typeof record.project === "string" ? record.project : undefined,
|
|
105
|
-
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
|
|
116
|
+
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
117
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
106
118
|
};
|
|
107
119
|
}
|
|
120
|
+
function writeRepoConnection(projectRoot, state) {
|
|
121
|
+
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
122
|
+
}
|
|
108
123
|
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
109
124
|
const repo = readRepoConnection(projectRoot);
|
|
110
125
|
if (!repo)
|
|
111
126
|
return null;
|
|
112
127
|
if (repo.selected === "local")
|
|
113
|
-
return { alias: "local", connection: { kind: "local", mode: "auto" } };
|
|
128
|
+
return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
|
|
114
129
|
const global = readGlobalConnections(options);
|
|
115
130
|
const connection = global.connections[repo.selected];
|
|
116
131
|
if (!connection) {
|
|
117
|
-
throw new
|
|
132
|
+
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
118
133
|
}
|
|
119
|
-
return { alias: repo.selected, connection };
|
|
134
|
+
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
135
|
+
}
|
|
136
|
+
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
137
|
+
const repo = readRepoConnection(projectRoot);
|
|
138
|
+
if (!repo)
|
|
139
|
+
return;
|
|
140
|
+
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
120
141
|
}
|
|
121
142
|
|
|
122
143
|
// packages/cli/src/commands/_server-client.ts
|
|
123
|
-
var
|
|
144
|
+
var scopedGitHubBearerTokens = new Map;
|
|
145
|
+
var serverPhaseListener = null;
|
|
146
|
+
function setServerPhaseListener(listener) {
|
|
147
|
+
const previous = serverPhaseListener;
|
|
148
|
+
serverPhaseListener = listener;
|
|
149
|
+
return previous;
|
|
150
|
+
}
|
|
151
|
+
function reportServerPhase(label) {
|
|
152
|
+
serverPhaseListener?.(label);
|
|
153
|
+
}
|
|
124
154
|
function cleanToken(value) {
|
|
125
155
|
const trimmed = value?.trim();
|
|
126
156
|
return trimmed ? trimmed : null;
|
|
@@ -137,49 +167,80 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
137
167
|
}
|
|
138
168
|
}
|
|
139
169
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
140
|
-
|
|
141
|
-
|
|
170
|
+
const scopedKey = resolve2(projectRoot);
|
|
171
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
172
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
142
173
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
143
|
-
if (privateSession)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return
|
|
174
|
+
if (privateSession)
|
|
175
|
+
return privateSession;
|
|
176
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
177
|
+
}
|
|
178
|
+
function readStoredGitHubAuthToken(projectRoot) {
|
|
179
|
+
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
180
|
+
if (!existsSync2(path))
|
|
181
|
+
return null;
|
|
182
|
+
try {
|
|
183
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
184
|
+
return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
151
187
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
156
|
-
});
|
|
157
|
-
cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
|
|
158
|
-
return cachedGitHubBearerToken;
|
|
188
|
+
}
|
|
189
|
+
function readLocalConnectionFallbackToken(projectRoot) {
|
|
190
|
+
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
159
191
|
}
|
|
160
192
|
async function ensureServerForCli(projectRoot) {
|
|
161
193
|
try {
|
|
162
194
|
const selected = resolveSelectedConnection(projectRoot);
|
|
163
195
|
if (selected?.connection.kind === "remote") {
|
|
196
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
197
|
+
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
198
|
+
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
164
199
|
return {
|
|
165
200
|
baseUrl: selected.connection.baseUrl,
|
|
166
|
-
authToken
|
|
167
|
-
connectionKind: "remote"
|
|
201
|
+
authToken,
|
|
202
|
+
connectionKind: "remote",
|
|
203
|
+
serverProjectRoot
|
|
168
204
|
};
|
|
169
205
|
}
|
|
206
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
170
207
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
171
208
|
return {
|
|
172
209
|
baseUrl: connection.baseUrl,
|
|
173
|
-
authToken: connection.authToken,
|
|
174
|
-
connectionKind: "local"
|
|
210
|
+
authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
|
|
211
|
+
connectionKind: "local",
|
|
212
|
+
serverProjectRoot: resolve2(projectRoot)
|
|
175
213
|
};
|
|
176
214
|
} catch (error) {
|
|
177
215
|
if (error instanceof Error) {
|
|
178
|
-
throw new
|
|
216
|
+
throw new CliError(error.message, 1);
|
|
179
217
|
}
|
|
180
218
|
throw error;
|
|
181
219
|
}
|
|
182
220
|
}
|
|
221
|
+
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
222
|
+
const repo = readRepoConnection(projectRoot);
|
|
223
|
+
const slug = repo?.project?.trim();
|
|
224
|
+
if (!slug)
|
|
225
|
+
return null;
|
|
226
|
+
try {
|
|
227
|
+
const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
|
|
228
|
+
headers: mergeHeaders(undefined, authToken)
|
|
229
|
+
});
|
|
230
|
+
if (!response.ok)
|
|
231
|
+
return null;
|
|
232
|
+
const payload = await response.json();
|
|
233
|
+
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
234
|
+
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
235
|
+
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
236
|
+
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
237
|
+
if (path)
|
|
238
|
+
writeRepoServerProjectRoot(projectRoot, path);
|
|
239
|
+
return path;
|
|
240
|
+
} catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
183
244
|
function mergeHeaders(headers, authToken) {
|
|
184
245
|
const merged = new Headers(headers);
|
|
185
246
|
if (authToken) {
|
|
@@ -202,12 +263,65 @@ function diagnosticMessage(payload) {
|
|
|
202
263
|
});
|
|
203
264
|
return messages.length > 0 ? messages.join("; ") : null;
|
|
204
265
|
}
|
|
266
|
+
var serverReachabilityCache = new Map;
|
|
267
|
+
async function probeServerReachability(baseUrl, authToken) {
|
|
268
|
+
try {
|
|
269
|
+
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
|
|
270
|
+
headers: mergeHeaders(undefined, authToken),
|
|
271
|
+
signal: AbortSignal.timeout(1500)
|
|
272
|
+
});
|
|
273
|
+
return response.ok;
|
|
274
|
+
} catch {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function cachedServerReachability(projectRoot, baseUrl, authToken) {
|
|
279
|
+
const key = resolve2(projectRoot);
|
|
280
|
+
const cached = serverReachabilityCache.get(key);
|
|
281
|
+
if (cached)
|
|
282
|
+
return cached;
|
|
283
|
+
const probe = probeServerReachability(baseUrl, authToken);
|
|
284
|
+
serverReachabilityCache.set(key, probe);
|
|
285
|
+
return probe;
|
|
286
|
+
}
|
|
287
|
+
function describeSelectedServer(projectRoot, server) {
|
|
288
|
+
try {
|
|
289
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
290
|
+
if (selected) {
|
|
291
|
+
return {
|
|
292
|
+
alias: selected.alias,
|
|
293
|
+
target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
} catch {}
|
|
297
|
+
return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
|
|
298
|
+
}
|
|
299
|
+
async function buildServerFailureContext(projectRoot, server) {
|
|
300
|
+
const { alias, target } = describeSelectedServer(projectRoot, server);
|
|
301
|
+
const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
|
|
302
|
+
const reachability = reachable ? "server is reachable" : "server is unreachable";
|
|
303
|
+
return {
|
|
304
|
+
contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
|
|
305
|
+
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
306
|
+
};
|
|
307
|
+
}
|
|
205
308
|
async function requestServerJson(context, pathname, init = {}) {
|
|
206
309
|
const server = await ensureServerForCli(context.projectRoot);
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
headers
|
|
210
|
-
});
|
|
310
|
+
const headers = mergeHeaders(init.headers, server.authToken);
|
|
311
|
+
if (server.serverProjectRoot)
|
|
312
|
+
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
313
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
314
|
+
let response;
|
|
315
|
+
try {
|
|
316
|
+
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
317
|
+
...init,
|
|
318
|
+
headers
|
|
319
|
+
});
|
|
320
|
+
} catch (error) {
|
|
321
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
322
|
+
throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
|
|
323
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
324
|
+
}
|
|
211
325
|
const text = await response.text();
|
|
212
326
|
const payload = text.trim().length > 0 ? (() => {
|
|
213
327
|
try {
|
|
@@ -219,7 +333,9 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
219
333
|
if (!response.ok) {
|
|
220
334
|
const diagnostics = diagnosticMessage(payload);
|
|
221
335
|
const detail = diagnostics ?? (text || response.statusText);
|
|
222
|
-
|
|
336
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
337
|
+
throw new CliError(`Rig server request failed (${response.status}): ${detail}
|
|
338
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
223
339
|
}
|
|
224
340
|
return payload;
|
|
225
341
|
}
|
|
@@ -235,6 +351,138 @@ async function postGitHubTokenViaServer(context, token, options = {}) {
|
|
|
235
351
|
});
|
|
236
352
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
237
353
|
}
|
|
354
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
355
|
+
"created",
|
|
356
|
+
"preparing",
|
|
357
|
+
"running",
|
|
358
|
+
"validating",
|
|
359
|
+
"reviewing",
|
|
360
|
+
"stopped",
|
|
361
|
+
"failed",
|
|
362
|
+
"needs-attention",
|
|
363
|
+
"needs_attention"
|
|
364
|
+
]);
|
|
365
|
+
|
|
366
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
367
|
+
import pc from "picocolors";
|
|
368
|
+
|
|
369
|
+
// packages/cli/src/commands/_spinner.ts
|
|
370
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
371
|
+
function createTtySpinner(input) {
|
|
372
|
+
const output = input.output ?? process.stdout;
|
|
373
|
+
const isTty = output.isTTY === true;
|
|
374
|
+
const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
|
|
375
|
+
let label = input.label;
|
|
376
|
+
let frame = 0;
|
|
377
|
+
let paused = false;
|
|
378
|
+
let stopped = false;
|
|
379
|
+
let lastPrintedLabel = "";
|
|
380
|
+
const render = () => {
|
|
381
|
+
if (stopped || paused)
|
|
382
|
+
return;
|
|
383
|
+
if (!isTty) {
|
|
384
|
+
if (label !== lastPrintedLabel) {
|
|
385
|
+
output.write(`${label}
|
|
386
|
+
`);
|
|
387
|
+
lastPrintedLabel = label;
|
|
388
|
+
}
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
frame = (frame + 1) % frames.length;
|
|
392
|
+
const glyph = frames[frame] ?? frames[0] ?? "";
|
|
393
|
+
output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
|
|
394
|
+
};
|
|
395
|
+
const clearLine = () => {
|
|
396
|
+
if (isTty)
|
|
397
|
+
output.write("\r\x1B[2K");
|
|
398
|
+
};
|
|
399
|
+
render();
|
|
400
|
+
const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
|
|
401
|
+
return {
|
|
402
|
+
setLabel(next) {
|
|
403
|
+
label = next;
|
|
404
|
+
render();
|
|
405
|
+
},
|
|
406
|
+
pause() {
|
|
407
|
+
paused = true;
|
|
408
|
+
clearLine();
|
|
409
|
+
},
|
|
410
|
+
resume() {
|
|
411
|
+
if (stopped)
|
|
412
|
+
return;
|
|
413
|
+
paused = false;
|
|
414
|
+
render();
|
|
415
|
+
},
|
|
416
|
+
stop(finalLine) {
|
|
417
|
+
if (stopped)
|
|
418
|
+
return;
|
|
419
|
+
stopped = true;
|
|
420
|
+
if (timer)
|
|
421
|
+
clearInterval(timer);
|
|
422
|
+
clearLine();
|
|
423
|
+
if (finalLine)
|
|
424
|
+
output.write(`${finalLine}
|
|
425
|
+
`);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
431
|
+
var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
432
|
+
var DONE_SYMBOL = pc.green("\u25C7");
|
|
433
|
+
var FAIL_SYMBOL = pc.red("\u25A0");
|
|
434
|
+
var activeUpdate = null;
|
|
435
|
+
async function withSpinner(label, work, options = {}) {
|
|
436
|
+
if (options.outputMode === "json") {
|
|
437
|
+
return work(() => {});
|
|
438
|
+
}
|
|
439
|
+
if (activeUpdate) {
|
|
440
|
+
const outer = activeUpdate;
|
|
441
|
+
outer(label);
|
|
442
|
+
return work(outer);
|
|
443
|
+
}
|
|
444
|
+
const output = options.output ?? process.stderr;
|
|
445
|
+
const isTty = output.isTTY === true;
|
|
446
|
+
let lastLabel = label;
|
|
447
|
+
if (!isTty) {
|
|
448
|
+
output.write(`${label}
|
|
449
|
+
`);
|
|
450
|
+
const update2 = (next) => {
|
|
451
|
+
lastLabel = next;
|
|
452
|
+
};
|
|
453
|
+
activeUpdate = update2;
|
|
454
|
+
const previousListener2 = setServerPhaseListener(update2);
|
|
455
|
+
try {
|
|
456
|
+
return await work(update2);
|
|
457
|
+
} finally {
|
|
458
|
+
activeUpdate = null;
|
|
459
|
+
setServerPhaseListener(previousListener2);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const spinner = createTtySpinner({
|
|
463
|
+
label,
|
|
464
|
+
output,
|
|
465
|
+
frames: CLACK_SPINNER_FRAMES,
|
|
466
|
+
styleFrame: (frame) => pc.magenta(frame)
|
|
467
|
+
});
|
|
468
|
+
const update = (next) => {
|
|
469
|
+
lastLabel = next;
|
|
470
|
+
spinner.setLabel(next);
|
|
471
|
+
};
|
|
472
|
+
activeUpdate = update;
|
|
473
|
+
const previousListener = setServerPhaseListener(update);
|
|
474
|
+
try {
|
|
475
|
+
const result = await work(update);
|
|
476
|
+
spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
|
|
477
|
+
return result;
|
|
478
|
+
} catch (error) {
|
|
479
|
+
spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
|
|
480
|
+
throw error;
|
|
481
|
+
} finally {
|
|
482
|
+
activeUpdate = null;
|
|
483
|
+
setServerPhaseListener(previousListener);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
238
486
|
|
|
239
487
|
// packages/cli/src/commands/github.ts
|
|
240
488
|
function printPayload(context, payload, fallback) {
|
|
@@ -244,49 +492,50 @@ function printPayload(context, payload, fallback) {
|
|
|
244
492
|
console.log(fallback);
|
|
245
493
|
}
|
|
246
494
|
function readGhToken() {
|
|
247
|
-
const result =
|
|
495
|
+
const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
|
|
248
496
|
if (result.status !== 0) {
|
|
249
497
|
const detail = result.stderr?.trim() || result.stdout?.trim() || "gh auth token failed";
|
|
250
|
-
throw new
|
|
498
|
+
throw new CliError(`Could not import GitHub token from gh: ${detail}`, 1, { hint: "Sign in first with `gh auth login`, or store a token directly: `rig github auth token --token <token>`." });
|
|
251
499
|
}
|
|
252
500
|
const token = result.stdout.trim();
|
|
253
501
|
if (!token)
|
|
254
|
-
throw new
|
|
502
|
+
throw new CliError("gh auth token returned an empty token.", 1, { hint: "Sign in with `gh auth login`, then re-run `rig github auth import-gh`." });
|
|
255
503
|
return token;
|
|
256
504
|
}
|
|
257
505
|
async function executeGithub(context, args) {
|
|
258
506
|
const [group, command, ...rest] = args;
|
|
259
507
|
if (group !== "auth") {
|
|
260
|
-
throw new
|
|
508
|
+
throw new CliError("Usage: rig github auth <status|import-gh|token>", 1);
|
|
261
509
|
}
|
|
262
510
|
switch (command) {
|
|
263
511
|
case "status": {
|
|
264
512
|
if (rest.length > 0)
|
|
265
|
-
throw new
|
|
266
|
-
const status = await getGitHubAuthStatusViaServer(context);
|
|
513
|
+
throw new CliError("Usage: rig github auth status", 1);
|
|
514
|
+
const status = await withSpinner("Checking GitHub auth on the server\u2026", () => getGitHubAuthStatusViaServer(context), { outputMode: context.outputMode });
|
|
267
515
|
printPayload(context, status, `GitHub auth: ${status.authenticated ? "authenticated" : "unauthenticated"}`);
|
|
268
516
|
return { ok: true, group: "github", command: "auth status", details: status };
|
|
269
517
|
}
|
|
270
518
|
case "token": {
|
|
271
519
|
const parsed = takeOption(rest, "--token");
|
|
272
520
|
if (parsed.rest.length > 0)
|
|
273
|
-
throw new
|
|
521
|
+
throw new CliError("Usage: rig github auth token --token <token>", 1);
|
|
274
522
|
const token = parsed.value?.trim();
|
|
275
523
|
if (!token)
|
|
276
|
-
throw new
|
|
277
|
-
const result = await postGitHubTokenViaServer(context, token);
|
|
524
|
+
throw new CliError("Missing --token value.", 1, { hint: "Re-run as `rig github auth token --token <token>`." });
|
|
525
|
+
const result = await withSpinner("Storing GitHub token on the server\u2026", () => postGitHubTokenViaServer(context, token), { outputMode: context.outputMode });
|
|
278
526
|
printPayload(context, result, "GitHub token stored on the selected Rig server.");
|
|
279
527
|
return { ok: true, group: "github", command: "auth token", details: result };
|
|
280
528
|
}
|
|
281
529
|
case "import-gh": {
|
|
282
530
|
if (rest.length > 0)
|
|
283
|
-
throw new
|
|
284
|
-
const
|
|
531
|
+
throw new CliError("Usage: rig github auth import-gh", 1);
|
|
532
|
+
const importedToken = readGhToken();
|
|
533
|
+
const result = await withSpinner("Storing GitHub token on the server\u2026", () => postGitHubTokenViaServer(context, importedToken), { outputMode: context.outputMode });
|
|
285
534
|
printPayload(context, result, "GitHub token imported from gh and stored on the selected Rig server.");
|
|
286
535
|
return { ok: true, group: "github", command: "auth import-gh", details: result };
|
|
287
536
|
}
|
|
288
537
|
default:
|
|
289
|
-
throw new
|
|
538
|
+
throw new CliError("Usage: rig github auth <status|import-gh|token>", 1);
|
|
290
539
|
}
|
|
291
540
|
}
|
|
292
541
|
export {
|