@h-rig/cli 0.0.6-alpha.82 → 0.0.6-alpha.83
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/build-rig-binaries.js +40 -8
- package/dist/bin/rig.js +2798 -1440
- package/dist/src/app/board.js +62 -13
- package/dist/src/app-opentui/adapters/command.d.ts +2 -0
- package/dist/src/app-opentui/adapters/command.js +329 -0
- package/dist/src/app-opentui/adapters/common.js +2 -2
- package/dist/src/app-opentui/adapters/doctor.js +63 -14
- package/dist/src/app-opentui/adapters/fleet.js +84 -20
- package/dist/src/app-opentui/adapters/inbox.js +83 -19
- package/dist/src/app-opentui/adapters/init.js +87 -23
- package/dist/src/app-opentui/adapters/pi-attach.js +96 -34
- package/dist/src/app-opentui/adapters/run-detail.js +83 -19
- package/dist/src/app-opentui/adapters/server.js +100 -23
- package/dist/src/app-opentui/adapters/tasks.d.ts +11 -0
- package/dist/src/app-opentui/adapters/tasks.js +742 -94
- package/dist/src/app-opentui/bootstrap.d.ts +1 -6
- package/dist/src/app-opentui/bootstrap.js +13769 -12368
- package/dist/src/app-opentui/command-pty-host.d.ts +62 -0
- package/dist/src/app-opentui/command-pty-host.js +248 -0
- package/dist/src/app-opentui/events.js +1 -1
- package/dist/src/app-opentui/focus-manager.d.ts +14 -0
- package/dist/src/app-opentui/focus-manager.js +24 -0
- package/dist/src/app-opentui/index.js +3802 -2882
- package/dist/src/app-opentui/intent.js +154 -48
- package/dist/src/app-opentui/keymap.d.ts +20 -0
- package/dist/src/app-opentui/keymap.js +707 -0
- package/dist/src/app-opentui/launch-routing.d.ts +16 -0
- package/dist/src/app-opentui/launch-routing.js +55 -0
- package/dist/src/app-opentui/layout.js +2 -2
- package/dist/src/app-opentui/pi-host-child.js +66 -16
- package/dist/src/app-opentui/pi-pty-host.d.ts +9 -0
- package/dist/src/app-opentui/pi-pty-host.js +9 -11
- package/dist/src/app-opentui/registry.js +3231 -2403
- package/dist/src/app-opentui/render/ascii-fleet.d.ts +15 -0
- package/dist/src/app-opentui/render/ascii-fleet.js +82 -0
- package/dist/src/app-opentui/render/graphics.d.ts +1 -1
- package/dist/src/app-opentui/render/graphics.js +131 -16
- package/dist/src/app-opentui/render/image-visual-layer-worker.js +408 -957
- package/dist/src/app-opentui/render/image-visual-layer.d.ts +18 -10
- package/dist/src/app-opentui/render/image-visual-layer.js +386 -356
- package/dist/src/app-opentui/render/panel-layout.d.ts +38 -0
- package/dist/src/app-opentui/render/panel-layout.js +48 -0
- package/dist/src/app-opentui/render/panels.d.ts +2 -0
- package/dist/src/app-opentui/render/panels.js +62 -29
- package/dist/src/app-opentui/render/preloader.d.ts +10 -0
- package/dist/src/app-opentui/render/preloader.js +163 -0
- package/dist/src/app-opentui/render/scene.d.ts +3 -0
- package/dist/src/app-opentui/render/scene.js +12 -0
- package/dist/src/app-opentui/render/text.js +5 -1
- package/dist/src/app-opentui/render/type-bar.d.ts +2 -1
- package/dist/src/app-opentui/render/type-bar.js +51 -16
- package/dist/src/app-opentui/runtime-resources.d.ts +16 -0
- package/dist/src/app-opentui/runtime-resources.js +62 -0
- package/dist/src/app-opentui/runtime.js +2322 -1513
- package/dist/src/app-opentui/scenes/command.d.ts +3 -0
- package/dist/src/app-opentui/scenes/command.js +103 -0
- package/dist/src/app-opentui/scenes/doctor.d.ts +2 -1
- package/dist/src/app-opentui/scenes/doctor.js +53 -14
- package/dist/src/app-opentui/scenes/error.js +44 -14
- package/dist/src/app-opentui/scenes/fleet.js +40 -21
- package/dist/src/app-opentui/scenes/handoff.js +142 -27
- package/dist/src/app-opentui/scenes/help.js +63 -48
- package/dist/src/app-opentui/scenes/inbox.d.ts +2 -1
- package/dist/src/app-opentui/scenes/inbox.js +64 -17
- package/dist/src/app-opentui/scenes/init.d.ts +2 -1
- package/dist/src/app-opentui/scenes/init.js +62 -21
- package/dist/src/app-opentui/scenes/main.d.ts +2 -1
- package/dist/src/app-opentui/scenes/main.js +80 -24
- package/dist/src/app-opentui/scenes/run-detail.d.ts +2 -1
- package/dist/src/app-opentui/scenes/run-detail.js +39 -25
- package/dist/src/app-opentui/scenes/server.d.ts +2 -1
- package/dist/src/app-opentui/scenes/server.js +71 -20
- package/dist/src/app-opentui/scenes/tasks.js +44 -22
- package/dist/src/app-opentui/state.js +70 -30
- package/dist/src/app-opentui/terminal-capabilities.d.ts +7 -0
- package/dist/src/app-opentui/terminal-capabilities.js +15 -0
- package/dist/src/app-opentui/types.d.ts +10 -2
- package/dist/src/commands/_doctor-checks.js +62 -13
- package/dist/src/commands/_operator-view.js +63 -13
- package/dist/src/commands/_pi-frontend.js +63 -13
- package/dist/src/commands/_preflight.js +64 -14
- package/dist/src/commands/_server-client.js +82 -18
- package/dist/src/commands/_snapshot-upload.js +62 -13
- package/dist/src/commands/connect.js +7 -1
- package/dist/src/commands/doctor.js +62 -13
- package/dist/src/commands/github.js +144 -23
- package/dist/src/commands/inbox.js +62 -13
- package/dist/src/commands/init.js +82 -18
- package/dist/src/commands/inspect.js +62 -13
- package/dist/src/commands/run.js +66 -13
- package/dist/src/commands/server.js +69 -14
- package/dist/src/commands/setup.js +62 -13
- package/dist/src/commands/stats.js +62 -13
- package/dist/src/commands/task-run-driver.js +62 -13
- package/dist/src/commands/task.js +65 -14
- package/dist/src/commands.js +227 -92
- package/dist/src/index.js +234 -99
- package/package.json +8 -9
- package/dist/src/app-opentui/render/image-visual-layer-native-canvas.d.ts +0 -2
- package/dist/src/app-opentui/render/image-visual-layer-native-canvas.js +0 -1484
package/dist/src/app/board.js
CHANGED
|
@@ -564,17 +564,21 @@ function cleanToken(value) {
|
|
|
564
564
|
const trimmed = value?.trim();
|
|
565
565
|
return trimmed ? trimmed : null;
|
|
566
566
|
}
|
|
567
|
-
function
|
|
567
|
+
function readRemoteAuthState(projectRoot) {
|
|
568
568
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
569
569
|
if (!existsSync2(path))
|
|
570
570
|
return null;
|
|
571
571
|
try {
|
|
572
572
|
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
573
|
-
return
|
|
573
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
574
574
|
} catch {
|
|
575
575
|
return null;
|
|
576
576
|
}
|
|
577
577
|
}
|
|
578
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
579
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
580
|
+
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
581
|
+
}
|
|
578
582
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
579
583
|
const scopedKey = resolve2(projectRoot);
|
|
580
584
|
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
@@ -585,15 +589,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
|
|
|
585
589
|
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
586
590
|
}
|
|
587
591
|
function readStoredGitHubAuthToken(projectRoot) {
|
|
588
|
-
const
|
|
589
|
-
|
|
592
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
593
|
+
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
594
|
+
}
|
|
595
|
+
function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
596
|
+
const repo = readRepoConnection(projectRoot);
|
|
597
|
+
const slug = repo?.project?.trim();
|
|
598
|
+
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
590
599
|
return null;
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
600
|
+
const auth = readRemoteAuthState(projectRoot);
|
|
601
|
+
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
602
|
+
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
603
|
+
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
595
604
|
return null;
|
|
596
|
-
|
|
605
|
+
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
606
|
+
if (!checkoutBaseDir)
|
|
607
|
+
return null;
|
|
608
|
+
const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
|
|
609
|
+
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
610
|
+
return inferred;
|
|
597
611
|
}
|
|
598
612
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
599
613
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
@@ -604,7 +618,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
604
618
|
if (selected?.connection.kind === "remote") {
|
|
605
619
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
606
620
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
607
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
621
|
+
const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
608
622
|
return {
|
|
609
623
|
baseUrl: selected.connection.baseUrl,
|
|
610
624
|
authToken,
|
|
@@ -633,7 +647,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
633
647
|
if (!slug)
|
|
634
648
|
return null;
|
|
635
649
|
try {
|
|
636
|
-
const
|
|
650
|
+
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
651
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
652
|
+
url.searchParams.set("rt", authToken);
|
|
653
|
+
const response = await fetch(url, {
|
|
637
654
|
headers: mergeHeaders(undefined, authToken)
|
|
638
655
|
});
|
|
639
656
|
if (!response.ok)
|
|
@@ -660,10 +677,23 @@ function appendTaskFilterParams(url, filters) {
|
|
|
660
677
|
if (filters.limit !== undefined)
|
|
661
678
|
url.searchParams.set("limit", String(filters.limit));
|
|
662
679
|
}
|
|
680
|
+
function mergeCookie(existing, name, value) {
|
|
681
|
+
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
682
|
+
if (!existing?.trim())
|
|
683
|
+
return encoded;
|
|
684
|
+
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
685
|
+
return [...parts, encoded].join("; ");
|
|
686
|
+
}
|
|
687
|
+
function queryAuthFallbackEnabled(env = process.env) {
|
|
688
|
+
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
689
|
+
}
|
|
663
690
|
function mergeHeaders(headers, authToken) {
|
|
664
691
|
const merged = new Headers(headers);
|
|
665
692
|
if (authToken) {
|
|
666
|
-
|
|
693
|
+
const bearer = `Bearer ${authToken}`;
|
|
694
|
+
merged.set("authorization", bearer);
|
|
695
|
+
merged.set("x-auth", bearer);
|
|
696
|
+
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
667
697
|
}
|
|
668
698
|
return merged;
|
|
669
699
|
}
|
|
@@ -724,15 +754,34 @@ async function buildServerFailureContext(projectRoot, server) {
|
|
|
724
754
|
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
725
755
|
};
|
|
726
756
|
}
|
|
757
|
+
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
758
|
+
try {
|
|
759
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
760
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
761
|
+
} catch {
|
|
762
|
+
return false;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
766
|
+
return pathname === "/health" || pathname === "/api/health" || pathname === "/api/server/status" || pathname === "/api/server/project-root" || pathname.startsWith("/api/github/auth/") || pathname === "/api/projects" || pathname.startsWith("/api/projects/");
|
|
767
|
+
}
|
|
727
768
|
async function requestServerJson(context, pathname, init = {}) {
|
|
728
769
|
const server = await ensureServerForCli(context.projectRoot);
|
|
770
|
+
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
771
|
+
if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
772
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
773
|
+
throw new CliError(`Remote server is selected for ${repo?.project ?? "this repo"}, but this checkout has no server-host project root link.`, 1, { hint: "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to repair the remote project link before task/run commands." });
|
|
774
|
+
}
|
|
729
775
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
730
776
|
if (server.serverProjectRoot)
|
|
731
777
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
778
|
+
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
779
|
+
requestUrl.searchParams.set("rt", server.authToken);
|
|
780
|
+
}
|
|
732
781
|
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
733
782
|
let response;
|
|
734
783
|
try {
|
|
735
|
-
response = await fetch(
|
|
784
|
+
response = await fetch(requestUrl, {
|
|
736
785
|
...init,
|
|
737
786
|
headers
|
|
738
787
|
});
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/app-opentui/adapters/common.ts
|
|
3
|
+
function projectRootOf(ctx) {
|
|
4
|
+
const root = ctx.rig?.projectRoot ?? ctx.projectRoot ?? ctx.getState().projectRoot;
|
|
5
|
+
if (typeof root === "string" && root.trim())
|
|
6
|
+
return root;
|
|
7
|
+
throw new Error("App adapter requires a projectRoot.");
|
|
8
|
+
}
|
|
9
|
+
function normalizeAppError(error) {
|
|
10
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11
|
+
const rawHint = error instanceof Error ? error.hint : undefined;
|
|
12
|
+
return {
|
|
13
|
+
message,
|
|
14
|
+
...typeof rawHint === "string" && rawHint.trim() ? { hint: rawHint.trim() } : {}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function emitStarted(ctx, label, optimistic) {
|
|
18
|
+
ctx.emit({ type: "action.started", label, ...optimistic ? { optimistic } : {} });
|
|
19
|
+
}
|
|
20
|
+
function emitCompleted(ctx, label, data) {
|
|
21
|
+
ctx.emit({ type: "action.completed", label, ...data ? { data } : {} });
|
|
22
|
+
}
|
|
23
|
+
function emitFailed(ctx, label, error, data) {
|
|
24
|
+
if (data)
|
|
25
|
+
patchData(ctx, data);
|
|
26
|
+
const normalized = normalizeAppError(error);
|
|
27
|
+
ctx.emit({
|
|
28
|
+
type: "action.failed",
|
|
29
|
+
label,
|
|
30
|
+
message: normalized.message,
|
|
31
|
+
...normalized.hint ? { hint: normalized.hint } : {},
|
|
32
|
+
cause: error
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function patchData(ctx, data) {
|
|
36
|
+
ctx.emit({ type: "data.patch", data });
|
|
37
|
+
}
|
|
38
|
+
function patchFooter(ctx, footer) {
|
|
39
|
+
ctx.emit({ type: "footer.patch", footer });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// packages/cli/src/app-opentui/command-pty-host.ts
|
|
43
|
+
import { basename } from "path";
|
|
44
|
+
import { fileURLToPath } from "url";
|
|
45
|
+
import { Terminal as XtermTerminal } from "@xterm/headless";
|
|
46
|
+
var MIN_COLS = 40;
|
|
47
|
+
var MIN_ROWS = 10;
|
|
48
|
+
var MAX_ROWS = 300;
|
|
49
|
+
var MAX_SNAPSHOT_LINES = 420;
|
|
50
|
+
var SNAPSHOT_DELAY_MS = 80;
|
|
51
|
+
var fallbackCliScriptPath = fileURLToPath(new URL("../../bin/rig.ts", import.meta.url));
|
|
52
|
+
var activeHost = null;
|
|
53
|
+
function clampCols(cols) {
|
|
54
|
+
return Math.max(MIN_COLS, Math.trunc(cols || 120));
|
|
55
|
+
}
|
|
56
|
+
function clampRows(rows) {
|
|
57
|
+
return Math.max(MIN_ROWS, Math.min(MAX_ROWS, Math.trunc(rows || 36)));
|
|
58
|
+
}
|
|
59
|
+
function childCommandPrefix() {
|
|
60
|
+
const execName = basename(process.execPath).toLowerCase();
|
|
61
|
+
const currentEntry = process.argv[1];
|
|
62
|
+
if (execName === "bun" || execName === "bun.exe") {
|
|
63
|
+
return currentEntry ? [process.execPath, currentEntry] : [process.execPath, fallbackCliScriptPath];
|
|
64
|
+
}
|
|
65
|
+
return [process.execPath];
|
|
66
|
+
}
|
|
67
|
+
function withEnv(base) {
|
|
68
|
+
const env = {};
|
|
69
|
+
for (const [key, value] of Object.entries(base ?? process.env)) {
|
|
70
|
+
if (key === "NO_COLOR")
|
|
71
|
+
continue;
|
|
72
|
+
if (typeof value === "string")
|
|
73
|
+
env[key] = value;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
...env,
|
|
77
|
+
TERM: "xterm-256color",
|
|
78
|
+
COLORTERM: "truecolor",
|
|
79
|
+
FORCE_COLOR: env.FORCE_COLOR ?? "1",
|
|
80
|
+
RIG_PLAIN: "1",
|
|
81
|
+
RIG_OPENTUI_COMMAND_HOST: "1",
|
|
82
|
+
RIG_CLI_PLAIN_HELP: env.RIG_CLI_PLAIN_HELP ?? "1"
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function commandLabel(argv, fallback) {
|
|
86
|
+
const command = argv.join(" ").trim();
|
|
87
|
+
return fallback?.trim() || (command ? `rig ${command}` : "rig");
|
|
88
|
+
}
|
|
89
|
+
async function startCommandPtyHost(options) {
|
|
90
|
+
activeHost?.dispose("replace");
|
|
91
|
+
const host = new CommandPtyHost(options);
|
|
92
|
+
activeHost = host;
|
|
93
|
+
await host.start();
|
|
94
|
+
return host;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
class CommandPtyHost {
|
|
98
|
+
argv;
|
|
99
|
+
projectRoot;
|
|
100
|
+
label;
|
|
101
|
+
env;
|
|
102
|
+
onSnapshot;
|
|
103
|
+
onExit;
|
|
104
|
+
onError;
|
|
105
|
+
terminal;
|
|
106
|
+
decoder = new TextDecoder("utf-8");
|
|
107
|
+
proc = null;
|
|
108
|
+
pty = null;
|
|
109
|
+
status = "starting";
|
|
110
|
+
cols;
|
|
111
|
+
rows;
|
|
112
|
+
message = "starting command";
|
|
113
|
+
exitCode;
|
|
114
|
+
signal;
|
|
115
|
+
notifyTimer = null;
|
|
116
|
+
_disposed = false;
|
|
117
|
+
constructor(options) {
|
|
118
|
+
this.argv = [...options.argv];
|
|
119
|
+
this.projectRoot = options.projectRoot;
|
|
120
|
+
this.label = commandLabel(options.argv, options.label);
|
|
121
|
+
this.env = options.env;
|
|
122
|
+
this.cols = clampCols(options.cols);
|
|
123
|
+
this.rows = clampRows(options.rows);
|
|
124
|
+
this.onSnapshot = options.onSnapshot;
|
|
125
|
+
this.onExit = options.onExit;
|
|
126
|
+
this.onError = options.onError;
|
|
127
|
+
this.terminal = new XtermTerminal({ allowProposedApi: true, cols: this.cols, rows: this.rows, scrollback: 2000 });
|
|
128
|
+
}
|
|
129
|
+
get disposed() {
|
|
130
|
+
return this._disposed;
|
|
131
|
+
}
|
|
132
|
+
get snapshot() {
|
|
133
|
+
return this.createSnapshot();
|
|
134
|
+
}
|
|
135
|
+
async start() {
|
|
136
|
+
if (this._disposed)
|
|
137
|
+
throw new Error("Command PTY host is disposed.");
|
|
138
|
+
if (typeof Bun.Terminal !== "function") {
|
|
139
|
+
throw new Error("Bun native PTY is unavailable in this runtime. Command host requires Bun.Terminal.");
|
|
140
|
+
}
|
|
141
|
+
const spawnOptions = {
|
|
142
|
+
cwd: this.projectRoot,
|
|
143
|
+
env: withEnv(this.env),
|
|
144
|
+
terminal: {
|
|
145
|
+
cols: this.cols,
|
|
146
|
+
rows: this.rows,
|
|
147
|
+
name: "xterm-256color",
|
|
148
|
+
data: (_terminal, data) => this.handlePtyData(data)
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const proc = Bun.spawn([...childCommandPrefix(), ...this.argv], spawnOptions);
|
|
152
|
+
if (!proc.terminal)
|
|
153
|
+
throw new Error("Bun did not attach a terminal to the command child process.");
|
|
154
|
+
this.proc = proc;
|
|
155
|
+
this.pty = proc.terminal;
|
|
156
|
+
this.status = "running";
|
|
157
|
+
this.message = "running";
|
|
158
|
+
this.emitSnapshotSoon(0);
|
|
159
|
+
proc.exited.then((exitCode) => {
|
|
160
|
+
if (this._disposed)
|
|
161
|
+
return;
|
|
162
|
+
this.status = exitCode === 0 ? "exited" : "failed";
|
|
163
|
+
this.exitCode = exitCode;
|
|
164
|
+
this.signal = null;
|
|
165
|
+
this.message = exitCode === 0 ? "completed" : `exited with code ${exitCode}`;
|
|
166
|
+
const snapshot = this.createSnapshot();
|
|
167
|
+
this.onSnapshot?.(snapshot);
|
|
168
|
+
this.onExit?.(snapshot);
|
|
169
|
+
if (activeHost === this)
|
|
170
|
+
activeHost = null;
|
|
171
|
+
this.dispose("exit", { kill: false, notify: false });
|
|
172
|
+
}).catch((error) => {
|
|
173
|
+
if (this._disposed)
|
|
174
|
+
return;
|
|
175
|
+
this.status = "failed";
|
|
176
|
+
this.message = error instanceof Error ? error.message : String(error);
|
|
177
|
+
const snapshot = this.createSnapshot();
|
|
178
|
+
this.onSnapshot?.(snapshot);
|
|
179
|
+
this.onError?.(error, snapshot);
|
|
180
|
+
if (activeHost === this)
|
|
181
|
+
activeHost = null;
|
|
182
|
+
this.dispose("error", { kill: false, notify: false });
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
write(data) {
|
|
186
|
+
if (this._disposed || !this.pty)
|
|
187
|
+
return;
|
|
188
|
+
try {
|
|
189
|
+
this.pty.write(data);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
this.status = "failed";
|
|
192
|
+
this.message = error instanceof Error ? error.message : String(error);
|
|
193
|
+
const snapshot = this.createSnapshot();
|
|
194
|
+
this.onSnapshot?.(snapshot);
|
|
195
|
+
this.onError?.(error, snapshot);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
resize(cols, rows) {
|
|
199
|
+
const nextCols = clampCols(cols);
|
|
200
|
+
const nextRows = clampRows(rows);
|
|
201
|
+
if (nextCols === this.cols && nextRows === this.rows)
|
|
202
|
+
return;
|
|
203
|
+
this.cols = nextCols;
|
|
204
|
+
this.rows = nextRows;
|
|
205
|
+
this.terminal.resize(nextCols, nextRows);
|
|
206
|
+
try {
|
|
207
|
+
this.pty?.resize(nextCols, nextRows);
|
|
208
|
+
} catch {}
|
|
209
|
+
this.emitSnapshotSoon(0);
|
|
210
|
+
}
|
|
211
|
+
dispose(reason = "dispose", options = {}) {
|
|
212
|
+
if (this._disposed)
|
|
213
|
+
return;
|
|
214
|
+
this._disposed = true;
|
|
215
|
+
if (this.notifyTimer)
|
|
216
|
+
clearTimeout(this.notifyTimer);
|
|
217
|
+
this.notifyTimer = null;
|
|
218
|
+
if (options.kill !== false) {
|
|
219
|
+
try {
|
|
220
|
+
this.proc?.kill("SIGTERM");
|
|
221
|
+
} catch {}
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
this.pty?.close();
|
|
225
|
+
} catch {}
|
|
226
|
+
try {
|
|
227
|
+
this.terminal.dispose();
|
|
228
|
+
} catch {}
|
|
229
|
+
if (activeHost === this)
|
|
230
|
+
activeHost = null;
|
|
231
|
+
if (options.notify) {
|
|
232
|
+
this.message = reason;
|
|
233
|
+
this.onSnapshot?.(this.createSnapshot(reason));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
handlePtyData(data) {
|
|
237
|
+
if (this._disposed)
|
|
238
|
+
return;
|
|
239
|
+
this.decoder.decode(data, { stream: true });
|
|
240
|
+
this.terminal.write(data, () => this.emitSnapshotSoon());
|
|
241
|
+
}
|
|
242
|
+
emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS) {
|
|
243
|
+
if (this._disposed || this.notifyTimer)
|
|
244
|
+
return;
|
|
245
|
+
this.notifyTimer = setTimeout(() => {
|
|
246
|
+
this.notifyTimer = null;
|
|
247
|
+
if (this._disposed)
|
|
248
|
+
return;
|
|
249
|
+
this.onSnapshot?.(this.createSnapshot());
|
|
250
|
+
}, delayMs);
|
|
251
|
+
}
|
|
252
|
+
createSnapshot(message = this.message) {
|
|
253
|
+
const buffer = this.terminal.buffer.active;
|
|
254
|
+
const end = buffer.length;
|
|
255
|
+
const start = Math.max(0, end - MAX_SNAPSHOT_LINES);
|
|
256
|
+
const lines = [];
|
|
257
|
+
for (let row = start;row < end; row += 1) {
|
|
258
|
+
const line = buffer.getLine(row);
|
|
259
|
+
lines.push((line?.translateToString(false) ?? "").slice(0, this.cols));
|
|
260
|
+
}
|
|
261
|
+
while (lines.length < this.rows)
|
|
262
|
+
lines.push("");
|
|
263
|
+
return {
|
|
264
|
+
command: this.argv,
|
|
265
|
+
label: this.label,
|
|
266
|
+
status: this.status,
|
|
267
|
+
cols: this.cols,
|
|
268
|
+
rows: this.rows,
|
|
269
|
+
lines,
|
|
270
|
+
message,
|
|
271
|
+
...this.exitCode !== undefined ? { exitCode: this.exitCode } : {},
|
|
272
|
+
...this.signal !== undefined ? { signal: this.signal } : {}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// packages/cli/src/app-opentui/adapters/command.ts
|
|
278
|
+
function commandLabel2(argv) {
|
|
279
|
+
const command = argv.join(" ").trim();
|
|
280
|
+
return command ? `rig ${command}` : "rig";
|
|
281
|
+
}
|
|
282
|
+
async function runCommandInAppPty(ctx, argv) {
|
|
283
|
+
const label = commandLabel2(argv);
|
|
284
|
+
emitStarted(ctx, label, { command: { label, argv, status: "starting" } });
|
|
285
|
+
patchData(ctx, {
|
|
286
|
+
command: { label, argv, status: "starting" },
|
|
287
|
+
commandTerminal: {
|
|
288
|
+
command: argv,
|
|
289
|
+
label,
|
|
290
|
+
status: "starting",
|
|
291
|
+
cols: 120,
|
|
292
|
+
rows: 32,
|
|
293
|
+
lines: [],
|
|
294
|
+
message: "starting"
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
patchFooter(ctx, { message: label });
|
|
298
|
+
try {
|
|
299
|
+
const host = await startCommandPtyHost({
|
|
300
|
+
argv,
|
|
301
|
+
projectRoot: projectRootOf(ctx),
|
|
302
|
+
cols: 120,
|
|
303
|
+
rows: 32,
|
|
304
|
+
label,
|
|
305
|
+
onSnapshot(snapshot) {
|
|
306
|
+
patchData(ctx, { commandTerminal: snapshot, command: { label, argv, status: snapshot.status, message: snapshot.message } });
|
|
307
|
+
patchFooter(ctx, { message: `${label} \xB7 ${snapshot.message ?? snapshot.status}` });
|
|
308
|
+
},
|
|
309
|
+
onExit(snapshot) {
|
|
310
|
+
patchData(ctx, { commandTerminal: snapshot, command: { label, argv, status: snapshot.status, message: snapshot.message } });
|
|
311
|
+
if (snapshot.status === "exited")
|
|
312
|
+
emitCompleted(ctx, label, { exitCode: snapshot.exitCode ?? 0 });
|
|
313
|
+
else
|
|
314
|
+
emitFailed(ctx, label, new Error(snapshot.message ?? "Command failed"), { commandTerminal: snapshot });
|
|
315
|
+
},
|
|
316
|
+
onError(error, snapshot) {
|
|
317
|
+
patchData(ctx, { commandTerminal: snapshot, command: { label, argv, status: "failed", message: snapshot.message } });
|
|
318
|
+
emitFailed(ctx, label, error, { commandTerminal: snapshot });
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
patchData(ctx, { commandTerminal: host.snapshot });
|
|
322
|
+
} catch (error) {
|
|
323
|
+
emitFailed(ctx, label, error);
|
|
324
|
+
throw error;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
export {
|
|
328
|
+
runCommandInAppPty
|
|
329
|
+
};
|
|
@@ -13,7 +13,7 @@ function projectRootOf(ctx) {
|
|
|
13
13
|
const root = ctx.rig?.projectRoot ?? ctx.projectRoot ?? ctx.getState().projectRoot;
|
|
14
14
|
if (typeof root === "string" && root.trim())
|
|
15
15
|
return root;
|
|
16
|
-
throw new Error("
|
|
16
|
+
throw new Error("App adapter requires a projectRoot.");
|
|
17
17
|
}
|
|
18
18
|
async function runtimeOf(ctx) {
|
|
19
19
|
if (ctx.rig && "runId" in ctx.rig && ctx.ensureRuntime === undefined) {
|
|
@@ -21,7 +21,7 @@ async function runtimeOf(ctx) {
|
|
|
21
21
|
}
|
|
22
22
|
if (ctx.ensureRuntime)
|
|
23
23
|
return ctx.ensureRuntime();
|
|
24
|
-
throw new Error("
|
|
24
|
+
throw new Error("App adapter requires ensureRuntime() before this action can run.");
|
|
25
25
|
}
|
|
26
26
|
function normalizeAppError(error) {
|
|
27
27
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -145,17 +145,21 @@ function cleanToken(value) {
|
|
|
145
145
|
const trimmed = value?.trim();
|
|
146
146
|
return trimmed ? trimmed : null;
|
|
147
147
|
}
|
|
148
|
-
function
|
|
148
|
+
function readRemoteAuthState(projectRoot) {
|
|
149
149
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
150
150
|
if (!existsSync2(path))
|
|
151
151
|
return null;
|
|
152
152
|
try {
|
|
153
153
|
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
154
|
-
return
|
|
154
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
155
155
|
} catch {
|
|
156
156
|
return null;
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
160
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
161
|
+
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
162
|
+
}
|
|
159
163
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
160
164
|
const scopedKey = resolve2(projectRoot);
|
|
161
165
|
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
@@ -166,15 +170,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
|
|
|
166
170
|
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
167
171
|
}
|
|
168
172
|
function readStoredGitHubAuthToken(projectRoot) {
|
|
169
|
-
const
|
|
170
|
-
|
|
173
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
174
|
+
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
175
|
+
}
|
|
176
|
+
function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
177
|
+
const repo = readRepoConnection(projectRoot);
|
|
178
|
+
const slug = repo?.project?.trim();
|
|
179
|
+
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
171
180
|
return null;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
181
|
+
const auth = readRemoteAuthState(projectRoot);
|
|
182
|
+
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
183
|
+
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
184
|
+
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
176
185
|
return null;
|
|
177
|
-
|
|
186
|
+
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
187
|
+
if (!checkoutBaseDir)
|
|
188
|
+
return null;
|
|
189
|
+
const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
|
|
190
|
+
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
191
|
+
return inferred;
|
|
178
192
|
}
|
|
179
193
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
180
194
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
@@ -185,7 +199,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
185
199
|
if (selected?.connection.kind === "remote") {
|
|
186
200
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
187
201
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
188
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
202
|
+
const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
189
203
|
return {
|
|
190
204
|
baseUrl: selected.connection.baseUrl,
|
|
191
205
|
authToken,
|
|
@@ -214,7 +228,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
214
228
|
if (!slug)
|
|
215
229
|
return null;
|
|
216
230
|
try {
|
|
217
|
-
const
|
|
231
|
+
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
232
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
233
|
+
url.searchParams.set("rt", authToken);
|
|
234
|
+
const response = await fetch(url, {
|
|
218
235
|
headers: mergeHeaders(undefined, authToken)
|
|
219
236
|
});
|
|
220
237
|
if (!response.ok)
|
|
@@ -231,10 +248,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
231
248
|
return null;
|
|
232
249
|
}
|
|
233
250
|
}
|
|
251
|
+
function mergeCookie(existing, name, value) {
|
|
252
|
+
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
253
|
+
if (!existing?.trim())
|
|
254
|
+
return encoded;
|
|
255
|
+
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
256
|
+
return [...parts, encoded].join("; ");
|
|
257
|
+
}
|
|
258
|
+
function queryAuthFallbackEnabled(env = process.env) {
|
|
259
|
+
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
260
|
+
}
|
|
234
261
|
function mergeHeaders(headers, authToken) {
|
|
235
262
|
const merged = new Headers(headers);
|
|
236
263
|
if (authToken) {
|
|
237
|
-
|
|
264
|
+
const bearer = `Bearer ${authToken}`;
|
|
265
|
+
merged.set("authorization", bearer);
|
|
266
|
+
merged.set("x-auth", bearer);
|
|
267
|
+
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
238
268
|
}
|
|
239
269
|
return merged;
|
|
240
270
|
}
|
|
@@ -294,15 +324,34 @@ async function buildServerFailureContext(projectRoot, server) {
|
|
|
294
324
|
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
295
325
|
};
|
|
296
326
|
}
|
|
327
|
+
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
328
|
+
try {
|
|
329
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
330
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
331
|
+
} catch {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
336
|
+
return pathname === "/health" || pathname === "/api/health" || pathname === "/api/server/status" || pathname === "/api/server/project-root" || pathname.startsWith("/api/github/auth/") || pathname === "/api/projects" || pathname.startsWith("/api/projects/");
|
|
337
|
+
}
|
|
297
338
|
async function requestServerJson(context, pathname, init = {}) {
|
|
298
339
|
const server = await ensureServerForCli(context.projectRoot);
|
|
340
|
+
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
341
|
+
if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
342
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
343
|
+
throw new CliError(`Remote server is selected for ${repo?.project ?? "this repo"}, but this checkout has no server-host project root link.`, 1, { hint: "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to repair the remote project link before task/run commands." });
|
|
344
|
+
}
|
|
299
345
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
300
346
|
if (server.serverProjectRoot)
|
|
301
347
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
348
|
+
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
349
|
+
requestUrl.searchParams.set("rt", server.authToken);
|
|
350
|
+
}
|
|
302
351
|
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
303
352
|
let response;
|
|
304
353
|
try {
|
|
305
|
-
response = await fetch(
|
|
354
|
+
response = await fetch(requestUrl, {
|
|
306
355
|
...init,
|
|
307
356
|
headers
|
|
308
357
|
});
|
|
@@ -699,7 +748,7 @@ function projectRootOf(ctx) {
|
|
|
699
748
|
const root = ctx.rig?.projectRoot ?? ctx.projectRoot ?? ctx.getState().projectRoot;
|
|
700
749
|
if (typeof root === "string" && root.trim())
|
|
701
750
|
return root;
|
|
702
|
-
throw new Error("
|
|
751
|
+
throw new Error("App adapter requires a projectRoot.");
|
|
703
752
|
}
|
|
704
753
|
function normalizeAppError(error) {
|
|
705
754
|
const message = error instanceof Error ? error.message : String(error);
|