@h-rig/cli 0.0.6-alpha.7 → 0.0.6-alpha.71
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 +4507 -1506
- package/dist/src/commands/_async-ui.js +152 -0
- 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 +30 -11
- package/dist/src/commands/_doctor-checks.js +177 -43
- package/dist/src/commands/_help-catalog.js +485 -0
- package/dist/src/commands/_json-output.js +56 -0
- package/dist/src/commands/_operator-surface.js +220 -0
- package/dist/src/commands/_operator-view.js +595 -72
- package/dist/src/commands/_parsers.js +18 -11
- package/dist/src/commands/_pi-frontend.js +411 -0
- package/dist/src/commands/_pi-install.js +4 -3
- package/dist/src/commands/_policy.js +12 -5
- package/dist/src/commands/_preflight.js +187 -127
- package/dist/src/commands/_run-driver-helpers.js +75 -22
- package/dist/src/commands/_run-replay.js +142 -0
- package/dist/src/commands/_server-client.js +343 -60
- package/dist/src/commands/_snapshot-upload.js +160 -38
- package/dist/src/commands/_spinner.js +65 -0
- package/dist/src/commands/_task-picker.js +44 -16
- package/dist/src/commands/agent.js +39 -20
- package/dist/src/commands/browser.js +28 -21
- package/dist/src/commands/connect.js +146 -33
- package/dist/src/commands/dist.js +19 -12
- package/dist/src/commands/doctor.js +304 -44
- package/dist/src/commands/github.js +301 -52
- package/dist/src/commands/inbox.js +679 -72
- package/dist/src/commands/init.js +622 -118
- package/dist/src/commands/inspect.js +515 -32
- package/dist/src/commands/inspector.js +20 -13
- package/dist/src/commands/pi.js +177 -0
- package/dist/src/commands/plugin.js +95 -27
- package/dist/src/commands/profile-and-review.js +26 -19
- package/dist/src/commands/queue.js +32 -12
- package/dist/src/commands/remote.js +43 -36
- package/dist/src/commands/repo-git-harness.js +22 -15
- package/dist/src/commands/run.js +1162 -158
- package/dist/src/commands/server.js +373 -56
- package/dist/src/commands/setup.js +316 -62
- package/dist/src/commands/stats.js +1030 -0
- package/dist/src/commands/task-report-bug.js +29 -22
- package/dist/src/commands/task-run-driver.js +862 -129
- package/dist/src/commands/task.js +1423 -311
- package/dist/src/commands/test.js +15 -8
- package/dist/src/commands/workspace.js +18 -11
- package/dist/src/commands.js +4446 -1499
- package/dist/src/index.js +4502 -1504
- package/dist/src/launcher.js +77 -13
- package/dist/src/report-bug.js +3 -3
- package/dist/src/runner.js +16 -22
- package/package.json +10 -5
|
@@ -7,12 +7,19 @@ import { resolve as resolve6 } from "path";
|
|
|
7
7
|
|
|
8
8
|
// packages/cli/src/runner.ts
|
|
9
9
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
10
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
10
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
11
11
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
12
|
-
import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
13
|
-
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
14
12
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
15
|
-
|
|
13
|
+
|
|
14
|
+
class CliError extends RuntimeCliError {
|
|
15
|
+
hint;
|
|
16
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
17
|
+
super(message, exitCode);
|
|
18
|
+
if (options.hint?.trim()) {
|
|
19
|
+
this.hint = options.hint.trim();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
16
23
|
function requireNoExtraArgs(args, usage) {
|
|
17
24
|
if (args.length > 0) {
|
|
18
25
|
throw new CliError(`Unexpected arguments: ${args.join(" ")}
|
|
@@ -102,7 +109,8 @@ function resolveControlPlaneDefinitionRoot(projectRoot) {
|
|
|
102
109
|
import { existsSync, readFileSync, rmSync } from "fs";
|
|
103
110
|
import { homedir } from "os";
|
|
104
111
|
import { resolve as resolve2 } from "path";
|
|
105
|
-
var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
112
|
+
var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig";
|
|
113
|
+
var LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
106
114
|
async function defaultCommandRunner(command, options = {}) {
|
|
107
115
|
const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
|
|
108
116
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
@@ -121,7 +129,7 @@ function resolvePiHomeDir(inputHomeDir) {
|
|
|
121
129
|
function piListContainsPiRig(output) {
|
|
122
130
|
return output.split(/\r?\n/).some((line) => {
|
|
123
131
|
const normalized = line.trim();
|
|
124
|
-
return normalized.includes(PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
132
|
+
return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
125
133
|
});
|
|
126
134
|
}
|
|
127
135
|
async function safeRun(runner, command, options) {
|
|
@@ -197,9 +205,14 @@ function readJsonFile(path) {
|
|
|
197
205
|
try {
|
|
198
206
|
return JSON.parse(readFileSync2(path, "utf8"));
|
|
199
207
|
} catch (error) {
|
|
200
|
-
throw new
|
|
208
|
+
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>`." });
|
|
201
209
|
}
|
|
202
210
|
}
|
|
211
|
+
function writeJsonFile(path, value) {
|
|
212
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
213
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
214
|
+
`, "utf8");
|
|
215
|
+
}
|
|
203
216
|
function normalizeConnection(value) {
|
|
204
217
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
205
218
|
return null;
|
|
@@ -240,29 +253,47 @@ function readRepoConnection(projectRoot) {
|
|
|
240
253
|
return {
|
|
241
254
|
selected,
|
|
242
255
|
project: typeof record.project === "string" ? record.project : undefined,
|
|
243
|
-
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
|
|
256
|
+
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
257
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
244
258
|
};
|
|
245
259
|
}
|
|
260
|
+
function writeRepoConnection(projectRoot, state) {
|
|
261
|
+
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
262
|
+
}
|
|
246
263
|
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
247
264
|
const repo = readRepoConnection(projectRoot);
|
|
248
265
|
if (!repo)
|
|
249
266
|
return null;
|
|
250
267
|
if (repo.selected === "local")
|
|
251
|
-
return { alias: "local", connection: { kind: "local", mode: "auto" } };
|
|
268
|
+
return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
|
|
252
269
|
const global = readGlobalConnections(options);
|
|
253
270
|
const connection = global.connections[repo.selected];
|
|
254
271
|
if (!connection) {
|
|
255
|
-
throw new
|
|
272
|
+
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
256
273
|
}
|
|
257
|
-
return { alias: repo.selected, connection };
|
|
274
|
+
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
275
|
+
}
|
|
276
|
+
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
277
|
+
const repo = readRepoConnection(projectRoot);
|
|
278
|
+
if (!repo)
|
|
279
|
+
return;
|
|
280
|
+
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
258
281
|
}
|
|
259
282
|
|
|
260
283
|
// packages/cli/src/commands/_server-client.ts
|
|
261
|
-
import { spawnSync } from "child_process";
|
|
262
284
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
263
285
|
import { resolve as resolve4 } from "path";
|
|
264
286
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
265
|
-
var
|
|
287
|
+
var scopedGitHubBearerTokens = new Map;
|
|
288
|
+
var serverPhaseListener = null;
|
|
289
|
+
function setServerPhaseListener(listener) {
|
|
290
|
+
const previous = serverPhaseListener;
|
|
291
|
+
serverPhaseListener = listener;
|
|
292
|
+
return previous;
|
|
293
|
+
}
|
|
294
|
+
function reportServerPhase(label) {
|
|
295
|
+
serverPhaseListener?.(label);
|
|
296
|
+
}
|
|
266
297
|
function cleanToken(value) {
|
|
267
298
|
const trimmed = value?.trim();
|
|
268
299
|
return trimmed ? trimmed : null;
|
|
@@ -279,49 +310,80 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
279
310
|
}
|
|
280
311
|
}
|
|
281
312
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
282
|
-
|
|
283
|
-
|
|
313
|
+
const scopedKey = resolve4(projectRoot);
|
|
314
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
315
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
284
316
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
285
|
-
if (privateSession)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
return
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
317
|
+
if (privateSession)
|
|
318
|
+
return privateSession;
|
|
319
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
320
|
+
}
|
|
321
|
+
function readStoredGitHubAuthToken(projectRoot) {
|
|
322
|
+
const path = resolve4(projectRoot, ".rig", "state", "github-auth.json");
|
|
323
|
+
if (!existsSync3(path))
|
|
324
|
+
return null;
|
|
325
|
+
try {
|
|
326
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
327
|
+
return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
|
|
328
|
+
} catch {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function readLocalConnectionFallbackToken(projectRoot) {
|
|
333
|
+
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
301
334
|
}
|
|
302
335
|
async function ensureServerForCli(projectRoot) {
|
|
303
336
|
try {
|
|
304
337
|
const selected = resolveSelectedConnection(projectRoot);
|
|
305
338
|
if (selected?.connection.kind === "remote") {
|
|
339
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
340
|
+
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
341
|
+
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
306
342
|
return {
|
|
307
343
|
baseUrl: selected.connection.baseUrl,
|
|
308
|
-
authToken
|
|
309
|
-
connectionKind: "remote"
|
|
344
|
+
authToken,
|
|
345
|
+
connectionKind: "remote",
|
|
346
|
+
serverProjectRoot
|
|
310
347
|
};
|
|
311
348
|
}
|
|
349
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
312
350
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
313
351
|
return {
|
|
314
352
|
baseUrl: connection.baseUrl,
|
|
315
|
-
authToken: connection.authToken,
|
|
316
|
-
connectionKind: "local"
|
|
353
|
+
authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
|
|
354
|
+
connectionKind: "local",
|
|
355
|
+
serverProjectRoot: resolve4(projectRoot)
|
|
317
356
|
};
|
|
318
357
|
} catch (error) {
|
|
319
358
|
if (error instanceof Error) {
|
|
320
|
-
throw new
|
|
359
|
+
throw new CliError(error.message, 1);
|
|
321
360
|
}
|
|
322
361
|
throw error;
|
|
323
362
|
}
|
|
324
363
|
}
|
|
364
|
+
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
365
|
+
const repo = readRepoConnection(projectRoot);
|
|
366
|
+
const slug = repo?.project?.trim();
|
|
367
|
+
if (!slug)
|
|
368
|
+
return null;
|
|
369
|
+
try {
|
|
370
|
+
const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
|
|
371
|
+
headers: mergeHeaders(undefined, authToken)
|
|
372
|
+
});
|
|
373
|
+
if (!response.ok)
|
|
374
|
+
return null;
|
|
375
|
+
const payload = await response.json();
|
|
376
|
+
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
377
|
+
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
378
|
+
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
379
|
+
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
380
|
+
if (path)
|
|
381
|
+
writeRepoServerProjectRoot(projectRoot, path);
|
|
382
|
+
return path;
|
|
383
|
+
} catch {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
325
387
|
function mergeHeaders(headers, authToken) {
|
|
326
388
|
const merged = new Headers(headers);
|
|
327
389
|
if (authToken) {
|
|
@@ -344,12 +406,65 @@ function diagnosticMessage(payload) {
|
|
|
344
406
|
});
|
|
345
407
|
return messages.length > 0 ? messages.join("; ") : null;
|
|
346
408
|
}
|
|
409
|
+
var serverReachabilityCache = new Map;
|
|
410
|
+
async function probeServerReachability(baseUrl, authToken) {
|
|
411
|
+
try {
|
|
412
|
+
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
|
|
413
|
+
headers: mergeHeaders(undefined, authToken),
|
|
414
|
+
signal: AbortSignal.timeout(1500)
|
|
415
|
+
});
|
|
416
|
+
return response.ok;
|
|
417
|
+
} catch {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function cachedServerReachability(projectRoot, baseUrl, authToken) {
|
|
422
|
+
const key = resolve4(projectRoot);
|
|
423
|
+
const cached = serverReachabilityCache.get(key);
|
|
424
|
+
if (cached)
|
|
425
|
+
return cached;
|
|
426
|
+
const probe = probeServerReachability(baseUrl, authToken);
|
|
427
|
+
serverReachabilityCache.set(key, probe);
|
|
428
|
+
return probe;
|
|
429
|
+
}
|
|
430
|
+
function describeSelectedServer(projectRoot, server) {
|
|
431
|
+
try {
|
|
432
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
433
|
+
if (selected) {
|
|
434
|
+
return {
|
|
435
|
+
alias: selected.alias,
|
|
436
|
+
target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
} catch {}
|
|
440
|
+
return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
|
|
441
|
+
}
|
|
442
|
+
async function buildServerFailureContext(projectRoot, server) {
|
|
443
|
+
const { alias, target } = describeSelectedServer(projectRoot, server);
|
|
444
|
+
const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
|
|
445
|
+
const reachability = reachable ? "server is reachable" : "server is unreachable";
|
|
446
|
+
return {
|
|
447
|
+
contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
|
|
448
|
+
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
449
|
+
};
|
|
450
|
+
}
|
|
347
451
|
async function requestServerJson(context, pathname, init = {}) {
|
|
348
452
|
const server = await ensureServerForCli(context.projectRoot);
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
headers
|
|
352
|
-
});
|
|
453
|
+
const headers = mergeHeaders(init.headers, server.authToken);
|
|
454
|
+
if (server.serverProjectRoot)
|
|
455
|
+
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
456
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
457
|
+
let response;
|
|
458
|
+
try {
|
|
459
|
+
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
460
|
+
...init,
|
|
461
|
+
headers
|
|
462
|
+
});
|
|
463
|
+
} catch (error) {
|
|
464
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
465
|
+
throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
|
|
466
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
467
|
+
}
|
|
353
468
|
const text = await response.text();
|
|
354
469
|
const payload = text.trim().length > 0 ? (() => {
|
|
355
470
|
try {
|
|
@@ -361,10 +476,23 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
361
476
|
if (!response.ok) {
|
|
362
477
|
const diagnostics = diagnosticMessage(payload);
|
|
363
478
|
const detail = diagnostics ?? (text || response.statusText);
|
|
364
|
-
|
|
479
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
480
|
+
throw new CliError(`Rig server request failed (${response.status}): ${detail}
|
|
481
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
365
482
|
}
|
|
366
483
|
return payload;
|
|
367
484
|
}
|
|
485
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
486
|
+
"created",
|
|
487
|
+
"preparing",
|
|
488
|
+
"running",
|
|
489
|
+
"validating",
|
|
490
|
+
"reviewing",
|
|
491
|
+
"stopped",
|
|
492
|
+
"failed",
|
|
493
|
+
"needs-attention",
|
|
494
|
+
"needs_attention"
|
|
495
|
+
]);
|
|
368
496
|
|
|
369
497
|
// packages/cli/src/commands/_doctor-checks.ts
|
|
370
498
|
function check(id, label, status, detail, remediation) {
|
|
@@ -485,7 +613,10 @@ async function runRigDoctorChecks(options) {
|
|
|
485
613
|
const bunVersion = options.bunVersion ?? Bun.version;
|
|
486
614
|
const request = options.requestJson ?? ((pathname, init) => requestServerJson({ projectRoot }, pathname, init));
|
|
487
615
|
const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
|
|
616
|
+
const progress = options.onProgress ?? (() => {});
|
|
617
|
+
progress("Checking local toolchain\u2026");
|
|
488
618
|
checks.push(check("bun", `bun >= ${MIN_SUPPORTED_BUN_VERSION}`, isSupportedBunVersion(bunVersion) ? "pass" : "fail", `found ${bunVersion}`, `Install Bun ${MIN_SUPPORTED_BUN_VERSION} or newer.`), check("git", "git", which("git") ? "pass" : "fail", which("git") ?? undefined, "Install git and ensure it is on PATH."), check("jq", "jq", which("jq") ? "pass" : "warn", which("jq") ?? undefined, "Install jq (for example `brew install jq`)."));
|
|
619
|
+
progress("Loading rig.config\u2026");
|
|
489
620
|
const loadedConfig = await loadConfig(projectRoot).catch(() => null);
|
|
490
621
|
const config = loadedConfig ?? loadFallbackConfig(projectRoot);
|
|
491
622
|
const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve5(projectRoot, name)));
|
|
@@ -493,7 +624,7 @@ async function runRigDoctorChecks(options) {
|
|
|
493
624
|
const taskSourceKind = config?.taskSource?.kind;
|
|
494
625
|
checks.push(taskSourceKind ? check("task-source", "task source configured", "pass", taskSourceKind) : check("task-source", "task source configured", "fail", "missing taskSource", "Configure taskSource in rig.config.ts."));
|
|
495
626
|
const repo = readRepoConnection(projectRoot);
|
|
496
|
-
checks.push(repo ? check("project-link", "repo selected Rig
|
|
627
|
+
checks.push(repo ? check("project-link", "repo selected Rig server", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to link this checkout to a GitHub repo slug.") : check("project-link", "repo selected Rig server", "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
|
|
497
628
|
const selected = (() => {
|
|
498
629
|
try {
|
|
499
630
|
return resolveSelectedConnection(projectRoot);
|
|
@@ -501,9 +632,10 @@ async function runRigDoctorChecks(options) {
|
|
|
501
632
|
return null;
|
|
502
633
|
}
|
|
503
634
|
})();
|
|
504
|
-
checks.push(selected ? check("connection", "selected server connection", "pass", selected.connection.kind === "remote" ? selected.connection.baseUrl : "local auto") : check("connection", "selected server
|
|
635
|
+
checks.push(selected ? check("connection", "selected server connection", "pass", selected.connection.kind === "remote" ? selected.connection.baseUrl : "local auto") : check("connection", "selected server", repo ? "fail" : "warn", repo ? "selected alias is missing" : "will auto-start local server", repo ? "Run `rig server list` and `rig server use <alias|local>`." : undefined));
|
|
505
636
|
let server = null;
|
|
506
637
|
try {
|
|
638
|
+
progress("Connecting to the selected Rig server\u2026");
|
|
507
639
|
server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
|
|
508
640
|
checks.push(check("server", "Rig server reachable", "pass", `${server.connectionKind} ${server.baseUrl}`));
|
|
509
641
|
} catch (error) {
|
|
@@ -511,18 +643,21 @@ async function runRigDoctorChecks(options) {
|
|
|
511
643
|
}
|
|
512
644
|
if (server || options.requestJson) {
|
|
513
645
|
try {
|
|
646
|
+
progress("Checking server status\u2026");
|
|
514
647
|
const status = await request("/api/server/status");
|
|
515
648
|
checks.push(check("server-status", "server project status", "pass", JSON.stringify(status).slice(0, 180)));
|
|
516
649
|
} catch (error) {
|
|
517
650
|
checks.push(check("server-status", "server project status", "fail", errorMessage(error), "Run `rig doctor` after the selected server is reachable."));
|
|
518
651
|
}
|
|
519
652
|
try {
|
|
653
|
+
progress("Checking GitHub auth\u2026");
|
|
520
654
|
const auth = await request("/api/github/auth/status");
|
|
521
655
|
checks.push(isAuthenticated(auth) ? check("github-auth", "GitHub auth", "pass") : check("github-auth", "GitHub auth", "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
|
|
522
656
|
} catch (error) {
|
|
523
657
|
checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
|
|
524
658
|
}
|
|
525
659
|
try {
|
|
660
|
+
progress("Checking GitHub repo permissions\u2026");
|
|
526
661
|
const permissions = await request("/api/github/repo/permissions");
|
|
527
662
|
const allowed = permissionAllowsPr(permissions);
|
|
528
663
|
checks.push(allowed === true ? check("github-repo-permissions", "GitHub repo PR permissions", "pass", JSON.stringify(permissions).slice(0, 180)) : allowed === false ? check("github-repo-permissions", "GitHub repo PR permissions", "fail", JSON.stringify(permissions).slice(0, 180), "Grant the selected GitHub token permission to push branches, open PRs, and merge according to repo rules.") : check("github-repo-permissions", "GitHub repo PR permissions", "warn", JSON.stringify(permissions).slice(0, 180), "Confirm the selected token can push branches and open PRs."));
|
|
@@ -530,6 +665,7 @@ async function runRigDoctorChecks(options) {
|
|
|
530
665
|
checks.push(check("github-repo-permissions", "GitHub repo PR permissions", "warn", errorMessage(error), "Ensure the server exposes repo permission checks and the token can open PRs."));
|
|
531
666
|
}
|
|
532
667
|
try {
|
|
668
|
+
progress("Checking GitHub issue labels\u2026");
|
|
533
669
|
const labels = await request("/api/workspace/task-labels");
|
|
534
670
|
const ready = labelsReady(labels);
|
|
535
671
|
checks.push(ready === false ? check("task-labels", "GitHub issue labels", "fail", JSON.stringify(labels).slice(0, 180), "Let Rig create required labels or create the configured lifecycle labels manually.") : check("task-labels", "GitHub issue labels", ready === true ? "pass" : "warn", JSON.stringify(labels).slice(0, 180), "Confirm required Rig lifecycle labels exist."));
|
|
@@ -537,6 +673,7 @@ async function runRigDoctorChecks(options) {
|
|
|
537
673
|
checks.push(check("task-labels", "GitHub issue labels", "warn", errorMessage(error), "Run `rig init`/`rig doctor` after label setup is wired on the server."));
|
|
538
674
|
}
|
|
539
675
|
try {
|
|
676
|
+
progress("Checking task projection\u2026");
|
|
540
677
|
const projection = await request("/api/workspace/task-projection");
|
|
541
678
|
checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
|
|
542
679
|
} catch (error) {
|
|
@@ -545,6 +682,7 @@ async function runRigDoctorChecks(options) {
|
|
|
545
682
|
const slug = projectStatusSlug(projectRoot, config);
|
|
546
683
|
if (slug) {
|
|
547
684
|
try {
|
|
685
|
+
progress("Checking server project checkout\u2026");
|
|
548
686
|
const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
|
|
549
687
|
checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
|
|
550
688
|
} catch (error) {
|
|
@@ -559,6 +697,7 @@ async function runRigDoctorChecks(options) {
|
|
|
559
697
|
}
|
|
560
698
|
checks.push(githubProjectsCheck(config));
|
|
561
699
|
checks.push(prMergeCheck(config));
|
|
700
|
+
progress("Checking Pi installation\u2026");
|
|
562
701
|
const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
|
|
563
702
|
ok: false,
|
|
564
703
|
label: "pi/pi-rig checks",
|
|
@@ -582,12 +721,133 @@ function countDoctorFailures(checks) {
|
|
|
582
721
|
return checks.filter((entry) => entry.status === "fail").length;
|
|
583
722
|
}
|
|
584
723
|
|
|
724
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
725
|
+
import pc from "picocolors";
|
|
726
|
+
|
|
727
|
+
// packages/cli/src/commands/_spinner.ts
|
|
728
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
729
|
+
function createTtySpinner(input) {
|
|
730
|
+
const output = input.output ?? process.stdout;
|
|
731
|
+
const isTty = output.isTTY === true;
|
|
732
|
+
const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
|
|
733
|
+
let label = input.label;
|
|
734
|
+
let frame = 0;
|
|
735
|
+
let paused = false;
|
|
736
|
+
let stopped = false;
|
|
737
|
+
let lastPrintedLabel = "";
|
|
738
|
+
const render = () => {
|
|
739
|
+
if (stopped || paused)
|
|
740
|
+
return;
|
|
741
|
+
if (!isTty) {
|
|
742
|
+
if (label !== lastPrintedLabel) {
|
|
743
|
+
output.write(`${label}
|
|
744
|
+
`);
|
|
745
|
+
lastPrintedLabel = label;
|
|
746
|
+
}
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
frame = (frame + 1) % frames.length;
|
|
750
|
+
const glyph = frames[frame] ?? frames[0] ?? "";
|
|
751
|
+
output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
|
|
752
|
+
};
|
|
753
|
+
const clearLine = () => {
|
|
754
|
+
if (isTty)
|
|
755
|
+
output.write("\r\x1B[2K");
|
|
756
|
+
};
|
|
757
|
+
render();
|
|
758
|
+
const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
|
|
759
|
+
return {
|
|
760
|
+
setLabel(next) {
|
|
761
|
+
label = next;
|
|
762
|
+
render();
|
|
763
|
+
},
|
|
764
|
+
pause() {
|
|
765
|
+
paused = true;
|
|
766
|
+
clearLine();
|
|
767
|
+
},
|
|
768
|
+
resume() {
|
|
769
|
+
if (stopped)
|
|
770
|
+
return;
|
|
771
|
+
paused = false;
|
|
772
|
+
render();
|
|
773
|
+
},
|
|
774
|
+
stop(finalLine) {
|
|
775
|
+
if (stopped)
|
|
776
|
+
return;
|
|
777
|
+
stopped = true;
|
|
778
|
+
if (timer)
|
|
779
|
+
clearInterval(timer);
|
|
780
|
+
clearLine();
|
|
781
|
+
if (finalLine)
|
|
782
|
+
output.write(`${finalLine}
|
|
783
|
+
`);
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// packages/cli/src/commands/_async-ui.ts
|
|
789
|
+
var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
790
|
+
var DONE_SYMBOL = pc.green("\u25C7");
|
|
791
|
+
var FAIL_SYMBOL = pc.red("\u25A0");
|
|
792
|
+
var activeUpdate = null;
|
|
793
|
+
async function withSpinner(label, work, options = {}) {
|
|
794
|
+
if (options.outputMode === "json") {
|
|
795
|
+
return work(() => {});
|
|
796
|
+
}
|
|
797
|
+
if (activeUpdate) {
|
|
798
|
+
const outer = activeUpdate;
|
|
799
|
+
outer(label);
|
|
800
|
+
return work(outer);
|
|
801
|
+
}
|
|
802
|
+
const output = options.output ?? process.stderr;
|
|
803
|
+
const isTty = output.isTTY === true;
|
|
804
|
+
let lastLabel = label;
|
|
805
|
+
if (!isTty) {
|
|
806
|
+
output.write(`${label}
|
|
807
|
+
`);
|
|
808
|
+
const update2 = (next) => {
|
|
809
|
+
lastLabel = next;
|
|
810
|
+
};
|
|
811
|
+
activeUpdate = update2;
|
|
812
|
+
const previousListener2 = setServerPhaseListener(update2);
|
|
813
|
+
try {
|
|
814
|
+
return await work(update2);
|
|
815
|
+
} finally {
|
|
816
|
+
activeUpdate = null;
|
|
817
|
+
setServerPhaseListener(previousListener2);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
const spinner = createTtySpinner({
|
|
821
|
+
label,
|
|
822
|
+
output,
|
|
823
|
+
frames: CLACK_SPINNER_FRAMES,
|
|
824
|
+
styleFrame: (frame) => pc.magenta(frame)
|
|
825
|
+
});
|
|
826
|
+
const update = (next) => {
|
|
827
|
+
lastLabel = next;
|
|
828
|
+
spinner.setLabel(next);
|
|
829
|
+
};
|
|
830
|
+
activeUpdate = update;
|
|
831
|
+
const previousListener = setServerPhaseListener(update);
|
|
832
|
+
try {
|
|
833
|
+
const result = await work(update);
|
|
834
|
+
spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
|
|
835
|
+
return result;
|
|
836
|
+
} catch (error) {
|
|
837
|
+
spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
|
|
838
|
+
throw error;
|
|
839
|
+
} finally {
|
|
840
|
+
activeUpdate = null;
|
|
841
|
+
setServerPhaseListener(previousListener);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
585
845
|
// packages/cli/src/commands/setup.ts
|
|
586
846
|
async function executeSetup(context, args) {
|
|
587
847
|
const [command = "check", ...rest] = args;
|
|
588
848
|
switch (command) {
|
|
589
849
|
case "bootstrap":
|
|
590
|
-
requireNoExtraArgs(rest, "
|
|
850
|
+
requireNoExtraArgs(rest, "rig setup bootstrap");
|
|
591
851
|
{
|
|
592
852
|
const hostBash = Bun.which("bash") || "/bin/bash";
|
|
593
853
|
const env = { ...process.env };
|
|
@@ -605,32 +865,26 @@ async function executeSetup(context, args) {
|
|
|
605
865
|
});
|
|
606
866
|
const exitCode = await proc.exited;
|
|
607
867
|
if (exitCode !== 0) {
|
|
608
|
-
throw new
|
|
868
|
+
throw new CliError(`Command failed (${exitCode}): ${hostBash} ./bootstrap.sh`, exitCode);
|
|
609
869
|
}
|
|
610
870
|
}
|
|
611
871
|
return { ok: true, group: "setup", command };
|
|
612
872
|
case "check":
|
|
613
|
-
requireNoExtraArgs(rest, `
|
|
873
|
+
requireNoExtraArgs(rest, `rig setup ${command}`);
|
|
614
874
|
{
|
|
615
|
-
const checks = await withMutedConsole(context.outputMode === "json", () => runSetupCheck(context.projectRoot));
|
|
875
|
+
const checks = await withMutedConsole(context.outputMode === "json", () => runSetupCheck(context.projectRoot, context.outputMode));
|
|
616
876
|
return { ok: true, group: "setup", command, details: { checks, failures: countDoctorFailures(checks) } };
|
|
617
877
|
}
|
|
618
878
|
case "setup":
|
|
619
|
-
requireNoExtraArgs(rest, "
|
|
879
|
+
requireNoExtraArgs(rest, "rig setup setup");
|
|
620
880
|
withMutedConsole(context.outputMode === "json", () => runSetupInit(context.projectRoot));
|
|
621
881
|
return { ok: true, group: "setup", command };
|
|
622
882
|
case "preflight":
|
|
623
|
-
requireNoExtraArgs(rest, "
|
|
624
|
-
await withMutedConsole(context.outputMode === "json", () => runSetupPreflight(context.projectRoot));
|
|
625
|
-
return { ok: true, group: "setup", command };
|
|
626
|
-
case "install-agent-shell":
|
|
627
|
-
requireNoExtraArgs(rest, "bun run rig setup install-agent-shell");
|
|
628
|
-
if (context.outputMode === "text") {
|
|
629
|
-
console.log("install-agent-shell is deprecated. Runtime shells now use compiled rig-agent directly.");
|
|
630
|
-
}
|
|
883
|
+
requireNoExtraArgs(rest, "rig setup preflight");
|
|
884
|
+
await withMutedConsole(context.outputMode === "json", () => runSetupPreflight(context.projectRoot, context.outputMode));
|
|
631
885
|
return { ok: true, group: "setup", command };
|
|
632
886
|
default:
|
|
633
|
-
throw new
|
|
887
|
+
throw new CliError(`Unknown setup command: ${command}`, 1, { hint: "Run `rig setup --help` \u2014 commands are bootstrap|check|preflight." });
|
|
634
888
|
}
|
|
635
889
|
}
|
|
636
890
|
function formatTaskSourceKinds(kinds) {
|
|
@@ -664,17 +918,17 @@ function runSetupInit(projectRoot) {
|
|
|
664
918
|
}
|
|
665
919
|
console.log("Harness directories ready.");
|
|
666
920
|
}
|
|
667
|
-
async function runSetupCheck(projectRoot) {
|
|
668
|
-
const doctorChecks = await runRigDoctorChecks({ projectRoot });
|
|
921
|
+
async function runSetupCheck(projectRoot, outputMode = "text") {
|
|
922
|
+
const doctorChecks = await withSpinner("Running setup checks\u2026", (update) => runRigDoctorChecks({ projectRoot, onProgress: update }), { outputMode });
|
|
669
923
|
console.log(formatDoctorChecks(doctorChecks));
|
|
670
924
|
const failures = countDoctorFailures(doctorChecks);
|
|
671
925
|
if (failures > 0) {
|
|
672
|
-
throw new
|
|
926
|
+
throw new CliError(`Setup check failed (${failures} failing doctor check${failures === 1 ? "" : "s"}).`, 1, { hint: "Run `rig doctor` for the full check list with remediations." });
|
|
673
927
|
}
|
|
674
928
|
return doctorChecks;
|
|
675
929
|
}
|
|
676
|
-
async function runSetupPreflight(projectRoot) {
|
|
677
|
-
await runSetupCheck(projectRoot);
|
|
930
|
+
async function runSetupPreflight(projectRoot, outputMode = "text") {
|
|
931
|
+
await runSetupCheck(projectRoot, outputMode);
|
|
678
932
|
const validationRoot = resolve6(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
|
|
679
933
|
if (existsSync5(validationRoot)) {
|
|
680
934
|
const validators = readdirSync(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
|