@h-rig/cli 0.0.6-alpha.9 → 0.0.6-alpha.90

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 (114) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/build-rig-binaries.d.ts +2 -0
  3. package/dist/bin/rig.d.ts +2 -0
  4. package/dist/bin/rig.js +11052 -6668
  5. package/dist/src/app/board.d.ts +23 -0
  6. package/dist/src/app/board.js +1786 -0
  7. package/dist/src/app/drone-ui.d.ts +37 -0
  8. package/dist/src/app/drone-ui.js +294 -0
  9. package/dist/src/app/theme.d.ts +47 -0
  10. package/dist/src/app/theme.js +150 -0
  11. package/dist/src/commands/_async-ui.d.ts +13 -0
  12. package/dist/src/commands/_async-ui.js +223 -0
  13. package/dist/src/commands/_authority-runs.d.ts +22 -0
  14. package/dist/src/commands/_authority-runs.js +2 -3
  15. package/dist/src/commands/_cli-format.d.ts +49 -0
  16. package/dist/src/commands/_cli-format.js +447 -0
  17. package/dist/src/commands/_connection-state.d.ts +44 -0
  18. package/dist/src/commands/_connection-state.js +30 -11
  19. package/dist/src/commands/_doctor-checks.d.ts +52 -0
  20. package/dist/src/commands/_doctor-checks.js +180 -44
  21. package/dist/src/commands/_help-catalog.d.ts +51 -0
  22. package/dist/src/commands/_help-catalog.js +442 -0
  23. package/dist/src/commands/_json-output.d.ts +11 -0
  24. package/dist/src/commands/_json-output.js +56 -0
  25. package/dist/src/commands/_operator-surface.d.ts +34 -0
  26. package/dist/src/commands/_operator-surface.js +220 -0
  27. package/dist/src/commands/_operator-view.d.ts +30 -0
  28. package/dist/src/commands/_operator-view.js +803 -73
  29. package/dist/src/commands/_parsers.d.ts +15 -0
  30. package/dist/src/commands/_parsers.js +18 -11
  31. package/dist/src/commands/_paths.d.ts +11 -0
  32. package/dist/src/commands/_pi-frontend.d.ts +27 -0
  33. package/dist/src/commands/_pi-frontend.js +742 -0
  34. package/dist/src/commands/_pi-install.d.ts +42 -0
  35. package/dist/src/commands/_pi-install.js +5 -4
  36. package/dist/src/commands/_policy.d.ts +8 -0
  37. package/dist/src/commands/_policy.js +12 -5
  38. package/dist/src/commands/_preflight.d.ts +22 -0
  39. package/dist/src/commands/_preflight.js +190 -128
  40. package/dist/src/commands/_probes.d.ts +1 -0
  41. package/dist/src/commands/_run-driver-helpers.d.ts +99 -0
  42. package/dist/src/commands/_run-driver-helpers.js +75 -22
  43. package/dist/src/commands/_run-replay.d.ts +24 -0
  44. package/dist/src/commands/_run-replay.js +142 -0
  45. package/dist/src/commands/_server-client.d.ts +186 -0
  46. package/dist/src/commands/_server-client.js +360 -61
  47. package/dist/src/commands/_snapshot-upload.d.ts +39 -0
  48. package/dist/src/commands/_snapshot-upload.js +163 -39
  49. package/dist/src/commands/_spinner.d.ts +25 -0
  50. package/dist/src/commands/_spinner.js +65 -0
  51. package/dist/src/commands/_task-picker.d.ts +9 -0
  52. package/dist/src/commands/_task-picker.js +172 -19
  53. package/dist/src/commands/agent.d.ts +3 -0
  54. package/dist/src/commands/agent.js +39 -20
  55. package/dist/src/commands/browser.d.ts +65 -0
  56. package/dist/src/commands/browser.js +334 -51
  57. package/dist/src/commands/connect.d.ts +7 -0
  58. package/dist/src/commands/connect.js +272 -33
  59. package/dist/src/commands/dist.d.ts +28 -0
  60. package/dist/src/commands/dist.js +19 -12
  61. package/dist/src/commands/doctor.d.ts +3 -0
  62. package/dist/src/commands/doctor.js +378 -45
  63. package/dist/src/commands/github.d.ts +3 -0
  64. package/dist/src/commands/github.js +375 -53
  65. package/dist/src/commands/inbox.d.ts +30 -0
  66. package/dist/src/commands/inbox.js +760 -70
  67. package/dist/src/commands/init.d.ts +74 -0
  68. package/dist/src/commands/init.js +963 -119
  69. package/dist/src/commands/inspect.d.ts +3 -0
  70. package/dist/src/commands/inspect.js +588 -32
  71. package/dist/src/commands/inspector.d.ts +3 -0
  72. package/dist/src/commands/inspector.js +20 -13
  73. package/dist/src/commands/pi.d.ts +3 -0
  74. package/dist/src/commands/pi.js +177 -0
  75. package/dist/src/commands/plugin.d.ts +16 -0
  76. package/dist/src/commands/plugin.js +95 -27
  77. package/dist/src/commands/profile-and-review.d.ts +4 -0
  78. package/dist/src/commands/profile-and-review.js +26 -19
  79. package/dist/src/commands/queue.d.ts +3 -0
  80. package/dist/src/commands/queue.js +32 -12
  81. package/dist/src/commands/remote.d.ts +3 -0
  82. package/dist/src/commands/remote.js +43 -36
  83. package/dist/src/commands/repo-git-harness.d.ts +5 -0
  84. package/dist/src/commands/repo-git-harness.js +22 -15
  85. package/dist/src/commands/run.d.ts +3 -0
  86. package/dist/src/commands/run.js +1379 -159
  87. package/dist/src/commands/server.d.ts +7 -0
  88. package/dist/src/commands/server.js +502 -57
  89. package/dist/src/commands/setup.d.ts +16 -0
  90. package/dist/src/commands/setup.js +390 -63
  91. package/dist/src/commands/stats.d.ts +12 -0
  92. package/dist/src/commands/stats.js +1051 -0
  93. package/dist/src/commands/task-report-bug.d.ts +19 -0
  94. package/dist/src/commands/task-report-bug.js +260 -62
  95. package/dist/src/commands/task-run-driver.d.ts +132 -0
  96. package/dist/src/commands/task-run-driver.js +886 -130
  97. package/dist/src/commands/task.d.ts +14 -0
  98. package/dist/src/commands/task.js +1644 -314
  99. package/dist/src/commands/test.d.ts +3 -0
  100. package/dist/src/commands/test.js +15 -8
  101. package/dist/src/commands/workspace.d.ts +3 -0
  102. package/dist/src/commands/workspace.js +18 -11
  103. package/dist/src/commands.d.ts +29 -0
  104. package/dist/src/commands.js +10967 -6637
  105. package/dist/src/index.d.ts +4 -0
  106. package/dist/src/index.js +10168 -5787
  107. package/dist/src/launcher.d.ts +61 -0
  108. package/dist/src/launcher.js +77 -13
  109. package/dist/src/report-bug.d.ts +44 -0
  110. package/dist/src/report-bug.js +3 -3
  111. package/dist/src/runner.d.ts +47 -0
  112. package/dist/src/runner.js +16 -22
  113. package/dist/src/withMutedConsole.d.ts +2 -0
  114. package/package.json +13 -6
