@h-rig/cli 0.0.6-alpha.5 → 0.0.6-alpha.50

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 (49) hide show
  1. package/dist/bin/rig.js +4072 -1204
  2. package/dist/src/commands/_cli-format.js +369 -0
  3. package/dist/src/commands/_connection-state.js +12 -6
  4. package/dist/src/commands/_doctor-checks.js +65 -34
  5. package/dist/src/commands/_help-catalog.js +445 -0
  6. package/dist/src/commands/_operator-surface.js +220 -0
  7. package/dist/src/commands/_operator-view.js +1109 -63
  8. package/dist/src/commands/_parsers.js +0 -2
  9. package/dist/src/commands/_pi-frontend.js +1066 -0
  10. package/dist/src/commands/_pi-install.js +4 -3
  11. package/dist/src/commands/_pi-remote-session.js +757 -0
  12. package/dist/src/commands/_pi-worker-bridge-extension.js +820 -0
  13. package/dist/src/commands/_policy.js +0 -2
  14. package/dist/src/commands/_preflight.js +84 -116
  15. package/dist/src/commands/_run-driver-helpers.js +2 -2
  16. package/dist/src/commands/_server-client.js +211 -48
  17. package/dist/src/commands/_snapshot-upload.js +60 -30
  18. package/dist/src/commands/_spinner.js +63 -0
  19. package/dist/src/commands/_task-picker.js +44 -16
  20. package/dist/src/commands/agent.js +8 -9
  21. package/dist/src/commands/browser.js +4 -6
  22. package/dist/src/commands/connect.js +134 -26
  23. package/dist/src/commands/dist.js +4 -6
  24. package/dist/src/commands/doctor.js +65 -34
  25. package/dist/src/commands/github.js +62 -32
  26. package/dist/src/commands/inbox.js +396 -31
  27. package/dist/src/commands/init.js +342 -88
  28. package/dist/src/commands/inspect.js +282 -23
  29. package/dist/src/commands/inspector.js +2 -4
  30. package/dist/src/commands/pi.js +168 -0
  31. package/dist/src/commands/plugin.js +81 -22
  32. package/dist/src/commands/profile-and-review.js +8 -10
  33. package/dist/src/commands/queue.js +2 -3
  34. package/dist/src/commands/remote.js +18 -20
  35. package/dist/src/commands/repo-git-harness.js +6 -8
  36. package/dist/src/commands/run.js +1407 -130
  37. package/dist/src/commands/server.js +266 -40
  38. package/dist/src/commands/setup.js +69 -44
  39. package/dist/src/commands/task-report-bug.js +5 -7
  40. package/dist/src/commands/task-run-driver.js +662 -70
  41. package/dist/src/commands/task.js +1846 -259
  42. package/dist/src/commands/test.js +3 -5
  43. package/dist/src/commands/workspace.js +4 -6
  44. package/dist/src/commands.js +4054 -1180
  45. package/dist/src/index.js +4065 -1200
  46. package/dist/src/launcher.js +5 -3
  47. package/dist/src/report-bug.js +3 -3
  48. package/dist/src/runner.js +5 -19
  49. package/package.json +7 -4
@@ -1,6 +1,5 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/_server-client.ts
3
- import { spawnSync } from "child_process";
4
3
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
5
4
  import { resolve as resolve2 } from "path";
6
5
 
@@ -8,8 +7,6 @@ import { resolve as resolve2 } from "path";
8
7
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
9
8
  import { CliError } from "@rig/runtime/control-plane/errors";
10
9
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
11
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
12
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
13
10
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
14
11
  import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
15
12
 
@@ -41,6 +38,11 @@ function readJsonFile(path) {
41
38
  throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
42
39
  }
43
40
  }
