@h-rig/cli 0.0.6-alpha.82 → 0.0.6-alpha.84

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 (206) hide show
  1. package/dist/bin/build-rig-binaries.js +40 -8
  2. package/dist/bin/rig.js +23402 -14577
  3. package/dist/src/app/board.js +217 -41
  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.d.ts +0 -2
  8. package/dist/src/app-opentui/adapters/doctor.js +64 -39
  9. package/dist/src/app-opentui/adapters/family.d.ts +62 -0
  10. package/dist/src/app-opentui/adapters/family.js +14305 -0
  11. package/dist/src/app-opentui/adapters/fleet.d.ts +0 -2
  12. package/dist/src/app-opentui/adapters/fleet.js +90 -60
  13. package/dist/src/app-opentui/adapters/inbox.d.ts +12 -2
  14. package/dist/src/app-opentui/adapters/inbox.js +137 -78
  15. package/dist/src/app-opentui/adapters/init.d.ts +0 -2
  16. package/dist/src/app-opentui/adapters/init.js +85 -47
  17. package/dist/src/app-opentui/adapters/inspect.d.ts +52 -0
  18. package/dist/src/app-opentui/adapters/inspect.js +1024 -0
  19. package/dist/src/app-opentui/adapters/pi-attach.d.ts +15 -6
  20. package/dist/src/app-opentui/adapters/pi-attach.js +442 -125
  21. package/dist/src/app-opentui/adapters/pi.d.ts +23 -0
  22. package/dist/src/app-opentui/adapters/pi.js +363 -0
  23. package/dist/src/app-opentui/adapters/plugin.d.ts +84 -0
  24. package/dist/src/app-opentui/adapters/plugin.js +544 -0
  25. package/dist/src/app-opentui/adapters/repo.d.ts +37 -0
  26. package/dist/src/app-opentui/adapters/repo.js +186 -0
  27. package/dist/src/app-opentui/adapters/run-detail.d.ts +9 -2
  28. package/dist/src/app-opentui/adapters/run-detail.js +180 -63
  29. package/dist/src/app-opentui/adapters/server.d.ts +10 -2
  30. package/dist/src/app-opentui/adapters/server.js +191 -45
  31. package/dist/src/app-opentui/adapters/tasks.d.ts +11 -2
  32. package/dist/src/app-opentui/adapters/tasks.js +1123 -143
  33. package/dist/src/app-opentui/adapters/workspace.d.ts +49 -0
  34. package/dist/src/app-opentui/adapters/workspace.js +333 -0
  35. package/dist/src/app-opentui/autocomplete.d.ts +20 -0
  36. package/dist/src/app-opentui/autocomplete.js +576 -0
  37. package/dist/src/app-opentui/bootstrap.d.ts +1 -6
  38. package/dist/src/app-opentui/bootstrap.js +25252 -16474
  39. package/dist/src/app-opentui/command-palette.d.ts +3 -0
  40. package/dist/src/app-opentui/command-palette.js +1010 -0
  41. package/dist/src/app-opentui/command-pty-host.d.ts +62 -0
  42. package/dist/src/app-opentui/command-pty-host.js +248 -0
  43. package/dist/src/app-opentui/drone.js +8 -6
  44. package/dist/src/app-opentui/events.js +1 -1
  45. package/dist/src/app-opentui/fleet-stats.d.ts +32 -0
  46. package/dist/src/app-opentui/fleet-stats.js +114 -0
  47. package/dist/src/app-opentui/focus-manager.d.ts +14 -0
  48. package/dist/src/app-opentui/focus-manager.js +24 -0
  49. package/dist/src/app-opentui/index.js +5431 -2797
  50. package/dist/src/app-opentui/intent.js +179 -50
  51. package/dist/src/app-opentui/keymap.d.ts +21 -0
  52. package/dist/src/app-opentui/keymap.js +1748 -0
  53. package/dist/src/app-opentui/launch-routing.d.ts +16 -0
  54. package/dist/src/app-opentui/launch-routing.js +55 -0
  55. package/dist/src/app-opentui/layout.d.ts +7 -0
  56. package/dist/src/app-opentui/layout.js +13 -6
  57. package/dist/src/app-opentui/list-search.d.ts +24 -0
  58. package/dist/src/app-opentui/list-search.js +88 -1
  59. package/dist/src/app-opentui/pi-host-child.js +99 -17
  60. package/dist/src/app-opentui/pi-pty-host.d.ts +14 -0
  61. package/dist/src/app-opentui/pi-pty-host.js +30 -14
  62. package/dist/src/app-opentui/react/App.d.ts +9 -0
  63. package/dist/src/app-opentui/react/App.js +5144 -0
  64. package/dist/src/app-opentui/react/Backdrop.d.ts +5 -0
  65. package/dist/src/app-opentui/react/Backdrop.js +1834 -0
  66. package/dist/src/app-opentui/react/ChromeHost.d.ts +5 -0
  67. package/dist/src/app-opentui/react/ChromeHost.js +2942 -0
  68. package/dist/src/app-opentui/react/SceneFrameView.d.ts +7 -0
  69. package/dist/src/app-opentui/react/SceneFrameView.js +529 -0
  70. package/dist/src/app-opentui/react/context.d.ts +17 -0
  71. package/dist/src/app-opentui/react/context.js +37 -0
  72. package/dist/src/app-opentui/react/launch.d.ts +2 -0
  73. package/dist/src/app-opentui/react/launch.js +5743 -0
  74. package/dist/src/app-opentui/react/nav.d.ts +18 -0
  75. package/dist/src/app-opentui/react/nav.js +54 -0
  76. package/dist/src/app-opentui/react/scroll.d.ts +12 -0
  77. package/dist/src/app-opentui/react/scroll.js +21 -0
  78. package/dist/src/app-opentui/react/syntax.d.ts +2 -0
  79. package/dist/src/app-opentui/react/syntax.js +64 -0
  80. package/dist/src/app-opentui/registry.js +20428 -4828
  81. package/dist/src/app-opentui/render/ascii-fleet.d.ts +15 -0
  82. package/dist/src/app-opentui/render/ascii-fleet.js +82 -0
  83. package/dist/src/app-opentui/render/constants.d.ts +31 -0
  84. package/dist/src/app-opentui/render/constants.js +66 -0
  85. package/dist/src/app-opentui/render/graphics.d.ts +2 -1
  86. package/dist/src/app-opentui/render/graphics.js +228 -46
  87. package/dist/src/app-opentui/render/image-visual-layer-worker.js +469 -930
  88. package/dist/src/app-opentui/render/image-visual-layer.d.ts +25 -10
  89. package/dist/src/app-opentui/render/image-visual-layer.js +448 -329
  90. package/dist/src/app-opentui/render/native-host.d.ts +37 -0
  91. package/dist/src/app-opentui/render/native-host.js +179 -0
  92. package/dist/src/app-opentui/render/panel-layout.d.ts +38 -0
  93. package/dist/src/app-opentui/render/panel-layout.js +48 -0
  94. package/dist/src/app-opentui/render/panels.d.ts +2 -0
  95. package/dist/src/app-opentui/render/panels.js +78 -38
  96. package/dist/src/app-opentui/render/preloader.d.ts +10 -0
  97. package/dist/src/app-opentui/render/preloader.js +165 -0
  98. package/dist/src/app-opentui/render/scene.d.ts +53 -1
  99. package/dist/src/app-opentui/render/scene.js +195 -6
  100. package/dist/src/app-opentui/render/text.d.ts +7 -1
  101. package/dist/src/app-opentui/render/text.js +15 -8
  102. package/dist/src/app-opentui/render/type-bar.d.ts +2 -1
  103. package/dist/src/app-opentui/render/type-bar.js +113 -39
  104. package/dist/src/app-opentui/runtime-resources.d.ts +16 -0
  105. package/dist/src/app-opentui/runtime-resources.js +62 -0
  106. package/dist/src/app-opentui/runtime.d.ts +44 -1
  107. package/dist/src/app-opentui/runtime.js +5415 -2738
  108. package/dist/src/app-opentui/scenes/command.d.ts +3 -0
  109. package/dist/src/app-opentui/scenes/command.js +117 -0
  110. package/dist/src/app-opentui/scenes/doctor.d.ts +2 -1
  111. package/dist/src/app-opentui/scenes/doctor.js +221 -17
  112. package/dist/src/app-opentui/scenes/error.js +60 -20
  113. package/dist/src/app-opentui/scenes/family-domains/agent.d.ts +2 -0
  114. package/dist/src/app-opentui/scenes/family-domains/agent.js +348 -0
  115. package/dist/src/app-opentui/scenes/family-domains/browser.d.ts +2 -0
  116. package/dist/src/app-opentui/scenes/family-domains/browser.js +195 -0
  117. package/dist/src/app-opentui/scenes/family-domains/dist.d.ts +2 -0
  118. package/dist/src/app-opentui/scenes/family-domains/dist.js +243 -0
  119. package/dist/src/app-opentui/scenes/family-domains/git.d.ts +2 -0
  120. package/dist/src/app-opentui/scenes/family-domains/git.js +195 -0
  121. package/dist/src/app-opentui/scenes/family-domains/github.d.ts +2 -0
  122. package/dist/src/app-opentui/scenes/family-domains/github.js +274 -0
  123. package/dist/src/app-opentui/scenes/family-domains/harness.d.ts +2 -0
  124. package/dist/src/app-opentui/scenes/family-domains/harness.js +152 -0
  125. package/dist/src/app-opentui/scenes/family-domains/index.d.ts +4 -0
  126. package/dist/src/app-opentui/scenes/family-domains/index.js +1679 -0
  127. package/dist/src/app-opentui/scenes/family-domains/kit.d.ts +76 -0
  128. package/dist/src/app-opentui/scenes/family-domains/kit.js +305 -0
  129. package/dist/src/app-opentui/scenes/family-domains/profile.d.ts +2 -0
  130. package/dist/src/app-opentui/scenes/family-domains/profile.js +212 -0
  131. package/dist/src/app-opentui/scenes/family-domains/queue.d.ts +2 -0
  132. package/dist/src/app-opentui/scenes/family-domains/queue.js +146 -0
  133. package/dist/src/app-opentui/scenes/family-domains/remote.d.ts +2 -0
  134. package/dist/src/app-opentui/scenes/family-domains/remote.js +518 -0
  135. package/dist/src/app-opentui/scenes/family-domains/review.d.ts +2 -0
  136. package/dist/src/app-opentui/scenes/family-domains/review.js +280 -0
  137. package/dist/src/app-opentui/scenes/family-domains/setup.d.ts +2 -0
  138. package/dist/src/app-opentui/scenes/family-domains/setup.js +267 -0
  139. package/dist/src/app-opentui/scenes/family-domains/stats.d.ts +2 -0
  140. package/dist/src/app-opentui/scenes/family-domains/stats.js +370 -0
  141. package/dist/src/app-opentui/scenes/family.d.ts +3 -0
  142. package/dist/src/app-opentui/scenes/family.js +2144 -0
  143. package/dist/src/app-opentui/scenes/fleet.js +552 -43
  144. package/dist/src/app-opentui/scenes/handoff.js +342 -35
  145. package/dist/src/app-opentui/scenes/help.js +640 -56
  146. package/dist/src/app-opentui/scenes/inbox.d.ts +2 -1
  147. package/dist/src/app-opentui/scenes/inbox.js +329 -21
  148. package/dist/src/app-opentui/scenes/init.d.ts +2 -1
  149. package/dist/src/app-opentui/scenes/init.js +120 -34
  150. package/dist/src/app-opentui/scenes/inspect.d.ts +3 -0
  151. package/dist/src/app-opentui/scenes/inspect.js +793 -0
  152. package/dist/src/app-opentui/scenes/main.d.ts +2 -1
  153. package/dist/src/app-opentui/scenes/main.js +264 -29
  154. package/dist/src/app-opentui/scenes/pi.d.ts +3 -0
  155. package/dist/src/app-opentui/scenes/pi.js +508 -0
  156. package/dist/src/app-opentui/scenes/plugin.d.ts +3 -0
  157. package/dist/src/app-opentui/scenes/plugin.js +486 -0
  158. package/dist/src/app-opentui/scenes/repo.d.ts +3 -0
  159. package/dist/src/app-opentui/scenes/repo.js +424 -0
  160. package/dist/src/app-opentui/scenes/run-detail.d.ts +2 -1
  161. package/dist/src/app-opentui/scenes/run-detail.js +362 -35
  162. package/dist/src/app-opentui/scenes/server.d.ts +2 -1
  163. package/dist/src/app-opentui/scenes/server.js +243 -26
  164. package/dist/src/app-opentui/scenes/tasks.js +518 -41
  165. package/dist/src/app-opentui/scenes/workspace.d.ts +3 -0
  166. package/dist/src/app-opentui/scenes/workspace.js +426 -0
  167. package/dist/src/app-opentui/selectable.d.ts +19 -0
  168. package/dist/src/app-opentui/selectable.js +79 -0
  169. package/dist/src/app-opentui/state.js +129 -36
  170. package/dist/src/app-opentui/surface-catalog.d.ts +20 -0
  171. package/dist/src/app-opentui/surface-catalog.js +540 -0
  172. package/dist/src/app-opentui/terminal-capabilities.d.ts +7 -0
  173. package/dist/src/app-opentui/terminal-capabilities.js +15 -0
  174. package/dist/src/app-opentui/theme.d.ts +28 -6
  175. package/dist/src/app-opentui/theme.js +61 -8
  176. package/dist/src/app-opentui/types.d.ts +130 -5
  177. package/dist/src/commands/_authority-runs.d.ts +1 -1
  178. package/dist/src/commands/_authority-runs.js +2 -12
  179. package/dist/src/commands/_doctor-checks.js +62 -13
  180. package/dist/src/commands/_help-catalog.js +95 -15
  181. package/dist/src/commands/_operator-view.js +97 -15
  182. package/dist/src/commands/_pi-frontend.d.ts +2 -0
  183. package/dist/src/commands/_pi-frontend.js +97 -13
  184. package/dist/src/commands/_preflight.js +64 -14
  185. package/dist/src/commands/_server-client.js +82 -18
  186. package/dist/src/commands/_server-events.d.ts +26 -0
  187. package/dist/src/commands/_server-events.js +310 -0
  188. package/dist/src/commands/_snapshot-upload.js +62 -13
  189. package/dist/src/commands/agent.js +2 -12
  190. package/dist/src/commands/connect.js +7 -1
  191. package/dist/src/commands/doctor.js +62 -13
  192. package/dist/src/commands/github.js +144 -23
  193. package/dist/src/commands/inbox.js +62 -13
  194. package/dist/src/commands/init.js +82 -18
  195. package/dist/src/commands/inspect.js +62 -13
  196. package/dist/src/commands/run.js +100 -15
  197. package/dist/src/commands/server.js +71 -26
  198. package/dist/src/commands/setup.js +62 -13
  199. package/dist/src/commands/stats.js +157 -28
  200. package/dist/src/commands/task-run-driver.js +64 -25
  201. package/dist/src/commands/task.js +196 -43
  202. package/dist/src/commands.js +419 -123
  203. package/dist/src/index.js +426 -130
  204. package/package.json +11 -10
  205. package/dist/src/app-opentui/render/image-visual-layer-native-canvas.d.ts +0 -2
  206. package/dist/src/app-opentui/render/image-visual-layer-native-canvas.js +0 -1484
