@h-rig/cli 0.0.6-alpha.67 → 0.0.6-alpha.68
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 +232 -71
- package/dist/src/commands/_async-ui.js +141 -0
- package/dist/src/commands/_doctor-checks.js +18 -0
- package/dist/src/commands/_operator-view.js +143 -4
- package/dist/src/commands/_pi-frontend.js +13 -1
- package/dist/src/commands/_preflight.js +7 -0
- package/dist/src/commands/_server-client.js +13 -0
- package/dist/src/commands/_snapshot-upload.js +7 -0
- package/dist/src/commands/_spinner.js +4 -2
- package/dist/src/commands/doctor.js +145 -1
- package/dist/src/commands/github.js +137 -3
- package/dist/src/commands/inbox.js +139 -6
- package/dist/src/commands/init.js +18 -0
- package/dist/src/commands/inspect.js +147 -8
- package/dist/src/commands/run.js +173 -31
- package/dist/src/commands/server.js +7 -0
- package/dist/src/commands/setup.js +150 -6
- package/dist/src/commands/stats.js +156 -22
- package/dist/src/commands/task-run-driver.js +7 -0
- package/dist/src/commands/task.js +186 -47
- package/dist/src/commands.js +232 -71
- package/dist/src/index.js +232 -71
- package/package.json +8 -8
|
@@ -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}`, {
|
|
@@ -518,7 +530,10 @@ async function runRigDoctorChecks(options) {
|
|
|
518
530
|
const bunVersion = options.bunVersion ?? Bun.version;
|
|
519
531
|
const request = options.requestJson ?? ((pathname, init) => requestServerJson({ projectRoot }, pathname, init));
|
|
520
532
|
const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
|
|
533
|
+
const progress = options.onProgress ?? (() => {});
|
|
534
|
+
progress("Checking local toolchain\u2026");
|
|
521
535
|
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`)."));
|
|
536
|
+
progress("Loading rig.config\u2026");
|
|
522
537
|
const loadedConfig = await loadConfig(projectRoot).catch(() => null);
|
|
523
538
|
const config = loadedConfig ?? loadFallbackConfig(projectRoot);
|
|
524
539
|
const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve4(projectRoot, name)));
|
|
@@ -537,6 +552,7 @@ async function runRigDoctorChecks(options) {
|
|
|
537
552
|
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
553
|
let server = null;
|
|
539
554
|
try {
|
|
555
|
+
progress("Connecting to the selected Rig server\u2026");
|
|
540
556
|
server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
|
|
541
557
|
checks.push(check("server", "Rig server reachable", "pass", `${server.connectionKind} ${server.baseUrl}`));
|
|
542
558
|
} catch (error) {
|
|
@@ -544,18 +560,21 @@ async function runRigDoctorChecks(options) {
|
|
|
544
560
|
}
|
|
545
561
|
if (server || options.requestJson) {
|
|
546
562
|
try {
|
|
563
|
+
progress("Checking server status\u2026");
|
|
547
564
|
const status = await request("/api/server/status");
|
|
548
565
|
checks.push(check("server-status", "server project status", "pass", JSON.stringify(status).slice(0, 180)));
|
|
549
566
|
} catch (error) {
|
|
550
567
|
checks.push(check("server-status", "server project status", "fail", errorMessage(error), "Run `rig doctor` after the selected server is reachable."));
|
|
551
568
|
}
|
|
552
569
|
try {
|
|
570
|
+
progress("Checking GitHub auth\u2026");
|
|
553
571
|
const auth = await request("/api/github/auth/status");
|
|
554
572
|
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
573
|
} catch (error) {
|
|
556
574
|
checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
|
|
557
575
|
}
|
|
558
576
|
try {
|
|
577
|
+
progress("Checking GitHub repo permissions\u2026");
|
|
559
578
|
const permissions = await request("/api/github/repo/permissions");
|
|
560
579
|
const allowed = permissionAllowsPr(permissions);
|
|
561
580
|
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 +582,7 @@ async function runRigDoctorChecks(options) {
|
|
|
563
582
|
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
583
|
}
|
|
565
584
|
try {
|
|
585
|
+
progress("Checking GitHub issue labels\u2026");
|
|
566
586
|
const labels = await request("/api/workspace/task-labels");
|
|
567
587
|
const ready = labelsReady(labels);
|
|
568
588
|
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 +590,7 @@ async function runRigDoctorChecks(options) {
|
|
|
570
590
|
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
591
|
}
|
|
572
592
|
try {
|
|
593
|
+
progress("Checking task projection\u2026");
|
|
573
594
|
const projection = await request("/api/workspace/task-projection");
|
|
574
595
|
checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
|
|
575
596
|
} catch (error) {
|
|
@@ -578,6 +599,7 @@ async function runRigDoctorChecks(options) {
|
|
|
578
599
|
const slug = projectStatusSlug(projectRoot, config);
|
|
579
600
|
if (slug) {
|
|
580
601
|
try {
|
|
602
|
+
progress("Checking server project checkout\u2026");
|
|
581
603
|
const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
|
|
582
604
|
checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
|
|
583
605
|
} catch (error) {
|
|
@@ -592,6 +614,7 @@ async function runRigDoctorChecks(options) {
|
|
|
592
614
|
}
|
|
593
615
|
checks.push(githubProjectsCheck(config));
|
|
594
616
|
checks.push(prMergeCheck(config));
|
|
617
|
+
progress("Checking Pi installation\u2026");
|
|
595
618
|
const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
|
|
596
619
|
ok: false,
|
|
597
620
|
label: "pi/pi-rig checks",
|
|
@@ -615,10 +638,131 @@ function countDoctorFailures(checks) {
|
|
|
615
638
|
return checks.filter((entry) => entry.status === "fail").length;
|
|
616
639
|
}
|
|
617
640
|
|
|
641
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
642
|
+
import pc from "picocolors";
|
|
643
|
+
|
|
644
|
+
// packages/cli/src/commands/_spinner.ts
|
|
645
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
646
|
+
function createTtySpinner(input) {
|
|
647
|
+
const output = input.output ?? process.stdout;
|
|
648
|
+
const isTty = output.isTTY === true;
|
|
649
|
+
const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
|
|
650
|
+
let label = input.label;
|
|
651
|
+
let frame = 0;
|
|
652
|
+
let paused = false;
|
|
653
|
+
let stopped = false;
|
|
654
|
+
let lastPrintedLabel = "";
|
|
655
|
+
const render = () => {
|
|
656
|
+
if (stopped || paused)
|
|
657
|
+
return;
|
|
658
|
+
if (!isTty) {
|
|
659
|
+
if (label !== lastPrintedLabel) {
|
|
660
|
+
output.write(`${label}
|
|
661
|
+
`);
|
|
662
|
+
lastPrintedLabel = label;
|
|
663
|
+
}
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
frame = (frame + 1) % frames.length;
|
|
667
|
+
const glyph = frames[frame] ?? frames[0] ?? "";
|
|
668
|
+
output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
|
|
669
|
+
};
|
|
670
|
+
const clearLine = () => {
|
|
671
|
+
if (isTty)
|
|
672
|
+
output.write("\r\x1B[2K");
|
|
673
|
+
};
|
|
674
|
+
render();
|
|
675
|
+
const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
|
|
676
|
+
return {
|
|
677
|
+
setLabel(next) {
|
|
678
|
+
label = next;
|
|
679
|
+
render();
|
|
680
|
+
},
|
|
681
|
+
pause() {
|
|
682
|
+
paused = true;
|
|
683
|
+
clearLine();
|
|
684
|
+
},
|
|
685
|
+
resume() {
|
|
686
|
+
if (stopped)
|
|
687
|
+
return;
|
|
688
|
+
paused = false;
|
|
689
|
+
render();
|
|
690
|
+
},
|
|
691
|
+
stop(finalLine) {
|
|
692
|
+
if (stopped)
|
|
693
|
+
return;
|
|
694
|
+
stopped = true;
|
|
695
|
+
if (timer)
|
|
696
|
+
clearInterval(timer);
|
|
697
|
+
clearLine();
|
|
698
|
+
if (finalLine)
|
|
699
|
+
output.write(`${finalLine}
|
|
700
|
+
`);
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
706
|
+
var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
707
|
+
var DONE_SYMBOL = pc.green("\u25C7");
|
|
708
|
+
var FAIL_SYMBOL = pc.red("\u25A0");
|
|
709
|
+
var activeUpdate = null;
|
|
710
|
+
async function withSpinner(label, work, options = {}) {
|
|
711
|
+
if (options.outputMode === "json") {
|
|
712
|
+
return work(() => {});
|
|
713
|
+
}
|
|
714
|
+
if (activeUpdate) {
|
|
715
|
+
const outer = activeUpdate;
|
|
716
|
+
outer(label);
|
|
717
|
+
return work(outer);
|
|
718
|
+
}
|
|
719
|
+
const output = options.output ?? process.stderr;
|
|
720
|
+
const isTty = output.isTTY === true;
|
|
721
|
+
let lastLabel = label;
|
|
722
|
+
if (!isTty) {
|
|
723
|
+
output.write(`${label}
|
|
724
|
+
`);
|
|
725
|
+
const update2 = (next) => {
|
|
726
|
+
lastLabel = next;
|
|
727
|
+
};
|
|
728
|
+
activeUpdate = update2;
|
|
729
|
+
const previousListener2 = setServerPhaseListener(update2);
|
|
730
|
+
try {
|
|
731
|
+
return await work(update2);
|
|
732
|
+
} finally {
|
|
733
|
+
activeUpdate = null;
|
|
734
|
+
setServerPhaseListener(previousListener2);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
const spinner = createTtySpinner({
|
|
738
|
+
label,
|
|
739
|
+
output,
|
|
740
|
+
frames: CLACK_SPINNER_FRAMES,
|
|
741
|
+
styleFrame: (frame) => pc.magenta(frame)
|
|
742
|
+
});
|
|
743
|
+
const update = (next) => {
|
|
744
|
+
lastLabel = next;
|
|
745
|
+
spinner.setLabel(next);
|
|
746
|
+
};
|
|
747
|
+
activeUpdate = update;
|
|
748
|
+
const previousListener = setServerPhaseListener(update);
|
|
749
|
+
try {
|
|
750
|
+
const result = await work(update);
|
|
751
|
+
spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
|
|
752
|
+
return result;
|
|
753
|
+
} catch (error) {
|
|
754
|
+
spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
|
|
755
|
+
throw error;
|
|
756
|
+
} finally {
|
|
757
|
+
activeUpdate = null;
|
|
758
|
+
setServerPhaseListener(previousListener);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
618
762
|
// packages/cli/src/commands/doctor.ts
|
|
619
763
|
async function executeDoctor(context, args) {
|
|
620
764
|
requireNoExtraArgs(args, "rig doctor");
|
|
621
|
-
const checks = await runRigDoctorChecks({ projectRoot: context.projectRoot });
|
|
765
|
+
const checks = await withSpinner("Running doctor checks\u2026", (update) => runRigDoctorChecks({ projectRoot: context.projectRoot, onProgress: update }), { outputMode: context.outputMode });
|
|
622
766
|
if (context.outputMode === "text") {
|
|
623
767
|
console.log(formatDoctorChecks(checks));
|
|
624
768
|
}
|
|
@@ -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}`, {
|
|
@@ -340,6 +352,127 @@ async function postGitHubTokenViaServer(context, token, options = {}) {
|
|
|
340
352
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
341
353
|
}
|
|
342
354
|
|
|
355
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
356
|
+
import pc from "picocolors";
|
|
357
|
+
|
|
358
|
+
// packages/cli/src/commands/_spinner.ts
|
|
359
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
360
|
+
function createTtySpinner(input) {
|
|
361
|
+
const output = input.output ?? process.stdout;
|
|
362
|
+
const isTty = output.isTTY === true;
|
|
363
|
+
const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
|
|
364
|
+
let label = input.label;
|
|
365
|
+
let frame = 0;
|
|
366
|
+
let paused = false;
|
|
367
|
+
let stopped = false;
|
|
368
|
+
let lastPrintedLabel = "";
|
|
369
|
+
const render = () => {
|
|
370
|
+
if (stopped || paused)
|
|
371
|
+
return;
|
|
372
|
+
if (!isTty) {
|
|
373
|
+
if (label !== lastPrintedLabel) {
|
|
374
|
+
output.write(`${label}
|
|
375
|
+
`);
|
|
376
|
+
lastPrintedLabel = label;
|
|
377
|
+
}
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
frame = (frame + 1) % frames.length;
|
|
381
|
+
const glyph = frames[frame] ?? frames[0] ?? "";
|
|
382
|
+
output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
|
|
383
|
+
};
|
|
384
|
+
const clearLine = () => {
|
|
385
|
+
if (isTty)
|
|
386
|
+
output.write("\r\x1B[2K");
|
|
387
|
+
};
|
|
388
|
+
render();
|
|
389
|
+
const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
|
|
390
|
+
return {
|
|
391
|
+
setLabel(next) {
|
|
392
|
+
label = next;
|
|
393
|
+
render();
|
|
394
|
+
},
|
|
395
|
+
pause() {
|
|
396
|
+
paused = true;
|
|
397
|
+
clearLine();
|
|
398
|
+
},
|
|
399
|
+
resume() {
|
|
400
|
+
if (stopped)
|
|
401
|
+
return;
|
|
402
|
+
paused = false;
|
|
403
|
+
render();
|
|
404
|
+
},
|
|
405
|
+
stop(finalLine) {
|
|
406
|
+
if (stopped)
|
|
407
|
+
return;
|
|
408
|
+
stopped = true;
|
|
409
|
+
if (timer)
|
|
410
|
+
clearInterval(timer);
|
|
411
|
+
clearLine();
|
|
412
|
+
if (finalLine)
|
|
413
|
+
output.write(`${finalLine}
|
|
414
|
+
`);
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
420
|
+
var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
421
|
+
var DONE_SYMBOL = pc.green("\u25C7");
|
|
422
|
+
var FAIL_SYMBOL = pc.red("\u25A0");
|
|
423
|
+
var activeUpdate = null;
|
|
424
|
+
async function withSpinner(label, work, options = {}) {
|
|
425
|
+
if (options.outputMode === "json") {
|
|
426
|
+
return work(() => {});
|
|
427
|
+
}
|
|
428
|
+
if (activeUpdate) {
|
|
429
|
+
const outer = activeUpdate;
|
|
430
|
+
outer(label);
|
|
431
|
+
return work(outer);
|
|
432
|
+
}
|
|
433
|
+
const output = options.output ?? process.stderr;
|
|
434
|
+
const isTty = output.isTTY === true;
|
|
435
|
+
let lastLabel = label;
|
|
436
|
+
if (!isTty) {
|
|
437
|
+
output.write(`${label}
|
|
438
|
+
`);
|
|
439
|
+
const update2 = (next) => {
|
|
440
|
+
lastLabel = next;
|
|
441
|
+
};
|
|
442
|
+
activeUpdate = update2;
|
|
443
|
+
const previousListener2 = setServerPhaseListener(update2);
|
|
444
|
+
try {
|
|
445
|
+
return await work(update2);
|
|
446
|
+
} finally {
|
|
447
|
+
activeUpdate = null;
|
|
448
|
+
setServerPhaseListener(previousListener2);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const spinner = createTtySpinner({
|
|
452
|
+
label,
|
|
453
|
+
output,
|
|
454
|
+
frames: CLACK_SPINNER_FRAMES,
|
|
455
|
+
styleFrame: (frame) => pc.magenta(frame)
|
|
456
|
+
});
|
|
457
|
+
const update = (next) => {
|
|
458
|
+
lastLabel = next;
|
|
459
|
+
spinner.setLabel(next);
|
|
460
|
+
};
|
|
461
|
+
activeUpdate = update;
|
|
462
|
+
const previousListener = setServerPhaseListener(update);
|
|
463
|
+
try {
|
|
464
|
+
const result = await work(update);
|
|
465
|
+
spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
|
|
466
|
+
return result;
|
|
467
|
+
} catch (error) {
|
|
468
|
+
spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
|
|
469
|
+
throw error;
|
|
470
|
+
} finally {
|
|
471
|
+
activeUpdate = null;
|
|
472
|
+
setServerPhaseListener(previousListener);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
343
476
|
// packages/cli/src/commands/github.ts
|
|
344
477
|
function printPayload(context, payload, fallback) {
|
|
345
478
|
if (context.outputMode === "json")
|
|
@@ -367,7 +500,7 @@ async function executeGithub(context, args) {
|
|
|
367
500
|
case "status": {
|
|
368
501
|
if (rest.length > 0)
|
|
369
502
|
throw new CliError("Usage: rig github auth status", 1);
|
|
370
|
-
const status = await getGitHubAuthStatusViaServer(context);
|
|
503
|
+
const status = await withSpinner("Checking GitHub auth on the server\u2026", () => getGitHubAuthStatusViaServer(context), { outputMode: context.outputMode });
|
|
371
504
|
printPayload(context, status, `GitHub auth: ${status.authenticated ? "authenticated" : "unauthenticated"}`);
|
|
372
505
|
return { ok: true, group: "github", command: "auth status", details: status };
|
|
373
506
|
}
|
|
@@ -378,14 +511,15 @@ async function executeGithub(context, args) {
|
|
|
378
511
|
const token = parsed.value?.trim();
|
|
379
512
|
if (!token)
|
|
380
513
|
throw new CliError("Missing --token value.", 1, { hint: "Re-run as `rig github auth token --token <token>`." });
|
|
381
|
-
const result = await postGitHubTokenViaServer(context, token);
|
|
514
|
+
const result = await withSpinner("Storing GitHub token on the server\u2026", () => postGitHubTokenViaServer(context, token), { outputMode: context.outputMode });
|
|
382
515
|
printPayload(context, result, "GitHub token stored on the selected Rig server.");
|
|
383
516
|
return { ok: true, group: "github", command: "auth token", details: result };
|
|
384
517
|
}
|
|
385
518
|
case "import-gh": {
|
|
386
519
|
if (rest.length > 0)
|
|
387
520
|
throw new CliError("Usage: rig github auth import-gh", 1);
|
|
388
|
-
const
|
|
521
|
+
const importedToken = readGhToken();
|
|
522
|
+
const result = await withSpinner("Storing GitHub token on the server\u2026", () => postGitHubTokenViaServer(context, importedToken), { outputMode: context.outputMode });
|
|
389
523
|
printPayload(context, result, "GitHub token imported from gh and stored on the selected Rig server.");
|
|
390
524
|
return { ok: true, group: "github", command: "auth import-gh", details: result };
|
|
391
525
|
}
|
|
@@ -227,6 +227,15 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
|
227
227
|
|
|
228
228
|
// packages/cli/src/commands/_server-client.ts
|
|
229
229
|
var scopedGitHubBearerTokens = new Map;
|
|
230
|
+
var serverPhaseListener = null;
|
|
231
|
+
function setServerPhaseListener(listener) {
|
|
232
|
+
const previous = serverPhaseListener;
|
|
233
|
+
serverPhaseListener = listener;
|
|
234
|
+
return previous;
|
|
235
|
+
}
|
|
236
|
+
function reportServerPhase(label) {
|
|
237
|
+
serverPhaseListener?.(label);
|
|
238
|
+
}
|
|
230
239
|
function cleanToken(value) {
|
|
231
240
|
const trimmed = value?.trim();
|
|
232
241
|
return trimmed ? trimmed : null;
|
|
@@ -269,6 +278,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
269
278
|
try {
|
|
270
279
|
const selected = resolveSelectedConnection(projectRoot);
|
|
271
280
|
if (selected?.connection.kind === "remote") {
|
|
281
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
272
282
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
273
283
|
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
274
284
|
return {
|
|
@@ -278,6 +288,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
278
288
|
serverProjectRoot
|
|
279
289
|
};
|
|
280
290
|
}
|
|
291
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
281
292
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
282
293
|
return {
|
|
283
294
|
baseUrl: connection.baseUrl,
|
|
@@ -384,6 +395,7 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
384
395
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
385
396
|
if (server.serverProjectRoot)
|
|
386
397
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
398
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
387
399
|
let response;
|
|
388
400
|
try {
|
|
389
401
|
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
@@ -413,6 +425,127 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
|
|
|
413
425
|
return payload;
|
|
414
426
|
}
|
|
415
427
|
|
|
428
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
429
|
+
import pc2 from "picocolors";
|
|
430
|
+
|
|
431
|
+
// packages/cli/src/commands/_spinner.ts
|
|
432
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
433
|
+
function createTtySpinner(input) {
|
|
434
|
+
const output = input.output ?? process.stdout;
|
|
435
|
+
const isTty = output.isTTY === true;
|
|
436
|
+
const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
|
|
437
|
+
let label = input.label;
|
|
438
|
+
let frame = 0;
|
|
439
|
+
let paused = false;
|
|
440
|
+
let stopped = false;
|
|
441
|
+
let lastPrintedLabel = "";
|
|
442
|
+
const render = () => {
|
|
443
|
+
if (stopped || paused)
|
|
444
|
+
return;
|
|
445
|
+
if (!isTty) {
|
|
446
|
+
if (label !== lastPrintedLabel) {
|
|
447
|
+
output.write(`${label}
|
|
448
|
+
`);
|
|
449
|
+
lastPrintedLabel = label;
|
|
450
|
+
}
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
frame = (frame + 1) % frames.length;
|
|
454
|
+
const glyph = frames[frame] ?? frames[0] ?? "";
|
|
455
|
+
output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
|
|
456
|
+
};
|
|
457
|
+
const clearLine = () => {
|
|
458
|
+
if (isTty)
|
|
459
|
+
output.write("\r\x1B[2K");
|
|
460
|
+
};
|
|
461
|
+
render();
|
|
462
|
+
const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
|
|
463
|
+
return {
|
|
464
|
+
setLabel(next) {
|
|
465
|
+
label = next;
|
|
466
|
+
render();
|
|
467
|
+
},
|
|
468
|
+
pause() {
|
|
469
|
+
paused = true;
|
|
470
|
+
clearLine();
|
|
471
|
+
},
|
|
472
|
+
resume() {
|
|
473
|
+
if (stopped)
|
|
474
|
+
return;
|
|
475
|
+
paused = false;
|
|
476
|
+
render();
|
|
477
|
+
},
|
|
478
|
+
stop(finalLine) {
|
|
479
|
+
if (stopped)
|
|
480
|
+
return;
|
|
481
|
+
stopped = true;
|
|
482
|
+
if (timer)
|
|
483
|
+
clearInterval(timer);
|
|
484
|
+
clearLine();
|
|
485
|
+
if (finalLine)
|
|
486
|
+
output.write(`${finalLine}
|
|
487
|
+
`);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
493
|
+
var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
494
|
+
var DONE_SYMBOL = pc2.green("\u25C7");
|
|
495
|
+
var FAIL_SYMBOL = pc2.red("\u25A0");
|
|
496
|
+
var activeUpdate = null;
|
|
497
|
+
async function withSpinner(label, work, options = {}) {
|
|
498
|
+
if (options.outputMode === "json") {
|
|
499
|
+
return work(() => {});
|
|
500
|
+
}
|
|
501
|
+
if (activeUpdate) {
|
|
502
|
+
const outer = activeUpdate;
|
|
503
|
+
outer(label);
|
|
504
|
+
return work(outer);
|
|
505
|
+
}
|
|
506
|
+
const output = options.output ?? process.stderr;
|
|
507
|
+
const isTty = output.isTTY === true;
|
|
508
|
+
let lastLabel = label;
|
|
509
|
+
if (!isTty) {
|
|
510
|
+
output.write(`${label}
|
|
511
|
+
`);
|
|
512
|
+
const update2 = (next) => {
|
|
513
|
+
lastLabel = next;
|
|
514
|
+
};
|
|
515
|
+
activeUpdate = update2;
|
|
516
|
+
const previousListener2 = setServerPhaseListener(update2);
|
|
517
|
+
try {
|
|
518
|
+
return await work(update2);
|
|
519
|
+
} finally {
|
|
520
|
+
activeUpdate = null;
|
|
521
|
+
setServerPhaseListener(previousListener2);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const spinner = createTtySpinner({
|
|
525
|
+
label,
|
|
526
|
+
output,
|
|
527
|
+
frames: CLACK_SPINNER_FRAMES,
|
|
528
|
+
styleFrame: (frame) => pc2.magenta(frame)
|
|
529
|
+
});
|
|
530
|
+
const update = (next) => {
|
|
531
|
+
lastLabel = next;
|
|
532
|
+
spinner.setLabel(next);
|
|
533
|
+
};
|
|
534
|
+
activeUpdate = update;
|
|
535
|
+
const previousListener = setServerPhaseListener(update);
|
|
536
|
+
try {
|
|
537
|
+
const result = await work(update);
|
|
538
|
+
spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
|
|
539
|
+
return result;
|
|
540
|
+
} catch (error) {
|
|
541
|
+
spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
|
|
542
|
+
throw error;
|
|
543
|
+
} finally {
|
|
544
|
+
activeUpdate = null;
|
|
545
|
+
setServerPhaseListener(previousListener);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
416
549
|
// packages/cli/src/commands/inbox.ts
|
|
417
550
|
async function listInboxRecords(context, kind, filters) {
|
|
418
551
|
const params = new URLSearchParams;
|
|
@@ -517,7 +650,7 @@ async function executeInbox(context, args) {
|
|
|
517
650
|
const task = takeOption(pending, "--task");
|
|
518
651
|
pending = task.rest;
|
|
519
652
|
requireNoExtraArgs(pending, "rig inbox approvals [--run <id>] [--task <id>]");
|
|
520
|
-
const approvals = await listInboxRecords(context, "approvals", { run: run.value, task: task.value });
|
|
653
|
+
const approvals = await withSpinner("Reading approvals from server\u2026", () => listInboxRecords(context, "approvals", { run: run.value, task: task.value }), { outputMode: context.outputMode });
|
|
521
654
|
renderList(context, "approvals", approvals);
|
|
522
655
|
return { ok: true, group: "inbox", command, details: { approvals } };
|
|
523
656
|
}
|
|
@@ -528,7 +661,7 @@ async function executeInbox(context, args) {
|
|
|
528
661
|
const task = takeOption(pending, "--task");
|
|
529
662
|
pending = task.rest;
|
|
530
663
|
requireNoExtraArgs(pending, "rig inbox inputs [--run <id>] [--task <id>]");
|
|
531
|
-
const requests = await listInboxRecords(context, "inputs", { run: run.value, task: task.value });
|
|
664
|
+
const requests = await withSpinner("Reading input requests from server\u2026", () => listInboxRecords(context, "inputs", { run: run.value, task: task.value }), { outputMode: context.outputMode });
|
|
532
665
|
renderList(context, "inputs", requests);
|
|
533
666
|
return { ok: true, group: "inbox", command, details: { requests } };
|
|
534
667
|
}
|
|
@@ -549,7 +682,7 @@ async function executeInbox(context, args) {
|
|
|
549
682
|
if (decision.value !== "approve" && decision.value !== "reject") {
|
|
550
683
|
throw new CliError("decision must be approve or reject.", 1, { hint: "Re-run as `rig inbox approve --run <id> --request <id> --decision approve|reject`." });
|
|
551
684
|
}
|
|
552
|
-
const result = await requestServerJson(context, "/api/inbox/approvals/resolve", {
|
|
685
|
+
const result = await withSpinner(`Resolving approval ${request.value}\u2026`, () => requestServerJson(context, "/api/inbox/approvals/resolve", {
|
|
553
686
|
method: "POST",
|
|
554
687
|
headers: { "content-type": "application/json" },
|
|
555
688
|
body: JSON.stringify({
|
|
@@ -558,7 +691,7 @@ async function executeInbox(context, args) {
|
|
|
558
691
|
decision: decision.value,
|
|
559
692
|
note: note2.value ?? null
|
|
560
693
|
})
|
|
561
|
-
});
|
|
694
|
+
}), { outputMode: context.outputMode });
|
|
562
695
|
return { ok: true, group: "inbox", command, details: { result } };
|
|
563
696
|
}
|
|
564
697
|
case "respond": {
|
|
@@ -592,7 +725,7 @@ async function executeInbox(context, args) {
|
|
|
592
725
|
const [key, ...restValue] = entry.split("=");
|
|
593
726
|
return [key, restValue.join("=")];
|
|
594
727
|
}));
|
|
595
|
-
const result = await requestServerJson(context, "/api/inbox/inputs/respond", {
|
|
728
|
+
const result = await withSpinner(`Sending response for ${request.value}\u2026`, () => requestServerJson(context, "/api/inbox/inputs/respond", {
|
|
596
729
|
method: "POST",
|
|
597
730
|
headers: { "content-type": "application/json" },
|
|
598
731
|
body: JSON.stringify({
|
|
@@ -600,7 +733,7 @@ async function executeInbox(context, args) {
|
|
|
600
733
|
requestId: request.value,
|
|
601
734
|
answers: parsedAnswers
|
|
602
735
|
})
|
|
603
|
-
});
|
|
736
|
+
}), { outputMode: context.outputMode });
|
|
604
737
|
return { ok: true, group: "inbox", command, details: { result } };
|
|
605
738
|
}
|
|
606
739
|
case "watch": {
|