41
+ function writeJsonFile(path, value) {
42
+ mkdirSync(dirname(path), { recursive: true });
43
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
44
+ `, "utf8");
45
+ }
44
46
  function normalizeConnection(value) {
45
47
  if (!value || typeof value !== "object" || Array.isArray(value))
46
48
  return null;
@@ -81,31 +83,42 @@ function readRepoConnection(projectRoot) {
81
83
  return {
82
84
  selected,
83
85
  project: typeof record.project === "string" ? record.project : undefined,
84
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
86
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
87
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
85
88
  };
86
89
  }
90
+ function writeRepoConnection(projectRoot, state) {
91
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
92
+ }
87
93
  function resolveSelectedConnection(projectRoot, options = {}) {
88
94
  const repo = readRepoConnection(projectRoot);
89
95
  if (!repo)
90
96
  return null;
91
97
  if (repo.selected === "local")
92
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
98
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
93
99
  const global = readGlobalConnections(options);
94
100
  const connection = global.connections[repo.selected];
95
101
  if (!connection) {
96
- throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
102
+ throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
97
103
  }
98
- return { alias: repo.selected, connection };
104
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
105
+ }
106
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
107
+ const repo = readRepoConnection(projectRoot);
108
+ if (!repo)
109
+ return;
110
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
99
111
  }
100
112
 
101
113
  // packages/cli/src/commands/_server-client.ts
102
- var cachedGitHubBearerToken;
114
+ var scopedGitHubBearerTokens = new Map;
103
115
  function cleanToken(value) {
104
116
  const trimmed = value?.trim();
105
117
  return trimmed ? trimmed : null;
106
118
  }
107
- function setGitHubBearerTokenForCurrentProcess(token) {
108
- cachedGitHubBearerToken = cleanToken(token ?? undefined);
119
+ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
120
+ const scopedKey = resolve2(projectRoot ?? process.cwd());
121
+ scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
109
122
  }
110
123
  function readPrivateRemoteSessionToken(projectRoot) {
111
124
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
@@ -119,41 +132,33 @@ function readPrivateRemoteSessionToken(projectRoot) {
119
132
  }
120
133
  }
121
134
  function readGitHubBearerTokenForRemote(projectRoot) {
122
- if (cachedGitHubBearerToken !== undefined)
123
- return cachedGitHubBearerToken;
135
+ const scopedKey = resolve2(projectRoot);
136
+ if (scopedGitHubBearerTokens.has(scopedKey))
137
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
124
138
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
125
- if (privateSession) {
126
- cachedGitHubBearerToken = privateSession;
127
- return cachedGitHubBearerToken;
128
- }
129
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
130
- if (envToken) {
131
- cachedGitHubBearerToken = envToken;
132
- return cachedGitHubBearerToken;
133
- }
134
- const result = spawnSync("gh", ["auth", "token"], {
135
- encoding: "utf8",
136
- timeout: 5000,
137
- stdio: ["ignore", "pipe", "ignore"]
138
- });
139
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
140
- return cachedGitHubBearerToken;
139
+ if (privateSession)
140
+ return privateSession;
141
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
141
142
  }
142
143
  async function ensureServerForCli(projectRoot) {
143
144
  try {
144
145
  const selected = resolveSelectedConnection(projectRoot);
145
146
  if (selected?.connection.kind === "remote") {
147
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
148
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
146
149
  return {
147
150
  baseUrl: selected.connection.baseUrl,
148
- authToken: readGitHubBearerTokenForRemote(projectRoot),
149
- connectionKind: "remote"
151
+ authToken,
152
+ connectionKind: "remote",
153
+ serverProjectRoot
150
154
  };
151
155
  }
152
156
  const connection = await ensureLocalRigServerConnection(projectRoot);
153
157
  return {
154
158
  baseUrl: connection.baseUrl,
155
159
  authToken: connection.authToken,
156
- connectionKind: "local"
160
+ connectionKind: "local",
161
+ serverProjectRoot: resolve2(projectRoot)
157
162
  };
158
163
  } catch (error) {
159
164
  if (error instanceof Error) {
@@ -162,6 +167,29 @@ async function ensureServerForCli(projectRoot) {
162
167
  throw error;
163
168
  }
164
169
  }
170
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
171
+ const repo = readRepoConnection(projectRoot);
172
+ const slug = repo?.project?.trim();
173
+ if (!slug)
174
+ return null;
175
+ try {
176
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
177
+ headers: mergeHeaders(undefined, authToken)
178
+ });
179
+ if (!response.ok)
180
+ return null;
181
+ const payload = await response.json();
182
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
183
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
184
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
185
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
186
+ if (path)
187
+ writeRepoServerProjectRoot(projectRoot, path);
188
+ return path;
189
+ } catch {
190
+ return null;
191
+ }
192
+ }
165
193
  function appendTaskFilterParams(url, filters) {
166
194
  if (filters.assignee)
167
195
  url.searchParams.set("assignee", filters.assignee);
@@ -196,9 +224,12 @@ function diagnosticMessage(payload) {
196
224
  }
197
225
  async function requestServerJson(context, pathname, init = {}) {
198
226
  const server = await ensureServerForCli(context.projectRoot);
227
+ const headers = mergeHeaders(init.headers, server.authToken);
228
+ if (server.serverProjectRoot)
229
+ headers.set("x-rig-project-root", server.serverProjectRoot);
199
230
  const response = await fetch(`${server.baseUrl}${pathname}`, {
200
231
  ...init,
201
- headers: mergeHeaders(init.headers, server.authToken)
232
+ headers
202
233
  });
203
234
  const text = await response.text();
204
235
  const payload = text.trim().length > 0 ? (() => {
@@ -275,32 +306,50 @@ async function registerProjectViaServer(context, input) {
275
306
  function sleep(ms) {
276
307
  return new Promise((resolve3) => setTimeout(resolve3, ms));
277
308
  }
309
+ function isRetryableProjectRootSwitchError(error) {
310
+ if (!(error instanceof Error))
311
+ return false;
312
+ const message = error.message.toLowerCase();
313
+ return message.includes("rig server request failed (401): auth-required") || message.includes("rig server request failed (401): github-token-required") || message.includes("rig server request failed (502)") || message.includes("rig server request failed (503)") || message.includes("bad gateway") || message.includes("fetch failed") || message.includes("econnrefused") || message.includes("connection refused");
314
+ }
278
315
  async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
279
- const switched = await requestServerJson(context, "/api/server/project-root", {
280
- method: "POST",
281
- headers: { "content-type": "application/json" },
282
- body: JSON.stringify({ projectRoot })
283
- });
284
316
  const timeoutMs = options.timeoutMs ?? 30000;
285
317
  const pollMs = options.pollMs ?? 1000;
286
318
  const deadline = Date.now() + timeoutMs;
287
319
  let lastError;
320
+ let switched = null;
288
321
  while (Date.now() < deadline) {
289
322
  try {
290
- const status = await requestServerJson(context, "/api/server/status");
291
- if (status && typeof status === "object" && !Array.isArray(status)) {
292
- const record = status;
293
- if (record.projectRoot === projectRoot) {
294
- return { ok: true, switched, status: record };
295
- }
296
- lastError = `server projectRoot=${String(record.projectRoot ?? "unknown")}`;
297
- }
323
+ switched = await requestServerJson(context, "/api/server/project-root", {
324
+ method: "POST",
325
+ headers: { "content-type": "application/json" },
326
+ body: JSON.stringify({ projectRoot })
327
+ });
328
+ break;
298
329
  } catch (error) {
299
330
  lastError = error;
331
+ if (!isRetryableProjectRootSwitchError(error))
332
+ throw error;
333
+ await sleep(pollMs);
300
334
  }
301
- await sleep(pollMs);
302
335
  }
303
- throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
336
+ if (!switched) {
337
+ 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);
338
+ }
339
+ const record = switched && typeof switched === "object" && !Array.isArray(switched) ? switched : {};
340
+ if (record.ok === true) {
341
+ writeRepoServerProjectRoot(context.projectRoot, projectRoot);
342
+ return { ok: true, switched: record };
343
+ }
344
+ throw new CliError2(`Rig server rejected project-root scope ${projectRoot}: ${String(record.message ?? record.error ?? "unknown error")}`, 1);
345
+ }
346
+ async function listRunsViaServer(context, options = {}) {
347
+ const url = new URL("http://rig.local/api/runs");
348
+ if (options.limit !== undefined)
349
+ url.searchParams.set("limit", String(options.limit));
350
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
351
+ const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
352
+ return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
304
353
  }
305
354
  async function getRunDetailsViaServer(context, runId) {
306
355
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}`);
@@ -315,6 +364,37 @@ async function getRunLogsViaServer(context, runId, options = {}) {
315
364
  const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
316
365
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
317
366
  }
