@h-rig/cli 0.0.6-alpha.81 → 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 +2807 -1443
- 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 +102 -36
- 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 +13776 -12369
- 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 +3806 -2878
- 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 +15 -13
- package/dist/src/app-opentui/registry.js +3228 -2396
- 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 +413 -958
- package/dist/src/app-opentui/render/image-visual-layer.d.ts +19 -10
- package/dist/src/app-opentui/render/image-visual-layer.js +391 -357
- 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 +2329 -1512
- 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 -1480
|
@@ -192,17 +192,21 @@ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
|
|
|
192
192
|
const scopedKey = resolve2(projectRoot ?? process.cwd());
|
|
193
193
|
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
194
194
|
}
|
|
195
|
-
function
|
|
195
|
+
function readRemoteAuthState(projectRoot) {
|
|
196
196
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
197
197
|
if (!existsSync2(path))
|
|
198
198
|
return null;
|
|
199
199
|
try {
|
|
200
200
|
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
201
|
-
return
|
|
201
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
202
202
|
} catch {
|
|
203
203
|
return null;
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
207
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
208
|
+
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
209
|
+
}
|
|
206
210
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
207
211
|
const scopedKey = resolve2(projectRoot);
|
|
208
212
|
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
@@ -213,15 +217,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
|
|
|
213
217
|
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
214
218
|
}
|
|
215
219
|
function readStoredGitHubAuthToken(projectRoot) {
|
|
216
|
-
const
|
|
217
|
-
|
|
220
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
221
|
+
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
222
|
+
}
|
|
223
|
+
function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
224
|
+
const repo = readRepoConnection(projectRoot);
|
|
225
|
+
const slug = repo?.project?.trim();
|
|
226
|
+
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
218
227
|
return null;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
228
|
+
const auth = readRemoteAuthState(projectRoot);
|
|
229
|
+
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
230
|
+
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
231
|
+
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
223
232
|
return null;
|
|
224
|
-
|
|
233
|
+
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
234
|
+
if (!checkoutBaseDir)
|
|
235
|
+
return null;
|
|
236
|
+
const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
|
|
237
|
+
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
238
|
+
return inferred;
|
|
225
239
|
}
|
|
226
240
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
227
241
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
@@ -232,7 +246,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
232
246
|
if (selected?.connection.kind === "remote") {
|
|
233
247
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
234
248
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
235
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
249
|
+
const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
236
250
|
return {
|
|
237
251
|
baseUrl: selected.connection.baseUrl,
|
|
238
252
|
authToken,
|
|
@@ -261,7 +275,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
261
275
|
if (!slug)
|
|
262
276
|
return null;
|
|
263
277
|
try {
|
|
264
|
-
const
|
|
278
|
+
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
279
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
280
|
+
url.searchParams.set("rt", authToken);
|
|
281
|
+
const response = await fetch(url, {
|
|
265
282
|
headers: mergeHeaders(undefined, authToken)
|
|
266
283
|
});
|
|
267
284
|
if (!response.ok)
|
|
@@ -288,10 +305,23 @@ function appendTaskFilterParams(url, filters) {
|
|
|
288
305
|
if (filters.limit !== undefined)
|
|
289
306
|
url.searchParams.set("limit", String(filters.limit));
|
|
290
307
|
}
|
|
308
|
+
function mergeCookie(existing, name, value) {
|
|
309
|
+
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
310
|
+
if (!existing?.trim())
|
|
311
|
+
return encoded;
|
|
312
|
+
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
313
|
+
return [...parts, encoded].join("; ");
|
|
314
|
+
}
|
|
315
|
+
function queryAuthFallbackEnabled(env = process.env) {
|
|
316
|
+
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
317
|
+
}
|
|
291
318
|
function mergeHeaders(headers, authToken) {
|
|
292
319
|
const merged = new Headers(headers);
|
|
293
320
|
if (authToken) {
|
|
294
|
-
|
|
321
|
+
const bearer = `Bearer ${authToken}`;
|
|
322
|
+
merged.set("authorization", bearer);
|
|
323
|
+
merged.set("x-auth", bearer);
|
|
324
|
+
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
295
325
|
}
|
|
296
326
|
return merged;
|
|
297
327
|
}
|
|
@@ -351,15 +381,34 @@ async function buildServerFailureContext(projectRoot, server) {
|
|
|
351
381
|
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
352
382
|
};
|
|
353
383
|
}
|
|
384
|
+
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
385
|
+
try {
|
|
386
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
387
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
388
|
+
} catch {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
393
|
+
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/");
|
|
394
|
+
}
|
|
354
395
|
async function requestServerJson(context, pathname, init = {}) {
|
|
355
396
|
const server = await ensureServerForCli(context.projectRoot);
|
|
397
|
+
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
398
|
+
if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
399
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
400
|
+
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." });
|
|
401
|
+
}
|
|
356
402
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
357
403
|
if (server.serverProjectRoot)
|
|
358
404
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
405
|
+
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
406
|
+
requestUrl.searchParams.set("rt", server.authToken);
|
|
407
|
+
}
|
|
359
408
|
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
360
409
|
let response;
|
|
361
410
|
try {
|
|
362
|
-
response = await fetch(
|
|
411
|
+
response = await fetch(requestUrl, {
|
|
363
412
|
...init,
|
|
364
413
|
headers
|
|
365
414
|
});
|
|
@@ -421,11 +470,26 @@ async function getGitHubAuthStatusViaServer(context) {
|
|
|
421
470
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
422
471
|
}
|
|
423
472
|
async function postGitHubTokenViaServer(context, token, options = {}) {
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
473
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
474
|
+
const requestUrl = new URL(`${server.baseUrl}/api/github/auth/token`);
|
|
475
|
+
reportServerPhase("POST /api/github/auth/token\u2026");
|
|
476
|
+
let response;
|
|
477
|
+
try {
|
|
478
|
+
response = await fetch(requestUrl, {
|
|
479
|
+
method: "POST",
|
|
480
|
+
headers: { "content-type": "application/json" },
|
|
481
|
+
body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot ?? server.serverProjectRoot ?? undefined })
|
|
482
|
+
});
|
|
483
|
+
} catch (error) {
|
|
484
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
485
|
+
throw new CliError(`Rig server auth bootstrap failed: ${error instanceof Error ? error.message : String(error)}
|
|
486
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
487
|
+
}
|
|
488
|
+
const payload = await response.json().catch(() => null);
|
|
489
|
+
if (!response.ok) {
|
|
490
|
+
const detail = payload && typeof payload === "object" && !Array.isArray(payload) ? String(payload.error ?? JSON.stringify(payload)) : `HTTP ${response.status}`;
|
|
491
|
+
throw new CliError(`Rig server auth bootstrap failed (${response.status}): ${detail}`, 1, { hint: "Re-run `rig github auth import-gh`, or check the selected server with `rig server status`." });
|
|
492
|
+
}
|
|
429
493
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
430
494
|
}
|
|
431
495
|
async function prepareRemoteCheckoutViaServer(context, input) {
|
|
@@ -716,7 +780,7 @@ function projectRootOf(ctx) {
|
|
|
716
780
|
const root = ctx.rig?.projectRoot ?? ctx.projectRoot ?? ctx.getState().projectRoot;
|
|
717
781
|
if (typeof root === "string" && root.trim())
|
|
718
782
|
return root;
|
|
719
|
-
throw new Error("
|
|
783
|
+
throw new Error("App adapter requires a projectRoot.");
|
|
720
784
|
}
|
|
721
785
|
function normalizeAppError(error) {
|
|
722
786
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -800,7 +864,7 @@ async function refreshFleet(ctx, options = {}) {
|
|
|
800
864
|
try {
|
|
801
865
|
const { listRunsViaServer: listRunsViaServer2, resolveServerConnectionLabel: resolveServerConnectionLabel2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
|
|
802
866
|
const projectRoot = projectRootOf(ctx);
|
|
803
|
-
emitProgress(ctx, label, "contacting selected
|
|
867
|
+
emitProgress(ctx, label, "contacting selected server");
|
|
804
868
|
const [runs, serverLabel] = await Promise.all([
|
|
805
869
|
listRunsViaServer2({ projectRoot }, { limit: options.limit ?? 50 }),
|
|
806
870
|
resolveServerConnectionLabel2(projectRoot)
|
|
@@ -455,17 +455,21 @@ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
|
|
|
455
455
|
const scopedKey = resolve2(projectRoot ?? process.cwd());
|
|
456
456
|
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
457
457
|
}
|
|
458
|
-
function
|
|
458
|
+
function readRemoteAuthState(projectRoot) {
|
|
459
459
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
460
460
|
if (!existsSync2(path))
|
|
461
461
|
return null;
|
|
462
462
|
try {
|
|
463
463
|
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
464
|
-
return
|
|
464
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
465
465
|
} catch {
|
|
466
466
|
return null;
|
|
467
467
|
}
|
|
468
468
|
}
|
|
469
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
470
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
471
|
+
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
472
|
+
}
|
|
469
473
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
470
474
|
const scopedKey = resolve2(projectRoot);
|
|
471
475
|
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
@@ -476,15 +480,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
|
|
|
476
480
|
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
477
481
|
}
|
|
478
482
|
function readStoredGitHubAuthToken(projectRoot) {
|
|
479
|
-
const
|
|
480
|
-
|
|
483
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
484
|
+
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
485
|
+
}
|
|
486
|
+
function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
487
|
+
const repo = readRepoConnection(projectRoot);
|
|
488
|
+
const slug = repo?.project?.trim();
|
|
489
|
+
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
481
490
|
return null;
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
491
|
+
const auth = readRemoteAuthState(projectRoot);
|
|
492
|
+
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
493
|
+
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
494
|
+
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
486
495
|
return null;
|
|
487
|
-
|
|
496
|
+
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
497
|
+
if (!checkoutBaseDir)
|
|
498
|
+
return null;
|
|
499
|
+
const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
|
|
500
|
+
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
501
|
+
return inferred;
|
|
488
502
|
}
|
|
489
503
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
490
504
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
@@ -495,7 +509,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
495
509
|
if (selected?.connection.kind === "remote") {
|
|
496
510
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
497
511
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
498
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
512
|
+
const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
499
513
|
return {
|
|
500
514
|
baseUrl: selected.connection.baseUrl,
|
|
501
515
|
authToken,
|
|
@@ -524,7 +538,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
524
538
|
if (!slug)
|
|
525
539
|
return null;
|
|
526
540
|
try {
|
|
527
|
-
const
|
|
541
|
+
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
542
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
543
|
+
url.searchParams.set("rt", authToken);
|
|
544
|
+
const response = await fetch(url, {
|
|
528
545
|
headers: mergeHeaders(undefined, authToken)
|
|
529
546
|
});
|
|
530
547
|
if (!response.ok)
|
|
@@ -551,10 +568,23 @@ function appendTaskFilterParams(url, filters) {
|
|
|
551
568
|
if (filters.limit !== undefined)
|
|
552
569
|
url.searchParams.set("limit", String(filters.limit));
|
|
553
570
|
}
|
|
571
|
+
function mergeCookie(existing, name, value) {
|
|
572
|
+
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
573
|
+
if (!existing?.trim())
|
|
574
|
+
return encoded;
|
|
575
|
+
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
576
|
+
return [...parts, encoded].join("; ");
|
|
577
|
+
}
|
|
578
|
+
function queryAuthFallbackEnabled(env = process.env) {
|
|
579
|
+
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
580
|
+
}
|
|
554
581
|
function mergeHeaders(headers, authToken) {
|
|
555
582
|
const merged = new Headers(headers);
|
|
556
583
|
if (authToken) {
|
|
557
|
-
|
|
584
|
+
const bearer = `Bearer ${authToken}`;
|
|
585
|
+
merged.set("authorization", bearer);
|
|
586
|
+
merged.set("x-auth", bearer);
|
|
587
|
+
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
558
588
|
}
|
|
559
589
|
return merged;
|
|
560
590
|
}
|
|
@@ -614,15 +644,34 @@ async function buildServerFailureContext(projectRoot, server) {
|
|
|
614
644
|
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
615
645
|
};
|
|
616
646
|
}
|
|
647
|
+
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
648
|
+
try {
|
|
649
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
650
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
651
|
+
} catch {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
656
|
+
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/");
|
|
657
|
+
}
|
|
617
658
|
async function requestServerJson(context, pathname, init = {}) {
|
|
618
659
|
const server = await ensureServerForCli(context.projectRoot);
|
|
660
|
+
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
661
|
+
if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
662
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
663
|
+
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." });
|
|
664
|
+
}
|
|
619
665
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
620
666
|
if (server.serverProjectRoot)
|
|
621
667
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
668
|
+
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
669
|
+
requestUrl.searchParams.set("rt", server.authToken);
|
|
670
|
+
}
|
|
622
671
|
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
623
672
|
let response;
|
|
624
673
|
try {
|
|
625
|
-
response = await fetch(
|
|
674
|
+
response = await fetch(requestUrl, {
|
|
626
675
|
...init,
|
|
627
676
|
headers
|
|
628
677
|
});
|
|
@@ -684,11 +733,26 @@ async function getGitHubAuthStatusViaServer(context) {
|
|
|
684
733
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
685
734
|
}
|
|
686
735
|
async function postGitHubTokenViaServer(context, token, options = {}) {
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
736
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
737
|
+
const requestUrl = new URL(`${server.baseUrl}/api/github/auth/token`);
|
|
738
|
+
reportServerPhase("POST /api/github/auth/token\u2026");
|
|
739
|
+
let response;
|
|
740
|
+
try {
|
|
741
|
+
response = await fetch(requestUrl, {
|
|
742
|
+
method: "POST",
|
|
743
|
+
headers: { "content-type": "application/json" },
|
|
744
|
+
body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot ?? server.serverProjectRoot ?? undefined })
|
|
745
|
+
});
|
|
746
|
+
} catch (error) {
|
|
747
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
748
|
+
throw new CliError(`Rig server auth bootstrap failed: ${error instanceof Error ? error.message : String(error)}
|
|
749
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
750
|
+
}
|
|
751
|
+
const payload = await response.json().catch(() => null);
|
|
752
|
+
if (!response.ok) {
|
|
753
|
+
const detail = payload && typeof payload === "object" && !Array.isArray(payload) ? String(payload.error ?? JSON.stringify(payload)) : `HTTP ${response.status}`;
|
|
754
|
+
throw new CliError(`Rig server auth bootstrap failed (${response.status}): ${detail}`, 1, { hint: "Re-run `rig github auth import-gh`, or check the selected server with `rig server status`." });
|
|
755
|
+
}
|
|
692
756
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
693
757
|
}
|
|
694
758
|
async function prepareRemoteCheckoutViaServer(context, input) {
|
|
@@ -1258,7 +1322,7 @@ function projectRootOf(ctx) {
|
|
|
1258
1322
|
const root = ctx.rig?.projectRoot ?? ctx.projectRoot ?? ctx.getState().projectRoot;
|
|
1259
1323
|
if (typeof root === "string" && root.trim())
|
|
1260
1324
|
return root;
|
|
1261
|
-
throw new Error("
|
|
1325
|
+
throw new Error("App adapter requires a projectRoot.");
|
|
1262
1326
|
}
|
|
1263
1327
|
function normalizeAppError(error) {
|
|
1264
1328
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -193,17 +193,21 @@ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
|
|
|
193
193
|
const scopedKey = resolve2(projectRoot ?? process.cwd());
|
|
194
194
|
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
195
195
|
}
|
|
196
|
-
function
|
|
196
|
+
function readRemoteAuthState(projectRoot) {
|
|
197
197
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
198
198
|
if (!existsSync2(path))
|
|
199
199
|
return null;
|
|
200
200
|
try {
|
|
201
201
|
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
202
|
-
return
|
|
202
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
203
203
|
} catch {
|
|
204
204
|
return null;
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
208
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
209
|
+
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
210
|
+
}
|
|
207
211
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
208
212
|
const scopedKey = resolve2(projectRoot);
|
|
209
213
|
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
@@ -214,15 +218,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
|
|
|
214
218
|
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
215
219
|
}
|
|
216
220
|
function readStoredGitHubAuthToken(projectRoot) {
|
|
217
|
-
const
|
|
218
|
-
|
|
221
|
+
const parsed = readRemoteAuthState(projectRoot);
|
|
222
|
+
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
223
|
+
}
|
|
224
|
+
function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
225
|
+
const repo = readRepoConnection(projectRoot);
|
|
226
|
+
const slug = repo?.project?.trim();
|
|
227
|
+
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
219
228
|
return null;
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
229
|
+
const auth = readRemoteAuthState(projectRoot);
|
|
230
|
+
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
231
|
+
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
232
|
+
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
224
233
|
return null;
|
|
225
|
-
|
|
234
|
+
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
235
|
+
if (!checkoutBaseDir)
|
|
236
|
+
return null;
|
|
237
|
+
const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
|
|
238
|
+
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
239
|
+
return inferred;
|
|
226
240
|
}
|
|
227
241
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
228
242
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
@@ -233,7 +247,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
233
247
|
if (selected?.connection.kind === "remote") {
|
|
234
248
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
235
249
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
236
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
250
|
+
const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
237
251
|
return {
|
|
238
252
|
baseUrl: selected.connection.baseUrl,
|
|
239
253
|
authToken,
|
|
@@ -262,7 +276,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
262
276
|
if (!slug)
|
|
263
277
|
return null;
|
|
264
278
|
try {
|
|
265
|
-
const
|
|
279
|
+
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
280
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
281
|
+
url.searchParams.set("rt", authToken);
|
|
282
|
+
const response = await fetch(url, {
|
|
266
283
|
headers: mergeHeaders(undefined, authToken)
|
|
267
284
|
});
|
|
268
285
|
if (!response.ok)
|
|
@@ -279,10 +296,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
|
|
|
279
296
|
return null;
|
|
280
297
|
}
|
|
281
298
|
}
|
|
299
|
+
function mergeCookie(existing, name, value) {
|
|
300
|
+
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
301
|
+
if (!existing?.trim())
|
|
302
|
+
return encoded;
|
|
303
|
+
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
304
|
+
return [...parts, encoded].join("; ");
|
|
305
|
+
}
|
|
306
|
+
function queryAuthFallbackEnabled(env = process.env) {
|
|
307
|
+
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
308
|
+
}
|
|
282
309
|
function mergeHeaders(headers, authToken) {
|
|
283
310
|
const merged = new Headers(headers);
|
|
284
311
|
if (authToken) {
|
|
285
|
-
|
|
312
|
+
const bearer = `Bearer ${authToken}`;
|
|
313
|
+
merged.set("authorization", bearer);
|
|
314
|
+
merged.set("x-auth", bearer);
|
|
315
|
+
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
286
316
|
}
|
|
287
317
|
return merged;
|
|
288
318
|
}
|
|
@@ -342,15 +372,34 @@ async function buildServerFailureContext(projectRoot, server) {
|
|
|
342
372
|
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
343
373
|
};
|
|
344
374
|
}
|
|
375
|
+
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
376
|
+
try {
|
|
377
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
378
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
379
|
+
} catch {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
384
|
+
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/");
|
|
385
|
+
}
|
|
345
386
|
async function requestServerJson(context, pathname, init = {}) {
|
|
346
387
|
const server = await ensureServerForCli(context.projectRoot);
|
|
388
|
+
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
389
|
+
if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
390
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
391
|
+
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." });
|
|
392
|
+
}
|
|
347
393
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
348
394
|
if (server.serverProjectRoot)
|
|
349
395
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
396
|
+
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
397
|
+
requestUrl.searchParams.set("rt", server.authToken);
|
|
398
|
+
}
|
|
350
399
|
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
351
400
|
let response;
|
|
352
401
|
try {
|
|
353
|
-
response = await fetch(
|
|
402
|
+
response = await fetch(requestUrl, {
|
|
354
403
|
...init,
|
|
355
404
|
headers
|
|
356
405
|
});
|
|
@@ -379,11 +428,26 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
|
|
|
379
428
|
return payload;
|
|
380
429
|
}
|
|
381
430
|
async function postGitHubTokenViaServer(context, token, options = {}) {
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
431
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
432
|
+
const requestUrl = new URL(`${server.baseUrl}/api/github/auth/token`);
|
|
433
|
+
reportServerPhase("POST /api/github/auth/token\u2026");
|
|
434
|
+
let response;
|
|
435
|
+
try {
|
|
436
|
+
response = await fetch(requestUrl, {
|
|
437
|
+
method: "POST",
|
|
438
|
+
headers: { "content-type": "application/json" },
|
|
439
|
+
body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot ?? server.serverProjectRoot ?? undefined })
|
|
440
|
+
});
|
|
441
|
+
} catch (error) {
|
|
442
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
443
|
+
throw new CliError(`Rig server auth bootstrap failed: ${error instanceof Error ? error.message : String(error)}
|
|
444
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
445
|
+
}
|
|
446
|
+
const payload = await response.json().catch(() => null);
|
|
447
|
+
if (!response.ok) {
|
|
448
|
+
const detail = payload && typeof payload === "object" && !Array.isArray(payload) ? String(payload.error ?? JSON.stringify(payload)) : `HTTP ${response.status}`;
|
|
449
|
+
throw new CliError(`Rig server auth bootstrap failed (${response.status}): ${detail}`, 1, { hint: "Re-run `rig github auth import-gh`, or check the selected server with `rig server status`." });
|
|
450
|
+
}
|
|
387
451
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
388
452
|
}
|
|
389
453
|
async function prepareRemoteCheckoutViaServer(context, input) {
|
|
@@ -2216,7 +2280,7 @@ function projectRootOf(ctx) {
|
|
|
2216
2280
|
const root = ctx.rig?.projectRoot ?? ctx.projectRoot ?? ctx.getState().projectRoot;
|
|
2217
2281
|
if (typeof root === "string" && root.trim())
|
|
2218
2282
|
return root;
|
|
2219
|
-
throw new Error("
|
|
2283
|
+
throw new Error("App adapter requires a projectRoot.");
|
|
2220
2284
|
}
|
|
2221
2285
|
async function runtimeOf(ctx) {
|
|
2222
2286
|
if (ctx.rig && "runId" in ctx.rig && ctx.ensureRuntime === undefined) {
|
|
@@ -2224,7 +2288,7 @@ async function runtimeOf(ctx) {
|
|
|
2224
2288
|
}
|
|
2225
2289
|
if (ctx.ensureRuntime)
|
|
2226
2290
|
return ctx.ensureRuntime();
|
|
2227
|
-
throw new Error("
|
|
2291
|
+
throw new Error("App adapter requires ensureRuntime() before this action can run.");
|
|
2228
2292
|
}
|
|
2229
2293
|
function normalizeAppError(error) {
|
|
2230
2294
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2292,8 +2356,8 @@ function createInitAdapter() {
|
|
|
2292
2356
|
if (intent.scene !== "init" && intent.action.kind !== "init-start")
|
|
2293
2357
|
return false;
|
|
2294
2358
|
const ctx = adapterContextFromRuntime(runtime);
|
|
2295
|
-
if (intent.action.kind === "init-start"
|
|
2296
|
-
await runInitYes(ctx,
|
|
2359
|
+
if (intent.action.kind === "init-start") {
|
|
2360
|
+
await runInitYes(ctx, intent.argv.slice(1));
|
|
2297
2361
|
return true;
|
|
2298
2362
|
}
|
|
2299
2363
|
await loadInitFacts(ctx);
|
|
@@ -2326,7 +2390,7 @@ async function loadInitFacts(ctx) {
|
|
|
2326
2390
|
}
|
|
2327
2391
|
}
|
|
2328
2392
|
async function runInitYes(ctx, args = ["--yes"]) {
|
|
2329
|
-
const label = "Initializing
|
|
2393
|
+
const label = "Initializing project";
|
|
2330
2394
|
emitStarted(ctx, label, { init: { status: "writing-config" } });
|
|
2331
2395
|
try {
|
|
2332
2396
|
const runtime = await runtimeOf(ctx);
|