@@ -1,17 +1,23 @@
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
 
7
6
  // packages/cli/src/runner.ts
8
7
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
9
- import { CliError } from "@rig/runtime/control-plane/errors";
8
+ import { CliError as RuntimeCliError } 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
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
11
+
12
+ class CliError extends RuntimeCliError {
13
+ hint;
14
+ constructor(message, exitCode = 1, options = {}) {
15
+ super(message, exitCode);
16
+ if (options.hint?.trim()) {
17
+ this.hint = options.hint.trim();
18
+ }
19
+ }
20
+ }
15
21
 
16
22
  // packages/cli/src/commands/_server-client.ts
17
23
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
@@ -38,9 +44,14 @@ function readJsonFile(path) {
38
44
  try {
39
45
  return JSON.parse(readFileSync(path, "utf8"));
40
46
  } catch (error) {
41
- throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
47
+ throw new CliError(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1, { hint: "Fix or delete that file, then re-select a server with `rig server use <alias|local>`." });
42
48
  }
43
49
  }
50
+ function writeJsonFile(path, value) {
51
+ mkdirSync(dirname(path), { recursive: true });
52
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
53
+ `, "utf8");
54
+ }
44
55
  function normalizeConnection(value) {
45
56
  if (!value || typeof value !== "object" || Array.isArray(value))
46
57
  return null;
@@ -81,31 +92,51 @@ function readRepoConnection(projectRoot) {
81
92
  return {
82
93
  selected,
83
94
  project: typeof record.project === "string" ? record.project : undefined,
84
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
95
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
96
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
85
97
  };
86
98
  }
99
+ function writeRepoConnection(projectRoot, state) {
100
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
101
+ }
87
102
  function resolveSelectedConnection(projectRoot, options = {}) {
88
103
  const repo = readRepoConnection(projectRoot);
89
104
  if (!repo)
90
105
  return null;
91
106
  if (repo.selected === "local")
92
- return { alias: "local", connection: { kind: "local", mode: "auto" } };
107
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
93
108
  const global = readGlobalConnections(options);
94
109
  const connection = global.connections[repo.selected];
95
110
  if (!connection) {
96
- throw new CliError2(`Selected Rig connection "${repo.selected}" was not found. Run \`rig connect list\` or \`rig connect use local\`.`, 1);
111
+ throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
97
112
  }