367
+ async function getRunTimelineViaServer(context, runId, options = {}) {
368
+ const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
369
+ if (options.limit !== undefined)
370
+ url.searchParams.set("limit", String(options.limit));
371
+ if (options.cursor)
372
+ url.searchParams.set("cursor", options.cursor);
373
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
374
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
375
+ }
376
+ async function ensureTaskLabelsViaServer(context) {
377
+ const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
378
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
379
+ }
380
+ async function listGitHubProjectsViaServer(context, owner) {
381
+ const url = new URL("http://rig.local/api/github/projects");
382
+ url.searchParams.set("owner", owner);
383
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
384
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
385
+ }
386
+ async function getGitHubProjectStatusFieldViaServer(context, projectId) {
387
+ const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
388
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
389
+ }
390
+ async function updateWorkspaceTaskViaServer(context, input) {
391
+ const payload = await requestServerJson(context, "/api/tasks/update", {
392
+ method: "POST",
393
+ headers: { "content-type": "application/json" },
394
+ body: JSON.stringify(input)
395
+ });
396
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
397
+ }
318
398
  async function stopRunViaServer(context, runId) {
319
399
  const payload = await requestServerJson(context, "/api/runs/stop", {
320
400
  method: "POST",
@@ -331,6 +411,72 @@ async function steerRunViaServer(context, runId, message) {
331
411
  });
332
412
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
333
413
  }
414
+ async function getRunPiSessionViaServer(context, runId) {
415
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi`);
416
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
417
+ }
418
+ async function getRunPiMessagesViaServer(context, runId) {
419
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/messages`);
420
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { messages: [] };
421
+ }
422
+ async function getRunPiStatusViaServer(context, runId) {
423
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/status`);
424
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
425
+ }
426
+ async function getRunPiCommandsViaServer(context, runId) {
427
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
428
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
429
+ }
430
+ async function getRunPiCapabilitiesViaServer(context, runId) {
431
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
432
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
433
+ }
434
+ async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
435
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
436
+ method: "POST",
437
+ headers: { "content-type": "application/json" },
438
+ body: JSON.stringify({ text, streamingBehavior })
439
+ });
440
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
441
+ }
442
+ async function sendRunPiShellViaServer(context, runId, text) {
443
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/shell`, {
444
+ method: "POST",
445
+ headers: { "content-type": "application/json" },
446
+ body: JSON.stringify({ text })
447
+ });
448
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
449
+ }
450
+ async function runRunPiCommandViaServer(context, runId, text) {
451
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands/run`, {
452
+ method: "POST",
453
+ headers: { "content-type": "application/json" },
454
+ body: JSON.stringify({ text })
455
+ });
456
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { type: "done" };
457
+ }
458
+ async function respondRunPiExtensionUiViaServer(context, runId, requestId, valueOrCancel) {
459
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/extension-ui/respond`, {
460
+ method: "POST",
461
+ headers: { "content-type": "application/json" },
462
+ body: JSON.stringify({ requestId, ...valueOrCancel })
463
+ });
464
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
465
+ }
466
+ async function abortRunPiViaServer(context, runId) {
467
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/abort`, { method: "POST" });
468
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { aborted: true };
469
+ }
470
+ async function buildRunPiEventsWebSocketUrl(context, runId) {
471
+ const server = await ensureServerForCli(context.projectRoot);
472
+ const url = new URL(`${server.baseUrl.replace(/\/+$/, "")}/api/runs/${encodeURIComponent(runId)}/pi/events`);
473
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
474
+ if (server.authToken)
475
+ url.searchParams.set("token", server.authToken);
476
+ if (server.serverProjectRoot)
477
+ url.searchParams.set("rigProjectRoot", server.serverProjectRoot);
478
+ return url.toString();
479
+ }
334
480
  async function submitTaskRunViaServer(context, input) {
335
481
  const isTaskRun = Boolean(input.taskId);
336
482
  const endpoint = isTaskRun ? "/api/runs/task" : "/api/runs/adhoc";
@@ -363,20 +509,37 @@ async function submitTaskRunViaServer(context, input) {
363
509
  return { runId };
364
510
  }
365
511
  export {
512
+ updateWorkspaceTaskViaServer,
366
513
  switchServerProjectRootViaServer,
367
514
  submitTaskRunViaServer,
368
515
  stopRunViaServer,
369
516
  steerRunViaServer,
370
517
  setGitHubBearerTokenForCurrentProcess,
518
+ sendRunPiShellViaServer,
519
+ sendRunPiPromptViaServer,
371
520
  selectNextWorkspaceTaskViaServer,
521
+ runRunPiCommandViaServer,
522
+ respondRunPiExtensionUiViaServer,
372
523
  requestServerJson,
373
524
  registerProjectViaServer,
374
525
  prepareRemoteCheckoutViaServer,
375
526
  postGitHubTokenViaServer,
376
527
  listWorkspaceTasksViaServer,
528
+ listRunsViaServer,
529
+ listGitHubProjectsViaServer,
377
530
  getWorkspaceTaskViaServer,
531
+ getRunTimelineViaServer,
532
+ getRunPiStatusViaServer,
533
+ getRunPiSessionViaServer,
534
+ getRunPiMessagesViaServer,
535
+ getRunPiCommandsViaServer,
536
+ getRunPiCapabilitiesViaServer,
378
537
  getRunLogsViaServer,
379
538
  getRunDetailsViaServer,
539
+ getGitHubProjectStatusFieldViaServer,
380
540
  getGitHubAuthStatusViaServer,
381
- ensureServerForCli
541
+ ensureTaskLabelsViaServer,
542
+ ensureServerForCli,
543
+ buildRunPiEventsWebSocketUrl,
544
+ abortRunPiViaServer
382
545
  };
@@ -4,7 +4,6 @@ import { mkdir, readdir, readFile, writeFile } from "fs/promises";
4
4
  import { dirname as dirname2, resolve as resolve3, relative, sep } from "path";
5
5
 
6
6
  // packages/cli/src/commands/_server-client.ts
7
- import { spawnSync } from "child_process";
8
7
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
9
8
  import { resolve as resolve2 } from "path";
10
9
 
@@ -12,8 +11,6 @@ import { resolve as resolve2 } from "path";
12
11
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
13
12
  import { CliError } from "@rig/runtime/control-plane/errors";
14
13
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
15
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
16
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
17
14
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
18
15
  import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
19
16
 
@@ -45,6 +42,11 @@ function readJsonFile(path) {
45
42
  throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
46
43
  }
47
44
  }
45
+ function writeJsonFile(path, value) {
46
+ mkdirSync(dirname(path), { recursive: true });
47
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
48
+ `, "utf8");
49
+ }
48
50
  function normalizeConnection(value) {
49
51
  if (!value || typeof value !== "object" || Array.isArray(value))
50
52
  return null;
@@ -85,25 +87,35 @@ function readRepoConnection(projectRoot) {
85
87
  return {
86
88
  selected,
87
89
  project: typeof record.project === "string" ? record.project : undefined,
88
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
90
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
91
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
89
92
  };
90
93
  }
94
+ function writeRepoConnection(projectRoot, state) {
95
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
96
+ }
91
97
  function resolveSelectedConnection(projectRoot, options = {}) {
92
98
  const repo = readRepoConnection(projectRoot);
93
99
  if (!repo)
94
100
  return null;
95
101
  if (repo.selected === "local")
96
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
102
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
97
103
  const global = readGlobalConnections(options);
98
104
  const connection = global.connections[repo.selected];
99
105
  if (!connection) {
100
- throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
106
+ throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
101
107
  }
102
- return { alias: repo.selected, connection };
108
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
109
+ }
110
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
111
+ const repo = readRepoConnection(projectRoot);
112
+ if (!repo)
113
+ return;
114
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
103
115
  }