@@ -134,17 +134,21 @@ function cleanToken(value) {
134
134
  const trimmed = value?.trim();
135
135
  return trimmed ? trimmed : null;
136
136
  }
137
- function readPrivateRemoteSessionToken(projectRoot) {
137
+ function readRemoteAuthState(projectRoot) {
138
138
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
139
139
  if (!existsSync2(path))
140
140
  return null;
141
141
  try {
142
142
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
143
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
143
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
144
144
  } catch {
145
145
  return null;
146
146
  }
147
147
  }
148
+ function readPrivateRemoteSessionToken(projectRoot) {
149
+ const parsed = readRemoteAuthState(projectRoot);
150
+ return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
151
+ }
148
152
  function readGitHubBearerTokenForRemote(projectRoot) {
149
153
  const scopedKey = resolve2(projectRoot);
150
154
  if (scopedGitHubBearerTokens.has(scopedKey))
@@ -155,15 +159,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
155
159
  return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
156
160
  }
157
161
  function readStoredGitHubAuthToken(projectRoot) {
158
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
159
- if (!existsSync2(path))
162
+ const parsed = readRemoteAuthState(projectRoot);
163
+ return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
164
+ }
165
+ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
166
+ const repo = readRepoConnection(projectRoot);
167
+ const slug = repo?.project?.trim();
168
+ if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
160
169
  return null;
161
- try {
162
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
163
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
164
- } catch {
170
+ const auth = readRemoteAuthState(projectRoot);
171
+ const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
172
+ const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
173
+ if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
165
174
  return null;
166
- }
175
+ const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
176
+ if (!checkoutBaseDir)
177
+ return null;
178
+ const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
179
+ writeRepoServerProjectRoot(projectRoot, inferred);
180
+ return inferred;
167
181
  }
