@beastmode-develeap/beastmode 0.1.48 → 0.1.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 readFileSync26, writeFileSync as writeFileSync22, existsSync as existsSync29, readdirSync as readdirSync10, mkdirSync as mkdirSync18 } from "fs";
6716
- import { join as join27, resolve as resolve18, basename as basename5 } from "path";
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 (!existsSync29(filePath)) return null;
6719
+ if (!existsSync30(filePath)) return null;
6720
6720
  try {
6721
- return JSON.parse(readFileSync26(filePath, "utf-8"));
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 && existsSync29(join27(envPath, ".beastmode", "factory.json"))) {
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 (existsSync29(join27(dir, ".beastmode", "factory.json"))) {
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 = join27(factoryDir, ".beastmode");
6746
+ const bmDir = join28(factoryDir, ".beastmode");
6747
6747
  const factoryIdentity = FactoryIdentitySchema.parse(
6748
- JSON.parse(readFileSync26(join27(bmDir, "factory.json"), "utf-8"))
6748
+ JSON.parse(readFileSync27(join28(bmDir, "factory.json"), "utf-8"))
6749
6749
  );
6750
- const projectsDir = join27(bmDir, "projects");
6751
- const projectCount = existsSync29(projectsDir) ? readdirSync10(projectsDir).filter((f) => f.endsWith(".json")).length : 0;
6752
- const lockPath = join27(bmDir, "extensions.lock");
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 (existsSync29(lockPath)) {
6754
+ if (existsSync30(lockPath)) {
6755
6755
  try {
6756
- const lock = JSON.parse(readFileSync26(lockPath, "utf-8"));
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 = join27(factoryDir, "runs");
6776
+ const runsDir = join28(factoryDir, "runs");
6777
6777
  let runDirs = [];
6778
- if (existsSync29(runsDir)) {
6778
+ if (existsSync30(runsDir)) {
6779
6779
  runDirs = readdirSync10(runsDir).filter((d) => {
6780
6780
  try {
6781
- return readdirSync10(join27(runsDir, d)).length > 0;
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 = join27(bmDir, "daemon.pid");
6787
+ const pidFile = join28(bmDir, "daemon.pid");
6788
6788
  let daemonPid = null;
6789
6789
  let pidAlive = false;
6790
- if (existsSync29(pidFile)) {
6790
+ if (existsSync30(pidFile)) {
6791
6791
  try {
6792
- daemonPid = parseInt(readFileSync26(pidFile, "utf-8").trim(), 10);
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 = join27(factoryDir, ".beastmode", "board.json");
6814
- if (!existsSync29(filePath)) return [];
6813
+ const filePath = join28(factoryDir, ".beastmode", "board.json");
6814
+ if (!existsSync30(filePath)) return [];
6815
6815
  try {
6816
- const raw = JSON.parse(readFileSync26(filePath, "utf-8"));
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 = join27(factoryDir, ".beastmode", "board.json");
6824
- writeFileSync22(filePath, JSON.stringify({ items }, null, 2) + "\n", "utf-8");
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 = join27(factoryDir, ".beastmode", "config.json");
6848
- const config = existsSync29(configPath) ? JSON.parse(readFileSync26(configPath, "utf-8")) : generateDefaults();
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 = join27(factoryDir, ".beastmode", "config.json");
6867
- const config = existsSync29(configPath) ? JSON.parse(readFileSync26(configPath, "utf-8")) : generateDefaults();
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
- writeFileSync22(configPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
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 = join27(factoryDir, "runs");
6905
- if (!existsSync29(runsDir)) {
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(join27(runsDir, id, "checkpoint.json"));
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 = join27(factoryDir, "runs", run_id);
6926
- if (!existsSync29(runDir)) {
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(join27(runDir, "manifest.json"));
6930
- const checkpoint = readJsonFile2(join27(runDir, "checkpoint.json"));
6931
- const iterationsDir = join27(runDir, "iterations");
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 (existsSync29(iterationsDir)) {
6933
+ if (existsSync30(iterationsDir)) {
6934
6934
  for (const dir of readdirSync10(iterationsDir).sort()) {
6935
- const satisfaction = readJsonFile2(join27(iterationsDir, dir, "satisfaction.json"));
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 = join27(factoryDir, ".beastmode");
6981
+ const bmDir = join28(factoryDir, ".beastmode");
6982
6982
  let plugins = {};
6983
- const lockPath = join27(bmDir, "extensions.lock");
6984
- if (existsSync29(lockPath)) {
6983
+ const lockPath = join28(bmDir, "extensions.lock");
6984
+ if (existsSync30(lockPath)) {
6985
6985
  try {
6986
- const lock = JSON.parse(readFileSync26(lockPath, "utf-8"));
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 = join27(factoryDir, ".beastmode", "projects");
7021
- if (!existsSync29(projectsDir)) {
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(readFileSync26(join27(projectsDir, f), "utf-8"));
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 (!existsSync29(resolvedPath)) {
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 = join27(factoryDir, ".beastmode", "projects");
7061
+ const projectsDir = join28(factoryDir, ".beastmode", "projects");
7062
7062
  mkdirSync18(projectsDir, { recursive: true });
7063
- writeFileSync22(
7064
- join27(projectsDir, `${projectName}.json`),
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: readFileSync29 } = await import("fs");
7480
- const templateContent = readFileSync29(resolve5(opts.from), "utf-8");
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 existsSync25, readFileSync as readFileSync22, writeFileSync as writeFileSync18 } from "fs";
9687
- import { join as join23, resolve as resolve16 } from "path";
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 = join23(factoryDir, ".beastmode", "factory.json");
9690
- if (!existsSync25(path)) {
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(readFileSync22(path, "utf-8")));
9875
+ return FactoryIdentitySchema.parse(JSON.parse(readFileSync23(path, "utf-8")));
9694
9876
  }
9695
9877
  function readConfig3(factoryDir) {
9696
- const path = join23(factoryDir, ".beastmode", "config.json");
9697
- if (!existsSync25(path)) {
9878
+ const path = join24(factoryDir, ".beastmode", "config.json");
9879
+ if (!existsSync26(path)) {
9698
9880
  return {};
9699
9881
  }
9700
- return JSON.parse(readFileSync22(path, "utf-8"));
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
- writeFileSync18(
9713
- join23(factoryDir, ".beastmode", "factory.json"),
9894
+ writeFileSync19(
9895
+ join24(factoryDir, ".beastmode", "factory.json"),
9714
9896
  JSON.stringify(result.updatedIdentity, null, 2) + "\n"
9715
9897
  );
9716
- writeFileSync18(
9717
- join23(factoryDir, ".beastmode", "config.json"),
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").action((opts) => {
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 join24 } from "path";
9768
- import { existsSync as existsSync26, readFileSync as readFileSync23, mkdirSync as mkdirSync15, writeFileSync as writeFileSync19 } from "fs";
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 (!existsSync26(configPath)) {
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 = readFileSync23(configPath, "utf-8");
10006
+ const configContent = readFileSync24(configPath, "utf-8");
9790
10007
  const daemonConfig = parseDaemonConfig(configContent);
9791
- const runsDir = join24(cwd, "runs");
10008
+ const runsDir = join25(cwd, "runs");
9792
10009
  let runDirs = [];
9793
10010
  const checkpoints = /* @__PURE__ */ new Map();
9794
- if (existsSync26(runsDir)) {
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(join24(runsDir, d)).length > 0;
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 = join24(runsDir, dir, "checkpoint.json");
9805
- if (existsSync26(cpPath)) {
10021
+ const cpPath = join25(runsDir, dir, "checkpoint.json");
10022
+ if (existsSync27(cpPath)) {
9806
10023
  try {
9807
- const cp = JSON.parse(readFileSync23(cpPath, "utf-8"));
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 = join24(cwd, ".beastmode");
9865
- if (existsSync26(bmDir)) {
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 = join24(cwd, file.path);
10088
+ const fullPath = join25(cwd, file.path);
9872
10089
  const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
9873
10090
  mkdirSync15(dir, { recursive: true });
9874
- writeFileSync19(fullPath, file.content, "utf-8");
10091
+ writeFileSync20(fullPath, file.content, "utf-8");
9875
10092
  }
9876
- const runsSymlinkTarget = join24(cwd, "runs");
9877
- const bmRunsPath = join24(cwd, "runs");
9878
- if (existsSync26(runsSymlinkTarget)) {
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 = join24(bmDir, "board.json");
9882
- if (!existsSync26(boardPath)) {
9883
- writeFileSync19(boardPath, JSON.stringify({ items: [] }, null, 2), "utf-8");
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(join24(bmDir, ".cache"), { recursive: true });
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 join25 } from "path";
9904
- import { existsSync as existsSync27, readFileSync as readFileSync24, writeFileSync as writeFileSync20, mkdirSync as mkdirSync16 } from "fs";
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 = join25(factoryDir, ".beastmode");
10143
+ const bmDir = join26(factoryDir, ".beastmode");
9927
10144
  header("BeastMode Run");
9928
- const configPath = join25(bmDir, "config.json");
9929
- if (!existsSync27(configPath)) {
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(readFileSync24(configPath, "utf-8"))
10150
+ JSON.parse(readFileSync25(configPath, "utf-8"))
9934
10151
  );
9935
10152
  let projectConfig = null;
9936
- const projectsDir = join25(bmDir, "projects");
9937
- if (existsSync27(projectsDir)) {
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(readFileSync24(join25(projectsDir, file), "utf-8"))
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(readFileSync24(join25(projectsDir, projectFiles[0]), "utf-8"))
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 = join25(bmDir, "board.json");
10176
+ const boardPath = join26(bmDir, "board.json");
9960
10177
  let boardItems = [];
9961
- if (existsSync27(boardPath)) {
10178
+ if (existsSync28(boardPath)) {
9962
10179
  try {
9963
- const raw = JSON.parse(readFileSync24(boardPath, "utf-8"));
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
- writeFileSync20(boardPath, JSON.stringify({ items: boardItems }, null, 2), "utf-8");
10197
+ writeFileSync21(boardPath, JSON.stringify({ items: boardItems }, null, 2), "utf-8");
9981
10198
  info(`Created task: ${task.title} (${taskId})`);
9982
- const cacheDir = join25(bmDir, ".cache");
10199
+ const cacheDir = join26(bmDir, ".cache");
9983
10200
  mkdirSync16(cacheDir, { recursive: true });
9984
- const daemonConfigPath = join25(cacheDir, "daemon.json");
10201
+ const daemonConfigPath = join26(cacheDir, "daemon.json");
9985
10202
  const daemonConfig = generateDaemonConfig(factoryConfig, projectConfig, factoryDir);
9986
- writeFileSync20(daemonConfigPath, JSON.stringify(daemonConfig, null, 2), "utf-8");
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 (existsSync27(p)) {
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(readFileSync24(boardPath, "utf-8"));
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 join26 } from "path";
10080
- import { existsSync as existsSync28, readFileSync as readFileSync25, writeFileSync as writeFileSync21, mkdirSync as mkdirSync17 } from "fs";
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 = join26(factoryDir, ".beastmode");
10319
+ const bmDir = join27(factoryDir, ".beastmode");
10103
10320
  header("BeastMode Daemon");
10104
- const configPath = join26(bmDir, "config.json");
10105
- if (!existsSync28(configPath)) {
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(readFileSync25(configPath, "utf-8"))
10326
+ JSON.parse(readFileSync26(configPath, "utf-8"))
10110
10327
  );
10111
10328
  let projectConfig = null;
10112
- const projectsDir = join26(bmDir, "projects");
10113
- if (existsSync28(projectsDir)) {
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(readFileSync25(join26(projectsDir, projectFiles[0]), "utf-8"))
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 = join26(bmDir, ".cache");
10342
+ const cacheDir = join27(bmDir, ".cache");
10126
10343
  mkdirSync17(cacheDir, { recursive: true });
10127
- const daemonConfigPath = join26(cacheDir, "daemon.json");
10344
+ const daemonConfigPath = join27(cacheDir, "daemon.json");
10128
10345
  const daemonConfig = generateDaemonConfig(
10129
10346
  factoryConfig,
10130
10347
  projectConfig,
10131
10348
  factoryDir
10132
10349
  );
10133
- writeFileSync21(
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 = join26(bmDir, "daemon.pid");
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
- writeFileSync21(pidFile, String(child.pid), "utf-8");
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 join28 } from "path";
10222
- import { existsSync as existsSync30, writeFileSync as writeFileSync23, readFileSync as readFileSync27 } from "fs";
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 = join28(factoryDir, ".beastmode");
10278
- if (!existsSync30(bmDir)) {
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 = join28(factoryDir, "board", ".venv", "bin", "python");
10303
- const daemonVenvPython = join28(factoryDir, "daemon", ".venv", "bin", "python");
10304
- const boardPython = existsSync30(boardVenvPython) ? boardVenvPython : "python3";
10305
- const daemonPython = existsSync30(daemonVenvPython) ? daemonVenvPython : "python3";
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 = join28(factoryDir, ".env");
10311
- const secretsEnv = join28(bmDir, "secrets.env.local");
10312
- const envContent = existsSync30(dotEnv) ? readFileSync27(dotEnv, "utf-8") : "";
10313
- const secretsContent = existsSync30(secretsEnv) ? readFileSync27(secretsEnv, "utf-8") : "";
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 = existsSync30(secretsEnv) ? secretsEnv : dotEnv;
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
- writeFileSync23(target, (existsSync30(target) ? readFileSync27(target, "utf-8") : "") + append, "utf-8");
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 (existsSync30(secretsEnv)) {
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
- writeFileSync23(tmpPath, svc.content, "utf-8");
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 = join28(__dirname2, "..", "..", "infra", "cloudformation", "beastmode.yaml");
10541
- const cwdTemplate = join28(process.cwd(), "infra", "cloudformation", "beastmode.yaml");
10542
- const template = existsSync30(templatePath) ? templatePath : existsSync30(cwdTemplate) ? cwdTemplate : null;
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 writeFileSync24, chmodSync, mkdirSync as mkdirSync19, existsSync as existsSync31, unlinkSync as unlinkSync4 } from "fs";
10647
- import { join as join29 } from "path";
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 join29(homedir3(), "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
10868
+ return join30(homedir3(), "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
10652
10869
  }
10653
10870
  function agentLogPath() {
10654
- return join29(homedir3(), ".beastmode", "logs", "sync-claude-creds.log");
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 = join29(homedir3(), ".claude");
10684
- if (!existsSync31(claudeDir)) mkdirSync19(claudeDir, { recursive: true });
10685
- const credsPath = join29(claudeDir, ".credentials.json");
10686
- writeFileSync24(credsPath, rawJson + "\n", "utf-8");
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(join29(homedir3(), "Library", "LaunchAgents"), { recursive: true });
10730
- mkdirSync19(join29(homedir3(), ".beastmode", "logs"), { recursive: true });
10946
+ mkdirSync19(join30(homedir3(), "Library", "LaunchAgents"), { recursive: true });
10947
+ mkdirSync19(join30(homedir3(), ".beastmode", "logs"), { recursive: true });
10731
10948
  const uid = process.getuid?.();
10732
- if (existsSync31(plist)) {
10949
+ if (existsSync32(plist)) {
10733
10950
  spawnSync2("launchctl", ["bootout", `gui/${uid}`, plist], { stdio: "pipe" });
10734
10951
  }
10735
- writeFileSync24(plist, buildPlist(intervalSeconds), "utf-8");
10736
- writeFileSync24(logPath, "", { flag: "a" });
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 (!existsSync31(plist)) {
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 (!existsSync31(plist)) {
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 readFileSync28, writeFileSync as writeFileSync25 } from "fs";
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 = readFileSync28(composePath, "utf-8");
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
- writeFileSync25(composePath, content, "utf-8");
11168
+ writeFileSync26(composePath, content, "utf-8");
10952
11169
  }
10953
11170
  runCompose(["pull"], { cwd, inherit: true });
10954
11171
  runCompose(["up", "-d"], { cwd, inherit: true });