104
116
 
105
117
  // packages/cli/src/commands/_server-client.ts
106
- var cachedGitHubBearerToken;
118
+ var scopedGitHubBearerTokens = new Map;
107
119
  function cleanToken(value) {
108
120
  const trimmed = value?.trim();
109
121
  return trimmed ? trimmed : null;
@@ -120,41 +132,33 @@ function readPrivateRemoteSessionToken(projectRoot) {
120
132
  }
121
133
  }
122
134
  function readGitHubBearerTokenForRemote(projectRoot) {
123
- if (cachedGitHubBearerToken !== undefined)
124
- return cachedGitHubBearerToken;
135
+ const scopedKey = resolve2(projectRoot);
136
+ if (scopedGitHubBearerTokens.has(scopedKey))
137
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
125
138
  const privateSession = readPrivateRemoteSessionToken(projectRoot);
126
- if (privateSession) {
127
- cachedGitHubBearerToken = privateSession;
128
- return cachedGitHubBearerToken;
129
- }
130
- const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
131
- if (envToken) {
132
- cachedGitHubBearerToken = envToken;
133
- return cachedGitHubBearerToken;
134
- }
135
- const result = spawnSync("gh", ["auth", "token"], {
136
- encoding: "utf8",
137
- timeout: 5000,
138
- stdio: ["ignore", "pipe", "ignore"]
139
- });
140
- cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
141
- return cachedGitHubBearerToken;
139
+ if (privateSession)
140
+ return privateSession;
141
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
142
142
  }
