@h-rig/cli 0.0.6-alpha.6 → 0.0.6-alpha.60

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 (50) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/rig.js +4184 -1228
  3. package/dist/src/commands/_cli-format.js +369 -0
  4. package/dist/src/commands/_connection-state.js +12 -6
  5. package/dist/src/commands/_doctor-checks.js +79 -34
  6. package/dist/src/commands/_help-catalog.js +445 -0
  7. package/dist/src/commands/_operator-surface.js +220 -0
  8. package/dist/src/commands/_operator-view.js +1124 -64
  9. package/dist/src/commands/_parsers.js +0 -2
  10. package/dist/src/commands/_pi-frontend.js +1080 -0
  11. package/dist/src/commands/_pi-install.js +4 -3
  12. package/dist/src/commands/_pi-remote-session.js +771 -0
  13. package/dist/src/commands/_pi-worker-bridge-extension.js +834 -0
  14. package/dist/src/commands/_policy.js +0 -2
  15. package/dist/src/commands/_preflight.js +98 -116
  16. package/dist/src/commands/_run-driver-helpers.js +34 -2
  17. package/dist/src/commands/_server-client.js +225 -48
  18. package/dist/src/commands/_snapshot-upload.js +74 -30
  19. package/dist/src/commands/_spinner.js +63 -0
  20. package/dist/src/commands/_task-picker.js +44 -16
  21. package/dist/src/commands/agent.js +8 -9
  22. package/dist/src/commands/browser.js +4 -6
  23. package/dist/src/commands/connect.js +134 -26
  24. package/dist/src/commands/dist.js +4 -6
  25. package/dist/src/commands/doctor.js +79 -34
  26. package/dist/src/commands/github.js +76 -32
  27. package/dist/src/commands/inbox.js +410 -31
  28. package/dist/src/commands/init.js +398 -90
  29. package/dist/src/commands/inspect.js +296 -23
  30. package/dist/src/commands/inspector.js +2 -4
  31. package/dist/src/commands/pi.js +168 -0
  32. package/dist/src/commands/plugin.js +81 -22
  33. package/dist/src/commands/profile-and-review.js +8 -10
  34. package/dist/src/commands/queue.js +2 -3
  35. package/dist/src/commands/remote.js +18 -20
  36. package/dist/src/commands/repo-git-harness.js +6 -8
  37. package/dist/src/commands/run.js +1422 -131
  38. package/dist/src/commands/server.js +280 -40
  39. package/dist/src/commands/setup.js +84 -45
  40. package/dist/src/commands/task-report-bug.js +5 -7
  41. package/dist/src/commands/task-run-driver.js +710 -70
  42. package/dist/src/commands/task.js +1861 -260
  43. package/dist/src/commands/test.js +3 -5
  44. package/dist/src/commands/workspace.js +4 -6
  45. package/dist/src/commands.js +4143 -1181
  46. package/dist/src/index.js +4156 -1203
  47. package/dist/src/launcher.js +5 -3
  48. package/dist/src/report-bug.js +3 -3
  49. package/dist/src/runner.js +5 -19
  50. package/package.json +7 -5
@@ -5,8 +5,6 @@ var __require = import.meta.require;
5
5
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
6
6
  import { CliError } from "@rig/runtime/control-plane/errors";
7
7
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
8
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
9
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
10
8
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
11
9
  import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
12
10
  function requireNoExtraArgs(args, usage) {
@@ -46,6 +44,11 @@ function readJsonFile(path) {
46
44
  throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
47
45
  }
48
46
  }
