@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.
Files changed (100) hide show
  1. package/dist/bin/build-rig-binaries.js +40 -8
  2. package/dist/bin/rig.js +2798 -1440
  3. package/dist/src/app/board.js +62 -13
  4. package/dist/src/app-opentui/adapters/command.d.ts +2 -0
  5. package/dist/src/app-opentui/adapters/command.js +329 -0
  6. package/dist/src/app-opentui/adapters/common.js +2 -2
  7. package/dist/src/app-opentui/adapters/doctor.js +63 -14
  8. package/dist/src/app-opentui/adapters/fleet.js +84 -20
  9. package/dist/src/app-opentui/adapters/inbox.js +83 -19
  10. package/dist/src/app-opentui/adapters/init.js +87 -23
  11. package/dist/src/app-opentui/adapters/pi-attach.js +96 -34
  12. package/dist/src/app-opentui/adapters/run-detail.js +83 -19
  13. package/dist/src/app-opentui/adapters/server.js +100 -23
  14. package/dist/src/app-opentui/adapters/tasks.d.ts +11 -0
  15. package/dist/src/app-opentui/adapters/tasks.js +742 -94
  16. package/dist/src/app-opentui/bootstrap.d.ts +1 -6
  17. package/dist/src/app-opentui/bootstrap.js +13769 -12368
  18. package/dist/src/app-opentui/command-pty-host.d.ts +62 -0
  19. package/dist/src/app-opentui/command-pty-host.js +248 -0
  20. package/dist/src/app-opentui/events.js +1 -1
  21. package/dist/src/app-opentui/focus-manager.d.ts +14 -0
  22. package/dist/src/app-opentui/focus-manager.js +24 -0
  23. package/dist/src/app-opentui/index.js +3802 -2882
  24. package/dist/src/app-opentui/intent.js +154 -48
  25. package/dist/src/app-opentui/keymap.d.ts +20 -0
  26. package/dist/src/app-opentui/keymap.js +707 -0
  27. package/dist/src/app-opentui/launch-routing.d.ts +16 -0
  28. package/dist/src/app-opentui/launch-routing.js +55 -0
  29. package/dist/src/app-opentui/layout.js +2 -2
  30. package/dist/src/app-opentui/pi-host-child.js +66 -16
  31. package/dist/src/app-opentui/pi-pty-host.d.ts +9 -0
  32. package/dist/src/app-opentui/pi-pty-host.js +9 -11
  33. package/dist/src/app-opentui/registry.js +3231 -2403
  34. package/dist/src/app-opentui/render/ascii-fleet.d.ts +15 -0
  35. package/dist/src/app-opentui/render/ascii-fleet.js +82 -0
  36. package/dist/src/app-opentui/render/graphics.d.ts +1 -1
  37. package/dist/src/app-opentui/render/graphics.js +131 -16
  38. package/dist/src/app-opentui/render/image-visual-layer-worker.js +408 -957
  39. package/dist/src/app-opentui/render/image-visual-layer.d.ts +18 -10
  40. package/dist/src/app-opentui/render/image-visual-layer.js +386 -356
  41. package/dist/src/app-opentui/render/panel-layout.d.ts +38 -0
  42. package/dist/src/app-opentui/render/panel-layout.js +48 -0
  43. package/dist/src/app-opentui/render/panels.d.ts +2 -0
  44. package/dist/src/app-opentui/render/panels.js +62 -29
  45. package/dist/src/app-opentui/render/preloader.d.ts +10 -0
  46. package/dist/src/app-opentui/render/preloader.js +163 -0
  47. package/dist/src/app-opentui/render/scene.d.ts +3 -0
  48. package/dist/src/app-opentui/render/scene.js +12 -0
  49. package/dist/src/app-opentui/render/text.js +5 -1
  50. package/dist/src/app-opentui/render/type-bar.d.ts +2 -1
  51. package/dist/src/app-opentui/render/type-bar.js +51 -16
  52. package/dist/src/app-opentui/runtime-resources.d.ts +16 -0
  53. package/dist/src/app-opentui/runtime-resources.js +62 -0
  54. package/dist/src/app-opentui/runtime.js +2322 -1513
  55. package/dist/src/app-opentui/scenes/command.d.ts +3 -0
  56. package/dist/src/app-opentui/scenes/command.js +103 -0
  57. package/dist/src/app-opentui/scenes/doctor.d.ts +2 -1
  58. package/dist/src/app-opentui/scenes/doctor.js +53 -14
  59. package/dist/src/app-opentui/scenes/error.js +44 -14
  60. package/dist/src/app-opentui/scenes/fleet.js +40 -21
  61. package/dist/src/app-opentui/scenes/handoff.js +142 -27
  62. package/dist/src/app-opentui/scenes/help.js +63 -48
  63. package/dist/src/app-opentui/scenes/inbox.d.ts +2 -1
  64. package/dist/src/app-opentui/scenes/inbox.js +64 -17
  65. package/dist/src/app-opentui/scenes/init.d.ts +2 -1
  66. package/dist/src/app-opentui/scenes/init.js +62 -21
  67. package/dist/src/app-opentui/scenes/main.d.ts +2 -1
  68. package/dist/src/app-opentui/scenes/main.js +80 -24
  69. package/dist/src/app-opentui/scenes/run-detail.d.ts +2 -1
  70. package/dist/src/app-opentui/scenes/run-detail.js +39 -25
  71. package/dist/src/app-opentui/scenes/server.d.ts +2 -1
  72. package/dist/src/app-opentui/scenes/server.js +71 -20
  73. package/dist/src/app-opentui/scenes/tasks.js +44 -22
  74. package/dist/src/app-opentui/state.js +70 -30
  75. package/dist/src/app-opentui/terminal-capabilities.d.ts +7 -0
  76. package/dist/src/app-opentui/terminal-capabilities.js +15 -0
  77. package/dist/src/app-opentui/types.d.ts +10 -2
  78. package/dist/src/commands/_doctor-checks.js +62 -13
  79. package/dist/src/commands/_operator-view.js +63 -13
  80. package/dist/src/commands/_pi-frontend.js +63 -13
  81. package/dist/src/commands/_preflight.js +64 -14
  82. package/dist/src/commands/_server-client.js +82 -18
  83. package/dist/src/commands/_snapshot-upload.js +62 -13
  84. package/dist/src/commands/connect.js +7 -1
  85. package/dist/src/commands/doctor.js +62 -13
  86. package/dist/src/commands/github.js +144 -23
  87. package/dist/src/commands/inbox.js +62 -13
  88. package/dist/src/commands/init.js +82 -18
  89. package/dist/src/commands/inspect.js +62 -13
  90. package/dist/src/commands/run.js +66 -13
  91. package/dist/src/commands/server.js +69 -14
  92. package/dist/src/commands/setup.js +62 -13
  93. package/dist/src/commands/stats.js +62 -13
  94. package/dist/src/commands/task-run-driver.js +62 -13
  95. package/dist/src/commands/task.js +65 -14
  96. package/dist/src/commands.js +227 -92
  97. package/dist/src/index.js +234 -99
  98. package/package.json +8 -9
  99. package/dist/src/app-opentui/render/image-visual-layer-native-canvas.d.ts +0 -2
  100. package/dist/src/app-opentui/render/image-visual-layer-native-canvas.js +0 -1484