98
- return { alias: repo.selected, connection };
113
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
114
+ }
115
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
116
+ const repo = readRepoConnection(projectRoot);
117
+ if (!repo)
118
+ return;
119
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
99
120
  }
100
121
 
101
122
  // packages/cli/src/commands/_server-client.ts
102
- var cachedGitHubBearerToken;
123
+ var scopedGitHubBearerTokens = new Map;
124
+ var serverPhaseListener = null;
125
+ function setServerPhaseListener(listener) {
126
+ const previous = serverPhaseListener;
127
+ serverPhaseListener = listener;
128
+ return previous;
129
+ }
130
+ function reportServerPhase(label) {
131
+ serverPhaseListener?.(label);
132
+ }
103
133
  function cleanToken(value) {
104
134
  const trimmed = value?.trim();
105
135
  return trimmed ? trimmed : null;
106
136
  }
107
- function setGitHubBearerTokenForCurrentProcess(token) {
108
- cachedGitHubBearerToken = cleanToken(token ?? undefined);
137
+ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
138
+ const scopedKey = resolve2(projectRoot ?? process.cwd());
139
+ scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
109
140
  }
110
141
  function readPrivateRemoteSessionToken(projectRoot) {
111
142
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
@@ -119,49 +150,80 @@ function readPrivateRemoteSessionToken(projectRoot) {
119
150
  }
120
151
  }
121
152
  function readGitHubBearerTokenForRemote(projectRoot) {
122
- if (cachedGitHubBearerToken !== undefined)
123
- return cachedGitHubBearerToken;
153
+ const scopedKey = resolve2(projectRoot);
154
+ if (scopedGitHubBearerTokens.has(scopedKey))
155
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
124
156
  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;
157
+ if (privateSession)
158
+ return privateSession;
159
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
160
+ }
161
+ function readStoredGitHubAuthToken(projectRoot) {
162
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
163
+ if (!existsSync2(path))
164
+ return null;
165
+ try {
166
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
167
+ return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
168
+ } catch {
169
+ return null;
133
170
  }
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;
171
+ }
172
+ function readLocalConnectionFallbackToken(projectRoot) {
173
+ return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
141
174
  }