47
+ function writeJsonFile(path, value) {
48
+ mkdirSync(dirname(path), { recursive: true });
49
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
50
+ `, "utf8");
51
+ }
49
52
  function normalizeConnection(value) {
50
53
  if (!value || typeof value !== "object" || Array.isArray(value))
51
54
  return null;
@@ -86,29 +89,38 @@ function readRepoConnection(projectRoot) {
86
89
  return {
87
90
  selected,
88
91
  project: typeof record.project === "string" ? record.project : undefined,
89
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
92
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
93
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
90
94
  };
91
95
  }
96
+ function writeRepoConnection(projectRoot, state) {
97
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
98
+ }
92
99
  function resolveSelectedConnection(projectRoot, options = {}) {
93
100
  const repo = readRepoConnection(projectRoot);
94
101
  if (!repo)
95
102
  return null;
96
103
  if (repo.selected === "local")
97
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
104
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
98
105
  const global = readGlobalConnections(options);
99
106
  const connection = global.connections[repo.selected];
100
107
  if (!connection) {
101
- throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
108
+ throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
102
109
  }
103
- return { alias: repo.selected, connection };
110
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
111
+ }
112
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
113
+ const repo = readRepoConnection(projectRoot);
114
+ if (!repo)
115
+ return;
116
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
104
117
  }
105
118
 
106
119
  // packages/cli/src/commands/_server-client.ts
107
- import { spawnSync } from "child_process";
108
120
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
109
121
  import { resolve as resolve2 } from "path";
110
122
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
111
- var cachedGitHubBearerToken;
123
+ var scopedGitHubBearerTokens = new Map;
112
124
  function cleanToken(value) {
113
125
  const trimmed = value?.trim();
114
126
  return trimmed ? trimmed : null;
@@ -125,41 +137,47 @@ function readPrivateRemoteSessionToken(projectRoot) {
125
137
  }
126
138
  }
127
139
  function readGitHubBearerTokenForRemote(projectRoot) {
128
- if (cachedGitHubBearerToken !== undefined)
129
- return cachedGitHubBearerToken;
140
+ const scopedKey = resolve2(projectRoot);
141
+ if (scopedGitHubBearerTokens.has(scopedKey))
142
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
130
143
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
131
- if (privateSession) {
132
- cachedGitHubBearerToken = privateSession;
133
- return cachedGitHubBearerToken;
134
- }
135
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
136
- if (envToken) {
137
- cachedGitHubBearerToken = envToken;
138
- return cachedGitHubBearerToken;
144
+ if (privateSession)
145
+ return privateSession;
146
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
147
+ }
148
+ function readStoredGitHubAuthToken(projectRoot) {
149
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
150
+ if (!existsSync2(path))
151
+ return null;
152
+ try {
153
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
154
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
155
+ } catch {
156
+ return null;
139
157
  }
140
- const result = spawnSync("gh", ["auth", "token"], {
141
- encoding: "utf8",
142
- timeout: 5000,
143
- stdio: ["ignore", "pipe", "ignore"]
144
- });
145
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
146
- return cachedGitHubBearerToken;
158
+ }
159
+ function readLocalConnectionFallbackToken(projectRoot) {
160
+ return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
147
161
  }
148
162
  async function ensureServerForCli(projectRoot) {
149
163
  try {
150
164
  const selected = resolveSelectedConnection(projectRoot);
151
165
  if (selected?.connection.kind === "remote") {
166
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
167
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
152
168
  return {
153
169
  baseUrl: selected.connection.baseUrl,
154
- authToken: readGitHubBearerTokenForRemote(projectRoot),
155
- connectionKind: "remote"
170
+ authToken,
171
+ connectionKind: "remote",
172
+ serverProjectRoot
156
173
  };
157
174
  }
158
175
  const connection = await ensureLocalRigServerConnection(projectRoot);
159
176
  return {
160
177
  baseUrl: connection.baseUrl,
161
- authToken: connection.authToken,
162
- connectionKind: "local"
178
+ authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
179
+ connectionKind: "local",
180
+ serverProjectRoot: resolve2(projectRoot)
163
181
  };
164
182
  } catch (error) {
165
183
  if (error instanceof Error) {
@@ -168,6 +186,29 @@ async function ensureServerForCli(projectRoot) {
168
186
  throw error;
169
187
  }
170
188
  }
189
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
190
+ const repo = readRepoConnection(projectRoot);
191
+ const slug = repo?.project?.trim();
192
+ if (!slug)
193
+ return null;
194
+ try {
195
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
196
+ headers: mergeHeaders(undefined, authToken)
197
+ });
198
+ if (!response.ok)
199
+ return null;
200
+ const payload = await response.json();
201
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
202
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
203
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
204
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
205
+ if (path)
206
+ writeRepoServerProjectRoot(projectRoot, path);
207
+ return path;
208
+ } catch {
209
+ return null;
210
+ }
211
+ }
171
212
  function mergeHeaders(headers, authToken) {
172
213
  const merged = new Headers(headers);
173
214
  if (authToken) {
@@ -192,9 +233,12 @@ function diagnosticMessage(payload) {
192
233
  }
193
234
  async function requestServerJson(context, pathname, init = {}) {
194
235
  const server = await ensureServerForCli(context.projectRoot);
236
+ const headers = mergeHeaders(init.headers, server.authToken);
237
+ if (server.serverProjectRoot)
238
+ headers.set("x-rig-project-root", server.serverProjectRoot);
195
239
  const response = await fetch(`${server.baseUrl}${pathname}`, {
196
240
  ...init,
197
- headers: mergeHeaders(init.headers, server.authToken)
241
+ headers
198
242
  });
199
243
  const text = await response.text();
200
244
  const payload = text.trim().length > 0 ? (() => {
@@ -226,7 +270,8 @@ async function loadRigConfigOrNull(projectRoot) {
226
270
  import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
227
271
  import { homedir as homedir2 } from "os";
228
272
  import { resolve as resolve3 } from "path";
229
- var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
273
+ var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
274
+ var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
230
275
  async function defaultCommandRunner(command, options = {}) {
231
276
  const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
232
277
  const [stdout, stderr, exitCode] = await Promise.all([
@@ -245,7 +290,7 @@ function resolvePiHomeDir(inputHomeDir) {
245
290
  function piListContainsPiRig(output) {
246
291
  return output.split(/\r?\n/).some((line) => {
247
292
  const normalized = line.trim();
248
- return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
293
+ return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
249
294
  });
250
295
  }
251
296
  async function safeRun(runner, command, options) {
@@ -421,7 +466,7 @@ async function runRigDoctorChecks(options) {
421
466
  const taskSourceKind = config?.taskSource?.kind;
422
467
  checks.push(taskSourceKind ? check("task-source", "task source configured", "pass", taskSourceKind) : check("task-source", "task source configured", "fail", "missing taskSource", "Configure taskSource in rig.config.ts."));
423
468
  const repo = readRepoConnection(projectRoot);
424
- checks.push(repo ? check("project-link", "repo selected Rig connection", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to link this checkout to a GitHub repo slug.") : check("project-link", "repo selected Rig connection", "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig connect use <alias|local>`."));
469
+ checks.push(repo ? check("project-link", "repo selected Rig server", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to link this checkout to a GitHub repo slug.") : check("project-link", "repo selected Rig server", "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
425
470
  const selected = (() => {
426
471
  try {
427
472
  return resolveSelectedConnection(projectRoot);
@@ -429,7 +474,7 @@ async function runRigDoctorChecks(options) {
429
474
  return null;
430
475
  }
431
476
  })();
432
- checks.push(selected ? check("connection", "selected server connection", "pass", selected.connection.kind === "remote" ? selected.connection.baseUrl : "local auto") : check("connection", "selected server connection", repo ? "fail" : "warn", repo ? "selected alias is missing" : "will auto-start local server", repo ? "Run `rig connect list` and `rig connect use <alias|local>`." : undefined));
477
+ checks.push(selected ? check("connection", "selected server connection", "pass", selected.connection.kind === "remote" ? selected.connection.baseUrl : "local auto") : check("connection", "selected server", repo ? "fail" : "warn", repo ? "selected alias is missing" : "will auto-start local server", repo ? "Run `rig server list` and `rig server use <alias|local>`." : undefined));
433
478
  let server = null;
434
479
  try {
435
480
  server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
@@ -1,13 +1,11 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/github.ts
3
- import { spawnSync as spawnSync2 } from "child_process";
3
+ import { spawnSync } from "child_process";
4
4
 
5
5
  // packages/cli/src/runner.ts
6
6
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
7
7
  import { CliError } from "@rig/runtime/control-plane/errors";
8
8
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
9
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
10
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
11
9
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
12
10
  import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
13
11
  function takeOption(args, option) {
@@ -32,7 +30,6 @@ function takeOption(args, option) {
32
30
  }
33
31
 
34
32
  // packages/cli/src/commands/_server-client.ts
35
- import { spawnSync } from "child_process";
36
33
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
37
34
  import { resolve as resolve2 } from "path";
38
35
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
@@ -62,6 +59,11 @@ function readJsonFile(path) {
62
59
  throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
63
60
  }
64
61
  }
62
+ function writeJsonFile(path, value) {
63
+ mkdirSync(dirname(path), { recursive: true });
64
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
65
+ `, "utf8");
66
+ }
65
67
  function normalizeConnection(value) {
66
68
  if (!value || typeof value !== "object" || Array.isArray(value))
67
69
  return null;
@@ -102,25 +104,35 @@ function readRepoConnection(projectRoot) {
102
104
  return {
103
105
  selected,
104
106
  project: typeof record.project === "string" ? record.project : undefined,
105
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
107
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
108
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
106
109
  };
107
110
  }
111
+ function writeRepoConnection(projectRoot, state) {
112
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
113
+ }
108
114
  function resolveSelectedConnection(projectRoot, options = {}) {
109
115
  const repo = readRepoConnection(projectRoot);
110
116
  if (!repo)
111
117
  return null;
112
118
  if (repo.selected === "local")
113
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
119
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
114
120
  const global = readGlobalConnections(options);
115
121
  const connection = global.connections[repo.selected];
116
122
  if (!connection) {
117
- throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
123
+ throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
118
124
  }
119
- return { alias: repo.selected, connection };
125
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
126
+ }
127
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
128
+ const repo = readRepoConnection(projectRoot);
129
+ if (!repo)
130
+ return;
131
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
120
132
  }
121
133
 
122
134
  // packages/cli/src/commands/_server-client.ts
123
- var cachedGitHubBearerToken;
135
+ var scopedGitHubBearerTokens = new Map;
124
136
  function cleanToken(value) {
125
137
  const trimmed = value?.trim();
126
138
  return trimmed ? trimmed : null;
@@ -137,41 +149,47 @@ function readPrivateRemoteSessionToken(projectRoot) {
137
149
  }
138
150
  }
139
151
  function readGitHubBearerTokenForRemote(projectRoot) {
140
- if (cachedGitHubBearerToken !== undefined)
141
- return cachedGitHubBearerToken;
152
+ const scopedKey = resolve2(projectRoot);
153
+ if (scopedGitHubBearerTokens.has(scopedKey))
154
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
142
155
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
143
- if (privateSession) {
144
- cachedGitHubBearerToken = privateSession;
145
- return cachedGitHubBearerToken;
146
- }
147
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
148
- if (envToken) {
149
- cachedGitHubBearerToken = envToken;
150
- return cachedGitHubBearerToken;
156
+ if (privateSession)
157
+ return privateSession;
158
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
159
+ }
160
+ function readStoredGitHubAuthToken(projectRoot) {
161
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
162
+ if (!existsSync2(path))
163
+ return null;
164
+ try {
165
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
166
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
167
+ } catch {
168
+ return null;
151
169
  }
152
- const result = spawnSync("gh", ["auth", "token"], {
153
- encoding: "utf8",
154
- timeout: 5000,
155
- stdio: ["ignore", "pipe", "ignore"]
156
- });
157
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
158
- return cachedGitHubBearerToken;
170
+ }
171
+ function readLocalConnectionFallbackToken(projectRoot) {
172
+ return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
159
173
  }
160
174
  async function ensureServerForCli(projectRoot) {
161
175
  try {
162
176
  const selected = resolveSelectedConnection(projectRoot);
163
177
  if (selected?.connection.kind === "remote") {
178
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
179
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
164
180
  return {
165
181
  baseUrl: selected.connection.baseUrl,
166
- authToken: readGitHubBearerTokenForRemote(projectRoot),
167
- connectionKind: "remote"
182
+ authToken,
183
+ connectionKind: "remote",
184
+ serverProjectRoot
168
185
  };
169
186
  }
170
187
  const connection = await ensureLocalRigServerConnection(projectRoot);
171
188
  return {
172
189
  baseUrl: connection.baseUrl,
173
- authToken: connection.authToken,
174
- connectionKind: "local"
190
+ authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
191
+ connectionKind: "local",
192
+ serverProjectRoot: resolve2(projectRoot)
175
193
  };
176
194
  } catch (error) {
177
195
  if (error instanceof Error) {
@@ -180,6 +198,29 @@ async function ensureServerForCli(projectRoot) {
180
198
  throw error;
181
199
  }
182
200
  }
201
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
202
+ const repo = readRepoConnection(projectRoot);
203
+ const slug = repo?.project?.trim();
204
+ if (!slug)
205
+ return null;
206
+ try {
207
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
208
+ headers: mergeHeaders(undefined, authToken)
209
+ });
210
+ if (!response.ok)
211
+ return null;
212
+ const payload = await response.json();
213
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
214
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
215
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
216
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
217
+ if (path)
218
+ writeRepoServerProjectRoot(projectRoot, path);
219
+ return path;
220
+ } catch {
221
+ return null;
222
+ }
223
+ }
183
224
  function mergeHeaders(headers, authToken) {
184
225
  const merged = new Headers(headers);
185
226
  if (authToken) {
@@ -204,9 +245,12 @@ function diagnosticMessage(payload) {
204
245
  }
205
246
  async function requestServerJson(context, pathname, init = {}) {
206
247
  const server = await ensureServerForCli(context.projectRoot);
248
+ const headers = mergeHeaders(init.headers, server.authToken);
249
+ if (server.serverProjectRoot)
250
+ headers.set("x-rig-project-root", server.serverProjectRoot);
207
251
  const response = await fetch(`${server.baseUrl}${pathname}`, {
208
252
  ...init,
209
- headers: mergeHeaders(init.headers, server.authToken)
253
+ headers
210
254
  });
211
255
  const text = await response.text();
212
256
  const payload = text.trim().length > 0 ? (() => {
@@ -244,7 +288,7 @@ function printPayload(context, payload, fallback) {
244
288
  console.log(fallback);
245
289
  }
246
290
  function readGhToken() {
247
- const result = spawnSync2("gh", ["auth", "token"], { encoding: "utf8" });
291
+ const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
248
292
  if (result.status !== 0) {
249
293
  const detail = result.stderr?.trim() || result.stdout?.trim() || "gh auth token failed";
250
294
  throw new CliError2(`Could not import GitHub token from gh: ${detail}`, 1);