@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
@@ -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,47 @@ 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;
139
+ if (privateSession)
140
+ return privateSession;
141
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
142
+ }
143
+ function readStoredGitHubAuthToken(projectRoot) {
144
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
145
+ if (!existsSync2(path))
146
+ return null;
147
+ try {
148
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
149
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
150
+ } catch {
151
+ return null;
133
152
  }
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;
153
+ }
154
+ function readLocalConnectionFallbackToken(projectRoot) {
155
+ return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
141
156
  }
142
157
  async function ensureServerForCli(projectRoot) {
143
158
  try {
144
159
  const selected = resolveSelectedConnection(projectRoot);
145
160
  if (selected?.connection.kind === "remote") {
161
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
162
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
146
163
  return {
147
164
  baseUrl: selected.connection.baseUrl,
148
- authToken: readGitHubBearerTokenForRemote(projectRoot),
149
- connectionKind: "remote"
165
+ authToken,
166
+ connectionKind: "remote",
167
+ serverProjectRoot
150
168
  };
151
169
  }
152
170
  const connection = await ensureLocalRigServerConnection(projectRoot);
153
171
  return {
154
172
  baseUrl: connection.baseUrl,
155
- authToken: connection.authToken,
156
- connectionKind: "local"
173
+ authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
174
+ connectionKind: "local",
175
+ serverProjectRoot: resolve2(projectRoot)
157
176
  };
158
177
  } catch (error) {
159
178
  if (error instanceof Error) {
@@ -162,6 +181,29 @@ async function ensureServerForCli(projectRoot) {
162
181
  throw error;
163
182
  }
164
183
  }
184
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
185
+ const repo = readRepoConnection(projectRoot);
186
+ const slug = repo?.project?.trim();
187
+ if (!slug)
188
+ return null;
189
+ try {
190
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
191
+ headers: mergeHeaders(undefined, authToken)
192
+ });
193
+ if (!response.ok)
194
+ return null;
195
+ const payload = await response.json();
196
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
197
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
198
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
199
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
200
+ if (path)
201
+ writeRepoServerProjectRoot(projectRoot, path);
202
+ return path;
203
+ } catch {
204
+ return null;
205
+ }
206
+ }
165
207
  function appendTaskFilterParams(url, filters) {
166
208
  if (filters.assignee)
167
209
  url.searchParams.set("assignee", filters.assignee);
@@ -196,9 +238,12 @@ function diagnosticMessage(payload) {
196
238
  }
197
239
  async function requestServerJson(context, pathname, init = {}) {
198
240
  const server = await ensureServerForCli(context.projectRoot);
241
+ const headers = mergeHeaders(init.headers, server.authToken);
242
+ if (server.serverProjectRoot)
243
+ headers.set("x-rig-project-root", server.serverProjectRoot);
199
244
  const response = await fetch(`${server.baseUrl}${pathname}`, {
200
245
  ...init,
201
- headers: mergeHeaders(init.headers, server.authToken)
246
+ headers
202
247
  });
203
248
  const text = await response.text();
204
249
  const payload = text.trim().length > 0 ? (() => {
@@ -275,32 +320,50 @@ async function registerProjectViaServer(context, input) {
275
320
  function sleep(ms) {
276
321
  return new Promise((resolve3) => setTimeout(resolve3, ms));
277
322
  }
323
+ function isRetryableProjectRootSwitchError(error) {
324
+ if (!(error instanceof Error))
325
+ return false;
326
+ const message = error.message.toLowerCase();
327
+ 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");
328
+ }
278
329
  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
330
  const timeoutMs = options.timeoutMs ?? 30000;
285
331
  const pollMs = options.pollMs ?? 1000;
286
332
  const deadline = Date.now() + timeoutMs;
287
333
  let lastError;
334
+ let switched = null;
288
335
  while (Date.now() < deadline) {
289
336
  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
- }
337
+ switched = await requestServerJson(context, "/api/server/project-root", {
338
+ method: "POST",
339
+ headers: { "content-type": "application/json" },
340
+ body: JSON.stringify({ projectRoot })
341
+ });
342
+ break;
298
343
  } catch (error) {
299
344
  lastError = error;
345
+ if (!isRetryableProjectRootSwitchError(error))
346
+ throw error;
347
+ await sleep(pollMs);
300
348
  }
301
- await sleep(pollMs);
302
349
  }
303
- throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
350
+ if (!switched) {
351
+ 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);
352
+ }
353
+ const record = switched && typeof switched === "object" && !Array.isArray(switched) ? switched : {};
354
+ if (record.ok === true) {
355
+ writeRepoServerProjectRoot(context.projectRoot, projectRoot);
356
+ return { ok: true, switched: record };
357
+ }
358
+ throw new CliError2(`Rig server rejected project-root scope ${projectRoot}: ${String(record.message ?? record.error ?? "unknown error")}`, 1);
359
+ }
360
+ async function listRunsViaServer(context, options = {}) {
361
+ const url = new URL("http://rig.local/api/runs");
362
+ if (options.limit !== undefined)
363
+ url.searchParams.set("limit", String(options.limit));
364
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
365
+ const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
366
+ return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
304
367
  }
305
368
  async function getRunDetailsViaServer(context, runId) {
306
369
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}`);
@@ -315,6 +378,37 @@ async function getRunLogsViaServer(context, runId, options = {}) {
315
378
  const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
316
379
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
317
380
  }
381
+ async function getRunTimelineViaServer(context, runId, options = {}) {
382
+ const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
383
+ if (options.limit !== undefined)
384
+ url.searchParams.set("limit", String(options.limit));
385
+ if (options.cursor)
386
+ url.searchParams.set("cursor", options.cursor);
387
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
388
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
389
+ }
390
+ async function ensureTaskLabelsViaServer(context) {
391
+ const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
392
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
393
+ }
394
+ async function listGitHubProjectsViaServer(context, owner) {
395
+ const url = new URL("http://rig.local/api/github/projects");
396
+ url.searchParams.set("owner", owner);
397
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
398
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
399
+ }
400
+ async function getGitHubProjectStatusFieldViaServer(context, projectId) {
401
+ const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
402
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
403
+ }
404
+ async function updateWorkspaceTaskViaServer(context, input) {
405
+ const payload = await requestServerJson(context, "/api/tasks/update", {
406
+ method: "POST",
407
+ headers: { "content-type": "application/json" },
408
+ body: JSON.stringify(input)
409
+ });
410
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
411
+ }
318
412
  async function stopRunViaServer(context, runId) {
319
413
  const payload = await requestServerJson(context, "/api/runs/stop", {
320
414
  method: "POST",
@@ -331,6 +425,72 @@ async function steerRunViaServer(context, runId, message) {
331
425
  });
332
426
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
333
427
  }
428
+ async function getRunPiSessionViaServer(context, runId) {
429
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi`);
430
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
431
+ }
432
+ async function getRunPiMessagesViaServer(context, runId) {
433
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/messages`);
434
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { messages: [] };
435
+ }
436
+ async function getRunPiStatusViaServer(context, runId) {
437
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/status`);
438
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
439
+ }
440
+ async function getRunPiCommandsViaServer(context, runId) {
441
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
442
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
443
+ }
444
+ async function getRunPiCapabilitiesViaServer(context, runId) {
445
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
446
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
447
+ }
448
+ async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
449
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
450
+ method: "POST",
451
+ headers: { "content-type": "application/json" },
452
+ body: JSON.stringify({ text, streamingBehavior })
453
+ });
454
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
455
+ }
456
+ async function sendRunPiShellViaServer(context, runId, text) {
457
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/shell`, {
458
+ method: "POST",
459
+ headers: { "content-type": "application/json" },
460
+ body: JSON.stringify({ text })
461
+ });
462
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
463
+ }
464
+ async function runRunPiCommandViaServer(context, runId, text) {
465
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands/run`, {
466
+ method: "POST",
467
+ headers: { "content-type": "application/json" },
468
+ body: JSON.stringify({ text })
469
+ });
470
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { type: "done" };
471
+ }
472
+ async function respondRunPiExtensionUiViaServer(context, runId, requestId, valueOrCancel) {
473
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/extension-ui/respond`, {
474
+ method: "POST",
475
+ headers: { "content-type": "application/json" },
476
+ body: JSON.stringify({ requestId, ...valueOrCancel })
477
+ });
478
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
479
+ }
480
+ async function abortRunPiViaServer(context, runId) {
481
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/abort`, { method: "POST" });
482
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { aborted: true };
483
+ }
484
+ async function buildRunPiEventsWebSocketUrl(context, runId) {
485
+ const server = await ensureServerForCli(context.projectRoot);
486
+ const url = new URL(`${server.baseUrl.replace(/\/+$/, "")}/api/runs/${encodeURIComponent(runId)}/pi/events`);
487
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
488
+ if (server.authToken)
489
+ url.searchParams.set("token", server.authToken);
490
+ if (server.serverProjectRoot)
491
+ url.searchParams.set("rigProjectRoot", server.serverProjectRoot);
492
+ return url.toString();
493
+ }
334
494
  async function submitTaskRunViaServer(context, input) {
335
495
  const isTaskRun = Boolean(input.taskId);
336
496
  const endpoint = isTaskRun ? "/api/runs/task" : "/api/runs/adhoc";
@@ -363,20 +523,37 @@ async function submitTaskRunViaServer(context, input) {
363
523
  return { runId };
364
524
  }
365
525
  export {
526
+ updateWorkspaceTaskViaServer,
366
527
  switchServerProjectRootViaServer,
367
528
  submitTaskRunViaServer,
368
529
  stopRunViaServer,
369
530
  steerRunViaServer,
370
531
  setGitHubBearerTokenForCurrentProcess,
532
+ sendRunPiShellViaServer,
533
+ sendRunPiPromptViaServer,
371
534
  selectNextWorkspaceTaskViaServer,
535
+ runRunPiCommandViaServer,
536
+ respondRunPiExtensionUiViaServer,
372
537
  requestServerJson,
373
538
  registerProjectViaServer,
374
539
  prepareRemoteCheckoutViaServer,
375
540
  postGitHubTokenViaServer,
376
541
  listWorkspaceTasksViaServer,
542
+ listRunsViaServer,
543
+ listGitHubProjectsViaServer,
377
544
  getWorkspaceTaskViaServer,
545
+ getRunTimelineViaServer,
546
+ getRunPiStatusViaServer,
547
+ getRunPiSessionViaServer,
548
+ getRunPiMessagesViaServer,
549
+ getRunPiCommandsViaServer,
550
+ getRunPiCapabilitiesViaServer,
378
551
  getRunLogsViaServer,
379
552
  getRunDetailsViaServer,
553
+ getGitHubProjectStatusFieldViaServer,
380
554
  getGitHubAuthStatusViaServer,
381
- ensureServerForCli
555
+ ensureTaskLabelsViaServer,
556
+ ensureServerForCli,
557
+ buildRunPiEventsWebSocketUrl,
558
+ abortRunPiViaServer
382
559
  };
@@ -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,47 @@ 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;
139
+ if (privateSession)
140
+ return privateSession;
141
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
142
+ }
143
+ function readStoredGitHubAuthToken(projectRoot) {
144
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
145
+ if (!existsSync2(path))
146
+ return null;
147
+ try {
148
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
149
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
150
+ } catch {
151
+ return null;
134
152
  }
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;
153
+ }
154
+ function readLocalConnectionFallbackToken(projectRoot) {
155
+ return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
142
156
  }