142
175
  async function ensureServerForCli(projectRoot) {
143
176
  try {
144
177
  const selected = resolveSelectedConnection(projectRoot);
145
178
  if (selected?.connection.kind === "remote") {
179
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
180
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
181
+ const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
146
182
  return {
147
183
  baseUrl: selected.connection.baseUrl,
148
- authToken: readGitHubBearerTokenForRemote(projectRoot),
149
- connectionKind: "remote"
184
+ authToken,
185
+ connectionKind: "remote",
186
+ serverProjectRoot
150
187
  };
151
188
  }
189
+ reportServerPhase("Starting local Rig server\u2026");
152
190
  const connection = await ensureLocalRigServerConnection(projectRoot);
153
191
  return {
154
192
  baseUrl: connection.baseUrl,
155
- authToken: connection.authToken,
156
- connectionKind: "local"
193
+ authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
194
+ connectionKind: "local",
195
+ serverProjectRoot: resolve2(projectRoot)
157
196
  };
158
197
  } catch (error) {
159
198
  if (error instanceof Error) {
160
- throw new CliError2(error.message, 1);
199
+ throw new CliError(error.message, 1);
161
200
  }
162
201
  throw error;
163
202
  }
164
203
  }
204
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
205
+ const repo = readRepoConnection(projectRoot);
206
+ const slug = repo?.project?.trim();
207
+ if (!slug)
208
+ return null;
209
+ try {
210
+ const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
211
+ headers: mergeHeaders(undefined, authToken)
212
+ });
213
+ if (!response.ok)
214
+ return null;
215
+ const payload = await response.json();
216
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
217
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
218
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
219
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
220
+ if (path)
221
+ writeRepoServerProjectRoot(projectRoot, path);
222
+ return path;
223
+ } catch {
224
+ return null;
225
+ }
226
+ }
165
227
  function appendTaskFilterParams(url, filters) {
166
228
  if (filters.assignee)
167
229
  url.searchParams.set("assignee", filters.assignee);
@@ -194,12 +256,65 @@ function diagnosticMessage(payload) {
194
256
  });
195
257
  return messages.length > 0 ? messages.join("; ") : null;
196
258
  }
