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