@h-rig/cli 0.0.6-alpha.45 → 0.0.6-alpha.47

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.
@@ -59,6 +59,11 @@ function readJsonFile(path) {
59
59
  throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
60
60
  }
61
61
  }
62
+ function writeJsonFile(path, value) {
63
+ mkdirSync(dirname(path), { recursive: true });
64
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
65
+ `, "utf8");
66
+ }
62
67
  function normalizeConnection(value) {
63
68
  if (!value || typeof value !== "object" || Array.isArray(value))
64
69
  return null;
@@ -99,21 +104,31 @@ function readRepoConnection(projectRoot) {
99
104
  return {
100
105
  selected,
101
106
  project: typeof record.project === "string" ? record.project : undefined,
102
- 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
103
109
  };
104
110
  }
111
+ function writeRepoConnection(projectRoot, state) {
112
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
113
+ }
105
114
  function resolveSelectedConnection(projectRoot, options = {}) {
106
115
  const repo = readRepoConnection(projectRoot);
107
116
  if (!repo)
108
117
  return null;
109
118
  if (repo.selected === "local")
110
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
119
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
111
120
  const global = readGlobalConnections(options);
112
121
  const connection = global.connections[repo.selected];
113
122
  if (!connection) {
114
123
  throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
115
124
  }
116
- 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 });
117
132
  }
118
133
 
119
134
  // packages/cli/src/commands/_server-client.ts
@@ -146,17 +161,21 @@ async function ensureServerForCli(projectRoot) {
146
161
  try {
147
162
  const selected = resolveSelectedConnection(projectRoot);
148
163
  if (selected?.connection.kind === "remote") {
164
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
165
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
149
166
  return {
150
167
  baseUrl: selected.connection.baseUrl,
151
- authToken: readGitHubBearerTokenForRemote(projectRoot),
152
- connectionKind: "remote"
168
+ authToken,
169
+ connectionKind: "remote",
170
+ serverProjectRoot
153
171
  };
154
172
  }
155
173
  const connection = await ensureLocalRigServerConnection(projectRoot);
156
174
  return {
157
175
  baseUrl: connection.baseUrl,
158
176
  authToken: connection.authToken,
159
- connectionKind: "local"
177
+ connectionKind: "local",
178
+ serverProjectRoot: resolve2(projectRoot)
160
179
  };
161
180
  } catch (error) {
162
181
  if (error instanceof Error) {
@@ -165,6 +184,29 @@ async function ensureServerForCli(projectRoot) {
165
184
  throw error;
166
185
  }
167
186
  }
187
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
188
+ const repo = readRepoConnection(projectRoot);
189
+ const slug = repo?.project?.trim();
190
+ if (!slug)
191
+ return null;
192
+ try {
193
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
194
+ headers: mergeHeaders(undefined, authToken)
195
+ });
196
+ if (!response.ok)
197
+ return null;
198
+ const payload = await response.json();
199
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
200
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
201
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
202
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
203
+ if (path)
204
+ writeRepoServerProjectRoot(projectRoot, path);
205
+ return path;
206
+ } catch {
207
+ return null;
208
+ }
209
+ }
168
210
  function mergeHeaders(headers, authToken) {
169
211
  const merged = new Headers(headers);
170
212
  if (authToken) {
@@ -189,9 +231,12 @@ function diagnosticMessage(payload) {
189
231
  }
190
232
  async function requestServerJson(context, pathname, init = {}) {
191
233
  const server = await ensureServerForCli(context.projectRoot);
234
+ const headers = mergeHeaders(init.headers, server.authToken);
235
+ if (server.serverProjectRoot)
236
+ headers.set("x-rig-project-root", server.serverProjectRoot);
192
237
  const response = await fetch(`${server.baseUrl}${pathname}`, {
193
238
  ...init,
194
- headers: mergeHeaders(init.headers, server.authToken)
239
+ headers
195
240
  });
196
241
  const text = await response.text();
197
242
  const payload = text.trim().length > 0 ? (() => {
@@ -150,6 +150,11 @@ function readJsonFile(path) {
150
150
  throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
151
151
  }
152
152
  }
153
+ function writeJsonFile(path, value) {
154
+ mkdirSync(dirname(path), { recursive: true });
155
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
156
+ `, "utf8");
157
+ }
153
158
  function normalizeConnection(value) {
154
159
  if (!value || typeof value !== "object" || Array.isArray(value))
155
160
  return null;
@@ -190,21 +195,31 @@ function readRepoConnection(projectRoot) {
190
195
  return {
191
196
  selected,
192
197
  project: typeof record.project === "string" ? record.project : undefined,
193
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
198
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
199
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
194
200
  };
195
201
  }
202
+ function writeRepoConnection(projectRoot, state) {
203
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
204
+ }
196
205
  function resolveSelectedConnection(projectRoot, options = {}) {
197
206
  const repo = readRepoConnection(projectRoot);
198
207
  if (!repo)
199
208
  return null;
200
209
  if (repo.selected === "local")
201
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
210
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
202
211
  const global = readGlobalConnections(options);
203
212
  const connection = global.connections[repo.selected];
204
213
  if (!connection) {
205
214
  throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
206
215
  }
207
- return { alias: repo.selected, connection };
216
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
217
+ }
218
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
219
+ const repo = readRepoConnection(projectRoot);
220
+ if (!repo)
221
+ return;
222
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
208
223
  }