259
+ var serverReachabilityCache = new Map;
260
+ async function probeServerReachability(baseUrl, authToken) {
261
+ try {
262
+ const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
263
+ headers: mergeHeaders(undefined, authToken),
264
+ signal: AbortSignal.timeout(1500)
265
+ });
266
+ return response.ok;
267
+ } catch {
268
+ return false;
269
+ }
270
+ }
271
+ function cachedServerReachability(projectRoot, baseUrl, authToken) {
272
+ const key = resolve2(projectRoot);
273
+ const cached = serverReachabilityCache.get(key);
274
+ if (cached)
275
+ return cached;
276
+ const probe = probeServerReachability(baseUrl, authToken);
277
+ serverReachabilityCache.set(key, probe);
278
+ return probe;
279
+ }
280
+ function describeSelectedServer(projectRoot, server) {
281
+ try {
282
+ const selected = resolveSelectedConnection(projectRoot);
283
+ if (selected) {
284
+ return {
285
+ alias: selected.alias,
286
+ target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
287
+ };
288
+ }
289
+ } catch {}
290
+ return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
291
+ }
292
+ async function buildServerFailureContext(projectRoot, server) {
293
+ const { alias, target } = describeSelectedServer(projectRoot, server);
294
+ const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
295
+ const reachability = reachable ? "server is reachable" : "server is unreachable";
296
+ return {
297
+ contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
298
+ hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
299
+ };
300
+ }
197
301
  async function requestServerJson(context, pathname, init = {}) {
198
302
  const server = await ensureServerForCli(context.projectRoot);
199
- const response = await fetch(`${server.baseUrl}${pathname}`, {
200
- ...init,
201
- headers: mergeHeaders(init.headers, server.authToken)
202
- });
303
+ const headers = mergeHeaders(init.headers, server.authToken);
304
+ if (server.serverProjectRoot)
305
+ headers.set("x-rig-project-root", server.serverProjectRoot);
306
+ reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
307
+ let response;
308
+ try {
309
+ response = await fetch(`${server.baseUrl}${pathname}`, {
310
+ ...init,
311
+ headers
312
+ });
313
+ } catch (error) {
314
+ const failure = await buildServerFailureContext(context.projectRoot, server);
315
+ throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
316
+ ${failure.contextLine}`, 1, { hint: failure.hint });
317
+ }
203
318
  const text = await response.text();
204
319
  const payload = text.trim().length > 0 ? (() => {
205
320
  try {
@@ -210,8 +325,12 @@ async function requestServerJson(context, pathname, init = {}) {
210
325
  })() : null;
211
326
  if (!response.ok) {
212
327
  const diagnostics = diagnosticMessage(payload);
213
- const detail = diagnostics ?? (text || response.statusText);
214
- throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
328
+ const rawDetail = diagnostics ?? (text || response.statusText);
329
+ const detail = diagnostics ? rawDetail : rawDetail.split(`
330
+ `).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 200) ?? response.statusText;
331
+ const failure = await buildServerFailureContext(context.projectRoot, server);
332
+ throw new CliError(`Rig server request failed (${response.status}): ${detail}
333
+ ${failure.contextLine}`, 1, { hint: failure.hint });
215
334
  }
216
335
  return payload;
217
336
  }
@@ -220,7 +339,7 @@ async function listWorkspaceTasksViaServer(context, filters = {}) {
220
339
  appendTaskFilterParams(url, filters);
221
340
  const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
222
341
  if (!Array.isArray(payload)) {
223
- throw new CliError2("Rig server returned an invalid task list payload.", 1);
342
+ throw new CliError("Rig server returned an invalid task list payload.", 1, { hint: "Check the selected server with `rig server status`; mixed CLI/server versions can mismatch \u2014 try `rig doctor`." });
224
343
  }
225
344
  return payload.flatMap((entry) => entry && typeof entry === "object" && !Array.isArray(entry) ? [entry] : []);
226
345
  }
@@ -236,7 +355,7 @@ async function selectNextWorkspaceTaskViaServer(context, filters = {}) {
236
355
  appendTaskFilterParams(url, filters);
237
356
  const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
238
357
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
239
- throw new CliError2("Rig server returned an invalid next-task payload.", 1);
358
+ throw new CliError("Rig server returned an invalid next-task payload.", 1, { hint: "Check the selected server with `rig server status`; try `rig task list` to see the raw task set." });
240
359
  }
241
360
  const record = payload;
242
361
  const rawTask = record.task;
@@ -275,32 +394,50 @@ async function registerProjectViaServer(context, input) {
275
394
  function sleep(ms) {
276
395
  return new Promise((resolve3) => setTimeout(resolve3, ms));
277
396
  }
397
+ function isRetryableProjectRootSwitchError(error) {
398
+ if (!(error instanceof Error))
399
+ return false;
400
+ const message = error.message.toLowerCase();
401
+ 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");
402
+ }
278
403
  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
404
  const timeoutMs = options.timeoutMs ?? 30000;
285
405
  const pollMs = options.pollMs ?? 1000;
286
406
  const deadline = Date.now() + timeoutMs;
287
407
  let lastError;
408
+ let switched = null;
288
409
  while (Date.now() < deadline) {
289
410
  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
- }
411
+ switched = await requestServerJson(context, "/api/server/project-root", {
412
+ method: "POST",
413
+ headers: { "content-type": "application/json" },
414
+ body: JSON.stringify({ projectRoot })
415
+ });
416
+ break;
298
417
  } catch (error) {
299
418
  lastError = error;
419
+ if (!isRetryableProjectRootSwitchError(error))
420
+ throw error;
421
+ await sleep(pollMs);
300
422
  }
301
- await sleep(pollMs);
302
423
  }
303
- throw new CliError2(`Rig server did not switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no status")}).`, 1);
424
+ if (!switched) {
425
+ throw new CliError(`Rig server did not accept project-root switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no response")}).`, 1);
426
+ }
427
+ const record = switched && typeof switched === "object" && !Array.isArray(switched) ? switched : {};
428
+ if (record.ok === true) {
429
+ writeRepoServerProjectRoot(context.projectRoot, projectRoot);
430
+ return { ok: true, switched: record };
431
+ }
432
+ throw new CliError(`Rig server rejected project-root scope ${projectRoot}: ${String(record.message ?? record.error ?? "unknown error")}`, 1);
433
+ }
434
+ async function listRunsViaServer(context, options = {}) {
435
+ const url = new URL("http://rig.local/api/runs");
436
+ if (options.limit !== undefined)
437
+ url.searchParams.set("limit", String(options.limit));
438
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
439
+ const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
440
+ return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
304
441
  }
305
442
  async function getRunDetailsViaServer(context, runId) {
306
443
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}`);
@@ -315,6 +452,82 @@ async function getRunLogsViaServer(context, runId, options = {}) {
315
452
  const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
316
453
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
317
454
  }
455
+ async function getRunTimelineViaServer(context, runId, options = {}) {
456
+ const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
457
+ if (options.limit !== undefined)
458
+ url.searchParams.set("limit", String(options.limit));
459
+ if (options.cursor)
460
+ url.searchParams.set("cursor", options.cursor);
461
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
462
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
463
+ }
464
+ async function ensureTaskLabelsViaServer(context) {
465
+ const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
466
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
467
+ }
468
+ async function listGitHubProjectsViaServer(context, owner) {
469
+ const url = new URL("http://rig.local/api/github/projects");
470
+ url.searchParams.set("owner", owner);
471
+ const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
472
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
473
+ }
474
+ async function getGitHubProjectStatusFieldViaServer(context, projectId) {
475
+ const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
476
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
477
+ }
478
+ async function updateWorkspaceTaskViaServer(context, input) {
479
+ const payload = await requestServerJson(context, "/api/tasks/update", {
480
+ method: "POST",
481
+ headers: { "content-type": "application/json" },
482
+ body: JSON.stringify(input)
483
+ });
484
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
485
+ }
486
+ var RESUMABLE_RUN_STATUSES = new Set([
487
+ "created",
488
+ "preparing",
489
+ "running",
490
+ "validating",
491
+ "reviewing",
492
+ "stopped",
493
+ "failed",
494
+ "needs-attention",
495
+ "needs_attention"
496
+ ]);
497
+ async function resumeRunViaServer(context, runId, options) {
498
+ let targetRunId = runId?.trim() || null;
499
+ if (!targetRunId) {
500
+ const candidates = (await listRunsViaServer(context)).filter((run) => RESUMABLE_RUN_STATUSES.has(String(run.status ?? ""))).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")));
501
+ targetRunId = typeof candidates[0]?.runId === "string" ? candidates[0].runId : null;
502
+ }
503
+ if (!targetRunId) {
504
+ throw new CliError(options.restart ? "No run is available to restart." : "No resumable run is available.", 2, { hint: "List runs with `rig run list`, then pass an explicit id: `rig run resume <run-id>`." });
505
+ }
506
+ const payload = await requestServerJson(context, "/api/runs/resume", {
507
+ method: "POST",
508
+ headers: { "content-type": "application/json" },
509
+ body: JSON.stringify({ runId: targetRunId, createdAt: new Date().toISOString(), restart: options.restart })
510
+ });
511
+ const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
512
+ if (record.ok === false) {
513
+ const message = typeof record.error === "string" && record.error.trim() ? record.error : "run resume failed";
514
+ throw new CliError(`${options.restart ? "restart" : "resume"} failed for ${targetRunId}: ${message}`, 1);
515
+ }
516
+ return { ok: true, runId: targetRunId, ...record };
517
+ }
518
+ async function resolveServerConnectionLabel(projectRoot) {
519
+ try {
520
+ const selected = resolveSelectedConnection(projectRoot);
521
+ if (!selected)
522
+ return "no server selected";
523
+ if (selected.connection.kind === "remote") {
524
+ return selected.connection.baseUrl.replace(/^https?:\/\//, "");
525
+ }
526
+ return `local (${selected.alias})`;
527
+ } catch {
528
+ return "no server selected";
529
+ }
530
+ }
318
531
  async function stopRunViaServer(context, runId) {
319
532
  const payload = await requestServerJson(context, "/api/runs/stop", {
320
533
  method: "POST",
@@ -331,6 +544,72 @@ async function steerRunViaServer(context, runId, message) {
331
544
  });
332
545
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
333
546
  }
547
+ async function getRunPiSessionViaServer(context, runId) {
548
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi`);
549
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
550
+ }
551
+ async function getRunPiMessagesViaServer(context, runId) {
552
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/messages`);
553
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { messages: [] };
554
+ }
555
+ async function getRunPiStatusViaServer(context, runId) {
556
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/status`);
557
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
558
+ }
559
+ async function getRunPiCommandsViaServer(context, runId) {
560
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
561
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
562
+ }
563
+ async function getRunPiCapabilitiesViaServer(context, runId) {
564
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
565
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
566
+ }
567
+ async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
568
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
569
+ method: "POST",
570
+ headers: { "content-type": "application/json" },
571
+ body: JSON.stringify({ text, streamingBehavior })
572
+ });
573
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
574
+ }
575
+ async function sendRunPiShellViaServer(context, runId, text) {
576
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/shell`, {
577
+ method: "POST",
578
+ headers: { "content-type": "application/json" },
579
+ body: JSON.stringify({ text })
580
+ });
581
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
582
+ }
583
+ async function runRunPiCommandViaServer(context, runId, text) {
584
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands/run`, {
585
+ method: "POST",
586
+ headers: { "content-type": "application/json" },
587
+ body: JSON.stringify({ text })
588
+ });
589
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { type: "done" };
590
+ }
591
+ async function respondRunPiExtensionUiViaServer(context, runId, requestId, valueOrCancel) {
592
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/extension-ui/respond`, {
593
+ method: "POST",
594
+ headers: { "content-type": "application/json" },
595
+ body: JSON.stringify({ requestId, ...valueOrCancel })
596
+ });
597
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
598
+ }
599
+ async function abortRunPiViaServer(context, runId) {
600
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/abort`, { method: "POST" });
601
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { aborted: true };
602
+ }
603
+ async function buildRunPiEventsWebSocketUrl(context, runId) {
604
+ const server = await ensureServerForCli(context.projectRoot);
605
+ const url = new URL(`${server.baseUrl.replace(/\/+$/, "")}/api/runs/${encodeURIComponent(runId)}/pi/events`);
606
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
607
+ if (server.authToken)
608
+ url.searchParams.set("token", server.authToken);
609
+ if (server.serverProjectRoot)
610
+ url.searchParams.set("rigProjectRoot", server.serverProjectRoot);
611
+ return url.toString();
612
+ }
334
613
  async function submitTaskRunViaServer(context, input) {
335
614
  const isTaskRun = Boolean(input.taskId);
336
615
  const endpoint = isTaskRun ? "/api/runs/task" : "/api/runs/adhoc";
@@ -354,29 +633,49 @@ async function submitTaskRunViaServer(context, input) {
354
633
  })
