@beastmode-develeap/beastmode 0.1.48 → 0.1.49
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 +373 -156
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6712,26 +6712,26 @@ var init_server = __esm({
|
|
|
6712
6712
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6713
6713
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6714
6714
|
import { z as z2 } from "zod";
|
|
6715
|
-
import { readFileSync as
|
|
6716
|
-
import { join as
|
|
6715
|
+
import { readFileSync as readFileSync27, writeFileSync as writeFileSync23, existsSync as existsSync30, readdirSync as readdirSync10, mkdirSync as mkdirSync18 } from "fs";
|
|
6716
|
+
import { join as join28, resolve as resolve18, basename as basename5 } from "path";
|
|
6717
6717
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
6718
6718
|
function readJsonFile2(filePath) {
|
|
6719
|
-
if (!
|
|
6719
|
+
if (!existsSync30(filePath)) return null;
|
|
6720
6720
|
try {
|
|
6721
|
-
return JSON.parse(
|
|
6721
|
+
return JSON.parse(readFileSync27(filePath, "utf-8"));
|
|
6722
6722
|
} catch {
|
|
6723
6723
|
return null;
|
|
6724
6724
|
}
|
|
6725
6725
|
}
|
|
6726
6726
|
function getFactoryPath() {
|
|
6727
6727
|
const envPath = process.env.BEASTMODE_FACTORY_PATH;
|
|
6728
|
-
if (envPath &&
|
|
6728
|
+
if (envPath && existsSync30(join28(envPath, ".beastmode", "factory.json"))) {
|
|
6729
6729
|
return envPath;
|
|
6730
6730
|
}
|
|
6731
6731
|
let dir = process.cwd();
|
|
6732
6732
|
const root = resolve18("/");
|
|
6733
6733
|
while (dir !== root) {
|
|
6734
|
-
if (
|
|
6734
|
+
if (existsSync30(join28(dir, ".beastmode", "factory.json"))) {
|
|
6735
6735
|
return dir;
|
|
6736
6736
|
}
|
|
6737
6737
|
const parent = resolve18(dir, "..");
|
|
@@ -6743,17 +6743,17 @@ function getFactoryPath() {
|
|
|
6743
6743
|
);
|
|
6744
6744
|
}
|
|
6745
6745
|
function readFactoryStatus(factoryDir) {
|
|
6746
|
-
const bmDir =
|
|
6746
|
+
const bmDir = join28(factoryDir, ".beastmode");
|
|
6747
6747
|
const factoryIdentity = FactoryIdentitySchema.parse(
|
|
6748
|
-
JSON.parse(
|
|
6748
|
+
JSON.parse(readFileSync27(join28(bmDir, "factory.json"), "utf-8"))
|
|
6749
6749
|
);
|
|
6750
|
-
const projectsDir =
|
|
6751
|
-
const projectCount =
|
|
6752
|
-
const lockPath =
|
|
6750
|
+
const projectsDir = join28(bmDir, "projects");
|
|
6751
|
+
const projectCount = existsSync30(projectsDir) ? readdirSync10(projectsDir).filter((f) => f.endsWith(".json")).length : 0;
|
|
6752
|
+
const lockPath = join28(bmDir, "extensions.lock");
|
|
6753
6753
|
let pluginNames = [];
|
|
6754
|
-
if (
|
|
6754
|
+
if (existsSync30(lockPath)) {
|
|
6755
6755
|
try {
|
|
6756
|
-
const lock = JSON.parse(
|
|
6756
|
+
const lock = JSON.parse(readFileSync27(lockPath, "utf-8"));
|
|
6757
6757
|
pluginNames = Object.keys(lock.plugins || {});
|
|
6758
6758
|
} catch {
|
|
6759
6759
|
}
|
|
@@ -6773,23 +6773,23 @@ function readFactoryStatus(factoryDir) {
|
|
|
6773
6773
|
skillCount = listSkills(factoryDir).length;
|
|
6774
6774
|
} catch {
|
|
6775
6775
|
}
|
|
6776
|
-
const runsDir =
|
|
6776
|
+
const runsDir = join28(factoryDir, "runs");
|
|
6777
6777
|
let runDirs = [];
|
|
6778
|
-
if (
|
|
6778
|
+
if (existsSync30(runsDir)) {
|
|
6779
6779
|
runDirs = readdirSync10(runsDir).filter((d) => {
|
|
6780
6780
|
try {
|
|
6781
|
-
return readdirSync10(
|
|
6781
|
+
return readdirSync10(join28(runsDir, d)).length > 0;
|
|
6782
6782
|
} catch {
|
|
6783
6783
|
return false;
|
|
6784
6784
|
}
|
|
6785
6785
|
}).sort();
|
|
6786
6786
|
}
|
|
6787
|
-
const pidFile =
|
|
6787
|
+
const pidFile = join28(bmDir, "daemon.pid");
|
|
6788
6788
|
let daemonPid = null;
|
|
6789
6789
|
let pidAlive = false;
|
|
6790
|
-
if (
|
|
6790
|
+
if (existsSync30(pidFile)) {
|
|
6791
6791
|
try {
|
|
6792
|
-
daemonPid = parseInt(
|
|
6792
|
+
daemonPid = parseInt(readFileSync27(pidFile, "utf-8").trim(), 10);
|
|
6793
6793
|
process.kill(daemonPid, 0);
|
|
6794
6794
|
pidAlive = true;
|
|
6795
6795
|
} catch {
|
|
@@ -6810,18 +6810,18 @@ function readFactoryStatus(factoryDir) {
|
|
|
6810
6810
|
return collectStatus(input);
|
|
6811
6811
|
}
|
|
6812
6812
|
function readBoardItems(factoryDir) {
|
|
6813
|
-
const filePath =
|
|
6814
|
-
if (!
|
|
6813
|
+
const filePath = join28(factoryDir, ".beastmode", "board.json");
|
|
6814
|
+
if (!existsSync30(filePath)) return [];
|
|
6815
6815
|
try {
|
|
6816
|
-
const raw = JSON.parse(
|
|
6816
|
+
const raw = JSON.parse(readFileSync27(filePath, "utf-8"));
|
|
6817
6817
|
return Array.isArray(raw.items) ? raw.items : [];
|
|
6818
6818
|
} catch {
|
|
6819
6819
|
return [];
|
|
6820
6820
|
}
|
|
6821
6821
|
}
|
|
6822
6822
|
function writeBoardItems(factoryDir, items) {
|
|
6823
|
-
const filePath =
|
|
6824
|
-
|
|
6823
|
+
const filePath = join28(factoryDir, ".beastmode", "board.json");
|
|
6824
|
+
writeFileSync23(filePath, JSON.stringify({ items }, null, 2) + "\n", "utf-8");
|
|
6825
6825
|
}
|
|
6826
6826
|
function createMcpServer() {
|
|
6827
6827
|
const server = new McpServer(
|
|
@@ -6844,8 +6844,8 @@ function createMcpServer() {
|
|
|
6844
6844
|
{ key_path: z2.string().describe("Dot-notation key path") },
|
|
6845
6845
|
async ({ key_path }) => {
|
|
6846
6846
|
const factoryDir = getFactoryPath();
|
|
6847
|
-
const configPath =
|
|
6848
|
-
const config =
|
|
6847
|
+
const configPath = join28(factoryDir, ".beastmode", "config.json");
|
|
6848
|
+
const config = existsSync30(configPath) ? JSON.parse(readFileSync27(configPath, "utf-8")) : generateDefaults();
|
|
6849
6849
|
try {
|
|
6850
6850
|
const value = configGet(config, key_path);
|
|
6851
6851
|
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
@@ -6863,11 +6863,11 @@ function createMcpServer() {
|
|
|
6863
6863
|
},
|
|
6864
6864
|
async ({ key_path, value }) => {
|
|
6865
6865
|
const factoryDir = getFactoryPath();
|
|
6866
|
-
const configPath =
|
|
6867
|
-
const config =
|
|
6866
|
+
const configPath = join28(factoryDir, ".beastmode", "config.json");
|
|
6867
|
+
const config = existsSync30(configPath) ? JSON.parse(readFileSync27(configPath, "utf-8")) : generateDefaults();
|
|
6868
6868
|
const coerced = coerceValue(value);
|
|
6869
6869
|
const updated = configSet(config, key_path, coerced);
|
|
6870
|
-
|
|
6870
|
+
writeFileSync23(configPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
6871
6871
|
return { content: [{ type: "text", text: `Set ${key_path} = ${JSON.stringify(coerced)}` }] };
|
|
6872
6872
|
}
|
|
6873
6873
|
);
|
|
@@ -6901,14 +6901,14 @@ function createMcpServer() {
|
|
|
6901
6901
|
{},
|
|
6902
6902
|
async () => {
|
|
6903
6903
|
const factoryDir = getFactoryPath();
|
|
6904
|
-
const runsDir =
|
|
6905
|
-
if (!
|
|
6904
|
+
const runsDir = join28(factoryDir, "runs");
|
|
6905
|
+
if (!existsSync30(runsDir)) {
|
|
6906
6906
|
return { content: [{ type: "text", text: "No runs directory found." }] };
|
|
6907
6907
|
}
|
|
6908
6908
|
const runDirs = readdirSync10(runsDir).sort().reverse();
|
|
6909
6909
|
const activeRuns = [];
|
|
6910
6910
|
for (const id of runDirs.slice(0, 10)) {
|
|
6911
|
-
const cp = readJsonFile2(
|
|
6911
|
+
const cp = readJsonFile2(join28(runsDir, id, "checkpoint.json"));
|
|
6912
6912
|
if (cp) {
|
|
6913
6913
|
activeRuns.push({ id, checkpoint: cp });
|
|
6914
6914
|
}
|
|
@@ -6922,17 +6922,17 @@ function createMcpServer() {
|
|
|
6922
6922
|
{ run_id: z2.string().describe("Run ID (directory name)") },
|
|
6923
6923
|
async ({ run_id }) => {
|
|
6924
6924
|
const factoryDir = getFactoryPath();
|
|
6925
|
-
const runDir =
|
|
6926
|
-
if (!
|
|
6925
|
+
const runDir = join28(factoryDir, "runs", run_id);
|
|
6926
|
+
if (!existsSync30(runDir)) {
|
|
6927
6927
|
return { content: [{ type: "text", text: `Run not found: ${run_id}` }], isError: true };
|
|
6928
6928
|
}
|
|
6929
|
-
const manifest = readJsonFile2(
|
|
6930
|
-
const checkpoint = readJsonFile2(
|
|
6931
|
-
const iterationsDir =
|
|
6929
|
+
const manifest = readJsonFile2(join28(runDir, "manifest.json"));
|
|
6930
|
+
const checkpoint = readJsonFile2(join28(runDir, "checkpoint.json"));
|
|
6931
|
+
const iterationsDir = join28(runDir, "iterations");
|
|
6932
6932
|
const iterations = [];
|
|
6933
|
-
if (
|
|
6933
|
+
if (existsSync30(iterationsDir)) {
|
|
6934
6934
|
for (const dir of readdirSync10(iterationsDir).sort()) {
|
|
6935
|
-
const satisfaction = readJsonFile2(
|
|
6935
|
+
const satisfaction = readJsonFile2(join28(iterationsDir, dir, "satisfaction.json"));
|
|
6936
6936
|
iterations.push({ number: parseInt(dir, 10), satisfaction });
|
|
6937
6937
|
}
|
|
6938
6938
|
}
|
|
@@ -6978,12 +6978,12 @@ function createMcpServer() {
|
|
|
6978
6978
|
{},
|
|
6979
6979
|
async () => {
|
|
6980
6980
|
const factoryDir = getFactoryPath();
|
|
6981
|
-
const bmDir =
|
|
6981
|
+
const bmDir = join28(factoryDir, ".beastmode");
|
|
6982
6982
|
let plugins = {};
|
|
6983
|
-
const lockPath =
|
|
6984
|
-
if (
|
|
6983
|
+
const lockPath = join28(bmDir, "extensions.lock");
|
|
6984
|
+
if (existsSync30(lockPath)) {
|
|
6985
6985
|
try {
|
|
6986
|
-
const lock = JSON.parse(
|
|
6986
|
+
const lock = JSON.parse(readFileSync27(lockPath, "utf-8"));
|
|
6987
6987
|
plugins = lock.plugins || {};
|
|
6988
6988
|
} catch {
|
|
6989
6989
|
}
|
|
@@ -7017,13 +7017,13 @@ function createMcpServer() {
|
|
|
7017
7017
|
{},
|
|
7018
7018
|
async () => {
|
|
7019
7019
|
const factoryDir = getFactoryPath();
|
|
7020
|
-
const projectsDir =
|
|
7021
|
-
if (!
|
|
7020
|
+
const projectsDir = join28(factoryDir, ".beastmode", "projects");
|
|
7021
|
+
if (!existsSync30(projectsDir)) {
|
|
7022
7022
|
return { content: [{ type: "text", text: "[]" }] };
|
|
7023
7023
|
}
|
|
7024
7024
|
const projects = readdirSync10(projectsDir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
7025
7025
|
try {
|
|
7026
|
-
return JSON.parse(
|
|
7026
|
+
return JSON.parse(readFileSync27(join28(projectsDir, f), "utf-8"));
|
|
7027
7027
|
} catch {
|
|
7028
7028
|
return null;
|
|
7029
7029
|
}
|
|
@@ -7038,7 +7038,7 @@ function createMcpServer() {
|
|
|
7038
7038
|
async ({ path: projectPath }) => {
|
|
7039
7039
|
const factoryDir = getFactoryPath();
|
|
7040
7040
|
const resolvedPath = resolve18(projectPath);
|
|
7041
|
-
if (!
|
|
7041
|
+
if (!existsSync30(resolvedPath)) {
|
|
7042
7042
|
return { content: [{ type: "text", text: `Directory not found: ${resolvedPath}` }], isError: true };
|
|
7043
7043
|
}
|
|
7044
7044
|
const projectName = basename5(resolvedPath);
|
|
@@ -7058,10 +7058,10 @@ function createMcpServer() {
|
|
|
7058
7058
|
deploy: { target: stack.suggested_deploy },
|
|
7059
7059
|
plugins: stack.suggested_plugins
|
|
7060
7060
|
};
|
|
7061
|
-
const projectsDir =
|
|
7061
|
+
const projectsDir = join28(factoryDir, ".beastmode", "projects");
|
|
7062
7062
|
mkdirSync18(projectsDir, { recursive: true });
|
|
7063
|
-
|
|
7064
|
-
|
|
7063
|
+
writeFileSync23(
|
|
7064
|
+
join28(projectsDir, `${projectName}.json`),
|
|
7065
7065
|
JSON.stringify(projectConfig, null, 2) + "\n",
|
|
7066
7066
|
"utf-8"
|
|
7067
7067
|
);
|
|
@@ -7476,8 +7476,8 @@ async function runInit(name, opts) {
|
|
|
7476
7476
|
throw new Error(`Factory already exists at ./${factoryName}. Use 'beastmode config' to modify.`);
|
|
7477
7477
|
}
|
|
7478
7478
|
if (opts.from) {
|
|
7479
|
-
const { readFileSync:
|
|
7480
|
-
const templateContent =
|
|
7479
|
+
const { readFileSync: readFileSync30 } = await import("fs");
|
|
7480
|
+
const templateContent = readFileSync30(resolve5(opts.from), "utf-8");
|
|
7481
7481
|
const { parseTemplate: parseTemplate2 } = await Promise.resolve().then(() => (init_template_importer(), template_importer_exports));
|
|
7482
7482
|
const template = parseTemplate2(templateContent);
|
|
7483
7483
|
info(`Importing from template: ${opts.from}`);
|
|
@@ -9683,21 +9683,203 @@ init_upgrader();
|
|
|
9683
9683
|
init_schemas();
|
|
9684
9684
|
init_version();
|
|
9685
9685
|
import { Command as Command12 } from "commander";
|
|
9686
|
-
import { existsSync as
|
|
9687
|
-
import { join as
|
|
9686
|
+
import { existsSync as existsSync26, readFileSync as readFileSync23, writeFileSync as writeFileSync19 } from "fs";
|
|
9687
|
+
import { join as join24, resolve as resolve16 } from "path";
|
|
9688
|
+
|
|
9689
|
+
// src/cli/utils/regenerate.ts
|
|
9690
|
+
import { existsSync as existsSync25, readFileSync as readFileSync22, writeFileSync as writeFileSync18, copyFileSync } from "fs";
|
|
9691
|
+
import { join as join23 } from "path";
|
|
9692
|
+
var RECOGNIZED_KEYS = /* @__PURE__ */ new Set([
|
|
9693
|
+
"PROJECT_DIR",
|
|
9694
|
+
"PROJECT_GITHUB_TOKEN",
|
|
9695
|
+
"GITHUB_TOKEN",
|
|
9696
|
+
// legacy alias — reads mapped to projectGithubToken
|
|
9697
|
+
"GHCR_PULL_TOKEN",
|
|
9698
|
+
"BEASTMODE_UI_PASSWORD",
|
|
9699
|
+
"ANTHROPIC_API_KEY",
|
|
9700
|
+
"PROJECT_REPO"
|
|
9701
|
+
]);
|
|
9702
|
+
function parseExistingEnv(envContent) {
|
|
9703
|
+
const values = {
|
|
9704
|
+
projectDir: null,
|
|
9705
|
+
projectGithubToken: null,
|
|
9706
|
+
ghcrPullToken: null,
|
|
9707
|
+
uiPassword: null,
|
|
9708
|
+
anthropicApiKey: null,
|
|
9709
|
+
projectRepoOverride: null,
|
|
9710
|
+
customLines: []
|
|
9711
|
+
};
|
|
9712
|
+
for (const rawLine of envContent.split(/\r?\n/)) {
|
|
9713
|
+
const line = rawLine.trimEnd();
|
|
9714
|
+
if (line === "" || line.startsWith("#")) {
|
|
9715
|
+
continue;
|
|
9716
|
+
}
|
|
9717
|
+
const eq = line.indexOf("=");
|
|
9718
|
+
if (eq === -1) {
|
|
9719
|
+
values.customLines.push(rawLine);
|
|
9720
|
+
continue;
|
|
9721
|
+
}
|
|
9722
|
+
const key = line.slice(0, eq).trim();
|
|
9723
|
+
const value = line.slice(eq + 1);
|
|
9724
|
+
if (!RECOGNIZED_KEYS.has(key)) {
|
|
9725
|
+
values.customLines.push(rawLine);
|
|
9726
|
+
continue;
|
|
9727
|
+
}
|
|
9728
|
+
switch (key) {
|
|
9729
|
+
case "PROJECT_DIR":
|
|
9730
|
+
values.projectDir = value;
|
|
9731
|
+
break;
|
|
9732
|
+
case "PROJECT_GITHUB_TOKEN":
|
|
9733
|
+
values.projectGithubToken = value;
|
|
9734
|
+
break;
|
|
9735
|
+
case "GITHUB_TOKEN":
|
|
9736
|
+
if (values.projectGithubToken === null) {
|
|
9737
|
+
values.projectGithubToken = value;
|
|
9738
|
+
}
|
|
9739
|
+
break;
|
|
9740
|
+
case "GHCR_PULL_TOKEN":
|
|
9741
|
+
values.ghcrPullToken = value;
|
|
9742
|
+
break;
|
|
9743
|
+
case "BEASTMODE_UI_PASSWORD":
|
|
9744
|
+
values.uiPassword = value;
|
|
9745
|
+
break;
|
|
9746
|
+
case "ANTHROPIC_API_KEY":
|
|
9747
|
+
values.anthropicApiKey = value;
|
|
9748
|
+
break;
|
|
9749
|
+
case "PROJECT_REPO":
|
|
9750
|
+
values.projectRepoOverride = value;
|
|
9751
|
+
break;
|
|
9752
|
+
}
|
|
9753
|
+
}
|
|
9754
|
+
return values;
|
|
9755
|
+
}
|
|
9756
|
+
function buildNewEnv(values) {
|
|
9757
|
+
const lines = [
|
|
9758
|
+
"# BeastMode environment \u2014 DO NOT COMMIT",
|
|
9759
|
+
"",
|
|
9760
|
+
"# \u2500\u2500 Project credentials (Gap 16 \u2014 two-PAT model) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
9761
|
+
"# PROJECT_GITHUB_TOKEN: used by the daemon and Claude to commit /",
|
|
9762
|
+
"# push / open PRs / merge / review against the target project repo.",
|
|
9763
|
+
"# Needs `repo` scope for private repos, `public_repo` for public.",
|
|
9764
|
+
"# Aliased to GITHUB_TOKEN for backward compat with the gh CLI and",
|
|
9765
|
+
"# any code that still reads GITHUB_TOKEN.",
|
|
9766
|
+
`PROJECT_GITHUB_TOKEN=${values.projectGithubToken ?? ""}`,
|
|
9767
|
+
`GITHUB_TOKEN=${values.projectGithubToken ?? ""}`,
|
|
9768
|
+
"",
|
|
9769
|
+
"# GHCR_PULL_TOKEN: used by `docker compose pull` to fetch the",
|
|
9770
|
+
"# beastmode factory images from ghcr.io/develeap/beastmode/*.",
|
|
9771
|
+
"# Maintainer-provided (shared across a team) or your own PAT with",
|
|
9772
|
+
"# `read:packages` scope added. Rotates rarely. May be the same as",
|
|
9773
|
+
"# PROJECT_GITHUB_TOKEN but is normally a separate dedicated token.",
|
|
9774
|
+
`GHCR_PULL_TOKEN=${values.ghcrPullToken ?? ""}`,
|
|
9775
|
+
"",
|
|
9776
|
+
`BEASTMODE_UI_PASSWORD=${values.uiPassword ?? ""}`,
|
|
9777
|
+
"",
|
|
9778
|
+
"# \u2500\u2500 Project location \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
9779
|
+
"# Required. The daemon's git operations target the git remote of",
|
|
9780
|
+
"# this path. The daemon auto-resolves the github repo from",
|
|
9781
|
+
"# `git -C $PROJECT_DIR remote get-url origin` at startup.",
|
|
9782
|
+
`PROJECT_DIR=${values.projectDir ?? ""}`,
|
|
9783
|
+
"",
|
|
9784
|
+
"# \u2500\u2500 Optional \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
9785
|
+
"# Uncomment for faster direct Anthropic API calls (bypasses the",
|
|
9786
|
+
"# Claude Code subscription auth via ~/.claude.json).",
|
|
9787
|
+
values.anthropicApiKey !== null ? `ANTHROPIC_API_KEY=${values.anthropicApiKey}` : "# ANTHROPIC_API_KEY=sk-ant-...",
|
|
9788
|
+
"",
|
|
9789
|
+
"# Last-resort override if the daemon's project_repo auto-resolution",
|
|
9790
|
+
"# (git remote get-url origin) doesn't work for your setup.",
|
|
9791
|
+
"# The daemon handles this automatically in >99% of cases.",
|
|
9792
|
+
values.projectRepoOverride !== null ? `PROJECT_REPO=${values.projectRepoOverride}` : "# PROJECT_REPO=owner/repo",
|
|
9793
|
+
""
|
|
9794
|
+
];
|
|
9795
|
+
if (values.customLines.length > 0) {
|
|
9796
|
+
lines.push(
|
|
9797
|
+
"# \u2500\u2500 Custom (preserved verbatim from pre-upgrade .env) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
9798
|
+
);
|
|
9799
|
+
lines.push(...values.customLines);
|
|
9800
|
+
lines.push("");
|
|
9801
|
+
}
|
|
9802
|
+
return lines.join("\n");
|
|
9803
|
+
}
|
|
9804
|
+
function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
|
|
9805
|
+
const envPath = join23(factoryDir, ".env");
|
|
9806
|
+
const composePath = join23(factoryDir, "docker-compose.yml");
|
|
9807
|
+
if (!existsSync25(envPath)) {
|
|
9808
|
+
throw new Error(
|
|
9809
|
+
`.env not found at ${envPath}. This does not look like a beastmode factory \u2014 run 'beastmode init' first.`
|
|
9810
|
+
);
|
|
9811
|
+
}
|
|
9812
|
+
if (!existsSync25(composePath)) {
|
|
9813
|
+
throw new Error(
|
|
9814
|
+
`docker-compose.yml not found at ${composePath}. This does not look like a beastmode factory \u2014 run 'beastmode init' first.`
|
|
9815
|
+
);
|
|
9816
|
+
}
|
|
9817
|
+
const existingEnv = readFileSync22(envPath, "utf-8");
|
|
9818
|
+
const values = parseExistingEnv(existingEnv);
|
|
9819
|
+
const missing = [];
|
|
9820
|
+
if (!values.projectDir) missing.push("PROJECT_DIR");
|
|
9821
|
+
if (!values.projectGithubToken) missing.push("PROJECT_GITHUB_TOKEN");
|
|
9822
|
+
if (!values.ghcrPullToken) missing.push("GHCR_PULL_TOKEN");
|
|
9823
|
+
if (!values.uiPassword) missing.push("BEASTMODE_UI_PASSWORD");
|
|
9824
|
+
if (missing.length > 0) {
|
|
9825
|
+
return {
|
|
9826
|
+
envChanged: false,
|
|
9827
|
+
composeChanged: false,
|
|
9828
|
+
envBackupPath: null,
|
|
9829
|
+
composeBackupPath: null,
|
|
9830
|
+
missingValues: missing
|
|
9831
|
+
};
|
|
9832
|
+
}
|
|
9833
|
+
const newEnv = buildNewEnv(values);
|
|
9834
|
+
const newCompose = generateComposeYaml("latest");
|
|
9835
|
+
const existingCompose = readFileSync22(composePath, "utf-8");
|
|
9836
|
+
const envChanged = newEnv !== existingEnv;
|
|
9837
|
+
const composeChanged = newCompose !== existingCompose;
|
|
9838
|
+
if (!envChanged && !composeChanged) {
|
|
9839
|
+
return {
|
|
9840
|
+
envChanged: false,
|
|
9841
|
+
composeChanged: false,
|
|
9842
|
+
envBackupPath: null,
|
|
9843
|
+
composeBackupPath: null,
|
|
9844
|
+
missingValues: []
|
|
9845
|
+
};
|
|
9846
|
+
}
|
|
9847
|
+
const timestamp = now.toISOString().replace(/[:.]/g, "-");
|
|
9848
|
+
let envBackupPath = null;
|
|
9849
|
+
let composeBackupPath = null;
|
|
9850
|
+
if (envChanged) {
|
|
9851
|
+
envBackupPath = `${envPath}.backup.${timestamp}`;
|
|
9852
|
+
copyFileSync(envPath, envBackupPath);
|
|
9853
|
+
writeFileSync18(envPath, newEnv, "utf-8");
|
|
9854
|
+
}
|
|
9855
|
+
if (composeChanged) {
|
|
9856
|
+
composeBackupPath = `${composePath}.backup.${timestamp}`;
|
|
9857
|
+
copyFileSync(composePath, composeBackupPath);
|
|
9858
|
+
writeFileSync18(composePath, newCompose, "utf-8");
|
|
9859
|
+
}
|
|
9860
|
+
return {
|
|
9861
|
+
envChanged,
|
|
9862
|
+
composeChanged,
|
|
9863
|
+
envBackupPath,
|
|
9864
|
+
composeBackupPath,
|
|
9865
|
+
missingValues: []
|
|
9866
|
+
};
|
|
9867
|
+
}
|
|
9868
|
+
|
|
9869
|
+
// src/cli/commands/upgrade.ts
|
|
9688
9870
|
function readIdentity(factoryDir) {
|
|
9689
|
-
const path =
|
|
9690
|
-
if (!
|
|
9871
|
+
const path = join24(factoryDir, ".beastmode", "factory.json");
|
|
9872
|
+
if (!existsSync26(path)) {
|
|
9691
9873
|
throw new Error("No factory.json found. Run beastmode init first.");
|
|
9692
9874
|
}
|
|
9693
|
-
return FactoryIdentitySchema.parse(JSON.parse(
|
|
9875
|
+
return FactoryIdentitySchema.parse(JSON.parse(readFileSync23(path, "utf-8")));
|
|
9694
9876
|
}
|
|
9695
9877
|
function readConfig3(factoryDir) {
|
|
9696
|
-
const path =
|
|
9697
|
-
if (!
|
|
9878
|
+
const path = join24(factoryDir, ".beastmode", "config.json");
|
|
9879
|
+
if (!existsSync26(path)) {
|
|
9698
9880
|
return {};
|
|
9699
9881
|
}
|
|
9700
|
-
return JSON.parse(
|
|
9882
|
+
return JSON.parse(readFileSync23(path, "utf-8"));
|
|
9701
9883
|
}
|
|
9702
9884
|
function upgradeCheckAction(factoryDir) {
|
|
9703
9885
|
const identity = readIdentity(factoryDir);
|
|
@@ -9709,20 +9891,55 @@ function upgradeAction(factoryDir, migrateOnly = false) {
|
|
|
9709
9891
|
const targetVersion = migrateOnly ? identity.engine_version : ENGINE_VERSION;
|
|
9710
9892
|
const result = performUpgrade(identity, config, targetVersion, SCHEMA_VERSION);
|
|
9711
9893
|
if (result.changes.length > 0) {
|
|
9712
|
-
|
|
9713
|
-
|
|
9894
|
+
writeFileSync19(
|
|
9895
|
+
join24(factoryDir, ".beastmode", "factory.json"),
|
|
9714
9896
|
JSON.stringify(result.updatedIdentity, null, 2) + "\n"
|
|
9715
9897
|
);
|
|
9716
|
-
|
|
9717
|
-
|
|
9898
|
+
writeFileSync19(
|
|
9899
|
+
join24(factoryDir, ".beastmode", "config.json"),
|
|
9718
9900
|
JSON.stringify(result.updatedConfig, null, 2) + "\n"
|
|
9719
9901
|
);
|
|
9720
9902
|
}
|
|
9721
9903
|
return result;
|
|
9722
9904
|
}
|
|
9723
|
-
var upgradeCommand = new Command12("upgrade").description("Upgrade engine version and migrate config").option("--check", "Check for updates without modifying").option("--migrate-only", "Migrate config without bumping engine version").
|
|
9905
|
+
var upgradeCommand = new Command12("upgrade").description("Upgrade engine version and migrate config").option("--check", "Check for updates without modifying").option("--migrate-only", "Migrate config without bumping engine version").option(
|
|
9906
|
+
"--files",
|
|
9907
|
+
"Regenerate .env and docker-compose.yml from current templates while preserving user values (Gap 4). Backs up existing files with a timestamped suffix before writing."
|
|
9908
|
+
).action((opts) => {
|
|
9724
9909
|
const factoryDir = resolve16(".");
|
|
9725
9910
|
try {
|
|
9911
|
+
if (opts.files) {
|
|
9912
|
+
header("Upgrade: Regenerate factory files");
|
|
9913
|
+
console.log();
|
|
9914
|
+
const result2 = regenerateFactoryFiles(factoryDir);
|
|
9915
|
+
if (result2.missingValues.length > 0) {
|
|
9916
|
+
error(
|
|
9917
|
+
"Cannot regenerate \u2014 required values missing from existing .env:"
|
|
9918
|
+
);
|
|
9919
|
+
for (const key of result2.missingValues) {
|
|
9920
|
+
error(` ${key}`);
|
|
9921
|
+
}
|
|
9922
|
+
error(
|
|
9923
|
+
"Fix the .env by hand or re-run 'beastmode init' with the missing values, then try again."
|
|
9924
|
+
);
|
|
9925
|
+
process.exit(1);
|
|
9926
|
+
}
|
|
9927
|
+
if (!result2.envChanged && !result2.composeChanged) {
|
|
9928
|
+
success("Already up to date. No files needed regeneration.");
|
|
9929
|
+
return;
|
|
9930
|
+
}
|
|
9931
|
+
if (result2.envChanged) {
|
|
9932
|
+
info(` .env rewritten (backup: ${result2.envBackupPath})`);
|
|
9933
|
+
}
|
|
9934
|
+
if (result2.composeChanged) {
|
|
9935
|
+
info(
|
|
9936
|
+
` docker-compose.yml rewritten (backup: ${result2.composeBackupPath})`
|
|
9937
|
+
);
|
|
9938
|
+
}
|
|
9939
|
+
console.log();
|
|
9940
|
+
success("Factory files regenerated. Run 'beastmode doctor' to verify.");
|
|
9941
|
+
return;
|
|
9942
|
+
}
|
|
9726
9943
|
if (opts.check) {
|
|
9727
9944
|
const check = upgradeCheckAction(factoryDir);
|
|
9728
9945
|
header("Upgrade Check");
|
|
@@ -9764,8 +9981,8 @@ var upgradeCommand = new Command12("upgrade").description("Upgrade engine versio
|
|
|
9764
9981
|
|
|
9765
9982
|
// src/cli/commands/migrate.ts
|
|
9766
9983
|
import { Command as Command13 } from "commander";
|
|
9767
|
-
import { resolve as resolve17, join as
|
|
9768
|
-
import { existsSync as
|
|
9984
|
+
import { resolve as resolve17, join as join25 } from "path";
|
|
9985
|
+
import { existsSync as existsSync27, readFileSync as readFileSync24, mkdirSync as mkdirSync15, writeFileSync as writeFileSync20 } from "fs";
|
|
9769
9986
|
init_migrator();
|
|
9770
9987
|
var migrateCommand = new Command13("migrate").description("Migrate a daemon config into a .beastmode/ factory").option("--config <path>", "Path to beastmode.daemon.json").option("--dry-run", "Show what would be created without writing files").action(async (opts) => {
|
|
9771
9988
|
try {
|
|
@@ -9778,7 +9995,7 @@ var migrateCommand = new Command13("migrate").description("Migrate a daemon conf
|
|
|
9778
9995
|
async function runMigrate(opts) {
|
|
9779
9996
|
const cwd = process.cwd();
|
|
9780
9997
|
const configPath = opts.config ? resolve17(opts.config) : resolve17(cwd, "config", "beastmode.daemon.json");
|
|
9781
|
-
if (!
|
|
9998
|
+
if (!existsSync27(configPath)) {
|
|
9782
9999
|
throw new Error(
|
|
9783
10000
|
`Daemon config not found at ${configPath}
|
|
9784
10001
|
Use --config <path> to specify a different location.`
|
|
@@ -9786,25 +10003,25 @@ async function runMigrate(opts) {
|
|
|
9786
10003
|
}
|
|
9787
10004
|
header("BeastMode Migrate");
|
|
9788
10005
|
info(`Reading daemon config from: ${configPath}`);
|
|
9789
|
-
const configContent =
|
|
10006
|
+
const configContent = readFileSync24(configPath, "utf-8");
|
|
9790
10007
|
const daemonConfig = parseDaemonConfig(configContent);
|
|
9791
|
-
const runsDir =
|
|
10008
|
+
const runsDir = join25(cwd, "runs");
|
|
9792
10009
|
let runDirs = [];
|
|
9793
10010
|
const checkpoints = /* @__PURE__ */ new Map();
|
|
9794
|
-
if (
|
|
10011
|
+
if (existsSync27(runsDir)) {
|
|
9795
10012
|
const { readdirSync: readdirSync11 } = await import("fs");
|
|
9796
10013
|
runDirs = readdirSync11(runsDir).filter((d) => {
|
|
9797
10014
|
try {
|
|
9798
|
-
return readdirSync11(
|
|
10015
|
+
return readdirSync11(join25(runsDir, d)).length > 0;
|
|
9799
10016
|
} catch {
|
|
9800
10017
|
return false;
|
|
9801
10018
|
}
|
|
9802
10019
|
});
|
|
9803
10020
|
for (const dir of runDirs) {
|
|
9804
|
-
const cpPath =
|
|
9805
|
-
if (
|
|
10021
|
+
const cpPath = join25(runsDir, dir, "checkpoint.json");
|
|
10022
|
+
if (existsSync27(cpPath)) {
|
|
9806
10023
|
try {
|
|
9807
|
-
const cp = JSON.parse(
|
|
10024
|
+
const cp = JSON.parse(readFileSync24(cpPath, "utf-8"));
|
|
9808
10025
|
checkpoints.set(dir, cp);
|
|
9809
10026
|
} catch {
|
|
9810
10027
|
}
|
|
@@ -9861,28 +10078,28 @@ async function runMigrate(opts) {
|
|
|
9861
10078
|
warn("Dry run \u2014 no files written.");
|
|
9862
10079
|
return;
|
|
9863
10080
|
}
|
|
9864
|
-
const bmDir =
|
|
9865
|
-
if (
|
|
10081
|
+
const bmDir = join25(cwd, ".beastmode");
|
|
10082
|
+
if (existsSync27(bmDir)) {
|
|
9866
10083
|
throw new Error(
|
|
9867
10084
|
"A .beastmode/ directory already exists. Remove it first to re-migrate."
|
|
9868
10085
|
);
|
|
9869
10086
|
}
|
|
9870
10087
|
for (const file of files) {
|
|
9871
|
-
const fullPath =
|
|
10088
|
+
const fullPath = join25(cwd, file.path);
|
|
9872
10089
|
const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
|
|
9873
10090
|
mkdirSync15(dir, { recursive: true });
|
|
9874
|
-
|
|
10091
|
+
writeFileSync20(fullPath, file.content, "utf-8");
|
|
9875
10092
|
}
|
|
9876
|
-
const runsSymlinkTarget =
|
|
9877
|
-
const bmRunsPath =
|
|
9878
|
-
if (
|
|
10093
|
+
const runsSymlinkTarget = join25(cwd, "runs");
|
|
10094
|
+
const bmRunsPath = join25(cwd, "runs");
|
|
10095
|
+
if (existsSync27(runsSymlinkTarget)) {
|
|
9879
10096
|
info("Existing runs/ directory preserved in-place.");
|
|
9880
10097
|
}
|
|
9881
|
-
const boardPath =
|
|
9882
|
-
if (!
|
|
9883
|
-
|
|
10098
|
+
const boardPath = join25(bmDir, "board.json");
|
|
10099
|
+
if (!existsSync27(boardPath)) {
|
|
10100
|
+
writeFileSync20(boardPath, JSON.stringify({ items: [] }, null, 2), "utf-8");
|
|
9884
10101
|
}
|
|
9885
|
-
mkdirSync15(
|
|
10102
|
+
mkdirSync15(join25(bmDir, ".cache"), { recursive: true });
|
|
9886
10103
|
console.log();
|
|
9887
10104
|
success("Migration complete!");
|
|
9888
10105
|
info(`Factory created at: ${bmDir}`);
|
|
@@ -9900,8 +10117,8 @@ async function runMigrate(opts) {
|
|
|
9900
10117
|
|
|
9901
10118
|
// src/cli/commands/run.ts
|
|
9902
10119
|
import { Command as Command14 } from "commander";
|
|
9903
|
-
import { join as
|
|
9904
|
-
import { existsSync as
|
|
10120
|
+
import { join as join26 } from "path";
|
|
10121
|
+
import { existsSync as existsSync28, readFileSync as readFileSync25, writeFileSync as writeFileSync21, mkdirSync as mkdirSync16 } from "fs";
|
|
9905
10122
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9906
10123
|
init_bridge();
|
|
9907
10124
|
init_schemas();
|
|
@@ -9923,18 +10140,18 @@ async function runPipeline(projectName, opts) {
|
|
|
9923
10140
|
"No BeastMode factory found. Run 'beastmode init' first."
|
|
9924
10141
|
);
|
|
9925
10142
|
}
|
|
9926
|
-
const bmDir =
|
|
10143
|
+
const bmDir = join26(factoryDir, ".beastmode");
|
|
9927
10144
|
header("BeastMode Run");
|
|
9928
|
-
const configPath =
|
|
9929
|
-
if (!
|
|
10145
|
+
const configPath = join26(bmDir, "config.json");
|
|
10146
|
+
if (!existsSync28(configPath)) {
|
|
9930
10147
|
throw new Error("Factory config not found. Run 'beastmode init' first.");
|
|
9931
10148
|
}
|
|
9932
10149
|
const factoryConfig = FactoryConfigSchema.parse(
|
|
9933
|
-
JSON.parse(
|
|
10150
|
+
JSON.parse(readFileSync25(configPath, "utf-8"))
|
|
9934
10151
|
);
|
|
9935
10152
|
let projectConfig = null;
|
|
9936
|
-
const projectsDir =
|
|
9937
|
-
if (
|
|
10153
|
+
const projectsDir = join26(bmDir, "projects");
|
|
10154
|
+
if (existsSync28(projectsDir)) {
|
|
9938
10155
|
const { readdirSync: readdirSync11 } = await import("fs");
|
|
9939
10156
|
const projectFiles = readdirSync11(projectsDir).filter(
|
|
9940
10157
|
(f) => f.endsWith(".json")
|
|
@@ -9947,20 +10164,20 @@ async function runPipeline(projectName, opts) {
|
|
|
9947
10164
|
throw new Error(`Project not found: ${projectName}`);
|
|
9948
10165
|
}
|
|
9949
10166
|
projectConfig = ProjectConfigSchema.parse(
|
|
9950
|
-
JSON.parse(
|
|
10167
|
+
JSON.parse(readFileSync25(join26(projectsDir, file), "utf-8"))
|
|
9951
10168
|
);
|
|
9952
10169
|
} else if (projectFiles.length > 0) {
|
|
9953
10170
|
projectConfig = ProjectConfigSchema.parse(
|
|
9954
|
-
JSON.parse(
|
|
10171
|
+
JSON.parse(readFileSync25(join26(projectsDir, projectFiles[0]), "utf-8"))
|
|
9955
10172
|
);
|
|
9956
10173
|
info(`Using project: ${projectConfig.name}`);
|
|
9957
10174
|
}
|
|
9958
10175
|
}
|
|
9959
|
-
const boardPath =
|
|
10176
|
+
const boardPath = join26(bmDir, "board.json");
|
|
9960
10177
|
let boardItems = [];
|
|
9961
|
-
if (
|
|
10178
|
+
if (existsSync28(boardPath)) {
|
|
9962
10179
|
try {
|
|
9963
|
-
const raw = JSON.parse(
|
|
10180
|
+
const raw = JSON.parse(readFileSync25(boardPath, "utf-8"));
|
|
9964
10181
|
boardItems = Array.isArray(raw.items) ? raw.items : [];
|
|
9965
10182
|
} catch {
|
|
9966
10183
|
boardItems = [];
|
|
@@ -9977,13 +10194,13 @@ async function runPipeline(projectName, opts) {
|
|
|
9977
10194
|
updated_at: now
|
|
9978
10195
|
};
|
|
9979
10196
|
boardItems.push(task);
|
|
9980
|
-
|
|
10197
|
+
writeFileSync21(boardPath, JSON.stringify({ items: boardItems }, null, 2), "utf-8");
|
|
9981
10198
|
info(`Created task: ${task.title} (${taskId})`);
|
|
9982
|
-
const cacheDir =
|
|
10199
|
+
const cacheDir = join26(bmDir, ".cache");
|
|
9983
10200
|
mkdirSync16(cacheDir, { recursive: true });
|
|
9984
|
-
const daemonConfigPath =
|
|
10201
|
+
const daemonConfigPath = join26(cacheDir, "daemon.json");
|
|
9985
10202
|
const daemonConfig = generateDaemonConfig(factoryConfig, projectConfig, factoryDir);
|
|
9986
|
-
|
|
10203
|
+
writeFileSync21(daemonConfigPath, JSON.stringify(daemonConfig, null, 2), "utf-8");
|
|
9987
10204
|
info(`Generated daemon config at: ${daemonConfigPath}`);
|
|
9988
10205
|
const { execSync: execSync9 } = await import("child_process");
|
|
9989
10206
|
let pythonAvailable = false;
|
|
@@ -10007,7 +10224,7 @@ async function runPipeline(projectName, opts) {
|
|
|
10007
10224
|
const daemonPaths = findPythonDaemonPaths(envPath, factoryDir);
|
|
10008
10225
|
let daemonFound = false;
|
|
10009
10226
|
for (const p of daemonPaths) {
|
|
10010
|
-
if (
|
|
10227
|
+
if (existsSync28(p)) {
|
|
10011
10228
|
daemonFound = true;
|
|
10012
10229
|
break;
|
|
10013
10230
|
}
|
|
@@ -10034,7 +10251,7 @@ async function runPipeline(projectName, opts) {
|
|
|
10034
10251
|
const startTime = Date.now();
|
|
10035
10252
|
const pollInterval = setInterval(() => {
|
|
10036
10253
|
try {
|
|
10037
|
-
const board = JSON.parse(
|
|
10254
|
+
const board = JSON.parse(readFileSync25(boardPath, "utf-8"));
|
|
10038
10255
|
const items = Array.isArray(board.items) ? board.items : [];
|
|
10039
10256
|
const taskItem = items.find((i) => i.id === taskId);
|
|
10040
10257
|
if (taskItem) {
|
|
@@ -10076,8 +10293,8 @@ async function runPipeline(projectName, opts) {
|
|
|
10076
10293
|
|
|
10077
10294
|
// src/cli/commands/daemon-cmd.ts
|
|
10078
10295
|
import { Command as Command15 } from "commander";
|
|
10079
|
-
import { join as
|
|
10080
|
-
import { existsSync as
|
|
10296
|
+
import { join as join27 } from "path";
|
|
10297
|
+
import { existsSync as existsSync29, readFileSync as readFileSync26, writeFileSync as writeFileSync22, mkdirSync as mkdirSync17 } from "fs";
|
|
10081
10298
|
init_bridge();
|
|
10082
10299
|
init_schemas();
|
|
10083
10300
|
var daemonCommand = new Command15("daemon").description("Start the BeastMode daemon via bridge").option("--dry-run", "Generate config but don't start daemon").option(
|
|
@@ -10099,38 +10316,38 @@ async function runDaemon(opts) {
|
|
|
10099
10316
|
"No BeastMode factory found. Run 'beastmode init' first."
|
|
10100
10317
|
);
|
|
10101
10318
|
}
|
|
10102
|
-
const bmDir =
|
|
10319
|
+
const bmDir = join27(factoryDir, ".beastmode");
|
|
10103
10320
|
header("BeastMode Daemon");
|
|
10104
|
-
const configPath =
|
|
10105
|
-
if (!
|
|
10321
|
+
const configPath = join27(bmDir, "config.json");
|
|
10322
|
+
if (!existsSync29(configPath)) {
|
|
10106
10323
|
throw new Error("Factory config not found. Run 'beastmode init' first.");
|
|
10107
10324
|
}
|
|
10108
10325
|
const factoryConfig = FactoryConfigSchema.parse(
|
|
10109
|
-
JSON.parse(
|
|
10326
|
+
JSON.parse(readFileSync26(configPath, "utf-8"))
|
|
10110
10327
|
);
|
|
10111
10328
|
let projectConfig = null;
|
|
10112
|
-
const projectsDir =
|
|
10113
|
-
if (
|
|
10329
|
+
const projectsDir = join27(bmDir, "projects");
|
|
10330
|
+
if (existsSync29(projectsDir)) {
|
|
10114
10331
|
const { readdirSync: readdirSync11 } = await import("fs");
|
|
10115
10332
|
const projectFiles = readdirSync11(projectsDir).filter(
|
|
10116
10333
|
(f) => f.endsWith(".json")
|
|
10117
10334
|
);
|
|
10118
10335
|
if (projectFiles.length > 0) {
|
|
10119
10336
|
projectConfig = ProjectConfigSchema.parse(
|
|
10120
|
-
JSON.parse(
|
|
10337
|
+
JSON.parse(readFileSync26(join27(projectsDir, projectFiles[0]), "utf-8"))
|
|
10121
10338
|
);
|
|
10122
10339
|
info(`Using project: ${projectConfig.name}`);
|
|
10123
10340
|
}
|
|
10124
10341
|
}
|
|
10125
|
-
const cacheDir =
|
|
10342
|
+
const cacheDir = join27(bmDir, ".cache");
|
|
10126
10343
|
mkdirSync17(cacheDir, { recursive: true });
|
|
10127
|
-
const daemonConfigPath =
|
|
10344
|
+
const daemonConfigPath = join27(cacheDir, "daemon.json");
|
|
10128
10345
|
const daemonConfig = generateDaemonConfig(
|
|
10129
10346
|
factoryConfig,
|
|
10130
10347
|
projectConfig,
|
|
10131
10348
|
factoryDir
|
|
10132
10349
|
);
|
|
10133
|
-
|
|
10350
|
+
writeFileSync22(
|
|
10134
10351
|
daemonConfigPath,
|
|
10135
10352
|
JSON.stringify(daemonConfig, null, 2),
|
|
10136
10353
|
"utf-8"
|
|
@@ -10169,7 +10386,7 @@ async function runDaemon(opts) {
|
|
|
10169
10386
|
});
|
|
10170
10387
|
info(`Starting daemon: ${pythonCmd} ${cmd.args.join(" ")}`);
|
|
10171
10388
|
console.log();
|
|
10172
|
-
const pidFile =
|
|
10389
|
+
const pidFile = join27(bmDir, "daemon.pid");
|
|
10173
10390
|
const { spawn } = await import("child_process");
|
|
10174
10391
|
const child = spawn(pythonCmd, cmd.args, {
|
|
10175
10392
|
stdio: "inherit",
|
|
@@ -10180,7 +10397,7 @@ async function runDaemon(opts) {
|
|
|
10180
10397
|
}
|
|
10181
10398
|
});
|
|
10182
10399
|
if (child.pid) {
|
|
10183
|
-
|
|
10400
|
+
writeFileSync22(pidFile, String(child.pid), "utf-8");
|
|
10184
10401
|
}
|
|
10185
10402
|
const signalHandler = (signal) => {
|
|
10186
10403
|
info(`Forwarding ${signal} to daemon...`);
|
|
@@ -10218,8 +10435,8 @@ var mcpCommand = new Command16("mcp").description("Start the BeastMode MCP serve
|
|
|
10218
10435
|
|
|
10219
10436
|
// src/cli/commands/deploy.ts
|
|
10220
10437
|
import { Command as Command17 } from "commander";
|
|
10221
|
-
import { resolve as resolve19, join as
|
|
10222
|
-
import { existsSync as
|
|
10438
|
+
import { resolve as resolve19, join as join29 } from "path";
|
|
10439
|
+
import { existsSync as existsSync31, writeFileSync as writeFileSync24, readFileSync as readFileSync28 } from "fs";
|
|
10223
10440
|
import { execSync as execSync7 } from "child_process";
|
|
10224
10441
|
import { randomBytes } from "crypto";
|
|
10225
10442
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -10274,8 +10491,8 @@ async function runDeploy(opts) {
|
|
|
10274
10491
|
process.exit(1);
|
|
10275
10492
|
}
|
|
10276
10493
|
const factoryDir = resolve19(".");
|
|
10277
|
-
const bmDir =
|
|
10278
|
-
if (!
|
|
10494
|
+
const bmDir = join29(factoryDir, ".beastmode");
|
|
10495
|
+
if (!existsSync31(bmDir)) {
|
|
10279
10496
|
error(
|
|
10280
10497
|
"No .beastmode directory found. Run 'beastmode init' or 'beastmode migrate' first."
|
|
10281
10498
|
);
|
|
@@ -10299,33 +10516,33 @@ async function runDeploy(opts) {
|
|
|
10299
10516
|
"../../index.js"
|
|
10300
10517
|
);
|
|
10301
10518
|
}
|
|
10302
|
-
const boardVenvPython =
|
|
10303
|
-
const daemonVenvPython =
|
|
10304
|
-
const boardPython =
|
|
10305
|
-
const daemonPython =
|
|
10519
|
+
const boardVenvPython = join29(factoryDir, "board", ".venv", "bin", "python");
|
|
10520
|
+
const daemonVenvPython = join29(factoryDir, "daemon", ".venv", "bin", "python");
|
|
10521
|
+
const boardPython = existsSync31(boardVenvPython) ? boardVenvPython : "python3";
|
|
10522
|
+
const daemonPython = existsSync31(daemonVenvPython) ? daemonVenvPython : "python3";
|
|
10306
10523
|
const user = execSync7("whoami", { encoding: "utf-8" }).trim();
|
|
10307
10524
|
const home = process.env.HOME || `/home/${user}`;
|
|
10308
10525
|
const port = opts.port;
|
|
10309
10526
|
const host = opts.host;
|
|
10310
|
-
const dotEnv =
|
|
10311
|
-
const secretsEnv =
|
|
10312
|
-
const envContent =
|
|
10313
|
-
const secretsContent =
|
|
10527
|
+
const dotEnv = join29(factoryDir, ".env");
|
|
10528
|
+
const secretsEnv = join29(bmDir, "secrets.env.local");
|
|
10529
|
+
const envContent = existsSync31(dotEnv) ? readFileSync28(dotEnv, "utf-8") : "";
|
|
10530
|
+
const secretsContent = existsSync31(secretsEnv) ? readFileSync28(secretsEnv, "utf-8") : "";
|
|
10314
10531
|
const hasPassword = envContent.includes("BEASTMODE_UI_PASSWORD=") && !envContent.includes("BEASTMODE_UI_PASSWORD=\n") || secretsContent.includes("BEASTMODE_UI_PASSWORD=") && !secretsContent.includes("BEASTMODE_UI_PASSWORD=\n") || !!process.env.BEASTMODE_UI_PASSWORD;
|
|
10315
10532
|
if (!hasPassword && opts.host === "0.0.0.0") {
|
|
10316
10533
|
const generated = randomBytes(18).toString("base64url");
|
|
10317
|
-
const target =
|
|
10534
|
+
const target = existsSync31(secretsEnv) ? secretsEnv : dotEnv;
|
|
10318
10535
|
const append = `
|
|
10319
10536
|
# Auto-generated board UI password (deploy)
|
|
10320
10537
|
BEASTMODE_UI_PASSWORD=${generated}
|
|
10321
10538
|
`;
|
|
10322
|
-
|
|
10539
|
+
writeFileSync24(target, (existsSync31(target) ? readFileSync28(target, "utf-8") : "") + append, "utf-8");
|
|
10323
10540
|
info(`Board UI password auto-generated and saved to ${target}`);
|
|
10324
10541
|
success(`Password: ${generated}`);
|
|
10325
10542
|
info("Save this password \u2014 you'll need it to access the board UI.");
|
|
10326
10543
|
}
|
|
10327
10544
|
const envFileLines = [];
|
|
10328
|
-
if (
|
|
10545
|
+
if (existsSync31(secretsEnv)) {
|
|
10329
10546
|
envFileLines.push(`EnvironmentFile=${secretsEnv}`);
|
|
10330
10547
|
} else {
|
|
10331
10548
|
envFileLines.push(`# No secrets.env.local found at time of deploy`);
|
|
@@ -10408,7 +10625,7 @@ BEASTMODE_UI_PASSWORD=${generated}
|
|
|
10408
10625
|
info(`Writing service file to ${svc.path}...`);
|
|
10409
10626
|
try {
|
|
10410
10627
|
const tmpPath = `/tmp/${svc.name}.service`;
|
|
10411
|
-
|
|
10628
|
+
writeFileSync24(tmpPath, svc.content, "utf-8");
|
|
10412
10629
|
execSync7(`sudo cp ${tmpPath} ${svc.path}`, { stdio: "inherit" });
|
|
10413
10630
|
success(`${svc.name} service file installed`);
|
|
10414
10631
|
} catch {
|
|
@@ -10537,9 +10754,9 @@ async function deployToAWS(opts) {
|
|
|
10537
10754
|
}
|
|
10538
10755
|
const __filename2 = fileURLToPath3(import.meta.url);
|
|
10539
10756
|
const __dirname2 = dirname7(__filename2);
|
|
10540
|
-
const templatePath =
|
|
10541
|
-
const cwdTemplate =
|
|
10542
|
-
const template =
|
|
10757
|
+
const templatePath = join29(__dirname2, "..", "..", "infra", "cloudformation", "beastmode.yaml");
|
|
10758
|
+
const cwdTemplate = join29(process.cwd(), "infra", "cloudformation", "beastmode.yaml");
|
|
10759
|
+
const template = existsSync31(templatePath) ? templatePath : existsSync31(cwdTemplate) ? cwdTemplate : null;
|
|
10543
10760
|
if (!template) {
|
|
10544
10761
|
error("CloudFormation template not found. Expected at infra/cloudformation/beastmode.yaml");
|
|
10545
10762
|
process.exit(1);
|
|
@@ -10643,15 +10860,15 @@ async function deployToAWS(opts) {
|
|
|
10643
10860
|
// src/cli/commands/sync-claude-creds.ts
|
|
10644
10861
|
import { Command as Command18 } from "commander";
|
|
10645
10862
|
import { execSync as execSync8, spawnSync as spawnSync2 } from "child_process";
|
|
10646
|
-
import { writeFileSync as
|
|
10647
|
-
import { join as
|
|
10863
|
+
import { writeFileSync as writeFileSync25, chmodSync, mkdirSync as mkdirSync19, existsSync as existsSync32, unlinkSync as unlinkSync4 } from "fs";
|
|
10864
|
+
import { join as join30 } from "path";
|
|
10648
10865
|
import { homedir as homedir3, platform as platform2 } from "os";
|
|
10649
10866
|
var LAUNCH_AGENT_LABEL = "com.develeap.beastmode.claude-creds";
|
|
10650
10867
|
function plistPath() {
|
|
10651
|
-
return
|
|
10868
|
+
return join30(homedir3(), "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
|
|
10652
10869
|
}
|
|
10653
10870
|
function agentLogPath() {
|
|
10654
|
-
return
|
|
10871
|
+
return join30(homedir3(), ".beastmode", "logs", "sync-claude-creds.log");
|
|
10655
10872
|
}
|
|
10656
10873
|
function readKeychainToken() {
|
|
10657
10874
|
try {
|
|
@@ -10680,10 +10897,10 @@ function writeCredentialsFile(rawJson) {
|
|
|
10680
10897
|
info("Fix: run `claude login` again to reset the credential.");
|
|
10681
10898
|
process.exit(1);
|
|
10682
10899
|
}
|
|
10683
|
-
const claudeDir =
|
|
10684
|
-
if (!
|
|
10685
|
-
const credsPath =
|
|
10686
|
-
|
|
10900
|
+
const claudeDir = join30(homedir3(), ".claude");
|
|
10901
|
+
if (!existsSync32(claudeDir)) mkdirSync19(claudeDir, { recursive: true });
|
|
10902
|
+
const credsPath = join30(claudeDir, ".credentials.json");
|
|
10903
|
+
writeFileSync25(credsPath, rawJson + "\n", "utf-8");
|
|
10687
10904
|
chmodSync(credsPath, 384);
|
|
10688
10905
|
if (oauth.expiresAt) {
|
|
10689
10906
|
const hoursLeft = Math.round((oauth.expiresAt - Date.now()) / 36e5);
|
|
@@ -10726,14 +10943,14 @@ function buildPlist(intervalSeconds) {
|
|
|
10726
10943
|
function installAgent(intervalSeconds) {
|
|
10727
10944
|
const plist = plistPath();
|
|
10728
10945
|
const logPath = agentLogPath();
|
|
10729
|
-
mkdirSync19(
|
|
10730
|
-
mkdirSync19(
|
|
10946
|
+
mkdirSync19(join30(homedir3(), "Library", "LaunchAgents"), { recursive: true });
|
|
10947
|
+
mkdirSync19(join30(homedir3(), ".beastmode", "logs"), { recursive: true });
|
|
10731
10948
|
const uid = process.getuid?.();
|
|
10732
|
-
if (
|
|
10949
|
+
if (existsSync32(plist)) {
|
|
10733
10950
|
spawnSync2("launchctl", ["bootout", `gui/${uid}`, plist], { stdio: "pipe" });
|
|
10734
10951
|
}
|
|
10735
|
-
|
|
10736
|
-
|
|
10952
|
+
writeFileSync25(plist, buildPlist(intervalSeconds), "utf-8");
|
|
10953
|
+
writeFileSync25(logPath, "", { flag: "a" });
|
|
10737
10954
|
const result = spawnSync2("launchctl", ["bootstrap", `gui/${uid}`, plist], {
|
|
10738
10955
|
stdio: "pipe",
|
|
10739
10956
|
encoding: "utf-8"
|
|
@@ -10755,7 +10972,7 @@ function installAgent(intervalSeconds) {
|
|
|
10755
10972
|
function uninstallAgent() {
|
|
10756
10973
|
const plist = plistPath();
|
|
10757
10974
|
const uid = process.getuid?.();
|
|
10758
|
-
if (!
|
|
10975
|
+
if (!existsSync32(plist)) {
|
|
10759
10976
|
info("No LaunchAgent installed \u2014 nothing to remove.");
|
|
10760
10977
|
return;
|
|
10761
10978
|
}
|
|
@@ -10775,7 +10992,7 @@ function uninstallAgent() {
|
|
|
10775
10992
|
function showStatus() {
|
|
10776
10993
|
const plist = plistPath();
|
|
10777
10994
|
const uid = process.getuid?.();
|
|
10778
|
-
if (!
|
|
10995
|
+
if (!existsSync32(plist)) {
|
|
10779
10996
|
info("LaunchAgent not installed.");
|
|
10780
10997
|
info("Install with: beastmode sync-claude-creds --install");
|
|
10781
10998
|
return;
|
|
@@ -10937,18 +11154,18 @@ var logsCommand = new Command21("logs").description("Stream BeastMode service lo
|
|
|
10937
11154
|
|
|
10938
11155
|
// src/cli/commands/update.ts
|
|
10939
11156
|
import { Command as Command22 } from "commander";
|
|
10940
|
-
import { readFileSync as
|
|
11157
|
+
import { readFileSync as readFileSync29, writeFileSync as writeFileSync26 } from "fs";
|
|
10941
11158
|
async function runUpdate(opts) {
|
|
10942
11159
|
const cwd = opts.cwd ?? process.cwd();
|
|
10943
11160
|
const composePath = requireComposeFile(cwd);
|
|
10944
11161
|
if (opts.tag) {
|
|
10945
|
-
let content =
|
|
11162
|
+
let content = readFileSync29(composePath, "utf-8");
|
|
10946
11163
|
const tagPattern = new RegExp(
|
|
10947
11164
|
`(${GHCR_IMAGE_PREFIX.replace(/[/]/g, "\\/")}\\/(?:board|daemon|ui)):([\\w.\\-]+)`,
|
|
10948
11165
|
"g"
|
|
10949
11166
|
);
|
|
10950
11167
|
content = content.replace(tagPattern, `$1:${opts.tag}`);
|
|
10951
|
-
|
|
11168
|
+
writeFileSync26(composePath, content, "utf-8");
|
|
10952
11169
|
}
|
|
10953
11170
|
runCompose(["pull"], { cwd, inherit: true });
|
|
10954
11171
|
runCompose(["up", "-d"], { cwd, inherit: true });
|