143
143
  async function ensureServerForCli(projectRoot) {
144
144
  try {
145
145
  const selected = resolveSelectedConnection(projectRoot);
146
146
  if (selected?.connection.kind === "remote") {
147
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
148
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
147
149
  return {
148
150
  baseUrl: selected.connection.baseUrl,
149
- authToken: readGitHubBearerTokenForRemote(projectRoot),
150
- connectionKind: "remote"
151
+ authToken,
152
+ connectionKind: "remote",
153
+ serverProjectRoot
151
154
  };
152
155
  }
153
156
  const connection = await ensureLocalRigServerConnection(projectRoot);
154
157
  return {
155
158
  baseUrl: connection.baseUrl,
156
159
  authToken: connection.authToken,
157
- connectionKind: "local"
160
+ connectionKind: "local",
161
+ serverProjectRoot: resolve2(projectRoot)
158
162
  };
159
163
  } catch (error) {
160
164
  if (error instanceof Error) {
@@ -163,6 +167,29 @@ async function ensureServerForCli(projectRoot) {
163
167
  throw error;
164
168
  }
165
169
  }
170
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
171
+ const repo = readRepoConnection(projectRoot);
172
+ const slug = repo?.project?.trim();
173
+ if (!slug)
174
+ return null;
175
+ try {
176
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
177
+ headers: mergeHeaders(undefined, authToken)
178
+ });
179
+ if (!response.ok)
180
+ return null;
181
+ const payload = await response.json();
182
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
183
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
184
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
185
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
186
+ if (path)
187
+ writeRepoServerProjectRoot(projectRoot, path);
188
+ return path;
189
+ } catch {
190
+ return null;
191
+ }
192
+ }
166
193
  function mergeHeaders(headers, authToken) {
167
194
  const merged = new Headers(headers);
168
195
  if (authToken) {
@@ -187,9 +214,12 @@ function diagnosticMessage(payload) {
187
214
  }
188
215
  async function requestServerJson(context, pathname, init = {}) {
189
216
  const server = await ensureServerForCli(context.projectRoot);
217
+ const headers = mergeHeaders(init.headers, server.authToken);
218
+ if (server.serverProjectRoot)
219
+ headers.set("x-rig-project-root", server.serverProjectRoot);
190
220
  const response = await fetch(`${server.baseUrl}${pathname}`, {
191
221
  ...init,
192
- headers: mergeHeaders(init.headers, server.authToken)
222
+ headers
193
223
  });
194
224
  const text = await response.text();
195
225
  const payload = text.trim().length > 0 ? (() => {
@@ -0,0 +1,63 @@
1
+ // @bun
2
+ // packages/cli/src/commands/_spinner.ts
3
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
4
+ function createTtySpinner(input) {
5
+ const output = input.output ?? process.stdout;
6
+ const isTty = output.isTTY === true;
7
+ let label = input.label;
8
+ let frame = 0;
9
+ let paused = false;
10
+ let stopped = false;
11
+ let lastPrintedLabel = "";
12
+ const render = () => {
13
+ if (stopped || paused)
14
+ return;
15
+ if (!isTty) {
16
+ if (label !== lastPrintedLabel) {
17
+ output.write(`${label}
18
+ `);
19
+ lastPrintedLabel = label;
20
+ }
21
+ return;
22
+ }
23
+ frame = (frame + 1) % SPINNER_FRAMES.length;
24
+ output.write(`\r\x1B[2K${SPINNER_FRAMES[frame]} ${label}`);
25
+ };
26
+ const clearLine = () => {
27
+ if (isTty)
28
+ output.write("\r\x1B[2K");
29
+ };
30
+ render();
31
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
32
+ return {
33
+ setLabel(next) {
34
+ label = next;
35
+ render();
36
+ },
37
+ pause() {
38
+ paused = true;
39
+ clearLine();
40
+ },
41
+ resume() {
42
+ if (stopped)
43
+ return;
44
+ paused = false;
45
+ render();
46
+ },
47
+ stop(finalLine) {
48
+ if (stopped)
49
+ return;
50
+ stopped = true;
51
+ if (timer)
52
+ clearInterval(timer);
53
+ clearLine();
54
+ if (finalLine)
55
+ output.write(`${finalLine}
56
+ `);
57
+ }
58
+ };
59
+ }
60
+ export {
61
+ createTtySpinner,
62
+ SPINNER_FRAMES
63
+ };