355
634
  });
356
635
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
357
- throw new CliError2("Rig server returned an invalid run submission payload.", 1);
636
+ throw new CliError("Rig server returned an invalid run submission payload.", 1, { hint: "Check `rig server status` and retry; `rig run list` shows whether the run was created anyway." });
358
637
  }
359
638
  const runId = payload.runId;
360
639
  if (typeof runId !== "string" || runId.trim().length === 0) {
361
- throw new CliError2("Rig server returned no runId for the submitted run.", 1);
640
+ throw new CliError("Rig server returned no runId for the submitted run.", 1, { hint: "Check `rig run list` \u2014 the run may still have been created; otherwise retry the submission." });
362
641
  }
363
642
  return { runId };
364
643
  }
365
644
  export {
645
+ updateWorkspaceTaskViaServer,
366
646
  switchServerProjectRootViaServer,
367
647
  submitTaskRunViaServer,
368
648
  stopRunViaServer,
369
649
  steerRunViaServer,
650
+ setServerPhaseListener,
370
651
  setGitHubBearerTokenForCurrentProcess,
652
+ sendRunPiShellViaServer,
653
+ sendRunPiPromptViaServer,
371
654
  selectNextWorkspaceTaskViaServer,
655
+ runRunPiCommandViaServer,
656
+ resumeRunViaServer,
657
+ respondRunPiExtensionUiViaServer,
658
+ resolveServerConnectionLabel,
372
659
  requestServerJson,
373
660
  registerProjectViaServer,
374
661
  prepareRemoteCheckoutViaServer,
375
662
  postGitHubTokenViaServer,
376
663
  listWorkspaceTasksViaServer,
664
+ listRunsViaServer,
665
+ listGitHubProjectsViaServer,
377
666
  getWorkspaceTaskViaServer,
667
+ getRunTimelineViaServer,
668
+ getRunPiStatusViaServer,
669
+ getRunPiSessionViaServer,
670
+ getRunPiMessagesViaServer,
671
+ getRunPiCommandsViaServer,
672
+ getRunPiCapabilitiesViaServer,
378
673
  getRunLogsViaServer,
379
674
  getRunDetailsViaServer,
675
+ getGitHubProjectStatusFieldViaServer,
380
676
  getGitHubAuthStatusViaServer,
381
- ensureServerForCli
677
+ ensureTaskLabelsViaServer,
678
+ ensureServerForCli,
679
+ buildRunPiEventsWebSocketUrl,
680
+ abortRunPiViaServer
382
681
  };