@@ -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 readPrivateRemoteSessionToken(projectRoot) {
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 cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
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 path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
217
- if (!existsSync2(path))
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
- try {
220
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
221
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
222
- } catch {
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 response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
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
- merged.set("authorization", `Bearer ${authToken}`);
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(`${server.baseUrl}${pathname}`, {
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 payload = await requestServerJson(context, "/api/github/auth/token", {
425
- method: "POST",
426
- headers: { "content-type": "application/json" },
427
- body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot })
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("Rig app adapter requires a projectRoot.");
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 Rig server");
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 readPrivateRemoteSessionToken(projectRoot) {
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 cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
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 path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
480
- if (!existsSync2(path))
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
- try {
483
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
484
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
485
- } catch {
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 response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
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
- merged.set("authorization", `Bearer ${authToken}`);
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(`${server.baseUrl}${pathname}`, {
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 payload = await requestServerJson(context, "/api/github/auth/token", {
688
- method: "POST",
689
- headers: { "content-type": "application/json" },
690
- body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot })
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("Rig app adapter requires a projectRoot.");
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 readPrivateRemoteSessionToken(projectRoot) {
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 cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
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 path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
218
- if (!existsSync2(path))
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
- try {
221
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
222
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
223
- } catch {
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 response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
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
- merged.set("authorization", `Bearer ${authToken}`);
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(`${server.baseUrl}${pathname}`, {
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 payload = await requestServerJson(context, "/api/github/auth/token", {
383
- method: "POST",
384
- headers: { "content-type": "application/json" },
385
- body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot })
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("Rig app adapter requires a projectRoot.");
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("Rig app adapter requires ensureRuntime() before this action can run.");
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" && intent.argv.includes("--yes")) {
2296
- await runInitYes(ctx, ["--yes"]);
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 Rig project";
2393
+ const label = "Initializing project";
2330
2394
  emitStarted(ctx, label, { init: { status: "writing-config" } });
2331
2395
  try {
2332
2396
  const runtime = await runtimeOf(ctx);