@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
|
@@ -285,6 +285,15 @@ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
|
285
285
|
import { resolve as resolve4 } from "path";
|
|
286
286
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
287
287
|
var scopedGitHubBearerTokens = new Map;
|
|
288
|
+
var serverPhaseListener = null;
|
|
289
|
+
function setServerPhaseListener(listener) {
|
|
290
|
+
const previous = serverPhaseListener;
|
|
291
|
+
serverPhaseListener = listener;
|
|
292
|
+
return previous;
|
|
293
|
+
}
|
|
294
|
+
function reportServerPhase(label) {
|
|
295
|
+
serverPhaseListener?.(label);
|
|
296
|
+
}
|
|
288
297
|
function cleanToken(value) {
|
|
289
298
|
const trimmed = value?.trim();
|
|
290
299
|
return trimmed ? trimmed : null;
|
|
@@ -327,6 +336,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
327
336
|
try {
|
|
328
337
|
const selected = resolveSelectedConnection(projectRoot);
|
|
329
338
|
if (selected?.connection.kind === "remote") {
|
|
339
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
330
340
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
331
341
|
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
332
342
|
return {
|
|
@@ -336,6 +346,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
336
346
|
serverProjectRoot
|
|
337
347
|
};
|
|
338
348
|
}
|
|
349
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
339
350
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
340
351
|
return {
|
|
341
352
|
baseUrl: connection.baseUrl,
|
|
@@ -442,6 +453,7 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
442
453
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
443
454
|
if (server.serverProjectRoot)
|
|
444
455
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
456
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
445
457
|
let response;
|
|
446
458
|
try {
|
|
447
459
|
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
@@ -470,6 +482,17 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
|
|
|
470
482
|
}
|
|
471
483
|
return payload;
|
|
472
484
|
}
|
|
485
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
486
|
+
"created",
|
|
487
|
+
"preparing",
|
|
488
|
+
"running",
|
|
489
|
+
"validating",
|
|
490
|
+
"reviewing",
|
|
491
|
+
"stopped",
|
|
492
|
+
"failed",
|
|
493
|
+
"needs-attention",
|
|
494
|
+
"needs_attention"
|
|
495
|
+
]);
|
|
473
496
|
|
|
474
497
|
// packages/cli/src/commands/_doctor-checks.ts
|
|
475
498
|
function check(id, label, status, detail, remediation) {
|
|
@@ -590,7 +613,10 @@ async function runRigDoctorChecks(options) {
|
|
|
590
613
|
const bunVersion = options.bunVersion ?? Bun.version;
|
|
591
614
|
const request = options.requestJson ?? ((pathname, init) => requestServerJson({ projectRoot }, pathname, init));
|
|
592
615
|
const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
|
|
616
|
+
const progress = options.onProgress ?? (() => {});
|
|
617
|
+
progress("Checking local toolchain\u2026");
|
|
593
618
|
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`)."));
|
|
619
|
+
progress("Loading rig.config\u2026");
|
|
594
620
|
const loadedConfig = await loadConfig(projectRoot).catch(() => null);
|
|
595
621
|
const config = loadedConfig ?? loadFallbackConfig(projectRoot);
|
|
596
622
|
const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve5(projectRoot, name)));
|
|
@@ -609,6 +635,7 @@ async function runRigDoctorChecks(options) {
|
|
|
609
635
|
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));
|
|
610
636
|
let server = null;
|
|
611
637
|
try {
|
|
638
|
+
progress("Connecting to the selected Rig server\u2026");
|
|
612
639
|
server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
|
|
613
640
|
checks.push(check("server", "Rig server reachable", "pass", `${server.connectionKind} ${server.baseUrl}`));
|
|
614
641
|
} catch (error) {
|
|
@@ -616,18 +643,21 @@ async function runRigDoctorChecks(options) {
|
|
|
616
643
|
}
|
|
617
644
|
if (server || options.requestJson) {
|
|
618
645
|
try {
|
|
646
|
+
progress("Checking server status\u2026");
|
|
619
647
|
const status = await request("/api/server/status");
|
|
620
648
|
checks.push(check("server-status", "server project status", "pass", JSON.stringify(status).slice(0, 180)));
|
|
621
649
|
} catch (error) {
|
|
622
650
|
checks.push(check("server-status", "server project status", "fail", errorMessage(error), "Run `rig doctor` after the selected server is reachable."));
|
|
623
651
|
}
|
|
624
652
|
try {
|
|
653
|
+
progress("Checking GitHub auth\u2026");
|
|
625
654
|
const auth = await request("/api/github/auth/status");
|
|
626
655
|
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>`."));
|
|
627
656
|
} catch (error) {
|
|
628
657
|
checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
|
|
629
658
|
}
|
|
630
659
|
try {
|
|
660
|
+
progress("Checking GitHub repo permissions\u2026");
|
|
631
661
|
const permissions = await request("/api/github/repo/permissions");
|
|
632
662
|
const allowed = permissionAllowsPr(permissions);
|
|
633
663
|
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."));
|
|
@@ -635,6 +665,7 @@ async function runRigDoctorChecks(options) {
|
|
|
635
665
|
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."));
|
|
636
666
|
}
|
|
637
667
|
try {
|
|
668
|
+
progress("Checking GitHub issue labels\u2026");
|
|
638
669
|
const labels = await request("/api/workspace/task-labels");
|
|
639
670
|
const ready = labelsReady(labels);
|
|
640
671
|
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."));
|
|
@@ -642,6 +673,7 @@ async function runRigDoctorChecks(options) {
|
|
|
642
673
|
checks.push(check("task-labels", "GitHub issue labels", "warn", errorMessage(error), "Run `rig init`/`rig doctor` after label setup is wired on the server."));
|
|
643
674
|
}
|
|
644
675
|
try {
|
|
676
|
+
progress("Checking task projection\u2026");
|
|
645
677
|
const projection = await request("/api/workspace/task-projection");
|
|
646
678
|
checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
|
|
647
679
|
} catch (error) {
|
|
@@ -650,6 +682,7 @@ async function runRigDoctorChecks(options) {
|
|
|
650
682
|
const slug = projectStatusSlug(projectRoot, config);
|
|
651
683
|
if (slug) {
|
|
652
684
|
try {
|
|
685
|
+
progress("Checking server project checkout\u2026");
|
|
653
686
|
const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
|
|
654
687
|
checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
|
|
655
688
|
} catch (error) {
|
|
@@ -664,6 +697,7 @@ async function runRigDoctorChecks(options) {
|
|
|
664
697
|
}
|
|
665
698
|
checks.push(githubProjectsCheck(config));
|
|
666
699
|
checks.push(prMergeCheck(config));
|
|
700
|
+
progress("Checking Pi installation\u2026");
|
|
667
701
|
const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
|
|
668
702
|
ok: false,
|
|
669
703
|
label: "pi/pi-rig checks",
|
|
@@ -687,6 +721,127 @@ function countDoctorFailures(checks) {
|
|
|
687
721
|
return checks.filter((entry) => entry.status === "fail").length;
|
|
688
722
|
}
|
|
689
723
|
|
|
724
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
725
|
+
import pc from "picocolors";
|
|
726
|
+
|
|
727
|
+
// packages/cli/src/commands/_spinner.ts
|
|
728
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
729
|
+
function createTtySpinner(input) {
|
|
730
|
+
const output = input.output ?? process.stdout;
|
|
731
|
+
const isTty = output.isTTY === true;
|
|
732
|
+
const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
|
|
733
|
+
let label = input.label;
|
|
734
|
+
let frame = 0;
|
|
735
|
+
let paused = false;
|
|
736
|
+
let stopped = false;
|
|
737
|
+
let lastPrintedLabel = "";
|
|
738
|
+
const render = () => {
|
|
739
|
+
if (stopped || paused)
|
|
740
|
+
return;
|
|
741
|
+
if (!isTty) {
|
|
742
|
+
if (label !== lastPrintedLabel) {
|
|
743
|
+
output.write(`${label}
|
|
744
|
+
`);
|
|
745
|
+
lastPrintedLabel = label;
|
|
746
|
+
}
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
frame = (frame + 1) % frames.length;
|
|
750
|
+
const glyph = frames[frame] ?? frames[0] ?? "";
|
|
751
|
+
output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
|
|
752
|
+
};
|
|
753
|
+
const clearLine = () => {
|
|
754
|
+
if (isTty)
|
|
755
|
+
output.write("\r\x1B[2K");
|
|
756
|
+
};
|
|
757
|
+
render();
|
|
758
|
+
const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
|
|
759
|
+
return {
|
|
760
|
+
setLabel(next) {
|
|
761
|
+
label = next;
|
|
762
|
+
render();
|
|
763
|
+
},
|
|
764
|
+
pause() {
|
|
765
|
+
paused = true;
|
|
766
|
+
clearLine();
|
|
767
|
+
},
|
|
768
|
+
resume() {
|
|
769
|
+
if (stopped)
|
|
770
|
+
return;
|
|
771
|
+
paused = false;
|
|
772
|
+
render();
|
|
773
|
+
},
|
|
774
|
+
stop(finalLine) {
|
|
775
|
+
if (stopped)
|
|
776
|
+
return;
|
|
777
|
+
stopped = true;
|
|
778
|
+
if (timer)
|
|
779
|
+
clearInterval(timer);
|
|
780
|
+
clearLine();
|
|
781
|
+
if (finalLine)
|
|
782
|
+
output.write(`${finalLine}
|
|
783
|
+
`);
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
789
|
+
var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
790
|
+
var DONE_SYMBOL = pc.green("\u25C7");
|
|
791
|
+
var FAIL_SYMBOL = pc.red("\u25A0");
|
|
792
|
+
var activeUpdate = null;
|
|
793
|
+
async function withSpinner(label, work, options = {}) {
|
|
794
|
+
if (options.outputMode === "json") {
|
|
795
|
+
return work(() => {});
|
|
796
|
+
}
|
|
797
|
+
if (activeUpdate) {
|
|
798
|
+
const outer = activeUpdate;
|
|
799
|
+
outer(label);
|
|
800
|
+
return work(outer);
|
|
801
|
+
}
|
|
802
|
+
const output = options.output ?? process.stderr;
|
|
803
|
+
const isTty = output.isTTY === true;
|
|
804
|
+
let lastLabel = label;
|
|
805
|
+
if (!isTty) {
|
|
806
|
+
output.write(`${label}
|
|
807
|
+
`);
|
|
808
|
+
const update2 = (next) => {
|
|
809
|
+
lastLabel = next;
|
|
810
|
+
};
|
|
811
|
+
activeUpdate = update2;
|
|
812
|
+
const previousListener2 = setServerPhaseListener(update2);
|
|
813
|
+
try {
|
|
814
|
+
return await work(update2);
|
|
815
|
+
} finally {
|
|
816
|
+
activeUpdate = null;
|
|
817
|
+
setServerPhaseListener(previousListener2);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
const spinner = createTtySpinner({
|
|
821
|
+
label,
|
|
822
|
+
output,
|
|
823
|
+
frames: CLACK_SPINNER_FRAMES,
|
|
824
|
+
styleFrame: (frame) => pc.magenta(frame)
|
|
825
|
+
});
|
|
826
|
+
const update = (next) => {
|
|
827
|
+
lastLabel = next;
|
|
828
|
+
spinner.setLabel(next);
|
|
829
|
+
};
|
|
830
|
+
activeUpdate = update;
|
|
831
|
+
const previousListener = setServerPhaseListener(update);
|
|
832
|
+
try {
|
|
833
|
+
const result = await work(update);
|
|
834
|
+
spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
|
|
835
|
+
return result;
|
|
836
|
+
} catch (error) {
|
|
837
|
+
spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
|
|
838
|
+
throw error;
|
|
839
|
+
} finally {
|
|
840
|
+
activeUpdate = null;
|
|
841
|
+
setServerPhaseListener(previousListener);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
690
845
|
// packages/cli/src/commands/setup.ts
|
|
691
846
|
async function executeSetup(context, args) {
|
|
692
847
|
const [command = "check", ...rest] = args;
|
|
@@ -717,7 +872,7 @@ async function executeSetup(context, args) {
|
|
|
717
872
|
case "check":
|
|
718
873
|
requireNoExtraArgs(rest, `rig setup ${command}`);
|
|
719
874
|
{
|
|
720
|
-
const checks = await withMutedConsole(context.outputMode === "json", () => runSetupCheck(context.projectRoot));
|
|
875
|
+
const checks = await withMutedConsole(context.outputMode === "json", () => runSetupCheck(context.projectRoot, context.outputMode));
|
|
721
876
|
return { ok: true, group: "setup", command, details: { checks, failures: countDoctorFailures(checks) } };
|
|
722
877
|
}
|
|
723
878
|
case "setup":
|
|
@@ -726,7 +881,7 @@ async function executeSetup(context, args) {
|
|
|
726
881
|
return { ok: true, group: "setup", command };
|
|
727
882
|
case "preflight":
|
|
728
883
|
requireNoExtraArgs(rest, "rig setup preflight");
|
|
729
|
-
await withMutedConsole(context.outputMode === "json", () => runSetupPreflight(context.projectRoot));
|
|
884
|
+
await withMutedConsole(context.outputMode === "json", () => runSetupPreflight(context.projectRoot, context.outputMode));
|
|
730
885
|
return { ok: true, group: "setup", command };
|
|
731
886
|
default:
|
|
732
887
|
throw new CliError(`Unknown setup command: ${command}`, 1, { hint: "Run `rig setup --help` \u2014 commands are bootstrap|check|preflight." });
|
|
@@ -763,8 +918,8 @@ function runSetupInit(projectRoot) {
|
|
|
763
918
|
}
|
|
764
919
|
console.log("Harness directories ready.");
|
|
765
920
|
}
|
|
766
|
-
async function runSetupCheck(projectRoot) {
|
|
767
|
-
const doctorChecks = await runRigDoctorChecks({ projectRoot });
|
|
921
|
+
async function runSetupCheck(projectRoot, outputMode = "text") {
|
|
922
|
+
const doctorChecks = await withSpinner("Running setup checks\u2026", (update) => runRigDoctorChecks({ projectRoot, onProgress: update }), { outputMode });
|
|
768
923
|
console.log(formatDoctorChecks(doctorChecks));
|
|
769
924
|
const failures = countDoctorFailures(doctorChecks);
|
|
770
925
|
if (failures > 0) {
|
|
@@ -772,8 +927,8 @@ async function runSetupCheck(projectRoot) {
|
|
|
772
927
|
}
|
|
773
928
|
return doctorChecks;
|
|
774
929
|
}
|
|
775
|
-
async function runSetupPreflight(projectRoot) {
|
|
776
|
-
await runSetupCheck(projectRoot);
|
|
930
|
+
async function runSetupPreflight(projectRoot, outputMode = "text") {
|
|
931
|
+
await runSetupCheck(projectRoot, outputMode);
|
|
777
932
|
const validationRoot = resolve6(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
|
|
778
933
|
if (existsSync5(validationRoot)) {
|
|
779
934
|
const validators = readdirSync(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/commands/stats.ts
|
|
3
|
-
import
|
|
3
|
+
import pc4 from "picocolors";
|
|
4
4
|
|
|
5
5
|
// packages/cli/src/runner.ts
|
|
6
6
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
@@ -152,6 +152,15 @@ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
|
152
152
|
import { resolve as resolve2 } from "path";
|
|
153
153
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
154
154
|
var scopedGitHubBearerTokens = new Map;
|
|
155
|
+
var serverPhaseListener = null;
|
|
156
|
+
function setServerPhaseListener(listener) {
|
|
157
|
+
const previous = serverPhaseListener;
|
|
158
|
+
serverPhaseListener = listener;
|
|
159
|
+
return previous;
|
|
160
|
+
}
|
|
161
|
+
function reportServerPhase(label) {
|
|
162
|
+
serverPhaseListener?.(label);
|
|
163
|
+
}
|
|
155
164
|
function cleanToken(value) {
|
|
156
165
|
const trimmed = value?.trim();
|
|
157
166
|
return trimmed ? trimmed : null;
|
|
@@ -194,6 +203,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
194
203
|
try {
|
|
195
204
|
const selected = resolveSelectedConnection(projectRoot);
|
|
196
205
|
if (selected?.connection.kind === "remote") {
|
|
206
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
197
207
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
198
208
|
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
199
209
|
return {
|
|
@@ -203,6 +213,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
203
213
|
serverProjectRoot
|
|
204
214
|
};
|
|
205
215
|
}
|
|
216
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
206
217
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
207
218
|
return {
|
|
208
219
|
baseUrl: connection.baseUrl,
|
|
@@ -309,6 +320,7 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
309
320
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
310
321
|
if (server.serverProjectRoot)
|
|
311
322
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
323
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
312
324
|
let response;
|
|
313
325
|
try {
|
|
314
326
|
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
@@ -337,6 +349,17 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
|
|
|
337
349
|
}
|
|
338
350
|
return payload;
|
|
339
351
|
}
|
|
352
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
353
|
+
"created",
|
|
354
|
+
"preparing",
|
|
355
|
+
"running",
|
|
356
|
+
"validating",
|
|
357
|
+
"reviewing",
|
|
358
|
+
"stopped",
|
|
359
|
+
"failed",
|
|
360
|
+
"needs-attention",
|
|
361
|
+
"needs_attention"
|
|
362
|
+
]);
|
|
340
363
|
|
|
341
364
|
// packages/cli/src/commands/_cli-format.ts
|
|
342
365
|
import { log, note } from "@clack/prompts";
|
|
@@ -363,9 +386,130 @@ function formatNextSteps(steps) {
|
|
|
363
386
|
return [pc.bold("Next"), ...steps.map((step) => `${pc.dim("\u203A")} ${step}`)];
|
|
364
387
|
}
|
|
365
388
|
|
|
389
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
390
|
+
import pc2 from "picocolors";
|
|
391
|
+
|
|
392
|
+
// packages/cli/src/commands/_spinner.ts
|
|
393
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
394
|
+
function createTtySpinner(input) {
|
|
395
|
+
const output = input.output ?? process.stdout;
|
|
396
|
+
const isTty = output.isTTY === true;
|
|
397
|
+
const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
|
|
398
|
+
let label = input.label;
|
|
399
|
+
let frame = 0;
|
|
400
|
+
let paused = false;
|
|
401
|
+
let stopped = false;
|
|
402
|
+
let lastPrintedLabel = "";
|
|
403
|
+
const render = () => {
|
|
404
|
+
if (stopped || paused)
|
|
405
|
+
return;
|
|
406
|
+
if (!isTty) {
|
|
407
|
+
if (label !== lastPrintedLabel) {
|
|
408
|
+
output.write(`${label}
|
|
409
|
+
`);
|
|
410
|
+
lastPrintedLabel = label;
|
|
411
|
+
}
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
frame = (frame + 1) % frames.length;
|
|
415
|
+
const glyph = frames[frame] ?? frames[0] ?? "";
|
|
416
|
+
output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
|
|
417
|
+
};
|
|
418
|
+
const clearLine = () => {
|
|
419
|
+
if (isTty)
|
|
420
|
+
output.write("\r\x1B[2K");
|
|
421
|
+
};
|
|
422
|
+
render();
|
|
423
|
+
const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
|
|
424
|
+
return {
|
|
425
|
+
setLabel(next) {
|
|
426
|
+
label = next;
|
|
427
|
+
render();
|
|
428
|
+
},
|
|
429
|
+
pause() {
|
|
430
|
+
paused = true;
|
|
431
|
+
clearLine();
|
|
432
|
+
},
|
|
433
|
+
resume() {
|
|
434
|
+
if (stopped)
|
|
435
|
+
return;
|
|
436
|
+
paused = false;
|
|
437
|
+
render();
|
|
438
|
+
},
|
|
439
|
+
stop(finalLine) {
|
|
440
|
+
if (stopped)
|
|
441
|
+
return;
|
|
442
|
+
stopped = true;
|
|
443
|
+
if (timer)
|
|
444
|
+
clearInterval(timer);
|
|
445
|
+
clearLine();
|
|
446
|
+
if (finalLine)
|
|
447
|
+
output.write(`${finalLine}
|
|
448
|
+
`);
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
454
|
+
var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
455
|
+
var DONE_SYMBOL = pc2.green("\u25C7");
|
|
456
|
+
var FAIL_SYMBOL = pc2.red("\u25A0");
|
|
457
|
+
var activeUpdate = null;
|
|
458
|
+
async function withSpinner(label, work, options = {}) {
|
|
459
|
+
if (options.outputMode === "json") {
|
|
460
|
+
return work(() => {});
|
|
461
|
+
}
|
|
462
|
+
if (activeUpdate) {
|
|
463
|
+
const outer = activeUpdate;
|
|
464
|
+
outer(label);
|
|
465
|
+
return work(outer);
|
|
466
|
+
}
|
|
467
|
+
const output = options.output ?? process.stderr;
|
|
468
|
+
const isTty = output.isTTY === true;
|
|
469
|
+
let lastLabel = label;
|
|
470
|
+
if (!isTty) {
|
|
471
|
+
output.write(`${label}
|
|
472
|
+
`);
|
|
473
|
+
const update2 = (next) => {
|
|
474
|
+
lastLabel = next;
|
|
475
|
+
};
|
|
476
|
+
activeUpdate = update2;
|
|
477
|
+
const previousListener2 = setServerPhaseListener(update2);
|
|
478
|
+
try {
|
|
479
|
+
return await work(update2);
|
|
480
|
+
} finally {
|
|
481
|
+
activeUpdate = null;
|
|
482
|
+
setServerPhaseListener(previousListener2);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const spinner = createTtySpinner({
|
|
486
|
+
label,
|
|
487
|
+
output,
|
|
488
|
+
frames: CLACK_SPINNER_FRAMES,
|
|
489
|
+
styleFrame: (frame) => pc2.magenta(frame)
|
|
490
|
+
});
|
|
491
|
+
const update = (next) => {
|
|
492
|
+
lastLabel = next;
|
|
493
|
+
spinner.setLabel(next);
|
|
494
|
+
};
|
|
495
|
+
activeUpdate = update;
|
|
496
|
+
const previousListener = setServerPhaseListener(update);
|
|
497
|
+
try {
|
|
498
|
+
const result = await work(update);
|
|
499
|
+
spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
|
|
500
|
+
return result;
|
|
501
|
+
} catch (error) {
|
|
502
|
+
spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
|
|
503
|
+
throw error;
|
|
504
|
+
} finally {
|
|
505
|
+
activeUpdate = null;
|
|
506
|
+
setServerPhaseListener(previousListener);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
366
510
|
// packages/cli/src/commands/_help-catalog.ts
|
|
367
511
|
import { intro, log as log2, note as note2, outro } from "@clack/prompts";
|
|
368
|
-
import
|
|
512
|
+
import pc3 from "picocolors";
|
|
369
513
|
var TOP_LEVEL_SECTIONS = [
|
|
370
514
|
{
|
|
371
515
|
title: "Start here",
|
|
@@ -647,13 +791,13 @@ var ADVANCED_GROUPS = [
|
|
|
647
791
|
];
|
|
648
792
|
var ALL_GROUPS = [...PRIMARY_GROUPS, ...ADVANCED_GROUPS];
|
|
649
793
|
function heading(title) {
|
|
650
|
-
return
|
|
794
|
+
return pc3.bold(pc3.cyan(title));
|
|
651
795
|
}
|
|
652
796
|
function renderRigBanner(version) {
|
|
653
|
-
const m = (s) =>
|
|
654
|
-
const c = (s) =>
|
|
655
|
-
const y = (s) =>
|
|
656
|
-
const d = (s) =>
|
|
797
|
+
const m = (s) => pc3.bold(pc3.magenta(s));
|
|
798
|
+
const c = (s) => pc3.bold(pc3.cyan(s));
|
|
799
|
+
const y = (s) => pc3.yellow(s);
|
|
800
|
+
const d = (s) => pc3.dim(s);
|
|
657
801
|
const lines = [
|
|
658
802
|
m(" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ") + d(" \u2591\u2592\u2593\u2588 "),
|
|
659
803
|
m(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D ") + d("\u2593\u2592\u2591"),
|
|
@@ -662,7 +806,7 @@ function renderRigBanner(version) {
|
|
|
662
806
|
y(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D") + d(" \u2592\u2591"),
|
|
663
807
|
y(" \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D "),
|
|
664
808
|
"",
|
|
665
|
-
` ${c("\u25E2\u25E4")} ${
|
|
809
|
+
` ${c("\u25E2\u25E4")} ${pc3.bold("the control rig for autonomous coding agents")} ${m("//")} ${d("you are the operator")}`,
|
|
666
810
|
version ? ` ${d(`v${version} \xB7 jack in: rig task run --next`)}` : ` ${d("jack in: rig task run --next")}`
|
|
667
811
|
];
|
|
668
812
|
return lines.join(`
|
|
@@ -670,7 +814,7 @@ function renderRigBanner(version) {
|
|
|
670
814
|
}
|
|
671
815
|
function commandLine(command, description) {
|
|
672
816
|
const commandColumn = command.length >= 38 ? `${command} ` : command.padEnd(38);
|
|
673
|
-
return `${
|
|
817
|
+
return `${pc3.dim("\u2502")} ${pc3.bold(commandColumn)} ${description}`;
|
|
674
818
|
}
|
|
675
819
|
function renderCommandBlock(commands) {
|
|
676
820
|
return commands.map((entry) => commandLine(entry.command, entry.description)).join(`
|
|
@@ -680,37 +824,37 @@ function renderGroup(group) {
|
|
|
680
824
|
const lines = [
|
|
681
825
|
`${heading(`rig ${group.name}`)} \u2014 ${group.summary}`,
|
|
682
826
|
"",
|
|
683
|
-
|
|
827
|
+
pc3.bold("Usage"),
|
|
684
828
|
...group.usage.map((line) => ` ${line}`),
|
|
685
829
|
"",
|
|
686
|
-
|
|
830
|
+
pc3.bold("Commands"),
|
|
687
831
|
...group.commands.map((entry) => commandLine(entry.command, entry.description))
|
|
688
832
|
];
|
|
689
833
|
if (group.examples?.length) {
|
|
690
|
-
lines.push("",
|
|
834
|
+
lines.push("", pc3.bold("Examples"), ...group.examples.map((line) => ` ${pc3.dim("$")} ${line}`));
|
|
691
835
|
}
|
|
692
836
|
if (group.next?.length) {
|
|
693
|
-
lines.push("",
|
|
837
|
+
lines.push("", pc3.bold("Next steps"), ...group.next.map((line) => ` ${pc3.dim("\u203A")} ${line}`));
|
|
694
838
|
}
|
|
695
839
|
if (group.advanced?.length) {
|
|
696
|
-
lines.push("",
|
|
840
|
+
lines.push("", pc3.bold("Compatibility / advanced"), ...group.advanced.map((line) => ` ${pc3.dim("\u203A")} ${line}`));
|
|
697
841
|
}
|
|
698
842
|
return lines.join(`
|
|
699
843
|
`);
|
|
700
844
|
}
|
|
701
845
|
function renderTopLevelHelp() {
|
|
702
846
|
return [
|
|
703
|
-
`${heading("rig")} ${
|
|
704
|
-
|
|
847
|
+
`${heading("rig")} ${pc3.dim("\u2014 server-owned task/run control plane for Pi-backed engineering work")}`,
|
|
848
|
+
pc3.dim("Current path: select a server, choose a task, submit a run, attach with native Pi, clear inbox gates."),
|
|
705
849
|
"",
|
|
706
850
|
...TOP_LEVEL_SECTIONS.flatMap((section) => [
|
|
707
|
-
`${
|
|
851
|
+
`${pc3.bold(pc3.magenta(`\u25C7 ${section.title}`))} \u2014 ${pc3.dim(section.subtitle)}`,
|
|
708
852
|
renderCommandBlock(section.commands),
|
|
709
853
|
""
|
|
710
854
|
]),
|
|
711
|
-
|
|
855
|
+
pc3.dim("More: `rig help --advanced` for dev/compatibility commands; `rig <group> --help` for rich per-group help; `rig --version` for the installed version."),
|
|
712
856
|
"",
|
|
713
|
-
|
|
857
|
+
pc3.bold("Global options"),
|
|
714
858
|
commandLine("--project <path>", "Use a project root instead of auto-discovery."),
|
|
715
859
|
commandLine("--json", "Emit structured output for scripts/agents."),
|
|
716
860
|
commandLine("--dry-run", "Print the command plan without mutating state.")
|
|
@@ -834,11 +978,11 @@ function formatStatsReport(stats) {
|
|
|
834
978
|
const keyWidth = Math.max(...rows.map(([key]) => key.length));
|
|
835
979
|
const lines = [
|
|
836
980
|
formatSection("Fleet stats", window),
|
|
837
|
-
...rows.map(([key, value]) => `${
|
|
981
|
+
...rows.map(([key, value]) => `${pc4.dim("\u2502")} ${pc4.dim(key.padEnd(keyWidth + 2))} ${value}`)
|
|
838
982
|
];
|
|
839
983
|
const otherStatuses = Object.entries(stats.statusCounts).filter(([status]) => !["completed", "failed", "needs-attention"].includes(status)).sort(([, left], [, right]) => right - left);
|
|
840
984
|
if (otherStatuses.length > 0) {
|
|
841
|
-
lines.push(`${
|
|
985
|
+
lines.push(`${pc4.dim("\u2502")} ${pc4.dim("other".padEnd(keyWidth + 2))} ${otherStatuses.map(([status, count]) => `${status}:${count}`).join(" ")}`);
|
|
842
986
|
}
|
|
843
987
|
lines.push("", ...formatNextSteps([
|
|
844
988
|
"Inspect a run: `rig run list` then `rig run show <run-id>`",
|
|
@@ -857,7 +1001,8 @@ async function executeStats(context, args) {
|
|
|
857
1001
|
const sinceResult = takeOption(pending, "--since");
|
|
858
1002
|
requireNoExtraArgs(sinceResult.rest, "rig stats [show] [--since <7d|30d|ISO date>]");
|
|
859
1003
|
const since = parseSinceOption(sinceResult.value);
|
|
860
|
-
const
|
|
1004
|
+
const remoteStats = isRemoteConnectionSelected(context.projectRoot);
|
|
1005
|
+
const stats = await withSpinner(remoteStats ? "Computing fleet stats on the server\u2026" : "Scanning local run state\u2026", async () => remoteStats ? await requestServerJson(context, `/api/stats${since ? `?since=${encodeURIComponent(since.toISOString())}` : ""}`) : computeRigStats(context.projectRoot, { since }), { outputMode: context.outputMode });
|
|
861
1006
|
if (context.outputMode === "text") {
|
|
862
1007
|
printFormattedOutput(formatStatsReport(stats));
|
|
863
1008
|
}
|
|
@@ -543,6 +543,10 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
|
543
543
|
|
|
544
544
|
// packages/cli/src/commands/_server-client.ts
|
|
545
545
|
var scopedGitHubBearerTokens = new Map;
|
|
546
|
+
var serverPhaseListener = null;
|
|
547
|
+
function reportServerPhase(label) {
|
|
548
|
+
serverPhaseListener?.(label);
|
|
549
|
+
}
|
|
546
550
|
function cleanToken(value) {
|
|
547
551
|
const trimmed = value?.trim();
|
|
548
552
|
return trimmed ? trimmed : null;
|
|
@@ -585,6 +589,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
585
589
|
try {
|
|
586
590
|
const selected = resolveSelectedConnection(projectRoot);
|
|
587
591
|
if (selected?.connection.kind === "remote") {
|
|
592
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
588
593
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
589
594
|
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
590
595
|
return {
|
|
@@ -594,6 +599,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
594
599
|
serverProjectRoot
|
|
595
600
|
};
|
|
596
601
|
}
|
|
602
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
597
603
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
598
604
|
return {
|
|
599
605
|
baseUrl: connection.baseUrl,
|
|
@@ -700,6 +706,7 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
700
706
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
701
707
|
if (server.serverProjectRoot)
|
|
702
708
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
709
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
703
710
|
let response;
|
|
704
711
|
try {
|
|
705
712
|
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
@@ -736,6 +743,17 @@ async function updateWorkspaceTaskViaServer(context, input) {
|
|
|
736
743
|
});
|
|
737
744
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
738
745
|
}
|
|
746
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
747
|
+
"created",
|
|
748
|
+
"preparing",
|
|
749
|
+
"running",
|
|
750
|
+
"validating",
|
|
751
|
+
"reviewing",
|
|
752
|
+
"stopped",
|
|
753
|
+
"failed",
|
|
754
|
+
"needs-attention",
|
|
755
|
+
"needs_attention"
|
|
756
|
+
]);
|
|
739
757
|
|
|
740
758
|
// packages/cli/src/commands/task-run-driver.ts
|
|
741
759
|
var PI_CANONICAL_RUN_STAGES = [
|