@beastmode-develeap/beastmode 0.1.151 → 0.1.153
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/index.js +383 -42
- package/dist/index.js.map +1 -1
- package/dist/web/board.html +6 -6
- package/dist/web/build-commit.txt +1 -1
- package/dist/web/build-stamp.txt +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2652,7 +2652,7 @@ var init_migrator = __esm({
|
|
|
2652
2652
|
|
|
2653
2653
|
// src/engine/bridge.ts
|
|
2654
2654
|
function generateDaemonConfig(factoryConfig, projectConfig, factoryPath) {
|
|
2655
|
-
const
|
|
2655
|
+
const pipeline2 = factoryConfig.pipeline;
|
|
2656
2656
|
const models = factoryConfig.models;
|
|
2657
2657
|
const resilience = factoryConfig.resilience;
|
|
2658
2658
|
const cost = factoryConfig.cost;
|
|
@@ -2720,9 +2720,9 @@ function generateDaemonConfig(factoryConfig, projectConfig, factoryPath) {
|
|
|
2720
2720
|
},
|
|
2721
2721
|
verification: {
|
|
2722
2722
|
enabled: true,
|
|
2723
|
-
satisfaction_threshold:
|
|
2723
|
+
satisfaction_threshold: pipeline2.satisfaction_threshold,
|
|
2724
2724
|
timeout_minutes: 30,
|
|
2725
|
-
prod_accept_floor:
|
|
2725
|
+
prod_accept_floor: pipeline2.prod_accept_floor
|
|
2726
2726
|
},
|
|
2727
2727
|
review: {
|
|
2728
2728
|
enabled: humanGates.pr_review !== "disabled",
|
|
@@ -2745,8 +2745,8 @@ function generateDaemonConfig(factoryConfig, projectConfig, factoryPath) {
|
|
|
2745
2745
|
build_timeout_seconds: 600
|
|
2746
2746
|
},
|
|
2747
2747
|
convergence: {
|
|
2748
|
-
max_iterations:
|
|
2749
|
-
fail_fast_threshold:
|
|
2748
|
+
max_iterations: pipeline2.max_iterations,
|
|
2749
|
+
fail_fast_threshold: pipeline2.fail_fast_threshold,
|
|
2750
2750
|
infra_retry_max: 3,
|
|
2751
2751
|
max_precheck_retries: 2
|
|
2752
2752
|
},
|
|
@@ -4343,7 +4343,7 @@ You are currently scoped to project "${scope}". Focus your answers on this proje
|
|
|
4343
4343
|
async function runViaCli(session, content, scope = "factory") {
|
|
4344
4344
|
session.busy = true;
|
|
4345
4345
|
try {
|
|
4346
|
-
const { spawn:
|
|
4346
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
4347
4347
|
let boardContext = "";
|
|
4348
4348
|
try {
|
|
4349
4349
|
const boardUrl = getBoardUrl(session.factoryPath);
|
|
@@ -4435,7 +4435,7 @@ Respond concisely. Continue the conversation naturally.`;
|
|
|
4435
4435
|
spawnCmd = "claude";
|
|
4436
4436
|
spawnArgs = claudeArgs;
|
|
4437
4437
|
}
|
|
4438
|
-
const child =
|
|
4438
|
+
const child = spawn4(spawnCmd, spawnArgs, {
|
|
4439
4439
|
cwd: session.factoryPath,
|
|
4440
4440
|
env: {
|
|
4441
4441
|
...process.env,
|
|
@@ -11401,8 +11401,8 @@ async function runMigrate(opts) {
|
|
|
11401
11401
|
}
|
|
11402
11402
|
let worktreeOutput = "";
|
|
11403
11403
|
try {
|
|
11404
|
-
const { execSync:
|
|
11405
|
-
worktreeOutput =
|
|
11404
|
+
const { execSync: execSync12 } = await import("child_process");
|
|
11405
|
+
worktreeOutput = execSync12("git worktree list", {
|
|
11406
11406
|
cwd,
|
|
11407
11407
|
encoding: "utf-8",
|
|
11408
11408
|
timeout: 5e3
|
|
@@ -11575,14 +11575,14 @@ async function runPipeline(projectName, opts) {
|
|
|
11575
11575
|
const daemonConfig = generateDaemonConfig(factoryConfig, projectConfig, factoryDir);
|
|
11576
11576
|
writeFileSync22(daemonConfigPath, JSON.stringify(daemonConfig, null, 2), "utf-8");
|
|
11577
11577
|
info(`Generated daemon config at: ${daemonConfigPath}`);
|
|
11578
|
-
const { execSync:
|
|
11578
|
+
const { execSync: execSync12 } = await import("child_process");
|
|
11579
11579
|
let pythonAvailable = false;
|
|
11580
11580
|
try {
|
|
11581
|
-
|
|
11581
|
+
execSync12("python --version", { timeout: 5e3, encoding: "utf-8" });
|
|
11582
11582
|
pythonAvailable = true;
|
|
11583
11583
|
} catch {
|
|
11584
11584
|
try {
|
|
11585
|
-
|
|
11585
|
+
execSync12("python3 --version", { timeout: 5e3, encoding: "utf-8" });
|
|
11586
11586
|
pythonAvailable = true;
|
|
11587
11587
|
} catch {
|
|
11588
11588
|
}
|
|
@@ -11610,8 +11610,8 @@ async function runPipeline(projectName, opts) {
|
|
|
11610
11610
|
}
|
|
11611
11611
|
const cmd = buildDaemonCommand(null, daemonConfigPath);
|
|
11612
11612
|
info(`Spawning daemon: ${cmd.command} ${cmd.args.join(" ")}`);
|
|
11613
|
-
const { spawn:
|
|
11614
|
-
const child =
|
|
11613
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
11614
|
+
const child = spawn4(cmd.command, cmd.args, {
|
|
11615
11615
|
stdio: "inherit",
|
|
11616
11616
|
cwd: factoryDir,
|
|
11617
11617
|
env: {
|
|
@@ -11735,16 +11735,16 @@ async function runDaemon(opts) {
|
|
|
11735
11735
|
console.log(JSON.stringify(daemonConfig, null, 2));
|
|
11736
11736
|
return;
|
|
11737
11737
|
}
|
|
11738
|
-
const { execSync:
|
|
11738
|
+
const { execSync: execSync12 } = await import("child_process");
|
|
11739
11739
|
let pythonCmd = "python";
|
|
11740
11740
|
let pythonAvailable = false;
|
|
11741
11741
|
try {
|
|
11742
|
-
|
|
11742
|
+
execSync12("python --version", { timeout: 5e3, encoding: "utf-8" });
|
|
11743
11743
|
pythonAvailable = true;
|
|
11744
11744
|
pythonCmd = "python";
|
|
11745
11745
|
} catch {
|
|
11746
11746
|
try {
|
|
11747
|
-
|
|
11747
|
+
execSync12("python3 --version", { timeout: 5e3, encoding: "utf-8" });
|
|
11748
11748
|
pythonAvailable = true;
|
|
11749
11749
|
pythonCmd = "python3";
|
|
11750
11750
|
} catch {
|
|
@@ -11762,8 +11762,8 @@ async function runDaemon(opts) {
|
|
|
11762
11762
|
info(`Starting daemon: ${pythonCmd} ${cmd.args.join(" ")}`);
|
|
11763
11763
|
console.log();
|
|
11764
11764
|
const pidFile = join28(bmDir, "daemon.pid");
|
|
11765
|
-
const { spawn:
|
|
11766
|
-
const child =
|
|
11765
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
11766
|
+
const child = spawn4(pythonCmd, cmd.args, {
|
|
11767
11767
|
stdio: "inherit",
|
|
11768
11768
|
cwd: factoryDir,
|
|
11769
11769
|
env: {
|
|
@@ -11783,8 +11783,8 @@ async function runDaemon(opts) {
|
|
|
11783
11783
|
const exitCode = await new Promise((resolvePromise) => {
|
|
11784
11784
|
child.on("exit", (code) => {
|
|
11785
11785
|
try {
|
|
11786
|
-
const { unlinkSync:
|
|
11787
|
-
|
|
11786
|
+
const { unlinkSync: unlinkSync7 } = __require("fs");
|
|
11787
|
+
unlinkSync7(pidFile);
|
|
11788
11788
|
} catch {
|
|
11789
11789
|
}
|
|
11790
11790
|
resolvePromise(code ?? 1);
|
|
@@ -12422,7 +12422,7 @@ var updateCommand = new Command22("update").description("Pull latest BeastMode i
|
|
|
12422
12422
|
// src/cli/commands/runner-cmd.ts
|
|
12423
12423
|
init_display();
|
|
12424
12424
|
import { Command as Command23 } from "commander";
|
|
12425
|
-
import { spawn as
|
|
12425
|
+
import { spawn as spawn3 } from "child_process";
|
|
12426
12426
|
import { resolve as resolve20 } from "path";
|
|
12427
12427
|
|
|
12428
12428
|
// src/cli/github-runners.ts
|
|
@@ -12497,7 +12497,13 @@ function resolveGitHubConfig() {
|
|
|
12497
12497
|
// src/cli/runner-helpers.ts
|
|
12498
12498
|
import { execSync as execSync10, spawn, spawnSync as spawnSync5 } from "child_process";
|
|
12499
12499
|
import { promises as fs } from "fs";
|
|
12500
|
+
import { join as join31 } from "path";
|
|
12500
12501
|
init_display();
|
|
12502
|
+
async function writeRunnerMeta(dir, meta) {
|
|
12503
|
+
await fs.mkdir(dir, { recursive: true });
|
|
12504
|
+
const path = join31(dir, "runner-meta.json");
|
|
12505
|
+
await fs.writeFile(path, JSON.stringify(meta, null, 2) + "\n", "utf-8");
|
|
12506
|
+
}
|
|
12501
12507
|
function resolveRepoSlug() {
|
|
12502
12508
|
let rawUrl;
|
|
12503
12509
|
try {
|
|
@@ -12687,15 +12693,326 @@ function setupStep(text) {
|
|
|
12687
12693
|
info(text);
|
|
12688
12694
|
}
|
|
12689
12695
|
|
|
12690
|
-
// src/cli/
|
|
12696
|
+
// src/cli/native-runner.ts
|
|
12697
|
+
import {
|
|
12698
|
+
execSync as execSync11,
|
|
12699
|
+
spawn as spawn2,
|
|
12700
|
+
spawnSync as spawnSync6
|
|
12701
|
+
} from "child_process";
|
|
12691
12702
|
import {
|
|
12703
|
+
createWriteStream,
|
|
12692
12704
|
existsSync as existsSync33,
|
|
12693
12705
|
mkdirSync as mkdirSync20,
|
|
12694
|
-
readFileSync as readFileSync31,
|
|
12695
12706
|
unlinkSync as unlinkSync5,
|
|
12696
12707
|
writeFileSync as writeFileSync27
|
|
12697
12708
|
} from "fs";
|
|
12698
|
-
import {
|
|
12709
|
+
import { homedir as homedir4 } from "os";
|
|
12710
|
+
import { join as join32, dirname as dirname8 } from "path";
|
|
12711
|
+
import { Readable } from "stream";
|
|
12712
|
+
import { pipeline } from "stream/promises";
|
|
12713
|
+
init_display();
|
|
12714
|
+
var RUNNER_VERSION = "2.322.0";
|
|
12715
|
+
function detectPlatform() {
|
|
12716
|
+
const platform3 = process.platform;
|
|
12717
|
+
if (platform3 === "win32") {
|
|
12718
|
+
throw new Error(
|
|
12719
|
+
"--native is not supported on Windows. Use Docker or WSL."
|
|
12720
|
+
);
|
|
12721
|
+
}
|
|
12722
|
+
if (platform3 !== "linux" && platform3 !== "darwin") {
|
|
12723
|
+
throw new Error(
|
|
12724
|
+
`--native is not supported on ${platform3}. Use Docker instead.`
|
|
12725
|
+
);
|
|
12726
|
+
}
|
|
12727
|
+
const arch = process.arch;
|
|
12728
|
+
if (arch !== "x64" && arch !== "arm64") {
|
|
12729
|
+
throw new Error(
|
|
12730
|
+
`Unsupported architecture: ${arch}. GitHub runners support x64 and arm64.`
|
|
12731
|
+
);
|
|
12732
|
+
}
|
|
12733
|
+
return { os: platform3, arch };
|
|
12734
|
+
}
|
|
12735
|
+
function runnerDownloadUrl(os, arch) {
|
|
12736
|
+
const ghOs = os === "darwin" ? "osx" : "linux";
|
|
12737
|
+
return `https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-${ghOs}-${arch}-${RUNNER_VERSION}.tar.gz`;
|
|
12738
|
+
}
|
|
12739
|
+
async function downloadAndExtractRunner(installDir, os, arch) {
|
|
12740
|
+
const runShPath = join32(installDir, "run.sh");
|
|
12741
|
+
if (existsSync33(runShPath)) {
|
|
12742
|
+
info(
|
|
12743
|
+
`Runner binary already present at ${runShPath} \u2014 skipping download.`
|
|
12744
|
+
);
|
|
12745
|
+
if (os === "darwin") {
|
|
12746
|
+
spawnSync6("xattr", ["-c", "-r", installDir], { stdio: "ignore" });
|
|
12747
|
+
}
|
|
12748
|
+
return;
|
|
12749
|
+
}
|
|
12750
|
+
const url = runnerDownloadUrl(os, arch);
|
|
12751
|
+
const tarball = join32(installDir, "runner.tar.gz");
|
|
12752
|
+
setupStep(
|
|
12753
|
+
`Downloading GitHub Actions runner v${RUNNER_VERSION} (${os}/${arch})...`
|
|
12754
|
+
);
|
|
12755
|
+
const resp = await fetch(url);
|
|
12756
|
+
if (!resp.ok || !resp.body) {
|
|
12757
|
+
throw new Error(
|
|
12758
|
+
`Failed to download runner: HTTP ${resp.status} from ${url}`
|
|
12759
|
+
);
|
|
12760
|
+
}
|
|
12761
|
+
await pipeline(
|
|
12762
|
+
Readable.fromWeb(resp.body),
|
|
12763
|
+
createWriteStream(tarball)
|
|
12764
|
+
);
|
|
12765
|
+
setupStep("Extracting runner...");
|
|
12766
|
+
try {
|
|
12767
|
+
execSync11("tar -xzf runner.tar.gz", { cwd: installDir, stdio: "ignore" });
|
|
12768
|
+
} catch (err) {
|
|
12769
|
+
throw new Error(
|
|
12770
|
+
`Failed to extract runner archive: ${err?.message ?? String(err)}`
|
|
12771
|
+
);
|
|
12772
|
+
}
|
|
12773
|
+
try {
|
|
12774
|
+
unlinkSync5(tarball);
|
|
12775
|
+
} catch {
|
|
12776
|
+
}
|
|
12777
|
+
if (os === "darwin") {
|
|
12778
|
+
setupStep("Removing macOS Gatekeeper quarantine attribute...");
|
|
12779
|
+
warn(
|
|
12780
|
+
"macOS Gatekeeper quarantine: running xattr -c -r on runner binaries."
|
|
12781
|
+
);
|
|
12782
|
+
warn(
|
|
12783
|
+
"This is required because macOS quarantines downloaded executables."
|
|
12784
|
+
);
|
|
12785
|
+
spawnSync6("xattr", ["-c", "-r", installDir], { stdio: "ignore" });
|
|
12786
|
+
}
|
|
12787
|
+
}
|
|
12788
|
+
async function configureRunner(installDir, repoUrl, token, name, labels) {
|
|
12789
|
+
setupStep("Configuring runner...");
|
|
12790
|
+
const args = [
|
|
12791
|
+
"--url",
|
|
12792
|
+
repoUrl,
|
|
12793
|
+
"--token",
|
|
12794
|
+
token,
|
|
12795
|
+
"--name",
|
|
12796
|
+
name,
|
|
12797
|
+
"--labels",
|
|
12798
|
+
labels.join(","),
|
|
12799
|
+
"--unattended",
|
|
12800
|
+
"--replace"
|
|
12801
|
+
];
|
|
12802
|
+
const result = spawnSync6(join32(installDir, "config.sh"), args, {
|
|
12803
|
+
cwd: installDir,
|
|
12804
|
+
stdio: ["ignore", "inherit", "pipe"],
|
|
12805
|
+
encoding: "utf-8"
|
|
12806
|
+
});
|
|
12807
|
+
if (result.error && result.error.code === "ENOENT") {
|
|
12808
|
+
throw new Error(
|
|
12809
|
+
`Runner configuration failed: config.sh not found at ${installDir}/config.sh.`
|
|
12810
|
+
);
|
|
12811
|
+
}
|
|
12812
|
+
if (result.status !== 0) {
|
|
12813
|
+
const stderr = (result.stderr || "").replace(token, "<REDACTED>");
|
|
12814
|
+
throw new Error(
|
|
12815
|
+
`Runner configuration failed (exit ${result.status}): ${stderr}`
|
|
12816
|
+
);
|
|
12817
|
+
}
|
|
12818
|
+
}
|
|
12819
|
+
function startRunnerForeground(installDir) {
|
|
12820
|
+
setupStep("Starting runner in foreground mode (Ctrl+C to stop)...");
|
|
12821
|
+
info("The runner will stop when this terminal closes.");
|
|
12822
|
+
if (process.platform === "darwin") {
|
|
12823
|
+
warn(
|
|
12824
|
+
"macOS pitfall: if the terminal session disconnects, the runner dies."
|
|
12825
|
+
);
|
|
12826
|
+
warn(
|
|
12827
|
+
"Use --service to install as a launchd agent for persistent operation."
|
|
12828
|
+
);
|
|
12829
|
+
}
|
|
12830
|
+
spawn2(join32(installDir, "run.sh"), [], {
|
|
12831
|
+
cwd: installDir,
|
|
12832
|
+
stdio: "inherit"
|
|
12833
|
+
});
|
|
12834
|
+
}
|
|
12835
|
+
function systemdUnitContent(installDir, name) {
|
|
12836
|
+
return `[Unit]
|
|
12837
|
+
Description=GitHub Actions Runner (${name})
|
|
12838
|
+
After=network.target
|
|
12839
|
+
|
|
12840
|
+
[Service]
|
|
12841
|
+
Type=simple
|
|
12842
|
+
WorkingDirectory=${installDir}
|
|
12843
|
+
ExecStart=${installDir}/run.sh
|
|
12844
|
+
Restart=on-failure
|
|
12845
|
+
RestartSec=5
|
|
12846
|
+
KillSignal=SIGTERM
|
|
12847
|
+
TimeoutStopSec=30
|
|
12848
|
+
|
|
12849
|
+
[Install]
|
|
12850
|
+
WantedBy=default.target
|
|
12851
|
+
`;
|
|
12852
|
+
}
|
|
12853
|
+
async function installSystemdService(installDir, name) {
|
|
12854
|
+
const unitName = `beastmode-runner-${name}.service`;
|
|
12855
|
+
const unitDir = join32(homedir4(), ".config", "systemd", "user");
|
|
12856
|
+
mkdirSync20(unitDir, { recursive: true });
|
|
12857
|
+
writeFileSync27(
|
|
12858
|
+
join32(unitDir, unitName),
|
|
12859
|
+
systemdUnitContent(installDir, name),
|
|
12860
|
+
"utf-8"
|
|
12861
|
+
);
|
|
12862
|
+
try {
|
|
12863
|
+
execSync11("systemctl --user daemon-reload", { stdio: "inherit" });
|
|
12864
|
+
execSync11(`systemctl --user enable --now ${unitName}`, {
|
|
12865
|
+
stdio: "inherit"
|
|
12866
|
+
});
|
|
12867
|
+
} catch (err) {
|
|
12868
|
+
throw new Error(
|
|
12869
|
+
`systemd user-service install failed: ${err?.message ?? String(err)}. Ensure systemd --user is available (some minimal Linux containers don't ship it).`
|
|
12870
|
+
);
|
|
12871
|
+
}
|
|
12872
|
+
success(`Installed and started systemd user service: ${unitName}`);
|
|
12873
|
+
info(` Check status: systemctl --user status ${unitName}`);
|
|
12874
|
+
info(` View logs: journalctl --user -u ${unitName} -f`);
|
|
12875
|
+
}
|
|
12876
|
+
function launchdPlistContent(installDir, name) {
|
|
12877
|
+
const label = `com.beastmode.runner.${name}`;
|
|
12878
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
12879
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
12880
|
+
<plist version="1.0">
|
|
12881
|
+
<dict>
|
|
12882
|
+
<key>Label</key>
|
|
12883
|
+
<string>${label}</string>
|
|
12884
|
+
<key>WorkingDirectory</key>
|
|
12885
|
+
<string>${installDir}</string>
|
|
12886
|
+
<key>ProgramArguments</key>
|
|
12887
|
+
<array>
|
|
12888
|
+
<string>${installDir}/run.sh</string>
|
|
12889
|
+
</array>
|
|
12890
|
+
<key>RunAtLoad</key>
|
|
12891
|
+
<true/>
|
|
12892
|
+
<key>KeepAlive</key>
|
|
12893
|
+
<dict>
|
|
12894
|
+
<key>SuccessfulExit</key>
|
|
12895
|
+
<false/>
|
|
12896
|
+
</dict>
|
|
12897
|
+
<key>StandardOutPath</key>
|
|
12898
|
+
<string>${installDir}/logs/stdout.log</string>
|
|
12899
|
+
<key>StandardErrorPath</key>
|
|
12900
|
+
<string>${installDir}/logs/stderr.log</string>
|
|
12901
|
+
<key>EnvironmentVariables</key>
|
|
12902
|
+
<dict>
|
|
12903
|
+
<key>PATH</key>
|
|
12904
|
+
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin</string>
|
|
12905
|
+
</dict>
|
|
12906
|
+
</dict>
|
|
12907
|
+
</plist>
|
|
12908
|
+
`;
|
|
12909
|
+
}
|
|
12910
|
+
async function installLaunchdService(installDir, name) {
|
|
12911
|
+
const label = `com.beastmode.runner.${name}`;
|
|
12912
|
+
const plistPath2 = join32(
|
|
12913
|
+
homedir4(),
|
|
12914
|
+
"Library",
|
|
12915
|
+
"LaunchAgents",
|
|
12916
|
+
`${label}.plist`
|
|
12917
|
+
);
|
|
12918
|
+
mkdirSync20(dirname8(plistPath2), { recursive: true });
|
|
12919
|
+
mkdirSync20(join32(installDir, "logs"), { recursive: true });
|
|
12920
|
+
writeFileSync27(plistPath2, launchdPlistContent(installDir, name), "utf-8");
|
|
12921
|
+
try {
|
|
12922
|
+
execSync11(`launchctl load ${plistPath2}`, { stdio: "inherit" });
|
|
12923
|
+
} catch (err) {
|
|
12924
|
+
throw new Error(
|
|
12925
|
+
`launchd agent install failed: ${err?.message ?? String(err)}.`
|
|
12926
|
+
);
|
|
12927
|
+
}
|
|
12928
|
+
success(`Installed and loaded launchd agent: ${label}`);
|
|
12929
|
+
info(` Check status: launchctl list | grep ${label}`);
|
|
12930
|
+
info(` View logs: tail -f ${installDir}/logs/stdout.log`);
|
|
12931
|
+
warn("macOS pitfall: launchd agents only run while the user is logged in.");
|
|
12932
|
+
warn(
|
|
12933
|
+
"If you reboot without auto-login, the runner won't start until you log in."
|
|
12934
|
+
);
|
|
12935
|
+
warn("For headless servers, use a LaunchDaemon (requires root) instead.");
|
|
12936
|
+
}
|
|
12937
|
+
async function installRunnerService(installDir, name, os) {
|
|
12938
|
+
if (os === "linux") {
|
|
12939
|
+
await installSystemdService(installDir, name);
|
|
12940
|
+
} else {
|
|
12941
|
+
await installLaunchdService(installDir, name);
|
|
12942
|
+
}
|
|
12943
|
+
}
|
|
12944
|
+
async function nativeRunnerSetup(opts) {
|
|
12945
|
+
if (opts.service !== void 0 && !opts.native) {
|
|
12946
|
+
throw new Error(
|
|
12947
|
+
"--service requires --native. The Docker runner always runs as a container service."
|
|
12948
|
+
);
|
|
12949
|
+
}
|
|
12950
|
+
const { os: platformOs, arch } = detectPlatform();
|
|
12951
|
+
if (platformOs === "darwin") {
|
|
12952
|
+
header("Native Runner Setup (macOS)");
|
|
12953
|
+
warn(
|
|
12954
|
+
"macOS has several pitfalls for self-hosted runners. Read the warnings below carefully."
|
|
12955
|
+
);
|
|
12956
|
+
} else {
|
|
12957
|
+
header("Native Runner Setup (Linux)");
|
|
12958
|
+
}
|
|
12959
|
+
const ghConfig = resolveGitHubConfig();
|
|
12960
|
+
const repoSlug = opts.repo ?? resolveRepoSlug();
|
|
12961
|
+
const installDir = join32(homedir4(), ".beastmode", "runners", opts.name);
|
|
12962
|
+
if (opts.dryRun) {
|
|
12963
|
+
info(
|
|
12964
|
+
`[dry-run] Would install native runner '${opts.name}' for repo ${repoSlug}`
|
|
12965
|
+
);
|
|
12966
|
+
info(`[dry-run] Platform: ${platformOs}/${arch}`);
|
|
12967
|
+
info(`[dry-run] Install dir: ${installDir}`);
|
|
12968
|
+
info(`[dry-run] Mode: ${opts.service ? "service" : "foreground"}`);
|
|
12969
|
+
return;
|
|
12970
|
+
}
|
|
12971
|
+
mkdirSync20(installDir, { recursive: true });
|
|
12972
|
+
setupStep("Generating registration token via GitHub API...");
|
|
12973
|
+
const { token: regToken } = await createRegistrationToken(ghConfig);
|
|
12974
|
+
await downloadAndExtractRunner(installDir, platformOs, arch);
|
|
12975
|
+
const repoUrl = `https://github.com/${ghConfig.owner}/${ghConfig.repo}`;
|
|
12976
|
+
await configureRunner(installDir, repoUrl, regToken, opts.name, [
|
|
12977
|
+
"self-hosted",
|
|
12978
|
+
opts.label
|
|
12979
|
+
]);
|
|
12980
|
+
await writeEnvEntries({
|
|
12981
|
+
RUNNER_REPO_URL: repoUrl,
|
|
12982
|
+
RUNNER_TOKEN: regToken,
|
|
12983
|
+
RUNNER_NAME: opts.name,
|
|
12984
|
+
RUNNER_MODE: "native"
|
|
12985
|
+
});
|
|
12986
|
+
await writeRunnerMeta(installDir, {
|
|
12987
|
+
name: opts.name,
|
|
12988
|
+
mode: "native",
|
|
12989
|
+
platform: platformOs,
|
|
12990
|
+
arch,
|
|
12991
|
+
installDir,
|
|
12992
|
+
service: opts.service ?? false,
|
|
12993
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12994
|
+
});
|
|
12995
|
+
if (opts.service) {
|
|
12996
|
+
await installRunnerService(installDir, opts.name, platformOs);
|
|
12997
|
+
} else {
|
|
12998
|
+
startRunnerForeground(installDir);
|
|
12999
|
+
}
|
|
13000
|
+
setupStep("Waiting for runner to appear online on GitHub (timeout 60s)...");
|
|
13001
|
+
await pollUntilOnline(ghConfig, opts.name, 6e4);
|
|
13002
|
+
success(
|
|
13003
|
+
`Runner '${opts.name}' registered and online (native/${platformOs}).`
|
|
13004
|
+
);
|
|
13005
|
+
}
|
|
13006
|
+
|
|
13007
|
+
// src/cli/workflow-switcher.ts
|
|
13008
|
+
import {
|
|
13009
|
+
existsSync as existsSync34,
|
|
13010
|
+
mkdirSync as mkdirSync21,
|
|
13011
|
+
readFileSync as readFileSync31,
|
|
13012
|
+
unlinkSync as unlinkSync6,
|
|
13013
|
+
writeFileSync as writeFileSync28
|
|
13014
|
+
} from "fs";
|
|
13015
|
+
import { dirname as dirname9, join as join33 } from "path";
|
|
12699
13016
|
var TARGET_LABEL = "[self-hosted, beastmode]";
|
|
12700
13017
|
var TARGET_WORKFLOWS = [
|
|
12701
13018
|
".github/workflows/test.yml",
|
|
@@ -12734,8 +13051,8 @@ function restoreRunsOn(content, originals) {
|
|
|
12734
13051
|
return newLines.join("\n");
|
|
12735
13052
|
}
|
|
12736
13053
|
async function switchWorkflows(projectDir) {
|
|
12737
|
-
const statePath =
|
|
12738
|
-
if (
|
|
13054
|
+
const statePath = join33(projectDir, STATE_FILE);
|
|
13055
|
+
if (existsSync34(statePath)) {
|
|
12739
13056
|
return { alreadySwitched: true, files: [] };
|
|
12740
13057
|
}
|
|
12741
13058
|
const state = {
|
|
@@ -12745,8 +13062,8 @@ async function switchWorkflows(projectDir) {
|
|
|
12745
13062
|
};
|
|
12746
13063
|
const resultFiles = [];
|
|
12747
13064
|
for (const relPath of TARGET_WORKFLOWS) {
|
|
12748
|
-
const absPath =
|
|
12749
|
-
if (!
|
|
13065
|
+
const absPath = join33(projectDir, relPath);
|
|
13066
|
+
if (!existsSync34(absPath)) {
|
|
12750
13067
|
throw new Error(`Workflow file not found: ${relPath}`);
|
|
12751
13068
|
}
|
|
12752
13069
|
const content = readFileSync31(absPath, "utf-8");
|
|
@@ -12754,17 +13071,17 @@ async function switchWorkflows(projectDir) {
|
|
|
12754
13071
|
if (originals.length === 0) {
|
|
12755
13072
|
throw new Error(`No runs-on found in ${relPath}`);
|
|
12756
13073
|
}
|
|
12757
|
-
|
|
13074
|
+
writeFileSync28(absPath, newContent, "utf-8");
|
|
12758
13075
|
state.files.push({ relativePath: relPath, originals });
|
|
12759
13076
|
resultFiles.push({ relativePath: relPath, jobCount: originals.length });
|
|
12760
13077
|
}
|
|
12761
|
-
|
|
12762
|
-
|
|
13078
|
+
mkdirSync21(dirname9(statePath), { recursive: true });
|
|
13079
|
+
writeFileSync28(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
12763
13080
|
return { alreadySwitched: false, files: resultFiles };
|
|
12764
13081
|
}
|
|
12765
13082
|
async function restoreWorkflows(projectDir) {
|
|
12766
|
-
const statePath =
|
|
12767
|
-
if (!
|
|
13083
|
+
const statePath = join33(projectDir, STATE_FILE);
|
|
13084
|
+
if (!existsSync34(statePath)) {
|
|
12768
13085
|
return { nothingToRestore: true, files: [] };
|
|
12769
13086
|
}
|
|
12770
13087
|
const state = JSON.parse(
|
|
@@ -12772,27 +13089,32 @@ async function restoreWorkflows(projectDir) {
|
|
|
12772
13089
|
);
|
|
12773
13090
|
const resultFiles = [];
|
|
12774
13091
|
for (const fileState of state.files) {
|
|
12775
|
-
const absPath =
|
|
12776
|
-
if (!
|
|
13092
|
+
const absPath = join33(projectDir, fileState.relativePath);
|
|
13093
|
+
if (!existsSync34(absPath)) {
|
|
12777
13094
|
throw new Error(`Workflow file not found: ${fileState.relativePath}`);
|
|
12778
13095
|
}
|
|
12779
13096
|
const content = readFileSync31(absPath, "utf-8");
|
|
12780
13097
|
const newContent = restoreRunsOn(content, fileState.originals);
|
|
12781
|
-
|
|
13098
|
+
writeFileSync28(absPath, newContent, "utf-8");
|
|
12782
13099
|
resultFiles.push({
|
|
12783
13100
|
relativePath: fileState.relativePath,
|
|
12784
13101
|
jobCount: fileState.originals.length
|
|
12785
13102
|
});
|
|
12786
13103
|
}
|
|
12787
|
-
|
|
13104
|
+
unlinkSync6(statePath);
|
|
12788
13105
|
return { nothingToRestore: false, files: resultFiles };
|
|
12789
13106
|
}
|
|
12790
13107
|
|
|
12791
13108
|
// src/cli/commands/runner-cmd.ts
|
|
12792
13109
|
var runnerCommand = new Command23("runner").description("Manage self-hosted GitHub Actions runners");
|
|
12793
13110
|
async function runnerSetupAction(opts) {
|
|
13111
|
+
if (opts.service !== void 0 && !opts.native) {
|
|
13112
|
+
throw new Error(
|
|
13113
|
+
"--service requires --native. The Docker runner always runs as a container service."
|
|
13114
|
+
);
|
|
13115
|
+
}
|
|
12794
13116
|
if (opts.native) {
|
|
12795
|
-
|
|
13117
|
+
await nativeRunnerSetup(opts);
|
|
12796
13118
|
return;
|
|
12797
13119
|
}
|
|
12798
13120
|
const ghConfig = resolveGitHubConfig();
|
|
@@ -12833,11 +13155,19 @@ async function runnerSetupAction(opts) {
|
|
|
12833
13155
|
await pollUntilOnline(ghConfig, opts.name, 6e4);
|
|
12834
13156
|
setupStep("Adding runner to docker-compose...");
|
|
12835
13157
|
await composeUpRunner();
|
|
13158
|
+
await writeRunnerMeta(".beastmode", {
|
|
13159
|
+
name: opts.name,
|
|
13160
|
+
mode: "docker",
|
|
13161
|
+
platform: process.platform,
|
|
13162
|
+
arch: process.arch,
|
|
13163
|
+
service: true,
|
|
13164
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13165
|
+
});
|
|
12836
13166
|
success(`Runner '${opts.name}' registered and online.`);
|
|
12837
13167
|
}
|
|
12838
13168
|
async function composeUpRunner() {
|
|
12839
13169
|
await new Promise((resolve21, reject) => {
|
|
12840
|
-
const child =
|
|
13170
|
+
const child = spawn3(
|
|
12841
13171
|
"docker",
|
|
12842
13172
|
["compose", "--profile", "runner", "up", "-d", "runner"],
|
|
12843
13173
|
{ stdio: ["ignore", "inherit", "inherit"] }
|
|
@@ -12854,8 +13184,19 @@ async function composeUpRunner() {
|
|
|
12854
13184
|
});
|
|
12855
13185
|
});
|
|
12856
13186
|
}
|
|
12857
|
-
runnerCommand.command("setup").description("Set up a self-hosted GitHub Actions runner").option("--repo <owner/repo>", "GitHub repo for runner registration").option("--name <name>", "Container + runner name", "beastmode-runner").option("--label <label>", "Additional runner label", "beastmode").option("--dry-run", "Print what would happen without mutating state").option(
|
|
12858
|
-
|
|
13187
|
+
runnerCommand.command("setup").description("Set up a self-hosted GitHub Actions runner").option("--repo <owner/repo>", "GitHub repo for runner registration").option("--name <name>", "Container + runner name", "beastmode-runner").option("--label <label>", "Additional runner label", "beastmode").option("--dry-run", "Print what would happen without mutating state").option(
|
|
13188
|
+
"--native",
|
|
13189
|
+
"Use native runner binary instead of Docker (downloads actions/runner)"
|
|
13190
|
+
).option(
|
|
13191
|
+
"--service",
|
|
13192
|
+
"Install as background service (systemd/launchd). Requires --native."
|
|
13193
|
+
).action(async (opts) => {
|
|
13194
|
+
try {
|
|
13195
|
+
await runnerSetupAction(opts);
|
|
13196
|
+
} catch (err) {
|
|
13197
|
+
error(err?.message ? String(err.message) : String(err));
|
|
13198
|
+
process.exitCode = 1;
|
|
13199
|
+
}
|
|
12859
13200
|
});
|
|
12860
13201
|
async function runnerStatusAction(opts) {
|
|
12861
13202
|
const container = await findContainerByName(opts.name);
|