@@ -0,0 +1,39 @@
1
+ export declare const UPLOADED_SNAPSHOT_PR_MARKER = "<!-- rig:uploaded-snapshot -->";
2
+ export declare const SNAPSHOT_ARCHIVE_VERSION = 1;
3
+ export declare const SNAPSHOT_ARCHIVE_CONTENT_TYPE = "application/vnd.rig.snapshot+json";
4
+ export type SnapshotUploadManifest = {
5
+ readonly root: string;
6
+ readonly files: readonly string[];
7
+ readonly excludedDirectories: readonly string[];
8
+ };
9
+ export type SnapshotUploadArchiveFile = {
10
+ readonly path: string;
11
+ readonly contentBase64: string;
12
+ };
13
+ export type SnapshotUploadArchive = {
14
+ readonly version: typeof SNAPSHOT_ARCHIVE_VERSION;
15
+ readonly root: string;
16
+ readonly files: readonly SnapshotUploadArchiveFile[];
17
+ readonly excludedDirectories: readonly string[];
18
+ readonly createdAt: string;
19
+ };
20
+ export declare function buildSnapshotUploadManifest(projectRoot: string, options?: {
21
+ readonly excludedDirectories?: readonly string[];
22
+ }): Promise<SnapshotUploadManifest>;
23
+ export declare function createSnapshotUploadArchive(projectRoot: string, options?: {
24
+ readonly excludedDirectories?: readonly string[];
25
+ readonly now?: () => Date;
26
+ }): Promise<SnapshotUploadArchive>;
27
+ export declare function encodeSnapshotUploadArchive(archive: SnapshotUploadArchive): string;
28
+ export declare function writeSnapshotUploadArchive(projectRoot: string, outputPath: string, options?: {
29
+ readonly excludedDirectories?: readonly string[];
30
+ readonly now?: () => Date;
31
+ }): Promise<SnapshotUploadArchive>;
32
+ export declare function uploadSnapshotArchiveViaServer(context: {
33
+ readonly projectRoot: string;
34
+ }, input: {
35
+ readonly repoSlug: string;
36
+ readonly archive: SnapshotUploadArchive;
37
+ readonly baseDir?: string;
38
+ }): Promise<Record<string, unknown>>;
39
+ export declare function appendUploadedSnapshotPrNotice(body: string): string;