143
157
  async function ensureServerForCli(projectRoot) {
144
158
  try {
145
159
  const selected = resolveSelectedConnection(projectRoot);
146
160
  if (selected?.connection.kind === "remote") {
161
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
162
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
147
163
  return {
148
164
  baseUrl: selected.connection.baseUrl,
149
- authToken: readGitHubBearerTokenForRemote(projectRoot),
150
- connectionKind: "remote"
165
+ authToken,
166
+ connectionKind: "remote",
167
+ serverProjectRoot
151
168
  };
152
169
  }
153
170
  const connection = await ensureLocalRigServerConnection(projectRoot);
154
171
  return {
155
172
  baseUrl: connection.baseUrl,
156
- authToken: connection.authToken,
157
- connectionKind: "local"
173
+ authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
174
+ connectionKind: "local",
175
+ serverProjectRoot: resolve2(projectRoot)
158
176
  };
159
177
  } catch (error) {
160
178
  if (error instanceof Error) {
@@ -163,6 +181,29 @@ async function ensureServerForCli(projectRoot) {
163
181
  throw error;
164
182
  }
165
183
  }
184
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
185
+ const repo = readRepoConnection(projectRoot);
186
+ const slug = repo?.project?.trim();
187
+ if (!slug)
188
+ return null;
189
+ try {
190
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
191
+ headers: mergeHeaders(undefined, authToken)
192
+ });
193
+ if (!response.ok)
194
+ return null;
195
+ const payload = await response.json();
196
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
197
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
198
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
199
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
200
+ if (path)
201
+ writeRepoServerProjectRoot(projectRoot, path);
202
+ return path;
203
+ } catch {
204
+ return null;
205
+ }
206
+ }
166
207
  function mergeHeaders(headers, authToken) {
167
208
  const merged = new Headers(headers);
168
209
  if (authToken) {
@@ -187,9 +228,12 @@ function diagnosticMessage(payload) {
187
228
  }
188
229
  async function requestServerJson(context, pathname, init = {}) {
189
230
  const server = await ensureServerForCli(context.projectRoot);
231
+ const headers = mergeHeaders(init.headers, server.authToken);
232
+ if (server.serverProjectRoot)
233
+ headers.set("x-rig-project-root", server.serverProjectRoot);
190
234
  const response = await fetch(`${server.baseUrl}${pathname}`, {
191
235
  ...init,
192
- headers: mergeHeaders(init.headers, server.authToken)
236
+ headers
193
237
  });
194
238
  const text = await response.text();
195
239
  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
+ };