@h-rig/cli 0.0.6-alpha.67 → 0.0.6-alpha.69
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/dist/bin/rig.js +266 -75
- package/dist/src/commands/_async-ui.js +152 -0
- package/dist/src/commands/_doctor-checks.js +29 -0
- package/dist/src/commands/_operator-view.js +154 -4
- package/dist/src/commands/_pi-frontend.js +24 -1
- package/dist/src/commands/_preflight.js +18 -0
- package/dist/src/commands/_server-client.js +46 -0
- package/dist/src/commands/_snapshot-upload.js +18 -0
- package/dist/src/commands/_spinner.js +4 -2
- package/dist/src/commands/agent.js +11 -0
- package/dist/src/commands/doctor.js +156 -1
- package/dist/src/commands/github.js +148 -3
- package/dist/src/commands/inbox.js +150 -6
- package/dist/src/commands/init.js +29 -0
- package/dist/src/commands/inspect.js +158 -8
- package/dist/src/commands/queue.js +11 -0
- package/dist/src/commands/run.js +207 -35
- package/dist/src/commands/server.js +18 -0
- package/dist/src/commands/setup.js +161 -6
- package/dist/src/commands/stats.js +167 -22
- package/dist/src/commands/task-run-driver.js +18 -0
- package/dist/src/commands/task.js +197 -47
- package/dist/src/commands.js +266 -75
- package/dist/src/index.js +266 -75
- package/package.json +8 -8
|
@@ -121,6 +121,15 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
|
121
121
|
|
|
122
122
|
// packages/cli/src/commands/_server-client.ts
|
|
123
123
|
var scopedGitHubBearerTokens = new Map;
|
|
124
|
+
var serverPhaseListener = null;
|
|
125
|
+
function setServerPhaseListener(listener) {
|
|
126
|
+
const previous = serverPhaseListener;
|
|
127
|
+
serverPhaseListener = listener;
|
|
128
|
+
return previous;
|
|
129
|
+
}
|
|
130
|
+
function reportServerPhase(label) {
|
|
131
|
+
serverPhaseListener?.(label);
|
|
132
|
+
}
|
|
124
133
|
function cleanToken(value) {
|
|
125
134
|
const trimmed = value?.trim();
|
|
126
135
|
return trimmed ? trimmed : null;
|
|
@@ -167,6 +176,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
167
176
|
try {
|
|
168
177
|
const selected = resolveSelectedConnection(projectRoot);
|
|
169
178
|
if (selected?.connection.kind === "remote") {
|
|
179
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
170
180
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
171
181
|
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
172
182
|
return {
|
|
@@ -176,6 +186,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
176
186
|
serverProjectRoot
|
|
177
187
|
};
|
|
178
188
|
}
|
|
189
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
179
190
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
180
191
|
return {
|
|
181
192
|
baseUrl: connection.baseUrl,
|
|
@@ -292,6 +303,7 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
292
303
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
293
304
|
if (server.serverProjectRoot)
|
|
294
305
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
306
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
295
307
|
let response;
|
|
296
308
|
try {
|
|
297
309
|
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
@@ -469,6 +481,38 @@ async function updateWorkspaceTaskViaServer(context, input) {
|
|
|
469
481
|
});
|
|
470
482
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
471
483
|
}
|
|
484
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
485
|
+
"created",
|
|
486
|
+
"preparing",
|
|
487
|
+
"running",
|
|
488
|
+
"validating",
|
|
489
|
+
"reviewing",
|
|
490
|
+
"stopped",
|
|
491
|
+
"failed",
|
|
492
|
+
"needs-attention",
|
|
493
|
+
"needs_attention"
|
|
494
|
+
]);
|
|
495
|
+
async function resumeRunViaServer(context, runId, options) {
|
|
496
|
+
let targetRunId = runId?.trim() || null;
|
|
497
|
+
if (!targetRunId) {
|
|
498
|
+
const candidates = (await listRunsViaServer(context)).filter((run) => RESUMABLE_RUN_STATUSES.has(String(run.status ?? ""))).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")));
|
|
499
|
+
targetRunId = typeof candidates[0]?.runId === "string" ? candidates[0].runId : null;
|
|
500
|
+
}
|
|
501
|
+
if (!targetRunId) {
|
|
502
|
+
throw new CliError(options.restart ? "No run is available to restart." : "No resumable run is available.", 2, { hint: "List runs with `rig run list`, then pass an explicit id: `rig run resume <run-id>`." });
|
|
503
|
+
}
|
|
504
|
+
const payload = await requestServerJson(context, "/api/runs/resume", {
|
|
505
|
+
method: "POST",
|
|
506
|
+
headers: { "content-type": "application/json" },
|
|
507
|
+
body: JSON.stringify({ runId: targetRunId, createdAt: new Date().toISOString(), restart: options.restart })
|
|
508
|
+
});
|
|
509
|
+
const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
510
|
+
if (record.ok === false) {
|
|
511
|
+
const message = typeof record.error === "string" && record.error.trim() ? record.error : "run resume failed";
|
|
512
|
+
throw new CliError(`${options.restart ? "restart" : "resume"} failed for ${targetRunId}: ${message}`, 1);
|
|
513
|
+
}
|
|
514
|
+
return { ok: true, runId: targetRunId, ...record };
|
|
515
|
+
}
|
|
472
516
|
async function stopRunViaServer(context, runId) {
|
|
473
517
|
const payload = await requestServerJson(context, "/api/runs/stop", {
|
|
474
518
|
method: "POST",
|
|
@@ -588,11 +632,13 @@ export {
|
|
|
588
632
|
submitTaskRunViaServer,
|
|
589
633
|
stopRunViaServer,
|
|
590
634
|
steerRunViaServer,
|
|
635
|
+
setServerPhaseListener,
|
|
591
636
|
setGitHubBearerTokenForCurrentProcess,
|
|
592
637
|
sendRunPiShellViaServer,
|
|
593
638
|
sendRunPiPromptViaServer,
|
|
594
639
|
selectNextWorkspaceTaskViaServer,
|
|
595
640
|
runRunPiCommandViaServer,
|
|
641
|
+
resumeRunViaServer,
|
|
596
642
|
respondRunPiExtensionUiViaServer,
|
|
597
643
|
requestServerJson,
|
|
598
644
|
registerProjectViaServer,
|
|
@@ -125,6 +125,10 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
|
125
125
|
|
|
126
126
|
// packages/cli/src/commands/_server-client.ts
|
|
127
127
|
var scopedGitHubBearerTokens = new Map;
|
|
128
|
+
var serverPhaseListener = null;
|
|
129
|
+
function reportServerPhase(label) {
|
|
130
|
+
serverPhaseListener?.(label);
|
|
131
|
+
}
|
|
128
132
|
function cleanToken(value) {
|
|
129
133
|
const trimmed = value?.trim();
|
|
130
134
|
return trimmed ? trimmed : null;
|
|
@@ -167,6 +171,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
167
171
|
try {
|
|
168
172
|
const selected = resolveSelectedConnection(projectRoot);
|
|
169
173
|
if (selected?.connection.kind === "remote") {
|
|
174
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
170
175
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
171
176
|
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
172
177
|
return {
|
|
@@ -176,6 +181,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
176
181
|
serverProjectRoot
|
|
177
182
|
};
|
|
178
183
|
}
|
|
184
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
179
185
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
180
186
|
return {
|
|
181
187
|
baseUrl: connection.baseUrl,
|
|
@@ -282,6 +288,7 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
282
288
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
283
289
|
if (server.serverProjectRoot)
|
|
284
290
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
291
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
285
292
|
let response;
|
|
286
293
|
try {
|
|
287
294
|
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
@@ -310,6 +317,17 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
|
|
|
310
317
|
}
|
|
311
318
|
return payload;
|
|
312
319
|
}
|
|
320
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
321
|
+
"created",
|
|
322
|
+
"preparing",
|
|
323
|
+
"running",
|
|
324
|
+
"validating",
|
|
325
|
+
"reviewing",
|
|
326
|
+
"stopped",
|
|
327
|
+
"failed",
|
|
328
|
+
"needs-attention",
|
|
329
|
+
"needs_attention"
|
|
330
|
+
]);
|
|
313
331
|
|
|
314
332
|
// packages/cli/src/commands/_snapshot-upload.ts
|
|
315
333
|
var UPLOADED_SNAPSHOT_PR_MARKER = "<!-- rig:uploaded-snapshot -->";
|
|
@@ -4,6 +4,7 @@ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834"
|
|
|
4
4
|
function createTtySpinner(input) {
|
|
5
5
|
const output = input.output ?? process.stdout;
|
|
6
6
|
const isTty = output.isTTY === true;
|
|
7
|
+
const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
|
|
7
8
|
let label = input.label;
|
|
8
9
|
let frame = 0;
|
|
9
10
|
let paused = false;
|
|
@@ -20,8 +21,9 @@ function createTtySpinner(input) {
|
|
|
20
21
|
}
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
|
-
frame = (frame + 1) %
|
|
24
|
-
|
|
24
|
+
frame = (frame + 1) % frames.length;
|
|
25
|
+
const glyph = frames[frame] ?? frames[0] ?? "";
|
|
26
|
+
output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
|
|
25
27
|
};
|
|
26
28
|
const clearLine = () => {
|
|
27
29
|
if (isTty)
|
|
@@ -229,6 +229,17 @@ import { ensureProjectMainFreshBeforeRun } from "@rig/runtime/control-plane/proj
|
|
|
229
229
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
230
230
|
var scopedGitHubBearerTokens = new Map;
|
|
231
231
|
var serverReachabilityCache = new Map;
|
|
232
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
233
|
+
"created",
|
|
234
|
+
"preparing",
|
|
235
|
+
"running",
|
|
236
|
+
"validating",
|
|
237
|
+
"reviewing",
|
|
238
|
+
"stopped",
|
|
239
|
+
"failed",
|
|
240
|
+
"needs-attention",
|
|
241
|
+
"needs_attention"
|
|
242
|
+
]);
|
|
232
243
|
|
|
233
244
|
// packages/cli/src/commands/_preflight.ts
|
|
234
245
|
async function runProjectMainSyncPreflight(context, options) {
|
|
@@ -130,6 +130,15 @@ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
|
130
130
|
import { resolve as resolve2 } from "path";
|
|
131
131
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
132
132
|
var scopedGitHubBearerTokens = new Map;
|
|
133
|
+
var serverPhaseListener = null;
|
|
134
|
+
function setServerPhaseListener(listener) {
|
|
135
|
+
const previous = serverPhaseListener;
|
|
136
|
+
serverPhaseListener = listener;
|
|
137
|
+
return previous;
|
|
138
|
+
}
|
|
139
|
+
function reportServerPhase(label) {
|
|
140
|
+
serverPhaseListener?.(label);
|
|
141
|
+
}
|
|
133
142
|
function cleanToken(value) {
|
|
134
143
|
const trimmed = value?.trim();
|
|
135
144
|
return trimmed ? trimmed : null;
|
|
@@ -172,6 +181,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
172
181
|
try {
|
|
173
182
|
const selected = resolveSelectedConnection(projectRoot);
|
|
174
183
|
if (selected?.connection.kind === "remote") {
|
|
184
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
175
185
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
176
186
|
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
177
187
|
return {
|
|
@@ -181,6 +191,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
181
191
|
serverProjectRoot
|
|
182
192
|
};
|
|
183
193
|
}
|
|
194
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
184
195
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
185
196
|
return {
|
|
186
197
|
baseUrl: connection.baseUrl,
|
|
@@ -287,6 +298,7 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
287
298
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
288
299
|
if (server.serverProjectRoot)
|
|
289
300
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
301
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
290
302
|
let response;
|
|
291
303
|
try {
|
|
292
304
|
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
@@ -315,6 +327,17 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
|
|
|
315
327
|
}
|
|
316
328
|
return payload;
|
|
317
329
|
}
|
|
330
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
331
|
+
"created",
|
|
332
|
+
"preparing",
|
|
333
|
+
"running",
|
|
334
|
+
"validating",
|
|
335
|
+
"reviewing",
|
|
336
|
+
"stopped",
|
|
337
|
+
"failed",
|
|
338
|
+
"needs-attention",
|
|
339
|
+
"needs_attention"
|
|
340
|
+
]);
|
|
318
341
|
|
|
319
342
|
// packages/cli/src/commands/_parsers.ts
|
|
320
343
|
async function loadRigConfigOrNull(projectRoot) {
|
|
@@ -518,7 +541,10 @@ async function runRigDoctorChecks(options) {
|
|
|
518
541
|
const bunVersion = options.bunVersion ?? Bun.version;
|
|
519
542
|
const request = options.requestJson ?? ((pathname, init) => requestServerJson({ projectRoot }, pathname, init));
|
|
520
543
|
const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
|
|
544
|
+
const progress = options.onProgress ?? (() => {});
|
|
545
|
+
progress("Checking local toolchain\u2026");
|
|
521
546
|
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`)."));
|
|
547
|
+
progress("Loading rig.config\u2026");
|
|
522
548
|
const loadedConfig = await loadConfig(projectRoot).catch(() => null);
|
|
523
549
|
const config = loadedConfig ?? loadFallbackConfig(projectRoot);
|
|
524
550
|
const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve4(projectRoot, name)));
|
|
@@ -537,6 +563,7 @@ async function runRigDoctorChecks(options) {
|
|
|
537
563
|
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));
|
|
538
564
|
let server = null;
|
|
539
565
|
try {
|
|
566
|
+
progress("Connecting to the selected Rig server\u2026");
|
|
540
567
|
server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
|
|
541
568
|
checks.push(check("server", "Rig server reachable", "pass", `${server.connectionKind} ${server.baseUrl}`));
|
|
542
569
|
} catch (error) {
|
|
@@ -544,18 +571,21 @@ async function runRigDoctorChecks(options) {
|
|
|
544
571
|
}
|
|
545
572
|
if (server || options.requestJson) {
|
|
546
573
|
try {
|
|
574
|
+
progress("Checking server status\u2026");
|
|
547
575
|
const status = await request("/api/server/status");
|
|
548
576
|
checks.push(check("server-status", "server project status", "pass", JSON.stringify(status).slice(0, 180)));
|
|
549
577
|
} catch (error) {
|
|
550
578
|
checks.push(check("server-status", "server project status", "fail", errorMessage(error), "Run `rig doctor` after the selected server is reachable."));
|
|
551
579
|
}
|
|
552
580
|
try {
|
|
581
|
+
progress("Checking GitHub auth\u2026");
|
|
553
582
|
const auth = await request("/api/github/auth/status");
|
|
554
583
|
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>`."));
|
|
555
584
|
} catch (error) {
|
|
556
585
|
checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
|
|
557
586
|
}
|
|
558
587
|
try {
|
|
588
|
+
progress("Checking GitHub repo permissions\u2026");
|
|
559
589
|
const permissions = await request("/api/github/repo/permissions");
|
|
560
590
|
const allowed = permissionAllowsPr(permissions);
|
|
561
591
|
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."));
|
|
@@ -563,6 +593,7 @@ async function runRigDoctorChecks(options) {
|
|
|
563
593
|
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."));
|
|
564
594
|
}
|
|
565
595
|
try {
|
|
596
|
+
progress("Checking GitHub issue labels\u2026");
|
|
566
597
|
const labels = await request("/api/workspace/task-labels");
|
|
567
598
|
const ready = labelsReady(labels);
|
|
568
599
|
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."));
|
|
@@ -570,6 +601,7 @@ async function runRigDoctorChecks(options) {
|
|
|
570
601
|
checks.push(check("task-labels", "GitHub issue labels", "warn", errorMessage(error), "Run `rig init`/`rig doctor` after label setup is wired on the server."));
|
|
571
602
|
}
|
|
572
603
|
try {
|
|
604
|
+
progress("Checking task projection\u2026");
|
|
573
605
|
const projection = await request("/api/workspace/task-projection");
|
|
574
606
|
checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
|
|
575
607
|
} catch (error) {
|
|
@@ -578,6 +610,7 @@ async function runRigDoctorChecks(options) {
|
|
|
578
610
|
const slug = projectStatusSlug(projectRoot, config);
|
|
579
611
|
if (slug) {
|
|
580
612
|
try {
|
|
613
|
+
progress("Checking server project checkout\u2026");
|
|
581
614
|
const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
|
|
582
615
|
checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
|
|
583
616
|
} catch (error) {
|
|
@@ -592,6 +625,7 @@ async function runRigDoctorChecks(options) {
|
|
|
592
625
|
}
|
|
593
626
|
checks.push(githubProjectsCheck(config));
|
|
594
627
|
checks.push(prMergeCheck(config));
|
|
628
|
+
progress("Checking Pi installation\u2026");
|
|
595
629
|
const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
|
|
596
630
|
ok: false,
|
|
597
631
|
label: "pi/pi-rig checks",
|
|
@@ -615,10 +649,131 @@ function countDoctorFailures(checks) {
|
|
|
615
649
|
return checks.filter((entry) => entry.status === "fail").length;
|
|
616
650
|
}
|
|
617
651
|
|
|
652
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
653
|
+
import pc from "picocolors";
|
|
654
|
+
|
|
655
|
+
// packages/cli/src/commands/_spinner.ts
|
|
656
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
657
|
+
function createTtySpinner(input) {
|
|
658
|
+
const output = input.output ?? process.stdout;
|
|
659
|
+
const isTty = output.isTTY === true;
|
|
660
|
+
const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
|
|
661
|
+
let label = input.label;
|
|
662
|
+
let frame = 0;
|
|
663
|
+
let paused = false;
|
|
664
|
+
let stopped = false;
|
|
665
|
+
let lastPrintedLabel = "";
|
|
666
|
+
const render = () => {
|
|
667
|
+
if (stopped || paused)
|
|
668
|
+
return;
|
|
669
|
+
if (!isTty) {
|
|
670
|
+
if (label !== lastPrintedLabel) {
|
|
671
|
+
output.write(`${label}
|
|
672
|
+
`);
|
|
673
|
+
lastPrintedLabel = label;
|
|
674
|
+
}
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
frame = (frame + 1) % frames.length;
|
|
678
|
+
const glyph = frames[frame] ?? frames[0] ?? "";
|
|
679
|
+
output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
|
|
680
|
+
};
|
|
681
|
+
const clearLine = () => {
|
|
682
|
+
if (isTty)
|
|
683
|
+
output.write("\r\x1B[2K");
|
|
684
|
+
};
|
|
685
|
+
render();
|
|
686
|
+
const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
|
|
687
|
+
return {
|
|
688
|
+
setLabel(next) {
|
|
689
|
+
label = next;
|
|
690
|
+
render();
|
|
691
|
+
},
|
|
692
|
+
pause() {
|
|
693
|
+
paused = true;
|
|
694
|
+
clearLine();
|
|
695
|
+
},
|
|
696
|
+
resume() {
|
|
697
|
+
if (stopped)
|
|
698
|
+
return;
|
|
699
|
+
paused = false;
|
|
700
|
+
render();
|
|
701
|
+
},
|
|
702
|
+
stop(finalLine) {
|
|
703
|
+
if (stopped)
|
|
704
|
+
return;
|
|
705
|
+
stopped = true;
|
|
706
|
+
if (timer)
|
|
707
|
+
clearInterval(timer);
|
|
708
|
+
clearLine();
|
|
709
|
+
if (finalLine)
|
|
710
|
+
output.write(`${finalLine}
|
|
711
|
+
`);
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
717
|
+
var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
718
|
+
var DONE_SYMBOL = pc.green("\u25C7");
|
|
719
|
+
var FAIL_SYMBOL = pc.red("\u25A0");
|
|
720
|
+
var activeUpdate = null;
|
|
721
|
+
async function withSpinner(label, work, options = {}) {
|
|
722
|
+
if (options.outputMode === "json") {
|
|
723
|
+
return work(() => {});
|
|
724
|
+
}
|
|
725
|
+
if (activeUpdate) {
|
|
726
|
+
const outer = activeUpdate;
|
|
727
|
+
outer(label);
|
|
728
|
+
return work(outer);
|
|
729
|
+
}
|
|
730
|
+
const output = options.output ?? process.stderr;
|
|
731
|
+
const isTty = output.isTTY === true;
|
|
732
|
+
let lastLabel = label;
|
|
733
|
+
if (!isTty) {
|
|
734
|
+
output.write(`${label}
|
|
735
|
+
`);
|
|
736
|
+
const update2 = (next) => {
|
|
737
|
+
lastLabel = next;
|
|
738
|
+
};
|
|
739
|
+
activeUpdate = update2;
|
|
740
|
+
const previousListener2 = setServerPhaseListener(update2);
|
|
741
|
+
try {
|
|
742
|
+
return await work(update2);
|
|
743
|
+
} finally {
|
|
744
|
+
activeUpdate = null;
|
|
745
|
+
setServerPhaseListener(previousListener2);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
const spinner = createTtySpinner({
|
|
749
|
+
label,
|
|
750
|
+
output,
|
|
751
|
+
frames: CLACK_SPINNER_FRAMES,
|
|
752
|
+
styleFrame: (frame) => pc.magenta(frame)
|
|
753
|
+
});
|
|
754
|
+
const update = (next) => {
|
|
755
|
+
lastLabel = next;
|
|
756
|
+
spinner.setLabel(next);
|
|
757
|
+
};
|
|
758
|
+
activeUpdate = update;
|
|
759
|
+
const previousListener = setServerPhaseListener(update);
|
|
760
|
+
try {
|
|
761
|
+
const result = await work(update);
|
|
762
|
+
spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
|
|
763
|
+
return result;
|
|
764
|
+
} catch (error) {
|
|
765
|
+
spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
|
|
766
|
+
throw error;
|
|
767
|
+
} finally {
|
|
768
|
+
activeUpdate = null;
|
|
769
|
+
setServerPhaseListener(previousListener);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
618
773
|
// packages/cli/src/commands/doctor.ts
|
|
619
774
|
async function executeDoctor(context, args) {
|
|
620
775
|
requireNoExtraArgs(args, "rig doctor");
|
|
621
|
-
const checks = await runRigDoctorChecks({ projectRoot: context.projectRoot });
|
|
776
|
+
const checks = await withSpinner("Running doctor checks\u2026", (update) => runRigDoctorChecks({ projectRoot: context.projectRoot, onProgress: update }), { outputMode: context.outputMode });
|
|
622
777
|
if (context.outputMode === "text") {
|
|
623
778
|
console.log(formatDoctorChecks(checks));
|
|
624
779
|
}
|
|
@@ -142,6 +142,15 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
|
142
142
|
|
|
143
143
|
// packages/cli/src/commands/_server-client.ts
|
|
144
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
|
+
}
|
|
145
154
|
function cleanToken(value) {
|
|
146
155
|
const trimmed = value?.trim();
|
|
147
156
|
return trimmed ? trimmed : null;
|
|
@@ -184,6 +193,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
184
193
|
try {
|
|
185
194
|
const selected = resolveSelectedConnection(projectRoot);
|
|
186
195
|
if (selected?.connection.kind === "remote") {
|
|
196
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
187
197
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
188
198
|
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
189
199
|
return {
|
|
@@ -193,6 +203,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
193
203
|
serverProjectRoot
|
|
194
204
|
};
|
|
195
205
|
}
|
|
206
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
196
207
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
197
208
|
return {
|
|
198
209
|
baseUrl: connection.baseUrl,
|
|
@@ -299,6 +310,7 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
299
310
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
300
311
|
if (server.serverProjectRoot)
|
|
301
312
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
313
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
302
314
|
let response;
|
|
303
315
|
try {
|
|
304
316
|
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
@@ -339,6 +351,138 @@ async function postGitHubTokenViaServer(context, token, options = {}) {
|
|
|
339
351
|
});
|
|
340
352
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
341
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
|
+
}
|
|
342
486
|
|
|
343
487
|
// packages/cli/src/commands/github.ts
|
|
344
488
|
function printPayload(context, payload, fallback) {
|
|
@@ -367,7 +511,7 @@ async function executeGithub(context, args) {
|
|
|
367
511
|
case "status": {
|
|
368
512
|
if (rest.length > 0)
|
|
369
513
|
throw new CliError("Usage: rig github auth status", 1);
|
|
370
|
-
const status = await getGitHubAuthStatusViaServer(context);
|
|
514
|
+
const status = await withSpinner("Checking GitHub auth on the server\u2026", () => getGitHubAuthStatusViaServer(context), { outputMode: context.outputMode });
|
|
371
515
|
printPayload(context, status, `GitHub auth: ${status.authenticated ? "authenticated" : "unauthenticated"}`);
|
|
372
516
|
return { ok: true, group: "github", command: "auth status", details: status };
|
|
373
517
|
}
|
|
@@ -378,14 +522,15 @@ async function executeGithub(context, args) {
|
|
|
378
522
|
const token = parsed.value?.trim();
|
|
379
523
|
if (!token)
|
|
380
524
|
throw new CliError("Missing --token value.", 1, { hint: "Re-run as `rig github auth token --token <token>`." });
|
|
381
|
-
const result = await postGitHubTokenViaServer(context, token);
|
|
525
|
+
const result = await withSpinner("Storing GitHub token on the server\u2026", () => postGitHubTokenViaServer(context, token), { outputMode: context.outputMode });
|
|
382
526
|
printPayload(context, result, "GitHub token stored on the selected Rig server.");
|
|
383
527
|
return { ok: true, group: "github", command: "auth token", details: result };
|
|
384
528
|
}
|
|
385
529
|
case "import-gh": {
|
|
386
530
|
if (rest.length > 0)
|
|
387
531
|
throw new CliError("Usage: rig github auth import-gh", 1);
|
|
388
|
-
const
|
|
532
|
+
const importedToken = readGhToken();
|
|
533
|
+
const result = await withSpinner("Storing GitHub token on the server\u2026", () => postGitHubTokenViaServer(context, importedToken), { outputMode: context.outputMode });
|
|
389
534
|
printPayload(context, result, "GitHub token imported from gh and stored on the selected Rig server.");
|
|
390
535
|
return { ok: true, group: "github", command: "auth import-gh", details: result };
|
|
391
536
|
}
|