@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
|
@@ -459,7 +459,13 @@ async function executeConnectionCommand(context, args, options) {
|
|
|
459
459
|
if (!state.connections[alias])
|
|
460
460
|
throw new CliError(`Unknown Rig server: ${alias}`, 1, { hint: "Run `rig server list` to see saved aliases, or save one with `rig server add <alias> <url>`." });
|
|
461
461
|
}
|
|
462
|
-
const
|
|
462
|
+
const previousRepo = readRepoConnection(context.projectRoot);
|
|
463
|
+
const repoState = {
|
|
464
|
+
selected: alias,
|
|
465
|
+
...previousRepo?.project ? { project: previousRepo.project } : {},
|
|
466
|
+
linkedAt: new Date().toISOString(),
|
|
467
|
+
...previousRepo?.serverProjectRoot && previousRepo.selected === alias ? { serverProjectRoot: previousRepo.serverProjectRoot } : {}
|
|
468
|
+
};
|
|
463
469
|
writeRepoConnection(context.projectRoot, repoState);
|
|
464
470
|
printJsonOrText(context, repoState, formatSuccessCard("Rig server selected", [
|
|
465
471
|
["selected", alias],
|
|
@@ -495,17 +501,21 @@ function cleanToken(value) {
|
|
|
495
501
|
const trimmed = value?.trim();
|
|
496
502
|
return trimmed ? trimmed : null;
|
|
497
503
|
}
|
|
498
|
-
function
|
|
504
|
+
function readRemoteAuthState(projectRoot) {
|
|
499
505
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
500
506
|
if (!existsSync2(path))
|
|
501
507
|
return null;
|
|
502
508
|
try {
|
|
503
509
|
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
504
|
-
return
|
|
510
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
505
511
|
} catch {
|
|
506
512
|
return null;
|
|
507
513
|
}
|
|
508
514
|
}
|
|
515
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
516
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
517
|
+
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
518
|
+
}
|
|
509
519
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
510
520
|
const scopedKey = resolve2(projectRoot);
|
|
511
521
|
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
@@ -516,15 +526,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
|
|
|
516
526
|
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
517
527
|
}
|
|
518
528
|
function readStoredGitHubAuthToken(projectRoot) {
|
|
519
|
-
const
|
|
520
|
-
|
|
529
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
530
|
+
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
531
|
+
}
|
|
532
|
+
function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
533
|
+
const repo = readRepoConnection(projectRoot);
|
|
534
|
+
const slug = repo?.project?.trim();
|
|
535
|
+
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
521
536
|
return null;
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
537
|
+
const auth = readRemoteAuthState(projectRoot);
|
|
538
|
+
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
539
|
+
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
540
|
+
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
526
541
|
return null;
|
|
527
|
-
|
|
542
|
+
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
543
|
+
if (!checkoutBaseDir)
|
|
544
|
+
return null;
|
|
545
|
+
const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
|
|
546
|
+
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
547
|
+
return inferred;
|
|
528
548
|
}
|
|
529
549
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
530
550
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
@@ -535,7 +555,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
535
555
|
if (selected?.connection.kind === "remote") {
|
|
536
556
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
537
557
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
538
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
558
|
+
const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
539
559
|
return {
|
|
540
560
|
baseUrl: selected.connection.baseUrl,
|
|
541
561
|
authToken,
|
|
@@ -564,7 +584,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
564
584
|
if (!slug)
|
|
565
585
|
return null;
|
|
566
586
|
try {
|
|
567
|
-
const
|
|
587
|
+
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
588
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
589
|
+
url.searchParams.set("rt", authToken);
|
|
590
|
+
const response = await fetch(url, {
|
|
568
591
|
headers: mergeHeaders(undefined, authToken)
|
|
569
592
|
});
|
|
570
593
|
if (!response.ok)
|
|
@@ -581,10 +604,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
581
604
|
return null;
|
|
582
605
|
}
|
|
583
606
|
}
|
|
607
|
+
function mergeCookie(existing, name, value) {
|
|
608
|
+
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
609
|
+
if (!existing?.trim())
|
|
610
|
+
return encoded;
|
|
611
|
+
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
612
|
+
return [...parts, encoded].join("; ");
|
|
613
|
+
}
|
|
614
|
+
function queryAuthFallbackEnabled(env = process.env) {
|
|
615
|
+
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
616
|
+
}
|
|
584
617
|
function mergeHeaders(headers, authToken) {
|
|
585
618
|
const merged = new Headers(headers);
|
|
586
619
|
if (authToken) {
|
|
587
|
-
|
|
620
|
+
const bearer = `Bearer ${authToken}`;
|
|
621
|
+
merged.set("authorization", bearer);
|
|
622
|
+
merged.set("x-auth", bearer);
|
|
623
|
+
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
588
624
|
}
|
|
589
625
|
return merged;
|
|
590
626
|
}
|
|
@@ -645,15 +681,34 @@ async function buildServerFailureContext(projectRoot, server) {
|
|
|
645
681
|
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
646
682
|
};
|
|
647
683
|
}
|
|
684
|
+
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
685
|
+
try {
|
|
686
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
687
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
688
|
+
} catch {
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
693
|
+
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/");
|
|
694
|
+
}
|
|
648
695
|
async function requestServerJson(context, pathname, init = {}) {
|
|
649
696
|
const server = await ensureServerForCli(context.projectRoot);
|
|
697
|
+
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
698
|
+
if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
699
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
700
|
+
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." });
|
|
701
|
+
}
|
|
650
702
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
651
703
|
if (server.serverProjectRoot)
|
|
652
704
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
705
|
+
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
706
|
+
requestUrl.searchParams.set("rt", server.authToken);
|
|
707
|
+
}
|
|
653
708
|
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
654
709
|
let response;
|
|
655
710
|
try {
|
|
656
|
-
response = await fetch(
|
|
711
|
+
response = await fetch(requestUrl, {
|
|
657
712
|
...init,
|
|
658
713
|
headers
|
|
659
714
|
});
|
|
@@ -298,17 +298,21 @@ function cleanToken(value) {
|
|
|
298
298
|
const trimmed = value?.trim();
|
|
299
299
|
return trimmed ? trimmed : null;
|
|
300
300
|
}
|
|
301
|
-
function
|
|
301
|
+
function readRemoteAuthState(projectRoot) {
|
|
302
302
|
const path = resolve4(projectRoot, ".rig", "state", "github-auth.json");
|
|
303
303
|
if (!existsSync3(path))
|
|
304
304
|
return null;
|
|
305
305
|
try {
|
|
306
306
|
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
307
|
-
return
|
|
307
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
308
308
|
} catch {
|
|
309
309
|
return null;
|
|
310
310
|
}
|
|
311
311
|
}
|
|
312
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
313
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
314
|
+
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
315
|
+
}
|
|
312
316
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
313
317
|
const scopedKey = resolve4(projectRoot);
|
|
314
318
|
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
@@ -319,15 +323,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
|
|
|
319
323
|
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
320
324
|
}
|
|
321
325
|
function readStoredGitHubAuthToken(projectRoot) {
|
|
322
|
-
const
|
|
323
|
-
|
|
326
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
327
|
+
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
328
|
+
}
|
|
329
|
+
function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
330
|
+
const repo = readRepoConnection(projectRoot);
|
|
331
|
+
const slug = repo?.project?.trim();
|
|
332
|
+
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
324
333
|
return null;
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
334
|
+
const auth = readRemoteAuthState(projectRoot);
|
|
335
|
+
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
336
|
+
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
337
|
+
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
329
338
|
return null;
|
|
330
|
-
|
|
339
|
+
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
340
|
+
if (!checkoutBaseDir)
|
|
341
|
+
return null;
|
|
342
|
+
const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
|
|
343
|
+
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
344
|
+
return inferred;
|
|
331
345
|
}
|
|
332
346
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
333
347
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
@@ -338,7 +352,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
338
352
|
if (selected?.connection.kind === "remote") {
|
|
339
353
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
340
354
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
341
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
355
|
+
const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
342
356
|
return {
|
|
343
357
|
baseUrl: selected.connection.baseUrl,
|
|
344
358
|
authToken,
|
|
@@ -367,7 +381,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
367
381
|
if (!slug)
|
|
368
382
|
return null;
|
|
369
383
|
try {
|
|
370
|
-
const
|
|
384
|
+
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
385
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
386
|
+
url.searchParams.set("rt", authToken);
|
|
387
|
+
const response = await fetch(url, {
|
|
371
388
|
headers: mergeHeaders(undefined, authToken)
|
|
372
389
|
});
|
|
373
390
|
if (!response.ok)
|
|
@@ -384,10 +401,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
384
401
|
return null;
|
|
385
402
|
}
|
|
386
403
|
}
|
|
404
|
+
function mergeCookie(existing, name, value) {
|
|
405
|
+
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
406
|
+
if (!existing?.trim())
|
|
407
|
+
return encoded;
|
|
408
|
+
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
409
|
+
return [...parts, encoded].join("; ");
|
|
410
|
+
}
|
|
411
|
+
function queryAuthFallbackEnabled(env = process.env) {
|
|
412
|
+
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
413
|
+
}
|
|
387
414
|
function mergeHeaders(headers, authToken) {
|
|
388
415
|
const merged = new Headers(headers);
|
|
389
416
|
if (authToken) {
|
|
390
|
-
|
|
417
|
+
const bearer = `Bearer ${authToken}`;
|
|
418
|
+
merged.set("authorization", bearer);
|
|
419
|
+
merged.set("x-auth", bearer);
|
|
420
|
+
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
391
421
|
}
|
|
392
422
|
return merged;
|
|
393
423
|
}
|
|
@@ -448,15 +478,34 @@ async function buildServerFailureContext(projectRoot, server) {
|
|
|
448
478
|
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
449
479
|
};
|
|
450
480
|
}
|
|
481
|
+
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
482
|
+
try {
|
|
483
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
484
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
485
|
+
} catch {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
490
|
+
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/");
|
|
491
|
+
}
|
|
451
492
|
async function requestServerJson(context, pathname, init = {}) {
|
|
452
493
|
const server = await ensureServerForCli(context.projectRoot);
|
|
494
|
+
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
495
|
+
if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
496
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
497
|
+
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." });
|
|
498
|
+
}
|
|
453
499
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
454
500
|
if (server.serverProjectRoot)
|
|
455
501
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
502
|
+
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
503
|
+
requestUrl.searchParams.set("rt", server.authToken);
|
|
504
|
+
}
|
|
456
505
|
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
457
506
|
let response;
|
|
458
507
|
try {
|
|
459
|
-
response = await fetch(
|
|
508
|
+
response = await fetch(requestUrl, {
|
|
460
509
|
...init,
|
|
461
510
|
headers
|
|
462
511
|
});
|
|
@@ -165,17 +165,21 @@ function cleanToken(value) {
|
|
|
165
165
|
const trimmed = value?.trim();
|
|
166
166
|
return trimmed ? trimmed : null;
|
|
167
167
|
}
|
|
168
|
-
function
|
|
168
|
+
function readRemoteAuthState(projectRoot) {
|
|
169
169
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
170
170
|
if (!existsSync2(path))
|
|
171
171
|
return null;
|
|
172
172
|
try {
|
|
173
173
|
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
174
|
-
return
|
|
174
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
175
175
|
} catch {
|
|
176
176
|
return null;
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
180
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
181
|
+
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
182
|
+
}
|
|
179
183
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
180
184
|
const scopedKey = resolve2(projectRoot);
|
|
181
185
|
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
@@ -186,15 +190,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
|
|
|
186
190
|
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
187
191
|
}
|
|
188
192
|
function readStoredGitHubAuthToken(projectRoot) {
|
|
189
|
-
const
|
|
190
|
-
|
|
193
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
194
|
+
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
195
|
+
}
|
|
196
|
+
function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
197
|
+
const repo = readRepoConnection(projectRoot);
|
|
198
|
+
const slug = repo?.project?.trim();
|
|
199
|
+
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
191
200
|
return null;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
201
|
+
const auth = readRemoteAuthState(projectRoot);
|
|
202
|
+
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
203
|
+
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
204
|
+
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
196
205
|
return null;
|
|
197
|
-
|
|
206
|
+
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
207
|
+
if (!checkoutBaseDir)
|
|
208
|
+
return null;
|
|
209
|
+
const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
|
|
210
|
+
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
211
|
+
return inferred;
|
|
198
212
|
}
|
|
199
213
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
200
214
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
@@ -205,7 +219,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
205
219
|
if (selected?.connection.kind === "remote") {
|
|
206
220
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
207
221
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
208
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
222
|
+
const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
209
223
|
return {
|
|
210
224
|
baseUrl: selected.connection.baseUrl,
|
|
211
225
|
authToken,
|
|
@@ -234,7 +248,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
234
248
|
if (!slug)
|
|
235
249
|
return null;
|
|
236
250
|
try {
|
|
237
|
-
const
|
|
251
|
+
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
252
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
253
|
+
url.searchParams.set("rt", authToken);
|
|
254
|
+
const response = await fetch(url, {
|
|
238
255
|
headers: mergeHeaders(undefined, authToken)
|
|
239
256
|
});
|
|
240
257
|
if (!response.ok)
|
|
@@ -251,10 +268,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
251
268
|
return null;
|
|
252
269
|
}
|
|
253
270
|
}
|
|
271
|
+
function mergeCookie(existing, name, value) {
|
|
272
|
+
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
273
|
+
if (!existing?.trim())
|
|
274
|
+
return encoded;
|
|
275
|
+
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
276
|
+
return [...parts, encoded].join("; ");
|
|
277
|
+
}
|
|
278
|
+
function queryAuthFallbackEnabled(env = process.env) {
|
|
279
|
+
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
280
|
+
}
|
|
254
281
|
function mergeHeaders(headers, authToken) {
|
|
255
282
|
const merged = new Headers(headers);
|
|
256
283
|
if (authToken) {
|
|
257
|
-
|
|
284
|
+
const bearer = `Bearer ${authToken}`;
|
|
285
|
+
merged.set("authorization", bearer);
|
|
286
|
+
merged.set("x-auth", bearer);
|
|
287
|
+
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
258
288
|
}
|
|
259
289
|
return merged;
|
|
260
290
|
}
|
|
@@ -315,15 +345,34 @@ async function buildServerFailureContext(projectRoot, server) {
|
|
|
315
345
|
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
316
346
|
};
|
|
317
347
|
}
|
|
348
|
+
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
349
|
+
try {
|
|
350
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
351
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
352
|
+
} catch {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
357
|
+
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/");
|
|
358
|
+
}
|
|
318
359
|
async function requestServerJson(context, pathname, init = {}) {
|
|
319
360
|
const server = await ensureServerForCli(context.projectRoot);
|
|
361
|
+
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
362
|
+
if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
363
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
364
|
+
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." });
|
|
365
|
+
}
|
|
320
366
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
321
367
|
if (server.serverProjectRoot)
|
|
322
368
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
369
|
+
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
370
|
+
requestUrl.searchParams.set("rt", server.authToken);
|
|
371
|
+
}
|
|
323
372
|
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
324
373
|
let response;
|
|
325
374
|
try {
|
|
326
|
-
response = await fetch(
|
|
375
|
+
response = await fetch(requestUrl, {
|
|
327
376
|
...init,
|
|
328
377
|
headers
|
|
329
378
|
});
|
|
@@ -551,17 +551,21 @@ function cleanToken(value) {
|
|
|
551
551
|
const trimmed = value?.trim();
|
|
552
552
|
return trimmed ? trimmed : null;
|
|
553
553
|
}
|
|
554
|
-
function
|
|
554
|
+
function readRemoteAuthState(projectRoot) {
|
|
555
555
|
const path = resolve5(projectRoot, ".rig", "state", "github-auth.json");
|
|
556
556
|
if (!existsSync3(path))
|
|
557
557
|
return null;
|
|
558
558
|
try {
|
|
559
559
|
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
560
|
-
return
|
|
560
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
561
561
|
} catch {
|
|
562
562
|
return null;
|
|
563
563
|
}
|
|
564
564
|
}
|
|
565
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
566
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
567
|
+
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
568
|
+
}
|
|
565
569
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
566
570
|
const scopedKey = resolve5(projectRoot);
|
|
567
571
|
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
@@ -572,15 +576,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
|
|
|
572
576
|
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
573
577
|
}
|
|
574
578
|
function readStoredGitHubAuthToken(projectRoot) {
|
|
575
|
-
const
|
|
576
|
-
|
|
579
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
580
|
+
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
581
|
+
}
|
|
582
|
+
function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
583
|
+
const repo = readRepoConnection(projectRoot);
|
|
584
|
+
const slug = repo?.project?.trim();
|
|
585
|
+
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
577
586
|
return null;
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
587
|
+
const auth = readRemoteAuthState(projectRoot);
|
|
588
|
+
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
589
|
+
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
590
|
+
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
582
591
|
return null;
|
|
583
|
-
|
|
592
|
+
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
593
|
+
if (!checkoutBaseDir)
|
|
594
|
+
return null;
|
|
595
|
+
const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
|
|
596
|
+
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
597
|
+
return inferred;
|
|
584
598
|
}
|
|
585
599
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
586
600
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
@@ -591,7 +605,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
591
605
|
if (selected?.connection.kind === "remote") {
|
|
592
606
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
593
607
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
594
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
608
|
+
const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
595
609
|
return {
|
|
596
610
|
baseUrl: selected.connection.baseUrl,
|
|
597
611
|
authToken,
|
|
@@ -620,7 +634,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
620
634
|
if (!slug)
|
|
621
635
|
return null;
|
|
622
636
|
try {
|
|
623
|
-
const
|
|
637
|
+
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
638
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
639
|
+
url.searchParams.set("rt", authToken);
|
|
640
|
+
const response = await fetch(url, {
|
|
624
641
|
headers: mergeHeaders(undefined, authToken)
|
|
625
642
|
});
|
|
626
643
|
if (!response.ok)
|
|
@@ -637,10 +654,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
637
654
|
return null;
|
|
638
655
|
}
|
|
639
656
|
}
|
|
657
|
+
function mergeCookie(existing, name, value) {
|
|
658
|
+
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
659
|
+
if (!existing?.trim())
|
|
660
|
+
return encoded;
|
|
661
|
+
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
662
|
+
return [...parts, encoded].join("; ");
|
|
663
|
+
}
|
|
664
|
+
function queryAuthFallbackEnabled(env = process.env) {
|
|
665
|
+
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
666
|
+
}
|
|
640
667
|
function mergeHeaders(headers, authToken) {
|
|
641
668
|
const merged = new Headers(headers);
|
|
642
669
|
if (authToken) {
|
|
643
|
-
|
|
670
|
+
const bearer = `Bearer ${authToken}`;
|
|
671
|
+
merged.set("authorization", bearer);
|
|
672
|
+
merged.set("x-auth", bearer);
|
|
673
|
+
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
644
674
|
}
|
|
645
675
|
return merged;
|
|
646
676
|
}
|
|
@@ -701,15 +731,34 @@ async function buildServerFailureContext(projectRoot, server) {
|
|
|
701
731
|
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
702
732
|
};
|
|
703
733
|
}
|
|
734
|
+
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
735
|
+
try {
|
|
736
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
737
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
738
|
+
} catch {
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
743
|
+
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/");
|
|
744
|
+
}
|
|
704
745
|
async function requestServerJson(context, pathname, init = {}) {
|
|
705
746
|
const server = await ensureServerForCli(context.projectRoot);
|
|
747
|
+
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
748
|
+
if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
749
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
750
|
+
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." });
|
|
751
|
+
}
|
|
706
752
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
707
753
|
if (server.serverProjectRoot)
|
|
708
754
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
755
|
+
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
756
|
+
requestUrl.searchParams.set("rt", server.authToken);
|
|
757
|
+
}
|
|
709
758
|
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
710
759
|
let response;
|
|
711
760
|
try {
|
|
712
|
-
response = await fetch(
|
|
761
|
+
response = await fetch(requestUrl, {
|
|
713
762
|
...init,
|
|
714
763
|
headers
|
|
715
764
|
});
|