168
182
  function readLocalConnectionFallbackToken(projectRoot) {
169
183
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
@@ -174,7 +188,7 @@ async function ensureServerForCli(projectRoot) {
174
188
  if (selected?.connection.kind === "remote") {
175
189
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
176
190
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
177
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
191
+ const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
178
192
  return {
179
193
  baseUrl: selected.connection.baseUrl,
180
194
  authToken,
@@ -203,7 +217,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
203
217
  if (!slug)
204
218
  return null;
205
219
  try {
206
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
220
+ const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
221
+ if (authToken && queryAuthFallbackEnabled())
222
+ url.searchParams.set("rt", authToken);
223
+ const response = await fetch(url, {
207
224
  headers: mergeHeaders(undefined, authToken)
208
225
  });
209
226
  if (!response.ok)
@@ -220,10 +237,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
220
237
  return null;
221
238
  }
222
239
  }
240
+ function mergeCookie(existing, name, value) {
241
+ const encoded = `${name}=${encodeURIComponent(value)}`;
242
+ if (!existing?.trim())
243
+ return encoded;
244
+ const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
245
+ return [...parts, encoded].join("; ");
246
+ }
247
+ function queryAuthFallbackEnabled(env = process.env) {
248
+ return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
249
+ }
223
250
  function mergeHeaders(headers, authToken) {
224
251
  const merged = new Headers(headers);
225
252
  if (authToken) {
226
- merged.set("authorization", `Bearer ${authToken}`);
253
+ const bearer = `Bearer ${authToken}`;
254
+ merged.set("authorization", bearer);
255
+ merged.set("x-auth", bearer);
256
+ merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
227
257
  }
228
258
  return merged;
229
259
  }
@@ -284,15 +314,34 @@ async function buildServerFailureContext(projectRoot, server) {
284
314
  hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
285
315
  };
286
316
  }
317
+ function isLoopbackRemoteBaseUrl(baseUrl) {
318
+ try {
319
+ const host = new URL(baseUrl).hostname.toLowerCase();
320
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
321
+ } catch {
322
+ return false;
323
+ }
324
+ }
325
+ function canUseRemoteWithoutProjectRoot(pathname) {
326
+ 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/");
327
+ }
287
328
  async function requestServerJson(context, pathname, init = {}) {
288
329
  const server = await ensureServerForCli(context.projectRoot);
330
+ const requestUrl = new URL(`${server.baseUrl}${pathname}`);
331
+ if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
332
+ const repo = readRepoConnection(context.projectRoot);
333
+ 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." });
334
+ }
289
335
  const headers = mergeHeaders(init.headers, server.authToken);
290
336
  if (server.serverProjectRoot)
291
337
  headers.set("x-rig-project-root", server.serverProjectRoot);
338
+ if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
339
+ requestUrl.searchParams.set("rt", server.authToken);
340
+ }
292
341
  reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
293
342
  let response;
294
343
  try {
295
- response = await fetch(`${server.baseUrl}${pathname}`, {
344
+ response = await fetch(requestUrl, {
296
345
  ...init,
297
346
  headers
298
347
  });
@@ -770,6 +819,14 @@ async function withSpinner(label, work, options = {}) {
770
819
  }
771
820
 
772
821
  // packages/cli/src/commands/_pi-frontend.ts
822
+ var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
823
+
824
+ class OperatorTranscriptUnavailableError extends Error {
825
+ constructor(message) {
826
+ super(message);
827
+ this.name = "OperatorTranscriptUnavailableError";
828
+ }
829
+ }
773
830
  function setTemporaryEnv(updates) {
774
831
  const previous = new Map;
775
832
  for (const [key, value] of Object.entries(updates)) {
@@ -797,6 +854,23 @@ function buildOperatorPiEnv(input) {
797
854
  ...input.serverProjectRoot ? { RIG_PROJECT_ROOT: input.serverProjectRoot } : {}
798
855
  };
799
856
  }
857
+ function shouldRequireOperatorTranscript(status) {
858
+ return typeof status === "string" && TERMINAL_RUN_STATUSES.has(status.trim().toLowerCase());
859
+ }
860
+ function missingOperatorTranscriptMessage(runId, status) {
861
+ const shortRun = runId.trim().slice(0, 8) || "unknown";
862
+ const statusText = typeof status === "string" && status.trim() ? status.trim() : "terminal";
863
+ return `Run ${shortRun} is ${statusText}, but no worker Pi session transcript is recorded for this run. It likely failed before Pi started or predates full-session capture. Use \`rig run show ${runId}\` and \`rig run logs ${runId}\` for the lifecycle details.`;
864
+ }
865
+ function statusFromRunDetails(run) {
866
+ if (!run || typeof run !== "object" || Array.isArray(run))
867
+ return;
868
+ const record = run;
869
+ if (typeof record.status === "string")
870
+ return record.status;
871
+ const nested = record.run;
872
+ return nested && typeof nested === "object" && !Array.isArray(nested) ? nested.status : undefined;
873
+ }
800
874
  async function prepareOperatorConsole(context, runId, tempSessionDir) {
801
875
  const server = await ensureServerForCli(context.projectRoot);
802
876
  const localCwd = join(homedir2(), ".rig", "drones", runId.slice(0, 8));
@@ -826,6 +900,13 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
826
900
  sessionFileArg = ["--session", localSessionPath];
827
901
  }
828
902
  } catch {}
903
+ if (sessionFileArg.length === 0) {
904
+ const run = await getRunDetailsViaServer(context, runId).catch(() => null);
905
+ const status = statusFromRunDetails(run);
906
+ if (shouldRequireOperatorTranscript(status)) {
907
+ throw new OperatorTranscriptUnavailableError(missingOperatorTranscriptMessage(runId, status));
908
+ }
909
+ }
829
910
  return { server, sessionFileArg };
830
911
  }
831
912
  var RIG_PI_THEME = {
@@ -943,6 +1024,7 @@ async function attachRunBundledPiFrontend(context, input) {
943
1024
  let detached = false;
944
1025
  try {
945
1026
  await runPiMain([
1027
+ "--offline",
946
1028
  "--no-extensions",
947
1029
  "--no-skills",
948
1030
  "--no-prompt-templates",
@@ -972,7 +1054,7 @@ async function attachRunBundledPiFrontend(context, input) {
972
1054
  }
973
1055
 
974
1056
  // packages/cli/src/commands/_operator-view.ts
975
- var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
1057
+ var TERMINAL_RUN_STATUSES2 = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
976
1058
  function runStatusFromPayload(payload) {
977
1059
  const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
978
1060
  return String(run.status ?? "unknown").toLowerCase();
@@ -1050,7 +1132,7 @@ async function attachRunOperatorView(context, input) {
1050
1132
  }
1051
1133
  const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
1052
1134
  let timelineCursor = snapshot.timelineCursor;
1053
- while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
1135
+ while (!detached && !TERMINAL_RUN_STATUSES2.has(runStatusFromPayload(snapshot.run))) {
1054
1136
  await Bun.sleep(pollMs);
1055
1137
  snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
1056
1138
  timelineCursor = snapshot.timelineCursor;
@@ -20,6 +20,8 @@ export declare function buildOperatorPiEnv(input: {
20
20
  readonly serverProjectRoot?: string | null;
21
21
  readonly sessionDir: string;
22
22
  }): Record<string, string>;
23
+ export declare function shouldRequireOperatorTranscript(status: unknown): boolean;
24
+ export declare function missingOperatorTranscriptMessage(runId: string, status: unknown): string;
23
25
  export declare function attachRunBundledPiFrontend(context: Pick<RunnerContext, "projectRoot" | "outputMode">, input: {
24
26
  readonly runId: string;
25
27
  readonly steered?: boolean;
@@ -141,17 +141,21 @@ function cleanToken(value) {
141
141
  const trimmed = value?.trim();
142
142
  return trimmed ? trimmed : null;
143
143
  }
144
- function readPrivateRemoteSessionToken(projectRoot) {
144
+ function readRemoteAuthState(projectRoot) {
145
145
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
146
146
  if (!existsSync2(path))
147
147
  return null;
148
148
  try {
149
149
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
150
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
150
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
151
151
  } catch {
152
152
  return null;
153
153
  }
154
154
  }
155
+ function readPrivateRemoteSessionToken(projectRoot) {
156
+ const parsed = readRemoteAuthState(projectRoot);
157
+ return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
158
+ }
155
159
  function readGitHubBearerTokenForRemote(projectRoot) {
156
160
  const scopedKey = resolve2(projectRoot);
157
161
  if (scopedGitHubBearerTokens.has(scopedKey))
@@ -162,15 +166,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
162
166
  return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
163
167
  }
164
168
  function readStoredGitHubAuthToken(projectRoot) {
165
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
166
- if (!existsSync2(path))
169
+ const parsed = readRemoteAuthState(projectRoot);
170
+ return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
171
+ }
172
+ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
173
+ const repo = readRepoConnection(projectRoot);
174
+ const slug = repo?.project?.trim();
175
+ if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
167
176
  return null;
168
- try {
169
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
170
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
171
- } catch {
177
+ const auth = readRemoteAuthState(projectRoot);
178
+ const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
179
+ const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
180
+ if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
172
181
  return null;
173
- }
182
+ const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
183
+ if (!checkoutBaseDir)
184
+ return null;
185
+ const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
186
+ writeRepoServerProjectRoot(projectRoot, inferred);
187
+ return inferred;
174
188
  }
175
189
  function readLocalConnectionFallbackToken(projectRoot) {
176
190
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
@@ -181,7 +195,7 @@ async function ensureServerForCli(projectRoot) {
181
195
  if (selected?.connection.kind === "remote") {
182
196
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
183
197
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
184
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
198
+ const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
185
199
  return {
186
200
  baseUrl: selected.connection.baseUrl,
187
201
  authToken,
@@ -210,7 +224,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
210
224
  if (!slug)
211
225
  return null;
212
226
  try {
213
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
227
+ const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
228
+ if (authToken && queryAuthFallbackEnabled())
229
+ url.searchParams.set("rt", authToken);
230
+ const response = await fetch(url, {
214
231
  headers: mergeHeaders(undefined, authToken)
215
232
  });
216
233
  if (!response.ok)
@@ -227,10 +244,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
227
244
  return null;
228
245
  }
229
246
  }
247
+ function mergeCookie(existing, name, value) {
248
+ const encoded = `${name}=${encodeURIComponent(value)}`;
249
+ if (!existing?.trim())
250
+ return encoded;
251
+ const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
252
+ return [...parts, encoded].join("; ");
253
+ }
254
+ function queryAuthFallbackEnabled(env = process.env) {
255
+ return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
256
+ }
230
257
  function mergeHeaders(headers, authToken) {
231
258
  const merged = new Headers(headers);
232
259
  if (authToken) {
233
- merged.set("authorization", `Bearer ${authToken}`);
260
+ const bearer = `Bearer ${authToken}`;
261
+ merged.set("authorization", bearer);
262
+ merged.set("x-auth", bearer);
263
+ merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
234
264
  }
235
265
  return merged;
236
266
  }
@@ -291,15 +321,34 @@ async function buildServerFailureContext(projectRoot, server) {
291
321
  hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
292
322
  };
293
323
  }
324
+ function isLoopbackRemoteBaseUrl(baseUrl) {
325
+ try {
326
+ const host = new URL(baseUrl).hostname.toLowerCase();
327
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
328
+ } catch {
329
+ return false;
330
+ }
331
+ }
332
+ function canUseRemoteWithoutProjectRoot(pathname) {
333
+ 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/");
334
+ }
294
335
  async function requestServerJson(context, pathname, init = {}) {
295
336
  const server = await ensureServerForCli(context.projectRoot);
337
+ const requestUrl = new URL(`${server.baseUrl}${pathname}`);
338
+ if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
339
+ const repo = readRepoConnection(context.projectRoot);
340
+ 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." });
341
+ }
296
342
  const headers = mergeHeaders(init.headers, server.authToken);
297
343
  if (server.serverProjectRoot)
298
344
  headers.set("x-rig-project-root", server.serverProjectRoot);
345
+ if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
346
+ requestUrl.searchParams.set("rt", server.authToken);
347
+ }
299
348
  reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
300
349
  let response;
301
350
  try {
302
- response = await fetch(`${server.baseUrl}${pathname}`, {
351
+ response = await fetch(requestUrl, {
303
352
  ...init,
304
353
  headers
305
354
  });
@@ -536,6 +585,14 @@ async function withSpinner(label, work, options = {}) {
536
585
  }
537
586
 
538
587
  // packages/cli/src/commands/_pi-frontend.ts
588
+ var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
589
+
590
+ class OperatorTranscriptUnavailableError extends Error {
591
+ constructor(message) {
592
+ super(message);
593
+ this.name = "OperatorTranscriptUnavailableError";
594
+ }
595
+ }
539
596
  function setTemporaryEnv(updates) {
540
597
  const previous = new Map;
541
598
  for (const [key, value] of Object.entries(updates)) {
@@ -563,6 +620,23 @@ function buildOperatorPiEnv(input) {
563
620
  ...input.serverProjectRoot ? { RIG_PROJECT_ROOT: input.serverProjectRoot } : {}
564
621
  };
565
622
  }
623
+ function shouldRequireOperatorTranscript(status) {
624
+ return typeof status === "string" && TERMINAL_RUN_STATUSES.has(status.trim().toLowerCase());
625
+ }
626
+ function missingOperatorTranscriptMessage(runId, status) {
627
+ const shortRun = runId.trim().slice(0, 8) || "unknown";
628
+ const statusText = typeof status === "string" && status.trim() ? status.trim() : "terminal";
629
+ return `Run ${shortRun} is ${statusText}, but no worker Pi session transcript is recorded for this run. It likely failed before Pi started or predates full-session capture. Use \`rig run show ${runId}\` and \`rig run logs ${runId}\` for the lifecycle details.`;
630
+ }
631
+ function statusFromRunDetails(run) {
632
+ if (!run || typeof run !== "object" || Array.isArray(run))
633
+ return;
634
+ const record = run;
635
+ if (typeof record.status === "string")
636
+ return record.status;
637
+ const nested = record.run;
638
+ return nested && typeof nested === "object" && !Array.isArray(nested) ? nested.status : undefined;
639
+ }
566
640
  async function prepareOperatorConsole(context, runId, tempSessionDir) {
567
641
  const server = await ensureServerForCli(context.projectRoot);
568
642
  const localCwd = join(homedir2(), ".rig", "drones", runId.slice(0, 8));
@@ -592,6 +666,13 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
592
666
  sessionFileArg = ["--session", localSessionPath];
593
667
  }
594
668
  } catch {}
669
+ if (sessionFileArg.length === 0) {
670
+ const run = await getRunDetailsViaServer(context, runId).catch(() => null);
671
+ const status = statusFromRunDetails(run);
672
+ if (shouldRequireOperatorTranscript(status)) {
673
+ throw new OperatorTranscriptUnavailableError(missingOperatorTranscriptMessage(runId, status));
674
+ }
675
+ }
595
676
  return { server, sessionFileArg };
596
677
  }
597
678
  var RIG_PI_THEME = {
@@ -709,6 +790,7 @@ async function attachRunBundledPiFrontend(context, input) {
709
790
  let detached = false;
710
791
  try {
711
792
  await runPiMain([
793
+ "--offline",
712
794
  "--no-extensions",
713
795
  "--no-skills",
714
796
  "--no-prompt-templates",
@@ -737,6 +819,8 @@ async function attachRunBundledPiFrontend(context, input) {
737
819
  };
738
820
  }
739
821
  export {
822
+ shouldRequireOperatorTranscript,
823
+ missingOperatorTranscriptMessage,
740
824
  buildOperatorPiEnv,
741
825
  attachRunBundledPiFrontend
742
826
  };
@@ -128,17 +128,21 @@ function cleanToken(value) {
128
128
  const trimmed = value?.trim();
129
129
  return trimmed ? trimmed : null;
130
130
  }
131
- function readPrivateRemoteSessionToken(projectRoot) {
131
+ function readRemoteAuthState(projectRoot) {
132
132
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
133
133
  if (!existsSync2(path))
134
134
  return null;
135
135
  try {
136
136
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
137
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
137
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
138
138
  } catch {
139
139
  return null;
140
140
  }
141
141
  }
142
+ function readPrivateRemoteSessionToken(projectRoot) {
143
+ const parsed = readRemoteAuthState(projectRoot);
144
+ return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
145
+ }
142
146
  function readGitHubBearerTokenForRemote(projectRoot) {
143
147
  const scopedKey = resolve2(projectRoot);
144
148
  if (scopedGitHubBearerTokens.has(scopedKey))
@@ -149,15 +153,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
149
153
  return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
150
154
  }
151
155
  function readStoredGitHubAuthToken(projectRoot) {
152
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
153
- if (!existsSync2(path))
156
+ const parsed = readRemoteAuthState(projectRoot);
157
+ return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
158
+ }
159
+ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
160
+ const repo = readRepoConnection(projectRoot);
161
+ const slug = repo?.project?.trim();
162
+ if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
154
163
  return null;
155
- try {
156
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
157
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
158
- } catch {
164
+ const auth = readRemoteAuthState(projectRoot);
165
+ const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
166
+ const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
167
+ if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
159
168
  return null;
160
- }
169
+ const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
170
+ if (!checkoutBaseDir)
171
+ return null;
172
+ const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
173
+ writeRepoServerProjectRoot(projectRoot, inferred);
174
+ return inferred;
161
175
  }
162
176
  function readLocalConnectionFallbackToken(projectRoot) {
163
177
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
@@ -168,7 +182,7 @@ async function ensureServerForCli(projectRoot) {
168
182
  if (selected?.connection.kind === "remote") {
169
183
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
170
184
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
171
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
185
+ const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
172
186
  return {
173
187
  baseUrl: selected.connection.baseUrl,
174
188
  authToken,
@@ -197,7 +211,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
197
211
  if (!slug)
198
212
  return null;
199
213
  try {
200
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
214
+ const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
215
+ if (authToken && queryAuthFallbackEnabled())
216
+ url.searchParams.set("rt", authToken);
217
+ const response = await fetch(url, {
201
218
  headers: mergeHeaders(undefined, authToken)
202
219
  });
203
220
  if (!response.ok)
@@ -214,10 +231,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
214
231
  return null;
215
232
  }
216
233
  }
234
+ function mergeCookie(existing, name, value) {
235
+ const encoded = `${name}=${encodeURIComponent(value)}`;
236
+ if (!existing?.trim())
237
+ return encoded;
238
+ const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
239
+ return [...parts, encoded].join("; ");
240
+ }
241
+ function queryAuthFallbackEnabled(env = process.env) {
242
+ return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
243
+ }
217
244
  function mergeHeaders(headers, authToken) {
218
245
  const merged = new Headers(headers);
219
246
  if (authToken) {
220
- merged.set("authorization", `Bearer ${authToken}`);
247
+ const bearer = `Bearer ${authToken}`;
248
+ merged.set("authorization", bearer);
249
+ merged.set("x-auth", bearer);
250
+ merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
221
251
  }
222
252
  return merged;
223
253
  }
@@ -278,15 +308,34 @@ async function buildServerFailureContext(projectRoot, server) {
278
308
  hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
279
309
  };
280
310
  }
311
+ function isLoopbackRemoteBaseUrl(baseUrl) {
312
+ try {
313
+ const host = new URL(baseUrl).hostname.toLowerCase();
314
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
315
+ } catch {
316
+ return false;
317
+ }
318
+ }
319
+ function canUseRemoteWithoutProjectRoot(pathname) {
320
+ 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/");
321
+ }
281
322
  async function requestServerJson(context, pathname, init = {}) {
282
323
  const server = await ensureServerForCli(context.projectRoot);
324
+ const requestUrl = new URL(`${server.baseUrl}${pathname}`);
325
+ if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
326
+ const repo = readRepoConnection(context.projectRoot);
327
+ 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." });
328
+ }
283
329
  const headers = mergeHeaders(init.headers, server.authToken);
284
330
  if (server.serverProjectRoot)
285
331
  headers.set("x-rig-project-root", server.serverProjectRoot);
332
+ if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
333
+ requestUrl.searchParams.set("rt", server.authToken);
334
+ }
286
335
  reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
287
336
  let response;
288
337
  try {
289
- response = await fetch(`${server.baseUrl}${pathname}`, {
338
+ response = await fetch(requestUrl, {
290
339
  ...init,
291
340
  headers
292
341
  });
@@ -437,7 +486,8 @@ async function runFastTaskRunPreflight(context, options = {}) {
437
486
  }
438
487
  }
439
488
  const repo = readRepoConnection(context.projectRoot);
440
- checks.push(repo ? preflightCheck("project-link", "project linked to Rig server", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to Rig server", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
489
+ const remoteWithoutProjectLink = selectedServer?.connectionKind === "remote" && !repo?.project;
490
+ checks.push(repo ? preflightCheck("project-link", "project linked to selected server", repo.project ? "pass" : remoteWithoutProjectLink ? "fail" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : " (missing project link)"}`, remoteWithoutProjectLink ? "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to restore the remote project link." : "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to selected server", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
441
491
  try {
442
492
  const auth = await request("/api/github/auth/status");
443
493
  checks.push(isAuthenticated(auth) ? preflightCheck("github-auth", "GitHub auth valid", "pass") : preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));