209
224
 
210
225
  // packages/cli/src/commands/_server-client.ts
@@ -240,17 +255,21 @@ async function ensureServerForCli(projectRoot) {
240
255
  try {
241
256
  const selected = resolveSelectedConnection(projectRoot);
242
257
  if (selected?.connection.kind === "remote") {
258
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
259
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
243
260
  return {
244
261
  baseUrl: selected.connection.baseUrl,
245
- authToken: readGitHubBearerTokenForRemote(projectRoot),
246
- connectionKind: "remote"
262
+ authToken,
263
+ connectionKind: "remote",
264
+ serverProjectRoot
247
265
  };
248
266
  }
249
267
  const connection = await ensureLocalRigServerConnection(projectRoot);
250
268
  return {
251
269
  baseUrl: connection.baseUrl,
252
270
  authToken: connection.authToken,
253
- connectionKind: "local"
271
+ connectionKind: "local",
272
+ serverProjectRoot: resolve2(projectRoot)
254
273
  };
255
274
  } catch (error) {
256
275
  if (error instanceof Error) {
@@ -259,6 +278,29 @@ async function ensureServerForCli(projectRoot) {
259
278
  throw error;
260
279
  }
261
280
  }
281
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
282
+ const repo = readRepoConnection(projectRoot);
283
+ const slug = repo?.project?.trim();
284
+ if (!slug)
285
+ return null;
286
+ try {
287
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
288
+ headers: mergeHeaders(undefined, authToken)
289
+ });
290
+ if (!response.ok)
291
+ return null;
292
+ const payload = await response.json();
293
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
294
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
295
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
296
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
297
+ if (path)
298
+ writeRepoServerProjectRoot(projectRoot, path);
299
+ return path;
300
+ } catch {
301
+ return null;
302
+ }
303
+ }
262
304
  function mergeHeaders(headers, authToken) {
263
305
  const merged = new Headers(headers);
264
306
  if (authToken) {
@@ -283,9 +325,12 @@ function diagnosticMessage(payload) {
283
325
  }
284
326
  async function requestServerJson(context, pathname, init = {}) {
285
327
  const server = await ensureServerForCli(context.projectRoot);
328
+ const headers = mergeHeaders(init.headers, server.authToken);
329
+ if (server.serverProjectRoot)
330
+ headers.set("x-rig-project-root", server.serverProjectRoot);
286
331
  const response = await fetch(`${server.baseUrl}${pathname}`, {
287
332
  ...init,
288
- headers: mergeHeaders(init.headers, server.authToken)
333
+ headers
289
334
  });
290
335
  const text = await response.text();
291
336
  const payload = text.trim().length > 0 ? (() => {
@@ -131,7 +131,8 @@ function readRepoConnection(projectRoot) {
131
131
  return {
132
132
  selected,
133
133
  project: typeof record.project === "string" ? record.project : undefined,
134
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
134
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
135
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
135
136
  };
136
137
  }
137
138
  function writeRepoConnection(projectRoot, state) {
@@ -142,13 +143,19 @@ function resolveSelectedConnection(projectRoot, options = {}) {
142
143
  if (!repo)
143
144
  return null;
144
145
  if (repo.selected === "local")
145
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
146
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
146
147
  const global = readGlobalConnections(options);
147
148
  const connection = global.connections[repo.selected];
148
149
  if (!connection) {
149
150
  throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
150
151
  }
151
- return { alias: repo.selected, connection };
152
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
153
+ }
154
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
155
+ const repo = readRepoConnection(projectRoot);
156
+ if (!repo)
157
+ return;
158
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
152
159
  }
153
160
 
154
161
  // packages/cli/src/commands/_server-client.ts
@@ -188,17 +195,21 @@ async function ensureServerForCli(projectRoot) {
188
195
  try {
189
196
  const selected = resolveSelectedConnection(projectRoot);
190
197
  if (selected?.connection.kind === "remote") {
198
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
199
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
191
200
  return {
192
201
  baseUrl: selected.connection.baseUrl,
193
- authToken: readGitHubBearerTokenForRemote(projectRoot),
194
- connectionKind: "remote"
202
+ authToken,
203
+ connectionKind: "remote",
204
+ serverProjectRoot
195
205
  };
196
206
  }
197
207
  const connection = await ensureLocalRigServerConnection(projectRoot);
198
208
  return {
199
209
  baseUrl: connection.baseUrl,
200
210
  authToken: connection.authToken,
201
- connectionKind: "local"
211
+ connectionKind: "local",
212
+ serverProjectRoot: resolve2(projectRoot)
202
213
  };
203
214
  } catch (error) {
204
215
  if (error instanceof Error) {
@@ -207,6 +218,29 @@ async function ensureServerForCli(projectRoot) {
207
218
  throw error;
208
219
  }
209
220
  }
221
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
222
+ const repo = readRepoConnection(projectRoot);
223
+ const slug = repo?.project?.trim();
224
+ if (!slug)
225
+ return null;
226
+ try {
227
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
228
+ headers: mergeHeaders(undefined, authToken)
229
+ });
230
+ if (!response.ok)
231
+ return null;
232
+ const payload = await response.json();
233
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
234
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
235
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
236
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
237
+ if (path)
238
+ writeRepoServerProjectRoot(projectRoot, path);
239
+ return path;
240
+ } catch {
241
+ return null;
242
+ }
243
+ }
210
244
  function mergeHeaders(headers, authToken) {
211
245
  const merged = new Headers(headers);
212
246
  if (authToken) {
@@ -231,9 +265,12 @@ function diagnosticMessage(payload) {
231
265
  }
232
266
  async function requestServerJson(context, pathname, init = {}) {
233
267
  const server = await ensureServerForCli(context.projectRoot);
268
+ const headers = mergeHeaders(init.headers, server.authToken);
269
+ if (server.serverProjectRoot)
270
+ headers.set("x-rig-project-root", server.serverProjectRoot);
234
271
  const response = await fetch(`${server.baseUrl}${pathname}`, {
235
272
  ...init,
236
- headers: mergeHeaders(init.headers, server.authToken)
273
+ headers
237
274
  });
238
275
  const text = await response.text();
239
276
  const payload = text.trim().length > 0 ? (() => {
@@ -307,22 +344,12 @@ async function switchServerProjectRootViaServer(context, projectRoot, options =
307
344
  if (!switched) {
308
345
  throw new CliError2(`Rig server did not accept project-root switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no response")}).`, 1);
309
346
  }
310
- while (Date.now() < deadline) {
311
- try {
312
- const status = await requestServerJson(context, "/api/server/status");
313
- if (status && typeof status === "object" && !Array.isArray(status)) {
314
- const record = status;
315
- if (record.projectRoot === projectRoot) {
316
- return { ok: true, switched, status: record };
317
- }
318
- lastError = `server projectRoot=${String(record.projectRoot ?? "unknown")}`;
319
- }
320
- } catch (error) {
321
- lastError = error;
322
- }
323
- await sleep(pollMs);
347
+ const record = switched && typeof switched === "object" && !Array.isArray(switched) ? switched : {};
348
+ if (record.ok === true) {
349
+ writeRepoServerProjectRoot(context.projectRoot, projectRoot);
350
+ return { ok: true, switched: record };
324
351
  }
325
- throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
352
+ throw new CliError2(`Rig server rejected project-root scope ${projectRoot}: ${String(record.message ?? record.error ?? "unknown error")}`, 1);
326
353
  }
327
354
  async function ensureTaskLabelsViaServer(context) {
328
355
  const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
@@ -84,6 +84,11 @@ function readJsonFile(path) {
84
84
  throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
85
85
  }
86
86
  }
87
+ function writeJsonFile(path, value) {
88
+ mkdirSync(dirname(path), { recursive: true });
89
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
90
+ `, "utf8");
91
+ }
87
92
  function normalizeConnection(value) {
88
93
  if (!value || typeof value !== "object" || Array.isArray(value))
89
94
  return null;
@@ -124,21 +129,31 @@ function readRepoConnection(projectRoot) {
124
129
  return {
125
130
  selected,
126
131
  project: typeof record.project === "string" ? record.project : undefined,
127
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
132
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
133
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
128
134
  };
129
135
  }
136
+ function writeRepoConnection(projectRoot, state) {
137
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
138
+ }
130
139
  function resolveSelectedConnection(projectRoot, options = {}) {
131
140
  const repo = readRepoConnection(projectRoot);
132
141
  if (!repo)
133
142
  return null;
134
143
  if (repo.selected === "local")
135
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
144
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
136
145
  const global = readGlobalConnections(options);
137
146
  const connection = global.connections[repo.selected];
138
147
  if (!connection) {
139
148
  throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
140
149
  }
141
- return { alias: repo.selected, connection };
150
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
151
+ }
152
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
153
+ const repo = readRepoConnection(projectRoot);
154
+ if (!repo)
155
+ return;
156
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
142
157
  }
143
158
 
144
159
  // packages/cli/src/commands/_server-client.ts
@@ -171,17 +186,21 @@ async function ensureServerForCli(projectRoot) {
171
186
  try {
172
187
  const selected = resolveSelectedConnection(projectRoot);
173
188
  if (selected?.connection.kind === "remote") {
189
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
190
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
174
191
  return {
175
192
  baseUrl: selected.connection.baseUrl,
176
- authToken: readGitHubBearerTokenForRemote(projectRoot),
177
- connectionKind: "remote"
193
+ authToken,
194
+ connectionKind: "remote",
195
+ serverProjectRoot
178
196
  };
179
197
  }
180
198
  const connection = await ensureLocalRigServerConnection(projectRoot);
181
199
  return {
182
200
  baseUrl: connection.baseUrl,
183
201
  authToken: connection.authToken,
184
- connectionKind: "local"
202
+ connectionKind: "local",
203
+ serverProjectRoot: resolve2(projectRoot)
185
204
  };
186
205
  } catch (error) {
187
206
  if (error instanceof Error) {
@@ -190,6 +209,29 @@ async function ensureServerForCli(projectRoot) {
190
209
  throw error;
191
210
  }
192
211
  }
212
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
213
+ const repo = readRepoConnection(projectRoot);
214
+ const slug = repo?.project?.trim();
215
+ if (!slug)
216
+ return null;
217
+ try {
218
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
219
+ headers: mergeHeaders(undefined, authToken)
220
+ });
221
+ if (!response.ok)
222
+ return null;
223
+ const payload = await response.json();
224
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
225
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
226
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
227
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
228
+ if (path)
229
+ writeRepoServerProjectRoot(projectRoot, path);
230
+ return path;
231
+ } catch {
232
+ return null;
233
+ }
234
+ }
193
235
  function mergeHeaders(headers, authToken) {
194
236
  const merged = new Headers(headers);
195
237
  if (authToken) {
@@ -214,9 +256,12 @@ function diagnosticMessage(payload) {
214
256
  }
215
257
  async function requestServerJson(context, pathname, init = {}) {
216
258
  const server = await ensureServerForCli(context.projectRoot);
259
+ const headers = mergeHeaders(init.headers, server.authToken);
260
+ if (server.serverProjectRoot)
261
+ headers.set("x-rig-project-root", server.serverProjectRoot);
217
262
  const response = await fetch(`${server.baseUrl}${pathname}`, {
218
263
  ...init,
219
- headers: mergeHeaders(init.headers, server.authToken)
264
+ headers
220
265
  });
221
266
  const text = await response.text();
222
267
  const payload = text.trim().length > 0 ? (() => {
@@ -108,6 +108,11 @@ function readJsonFile(path) {
108
108
  throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
109
109
  }
110
110
  }
111
+ function writeJsonFile(path, value) {
112
+ mkdirSync(dirname(path), { recursive: true });
113
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
114
+ `, "utf8");
115
+ }
111
116
  function normalizeConnection(value) {
112
117
  if (!value || typeof value !== "object" || Array.isArray(value))
113
118
  return null;
@@ -148,21 +153,31 @@ function readRepoConnection(projectRoot) {
148
153
  return {
149
154
  selected,
150
155
  project: typeof record.project === "string" ? record.project : undefined,
151
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
156
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
157
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
152
158
  };
153
159
  }
160
+ function writeRepoConnection(projectRoot, state) {
161
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
162
+ }
154
163
  function resolveSelectedConnection(projectRoot, options = {}) {
155
164
  const repo = readRepoConnection(projectRoot);
156
165
  if (!repo)
157
166
  return null;
158
167
  if (repo.selected === "local")
159
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
168
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
160
169
  const global = readGlobalConnections(options);
161
170
  const connection = global.connections[repo.selected];
162
171
  if (!connection) {
163
172
  throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
164
173
  }
165
- return { alias: repo.selected, connection };
174
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
175
+ }
176
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
177
+ const repo = readRepoConnection(projectRoot);
178
+ if (!repo)
179
+ return;
180
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
166
181
  }
167
182
 
168
183
  // packages/cli/src/commands/_server-client.ts
@@ -195,17 +210,21 @@ async function ensureServerForCli(projectRoot) {
195
210
  try {
196
211
  const selected = resolveSelectedConnection(projectRoot);
197
212
  if (selected?.connection.kind === "remote") {
213
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
214
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
198
215
  return {
199
216
  baseUrl: selected.connection.baseUrl,
200
- authToken: readGitHubBearerTokenForRemote(projectRoot),
201
- connectionKind: "remote"
217
+ authToken,
218
+ connectionKind: "remote",
219
+ serverProjectRoot
202
220
  };
203
221
  }
204
222
  const connection = await ensureLocalRigServerConnection(projectRoot);
205
223
  return {
206
224
  baseUrl: connection.baseUrl,
207
225
  authToken: connection.authToken,
208
- connectionKind: "local"
226
+ connectionKind: "local",
227
+ serverProjectRoot: resolve2(projectRoot)
209
228
  };
210
229
  } catch (error) {
211
230
  if (error instanceof Error) {
@@ -214,6 +233,29 @@ async function ensureServerForCli(projectRoot) {
214
233
  throw error;
215
234
  }
216
235
  }
236
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
237
+ const repo = readRepoConnection(projectRoot);
238
+ const slug = repo?.project?.trim();
239
+ if (!slug)
240
+ return null;
241
+ try {
242
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
243
+ headers: mergeHeaders(undefined, authToken)
244
+ });
245
+ if (!response.ok)
246
+ return null;
247
+ const payload = await response.json();
248
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
249
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
250
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
251
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
252
+ if (path)
253
+ writeRepoServerProjectRoot(projectRoot, path);
254
+ return path;
255
+ } catch {
256
+ return null;
257
+ }
258
+ }
217
259
  function mergeHeaders(headers, authToken) {
218
260
  const merged = new Headers(headers);
219
261
  if (authToken) {
@@ -238,9 +280,12 @@ function diagnosticMessage(payload) {
238
280
  }
239
281
  async function requestServerJson(context, pathname, init = {}) {
240
282
  const server = await ensureServerForCli(context.projectRoot);
283
+ const headers = mergeHeaders(init.headers, server.authToken);
284
+ if (server.serverProjectRoot)
285
+ headers.set("x-rig-project-root", server.serverProjectRoot);
241
286
  const response = await fetch(`${server.baseUrl}${pathname}`, {
242
287
  ...init,
243
- headers: mergeHeaders(init.headers, server.authToken)
288
+ headers
244
289
  });
245
290
  const text = await response.text();
246
291
  const payload = text.trim().length > 0 ? (() => {
@@ -365,6 +410,8 @@ async function buildRunPiEventsWebSocketUrl(context, runId) {
365
410
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
366
411
  if (server.authToken)
367
412
  url.searchParams.set("token", server.authToken);
413
+ if (server.serverProjectRoot)
414
+ url.searchParams.set("rigProjectRoot", server.serverProjectRoot);
368
415
  return url.toString();
369
416
  }
370
417