@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.
Files changed (100) hide show
  1. package/dist/bin/build-rig-binaries.js +40 -8
  2. package/dist/bin/rig.js +2807 -1443
  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 +102 -36
  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 +13776 -12369
  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 +3806 -2878
  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 +15 -13
  33. package/dist/src/app-opentui/registry.js +3228 -2396
  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 +413 -958
  39. package/dist/src/app-opentui/render/image-visual-layer.d.ts +19 -10
  40. package/dist/src/app-opentui/render/image-visual-layer.js +391 -357
  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 +2329 -1512
  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 -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 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);
@@ -776,7 +840,9 @@ import { Terminal as XtermTerminal } from "@xterm/headless";
776
840
  var MIN_COLS = 40;
777
841
  var MIN_ROWS = 12;
778
842
  var MAX_ROWS = 300;
779
- var MAX_SNAPSHOT_LINES = 1200;
843
+ var MAX_SNAPSHOT_LINES = 360;
844
+ var STYLED_SNAPSHOT_MARGIN = 6;
845
+ var SNAPSHOT_DELAY_MS = 120;
780
846
  var fallbackChildScriptPath = fileURLToPath(new URL("./pi-host-child.ts", import.meta.url));
781
847
  var activeHost = null;
782
848
  function clampCols(cols) {
@@ -874,17 +940,15 @@ function lineToStyledText(line, cols) {
874
940
  function childCommandPrefix() {
875
941
  const execName = basename(process.execPath).toLowerCase();
876
942
  const currentEntry = process.argv[1];
877
- if ((execName === "bun" || execName === "bun.exe") && currentEntry) {
878
- return [process.execPath, currentEntry, "__opentui-pi-host"];
943
+ if (execName === "bun" || execName === "bun.exe") {
944
+ return currentEntry ? [process.execPath, currentEntry, "__opentui-pi-host"] : [process.execPath, fallbackChildScriptPath];
879
945
  }
880
- if (currentEntry)
881
- return [process.execPath, "__opentui-pi-host"];
882
- return [process.execPath, fallbackChildScriptPath];
946
+ return [process.execPath, "__opentui-pi-host"];
883
947
  }
884
948
  function withEnv(base) {
885
949
  const env = {};
886
950
  for (const [key, value] of Object.entries(base ?? process.env)) {
887
- if (key === "RIG_PLAIN" || key === "RIG_CLI_PLAIN_HELP")
951
+ if (key === "RIG_PLAIN" || key === "RIG_CLI_PLAIN_HELP" || key === "NO_COLOR")
888
952
  continue;
889
953
  if (typeof value === "string")
890
954
  env[key] = value;
@@ -935,7 +999,7 @@ class PiPtyHost {
935
999
  allowProposedApi: true,
936
1000
  cols: this.cols,
937
1001
  rows: this.rows,
938
- scrollback: 5000
1002
+ scrollback: 1000
939
1003
  });
940
1004
  this.registerTerminalResponders();
941
1005
  }
@@ -949,7 +1013,7 @@ class PiPtyHost {
949
1013
  if (this._disposed)
950
1014
  throw new Error("Pi PTY host is disposed.");
951
1015
  if (typeof Bun.Terminal !== "function") {
952
- throw new Error("Bun native PTY is unavailable in this runtime. Rig OpenTUI Pi host requires Bun.Terminal.");
1016
+ throw new Error("Bun native PTY is unavailable in this runtime. Pi host requires Bun.Terminal.");
953
1017
  }
954
1018
  const spawnOptions = {
955
1019
  cwd: this.projectRoot,
@@ -973,7 +1037,7 @@ class PiPtyHost {
973
1037
  this.proc = proc;
974
1038
  this.pty = proc.terminal;
975
1039
  this.status = "running";
976
- this.message = "bundled Pi running inside Rig";
1040
+ this.message = "bundled Pi running inside this app";
977
1041
  this.emitSnapshotSoon(0);
978
1042
  proc.exited.then((exitCode) => {
979
1043
  if (this._disposed)
@@ -1068,7 +1132,7 @@ class PiPtyHost {
1068
1132
  this.respondToRawTerminalQueries(text);
1069
1133
  this.terminal.write(data, () => this.emitSnapshotSoon());
1070
1134
  }
1071
- emitSnapshotSoon(delayMs = 16) {
1135
+ emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS) {
1072
1136
  if (this._disposed || this.notifyTimer)
1073
1137
  return;
1074
1138
  this.notifyTimer = setTimeout(() => {
@@ -1082,16 +1146,18 @@ class PiPtyHost {
1082
1146
  const buffer = this.terminal.buffer.active;
1083
1147
  const end = buffer.length;
1084
1148
  const start = Math.max(0, end - MAX_SNAPSHOT_LINES);
1149
+ const styledStart = Math.max(start, end - this.rows - STYLED_SNAPSHOT_MARGIN);
1085
1150
  const lines = [];
1086
1151
  const styledLines = [];
1087
1152
  for (let row = start;row < end; row += 1) {
1088
1153
  const line = buffer.getLine(row);
1089
1154
  lines.push((line?.translateToString(false) ?? "").slice(0, this.cols));
1090
- styledLines.push(lineToStyledText(line, this.cols));
1155
+ if (row >= styledStart)
1156
+ styledLines[lines.length - 1] = lineToStyledText(line, this.cols);
1091
1157
  }
1092
1158
  while (lines.length < this.rows) {
1093
1159
  lines.push("");
1094
- styledLines.push(new StyledText([{ __isChunk: true, text: "" }]));
1160
+ styledLines[lines.length - 1] = new StyledText([{ __isChunk: true, text: "" }]);
1095
1161
  }
1096
1162
  return {
1097
1163
  runId: this.runId,
@@ -1161,7 +1227,7 @@ async function refreshFleet(ctx, options = {}) {
1161
1227
  try {
1162
1228
  const { listRunsViaServer: listRunsViaServer2, resolveServerConnectionLabel: resolveServerConnectionLabel2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
1163
1229
  const projectRoot = projectRootOf(ctx);
1164
- emitProgress(ctx, label, "contacting selected Rig server");
1230
+ emitProgress(ctx, label, "contacting selected server");
1165
1231
  const [runs, serverLabel] = await Promise.all([
1166
1232
  listRunsViaServer2({ projectRoot }, { limit: options.limit ?? 50 }),
1167
1233
  resolveServerConnectionLabel2(projectRoot)
@@ -1240,7 +1306,7 @@ async function attachRunWithBundledPi(ctx, runId) {
1240
1306
  throw error;
1241
1307
  }
1242
1308
  emitStarted(ctx, label, { piAttach: { runId: cleanRunId, status: "entering-pi" } });
1243
- patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "starting bundled Pi inside Rig" } });
1309
+ patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "starting bundled Pi" } });
1244
1310
  try {
1245
1311
  await preparePiAttachHandoff(ctx, cleanRunId).catch(() => null);
1246
1312
  emitProgress(ctx, label, "starting Bun PTY host for bundled Pi");
@@ -1253,7 +1319,7 @@ async function attachRunWithBundledPi(ctx, runId) {
1253
1319
  piAttach: {
1254
1320
  runId: cleanRunId,
1255
1321
  status: snapshot.status === "failed" ? "failed" : snapshot.status === "exited" ? "returned" : "entering-pi",
1256
- message: snapshot.message ?? "bundled Pi running inside Rig",
1322
+ message: snapshot.message ?? "bundled Pi running",
1257
1323
  result: { runId: cleanRunId, status: snapshot.status, exitCode: snapshot.exitCode }
1258
1324
  }
1259
1325
  });
@@ -1280,7 +1346,7 @@ async function attachRunWithBundledPi(ctx, runId) {
1280
1346
  status: "running",
1281
1347
  rendered: "bundled Pi embedded via Bun.Terminal + @xterm/headless"
1282
1348
  };
1283
- emitProgress(ctx, label, "bundled Pi is running inside Rig", { piAttach: { runId: cleanRunId, status: "entering-pi", message: "bundled Pi running inside Rig", result: record } });
1349
+ emitProgress(ctx, label, "bundled Pi is running", { piAttach: { runId: cleanRunId, status: "entering-pi", message: "bundled Pi running", result: record } });
1284
1350
  return record;
1285
1351
  } catch (error) {
1286
1352
  patchData(ctx, { piAttach: { runId: cleanRunId, status: "failed", message: error instanceof Error ? error.message : String(error) } });
@@ -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);