@h-rig/cli 0.0.6-alpha.6 → 0.0.6-alpha.61
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.
- package/README.md +1 -1
- package/dist/bin/rig.js +5033 -1895
- package/dist/src/commands/_authority-runs.js +2 -3
- package/dist/src/commands/_cli-format.js +369 -0
- package/dist/src/commands/_connection-state.js +12 -6
- package/dist/src/commands/_doctor-checks.js +79 -34
- package/dist/src/commands/_help-catalog.js +446 -0
- package/dist/src/commands/_operator-surface.js +220 -0
- package/dist/src/commands/_operator-view.js +1124 -64
- package/dist/src/commands/_parsers.js +0 -2
- package/dist/src/commands/_pi-frontend.js +1080 -0
- package/dist/src/commands/_pi-install.js +4 -3
- package/dist/src/commands/_pi-remote-session.js +771 -0
- package/dist/src/commands/_pi-worker-bridge-extension.js +834 -0
- package/dist/src/commands/_policy.js +0 -2
- package/dist/src/commands/_preflight.js +98 -116
- package/dist/src/commands/_run-driver-helpers.js +46 -19
- package/dist/src/commands/_run-replay.js +142 -0
- package/dist/src/commands/_server-client.js +225 -48
- package/dist/src/commands/_snapshot-upload.js +74 -30
- package/dist/src/commands/_spinner.js +63 -0
- package/dist/src/commands/_task-picker.js +44 -16
- package/dist/src/commands/agent.js +10 -12
- package/dist/src/commands/browser.js +4 -6
- package/dist/src/commands/connect.js +134 -26
- package/dist/src/commands/dist.js +4 -6
- package/dist/src/commands/doctor.js +79 -34
- package/dist/src/commands/github.js +76 -32
- package/dist/src/commands/inbox.js +410 -31
- package/dist/src/commands/init.js +398 -90
- package/dist/src/commands/inspect.js +296 -23
- package/dist/src/commands/inspector.js +2 -4
- package/dist/src/commands/pi.js +168 -0
- package/dist/src/commands/plugin.js +81 -22
- package/dist/src/commands/profile-and-review.js +8 -10
- package/dist/src/commands/queue.js +2 -3
- package/dist/src/commands/remote.js +18 -20
- package/dist/src/commands/repo-git-harness.js +6 -8
- package/dist/src/commands/run.js +1600 -131
- package/dist/src/commands/server.js +281 -42
- package/dist/src/commands/setup.js +84 -45
- package/dist/src/commands/task-report-bug.js +5 -7
- package/dist/src/commands/task-run-driver.js +736 -93
- package/dist/src/commands/task.js +1863 -262
- package/dist/src/commands/test.js +3 -5
- package/dist/src/commands/workspace.js +4 -6
- package/dist/src/commands.js +5675 -2531
- package/dist/src/index.js +5654 -2519
- package/dist/src/launcher.js +5 -3
- package/dist/src/report-bug.js +3 -3
- package/dist/src/runner.js +5 -19
- 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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
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
|
|
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
|
+
};
|