@h-rig/cli 0.0.6-alpha.44 → 0.0.6-alpha.46

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,28 @@ 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 checkout = project?.checkout && typeof project.checkout === "object" && !Array.isArray(project.checkout) ? project.checkout : null;
201
+ const path = typeof checkout?.path === "string" && checkout.path.trim() ? checkout.path.trim() : typeof project?.path === "string" && project.path.trim() ? project.path.trim() : null;
202
+ if (path)
203
+ writeRepoServerProjectRoot(projectRoot, path);
204
+ return path;
205
+ } catch {
206
+ return null;
207
+ }
208
+ }
168
209
  function mergeHeaders(headers, authToken) {
169
210
  const merged = new Headers(headers);
170
211
  if (authToken) {
@@ -189,9 +230,12 @@ function diagnosticMessage(payload) {
189
230
  }
190
231
  async function requestServerJson(context, pathname, init = {}) {
191
232
  const server = await ensureServerForCli(context.projectRoot);
233
+ const headers = mergeHeaders(init.headers, server.authToken);
234
+ if (server.serverProjectRoot)
235
+ headers.set("x-rig-project-root", server.serverProjectRoot);
192
236
  const response = await fetch(`${server.baseUrl}${pathname}`, {
193
237
  ...init,
194
- headers: mergeHeaders(init.headers, server.authToken)
238
+ headers
195
239
  });
196
240
  const text = await response.text();
197
241
  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,28 @@ 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 checkout = project?.checkout && typeof project.checkout === "object" && !Array.isArray(project.checkout) ? project.checkout : null;
295
+ const path = typeof checkout?.path === "string" && checkout.path.trim() ? checkout.path.trim() : typeof project?.path === "string" && project.path.trim() ? project.path.trim() : null;
296
+ if (path)
297
+ writeRepoServerProjectRoot(projectRoot, path);
298
+ return path;
299
+ } catch {
300
+ return null;
301
+ }
302
+ }
262
303
  function mergeHeaders(headers, authToken) {
263
304
  const merged = new Headers(headers);
264
305
  if (authToken) {
@@ -283,9 +324,12 @@ function diagnosticMessage(payload) {
283
324
  }
284
325
  async function requestServerJson(context, pathname, init = {}) {
285
326
  const server = await ensureServerForCli(context.projectRoot);
327
+ const headers = mergeHeaders(init.headers, server.authToken);
328
+ if (server.serverProjectRoot)
329
+ headers.set("x-rig-project-root", server.serverProjectRoot);
286
330
  const response = await fetch(`${server.baseUrl}${pathname}`, {
287
331
  ...init,
288
- headers: mergeHeaders(init.headers, server.authToken)
332
+ headers
289
333
  });
290
334
  const text = await response.text();
291
335
  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,28 @@ 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 checkout = project?.checkout && typeof project.checkout === "object" && !Array.isArray(project.checkout) ? project.checkout : null;
235
+ const path = typeof checkout?.path === "string" && checkout.path.trim() ? checkout.path.trim() : typeof project?.path === "string" && project.path.trim() ? project.path.trim() : null;
236
+ if (path)
237
+ writeRepoServerProjectRoot(projectRoot, path);
238
+ return path;
239
+ } catch {
240
+ return null;
241
+ }
242
+ }
210
243
  function mergeHeaders(headers, authToken) {
211
244
  const merged = new Headers(headers);
212
245
  if (authToken) {
@@ -231,9 +264,12 @@ function diagnosticMessage(payload) {
231
264
  }
232
265
  async function requestServerJson(context, pathname, init = {}) {
233
266
  const server = await ensureServerForCli(context.projectRoot);
267
+ const headers = mergeHeaders(init.headers, server.authToken);
268
+ if (server.serverProjectRoot)
269
+ headers.set("x-rig-project-root", server.serverProjectRoot);
234
270
  const response = await fetch(`${server.baseUrl}${pathname}`, {
235
271
  ...init,
236
- headers: mergeHeaders(init.headers, server.authToken)
272
+ headers
237
273
  });
238
274
  const text = await response.text();
239
275
  const payload = text.trim().length > 0 ? (() => {
@@ -307,22 +343,12 @@ async function switchServerProjectRootViaServer(context, projectRoot, options =
307
343
  if (!switched) {
308
344
  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
345
  }
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);
346
+ const record = switched && typeof switched === "object" && !Array.isArray(switched) ? switched : {};
347
+ if (record.ok === true) {
348
+ writeRepoServerProjectRoot(context.projectRoot, projectRoot);
349
+ return { ok: true, switched: record };
324
350
  }
325
- throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
351
+ throw new CliError2(`Rig server rejected project-root scope ${projectRoot}: ${String(record.message ?? record.error ?? "unknown error")}`, 1);
326
352
  }
327
353
  async function ensureTaskLabelsViaServer(context) {
328
354
  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,28 @@ 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 checkout = project?.checkout && typeof project.checkout === "object" && !Array.isArray(project.checkout) ? project.checkout : null;
226
+ const path = typeof checkout?.path === "string" && checkout.path.trim() ? checkout.path.trim() : typeof project?.path === "string" && project.path.trim() ? project.path.trim() : null;
227
+ if (path)
228
+ writeRepoServerProjectRoot(projectRoot, path);
229
+ return path;
230
+ } catch {
231
+ return null;
232
+ }
233
+ }
193
234
  function mergeHeaders(headers, authToken) {
194
235
  const merged = new Headers(headers);
195
236
  if (authToken) {
@@ -214,9 +255,12 @@ function diagnosticMessage(payload) {
214
255
  }
215
256
  async function requestServerJson(context, pathname, init = {}) {
216
257
  const server = await ensureServerForCli(context.projectRoot);
258
+ const headers = mergeHeaders(init.headers, server.authToken);
259
+ if (server.serverProjectRoot)
260
+ headers.set("x-rig-project-root", server.serverProjectRoot);
217
261
  const response = await fetch(`${server.baseUrl}${pathname}`, {
218
262
  ...init,
219
- headers: mergeHeaders(init.headers, server.authToken)
263
+ headers
220
264
  });
221
265
  const text = await response.text();
222
266
  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,28 @@ 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 checkout = project?.checkout && typeof project.checkout === "object" && !Array.isArray(project.checkout) ? project.checkout : null;
250
+ const path = typeof checkout?.path === "string" && checkout.path.trim() ? checkout.path.trim() : typeof project?.path === "string" && project.path.trim() ? project.path.trim() : null;
251
+ if (path)
252
+ writeRepoServerProjectRoot(projectRoot, path);
253
+ return path;
254
+ } catch {
255
+ return null;
256
+ }
257
+ }
217
258
  function mergeHeaders(headers, authToken) {
218
259
  const merged = new Headers(headers);
219
260
  if (authToken) {
@@ -238,9 +279,12 @@ function diagnosticMessage(payload) {
238
279
  }
239
280
  async function requestServerJson(context, pathname, init = {}) {
240
281
  const server = await ensureServerForCli(context.projectRoot);
282
+ const headers = mergeHeaders(init.headers, server.authToken);
283
+ if (server.serverProjectRoot)
284
+ headers.set("x-rig-project-root", server.serverProjectRoot);
241
285
  const response = await fetch(`${server.baseUrl}${pathname}`, {
242
286
  ...init,
243
- headers: mergeHeaders(init.headers, server.authToken)
287
+ headers
244
288
  });
245
289
  const text = await response.text();
246
290
  const payload = text.trim().length > 0 ? (() => {
@@ -365,6 +409,8 @@ async function buildRunPiEventsWebSocketUrl(context, runId) {
365
409
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
366
410
  if (server.authToken)
367
411
  url.searchParams.set("token", server.authToken);
412
+ if (server.serverProjectRoot)
413
+ url.searchParams.set("rigProjectRoot", server.serverProjectRoot);
368
414
  return url.toString();
369
415
  }
370
416