@h-rig/cli 0.0.6-alpha.1 → 0.0.6-alpha.11
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 +481 -226
- package/dist/src/commands/_authority-runs.js +1 -0
- package/dist/src/commands/_doctor-checks.js +31 -13
- package/dist/src/commands/_operator-view.js +20 -2
- package/dist/src/commands/_preflight.js +25 -7
- package/dist/src/commands/_server-client.js +47 -9
- package/dist/src/commands/_snapshot-upload.js +26 -8
- package/dist/src/commands/agent.js +1 -0
- package/dist/src/commands/doctor.js +31 -13
- package/dist/src/commands/github.js +21 -3
- package/dist/src/commands/init.js +185 -67
- package/dist/src/commands/run.js +53 -9
- package/dist/src/commands/server.js +22 -4
- package/dist/src/commands/setup.js +36 -18
- package/dist/src/commands/task-run-driver.js +125 -16
- package/dist/src/commands/task.js +28 -10
- package/dist/src/commands.js +469 -217
- package/dist/src/index.js +481 -226
- package/dist/src/launcher.js +4 -2
- package/dist/src/runner.js +3 -2
- package/package.json +5 -4
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
var __require = import.meta.require;
|
|
3
3
|
|
|
4
4
|
// packages/cli/src/commands/init.ts
|
|
5
|
-
import { appendFileSync, existsSync as
|
|
5
|
+
import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
6
6
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
7
|
-
import { resolve as
|
|
7
|
+
import { resolve as resolve6 } from "path";
|
|
8
8
|
|
|
9
9
|
// packages/cli/src/runner.ts
|
|
10
10
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
@@ -154,6 +154,8 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
154
154
|
|
|
155
155
|
// packages/cli/src/commands/_server-client.ts
|
|
156
156
|
import { spawnSync } from "child_process";
|
|
157
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
158
|
+
import { resolve as resolve2 } from "path";
|
|
157
159
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
158
160
|
var cachedGitHubBearerToken;
|
|
159
161
|
function cleanToken(value) {
|
|
@@ -163,9 +165,25 @@ function cleanToken(value) {
|
|
|
163
165
|
function setGitHubBearerTokenForCurrentProcess(token) {
|
|
164
166
|
cachedGitHubBearerToken = cleanToken(token ?? undefined);
|
|
165
167
|
}
|
|
166
|
-
function
|
|
168
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
169
|
+
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
170
|
+
if (!existsSync2(path))
|
|
171
|
+
return null;
|
|
172
|
+
try {
|
|
173
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
174
|
+
return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
167
180
|
if (cachedGitHubBearerToken !== undefined)
|
|
168
181
|
return cachedGitHubBearerToken;
|
|
182
|
+
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
183
|
+
if (privateSession) {
|
|
184
|
+
cachedGitHubBearerToken = privateSession;
|
|
185
|
+
return cachedGitHubBearerToken;
|
|
186
|
+
}
|
|
169
187
|
const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
|
|
170
188
|
if (envToken) {
|
|
171
189
|
cachedGitHubBearerToken = envToken;
|
|
@@ -185,7 +203,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
185
203
|
if (selected?.connection.kind === "remote") {
|
|
186
204
|
return {
|
|
187
205
|
baseUrl: selected.connection.baseUrl,
|
|
188
|
-
authToken: readGitHubBearerTokenForRemote(),
|
|
206
|
+
authToken: readGitHubBearerTokenForRemote(projectRoot),
|
|
189
207
|
connectionKind: "remote"
|
|
190
208
|
};
|
|
191
209
|
}
|
|
@@ -249,7 +267,7 @@ async function postGitHubTokenViaServer(context, token, options = {}) {
|
|
|
249
267
|
const payload = await requestServerJson(context, "/api/github/auth/token", {
|
|
250
268
|
method: "POST",
|
|
251
269
|
headers: { "content-type": "application/json" },
|
|
252
|
-
body: JSON.stringify({ token, selectedRepo: options.selectedRepo })
|
|
270
|
+
body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot })
|
|
253
271
|
});
|
|
254
272
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
255
273
|
}
|
|
@@ -270,18 +288,38 @@ async function registerProjectViaServer(context, input) {
|
|
|
270
288
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
271
289
|
}
|
|
272
290
|
function sleep(ms) {
|
|
273
|
-
return new Promise((
|
|
291
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
292
|
+
}
|
|
293
|
+
function isRetryableProjectRootSwitchError(error) {
|
|
294
|
+
if (!(error instanceof Error))
|
|
295
|
+
return false;
|
|
296
|
+
const message = error.message.toLowerCase();
|
|
297
|
+
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");
|
|
274
298
|
}
|
|
275
299
|
async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
|
|
276
|
-
const switched = await requestServerJson(context, "/api/server/project-root", {
|
|
277
|
-
method: "POST",
|
|
278
|
-
headers: { "content-type": "application/json" },
|
|
279
|
-
body: JSON.stringify({ projectRoot })
|
|
280
|
-
});
|
|
281
300
|
const timeoutMs = options.timeoutMs ?? 30000;
|
|
282
301
|
const pollMs = options.pollMs ?? 1000;
|
|
283
302
|
const deadline = Date.now() + timeoutMs;
|
|
284
303
|
let lastError;
|
|
304
|
+
let switched = null;
|
|
305
|
+
while (Date.now() < deadline) {
|
|
306
|
+
try {
|
|
307
|
+
switched = await requestServerJson(context, "/api/server/project-root", {
|
|
308
|
+
method: "POST",
|
|
309
|
+
headers: { "content-type": "application/json" },
|
|
310
|
+
body: JSON.stringify({ projectRoot })
|
|
311
|
+
});
|
|
312
|
+
break;
|
|
313
|
+
} catch (error) {
|
|
314
|
+
lastError = error;
|
|
315
|
+
if (!isRetryableProjectRootSwitchError(error))
|
|
316
|
+
throw error;
|
|
317
|
+
await sleep(pollMs);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (!switched) {
|
|
321
|
+
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);
|
|
322
|
+
}
|
|
285
323
|
while (Date.now() < deadline) {
|
|
286
324
|
try {
|
|
287
325
|
const status = await requestServerJson(context, "/api/server/status");
|
|
@@ -301,9 +339,9 @@ async function switchServerProjectRootViaServer(context, projectRoot, options =
|
|
|
301
339
|
}
|
|
302
340
|
|
|
303
341
|
// packages/cli/src/commands/_pi-install.ts
|
|
304
|
-
import { existsSync as
|
|
342
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
|
|
305
343
|
import { homedir as homedir2 } from "os";
|
|
306
|
-
import { resolve as
|
|
344
|
+
import { resolve as resolve3 } from "path";
|
|
307
345
|
var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
|
|
308
346
|
var LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
|
|
309
347
|
export { default } from '@rig/pi-rig';
|
|
@@ -318,11 +356,11 @@ async function defaultCommandRunner(command, options = {}) {
|
|
|
318
356
|
return { exitCode, stdout, stderr };
|
|
319
357
|
}
|
|
320
358
|
function resolvePiRigExtensionPath(homeDir) {
|
|
321
|
-
return
|
|
359
|
+
return resolve3(homeDir, ".pi", "agent", "extensions", "pi-rig");
|
|
322
360
|
}
|
|
323
|
-
function resolvePiRigPackageSource(projectRoot, exists =
|
|
324
|
-
const localPackage =
|
|
325
|
-
if (exists(
|
|
361
|
+
function resolvePiRigPackageSource(projectRoot, exists = existsSync3) {
|
|
362
|
+
const localPackage = resolve3(projectRoot, "packages", "pi-rig");
|
|
363
|
+
if (exists(resolve3(localPackage, "package.json")))
|
|
326
364
|
return localPackage;
|
|
327
365
|
return `npm:${PI_RIG_PACKAGE_NAME}`;
|
|
328
366
|
}
|
|
@@ -373,13 +411,13 @@ async function ensurePiBinaryAvailable(input) {
|
|
|
373
411
|
...next.exitCode === 0 ? {} : { error: (next.stderr || next.stdout).trim() || "pi --version failed after install" }
|
|
374
412
|
};
|
|
375
413
|
}
|
|
376
|
-
function removeManagedLegacyPiRigBridge(homeDir, exists =
|
|
414
|
+
function removeManagedLegacyPiRigBridge(homeDir, exists = existsSync3) {
|
|
377
415
|
const extensionPath = resolvePiRigExtensionPath(homeDir);
|
|
378
|
-
const indexPath =
|
|
416
|
+
const indexPath = resolve3(extensionPath, "index.ts");
|
|
379
417
|
if (!exists(indexPath))
|
|
380
418
|
return;
|
|
381
419
|
try {
|
|
382
|
-
const content =
|
|
420
|
+
const content = readFileSync3(indexPath, "utf8");
|
|
383
421
|
if (content === LEGACY_PI_RIG_MARKER || content.includes("Managed by Rig. Source package: @rig/pi-rig")) {
|
|
384
422
|
rmSync(extensionPath, { recursive: true, force: true });
|
|
385
423
|
}
|
|
@@ -395,13 +433,13 @@ async function checkPiRigInstall(input = {}) {
|
|
|
395
433
|
piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
|
|
396
434
|
};
|
|
397
435
|
}
|
|
398
|
-
const exists = input.exists ??
|
|
436
|
+
const exists = input.exists ?? existsSync3;
|
|
399
437
|
const runner = input.commandRunner ?? defaultCommandRunner;
|
|
400
438
|
const piResult = await safeRun(runner, ["pi", "--version"]);
|
|
401
439
|
const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
|
|
402
440
|
const listedPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
|
|
403
441
|
${piListResult.stderr}`);
|
|
404
|
-
const legacyBridge = exists(
|
|
442
|
+
const legacyBridge = exists(resolve3(extensionPath, "index.ts"));
|
|
405
443
|
const hasPiRig = listedPiRig;
|
|
406
444
|
return {
|
|
407
445
|
extensionPath,
|
|
@@ -478,7 +516,7 @@ async function buildPiSetupChecks(input = {}) {
|
|
|
478
516
|
|
|
479
517
|
// packages/cli/src/commands/_snapshot-upload.ts
|
|
480
518
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
481
|
-
import { dirname as dirname2, resolve as
|
|
519
|
+
import { dirname as dirname2, resolve as resolve4, relative, sep } from "path";
|
|
482
520
|
var SNAPSHOT_ARCHIVE_VERSION = 1;
|
|
483
521
|
var SNAPSHOT_ARCHIVE_CONTENT_TYPE = "application/vnd.rig.snapshot+json";
|
|
484
522
|
var DEFAULT_EXCLUDED_DIRECTORIES = new Set([
|
|
@@ -500,15 +538,15 @@ function assertManifestPath(root, relativePath) {
|
|
|
500
538
|
if (!relativePath || relativePath.startsWith("/") || relativePath.includes("\x00")) {
|
|
501
539
|
throw new Error(`Invalid snapshot path: ${relativePath}`);
|
|
502
540
|
}
|
|
503
|
-
const resolved =
|
|
541
|
+
const resolved = resolve4(root, relativePath);
|
|
504
542
|
const relativeToRoot = relative(root, resolved);
|
|
505
|
-
if (relativeToRoot.startsWith("..") || relativeToRoot === ".." ||
|
|
543
|
+
if (relativeToRoot.startsWith("..") || relativeToRoot === ".." || resolve4(relativeToRoot) === resolved) {
|
|
506
544
|
throw new Error(`Snapshot path escapes project root: ${relativePath}`);
|
|
507
545
|
}
|
|
508
546
|
return resolved;
|
|
509
547
|
}
|
|
510
548
|
async function buildSnapshotUploadManifest(projectRoot, options = {}) {
|
|
511
|
-
const root =
|
|
549
|
+
const root = resolve4(projectRoot);
|
|
512
550
|
const excludedDirectories = [...new Set([
|
|
513
551
|
...DEFAULT_EXCLUDED_DIRECTORIES,
|
|
514
552
|
...options.excludedDirectories ?? []
|
|
@@ -520,7 +558,7 @@ async function buildSnapshotUploadManifest(projectRoot, options = {}) {
|
|
|
520
558
|
for (const entry of entries) {
|
|
521
559
|
if (entry.isDirectory() && excludedSet.has(entry.name))
|
|
522
560
|
continue;
|
|
523
|
-
const fullPath =
|
|
561
|
+
const fullPath = resolve4(dir, entry.name);
|
|
524
562
|
if (entry.isDirectory()) {
|
|
525
563
|
await visit(fullPath);
|
|
526
564
|
continue;
|
|
@@ -568,8 +606,8 @@ async function uploadSnapshotArchiveViaServer(context, input) {
|
|
|
568
606
|
}
|
|
569
607
|
|
|
570
608
|
// packages/cli/src/commands/_doctor-checks.ts
|
|
571
|
-
import { existsSync as
|
|
572
|
-
import { resolve as
|
|
609
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
610
|
+
import { resolve as resolve5 } from "path";
|
|
573
611
|
import { isSupportedBunVersion, MIN_SUPPORTED_BUN_VERSION } from "@rig/runtime/control-plane/setup-version";
|
|
574
612
|
|
|
575
613
|
// packages/cli/src/commands/_parsers.ts
|
|
@@ -621,11 +659,11 @@ function repoSlugFromConfig(config) {
|
|
|
621
659
|
function loadFallbackConfig(projectRoot) {
|
|
622
660
|
const candidates = ["rig.config.ts", "rig.config.mts", "rig.config.json"];
|
|
623
661
|
for (const name of candidates) {
|
|
624
|
-
const path =
|
|
625
|
-
if (!
|
|
662
|
+
const path = resolve5(projectRoot, name);
|
|
663
|
+
if (!existsSync4(path))
|
|
626
664
|
continue;
|
|
627
665
|
try {
|
|
628
|
-
const source =
|
|
666
|
+
const source = readFileSync4(path, "utf8");
|
|
629
667
|
if (name.endsWith(".json"))
|
|
630
668
|
return JSON.parse(source);
|
|
631
669
|
const owner = source.match(/owner\s*:\s*["']([^"']+)["']/)?.[1];
|
|
@@ -704,7 +742,7 @@ async function runRigDoctorChecks(options) {
|
|
|
704
742
|
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`)."));
|
|
705
743
|
const loadedConfig = await loadConfig(projectRoot).catch(() => null);
|
|
706
744
|
const config = loadedConfig ?? loadFallbackConfig(projectRoot);
|
|
707
|
-
const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) =>
|
|
745
|
+
const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve5(projectRoot, name)));
|
|
708
746
|
checks.push(config ? check("config", "rig.config loadable", "pass") : check("config", "rig.config loadable", hasConfigFile ? "fail" : "fail", hasConfigFile ? "config file exists but failed to load" : "missing rig.config.ts/json", "Run `rig init` or fix the config error."));
|
|
709
747
|
const taskSourceKind = config?.taskSource?.kind;
|
|
710
748
|
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."));
|
|
@@ -790,10 +828,10 @@ function countDoctorFailures(checks) {
|
|
|
790
828
|
}
|
|
791
829
|
|
|
792
830
|
// packages/cli/src/commands/init.ts
|
|
793
|
-
var
|
|
831
|
+
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest";
|
|
794
832
|
var RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
795
|
-
"@rig/core": `npm:@h-rig/core@${
|
|
796
|
-
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${
|
|
833
|
+
"@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
|
|
834
|
+
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
|
|
797
835
|
};
|
|
798
836
|
function parseRepoSlugFromRemote(remoteUrl) {
|
|
799
837
|
const trimmed = remoteUrl.trim();
|
|
@@ -813,20 +851,20 @@ function parseRepoSlug(value) {
|
|
|
813
851
|
return { owner: match[1], repo: match[2], slug: `${match[1]}/${match[2]}` };
|
|
814
852
|
}
|
|
815
853
|
function ensureRigPrivateDirs(projectRoot) {
|
|
816
|
-
const rigDir =
|
|
817
|
-
mkdirSync2(
|
|
818
|
-
mkdirSync2(
|
|
819
|
-
mkdirSync2(
|
|
820
|
-
mkdirSync2(
|
|
821
|
-
mkdirSync2(
|
|
822
|
-
const taskConfigPath =
|
|
823
|
-
if (!
|
|
854
|
+
const rigDir = resolve6(projectRoot, ".rig");
|
|
855
|
+
mkdirSync2(resolve6(rigDir, "state"), { recursive: true });
|
|
856
|
+
mkdirSync2(resolve6(rigDir, "logs"), { recursive: true });
|
|
857
|
+
mkdirSync2(resolve6(rigDir, "runs"), { recursive: true });
|
|
858
|
+
mkdirSync2(resolve6(rigDir, "tmp"), { recursive: true });
|
|
859
|
+
mkdirSync2(resolve6(projectRoot, "artifacts"), { recursive: true });
|
|
860
|
+
const taskConfigPath = resolve6(rigDir, "task-config.json");
|
|
861
|
+
if (!existsSync5(taskConfigPath))
|
|
824
862
|
writeFileSync2(taskConfigPath, `{}
|
|
825
863
|
`, "utf-8");
|
|
826
864
|
}
|
|
827
865
|
function ensureGitignoreEntries(projectRoot) {
|
|
828
|
-
const path =
|
|
829
|
-
const existing =
|
|
866
|
+
const path = resolve6(projectRoot, ".gitignore");
|
|
867
|
+
const existing = existsSync5(path) ? readFileSync5(path, "utf8") : "";
|
|
830
868
|
const entries = [".rig/state/", ".rig/logs/", ".rig/runs/", ".rig/tmp/"];
|
|
831
869
|
const missing = entries.filter((entry) => !existing.split(/\r?\n/).includes(entry));
|
|
832
870
|
if (missing.length === 0)
|
|
@@ -839,14 +877,14 @@ function ensureGitignoreEntries(projectRoot) {
|
|
|
839
877
|
`, "utf8");
|
|
840
878
|
}
|
|
841
879
|
function ensureRigConfigPackageDependencies(projectRoot) {
|
|
842
|
-
const path =
|
|
843
|
-
const existing =
|
|
880
|
+
const path = resolve6(projectRoot, "package.json");
|
|
881
|
+
const existing = existsSync5(path) ? JSON.parse(readFileSync5(path, "utf8")) : {};
|
|
844
882
|
const devDependencies = existing.devDependencies && typeof existing.devDependencies === "object" && !Array.isArray(existing.devDependencies) ? { ...existing.devDependencies } : {};
|
|
845
883
|
for (const [name, spec] of Object.entries(RIG_CONFIG_DEV_DEPENDENCIES)) {
|
|
846
884
|
devDependencies[name] = spec;
|
|
847
885
|
}
|
|
848
886
|
const next = {
|
|
849
|
-
...
|
|
887
|
+
...existsSync5(path) ? existing : { name: "rig-project", private: true },
|
|
850
888
|
devDependencies
|
|
851
889
|
};
|
|
852
890
|
writeFileSync2(path, `${JSON.stringify(next, null, 2)}
|
|
@@ -916,15 +954,71 @@ async function promptSelect(prompts, options) {
|
|
|
916
954
|
throw new CliError2("Init cancelled.", 1);
|
|
917
955
|
return String(value);
|
|
918
956
|
}
|
|
919
|
-
|
|
957
|
+
function sleep2(ms) {
|
|
958
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
959
|
+
}
|
|
960
|
+
function positiveIntFromEnv(name, fallback) {
|
|
961
|
+
const value = Number.parseInt(process.env[name] ?? "", 10);
|
|
962
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
963
|
+
}
|
|
964
|
+
function apiSessionTokenFrom(payload) {
|
|
965
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
966
|
+
return null;
|
|
967
|
+
const token = payload.apiSessionToken;
|
|
968
|
+
return typeof token === "string" && token.trim() ? token.trim() : null;
|
|
969
|
+
}
|
|
970
|
+
function cleanPayloadString(value) {
|
|
971
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
972
|
+
}
|
|
973
|
+
function remoteGitHubAuthMetadata(payload) {
|
|
974
|
+
if (!payload)
|
|
975
|
+
return {};
|
|
976
|
+
const userNamespace = payload.userNamespace && typeof payload.userNamespace === "object" && !Array.isArray(payload.userNamespace) ? payload.userNamespace : null;
|
|
977
|
+
return {
|
|
978
|
+
...cleanPayloadString(payload.login) ? { login: cleanPayloadString(payload.login) } : {},
|
|
979
|
+
...cleanPayloadString(payload.userId) ? { userId: cleanPayloadString(payload.userId) } : {},
|
|
980
|
+
...cleanPayloadString(userNamespace?.key) ? { userNamespaceKey: cleanPayloadString(userNamespace?.key) } : {},
|
|
981
|
+
...cleanPayloadString(userNamespace?.root) ? { userNamespaceRoot: cleanPayloadString(userNamespace?.root) } : {},
|
|
982
|
+
...cleanPayloadString(userNamespace?.checkoutBaseDir) ? { checkoutBaseDir: cleanPayloadString(userNamespace?.checkoutBaseDir) } : {},
|
|
983
|
+
...cleanPayloadString(userNamespace?.snapshotBaseDir) ? { snapshotBaseDir: cleanPayloadString(userNamespace?.snapshotBaseDir) } : {}
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
function writeRemoteGitHubAuthState(projectRoot, input) {
|
|
987
|
+
writeFileSync2(resolve6(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
|
|
988
|
+
authenticated: true,
|
|
989
|
+
source: input.source,
|
|
990
|
+
storedOnServer: true,
|
|
991
|
+
selectedRepo: input.selectedRepo,
|
|
992
|
+
...remoteGitHubAuthMetadata(input.authPayload ?? null),
|
|
993
|
+
...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
|
|
994
|
+
updatedAt: new Date().toISOString()
|
|
995
|
+
}, null, 2)}
|
|
996
|
+
`, "utf8");
|
|
997
|
+
}
|
|
998
|
+
async function pollDeviceAuthUntilComplete(context, pollId, firstPayload) {
|
|
920
999
|
if (typeof pollId !== "string" || !pollId.trim())
|
|
921
1000
|
return null;
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1001
|
+
const intervalSeconds = typeof firstPayload.interval === "number" && Number.isFinite(firstPayload.interval) && firstPayload.interval > 0 ? firstPayload.interval : 5;
|
|
1002
|
+
const timeoutMs = positiveIntFromEnv("RIG_DEVICE_AUTH_POLL_TIMEOUT_MS", 300000);
|
|
1003
|
+
const intervalMs = positiveIntFromEnv("RIG_DEVICE_AUTH_POLL_INTERVAL_MS", Math.max(1000, intervalSeconds * 1000));
|
|
1004
|
+
const deadline = Date.now() + timeoutMs;
|
|
1005
|
+
let last = null;
|
|
1006
|
+
do {
|
|
1007
|
+
const payload = await requestServerJson(context, "/api/github/auth/device/poll", {
|
|
1008
|
+
method: "POST",
|
|
1009
|
+
headers: { "content-type": "application/json" },
|
|
1010
|
+
body: JSON.stringify({ pollId })
|
|
1011
|
+
}).catch(() => null);
|
|
1012
|
+
last = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : null;
|
|
1013
|
+
const status = typeof last?.status === "string" ? last.status : null;
|
|
1014
|
+
if (status === "signed-in" || status === "expired" || status === "cancelled" || status === "failed") {
|
|
1015
|
+
return last;
|
|
1016
|
+
}
|
|
1017
|
+
if (timeoutMs <= 0)
|
|
1018
|
+
return last;
|
|
1019
|
+
await sleep2(intervalMs);
|
|
1020
|
+
} while (Date.now() < deadline);
|
|
1021
|
+
return last;
|
|
928
1022
|
}
|
|
929
1023
|
async function runControlPlaneInit(context, options) {
|
|
930
1024
|
const projectRoot = context.projectRoot;
|
|
@@ -947,9 +1041,9 @@ async function runControlPlaneInit(context, options) {
|
|
|
947
1041
|
});
|
|
948
1042
|
ensureRigPrivateDirs(projectRoot);
|
|
949
1043
|
ensureGitignoreEntries(projectRoot);
|
|
950
|
-
const configTsPath =
|
|
951
|
-
const configJsonPath =
|
|
952
|
-
const configExists =
|
|
1044
|
+
const configTsPath = resolve6(projectRoot, "rig.config.ts");
|
|
1045
|
+
const configJsonPath = resolve6(projectRoot, "rig.config.json");
|
|
1046
|
+
const configExists = existsSync5(configTsPath) || existsSync5(configJsonPath);
|
|
953
1047
|
if (!options.privateStateOnly) {
|
|
954
1048
|
if (configExists && !options.repair) {
|
|
955
1049
|
if (context.outputMode !== "json")
|
|
@@ -965,7 +1059,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
965
1059
|
}
|
|
966
1060
|
ensureRigConfigPackageDependencies(projectRoot);
|
|
967
1061
|
}
|
|
968
|
-
writeFileSync2(
|
|
1062
|
+
writeFileSync2(resolve6(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
|
|
969
1063
|
`, "utf8");
|
|
970
1064
|
const checkout = checkoutForInit(projectRoot, serverKind, options.remoteCheckout);
|
|
971
1065
|
let uploadedSnapshot = null;
|
|
@@ -987,10 +1081,15 @@ async function runControlPlaneInit(context, options) {
|
|
|
987
1081
|
const token = authMethod === "gh" && !options.githubToken ? readGhAuthToken() : options.githubToken?.trim();
|
|
988
1082
|
if (token) {
|
|
989
1083
|
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
|
|
990
|
-
|
|
1084
|
+
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
1085
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
991
1086
|
if (serverKind === "remote") {
|
|
992
|
-
|
|
993
|
-
|
|
1087
|
+
writeRemoteGitHubAuthState(projectRoot, {
|
|
1088
|
+
source: authMethod === "gh" ? "gh" : "init-token",
|
|
1089
|
+
selectedRepo: repo.slug,
|
|
1090
|
+
apiSessionToken,
|
|
1091
|
+
authPayload: githubAuth
|
|
1092
|
+
});
|
|
994
1093
|
}
|
|
995
1094
|
} else if (authMethod === "device") {
|
|
996
1095
|
const payload = await requestServerJson(context, "/api/github/auth/device/start", {
|
|
@@ -999,9 +1098,22 @@ async function runControlPlaneInit(context, options) {
|
|
|
999
1098
|
body: JSON.stringify({ repoSlug: repo.slug })
|
|
1000
1099
|
});
|
|
1001
1100
|
deviceAuth = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
1002
|
-
|
|
1003
|
-
|
|
1101
|
+
if (context.outputMode !== "json") {
|
|
1102
|
+
const verificationUri = String(deviceAuth.verificationUri ?? deviceAuth.verification_uri ?? deviceAuth.verification_uri_complete ?? "the verification URL returned by the server");
|
|
1103
|
+
const userCode = String(deviceAuth.userCode ?? deviceAuth.user_code ?? "the returned user code");
|
|
1104
|
+
console.log(`GitHub device flow: open ${verificationUri} and enter ${userCode}. Waiting for authorization...`);
|
|
1105
|
+
}
|
|
1106
|
+
const completed = await pollDeviceAuthUntilComplete(context, deviceAuth.pollId, deviceAuth);
|
|
1107
|
+
if (completed) {
|
|
1108
|
+
const apiSessionToken = apiSessionTokenFrom(completed);
|
|
1109
|
+
if (apiSessionToken) {
|
|
1110
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken);
|
|
1111
|
+
if (serverKind === "remote") {
|
|
1112
|
+
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1004
1115
|
deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
|
|
1116
|
+
}
|
|
1005
1117
|
}
|
|
1006
1118
|
let remoteCheckoutPreparation = null;
|
|
1007
1119
|
if (serverKind === "remote" && options.remoteCheckout?.kind !== "uploaded-snapshot") {
|
|
@@ -1021,6 +1133,12 @@ async function runControlPlaneInit(context, options) {
|
|
|
1021
1133
|
});
|
|
1022
1134
|
const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
|
|
1023
1135
|
const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
|
|
1136
|
+
if (serverRootSwitch && token) {
|
|
1137
|
+
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath ?? undefined });
|
|
1138
|
+
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
1139
|
+
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token);
|
|
1140
|
+
writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
|
|
1141
|
+
}
|
|
1024
1142
|
const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
|
|
1025
1143
|
const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
|
|
1026
1144
|
remote: true,
|
|
@@ -1130,7 +1248,7 @@ function parseInitOptions(args) {
|
|
|
1130
1248
|
async function runInteractiveControlPlaneInit(context, prompts) {
|
|
1131
1249
|
prompts.intro?.("Initialize a Rig control-plane project");
|
|
1132
1250
|
const projectRoot = context.projectRoot;
|
|
1133
|
-
const existingConfig =
|
|
1251
|
+
const existingConfig = existsSync5(resolve6(projectRoot, "rig.config.ts")) || existsSync5(resolve6(projectRoot, "rig.config.json"));
|
|
1134
1252
|
let repair = false;
|
|
1135
1253
|
let privateStateOnly = false;
|
|
1136
1254
|
if (existingConfig) {
|
|
@@ -1230,7 +1348,7 @@ async function runInteractiveControlPlaneInit(context, prompts) {
|
|
|
1230
1348
|
});
|
|
1231
1349
|
const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
|
|
1232
1350
|
const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
|
|
1233
|
-
const deviceMessage = deviceAuth ? ` GitHub device flow: open ${String(deviceAuth.verification_uri ?? deviceAuth.verification_uri_complete ?? "the verification URL returned by the server")} and enter ${String(deviceAuth.user_code ?? "the returned user code")}.` : "";
|
|
1351
|
+
const deviceMessage = deviceAuth ? ` GitHub device flow: open ${String(deviceAuth.verificationUri ?? deviceAuth.verification_uri ?? deviceAuth.verification_uri_complete ?? "the verification URL returned by the server")} and enter ${String(deviceAuth.userCode ?? deviceAuth.user_code ?? "the returned user code")}.` : "";
|
|
1234
1352
|
prompts.outro?.(`Rig project initialized.${deviceMessage} Next: rig doctor && rig task list`);
|
|
1235
1353
|
return result;
|
|
1236
1354
|
}
|
package/dist/src/commands/run.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/commands/run.ts
|
|
3
|
-
import { existsSync as
|
|
4
|
-
import { resolve as
|
|
3
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
4
|
+
import { resolve as resolve3 } from "path";
|
|
5
5
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
6
6
|
|
|
7
7
|
// packages/cli/src/runner.ts
|
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
listOpenEpics,
|
|
65
65
|
resolveDefaultEpic,
|
|
66
66
|
runResume,
|
|
67
|
+
runRestart,
|
|
67
68
|
runStatus,
|
|
68
69
|
runStop,
|
|
69
70
|
startRun,
|
|
@@ -85,6 +86,8 @@ function parsePositiveInt(value, option, fallback) {
|
|
|
85
86
|
|
|
86
87
|
// packages/cli/src/commands/_server-client.ts
|
|
87
88
|
import { spawnSync } from "child_process";
|
|
89
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
90
|
+
import { resolve as resolve2 } from "path";
|
|
88
91
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
89
92
|
|
|
90
93
|
// packages/cli/src/commands/_connection-state.ts
|
|
@@ -175,9 +178,25 @@ function cleanToken(value) {
|
|
|
175
178
|
const trimmed = value?.trim();
|
|
176
179
|
return trimmed ? trimmed : null;
|
|
177
180
|
}
|
|
178
|
-
function
|
|
181
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
182
|
+
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
183
|
+
if (!existsSync2(path))
|
|
184
|
+
return null;
|
|
185
|
+
try {
|
|
186
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
187
|
+
return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
179
193
|
if (cachedGitHubBearerToken !== undefined)
|
|
180
194
|
return cachedGitHubBearerToken;
|
|
195
|
+
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
196
|
+
if (privateSession) {
|
|
197
|
+
cachedGitHubBearerToken = privateSession;
|
|
198
|
+
return cachedGitHubBearerToken;
|
|
199
|
+
}
|
|
181
200
|
const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
|
|
182
201
|
if (envToken) {
|
|
183
202
|
cachedGitHubBearerToken = envToken;
|
|
@@ -197,7 +216,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
197
216
|
if (selected?.connection.kind === "remote") {
|
|
198
217
|
return {
|
|
199
218
|
baseUrl: selected.connection.baseUrl,
|
|
200
|
-
authToken: readGitHubBearerTokenForRemote(),
|
|
219
|
+
authToken: readGitHubBearerTokenForRemote(projectRoot),
|
|
201
220
|
connectionKind: "remote"
|
|
202
221
|
};
|
|
203
222
|
}
|
|
@@ -388,6 +407,17 @@ async function attachRunOperatorView(context, input) {
|
|
|
388
407
|
}
|
|
389
408
|
|
|
390
409
|
// packages/cli/src/commands/run.ts
|
|
410
|
+
function normalizeRemoteRunDetails(payload) {
|
|
411
|
+
const run = payload.run;
|
|
412
|
+
if (!run || typeof run !== "object" || Array.isArray(run))
|
|
413
|
+
return null;
|
|
414
|
+
return {
|
|
415
|
+
...run,
|
|
416
|
+
...Array.isArray(payload.timeline) ? { timeline: payload.timeline } : {},
|
|
417
|
+
...Array.isArray(payload.approvals) ? { approvals: payload.approvals } : {},
|
|
418
|
+
...Array.isArray(payload.userInputs) ? { userInputs: payload.userInputs } : {}
|
|
419
|
+
};
|
|
420
|
+
}
|
|
391
421
|
function shouldPromptForEpicSelection(context, command, promptEpic, noEpicPrompt) {
|
|
392
422
|
if (noEpicPrompt) {
|
|
393
423
|
return false;
|
|
@@ -526,7 +556,7 @@ async function executeRun(context, args) {
|
|
|
526
556
|
if (!run.value) {
|
|
527
557
|
throw new CliError2("run show requires --run <id>.");
|
|
528
558
|
}
|
|
529
|
-
const record = readAuthorityRun(context.projectRoot, run.value);
|
|
559
|
+
const record = readAuthorityRun(context.projectRoot, run.value) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, run.value).catch(() => ({})));
|
|
530
560
|
if (!record) {
|
|
531
561
|
throw new CliError2(`Run not found: ${run.value}`, 2);
|
|
532
562
|
}
|
|
@@ -545,7 +575,7 @@ async function executeRun(context, args) {
|
|
|
545
575
|
if (!run.value) {
|
|
546
576
|
throw new CliError2("run timeline requires --run <id>.");
|
|
547
577
|
}
|
|
548
|
-
const timelinePath =
|
|
578
|
+
const timelinePath = resolve3(resolveAuthorityRunDir(context.projectRoot, run.value), "timeline.jsonl");
|
|
549
579
|
const printEvents = () => {
|
|
550
580
|
const events2 = readJsonlFile(timelinePath);
|
|
551
581
|
if (context.outputMode === "text") {
|
|
@@ -557,12 +587,12 @@ async function executeRun(context, args) {
|
|
|
557
587
|
};
|
|
558
588
|
const events = printEvents();
|
|
559
589
|
if (follow.value && context.outputMode === "text") {
|
|
560
|
-
let lastLength =
|
|
590
|
+
let lastLength = existsSync3(timelinePath) ? readFileSync3(timelinePath, "utf8").length : 0;
|
|
561
591
|
while (true) {
|
|
562
592
|
await Bun.sleep(1000);
|
|
563
|
-
if (!
|
|
593
|
+
if (!existsSync3(timelinePath))
|
|
564
594
|
continue;
|
|
565
|
-
const next =
|
|
595
|
+
const next = readFileSync3(timelinePath, "utf8");
|
|
566
596
|
if (next.length <= lastLength)
|
|
567
597
|
continue;
|
|
568
598
|
const delta = next.slice(lastLength);
|
|
@@ -707,6 +737,20 @@ async function executeRun(context, args) {
|
|
|
707
737
|
}
|
|
708
738
|
return { ok: true, group: "run", command, details: resumed };
|
|
709
739
|
}
|
|
740
|
+
case "restart": {
|
|
741
|
+
requireNoExtraArgs(rest, "bun run rig run restart");
|
|
742
|
+
if (context.dryRun) {
|
|
743
|
+
if (context.outputMode === "text") {
|
|
744
|
+
console.log("[dry-run] rig run restart");
|
|
745
|
+
}
|
|
746
|
+
return { ok: true, group: "run", command };
|
|
747
|
+
}
|
|
748
|
+
const restarted = await runRestart(context.projectRoot, runtimeContext);
|
|
749
|
+
if (context.outputMode === "text") {
|
|
750
|
+
console.log(`Restarted run: ${restarted.runId}`);
|
|
751
|
+
}
|
|
752
|
+
return { ok: true, group: "run", command, details: restarted };
|
|
753
|
+
}
|
|
710
754
|
case "stop": {
|
|
711
755
|
const runOption = takeOption(rest, "--run");
|
|
712
756
|
const positionalRunId = runOption.rest.length > 0 ? runOption.rest[0] : undefined;
|