@beastmode-develeap/beastmode 0.1.118 → 0.1.119
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 +703 -665
- package/dist/index.js.map +1 -1
- package/dist/web/board.html +1 -1
- package/dist/web/build-stamp.txt +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2957,6 +2957,35 @@ var init_file_writer = __esm({
|
|
|
2957
2957
|
}
|
|
2958
2958
|
});
|
|
2959
2959
|
|
|
2960
|
+
// src/cli/utils/display.ts
|
|
2961
|
+
import chalk from "chalk";
|
|
2962
|
+
function header(text) {
|
|
2963
|
+
console.log();
|
|
2964
|
+
console.log(chalk.bold.cyan(` ${text}`));
|
|
2965
|
+
console.log(chalk.dim(" " + "\u2500".repeat(text.length + 2)));
|
|
2966
|
+
}
|
|
2967
|
+
function success(text) {
|
|
2968
|
+
console.log(chalk.green(` \u2713 ${text}`));
|
|
2969
|
+
}
|
|
2970
|
+
function warn(text) {
|
|
2971
|
+
console.log(chalk.yellow(` \u26A0 ${text}`));
|
|
2972
|
+
}
|
|
2973
|
+
function error(text) {
|
|
2974
|
+
console.log(chalk.red(` \u2717 ${text}`));
|
|
2975
|
+
}
|
|
2976
|
+
function info(text) {
|
|
2977
|
+
console.log(chalk.dim(` ${text}`));
|
|
2978
|
+
}
|
|
2979
|
+
function step(n, total, text) {
|
|
2980
|
+
console.log();
|
|
2981
|
+
console.log(chalk.bold(` [${n}/${total}] ${text}`));
|
|
2982
|
+
}
|
|
2983
|
+
var init_display = __esm({
|
|
2984
|
+
"src/cli/utils/display.ts"() {
|
|
2985
|
+
"use strict";
|
|
2986
|
+
}
|
|
2987
|
+
});
|
|
2988
|
+
|
|
2960
2989
|
// src/cli/ui/api-routes.ts
|
|
2961
2990
|
import { resolve as resolve3 } from "path";
|
|
2962
2991
|
import { existsSync as existsSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
@@ -6805,15 +6834,261 @@ var init_server = __esm({
|
|
|
6805
6834
|
}
|
|
6806
6835
|
});
|
|
6807
6836
|
|
|
6837
|
+
// src/cli/commands/sync-claude-creds.ts
|
|
6838
|
+
var sync_claude_creds_exports = {};
|
|
6839
|
+
__export(sync_claude_creds_exports, {
|
|
6840
|
+
installAgent: () => installAgent,
|
|
6841
|
+
readKeychainTokenSafe: () => readKeychainTokenSafe,
|
|
6842
|
+
syncClaudeCredsCommand: () => syncClaudeCredsCommand,
|
|
6843
|
+
syncClaudeCredsOnce: () => syncClaudeCredsOnce
|
|
6844
|
+
});
|
|
6845
|
+
import { Command } from "commander";
|
|
6846
|
+
import { execSync as execSync4, spawnSync as spawnSync2 } from "child_process";
|
|
6847
|
+
import { writeFileSync as writeFileSync13, chmodSync, mkdirSync as mkdirSync12, existsSync as existsSync17, unlinkSync as unlinkSync4 } from "fs";
|
|
6848
|
+
import { join as join15 } from "path";
|
|
6849
|
+
import { homedir as homedir2, platform } from "os";
|
|
6850
|
+
function plistPath() {
|
|
6851
|
+
return join15(homedir2(), "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
|
|
6852
|
+
}
|
|
6853
|
+
function agentLogPath() {
|
|
6854
|
+
return join15(homedir2(), ".beastmode", "logs", "sync-claude-creds.log");
|
|
6855
|
+
}
|
|
6856
|
+
function readKeychainTokenSafe() {
|
|
6857
|
+
try {
|
|
6858
|
+
return execSync4(
|
|
6859
|
+
`security find-generic-password -s "Claude Code-credentials" -w`,
|
|
6860
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
6861
|
+
).trim();
|
|
6862
|
+
} catch {
|
|
6863
|
+
return null;
|
|
6864
|
+
}
|
|
6865
|
+
}
|
|
6866
|
+
function readKeychainToken() {
|
|
6867
|
+
const token = readKeychainTokenSafe();
|
|
6868
|
+
if (token === null) {
|
|
6869
|
+
error("Could not read Claude Code credentials from Keychain.");
|
|
6870
|
+
info("Fix: run `claude login` first, then re-run this command.");
|
|
6871
|
+
process.exit(1);
|
|
6872
|
+
}
|
|
6873
|
+
return token;
|
|
6874
|
+
}
|
|
6875
|
+
function writeCredentialsFile(rawJson) {
|
|
6876
|
+
let parsed;
|
|
6877
|
+
try {
|
|
6878
|
+
parsed = JSON.parse(rawJson);
|
|
6879
|
+
} catch {
|
|
6880
|
+
throw new Error("Keychain entry is not valid JSON. Fix: run `claude login` again to reset the credential.");
|
|
6881
|
+
}
|
|
6882
|
+
const oauth = parsed.claudeAiOauth;
|
|
6883
|
+
if (!oauth?.accessToken) {
|
|
6884
|
+
throw new Error("Keychain entry missing claudeAiOauth.accessToken. Fix: run `claude login` again to reset the credential.");
|
|
6885
|
+
}
|
|
6886
|
+
const claudeDir = join15(homedir2(), ".claude");
|
|
6887
|
+
if (!existsSync17(claudeDir)) mkdirSync12(claudeDir, { recursive: true });
|
|
6888
|
+
const credsPath = join15(claudeDir, ".credentials.json");
|
|
6889
|
+
writeFileSync13(credsPath, rawJson + "\n", "utf-8");
|
|
6890
|
+
chmodSync(credsPath, 384);
|
|
6891
|
+
if (oauth.expiresAt) {
|
|
6892
|
+
const hoursLeft = Math.round((oauth.expiresAt - Date.now()) / 36e5);
|
|
6893
|
+
if (hoursLeft < 0) {
|
|
6894
|
+
warn(`Token already expired ${Math.abs(hoursLeft)}h ago \u2014 run \`claude login\` to refresh.`);
|
|
6895
|
+
} else if (hoursLeft < 2) {
|
|
6896
|
+
warn(`Token expires in ${hoursLeft}h \u2014 background agent will re-sync after host refreshes it.`);
|
|
6897
|
+
}
|
|
6898
|
+
}
|
|
6899
|
+
return credsPath;
|
|
6900
|
+
}
|
|
6901
|
+
function buildPlist(intervalSeconds) {
|
|
6902
|
+
const nodePath = process.execPath;
|
|
6903
|
+
const cliEntry = process.argv[1];
|
|
6904
|
+
const logPath = agentLogPath();
|
|
6905
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
6906
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
6907
|
+
<plist version="1.0">
|
|
6908
|
+
<dict>
|
|
6909
|
+
<key>Label</key>
|
|
6910
|
+
<string>${LAUNCH_AGENT_LABEL}</string>
|
|
6911
|
+
<key>ProgramArguments</key>
|
|
6912
|
+
<array>
|
|
6913
|
+
<string>${nodePath}</string>
|
|
6914
|
+
<string>${cliEntry}</string>
|
|
6915
|
+
<string>sync-claude-creds</string>
|
|
6916
|
+
</array>
|
|
6917
|
+
<key>StartInterval</key>
|
|
6918
|
+
<integer>${intervalSeconds}</integer>
|
|
6919
|
+
<key>RunAtLoad</key>
|
|
6920
|
+
<true/>
|
|
6921
|
+
<key>StandardOutPath</key>
|
|
6922
|
+
<string>${logPath}</string>
|
|
6923
|
+
<key>StandardErrorPath</key>
|
|
6924
|
+
<string>${logPath}</string>
|
|
6925
|
+
</dict>
|
|
6926
|
+
</plist>
|
|
6927
|
+
`;
|
|
6928
|
+
}
|
|
6929
|
+
function installAgent(intervalSeconds, { throwOnError = false } = {}) {
|
|
6930
|
+
const plist = plistPath();
|
|
6931
|
+
const logPath = agentLogPath();
|
|
6932
|
+
mkdirSync12(join15(homedir2(), "Library", "LaunchAgents"), { recursive: true });
|
|
6933
|
+
mkdirSync12(join15(homedir2(), ".beastmode", "logs"), { recursive: true });
|
|
6934
|
+
const uid = process.getuid?.();
|
|
6935
|
+
if (existsSync17(plist)) {
|
|
6936
|
+
spawnSync2("launchctl", ["bootout", `gui/${uid}`, plist], { stdio: "pipe" });
|
|
6937
|
+
}
|
|
6938
|
+
writeFileSync13(plist, buildPlist(intervalSeconds), "utf-8");
|
|
6939
|
+
writeFileSync13(logPath, "", { flag: "a" });
|
|
6940
|
+
const result = spawnSync2("launchctl", ["bootstrap", `gui/${uid}`, plist], {
|
|
6941
|
+
stdio: "pipe",
|
|
6942
|
+
encoding: "utf-8"
|
|
6943
|
+
});
|
|
6944
|
+
if (result.status !== 0) {
|
|
6945
|
+
const msg = `launchctl bootstrap failed: ${result.stderr || result.stdout}`;
|
|
6946
|
+
if (throwOnError) {
|
|
6947
|
+
throw new Error(msg);
|
|
6948
|
+
}
|
|
6949
|
+
error(msg);
|
|
6950
|
+
info(`Plist written to: ${plist}`);
|
|
6951
|
+
info(`Try manually: launchctl bootstrap gui/${uid} ${plist}`);
|
|
6952
|
+
process.exit(1);
|
|
6953
|
+
}
|
|
6954
|
+
success(`LaunchAgent installed: ${LAUNCH_AGENT_LABEL}`);
|
|
6955
|
+
info(` Plist: ${plist}`);
|
|
6956
|
+
info(` Interval: every ${Math.round(intervalSeconds / 60)} minutes`);
|
|
6957
|
+
info(` Logs: ${logPath}`);
|
|
6958
|
+
info(` Runs at: load + every ${Math.round(intervalSeconds / 60)}min (while you're logged in)`);
|
|
6959
|
+
console.log();
|
|
6960
|
+
info("The agent also runs once right now (RunAtLoad=true) \u2014 credentials synced.");
|
|
6961
|
+
}
|
|
6962
|
+
function syncClaudeCredsOnce() {
|
|
6963
|
+
const token = readKeychainTokenSafe();
|
|
6964
|
+
if (token === null) {
|
|
6965
|
+
return { error: "Could not read Claude Code credentials from Keychain \u2014 run `claude login` first." };
|
|
6966
|
+
}
|
|
6967
|
+
try {
|
|
6968
|
+
const path = writeCredentialsFile(token);
|
|
6969
|
+
return { path };
|
|
6970
|
+
} catch (e) {
|
|
6971
|
+
return { error: `Failed to write credentials: ${e instanceof Error ? e.message : String(e)}` };
|
|
6972
|
+
}
|
|
6973
|
+
}
|
|
6974
|
+
function uninstallAgent() {
|
|
6975
|
+
const plist = plistPath();
|
|
6976
|
+
const uid = process.getuid?.();
|
|
6977
|
+
if (!existsSync17(plist)) {
|
|
6978
|
+
info("No LaunchAgent installed \u2014 nothing to remove.");
|
|
6979
|
+
return;
|
|
6980
|
+
}
|
|
6981
|
+
const result = spawnSync2("launchctl", ["bootout", `gui/${uid}`, plist], {
|
|
6982
|
+
stdio: "pipe",
|
|
6983
|
+
encoding: "utf-8"
|
|
6984
|
+
});
|
|
6985
|
+
if (result.status !== 0 && !(result.stderr || "").includes("Could not find")) {
|
|
6986
|
+
warn(`launchctl bootout returned: ${result.stderr || result.stdout}`);
|
|
6987
|
+
}
|
|
6988
|
+
try {
|
|
6989
|
+
unlinkSync4(plist);
|
|
6990
|
+
} catch {
|
|
6991
|
+
}
|
|
6992
|
+
success(`LaunchAgent removed: ${LAUNCH_AGENT_LABEL}`);
|
|
6993
|
+
}
|
|
6994
|
+
function showStatus() {
|
|
6995
|
+
const plist = plistPath();
|
|
6996
|
+
const uid = process.getuid?.();
|
|
6997
|
+
if (!existsSync17(plist)) {
|
|
6998
|
+
info("LaunchAgent not installed.");
|
|
6999
|
+
info("Install with: beastmode sync-claude-creds --install");
|
|
7000
|
+
return;
|
|
7001
|
+
}
|
|
7002
|
+
const result = spawnSync2(
|
|
7003
|
+
"launchctl",
|
|
7004
|
+
["print", `gui/${uid}/${LAUNCH_AGENT_LABEL}`],
|
|
7005
|
+
{ stdio: "pipe", encoding: "utf-8" }
|
|
7006
|
+
);
|
|
7007
|
+
if (result.status === 0) {
|
|
7008
|
+
success(`LaunchAgent loaded: ${LAUNCH_AGENT_LABEL}`);
|
|
7009
|
+
const stateMatch = result.stdout.match(/state = (\S+)/);
|
|
7010
|
+
const lastExitMatch = result.stdout.match(/last exit code = (\S+)/);
|
|
7011
|
+
if (stateMatch) info(` State: ${stateMatch[1]}`);
|
|
7012
|
+
if (lastExitMatch) info(` Last exit code: ${lastExitMatch[1]}`);
|
|
7013
|
+
info(` Plist: ${plist}`);
|
|
7014
|
+
info(` Logs: ${agentLogPath()}`);
|
|
7015
|
+
} else {
|
|
7016
|
+
warn(`Plist exists but agent not loaded. Re-install with: beastmode sync-claude-creds --install`);
|
|
7017
|
+
}
|
|
7018
|
+
}
|
|
7019
|
+
var LAUNCH_AGENT_LABEL, syncClaudeCredsCommand;
|
|
7020
|
+
var init_sync_claude_creds = __esm({
|
|
7021
|
+
"src/cli/commands/sync-claude-creds.ts"() {
|
|
7022
|
+
"use strict";
|
|
7023
|
+
init_display();
|
|
7024
|
+
LAUNCH_AGENT_LABEL = "com.develeap.beastmode.claude-creds";
|
|
7025
|
+
syncClaudeCredsCommand = new Command("sync-claude-creds").description(
|
|
7026
|
+
"Sync Claude Code credentials from macOS Keychain to ~/.claude/.credentials.json (for Docker)"
|
|
7027
|
+
).option("--restart", "Restart the daemon container after syncing (one-shot only)").option("--install", "Install a launchd agent that re-syncs every 30 minutes").option("--uninstall", "Remove the launchd agent").option("--status", "Show launchd agent status").option("--interval <seconds>", "Sync interval for --install (default: 1800)", "1800").action(async (opts) => {
|
|
7028
|
+
if (platform() !== "darwin") {
|
|
7029
|
+
info("Not macOS \u2014 Docker mounts ~/.claude/.credentials.json directly. Nothing to sync.");
|
|
7030
|
+
return;
|
|
7031
|
+
}
|
|
7032
|
+
if (opts.install) {
|
|
7033
|
+
header("Install Claude Credential Sync Agent");
|
|
7034
|
+
console.log();
|
|
7035
|
+
const seconds = parseInt(opts.interval || "1800", 10);
|
|
7036
|
+
if (isNaN(seconds) || seconds < 60) {
|
|
7037
|
+
error(`Invalid --interval: ${opts.interval} (minimum 60 seconds)`);
|
|
7038
|
+
process.exit(1);
|
|
7039
|
+
}
|
|
7040
|
+
installAgent(seconds);
|
|
7041
|
+
return;
|
|
7042
|
+
}
|
|
7043
|
+
if (opts.uninstall) {
|
|
7044
|
+
header("Remove Claude Credential Sync Agent");
|
|
7045
|
+
console.log();
|
|
7046
|
+
uninstallAgent();
|
|
7047
|
+
return;
|
|
7048
|
+
}
|
|
7049
|
+
if (opts.status) {
|
|
7050
|
+
header("Claude Credential Sync Agent Status");
|
|
7051
|
+
console.log();
|
|
7052
|
+
showStatus();
|
|
7053
|
+
return;
|
|
7054
|
+
}
|
|
7055
|
+
header("Sync Claude Code Credentials");
|
|
7056
|
+
console.log();
|
|
7057
|
+
const rawJson = readKeychainToken();
|
|
7058
|
+
let credsPath;
|
|
7059
|
+
try {
|
|
7060
|
+
credsPath = writeCredentialsFile(rawJson);
|
|
7061
|
+
} catch (e) {
|
|
7062
|
+
error(e instanceof Error ? e.message : String(e));
|
|
7063
|
+
process.exit(1);
|
|
7064
|
+
}
|
|
7065
|
+
success(`Wrote ${credsPath}`);
|
|
7066
|
+
if (opts.restart) {
|
|
7067
|
+
console.log();
|
|
7068
|
+
info("Restarting daemon container...");
|
|
7069
|
+
const result = spawnSync2("docker", ["compose", "restart", "daemon"], { stdio: "inherit" });
|
|
7070
|
+
if (result.status !== 0) {
|
|
7071
|
+
warn("docker compose restart daemon failed \u2014 run it manually.");
|
|
7072
|
+
process.exit(result.status ?? 1);
|
|
7073
|
+
}
|
|
7074
|
+
success("Daemon restarted.");
|
|
7075
|
+
} else {
|
|
7076
|
+
console.log();
|
|
7077
|
+
info("Automate: beastmode sync-claude-creds --install");
|
|
7078
|
+
}
|
|
7079
|
+
});
|
|
7080
|
+
}
|
|
7081
|
+
});
|
|
7082
|
+
|
|
6808
7083
|
// src/mcp/server.ts
|
|
6809
7084
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6810
7085
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6811
7086
|
import { z as z2 } from "zod";
|
|
6812
|
-
import { readFileSync as readFileSync27, writeFileSync as
|
|
6813
|
-
import { join as
|
|
7087
|
+
import { readFileSync as readFileSync27, writeFileSync as writeFileSync24, existsSync as existsSync31, readdirSync as readdirSync11, mkdirSync as mkdirSync19 } from "fs";
|
|
7088
|
+
import { join as join29, resolve as resolve18, basename as basename5 } from "path";
|
|
6814
7089
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
6815
7090
|
function readJsonFile2(filePath) {
|
|
6816
|
-
if (!
|
|
7091
|
+
if (!existsSync31(filePath)) return null;
|
|
6817
7092
|
try {
|
|
6818
7093
|
return JSON.parse(readFileSync27(filePath, "utf-8"));
|
|
6819
7094
|
} catch {
|
|
@@ -6822,13 +7097,13 @@ function readJsonFile2(filePath) {
|
|
|
6822
7097
|
}
|
|
6823
7098
|
function getFactoryPath() {
|
|
6824
7099
|
const envPath = process.env.BEASTMODE_FACTORY_PATH;
|
|
6825
|
-
if (envPath &&
|
|
7100
|
+
if (envPath && existsSync31(join29(envPath, ".beastmode", "factory.json"))) {
|
|
6826
7101
|
return envPath;
|
|
6827
7102
|
}
|
|
6828
7103
|
let dir = process.cwd();
|
|
6829
7104
|
const root = resolve18("/");
|
|
6830
7105
|
while (dir !== root) {
|
|
6831
|
-
if (
|
|
7106
|
+
if (existsSync31(join29(dir, ".beastmode", "factory.json"))) {
|
|
6832
7107
|
return dir;
|
|
6833
7108
|
}
|
|
6834
7109
|
const parent = resolve18(dir, "..");
|
|
@@ -6840,15 +7115,15 @@ function getFactoryPath() {
|
|
|
6840
7115
|
);
|
|
6841
7116
|
}
|
|
6842
7117
|
function readFactoryStatus(factoryDir) {
|
|
6843
|
-
const bmDir =
|
|
7118
|
+
const bmDir = join29(factoryDir, ".beastmode");
|
|
6844
7119
|
const factoryIdentity = FactoryIdentitySchema.parse(
|
|
6845
|
-
JSON.parse(readFileSync27(
|
|
7120
|
+
JSON.parse(readFileSync27(join29(bmDir, "factory.json"), "utf-8"))
|
|
6846
7121
|
);
|
|
6847
|
-
const projectsDir =
|
|
6848
|
-
const projectCount =
|
|
6849
|
-
const lockPath =
|
|
7122
|
+
const projectsDir = join29(bmDir, "projects");
|
|
7123
|
+
const projectCount = existsSync31(projectsDir) ? readdirSync11(projectsDir).filter((f) => f.endsWith(".json")).length : 0;
|
|
7124
|
+
const lockPath = join29(bmDir, "extensions.lock");
|
|
6850
7125
|
let pluginNames = [];
|
|
6851
|
-
if (
|
|
7126
|
+
if (existsSync31(lockPath)) {
|
|
6852
7127
|
try {
|
|
6853
7128
|
const lock = JSON.parse(readFileSync27(lockPath, "utf-8"));
|
|
6854
7129
|
pluginNames = Object.keys(lock.plugins || {});
|
|
@@ -6870,21 +7145,21 @@ function readFactoryStatus(factoryDir) {
|
|
|
6870
7145
|
skillCount = listSkills(factoryDir).length;
|
|
6871
7146
|
} catch {
|
|
6872
7147
|
}
|
|
6873
|
-
const runsDir =
|
|
7148
|
+
const runsDir = join29(factoryDir, "runs");
|
|
6874
7149
|
let runDirs = [];
|
|
6875
|
-
if (
|
|
7150
|
+
if (existsSync31(runsDir)) {
|
|
6876
7151
|
runDirs = readdirSync11(runsDir).filter((d) => {
|
|
6877
7152
|
try {
|
|
6878
|
-
return readdirSync11(
|
|
7153
|
+
return readdirSync11(join29(runsDir, d)).length > 0;
|
|
6879
7154
|
} catch {
|
|
6880
7155
|
return false;
|
|
6881
7156
|
}
|
|
6882
7157
|
}).sort();
|
|
6883
7158
|
}
|
|
6884
|
-
const pidFile =
|
|
7159
|
+
const pidFile = join29(bmDir, "daemon.pid");
|
|
6885
7160
|
let daemonPid = null;
|
|
6886
7161
|
let pidAlive = false;
|
|
6887
|
-
if (
|
|
7162
|
+
if (existsSync31(pidFile)) {
|
|
6888
7163
|
try {
|
|
6889
7164
|
daemonPid = parseInt(readFileSync27(pidFile, "utf-8").trim(), 10);
|
|
6890
7165
|
process.kill(daemonPid, 0);
|
|
@@ -6907,8 +7182,8 @@ function readFactoryStatus(factoryDir) {
|
|
|
6907
7182
|
return collectStatus(input);
|
|
6908
7183
|
}
|
|
6909
7184
|
function readBoardItems(factoryDir) {
|
|
6910
|
-
const filePath =
|
|
6911
|
-
if (!
|
|
7185
|
+
const filePath = join29(factoryDir, ".beastmode", "board.json");
|
|
7186
|
+
if (!existsSync31(filePath)) return [];
|
|
6912
7187
|
try {
|
|
6913
7188
|
const raw = JSON.parse(readFileSync27(filePath, "utf-8"));
|
|
6914
7189
|
return Array.isArray(raw.items) ? raw.items : [];
|
|
@@ -6917,8 +7192,8 @@ function readBoardItems(factoryDir) {
|
|
|
6917
7192
|
}
|
|
6918
7193
|
}
|
|
6919
7194
|
function writeBoardItems(factoryDir, items) {
|
|
6920
|
-
const filePath =
|
|
6921
|
-
|
|
7195
|
+
const filePath = join29(factoryDir, ".beastmode", "board.json");
|
|
7196
|
+
writeFileSync24(filePath, JSON.stringify({ items }, null, 2) + "\n", "utf-8");
|
|
6922
7197
|
}
|
|
6923
7198
|
function createMcpServer() {
|
|
6924
7199
|
const server = new McpServer(
|
|
@@ -6941,8 +7216,8 @@ function createMcpServer() {
|
|
|
6941
7216
|
{ key_path: z2.string().describe("Dot-notation key path") },
|
|
6942
7217
|
async ({ key_path }) => {
|
|
6943
7218
|
const factoryDir = getFactoryPath();
|
|
6944
|
-
const configPath =
|
|
6945
|
-
const config =
|
|
7219
|
+
const configPath = join29(factoryDir, ".beastmode", "config.json");
|
|
7220
|
+
const config = existsSync31(configPath) ? JSON.parse(readFileSync27(configPath, "utf-8")) : generateDefaults();
|
|
6946
7221
|
try {
|
|
6947
7222
|
const value = configGet(config, key_path);
|
|
6948
7223
|
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
@@ -6960,11 +7235,11 @@ function createMcpServer() {
|
|
|
6960
7235
|
},
|
|
6961
7236
|
async ({ key_path, value }) => {
|
|
6962
7237
|
const factoryDir = getFactoryPath();
|
|
6963
|
-
const configPath =
|
|
6964
|
-
const config =
|
|
7238
|
+
const configPath = join29(factoryDir, ".beastmode", "config.json");
|
|
7239
|
+
const config = existsSync31(configPath) ? JSON.parse(readFileSync27(configPath, "utf-8")) : generateDefaults();
|
|
6965
7240
|
const coerced = coerceValue(value);
|
|
6966
7241
|
const updated = configSet(config, key_path, coerced);
|
|
6967
|
-
|
|
7242
|
+
writeFileSync24(configPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
6968
7243
|
return { content: [{ type: "text", text: `Set ${key_path} = ${JSON.stringify(coerced)}` }] };
|
|
6969
7244
|
}
|
|
6970
7245
|
);
|
|
@@ -6998,14 +7273,14 @@ function createMcpServer() {
|
|
|
6998
7273
|
{},
|
|
6999
7274
|
async () => {
|
|
7000
7275
|
const factoryDir = getFactoryPath();
|
|
7001
|
-
const runsDir =
|
|
7002
|
-
if (!
|
|
7276
|
+
const runsDir = join29(factoryDir, "runs");
|
|
7277
|
+
if (!existsSync31(runsDir)) {
|
|
7003
7278
|
return { content: [{ type: "text", text: "No runs directory found." }] };
|
|
7004
7279
|
}
|
|
7005
7280
|
const runDirs = readdirSync11(runsDir).sort().reverse();
|
|
7006
7281
|
const activeRuns = [];
|
|
7007
7282
|
for (const id of runDirs.slice(0, 10)) {
|
|
7008
|
-
const cp = readJsonFile2(
|
|
7283
|
+
const cp = readJsonFile2(join29(runsDir, id, "checkpoint.json"));
|
|
7009
7284
|
if (cp) {
|
|
7010
7285
|
activeRuns.push({ id, checkpoint: cp });
|
|
7011
7286
|
}
|
|
@@ -7019,17 +7294,17 @@ function createMcpServer() {
|
|
|
7019
7294
|
{ run_id: z2.string().describe("Run ID (directory name)") },
|
|
7020
7295
|
async ({ run_id }) => {
|
|
7021
7296
|
const factoryDir = getFactoryPath();
|
|
7022
|
-
const runDir =
|
|
7023
|
-
if (!
|
|
7297
|
+
const runDir = join29(factoryDir, "runs", run_id);
|
|
7298
|
+
if (!existsSync31(runDir)) {
|
|
7024
7299
|
return { content: [{ type: "text", text: `Run not found: ${run_id}` }], isError: true };
|
|
7025
7300
|
}
|
|
7026
|
-
const manifest = readJsonFile2(
|
|
7027
|
-
const checkpoint = readJsonFile2(
|
|
7028
|
-
const iterationsDir =
|
|
7301
|
+
const manifest = readJsonFile2(join29(runDir, "manifest.json"));
|
|
7302
|
+
const checkpoint = readJsonFile2(join29(runDir, "checkpoint.json"));
|
|
7303
|
+
const iterationsDir = join29(runDir, "iterations");
|
|
7029
7304
|
const iterations = [];
|
|
7030
|
-
if (
|
|
7305
|
+
if (existsSync31(iterationsDir)) {
|
|
7031
7306
|
for (const dir of readdirSync11(iterationsDir).sort()) {
|
|
7032
|
-
const satisfaction = readJsonFile2(
|
|
7307
|
+
const satisfaction = readJsonFile2(join29(iterationsDir, dir, "satisfaction.json"));
|
|
7033
7308
|
iterations.push({ number: parseInt(dir, 10), satisfaction });
|
|
7034
7309
|
}
|
|
7035
7310
|
}
|
|
@@ -7075,10 +7350,10 @@ function createMcpServer() {
|
|
|
7075
7350
|
{},
|
|
7076
7351
|
async () => {
|
|
7077
7352
|
const factoryDir = getFactoryPath();
|
|
7078
|
-
const bmDir =
|
|
7353
|
+
const bmDir = join29(factoryDir, ".beastmode");
|
|
7079
7354
|
let plugins = {};
|
|
7080
|
-
const lockPath =
|
|
7081
|
-
if (
|
|
7355
|
+
const lockPath = join29(bmDir, "extensions.lock");
|
|
7356
|
+
if (existsSync31(lockPath)) {
|
|
7082
7357
|
try {
|
|
7083
7358
|
const lock = JSON.parse(readFileSync27(lockPath, "utf-8"));
|
|
7084
7359
|
plugins = lock.plugins || {};
|
|
@@ -7114,13 +7389,13 @@ function createMcpServer() {
|
|
|
7114
7389
|
{},
|
|
7115
7390
|
async () => {
|
|
7116
7391
|
const factoryDir = getFactoryPath();
|
|
7117
|
-
const projectsDir =
|
|
7118
|
-
if (!
|
|
7392
|
+
const projectsDir = join29(factoryDir, ".beastmode", "projects");
|
|
7393
|
+
if (!existsSync31(projectsDir)) {
|
|
7119
7394
|
return { content: [{ type: "text", text: "[]" }] };
|
|
7120
7395
|
}
|
|
7121
7396
|
const projects = readdirSync11(projectsDir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
7122
7397
|
try {
|
|
7123
|
-
return JSON.parse(readFileSync27(
|
|
7398
|
+
return JSON.parse(readFileSync27(join29(projectsDir, f), "utf-8"));
|
|
7124
7399
|
} catch {
|
|
7125
7400
|
return null;
|
|
7126
7401
|
}
|
|
@@ -7135,7 +7410,7 @@ function createMcpServer() {
|
|
|
7135
7410
|
async ({ path: projectPath }) => {
|
|
7136
7411
|
const factoryDir = getFactoryPath();
|
|
7137
7412
|
const resolvedPath = resolve18(projectPath);
|
|
7138
|
-
if (!
|
|
7413
|
+
if (!existsSync31(resolvedPath)) {
|
|
7139
7414
|
return { content: [{ type: "text", text: `Directory not found: ${resolvedPath}` }], isError: true };
|
|
7140
7415
|
}
|
|
7141
7416
|
const projectName = basename5(resolvedPath);
|
|
@@ -7155,10 +7430,10 @@ function createMcpServer() {
|
|
|
7155
7430
|
deploy: { target: stack.suggested_deploy },
|
|
7156
7431
|
plugins: stack.suggested_plugins
|
|
7157
7432
|
};
|
|
7158
|
-
const projectsDir =
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7433
|
+
const projectsDir = join29(factoryDir, ".beastmode", "projects");
|
|
7434
|
+
mkdirSync19(projectsDir, { recursive: true });
|
|
7435
|
+
writeFileSync24(
|
|
7436
|
+
join29(projectsDir, `${projectName}.json`),
|
|
7162
7437
|
JSON.stringify(projectConfig, null, 2) + "\n",
|
|
7163
7438
|
"utf-8"
|
|
7164
7439
|
);
|
|
@@ -7263,10 +7538,10 @@ import { Command as Command23 } from "commander";
|
|
|
7263
7538
|
// src/cli/commands/init.ts
|
|
7264
7539
|
init_engine();
|
|
7265
7540
|
init_file_writer();
|
|
7266
|
-
import { Command } from "commander";
|
|
7541
|
+
import { Command as Command2 } from "commander";
|
|
7267
7542
|
import inquirer from "inquirer";
|
|
7268
|
-
import { resolve as resolve5, basename as basename4, join as
|
|
7269
|
-
import { existsSync as
|
|
7543
|
+
import { resolve as resolve5, basename as basename4, join as join16 } from "path";
|
|
7544
|
+
import { existsSync as existsSync18, writeFileSync as writeFileSync14, mkdirSync as mkdirSync13, readFileSync as readFileSync14 } from "fs";
|
|
7270
7545
|
|
|
7271
7546
|
// src/cli/utils/docker.ts
|
|
7272
7547
|
import { existsSync as existsSync9 } from "fs";
|
|
@@ -7415,12 +7690,18 @@ function generateComposeYaml(tag) {
|
|
|
7415
7690
|
- ./config:/app/config
|
|
7416
7691
|
- ./runs:/app/runs
|
|
7417
7692
|
- ./daemon/logs:/app/daemon/logs:ro
|
|
7418
|
-
# Claude credentials for the chat feature.
|
|
7419
|
-
#
|
|
7420
|
-
#
|
|
7421
|
-
#
|
|
7422
|
-
#
|
|
7423
|
-
#
|
|
7693
|
+
# Claude credentials for the chat feature. Claude Code stores
|
|
7694
|
+
# auth in TWO places on the host: ~/.claude.json (sibling file,
|
|
7695
|
+
# auth + settings) and ~/.claude/ (directory with backups/,
|
|
7696
|
+
# projects/, ...). Both must be mounted \u2014 the daemon's
|
|
7697
|
+
# docker-entrypoint.sh reads $HOME/.claude.json directly, and
|
|
7698
|
+
# pre-2026-04-18 only the directory was mounted, so every
|
|
7699
|
+
# container recreate came up un-authenticated and every Phase 1
|
|
7700
|
+
# spec run aborted with "Claude CLI Not Logged In".
|
|
7701
|
+
- type: bind
|
|
7702
|
+
source: \${HOME}/.claude.json
|
|
7703
|
+
target: /home/appuser/.claude.json
|
|
7704
|
+
read_only: true
|
|
7424
7705
|
- \${HOME}/.claude:/home/appuser/.claude:ro
|
|
7425
7706
|
depends_on:
|
|
7426
7707
|
board:
|
|
@@ -7463,6 +7744,14 @@ function generateComposeYaml(tag) {
|
|
|
7463
7744
|
# beatmode runtime dir (which has no source code). See
|
|
7464
7745
|
# docs/zero-to-productive-readiness.md Gap 1.
|
|
7465
7746
|
- \${PROJECT_DIR:?PROJECT_DIR must be set in .env \u2014 run 'beastmode init' to regenerate}:/app/project
|
|
7747
|
+
# See the ui service for the two-mount rationale. Without the
|
|
7748
|
+
# sibling-file mount every daemon recreate comes up without
|
|
7749
|
+
# Claude auth and every Phase 1 spec run aborts with
|
|
7750
|
+
# "Claude CLI Not Logged In" (2026-04-18 incident).
|
|
7751
|
+
- type: bind
|
|
7752
|
+
source: \${HOME}/.claude.json
|
|
7753
|
+
target: /home/appuser/.claude.json
|
|
7754
|
+
read_only: true
|
|
7466
7755
|
- \${HOME}/.claude:/home/appuser/.claude
|
|
7467
7756
|
depends_on:
|
|
7468
7757
|
board:
|
|
@@ -7471,32 +7760,9 @@ function generateComposeYaml(tag) {
|
|
|
7471
7760
|
`;
|
|
7472
7761
|
}
|
|
7473
7762
|
|
|
7474
|
-
// src/cli/utils/display.ts
|
|
7475
|
-
import chalk from "chalk";
|
|
7476
|
-
function header(text) {
|
|
7477
|
-
console.log();
|
|
7478
|
-
console.log(chalk.bold.cyan(` ${text}`));
|
|
7479
|
-
console.log(chalk.dim(" " + "\u2500".repeat(text.length + 2)));
|
|
7480
|
-
}
|
|
7481
|
-
function success(text) {
|
|
7482
|
-
console.log(chalk.green(` \u2713 ${text}`));
|
|
7483
|
-
}
|
|
7484
|
-
function warn(text) {
|
|
7485
|
-
console.log(chalk.yellow(` \u26A0 ${text}`));
|
|
7486
|
-
}
|
|
7487
|
-
function error(text) {
|
|
7488
|
-
console.log(chalk.red(` \u2717 ${text}`));
|
|
7489
|
-
}
|
|
7490
|
-
function info(text) {
|
|
7491
|
-
console.log(chalk.dim(` ${text}`));
|
|
7492
|
-
}
|
|
7493
|
-
function step(n, total, text) {
|
|
7494
|
-
console.log();
|
|
7495
|
-
console.log(chalk.bold(` [${n}/${total}] ${text}`));
|
|
7496
|
-
}
|
|
7497
|
-
|
|
7498
7763
|
// src/cli/commands/init.ts
|
|
7499
|
-
|
|
7764
|
+
init_display();
|
|
7765
|
+
var initCommand = new Command2("init").description("Create a new BeastMode factory").argument("[name]", "Factory name (default: current directory name)").option("--project <path>", "Path to target project").option("--preset <preset>", "Pipeline preset (full, lean, prototype, infra, docs)").option("--backend <backend>", "Task backend (beastmode-board, github-issues)").option("--deploy <target>", "Deploy target (pr-only, vercel, aws-ecs, ...)").option("--methodology <name>", "Development methodology plugin").option("--plugin <name>", "Install plugin (repeatable)", collect, []).option("--from <template>", "Import config from template file").option("--ui", "Open web wizard instead of CLI").option("--yes", "Accept all defaults (non-interactive)").option(
|
|
7500
7766
|
"--project-github-token <token>",
|
|
7501
7767
|
"GitHub PAT for the daemon's project operations (commit/push/PR/merge against PROJECT_DIR's git remote \u2014 needs `repo` scope). Alias for the legacy --github-token flag."
|
|
7502
7768
|
).option(
|
|
@@ -7514,10 +7780,7 @@ var initCommand = new Command("init").description("Create a new BeastMode factor
|
|
|
7514
7780
|
).option(
|
|
7515
7781
|
"--ghcr-pull-token-file <path>",
|
|
7516
7782
|
"Read GHCR pull token from file (safer than --ghcr-pull-token)"
|
|
7517
|
-
).option("--board-password <password>", "Board UI password (avoids interactive prompt)").option("--board-password-file <path>", "Read board password from file").
|
|
7518
|
-
"--telemetry",
|
|
7519
|
-
"Enable opt-in anonymous telemetry non-interactively (default off, including with --yes). See docs/telemetry.md."
|
|
7520
|
-
).action(async (name, opts) => {
|
|
7783
|
+
).option("--board-password <password>", "Board UI password (avoids interactive prompt)").option("--board-password-file <path>", "Read board password from file").action(async (name, opts) => {
|
|
7521
7784
|
try {
|
|
7522
7785
|
await runInit(name, opts);
|
|
7523
7786
|
} catch (err) {
|
|
@@ -7533,7 +7796,7 @@ function readSecretFile(filePath) {
|
|
|
7533
7796
|
return readFileSync14(resolve5(filePath), "utf-8").trim();
|
|
7534
7797
|
}
|
|
7535
7798
|
function isSourceRepo(dir) {
|
|
7536
|
-
return
|
|
7799
|
+
return existsSync18(resolve5(dir, "daemon")) && existsSync18(resolve5(dir, "board")) && existsSync18(resolve5(dir, "cli"));
|
|
7537
7800
|
}
|
|
7538
7801
|
async function runInit(name, opts) {
|
|
7539
7802
|
if (opts.ui) {
|
|
@@ -7568,11 +7831,11 @@ async function runInit(name, opts) {
|
|
|
7568
7831
|
await runImageModeInit(name, opts);
|
|
7569
7832
|
return;
|
|
7570
7833
|
}
|
|
7571
|
-
const totalSteps =
|
|
7834
|
+
const totalSteps = 5;
|
|
7572
7835
|
header("BeastMode \u2014 Dark Factory Init");
|
|
7573
7836
|
info("Creating your Custom Dark Factory\n");
|
|
7574
7837
|
const factoryName = name || basename4(resolve5("."));
|
|
7575
|
-
if (
|
|
7838
|
+
if (existsSync18(factoryName) && existsSync18(resolve5(factoryName, ".beastmode"))) {
|
|
7576
7839
|
throw new Error(`Factory already exists at ./${factoryName}. Use 'beastmode config' to modify.`);
|
|
7577
7840
|
}
|
|
7578
7841
|
if (opts.from) {
|
|
@@ -7622,7 +7885,7 @@ async function runInit(name, opts) {
|
|
|
7622
7885
|
name: "project",
|
|
7623
7886
|
message: "Path to your project:",
|
|
7624
7887
|
default: ".",
|
|
7625
|
-
validate: (input) =>
|
|
7888
|
+
validate: (input) => existsSync18(resolve5(input)) || `Directory not found: ${input}`
|
|
7626
7889
|
}
|
|
7627
7890
|
]);
|
|
7628
7891
|
projectPath = resolve5(answer.project);
|
|
@@ -7781,40 +8044,7 @@ async function runInit(name, opts) {
|
|
|
7781
8044
|
} else if (uiPassword) {
|
|
7782
8045
|
success("Board UI password set");
|
|
7783
8046
|
}
|
|
7784
|
-
step(5, totalSteps, "
|
|
7785
|
-
let telemetryEnabled = false;
|
|
7786
|
-
let telemetrySentryDsn = "";
|
|
7787
|
-
if (opts.telemetry) {
|
|
7788
|
-
telemetryEnabled = true;
|
|
7789
|
-
} else if (!opts.yes) {
|
|
7790
|
-
const answer = await inquirer.prompt([
|
|
7791
|
-
{
|
|
7792
|
-
type: "confirm",
|
|
7793
|
-
name: "enabled",
|
|
7794
|
-
message: "Help improve BeastMode with anonymous telemetry? (pipeline metrics only \u2014 no code, no PII)",
|
|
7795
|
-
default: false
|
|
7796
|
-
}
|
|
7797
|
-
]);
|
|
7798
|
-
telemetryEnabled = !!answer.enabled;
|
|
7799
|
-
if (telemetryEnabled) {
|
|
7800
|
-
const sentryAnswer = await inquirer.prompt([
|
|
7801
|
-
{
|
|
7802
|
-
type: "input",
|
|
7803
|
-
name: "dsn",
|
|
7804
|
-
message: "Sentry DSN for error reporting (leave empty to skip):",
|
|
7805
|
-
default: ""
|
|
7806
|
-
}
|
|
7807
|
-
]);
|
|
7808
|
-
telemetrySentryDsn = (sentryAnswer.dsn || "").trim();
|
|
7809
|
-
}
|
|
7810
|
-
}
|
|
7811
|
-
if (telemetryEnabled) {
|
|
7812
|
-
success("Telemetry enabled \u2014 anonymous pipeline metrics will be collected");
|
|
7813
|
-
info("You can disable anytime: set telemetry.enabled=false in config/beastmode.daemon.json");
|
|
7814
|
-
} else {
|
|
7815
|
-
info("Telemetry disabled \u2014 no data will be collected");
|
|
7816
|
-
}
|
|
7817
|
-
step(6, totalSteps, "Boot");
|
|
8047
|
+
step(5, totalSteps, "Boot");
|
|
7818
8048
|
const projectName = basename4(projectPath);
|
|
7819
8049
|
const actions = scaffoldFactory(factoryName, config, {
|
|
7820
8050
|
name: projectName,
|
|
@@ -7839,18 +8069,32 @@ async function runInit(name, opts) {
|
|
|
7839
8069
|
if (uiPassword) {
|
|
7840
8070
|
collectedSecrets.push(`BEASTMODE_UI_PASSWORD=${uiPassword}`);
|
|
7841
8071
|
}
|
|
7842
|
-
if (telemetryEnabled) {
|
|
7843
|
-
collectedSecrets.push(`BEASTMODE_TELEMETRY_ENABLED=true`);
|
|
7844
|
-
if (telemetrySentryDsn) {
|
|
7845
|
-
collectedSecrets.push(`BEASTMODE_TELEMETRY_SENTRY_DSN=${telemetrySentryDsn}`);
|
|
7846
|
-
}
|
|
7847
|
-
}
|
|
7848
8072
|
if (collectedSecrets.length > 0) {
|
|
7849
8073
|
const secretsPath = resolve5(factoryName, ".beastmode", "secrets.env.local");
|
|
7850
8074
|
const secretsContent = "# BeastMode secrets \u2014 DO NOT COMMIT\n" + collectedSecrets.join("\n") + "\n";
|
|
7851
8075
|
const { writeFileSync: writeSecrets } = await import("fs");
|
|
7852
8076
|
writeSecrets(secretsPath, secretsContent, "utf-8");
|
|
7853
8077
|
}
|
|
8078
|
+
if (process.platform === "darwin") {
|
|
8079
|
+
try {
|
|
8080
|
+
const { syncClaudeCredsOnce: syncClaudeCredsOnce2, installAgent: installAgent2 } = await Promise.resolve().then(() => (init_sync_claude_creds(), sync_claude_creds_exports));
|
|
8081
|
+
const sync = syncClaudeCredsOnce2();
|
|
8082
|
+
if ("error" in sync) {
|
|
8083
|
+
warn(`Claude creds sync skipped: ${sync.error}`);
|
|
8084
|
+
info(" Run `beastmode sync-claude-creds --install` after `claude login`.");
|
|
8085
|
+
} else {
|
|
8086
|
+
success(`Claude creds synced: ${sync.path}`);
|
|
8087
|
+
try {
|
|
8088
|
+
installAgent2(1800, { throwOnError: true });
|
|
8089
|
+
} catch (e) {
|
|
8090
|
+
warn(`Claude-creds LaunchAgent install failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
8091
|
+
info(" You can retry with: beastmode sync-claude-creds --install");
|
|
8092
|
+
}
|
|
8093
|
+
}
|
|
8094
|
+
} catch (e) {
|
|
8095
|
+
warn(`Claude-creds setup skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
8096
|
+
}
|
|
8097
|
+
}
|
|
7854
8098
|
console.log();
|
|
7855
8099
|
header(`Factory "${factoryName}" is ready!`);
|
|
7856
8100
|
console.log();
|
|
@@ -7865,7 +8109,7 @@ async function runInit(name, opts) {
|
|
|
7865
8109
|
console.log();
|
|
7866
8110
|
}
|
|
7867
8111
|
async function runImageModeInit(name, opts) {
|
|
7868
|
-
const totalSteps =
|
|
8112
|
+
const totalSteps = 4;
|
|
7869
8113
|
header("BeastMode \u2014 Factory Init (Image Mode)");
|
|
7870
8114
|
info("Setting up BeastMode from pre-built Docker images\n");
|
|
7871
8115
|
const factoryName = name || ".";
|
|
@@ -7928,14 +8172,14 @@ async function runImageModeInit(name, opts) {
|
|
|
7928
8172
|
let projectPath;
|
|
7929
8173
|
if (opts.project) {
|
|
7930
8174
|
projectPath = resolve5(opts.project);
|
|
7931
|
-
if (!
|
|
8175
|
+
if (!existsSync18(projectPath)) {
|
|
7932
8176
|
error(`--project path does not exist: ${projectPath}`);
|
|
7933
8177
|
process.exit(1);
|
|
7934
8178
|
}
|
|
7935
8179
|
success(`Project path: ${projectPath}`);
|
|
7936
8180
|
} else if (opts.yes) {
|
|
7937
8181
|
const cwd = resolve5(".");
|
|
7938
|
-
if (!
|
|
8182
|
+
if (!existsSync18(join16(cwd, ".git"))) {
|
|
7939
8183
|
error(
|
|
7940
8184
|
"--project is required in non-interactive mode (--yes) unless you run from inside a git repository. Pass --project <path> or cd into your project clone first."
|
|
7941
8185
|
);
|
|
@@ -7952,8 +8196,8 @@ async function runImageModeInit(name, opts) {
|
|
|
7952
8196
|
default: ".",
|
|
7953
8197
|
validate: (input) => {
|
|
7954
8198
|
const p = resolve5(input);
|
|
7955
|
-
if (!
|
|
7956
|
-
if (!
|
|
8199
|
+
if (!existsSync18(p)) return `Directory not found: ${p}`;
|
|
8200
|
+
if (!existsSync18(join16(p, ".git"))) {
|
|
7957
8201
|
return `Not a git repo: ${p} (run 'git init' first, or pick a different path)`;
|
|
7958
8202
|
}
|
|
7959
8203
|
return true;
|
|
@@ -7991,43 +8235,10 @@ async function runImageModeInit(name, opts) {
|
|
|
7991
8235
|
} else {
|
|
7992
8236
|
success("Authenticated to ghcr.io");
|
|
7993
8237
|
}
|
|
7994
|
-
step(3, totalSteps, "
|
|
7995
|
-
|
|
7996
|
-
let telemetrySentryDsn = "";
|
|
7997
|
-
if (opts.telemetry) {
|
|
7998
|
-
telemetryEnabled = true;
|
|
7999
|
-
} else if (!opts.yes) {
|
|
8000
|
-
const answer = await inquirer.prompt([
|
|
8001
|
-
{
|
|
8002
|
-
type: "confirm",
|
|
8003
|
-
name: "enabled",
|
|
8004
|
-
message: "Help improve BeastMode with anonymous telemetry? (pipeline metrics only \u2014 no code, no PII)",
|
|
8005
|
-
default: false
|
|
8006
|
-
}
|
|
8007
|
-
]);
|
|
8008
|
-
telemetryEnabled = !!answer.enabled;
|
|
8009
|
-
if (telemetryEnabled) {
|
|
8010
|
-
const sentryAnswer = await inquirer.prompt([
|
|
8011
|
-
{
|
|
8012
|
-
type: "input",
|
|
8013
|
-
name: "dsn",
|
|
8014
|
-
message: "Sentry DSN for error reporting (leave empty to skip):",
|
|
8015
|
-
default: ""
|
|
8016
|
-
}
|
|
8017
|
-
]);
|
|
8018
|
-
telemetrySentryDsn = (sentryAnswer.dsn || "").trim();
|
|
8019
|
-
}
|
|
8020
|
-
}
|
|
8021
|
-
if (telemetryEnabled) {
|
|
8022
|
-
success("Telemetry enabled \u2014 anonymous pipeline metrics will be collected");
|
|
8023
|
-
info("You can disable anytime: remove BEASTMODE_TELEMETRY_ENABLED from .env");
|
|
8024
|
-
} else {
|
|
8025
|
-
info("Telemetry disabled \u2014 no data will be collected");
|
|
8026
|
-
}
|
|
8027
|
-
step(4, totalSteps, "Generate");
|
|
8028
|
-
mkdirSync12(targetDir, { recursive: true });
|
|
8238
|
+
step(3, totalSteps, "Generate");
|
|
8239
|
+
mkdirSync13(targetDir, { recursive: true });
|
|
8029
8240
|
const composeContent = generateComposeYaml("latest");
|
|
8030
|
-
|
|
8241
|
+
writeFileSync14(join16(targetDir, "docker-compose.yml"), composeContent, "utf-8");
|
|
8031
8242
|
success("docker-compose.yml");
|
|
8032
8243
|
const envLines = [
|
|
8033
8244
|
"# BeastMode environment \u2014 DO NOT COMMIT",
|
|
@@ -8065,22 +8276,14 @@ async function runImageModeInit(name, opts) {
|
|
|
8065
8276
|
"# (git remote get-url origin) doesn't work for your setup.",
|
|
8066
8277
|
"# The daemon handles this automatically in >99% of cases.",
|
|
8067
8278
|
"# PROJECT_REPO=owner/repo",
|
|
8068
|
-
"",
|
|
8069
|
-
"# \u2500\u2500 Telemetry (Gap 15) \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",
|
|
8070
|
-
"# Opt-in anonymous pipeline metrics. Default: off. Collects",
|
|
8071
|
-
"# durations, satisfaction scores, iteration counts, error classes.",
|
|
8072
|
-
"# Never collects code, file paths, task descriptions, or PII.",
|
|
8073
|
-
"# See docs/telemetry.md for the full privacy contract.",
|
|
8074
|
-
`BEASTMODE_TELEMETRY_ENABLED=${telemetryEnabled ? "true" : "false"}`,
|
|
8075
|
-
`BEASTMODE_TELEMETRY_SENTRY_DSN=${telemetrySentryDsn}`,
|
|
8076
8279
|
""
|
|
8077
8280
|
];
|
|
8078
|
-
|
|
8281
|
+
writeFileSync14(join16(targetDir, ".env"), envLines.join("\n"), "utf-8");
|
|
8079
8282
|
success(`.env (PROJECT_DIR=${projectPath}, two-PAT model)`);
|
|
8080
8283
|
for (const dir of ["data", "runs", "daemon/logs", ".beastmode", "config"]) {
|
|
8081
|
-
|
|
8284
|
+
mkdirSync13(join16(targetDir, dir), { recursive: true });
|
|
8082
8285
|
}
|
|
8083
|
-
step(
|
|
8286
|
+
step(4, totalSteps, "Pull Images");
|
|
8084
8287
|
try {
|
|
8085
8288
|
const { execSync: exec } = await import("child_process");
|
|
8086
8289
|
exec("docker compose pull", {
|
|
@@ -8109,6 +8312,26 @@ async function runImageModeInit(name, opts) {
|
|
|
8109
8312
|
console.log();
|
|
8110
8313
|
warn("Files generated \u2014 fix auth above, then run: beastmode up --pull");
|
|
8111
8314
|
}
|
|
8315
|
+
if (process.platform === "darwin") {
|
|
8316
|
+
try {
|
|
8317
|
+
const { syncClaudeCredsOnce: syncClaudeCredsOnce2, installAgent: installAgent2 } = await Promise.resolve().then(() => (init_sync_claude_creds(), sync_claude_creds_exports));
|
|
8318
|
+
const sync = syncClaudeCredsOnce2();
|
|
8319
|
+
if ("error" in sync) {
|
|
8320
|
+
warn(`Claude creds sync skipped: ${sync.error}`);
|
|
8321
|
+
info(" Run `beastmode sync-claude-creds --install` after `claude login`.");
|
|
8322
|
+
} else {
|
|
8323
|
+
success(`Claude creds synced: ${sync.path}`);
|
|
8324
|
+
try {
|
|
8325
|
+
installAgent2(1800, { throwOnError: true });
|
|
8326
|
+
} catch (e) {
|
|
8327
|
+
warn(`Claude-creds LaunchAgent install failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
8328
|
+
info(" You can retry with: beastmode sync-claude-creds --install");
|
|
8329
|
+
}
|
|
8330
|
+
}
|
|
8331
|
+
} catch (e) {
|
|
8332
|
+
warn(`Claude-creds setup skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
8333
|
+
}
|
|
8334
|
+
}
|
|
8112
8335
|
console.log();
|
|
8113
8336
|
header("BeastMode is ready!");
|
|
8114
8337
|
console.log();
|
|
@@ -8126,20 +8349,21 @@ async function runImageModeInit(name, opts) {
|
|
|
8126
8349
|
|
|
8127
8350
|
// src/cli/commands/export-config.ts
|
|
8128
8351
|
init_export_adapter();
|
|
8129
|
-
|
|
8130
|
-
import {
|
|
8131
|
-
import {
|
|
8352
|
+
init_display();
|
|
8353
|
+
import { Command as Command3 } from "commander";
|
|
8354
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync15, existsSync as existsSync19, readdirSync as readdirSync7 } from "fs";
|
|
8355
|
+
import { resolve as resolve6, join as join17 } from "path";
|
|
8132
8356
|
function exportFactory(factoryDir, outputPath) {
|
|
8133
|
-
const bmDir =
|
|
8134
|
-
const configPath =
|
|
8135
|
-
if (!
|
|
8357
|
+
const bmDir = join17(factoryDir, ".beastmode");
|
|
8358
|
+
const configPath = join17(bmDir, "config.json");
|
|
8359
|
+
if (!existsSync19(configPath)) {
|
|
8136
8360
|
throw new Error(`No factory found at ${factoryDir}`);
|
|
8137
8361
|
}
|
|
8138
8362
|
const config = JSON.parse(readFileSync15(configPath, "utf-8"));
|
|
8139
|
-
const identity = JSON.parse(readFileSync15(
|
|
8363
|
+
const identity = JSON.parse(readFileSync15(join17(bmDir, "factory.json"), "utf-8"));
|
|
8140
8364
|
let plugins = [];
|
|
8141
|
-
const lockPath =
|
|
8142
|
-
if (
|
|
8365
|
+
const lockPath = join17(bmDir, "extensions.lock");
|
|
8366
|
+
if (existsSync19(lockPath)) {
|
|
8143
8367
|
const lock = JSON.parse(readFileSync15(lockPath, "utf-8"));
|
|
8144
8368
|
plugins = Object.keys(lock.plugins || {});
|
|
8145
8369
|
}
|
|
@@ -8149,7 +8373,7 @@ function exportFactory(factoryDir, outputPath) {
|
|
|
8149
8373
|
config,
|
|
8150
8374
|
plugins
|
|
8151
8375
|
};
|
|
8152
|
-
|
|
8376
|
+
writeFileSync15(outputPath, JSON.stringify(template, null, 2) + "\n", "utf-8");
|
|
8153
8377
|
}
|
|
8154
8378
|
function findRunDir(runId) {
|
|
8155
8379
|
const candidates = [
|
|
@@ -8157,7 +8381,7 @@ function findRunDir(runId) {
|
|
|
8157
8381
|
resolve6(".", runId)
|
|
8158
8382
|
];
|
|
8159
8383
|
for (const candidate of candidates) {
|
|
8160
|
-
if (
|
|
8384
|
+
if (existsSync19(candidate)) {
|
|
8161
8385
|
return candidate;
|
|
8162
8386
|
}
|
|
8163
8387
|
}
|
|
@@ -8166,28 +8390,28 @@ function findRunDir(runId) {
|
|
|
8166
8390
|
);
|
|
8167
8391
|
}
|
|
8168
8392
|
function createArtifactExportCommand(artifact) {
|
|
8169
|
-
return new
|
|
8393
|
+
return new Command3(artifact).description(`Export ${artifact} from a pipeline run`).requiredOption("--run <id>", "Run ID to export from").option("--adapter <id>", "Export adapter (default: generic:markdown)", "generic:markdown").option("--output <path>", "Output file path (default: stdout)").action((opts) => {
|
|
8170
8394
|
try {
|
|
8171
8395
|
const runDir = findRunDir(opts.run);
|
|
8172
8396
|
let sourceContent;
|
|
8173
8397
|
if (artifact === "scenarios") {
|
|
8174
|
-
const scenariosDir =
|
|
8175
|
-
if (!
|
|
8398
|
+
const scenariosDir = join17(runDir, "scenarios");
|
|
8399
|
+
if (!existsSync19(scenariosDir)) {
|
|
8176
8400
|
throw new Error(`No scenarios directory found in run ${opts.run}`);
|
|
8177
8401
|
}
|
|
8178
8402
|
const files = readdirSync7(scenariosDir).filter((f) => f.endsWith(".md")).sort();
|
|
8179
|
-
sourceContent = files.map((f) => readFileSync15(
|
|
8403
|
+
sourceContent = files.map((f) => readFileSync15(join17(scenariosDir, f), "utf-8")).join("\n\n---\n\n");
|
|
8180
8404
|
} else {
|
|
8181
8405
|
const artifactFile = artifact === "nlspec" ? "nlspec.md" : "plan.md";
|
|
8182
|
-
const artifactPath =
|
|
8183
|
-
if (!
|
|
8406
|
+
const artifactPath = join17(runDir, artifactFile);
|
|
8407
|
+
if (!existsSync19(artifactPath)) {
|
|
8184
8408
|
throw new Error(`${artifactFile} not found in run ${opts.run}`);
|
|
8185
8409
|
}
|
|
8186
8410
|
sourceContent = readFileSync15(artifactPath, "utf-8");
|
|
8187
8411
|
}
|
|
8188
8412
|
const result = runExportAdapter(opts.adapter, sourceContent);
|
|
8189
8413
|
if (opts.output) {
|
|
8190
|
-
|
|
8414
|
+
writeFileSync15(resolve6(opts.output), result, "utf-8");
|
|
8191
8415
|
header(`Exported ${artifact}`);
|
|
8192
8416
|
success(`Written to: ${opts.output}`);
|
|
8193
8417
|
} else {
|
|
@@ -8199,9 +8423,9 @@ function createArtifactExportCommand(artifact) {
|
|
|
8199
8423
|
}
|
|
8200
8424
|
});
|
|
8201
8425
|
}
|
|
8202
|
-
var exportCommand = new
|
|
8426
|
+
var exportCommand = new Command3("export").description("Export factory config or pipeline artifacts");
|
|
8203
8427
|
exportCommand.addCommand(
|
|
8204
|
-
new
|
|
8428
|
+
new Command3("config").description("Export factory configuration template (secrets excluded)").option("--output <path>", "Output file path", "beastmode-template.json").action((opts) => {
|
|
8205
8429
|
try {
|
|
8206
8430
|
const factoryDir = resolve6(".");
|
|
8207
8431
|
exportFactory(factoryDir, resolve6(opts.output));
|
|
@@ -8220,9 +8444,10 @@ exportCommand.addCommand(createArtifactExportCommand("scenarios"));
|
|
|
8220
8444
|
|
|
8221
8445
|
// src/cli/commands/validate.ts
|
|
8222
8446
|
init_config_validator();
|
|
8223
|
-
|
|
8447
|
+
init_display();
|
|
8448
|
+
import { Command as Command4 } from "commander";
|
|
8224
8449
|
import { resolve as resolve7 } from "path";
|
|
8225
|
-
var validateCommand = new
|
|
8450
|
+
var validateCommand = new Command4("validate").description("Validate factory health").option("--verbose", "Show all checks").action((opts) => {
|
|
8226
8451
|
const factoryDir = resolve7(".");
|
|
8227
8452
|
const result = validateFactory(factoryDir, process.env);
|
|
8228
8453
|
header("Factory Validation");
|
|
@@ -8251,7 +8476,8 @@ init_plugin_installer();
|
|
|
8251
8476
|
init_mcp_manager();
|
|
8252
8477
|
init_hook_manager();
|
|
8253
8478
|
init_skill_manager();
|
|
8254
|
-
|
|
8479
|
+
init_display();
|
|
8480
|
+
import { Command as Command5 } from "commander";
|
|
8255
8481
|
import { resolve as resolve8 } from "path";
|
|
8256
8482
|
async function addPluginAction(factoryDir, source, options) {
|
|
8257
8483
|
await installPlugin(factoryDir, source);
|
|
@@ -8278,8 +8504,8 @@ function addHookAction(factoryDir, event, name, options) {
|
|
|
8278
8504
|
source: "user"
|
|
8279
8505
|
});
|
|
8280
8506
|
}
|
|
8281
|
-
var addCommand = new
|
|
8282
|
-
new
|
|
8507
|
+
var addCommand = new Command5("add").description("Add extensions to the factory").addCommand(
|
|
8508
|
+
new Command5("plugin").description("Install a plugin (from path, URL, or registry name)").argument("<source>", "Plugin source: local path, git URL, or registry name").option("--version <ver>", "Specific version to install").action(async (source, opts) => {
|
|
8283
8509
|
try {
|
|
8284
8510
|
const factoryDir = resolve8(".");
|
|
8285
8511
|
await addPluginAction(factoryDir, source, opts);
|
|
@@ -8290,7 +8516,7 @@ var addCommand = new Command4("add").description("Add extensions to the factory"
|
|
|
8290
8516
|
}
|
|
8291
8517
|
})
|
|
8292
8518
|
).addCommand(
|
|
8293
|
-
new
|
|
8519
|
+
new Command5("mcp").description("Add an MCP server").argument("<name>", "Server name").requiredOption("--command <cmd>", "Command to run").option("--args <args...>", "Command arguments", []).option("--config <json>", "Configuration as JSON string").action(
|
|
8294
8520
|
(name, opts) => {
|
|
8295
8521
|
try {
|
|
8296
8522
|
const factoryDir = resolve8(".");
|
|
@@ -8304,7 +8530,7 @@ var addCommand = new Command4("add").description("Add extensions to the factory"
|
|
|
8304
8530
|
}
|
|
8305
8531
|
)
|
|
8306
8532
|
).addCommand(
|
|
8307
|
-
new
|
|
8533
|
+
new Command5("skill").description("Add a custom skill from a directory").argument("<path>", "Path to skill directory (must contain SKILL.md)").action((path) => {
|
|
8308
8534
|
try {
|
|
8309
8535
|
const factoryDir = resolve8(".");
|
|
8310
8536
|
addSkillAction(factoryDir, path);
|
|
@@ -8315,7 +8541,7 @@ var addCommand = new Command4("add").description("Add extensions to the factory"
|
|
|
8315
8541
|
}
|
|
8316
8542
|
})
|
|
8317
8543
|
).addCommand(
|
|
8318
|
-
new
|
|
8544
|
+
new Command5("hook").description("Add a pipeline hook").argument("<event>", "Hook event (e.g., pre-build, post-verify)").argument("<name>", "Hook name").option("--command <cmd>", "Shell command to run").option("--skill <name>", "Skill to invoke").option("--webhook <url>", "Webhook URL to POST to").option("--config <json>", "Configuration as JSON string").action(
|
|
8319
8545
|
(event, name, opts) => {
|
|
8320
8546
|
try {
|
|
8321
8547
|
const factoryDir = resolve8(".");
|
|
@@ -8340,7 +8566,8 @@ init_plugin_installer();
|
|
|
8340
8566
|
init_mcp_manager();
|
|
8341
8567
|
init_hook_manager();
|
|
8342
8568
|
init_skill_manager();
|
|
8343
|
-
|
|
8569
|
+
init_display();
|
|
8570
|
+
import { Command as Command6 } from "commander";
|
|
8344
8571
|
import { resolve as resolve9 } from "path";
|
|
8345
8572
|
function removePluginAction(factoryDir, name, options) {
|
|
8346
8573
|
removePlugin(factoryDir, name, options);
|
|
@@ -8354,8 +8581,8 @@ function removeSkillAction(factoryDir, name) {
|
|
|
8354
8581
|
function removeHookAction(factoryDir, event, name) {
|
|
8355
8582
|
removeHook(factoryDir, event, name);
|
|
8356
8583
|
}
|
|
8357
|
-
var removeCommand = new
|
|
8358
|
-
new
|
|
8584
|
+
var removeCommand = new Command6("remove").description("Remove extensions from the factory").addCommand(
|
|
8585
|
+
new Command6("plugin").description("Remove an installed plugin").argument("<name>", "Plugin name").option("--force", "Force removal even if other plugins depend on it").action((name, opts) => {
|
|
8359
8586
|
try {
|
|
8360
8587
|
const factoryDir = resolve9(".");
|
|
8361
8588
|
removePluginAction(factoryDir, name, opts);
|
|
@@ -8366,7 +8593,7 @@ var removeCommand = new Command5("remove").description("Remove extensions from t
|
|
|
8366
8593
|
}
|
|
8367
8594
|
})
|
|
8368
8595
|
).addCommand(
|
|
8369
|
-
new
|
|
8596
|
+
new Command6("mcp").description("Remove an MCP server").argument("<name>", "Server name").action((name) => {
|
|
8370
8597
|
try {
|
|
8371
8598
|
const factoryDir = resolve9(".");
|
|
8372
8599
|
removeMcpAction(factoryDir, name);
|
|
@@ -8377,7 +8604,7 @@ var removeCommand = new Command5("remove").description("Remove extensions from t
|
|
|
8377
8604
|
}
|
|
8378
8605
|
})
|
|
8379
8606
|
).addCommand(
|
|
8380
|
-
new
|
|
8607
|
+
new Command6("skill").description("Remove a custom skill").argument("<name>", "Skill name").action((name) => {
|
|
8381
8608
|
try {
|
|
8382
8609
|
const factoryDir = resolve9(".");
|
|
8383
8610
|
removeSkillAction(factoryDir, name);
|
|
@@ -8388,7 +8615,7 @@ var removeCommand = new Command5("remove").description("Remove extensions from t
|
|
|
8388
8615
|
}
|
|
8389
8616
|
})
|
|
8390
8617
|
).addCommand(
|
|
8391
|
-
new
|
|
8618
|
+
new Command6("hook").description("Remove a pipeline hook").argument("<event>", "Hook event (e.g., pre-build, post-verify)").argument("<name>", "Hook name").action((event, name) => {
|
|
8392
8619
|
try {
|
|
8393
8620
|
const factoryDir = resolve9(".");
|
|
8394
8621
|
removeHookAction(factoryDir, event, name);
|
|
@@ -8405,13 +8632,14 @@ init_mcp_manager();
|
|
|
8405
8632
|
init_hook_manager();
|
|
8406
8633
|
init_skill_manager();
|
|
8407
8634
|
init_presets();
|
|
8408
|
-
|
|
8635
|
+
init_display();
|
|
8636
|
+
import { Command as Command7 } from "commander";
|
|
8409
8637
|
import { resolve as resolve10 } from "path";
|
|
8410
|
-
import { readFileSync as readFileSync16, existsSync as
|
|
8411
|
-
import { join as
|
|
8638
|
+
import { readFileSync as readFileSync16, existsSync as existsSync20 } from "fs";
|
|
8639
|
+
import { join as join18 } from "path";
|
|
8412
8640
|
function listPluginsAction(factoryDir) {
|
|
8413
|
-
const lockPath =
|
|
8414
|
-
if (!
|
|
8641
|
+
const lockPath = join18(factoryDir, ".beastmode", "extensions.lock");
|
|
8642
|
+
if (!existsSync20(lockPath)) {
|
|
8415
8643
|
console.log(" No plugins installed (extensions.lock not found).");
|
|
8416
8644
|
return;
|
|
8417
8645
|
}
|
|
@@ -8488,39 +8716,40 @@ function listPresetsAction() {
|
|
|
8488
8716
|
console.log(` ${name} \u2014 ${preset.description}`);
|
|
8489
8717
|
}
|
|
8490
8718
|
}
|
|
8491
|
-
var listCommand = new
|
|
8492
|
-
new
|
|
8719
|
+
var listCommand = new Command7("list").description("List installed extensions and resources").addCommand(
|
|
8720
|
+
new Command7("plugins").description("List installed plugins").action(() => {
|
|
8493
8721
|
listPluginsAction(resolve10("."));
|
|
8494
8722
|
})
|
|
8495
8723
|
).addCommand(
|
|
8496
|
-
new
|
|
8724
|
+
new Command7("mcps").description("List configured MCP servers").action(() => {
|
|
8497
8725
|
listMcpsAction(resolve10("."));
|
|
8498
8726
|
})
|
|
8499
8727
|
).addCommand(
|
|
8500
|
-
new
|
|
8728
|
+
new Command7("skills").description("List available skills").action(() => {
|
|
8501
8729
|
listSkillsAction(resolve10("."));
|
|
8502
8730
|
})
|
|
8503
8731
|
).addCommand(
|
|
8504
|
-
new
|
|
8732
|
+
new Command7("hooks").description("List configured hooks").action(() => {
|
|
8505
8733
|
listHooksAction(resolve10("."));
|
|
8506
8734
|
})
|
|
8507
8735
|
).addCommand(
|
|
8508
|
-
new
|
|
8736
|
+
new Command7("presets").description("List available pipeline presets").action(() => {
|
|
8509
8737
|
listPresetsAction();
|
|
8510
8738
|
})
|
|
8511
8739
|
);
|
|
8512
8740
|
|
|
8513
8741
|
// src/cli/commands/import-cmd.ts
|
|
8514
8742
|
init_import_adapter();
|
|
8515
|
-
|
|
8516
|
-
import {
|
|
8517
|
-
import {
|
|
8743
|
+
init_display();
|
|
8744
|
+
import { Command as Command8 } from "commander";
|
|
8745
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync16, mkdirSync as mkdirSync14, existsSync as existsSync21 } from "fs";
|
|
8746
|
+
import { resolve as resolve11, join as join19 } from "path";
|
|
8518
8747
|
var VALID_ARTIFACTS = ["nlspec", "plan", "scenarios"];
|
|
8519
8748
|
function createArtifactCommand(artifact) {
|
|
8520
|
-
return new
|
|
8749
|
+
return new Command8(artifact).description(`Import external document as ${artifact}`).requiredOption("--from <path>", "Path to source file").requiredOption("--adapter <id>", "Adapter ID (e.g., generic:prd, bmad:brainstorm)").option("--output <path>", "Output path (default: stdout)").action((opts) => {
|
|
8521
8750
|
try {
|
|
8522
8751
|
const sourcePath = resolve11(opts.from);
|
|
8523
|
-
if (!
|
|
8752
|
+
if (!existsSync21(sourcePath)) {
|
|
8524
8753
|
throw new Error(`Source file not found: ${sourcePath}`);
|
|
8525
8754
|
}
|
|
8526
8755
|
const sourceContent = readFileSync17(sourcePath, "utf-8");
|
|
@@ -8529,15 +8758,15 @@ function createArtifactCommand(artifact) {
|
|
|
8529
8758
|
const outputPath = resolve11(opts.output);
|
|
8530
8759
|
if (artifact === "scenarios" && opts.adapter.endsWith(":test-cases")) {
|
|
8531
8760
|
const scenarios = JSON.parse(result);
|
|
8532
|
-
|
|
8761
|
+
mkdirSync14(outputPath, { recursive: true });
|
|
8533
8762
|
for (const scenario of scenarios) {
|
|
8534
|
-
const filePath =
|
|
8535
|
-
|
|
8763
|
+
const filePath = join19(outputPath, `${scenario.name}.md`);
|
|
8764
|
+
writeFileSync16(filePath, scenario.content, "utf-8");
|
|
8536
8765
|
success(` ${scenario.name}.md`);
|
|
8537
8766
|
}
|
|
8538
8767
|
header(`Imported ${scenarios.length} scenarios to ${opts.output}`);
|
|
8539
8768
|
} else {
|
|
8540
|
-
|
|
8769
|
+
writeFileSync16(outputPath, result, "utf-8");
|
|
8541
8770
|
header(`Imported ${artifact}`);
|
|
8542
8771
|
success(`Written to: ${opts.output}`);
|
|
8543
8772
|
}
|
|
@@ -8550,7 +8779,7 @@ function createArtifactCommand(artifact) {
|
|
|
8550
8779
|
}
|
|
8551
8780
|
});
|
|
8552
8781
|
}
|
|
8553
|
-
var importCommand = new
|
|
8782
|
+
var importCommand = new Command8("import").description("Import external artifacts into BeastMode format");
|
|
8554
8783
|
for (const artifact of VALID_ARTIFACTS) {
|
|
8555
8784
|
importCommand.addCommand(createArtifactCommand(artifact));
|
|
8556
8785
|
}
|
|
@@ -8558,51 +8787,52 @@ for (const artifact of VALID_ARTIFACTS) {
|
|
|
8558
8787
|
// src/cli/commands/status.ts
|
|
8559
8788
|
init_status_checker();
|
|
8560
8789
|
init_schemas();
|
|
8561
|
-
|
|
8562
|
-
import {
|
|
8563
|
-
import {
|
|
8564
|
-
import {
|
|
8790
|
+
init_display();
|
|
8791
|
+
import { Command as Command9 } from "commander";
|
|
8792
|
+
import { existsSync as existsSync22, readFileSync as readFileSync18, readdirSync as readdirSync8 } from "fs";
|
|
8793
|
+
import { execSync as execSync5 } from "child_process";
|
|
8794
|
+
import { join as join20, resolve as resolve12 } from "path";
|
|
8565
8795
|
function statusAction(factoryDir, opts) {
|
|
8566
|
-
const bmDir =
|
|
8567
|
-
if (!
|
|
8796
|
+
const bmDir = join20(factoryDir, ".beastmode");
|
|
8797
|
+
if (!existsSync22(bmDir)) {
|
|
8568
8798
|
throw new Error(`No factory found at ${factoryDir}`);
|
|
8569
8799
|
}
|
|
8570
|
-
const factoryJsonPath =
|
|
8800
|
+
const factoryJsonPath = join20(bmDir, "factory.json");
|
|
8571
8801
|
const rawIdentity = JSON.parse(readFileSync18(factoryJsonPath, "utf-8"));
|
|
8572
8802
|
const factoryIdentity = FactoryIdentitySchema.parse(rawIdentity);
|
|
8573
|
-
const projectsDir =
|
|
8574
|
-
const projectCount =
|
|
8575
|
-
const pluginsDir =
|
|
8576
|
-
const pluginNames =
|
|
8577
|
-
const mcpPath =
|
|
8803
|
+
const projectsDir = join20(bmDir, "projects");
|
|
8804
|
+
const projectCount = existsSync22(projectsDir) ? readdirSync8(projectsDir).filter((f) => f.endsWith(".json")).length : 0;
|
|
8805
|
+
const pluginsDir = join20(bmDir, "plugins");
|
|
8806
|
+
const pluginNames = existsSync22(pluginsDir) ? readdirSync8(pluginsDir) : [];
|
|
8807
|
+
const mcpPath = join20(bmDir, "mcp-servers.json");
|
|
8578
8808
|
let mcpServers = {};
|
|
8579
|
-
if (
|
|
8809
|
+
if (existsSync22(mcpPath)) {
|
|
8580
8810
|
try {
|
|
8581
8811
|
const raw = JSON.parse(readFileSync18(mcpPath, "utf-8"));
|
|
8582
8812
|
mcpServers = raw.servers || {};
|
|
8583
8813
|
} catch {
|
|
8584
8814
|
}
|
|
8585
8815
|
}
|
|
8586
|
-
const hooksPath =
|
|
8816
|
+
const hooksPath = join20(bmDir, "hooks.json");
|
|
8587
8817
|
let hooks = {};
|
|
8588
|
-
if (
|
|
8818
|
+
if (existsSync22(hooksPath)) {
|
|
8589
8819
|
try {
|
|
8590
8820
|
const raw = JSON.parse(readFileSync18(hooksPath, "utf-8"));
|
|
8591
8821
|
hooks = raw.hooks || {};
|
|
8592
8822
|
} catch {
|
|
8593
8823
|
}
|
|
8594
8824
|
}
|
|
8595
|
-
const skillsDir =
|
|
8596
|
-
const skillCount =
|
|
8597
|
-
const runsDir =
|
|
8825
|
+
const skillsDir = join20(bmDir, "skills");
|
|
8826
|
+
const skillCount = existsSync22(skillsDir) ? readdirSync8(skillsDir).length : 0;
|
|
8827
|
+
const runsDir = join20(factoryDir, "runs");
|
|
8598
8828
|
let runDirs = [];
|
|
8599
|
-
if (
|
|
8829
|
+
if (existsSync22(runsDir)) {
|
|
8600
8830
|
runDirs = readdirSync8(runsDir).filter((d) => d.startsWith("run-")).sort();
|
|
8601
8831
|
}
|
|
8602
|
-
const pidPath =
|
|
8832
|
+
const pidPath = join20(bmDir, "daemon.pid");
|
|
8603
8833
|
let daemonPid = null;
|
|
8604
8834
|
let pidAlive = false;
|
|
8605
|
-
if (
|
|
8835
|
+
if (existsSync22(pidPath)) {
|
|
8606
8836
|
try {
|
|
8607
8837
|
daemonPid = parseInt(readFileSync18(pidPath, "utf-8").trim(), 10);
|
|
8608
8838
|
process.kill(daemonPid, 0);
|
|
@@ -8613,7 +8843,7 @@ function statusAction(factoryDir, opts) {
|
|
|
8613
8843
|
}
|
|
8614
8844
|
if (!pidAlive) {
|
|
8615
8845
|
try {
|
|
8616
|
-
const out =
|
|
8846
|
+
const out = execSync5("pgrep -f 'beastmode_daemon' 2>/dev/null || true", {
|
|
8617
8847
|
encoding: "utf-8",
|
|
8618
8848
|
timeout: 3e3
|
|
8619
8849
|
}).trim();
|
|
@@ -8640,7 +8870,7 @@ function statusAction(factoryDir, opts) {
|
|
|
8640
8870
|
};
|
|
8641
8871
|
return collectStatus(input);
|
|
8642
8872
|
}
|
|
8643
|
-
var statusCommand = new
|
|
8873
|
+
var statusCommand = new Command9("status").description("Show factory status overview").option("--json", "Output as JSON").option("--watch", "Enable watch mode (display layer)").action((opts) => {
|
|
8644
8874
|
const factoryDir = resolve12(".");
|
|
8645
8875
|
try {
|
|
8646
8876
|
const status = statusAction(factoryDir, { json: !!opts.json });
|
|
@@ -8676,20 +8906,21 @@ var statusCommand = new Command8("status").description("Show factory status over
|
|
|
8676
8906
|
|
|
8677
8907
|
// src/cli/commands/config-cmd.ts
|
|
8678
8908
|
init_config_manager();
|
|
8679
|
-
|
|
8680
|
-
import {
|
|
8681
|
-
import {
|
|
8682
|
-
import {
|
|
8909
|
+
init_display();
|
|
8910
|
+
import { Command as Command10 } from "commander";
|
|
8911
|
+
import { existsSync as existsSync23, readFileSync as readFileSync19, writeFileSync as writeFileSync17 } from "fs";
|
|
8912
|
+
import { join as join21, resolve as resolve13 } from "path";
|
|
8913
|
+
import { execSync as execSync6 } from "child_process";
|
|
8683
8914
|
function readConfig2(factoryDir) {
|
|
8684
|
-
const configPath =
|
|
8685
|
-
if (!
|
|
8915
|
+
const configPath = join21(factoryDir, ".beastmode", "config.json");
|
|
8916
|
+
if (!existsSync23(configPath)) {
|
|
8686
8917
|
throw new Error("No config.json found. Run beastmode init first.");
|
|
8687
8918
|
}
|
|
8688
8919
|
return JSON.parse(readFileSync19(configPath, "utf-8"));
|
|
8689
8920
|
}
|
|
8690
8921
|
function writeConfig2(factoryDir, config) {
|
|
8691
|
-
const configPath =
|
|
8692
|
-
|
|
8922
|
+
const configPath = join21(factoryDir, ".beastmode", "config.json");
|
|
8923
|
+
writeFileSync17(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
8693
8924
|
}
|
|
8694
8925
|
function configGetAction(factoryDir, key) {
|
|
8695
8926
|
const config = readConfig2(factoryDir);
|
|
@@ -8706,7 +8937,7 @@ function configResetAction(factoryDir, key) {
|
|
|
8706
8937
|
const updated = configReset(config, key);
|
|
8707
8938
|
writeConfig2(factoryDir, updated);
|
|
8708
8939
|
}
|
|
8709
|
-
var configGetCmd = new
|
|
8940
|
+
var configGetCmd = new Command10("get").description("Get a config value by dot-notation key").argument("<key>", "Config key (e.g., pipeline.preset)").action((key) => {
|
|
8710
8941
|
const factoryDir = resolve13(".");
|
|
8711
8942
|
try {
|
|
8712
8943
|
const value = configGetAction(factoryDir, key);
|
|
@@ -8720,7 +8951,7 @@ var configGetCmd = new Command9("get").description("Get a config value by dot-no
|
|
|
8720
8951
|
process.exit(1);
|
|
8721
8952
|
}
|
|
8722
8953
|
});
|
|
8723
|
-
var configSetCmd = new
|
|
8954
|
+
var configSetCmd = new Command10("set").description("Set a config value by dot-notation key").argument("<key>", "Config key (e.g., pipeline.preset)").argument("<value>", "Value to set (auto-coerces types)").action((key, value) => {
|
|
8724
8955
|
const factoryDir = resolve13(".");
|
|
8725
8956
|
try {
|
|
8726
8957
|
configSetAction(factoryDir, key, value);
|
|
@@ -8730,22 +8961,22 @@ var configSetCmd = new Command9("set").description("Set a config value by dot-no
|
|
|
8730
8961
|
process.exit(1);
|
|
8731
8962
|
}
|
|
8732
8963
|
});
|
|
8733
|
-
var configEditCmd = new
|
|
8964
|
+
var configEditCmd = new Command10("edit").description("Open config.json in $EDITOR").action(() => {
|
|
8734
8965
|
const factoryDir = resolve13(".");
|
|
8735
|
-
const configPath =
|
|
8736
|
-
if (!
|
|
8966
|
+
const configPath = join21(factoryDir, ".beastmode", "config.json");
|
|
8967
|
+
if (!existsSync23(configPath)) {
|
|
8737
8968
|
error("No config.json found. Run beastmode init first.");
|
|
8738
8969
|
process.exit(1);
|
|
8739
8970
|
}
|
|
8740
8971
|
const editor = process.env.EDITOR || "vi";
|
|
8741
8972
|
try {
|
|
8742
|
-
|
|
8973
|
+
execSync6(`${editor} ${configPath}`, { stdio: "inherit" });
|
|
8743
8974
|
} catch {
|
|
8744
8975
|
error(`Failed to open editor: ${editor}`);
|
|
8745
8976
|
process.exit(1);
|
|
8746
8977
|
}
|
|
8747
8978
|
});
|
|
8748
|
-
var configResetCmd = new
|
|
8979
|
+
var configResetCmd = new Command10("reset").description("Reset config to defaults (all or specific key)").argument("[key]", "Optional key to reset (resets all if omitted)").action((key) => {
|
|
8749
8980
|
const factoryDir = resolve13(".");
|
|
8750
8981
|
try {
|
|
8751
8982
|
configResetAction(factoryDir, key);
|
|
@@ -8759,27 +8990,29 @@ var configResetCmd = new Command9("reset").description("Reset config to defaults
|
|
|
8759
8990
|
process.exit(1);
|
|
8760
8991
|
}
|
|
8761
8992
|
});
|
|
8762
|
-
var configCommand = new
|
|
8993
|
+
var configCommand = new Command10("config").description("Manage factory configuration").addCommand(configGetCmd).addCommand(configSetCmd).addCommand(configEditCmd).addCommand(configResetCmd);
|
|
8763
8994
|
|
|
8764
8995
|
// src/cli/commands/doctor.ts
|
|
8765
8996
|
init_doctor();
|
|
8766
8997
|
init_schemas();
|
|
8767
8998
|
init_version();
|
|
8768
8999
|
init_plugin_resolver();
|
|
8769
|
-
|
|
8770
|
-
import {
|
|
8771
|
-
import {
|
|
8772
|
-
import {
|
|
8773
|
-
import {
|
|
9000
|
+
init_display();
|
|
9001
|
+
import { Command as Command12 } from "commander";
|
|
9002
|
+
import { existsSync as existsSync25, readFileSync as readFileSync21, readdirSync as readdirSync10 } from "fs";
|
|
9003
|
+
import { join as join23, resolve as resolve15 } from "path";
|
|
9004
|
+
import { execSync as execSync8 } from "child_process";
|
|
9005
|
+
import { homedir as homedir3, platform as platform2 } from "os";
|
|
8774
9006
|
import * as http3 from "http";
|
|
8775
9007
|
import * as https from "https";
|
|
8776
9008
|
|
|
8777
9009
|
// src/cli/commands/board.ts
|
|
8778
|
-
|
|
8779
|
-
import {
|
|
8780
|
-
import {
|
|
8781
|
-
import {
|
|
8782
|
-
|
|
9010
|
+
init_display();
|
|
9011
|
+
import { Command as Command11 } from "commander";
|
|
9012
|
+
import { resolve as resolve14, join as join22 } from "path";
|
|
9013
|
+
import { existsSync as existsSync24, readFileSync as readFileSync20, mkdirSync as mkdirSync15, writeFileSync as writeFileSync18, readdirSync as readdirSync9 } from "fs";
|
|
9014
|
+
import { execSync as execSync7 } from "child_process";
|
|
9015
|
+
var boardCommand = new Command11("board").description("Launch the BeastMode Board web UI").option("--port <number>", "Port to serve on", "7669").option("--host <host>", "Host to bind to (use 0.0.0.0 for external access)", "127.0.0.1").action(async (opts) => {
|
|
8783
9016
|
try {
|
|
8784
9017
|
await runBoard(opts);
|
|
8785
9018
|
} catch (err) {
|
|
@@ -8801,28 +9034,28 @@ function findFactoryDir(startDir) {
|
|
|
8801
9034
|
let dir = startDir || process.cwd();
|
|
8802
9035
|
const root = resolve14("/");
|
|
8803
9036
|
while (dir !== root) {
|
|
8804
|
-
if (
|
|
9037
|
+
if (existsSync24(join22(dir, ".beastmode", "factory.json"))) {
|
|
8805
9038
|
return dir;
|
|
8806
9039
|
}
|
|
8807
9040
|
const parent = resolve14(dir, "..");
|
|
8808
9041
|
if (parent === dir) break;
|
|
8809
9042
|
dir = parent;
|
|
8810
9043
|
}
|
|
8811
|
-
if (
|
|
9044
|
+
if (existsSync24(join22(dir, ".beastmode", "factory.json"))) {
|
|
8812
9045
|
return dir;
|
|
8813
9046
|
}
|
|
8814
9047
|
return null;
|
|
8815
9048
|
}
|
|
8816
9049
|
function inferProjectName(factoryDir) {
|
|
8817
|
-
const projectsDir =
|
|
8818
|
-
if (!
|
|
9050
|
+
const projectsDir = join22(factoryDir, ".beastmode", "projects");
|
|
9051
|
+
if (!existsSync24(projectsDir)) return null;
|
|
8819
9052
|
const files = readdirSync9(projectsDir).filter((f) => f.endsWith(".json"));
|
|
8820
9053
|
if (files.length === 1) return files[0].replace(/\.json$/, "");
|
|
8821
9054
|
return null;
|
|
8822
9055
|
}
|
|
8823
9056
|
function tryExecSync(cmd, timeoutMs = 15e3) {
|
|
8824
9057
|
try {
|
|
8825
|
-
return
|
|
9058
|
+
return execSync7(cmd, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: timeoutMs }).trim();
|
|
8826
9059
|
} catch {
|
|
8827
9060
|
return null;
|
|
8828
9061
|
}
|
|
@@ -8889,13 +9122,13 @@ async function runBoard(opts) {
|
|
|
8889
9122
|
let factoryDir = findFactoryDir();
|
|
8890
9123
|
if (!factoryDir) {
|
|
8891
9124
|
factoryDir = process.cwd();
|
|
8892
|
-
const bmDir =
|
|
8893
|
-
if (!
|
|
8894
|
-
|
|
9125
|
+
const bmDir = join22(factoryDir, ".beastmode");
|
|
9126
|
+
if (!existsSync24(bmDir)) {
|
|
9127
|
+
mkdirSync15(bmDir, { recursive: true });
|
|
8895
9128
|
}
|
|
8896
|
-
const factoryJsonPath2 =
|
|
8897
|
-
if (!
|
|
8898
|
-
|
|
9129
|
+
const factoryJsonPath2 = join22(bmDir, "factory.json");
|
|
9130
|
+
if (!existsSync24(factoryJsonPath2)) {
|
|
9131
|
+
writeFileSync18(
|
|
8899
9132
|
factoryJsonPath2,
|
|
8900
9133
|
JSON.stringify({ factory_name: "BeastMode", created_at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
8901
9134
|
"utf-8"
|
|
@@ -8903,7 +9136,7 @@ async function runBoard(opts) {
|
|
|
8903
9136
|
info("No factory found \u2014 created minimal stub at .beastmode/factory.json");
|
|
8904
9137
|
}
|
|
8905
9138
|
}
|
|
8906
|
-
const factoryJsonPath =
|
|
9139
|
+
const factoryJsonPath = join22(factoryDir, ".beastmode", "factory.json");
|
|
8907
9140
|
const factoryJson = JSON.parse(readFileSync20(factoryJsonPath, "utf-8"));
|
|
8908
9141
|
const factoryName = factoryJson.factory_name || "BeastMode Factory";
|
|
8909
9142
|
header(`BeastMode Board \u2014 ${factoryName}`);
|
|
@@ -8942,7 +9175,7 @@ async function runBoard(opts) {
|
|
|
8942
9175
|
// src/cli/commands/doctor.ts
|
|
8943
9176
|
function tryExec(cmd, timeout = 8e3) {
|
|
8944
9177
|
try {
|
|
8945
|
-
return
|
|
9178
|
+
return execSync8(cmd, { encoding: "utf-8", timeout, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8946
9179
|
} catch {
|
|
8947
9180
|
return null;
|
|
8948
9181
|
}
|
|
@@ -8974,9 +9207,9 @@ async function checkClaudeAuth(key) {
|
|
|
8974
9207
|
fix: "npm install -g @anthropic-ai/claude-code && claude login"
|
|
8975
9208
|
});
|
|
8976
9209
|
}
|
|
8977
|
-
if (claudeInstalled &&
|
|
8978
|
-
const credsPath =
|
|
8979
|
-
if (!
|
|
9210
|
+
if (claudeInstalled && platform2() === "darwin" && !key) {
|
|
9211
|
+
const credsPath = join23(homedir3(), ".claude", ".credentials.json");
|
|
9212
|
+
if (!existsSync25(credsPath)) {
|
|
8980
9213
|
results.push({
|
|
8981
9214
|
label: "Claude creds (Docker)",
|
|
8982
9215
|
status: "warn",
|
|
@@ -9078,8 +9311,8 @@ async function checkProjectGithubToken(env, factoryDir) {
|
|
|
9078
9311
|
}
|
|
9079
9312
|
let ownerRepo = null;
|
|
9080
9313
|
if (factoryDir) {
|
|
9081
|
-
const envPath =
|
|
9082
|
-
if (
|
|
9314
|
+
const envPath = join23(factoryDir, ".env");
|
|
9315
|
+
if (existsSync25(envPath)) {
|
|
9083
9316
|
try {
|
|
9084
9317
|
const content = readFileSync21(envPath, "utf-8");
|
|
9085
9318
|
const dirLine = content.split("\n").find(
|
|
@@ -9087,7 +9320,7 @@ async function checkProjectGithubToken(env, factoryDir) {
|
|
|
9087
9320
|
);
|
|
9088
9321
|
if (dirLine) {
|
|
9089
9322
|
const projectDir = dirLine.split("=").slice(1).join("=").trim();
|
|
9090
|
-
if (projectDir &&
|
|
9323
|
+
if (projectDir && existsSync25(join23(projectDir, ".git"))) {
|
|
9091
9324
|
const remote = tryExec(
|
|
9092
9325
|
`git -C "${projectDir}" remote get-url origin 2>/dev/null`
|
|
9093
9326
|
);
|
|
@@ -9303,8 +9536,8 @@ function checkProjectDirEnv(factoryDir) {
|
|
|
9303
9536
|
detail: "no factory in scope"
|
|
9304
9537
|
};
|
|
9305
9538
|
}
|
|
9306
|
-
const envPath =
|
|
9307
|
-
if (!
|
|
9539
|
+
const envPath = join23(factoryDir, ".env");
|
|
9540
|
+
if (!existsSync25(envPath)) {
|
|
9308
9541
|
return {
|
|
9309
9542
|
label: "PROJECT_DIR (.env)",
|
|
9310
9543
|
status: "fail",
|
|
@@ -9343,7 +9576,7 @@ function checkProjectDirEnv(factoryDir) {
|
|
|
9343
9576
|
fix: "Re-run `beastmode init --project <path>` to populate it"
|
|
9344
9577
|
};
|
|
9345
9578
|
}
|
|
9346
|
-
if (!
|
|
9579
|
+
if (!existsSync25(value)) {
|
|
9347
9580
|
return {
|
|
9348
9581
|
label: "PROJECT_DIR (.env)",
|
|
9349
9582
|
status: "fail",
|
|
@@ -9351,7 +9584,7 @@ function checkProjectDirEnv(factoryDir) {
|
|
|
9351
9584
|
fix: `Clone your project to ${value} or re-run 'beastmode init --project <path>'`
|
|
9352
9585
|
};
|
|
9353
9586
|
}
|
|
9354
|
-
if (!
|
|
9587
|
+
if (!existsSync25(join23(value, ".git"))) {
|
|
9355
9588
|
return {
|
|
9356
9589
|
label: "PROJECT_DIR (.env)",
|
|
9357
9590
|
status: "fail",
|
|
@@ -9382,8 +9615,8 @@ async function checkFactoryContainers(factoryDir) {
|
|
|
9382
9615
|
detail: "no factory in scope"
|
|
9383
9616
|
};
|
|
9384
9617
|
}
|
|
9385
|
-
const composePath =
|
|
9386
|
-
if (!
|
|
9618
|
+
const composePath = join23(factoryDir, "docker-compose.yml");
|
|
9619
|
+
if (!existsSync25(composePath)) {
|
|
9387
9620
|
return {
|
|
9388
9621
|
label: "Factory containers",
|
|
9389
9622
|
status: "warn",
|
|
@@ -9665,9 +9898,9 @@ function checkProjectDirectory(factoryDir) {
|
|
|
9665
9898
|
fix: "Run beastmode init to create a factory"
|
|
9666
9899
|
};
|
|
9667
9900
|
}
|
|
9668
|
-
const bmDir =
|
|
9669
|
-
const projectsDir =
|
|
9670
|
-
if (!
|
|
9901
|
+
const bmDir = join23(factoryDir, ".beastmode");
|
|
9902
|
+
const projectsDir = join23(bmDir, "projects");
|
|
9903
|
+
if (!existsSync25(projectsDir)) {
|
|
9671
9904
|
return {
|
|
9672
9905
|
label: "Project directory",
|
|
9673
9906
|
status: "warn",
|
|
@@ -9675,7 +9908,7 @@ function checkProjectDirectory(factoryDir) {
|
|
|
9675
9908
|
fix: "Run: beastmode add project <path>"
|
|
9676
9909
|
};
|
|
9677
9910
|
}
|
|
9678
|
-
const projectFiles =
|
|
9911
|
+
const projectFiles = existsSync25(projectsDir) ? readdirSync10(projectsDir).filter((f) => f.endsWith(".json")) : [];
|
|
9679
9912
|
if (projectFiles.length === 0) {
|
|
9680
9913
|
return {
|
|
9681
9914
|
label: "Project directory",
|
|
@@ -9688,15 +9921,15 @@ function checkProjectDirectory(factoryDir) {
|
|
|
9688
9921
|
let anyFail = false;
|
|
9689
9922
|
for (const file of projectFiles) {
|
|
9690
9923
|
try {
|
|
9691
|
-
const proj = JSON.parse(readFileSync21(
|
|
9924
|
+
const proj = JSON.parse(readFileSync21(join23(projectsDir, file), "utf-8"));
|
|
9692
9925
|
const projPath = proj.path || "";
|
|
9693
9926
|
const projName = proj.name || file.replace(".json", "");
|
|
9694
|
-
if (!projPath || !
|
|
9927
|
+
if (!projPath || !existsSync25(projPath)) {
|
|
9695
9928
|
results.push(`${projName}: path not found`);
|
|
9696
9929
|
anyFail = true;
|
|
9697
9930
|
continue;
|
|
9698
9931
|
}
|
|
9699
|
-
const isGit =
|
|
9932
|
+
const isGit = existsSync25(join23(projPath, ".git"));
|
|
9700
9933
|
const manifests = [
|
|
9701
9934
|
"package.json",
|
|
9702
9935
|
"Cargo.toml",
|
|
@@ -9707,7 +9940,7 @@ function checkProjectDirectory(factoryDir) {
|
|
|
9707
9940
|
"build.gradle",
|
|
9708
9941
|
"build.gradle.kts"
|
|
9709
9942
|
];
|
|
9710
|
-
const manifest = manifests.find((m) =>
|
|
9943
|
+
const manifest = manifests.find((m) => existsSync25(join23(projPath, m)));
|
|
9711
9944
|
const framework = proj.stack?.detected || manifest?.replace(".json", "") || "unknown";
|
|
9712
9945
|
results.push(
|
|
9713
9946
|
`${projName}: ${projPath} (${framework})${isGit ? "" : " [no .git]"}`
|
|
@@ -9761,8 +9994,8 @@ function checkStack(factoryDir) {
|
|
|
9761
9994
|
detail: "no factory \u2014 run beastmode init"
|
|
9762
9995
|
};
|
|
9763
9996
|
}
|
|
9764
|
-
const projectsDir =
|
|
9765
|
-
if (!
|
|
9997
|
+
const projectsDir = join23(factoryDir, ".beastmode", "projects");
|
|
9998
|
+
if (!existsSync25(projectsDir)) {
|
|
9766
9999
|
return { label: "Stack", status: "warn", detail: "no projects configured" };
|
|
9767
10000
|
}
|
|
9768
10001
|
const projectFiles = readdirSync10(projectsDir).filter((f) => f.endsWith(".json"));
|
|
@@ -9772,7 +10005,7 @@ function checkStack(factoryDir) {
|
|
|
9772
10005
|
const stacks = [];
|
|
9773
10006
|
for (const file of projectFiles) {
|
|
9774
10007
|
try {
|
|
9775
|
-
const proj = JSON.parse(readFileSync21(
|
|
10008
|
+
const proj = JSON.parse(readFileSync21(join23(projectsDir, file), "utf-8"));
|
|
9776
10009
|
const name = proj.name || file.replace(".json", "");
|
|
9777
10010
|
const detected = proj.stack?.detected || "unknown";
|
|
9778
10011
|
const build = proj.stack?.build_command || "";
|
|
@@ -9816,10 +10049,10 @@ function checkBoardPassword(env, factoryDir) {
|
|
|
9816
10049
|
return { label: "Board password", status: "pass", detail: "set" };
|
|
9817
10050
|
}
|
|
9818
10051
|
if (factoryDir) {
|
|
9819
|
-
const dotEnv =
|
|
9820
|
-
const secretsEnv =
|
|
10052
|
+
const dotEnv = join23(factoryDir, ".env");
|
|
10053
|
+
const secretsEnv = join23(factoryDir, ".beastmode", "secrets.env.local");
|
|
9821
10054
|
for (const filePath of [dotEnv, secretsEnv]) {
|
|
9822
|
-
if (
|
|
10055
|
+
if (existsSync25(filePath)) {
|
|
9823
10056
|
try {
|
|
9824
10057
|
const content = readFileSync21(filePath, "utf-8");
|
|
9825
10058
|
const lines = content.split("\n");
|
|
@@ -9843,12 +10076,12 @@ function checkBoardPassword(env, factoryDir) {
|
|
|
9843
10076
|
};
|
|
9844
10077
|
}
|
|
9845
10078
|
function doctorAction(factoryDir, env) {
|
|
9846
|
-
const bmDir =
|
|
9847
|
-
const factoryDirExists =
|
|
10079
|
+
const bmDir = join23(factoryDir, ".beastmode");
|
|
10080
|
+
const factoryDirExists = existsSync25(bmDir);
|
|
9848
10081
|
let factoryIdentity = null;
|
|
9849
10082
|
if (factoryDirExists) {
|
|
9850
10083
|
try {
|
|
9851
|
-
const raw = JSON.parse(readFileSync21(
|
|
10084
|
+
const raw = JSON.parse(readFileSync21(join23(bmDir, "factory.json"), "utf-8"));
|
|
9852
10085
|
factoryIdentity = FactoryIdentitySchema.parse(raw);
|
|
9853
10086
|
} catch {
|
|
9854
10087
|
}
|
|
@@ -9856,8 +10089,8 @@ function doctorAction(factoryDir, env) {
|
|
|
9856
10089
|
let config = null;
|
|
9857
10090
|
let configParseError = null;
|
|
9858
10091
|
if (factoryDirExists) {
|
|
9859
|
-
const configPath =
|
|
9860
|
-
if (
|
|
10092
|
+
const configPath = join23(bmDir, "config.json");
|
|
10093
|
+
if (existsSync25(configPath)) {
|
|
9861
10094
|
try {
|
|
9862
10095
|
const raw = JSON.parse(readFileSync21(configPath, "utf-8"));
|
|
9863
10096
|
const result = FactoryConfigSchema.safeParse(raw);
|
|
@@ -9875,17 +10108,17 @@ function doctorAction(factoryDir, env) {
|
|
|
9875
10108
|
}
|
|
9876
10109
|
const projectPaths = [];
|
|
9877
10110
|
if (factoryDirExists) {
|
|
9878
|
-
const projectsDir =
|
|
9879
|
-
if (
|
|
10111
|
+
const projectsDir = join23(bmDir, "projects");
|
|
10112
|
+
if (existsSync25(projectsDir)) {
|
|
9880
10113
|
for (const file of readdirSync10(projectsDir)) {
|
|
9881
10114
|
if (file.endsWith(".json")) {
|
|
9882
10115
|
try {
|
|
9883
|
-
const proj = JSON.parse(readFileSync21(
|
|
10116
|
+
const proj = JSON.parse(readFileSync21(join23(projectsDir, file), "utf-8"));
|
|
9884
10117
|
if (proj.path) {
|
|
9885
10118
|
projectPaths.push({
|
|
9886
10119
|
name: proj.name || file.replace(".json", ""),
|
|
9887
10120
|
path: proj.path,
|
|
9888
|
-
exists:
|
|
10121
|
+
exists: existsSync25(proj.path)
|
|
9889
10122
|
});
|
|
9890
10123
|
}
|
|
9891
10124
|
} catch {
|
|
@@ -9896,11 +10129,11 @@ function doctorAction(factoryDir, env) {
|
|
|
9896
10129
|
}
|
|
9897
10130
|
const installedPlugins = [];
|
|
9898
10131
|
if (factoryDirExists) {
|
|
9899
|
-
const pluginsDir =
|
|
9900
|
-
if (
|
|
10132
|
+
const pluginsDir = join23(bmDir, "plugins");
|
|
10133
|
+
if (existsSync25(pluginsDir)) {
|
|
9901
10134
|
for (const pluginName of readdirSync10(pluginsDir)) {
|
|
9902
|
-
const manifestPath =
|
|
9903
|
-
if (
|
|
10135
|
+
const manifestPath = join23(pluginsDir, pluginName, "manifest.json");
|
|
10136
|
+
if (existsSync25(manifestPath)) {
|
|
9904
10137
|
try {
|
|
9905
10138
|
const manifest = JSON.parse(readFileSync21(manifestPath, "utf-8"));
|
|
9906
10139
|
installedPlugins.push({
|
|
@@ -9942,12 +10175,12 @@ function printCheck(check) {
|
|
|
9942
10175
|
}
|
|
9943
10176
|
}
|
|
9944
10177
|
}
|
|
9945
|
-
var doctorCommand = new
|
|
10178
|
+
var doctorCommand = new Command12("doctor").description("Health check \u2014 validates the entire BeastMode setup").action(async () => {
|
|
9946
10179
|
header("BeastMode Doctor");
|
|
9947
10180
|
console.log();
|
|
9948
10181
|
const env = process.env;
|
|
9949
10182
|
const factoryDir = findFactoryDir() ?? resolve15(".");
|
|
9950
|
-
const hasFactory =
|
|
10183
|
+
const hasFactory = existsSync25(join23(factoryDir, ".beastmode"));
|
|
9951
10184
|
const checks = [];
|
|
9952
10185
|
checks.push(...await checkClaudeAuth(env.ANTHROPIC_API_KEY));
|
|
9953
10186
|
checks.push(checkGithubToken(env));
|
|
@@ -10018,13 +10251,14 @@ var doctorCommand = new Command11("doctor").description("Health check \u2014 val
|
|
|
10018
10251
|
init_upgrader();
|
|
10019
10252
|
init_schemas();
|
|
10020
10253
|
init_version();
|
|
10021
|
-
|
|
10022
|
-
import {
|
|
10023
|
-
import {
|
|
10254
|
+
init_display();
|
|
10255
|
+
import { Command as Command13 } from "commander";
|
|
10256
|
+
import { existsSync as existsSync27, readFileSync as readFileSync23, writeFileSync as writeFileSync20 } from "fs";
|
|
10257
|
+
import { join as join25, resolve as resolve16 } from "path";
|
|
10024
10258
|
|
|
10025
10259
|
// src/cli/utils/regenerate.ts
|
|
10026
|
-
import { existsSync as
|
|
10027
|
-
import { join as
|
|
10260
|
+
import { existsSync as existsSync26, readFileSync as readFileSync22, writeFileSync as writeFileSync19, copyFileSync } from "fs";
|
|
10261
|
+
import { join as join24 } from "path";
|
|
10028
10262
|
var RECOGNIZED_KEYS = /* @__PURE__ */ new Set([
|
|
10029
10263
|
"PROJECT_DIR",
|
|
10030
10264
|
"PROJECT_GITHUB_TOKEN",
|
|
@@ -10138,14 +10372,14 @@ function buildNewEnv(values) {
|
|
|
10138
10372
|
return lines.join("\n");
|
|
10139
10373
|
}
|
|
10140
10374
|
function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
|
|
10141
|
-
const envPath =
|
|
10142
|
-
const composePath =
|
|
10143
|
-
if (!
|
|
10375
|
+
const envPath = join24(factoryDir, ".env");
|
|
10376
|
+
const composePath = join24(factoryDir, "docker-compose.yml");
|
|
10377
|
+
if (!existsSync26(envPath)) {
|
|
10144
10378
|
throw new Error(
|
|
10145
10379
|
`.env not found at ${envPath}. This does not look like a beastmode factory \u2014 run 'beastmode init' first.`
|
|
10146
10380
|
);
|
|
10147
10381
|
}
|
|
10148
|
-
if (!
|
|
10382
|
+
if (!existsSync26(composePath)) {
|
|
10149
10383
|
throw new Error(
|
|
10150
10384
|
`docker-compose.yml not found at ${composePath}. This does not look like a beastmode factory \u2014 run 'beastmode init' first.`
|
|
10151
10385
|
);
|
|
@@ -10186,12 +10420,12 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
|
|
|
10186
10420
|
if (envChanged) {
|
|
10187
10421
|
envBackupPath = `${envPath}.backup.${timestamp}`;
|
|
10188
10422
|
copyFileSync(envPath, envBackupPath);
|
|
10189
|
-
|
|
10423
|
+
writeFileSync19(envPath, newEnv, "utf-8");
|
|
10190
10424
|
}
|
|
10191
10425
|
if (composeChanged) {
|
|
10192
10426
|
composeBackupPath = `${composePath}.backup.${timestamp}`;
|
|
10193
10427
|
copyFileSync(composePath, composeBackupPath);
|
|
10194
|
-
|
|
10428
|
+
writeFileSync19(composePath, newCompose, "utf-8");
|
|
10195
10429
|
}
|
|
10196
10430
|
return {
|
|
10197
10431
|
envChanged,
|
|
@@ -10204,15 +10438,15 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
|
|
|
10204
10438
|
|
|
10205
10439
|
// src/cli/commands/upgrade.ts
|
|
10206
10440
|
function readIdentity(factoryDir) {
|
|
10207
|
-
const path =
|
|
10208
|
-
if (!
|
|
10441
|
+
const path = join25(factoryDir, ".beastmode", "factory.json");
|
|
10442
|
+
if (!existsSync27(path)) {
|
|
10209
10443
|
throw new Error("No factory.json found. Run beastmode init first.");
|
|
10210
10444
|
}
|
|
10211
10445
|
return FactoryIdentitySchema.parse(JSON.parse(readFileSync23(path, "utf-8")));
|
|
10212
10446
|
}
|
|
10213
10447
|
function readConfig3(factoryDir) {
|
|
10214
|
-
const path =
|
|
10215
|
-
if (!
|
|
10448
|
+
const path = join25(factoryDir, ".beastmode", "config.json");
|
|
10449
|
+
if (!existsSync27(path)) {
|
|
10216
10450
|
return {};
|
|
10217
10451
|
}
|
|
10218
10452
|
return JSON.parse(readFileSync23(path, "utf-8"));
|
|
@@ -10227,18 +10461,18 @@ function upgradeAction(factoryDir, migrateOnly = false) {
|
|
|
10227
10461
|
const targetVersion = migrateOnly ? identity.engine_version : ENGINE_VERSION;
|
|
10228
10462
|
const result = performUpgrade(identity, config, targetVersion, SCHEMA_VERSION);
|
|
10229
10463
|
if (result.changes.length > 0) {
|
|
10230
|
-
|
|
10231
|
-
|
|
10464
|
+
writeFileSync20(
|
|
10465
|
+
join25(factoryDir, ".beastmode", "factory.json"),
|
|
10232
10466
|
JSON.stringify(result.updatedIdentity, null, 2) + "\n"
|
|
10233
10467
|
);
|
|
10234
|
-
|
|
10235
|
-
|
|
10468
|
+
writeFileSync20(
|
|
10469
|
+
join25(factoryDir, ".beastmode", "config.json"),
|
|
10236
10470
|
JSON.stringify(result.updatedConfig, null, 2) + "\n"
|
|
10237
10471
|
);
|
|
10238
10472
|
}
|
|
10239
10473
|
return result;
|
|
10240
10474
|
}
|
|
10241
|
-
var upgradeCommand = new
|
|
10475
|
+
var upgradeCommand = new Command13("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(
|
|
10242
10476
|
"--files",
|
|
10243
10477
|
"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."
|
|
10244
10478
|
).action((opts) => {
|
|
@@ -10316,11 +10550,12 @@ var upgradeCommand = new Command12("upgrade").description("Upgrade engine versio
|
|
|
10316
10550
|
});
|
|
10317
10551
|
|
|
10318
10552
|
// src/cli/commands/migrate.ts
|
|
10319
|
-
|
|
10320
|
-
import { resolve as resolve17, join as join25 } from "path";
|
|
10321
|
-
import { existsSync as existsSync27, readFileSync as readFileSync24, mkdirSync as mkdirSync15, writeFileSync as writeFileSync20 } from "fs";
|
|
10553
|
+
init_display();
|
|
10322
10554
|
init_migrator();
|
|
10323
|
-
|
|
10555
|
+
import { Command as Command14 } from "commander";
|
|
10556
|
+
import { resolve as resolve17, join as join26 } from "path";
|
|
10557
|
+
import { existsSync as existsSync28, readFileSync as readFileSync24, mkdirSync as mkdirSync16, writeFileSync as writeFileSync21 } from "fs";
|
|
10558
|
+
var migrateCommand = new Command14("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) => {
|
|
10324
10559
|
try {
|
|
10325
10560
|
await runMigrate(opts);
|
|
10326
10561
|
} catch (err) {
|
|
@@ -10331,7 +10566,7 @@ var migrateCommand = new Command13("migrate").description("Migrate a daemon conf
|
|
|
10331
10566
|
async function runMigrate(opts) {
|
|
10332
10567
|
const cwd = process.cwd();
|
|
10333
10568
|
const configPath = opts.config ? resolve17(opts.config) : resolve17(cwd, "config", "beastmode.daemon.json");
|
|
10334
|
-
if (!
|
|
10569
|
+
if (!existsSync28(configPath)) {
|
|
10335
10570
|
throw new Error(
|
|
10336
10571
|
`Daemon config not found at ${configPath}
|
|
10337
10572
|
Use --config <path> to specify a different location.`
|
|
@@ -10341,21 +10576,21 @@ async function runMigrate(opts) {
|
|
|
10341
10576
|
info(`Reading daemon config from: ${configPath}`);
|
|
10342
10577
|
const configContent = readFileSync24(configPath, "utf-8");
|
|
10343
10578
|
const daemonConfig = parseDaemonConfig(configContent);
|
|
10344
|
-
const runsDir =
|
|
10579
|
+
const runsDir = join26(cwd, "runs");
|
|
10345
10580
|
let runDirs = [];
|
|
10346
10581
|
const checkpoints = /* @__PURE__ */ new Map();
|
|
10347
|
-
if (
|
|
10582
|
+
if (existsSync28(runsDir)) {
|
|
10348
10583
|
const { readdirSync: readdirSync12 } = await import("fs");
|
|
10349
10584
|
runDirs = readdirSync12(runsDir).filter((d) => {
|
|
10350
10585
|
try {
|
|
10351
|
-
return readdirSync12(
|
|
10586
|
+
return readdirSync12(join26(runsDir, d)).length > 0;
|
|
10352
10587
|
} catch {
|
|
10353
10588
|
return false;
|
|
10354
10589
|
}
|
|
10355
10590
|
});
|
|
10356
10591
|
for (const dir of runDirs) {
|
|
10357
|
-
const cpPath =
|
|
10358
|
-
if (
|
|
10592
|
+
const cpPath = join26(runsDir, dir, "checkpoint.json");
|
|
10593
|
+
if (existsSync28(cpPath)) {
|
|
10359
10594
|
try {
|
|
10360
10595
|
const cp = JSON.parse(readFileSync24(cpPath, "utf-8"));
|
|
10361
10596
|
checkpoints.set(dir, cp);
|
|
@@ -10414,28 +10649,28 @@ async function runMigrate(opts) {
|
|
|
10414
10649
|
warn("Dry run \u2014 no files written.");
|
|
10415
10650
|
return;
|
|
10416
10651
|
}
|
|
10417
|
-
const bmDir =
|
|
10418
|
-
if (
|
|
10652
|
+
const bmDir = join26(cwd, ".beastmode");
|
|
10653
|
+
if (existsSync28(bmDir)) {
|
|
10419
10654
|
throw new Error(
|
|
10420
10655
|
"A .beastmode/ directory already exists. Remove it first to re-migrate."
|
|
10421
10656
|
);
|
|
10422
10657
|
}
|
|
10423
10658
|
for (const file of files) {
|
|
10424
|
-
const fullPath =
|
|
10659
|
+
const fullPath = join26(cwd, file.path);
|
|
10425
10660
|
const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
|
|
10426
|
-
|
|
10427
|
-
|
|
10661
|
+
mkdirSync16(dir, { recursive: true });
|
|
10662
|
+
writeFileSync21(fullPath, file.content, "utf-8");
|
|
10428
10663
|
}
|
|
10429
|
-
const runsSymlinkTarget =
|
|
10430
|
-
const bmRunsPath =
|
|
10431
|
-
if (
|
|
10664
|
+
const runsSymlinkTarget = join26(cwd, "runs");
|
|
10665
|
+
const bmRunsPath = join26(cwd, "runs");
|
|
10666
|
+
if (existsSync28(runsSymlinkTarget)) {
|
|
10432
10667
|
info("Existing runs/ directory preserved in-place.");
|
|
10433
10668
|
}
|
|
10434
|
-
const boardPath =
|
|
10435
|
-
if (!
|
|
10436
|
-
|
|
10669
|
+
const boardPath = join26(bmDir, "board.json");
|
|
10670
|
+
if (!existsSync28(boardPath)) {
|
|
10671
|
+
writeFileSync21(boardPath, JSON.stringify({ items: [] }, null, 2), "utf-8");
|
|
10437
10672
|
}
|
|
10438
|
-
|
|
10673
|
+
mkdirSync16(join26(bmDir, ".cache"), { recursive: true });
|
|
10439
10674
|
console.log();
|
|
10440
10675
|
success("Migration complete!");
|
|
10441
10676
|
info(`Factory created at: ${bmDir}`);
|
|
@@ -10452,13 +10687,14 @@ async function runMigrate(opts) {
|
|
|
10452
10687
|
}
|
|
10453
10688
|
|
|
10454
10689
|
// src/cli/commands/run.ts
|
|
10455
|
-
|
|
10456
|
-
import {
|
|
10457
|
-
import {
|
|
10690
|
+
init_display();
|
|
10691
|
+
import { Command as Command15 } from "commander";
|
|
10692
|
+
import { join as join27 } from "path";
|
|
10693
|
+
import { existsSync as existsSync29, readFileSync as readFileSync25, writeFileSync as writeFileSync22, mkdirSync as mkdirSync17 } from "fs";
|
|
10458
10694
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
10459
10695
|
init_bridge();
|
|
10460
10696
|
init_schemas();
|
|
10461
|
-
var runCommand = new
|
|
10697
|
+
var runCommand = new Command15("run").description("Run a single pipeline task").argument("[project]", "Project name (defaults to first project)").option("--task <description>", "Task description").action(async (project, opts) => {
|
|
10462
10698
|
try {
|
|
10463
10699
|
await runPipeline(project, opts);
|
|
10464
10700
|
} catch (err) {
|
|
@@ -10476,18 +10712,18 @@ async function runPipeline(projectName, opts) {
|
|
|
10476
10712
|
"No BeastMode factory found. Run 'beastmode init' first."
|
|
10477
10713
|
);
|
|
10478
10714
|
}
|
|
10479
|
-
const bmDir =
|
|
10715
|
+
const bmDir = join27(factoryDir, ".beastmode");
|
|
10480
10716
|
header("BeastMode Run");
|
|
10481
|
-
const configPath =
|
|
10482
|
-
if (!
|
|
10717
|
+
const configPath = join27(bmDir, "config.json");
|
|
10718
|
+
if (!existsSync29(configPath)) {
|
|
10483
10719
|
throw new Error("Factory config not found. Run 'beastmode init' first.");
|
|
10484
10720
|
}
|
|
10485
10721
|
const factoryConfig = FactoryConfigSchema.parse(
|
|
10486
10722
|
JSON.parse(readFileSync25(configPath, "utf-8"))
|
|
10487
10723
|
);
|
|
10488
10724
|
let projectConfig = null;
|
|
10489
|
-
const projectsDir =
|
|
10490
|
-
if (
|
|
10725
|
+
const projectsDir = join27(bmDir, "projects");
|
|
10726
|
+
if (existsSync29(projectsDir)) {
|
|
10491
10727
|
const { readdirSync: readdirSync12 } = await import("fs");
|
|
10492
10728
|
const projectFiles = readdirSync12(projectsDir).filter(
|
|
10493
10729
|
(f) => f.endsWith(".json")
|
|
@@ -10500,18 +10736,18 @@ async function runPipeline(projectName, opts) {
|
|
|
10500
10736
|
throw new Error(`Project not found: ${projectName}`);
|
|
10501
10737
|
}
|
|
10502
10738
|
projectConfig = ProjectConfigSchema.parse(
|
|
10503
|
-
JSON.parse(readFileSync25(
|
|
10739
|
+
JSON.parse(readFileSync25(join27(projectsDir, file), "utf-8"))
|
|
10504
10740
|
);
|
|
10505
10741
|
} else if (projectFiles.length > 0) {
|
|
10506
10742
|
projectConfig = ProjectConfigSchema.parse(
|
|
10507
|
-
JSON.parse(readFileSync25(
|
|
10743
|
+
JSON.parse(readFileSync25(join27(projectsDir, projectFiles[0]), "utf-8"))
|
|
10508
10744
|
);
|
|
10509
10745
|
info(`Using project: ${projectConfig.name}`);
|
|
10510
10746
|
}
|
|
10511
10747
|
}
|
|
10512
|
-
const boardPath =
|
|
10748
|
+
const boardPath = join27(bmDir, "board.json");
|
|
10513
10749
|
let boardItems = [];
|
|
10514
|
-
if (
|
|
10750
|
+
if (existsSync29(boardPath)) {
|
|
10515
10751
|
try {
|
|
10516
10752
|
const raw = JSON.parse(readFileSync25(boardPath, "utf-8"));
|
|
10517
10753
|
boardItems = Array.isArray(raw.items) ? raw.items : [];
|
|
@@ -10530,13 +10766,13 @@ async function runPipeline(projectName, opts) {
|
|
|
10530
10766
|
updated_at: now
|
|
10531
10767
|
};
|
|
10532
10768
|
boardItems.push(task);
|
|
10533
|
-
|
|
10769
|
+
writeFileSync22(boardPath, JSON.stringify({ items: boardItems }, null, 2), "utf-8");
|
|
10534
10770
|
info(`Created task: ${task.title} (${taskId})`);
|
|
10535
|
-
const cacheDir =
|
|
10536
|
-
|
|
10537
|
-
const daemonConfigPath =
|
|
10771
|
+
const cacheDir = join27(bmDir, ".cache");
|
|
10772
|
+
mkdirSync17(cacheDir, { recursive: true });
|
|
10773
|
+
const daemonConfigPath = join27(cacheDir, "daemon.json");
|
|
10538
10774
|
const daemonConfig = generateDaemonConfig(factoryConfig, projectConfig, factoryDir);
|
|
10539
|
-
|
|
10775
|
+
writeFileSync22(daemonConfigPath, JSON.stringify(daemonConfig, null, 2), "utf-8");
|
|
10540
10776
|
info(`Generated daemon config at: ${daemonConfigPath}`);
|
|
10541
10777
|
const { execSync: execSync10 } = await import("child_process");
|
|
10542
10778
|
let pythonAvailable = false;
|
|
@@ -10560,7 +10796,7 @@ async function runPipeline(projectName, opts) {
|
|
|
10560
10796
|
const daemonPaths = findPythonDaemonPaths(envPath, factoryDir);
|
|
10561
10797
|
let daemonFound = false;
|
|
10562
10798
|
for (const p of daemonPaths) {
|
|
10563
|
-
if (
|
|
10799
|
+
if (existsSync29(p)) {
|
|
10564
10800
|
daemonFound = true;
|
|
10565
10801
|
break;
|
|
10566
10802
|
}
|
|
@@ -10628,12 +10864,13 @@ async function runPipeline(projectName, opts) {
|
|
|
10628
10864
|
}
|
|
10629
10865
|
|
|
10630
10866
|
// src/cli/commands/daemon-cmd.ts
|
|
10631
|
-
|
|
10632
|
-
import {
|
|
10633
|
-
import {
|
|
10867
|
+
init_display();
|
|
10868
|
+
import { Command as Command16 } from "commander";
|
|
10869
|
+
import { join as join28 } from "path";
|
|
10870
|
+
import { existsSync as existsSync30, readFileSync as readFileSync26, writeFileSync as writeFileSync23, mkdirSync as mkdirSync18 } from "fs";
|
|
10634
10871
|
init_bridge();
|
|
10635
10872
|
init_schemas();
|
|
10636
|
-
var daemonCommand = new
|
|
10873
|
+
var daemonCommand = new Command16("daemon").description("Start the BeastMode daemon via bridge").option("--dry-run", "Generate config but don't start daemon").option(
|
|
10637
10874
|
"--log-level <level>",
|
|
10638
10875
|
"Log level (DEBUG, INFO, WARNING, ERROR)",
|
|
10639
10876
|
"INFO"
|
|
@@ -10652,38 +10889,38 @@ async function runDaemon(opts) {
|
|
|
10652
10889
|
"No BeastMode factory found. Run 'beastmode init' first."
|
|
10653
10890
|
);
|
|
10654
10891
|
}
|
|
10655
|
-
const bmDir =
|
|
10892
|
+
const bmDir = join28(factoryDir, ".beastmode");
|
|
10656
10893
|
header("BeastMode Daemon");
|
|
10657
|
-
const configPath =
|
|
10658
|
-
if (!
|
|
10894
|
+
const configPath = join28(bmDir, "config.json");
|
|
10895
|
+
if (!existsSync30(configPath)) {
|
|
10659
10896
|
throw new Error("Factory config not found. Run 'beastmode init' first.");
|
|
10660
10897
|
}
|
|
10661
10898
|
const factoryConfig = FactoryConfigSchema.parse(
|
|
10662
10899
|
JSON.parse(readFileSync26(configPath, "utf-8"))
|
|
10663
10900
|
);
|
|
10664
10901
|
let projectConfig = null;
|
|
10665
|
-
const projectsDir =
|
|
10666
|
-
if (
|
|
10902
|
+
const projectsDir = join28(bmDir, "projects");
|
|
10903
|
+
if (existsSync30(projectsDir)) {
|
|
10667
10904
|
const { readdirSync: readdirSync12 } = await import("fs");
|
|
10668
10905
|
const projectFiles = readdirSync12(projectsDir).filter(
|
|
10669
10906
|
(f) => f.endsWith(".json")
|
|
10670
10907
|
);
|
|
10671
10908
|
if (projectFiles.length > 0) {
|
|
10672
10909
|
projectConfig = ProjectConfigSchema.parse(
|
|
10673
|
-
JSON.parse(readFileSync26(
|
|
10910
|
+
JSON.parse(readFileSync26(join28(projectsDir, projectFiles[0]), "utf-8"))
|
|
10674
10911
|
);
|
|
10675
10912
|
info(`Using project: ${projectConfig.name}`);
|
|
10676
10913
|
}
|
|
10677
10914
|
}
|
|
10678
|
-
const cacheDir =
|
|
10679
|
-
|
|
10680
|
-
const daemonConfigPath =
|
|
10915
|
+
const cacheDir = join28(bmDir, ".cache");
|
|
10916
|
+
mkdirSync18(cacheDir, { recursive: true });
|
|
10917
|
+
const daemonConfigPath = join28(cacheDir, "daemon.json");
|
|
10681
10918
|
const daemonConfig = generateDaemonConfig(
|
|
10682
10919
|
factoryConfig,
|
|
10683
10920
|
projectConfig,
|
|
10684
10921
|
factoryDir
|
|
10685
10922
|
);
|
|
10686
|
-
|
|
10923
|
+
writeFileSync23(
|
|
10687
10924
|
daemonConfigPath,
|
|
10688
10925
|
JSON.stringify(daemonConfig, null, 2),
|
|
10689
10926
|
"utf-8"
|
|
@@ -10722,7 +10959,7 @@ async function runDaemon(opts) {
|
|
|
10722
10959
|
});
|
|
10723
10960
|
info(`Starting daemon: ${pythonCmd} ${cmd.args.join(" ")}`);
|
|
10724
10961
|
console.log();
|
|
10725
|
-
const pidFile =
|
|
10962
|
+
const pidFile = join28(bmDir, "daemon.pid");
|
|
10726
10963
|
const { spawn } = await import("child_process");
|
|
10727
10964
|
const child = spawn(pythonCmd, cmd.args, {
|
|
10728
10965
|
stdio: "inherit",
|
|
@@ -10733,7 +10970,7 @@ async function runDaemon(opts) {
|
|
|
10733
10970
|
}
|
|
10734
10971
|
});
|
|
10735
10972
|
if (child.pid) {
|
|
10736
|
-
|
|
10973
|
+
writeFileSync23(pidFile, String(child.pid), "utf-8");
|
|
10737
10974
|
}
|
|
10738
10975
|
const signalHandler = (signal) => {
|
|
10739
10976
|
info(`Forwarding ${signal} to daemon...`);
|
|
@@ -10757,8 +10994,8 @@ async function runDaemon(opts) {
|
|
|
10757
10994
|
}
|
|
10758
10995
|
|
|
10759
10996
|
// src/cli/commands/mcp-cmd.ts
|
|
10760
|
-
import { Command as
|
|
10761
|
-
var mcpCommand = new
|
|
10997
|
+
import { Command as Command17 } from "commander";
|
|
10998
|
+
var mcpCommand = new Command17("mcp").description("Start the BeastMode MCP server (stdio transport)").action(async () => {
|
|
10762
10999
|
try {
|
|
10763
11000
|
const { startMcpServer: startMcpServer2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
|
|
10764
11001
|
await startMcpServer2();
|
|
@@ -10770,14 +11007,15 @@ var mcpCommand = new Command16("mcp").description("Start the BeastMode MCP serve
|
|
|
10770
11007
|
});
|
|
10771
11008
|
|
|
10772
11009
|
// src/cli/commands/deploy.ts
|
|
10773
|
-
|
|
10774
|
-
import {
|
|
10775
|
-
import {
|
|
10776
|
-
import {
|
|
11010
|
+
init_display();
|
|
11011
|
+
import { Command as Command18 } from "commander";
|
|
11012
|
+
import { resolve as resolve19, join as join30 } from "path";
|
|
11013
|
+
import { existsSync as existsSync32, writeFileSync as writeFileSync25, readFileSync as readFileSync28 } from "fs";
|
|
11014
|
+
import { execSync as execSync9 } from "child_process";
|
|
10777
11015
|
import { randomBytes } from "crypto";
|
|
10778
11016
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
10779
11017
|
import { dirname as dirname7 } from "path";
|
|
10780
|
-
var deployCommand = new
|
|
11018
|
+
var deployCommand = new Command18("deploy").description("Deploy BeastMode services as systemd services").option("--port <number>", "Port to serve the board UI on", "8420").option("--host <host>", "Host to bind the board UI to", "0.0.0.0").option("--dry-run", "Show what would be done without executing").option("--no-start", "Install services but don't start them").option("--stop", "Stop all 3 BeastMode services").option("--status", "Show status of all 3 BeastMode services").option("--cloud <provider>", "Deploy to cloud (aws)").option("--upgrade", "Update an existing cloud deployment").option("--destroy", "Destroy the cloud deployment (delete CloudFormation stack)").action(async (opts) => {
|
|
10781
11019
|
try {
|
|
10782
11020
|
await runDeploy(opts);
|
|
10783
11021
|
} catch (err) {
|
|
@@ -10795,7 +11033,7 @@ async function runDeploy(opts) {
|
|
|
10795
11033
|
header("BeastMode Service Status");
|
|
10796
11034
|
for (const name of SERVICE_NAMES) {
|
|
10797
11035
|
try {
|
|
10798
|
-
const out =
|
|
11036
|
+
const out = execSync9(`systemctl status ${name} --no-pager 2>&1 || true`, {
|
|
10799
11037
|
encoding: "utf-8",
|
|
10800
11038
|
shell: "/bin/bash"
|
|
10801
11039
|
});
|
|
@@ -10811,7 +11049,7 @@ async function runDeploy(opts) {
|
|
|
10811
11049
|
for (const name of SERVICE_NAMES) {
|
|
10812
11050
|
info(`Stopping ${name}...`);
|
|
10813
11051
|
try {
|
|
10814
|
-
|
|
11052
|
+
execSync9(`sudo systemctl stop ${name}`, { stdio: "inherit" });
|
|
10815
11053
|
success(`${name} stopped`);
|
|
10816
11054
|
} catch {
|
|
10817
11055
|
warn(`Could not stop ${name} (may not be running)`);
|
|
@@ -10827,8 +11065,8 @@ async function runDeploy(opts) {
|
|
|
10827
11065
|
process.exit(1);
|
|
10828
11066
|
}
|
|
10829
11067
|
const factoryDir = resolve19(".");
|
|
10830
|
-
const bmDir =
|
|
10831
|
-
if (!
|
|
11068
|
+
const bmDir = join30(factoryDir, ".beastmode");
|
|
11069
|
+
if (!existsSync32(bmDir)) {
|
|
10832
11070
|
error(
|
|
10833
11071
|
"No .beastmode directory found. Run 'beastmode init' or 'beastmode migrate' first."
|
|
10834
11072
|
);
|
|
@@ -10837,12 +11075,12 @@ async function runDeploy(opts) {
|
|
|
10837
11075
|
let nodePath;
|
|
10838
11076
|
let cliPath;
|
|
10839
11077
|
try {
|
|
10840
|
-
nodePath =
|
|
11078
|
+
nodePath = execSync9("which node", { encoding: "utf-8" }).trim();
|
|
10841
11079
|
} catch {
|
|
10842
11080
|
nodePath = process.execPath;
|
|
10843
11081
|
}
|
|
10844
11082
|
try {
|
|
10845
|
-
cliPath =
|
|
11083
|
+
cliPath = execSync9(
|
|
10846
11084
|
"readlink -f $(which beastmode) 2>/dev/null || which beastmode",
|
|
10847
11085
|
{ encoding: "utf-8", shell: "/bin/bash" }
|
|
10848
11086
|
).trim();
|
|
@@ -10852,33 +11090,33 @@ async function runDeploy(opts) {
|
|
|
10852
11090
|
"../../index.js"
|
|
10853
11091
|
);
|
|
10854
11092
|
}
|
|
10855
|
-
const boardVenvPython =
|
|
10856
|
-
const daemonVenvPython =
|
|
10857
|
-
const boardPython =
|
|
10858
|
-
const daemonPython =
|
|
10859
|
-
const user =
|
|
11093
|
+
const boardVenvPython = join30(factoryDir, "board", ".venv", "bin", "python");
|
|
11094
|
+
const daemonVenvPython = join30(factoryDir, "daemon", ".venv", "bin", "python");
|
|
11095
|
+
const boardPython = existsSync32(boardVenvPython) ? boardVenvPython : "python3";
|
|
11096
|
+
const daemonPython = existsSync32(daemonVenvPython) ? daemonVenvPython : "python3";
|
|
11097
|
+
const user = execSync9("whoami", { encoding: "utf-8" }).trim();
|
|
10860
11098
|
const home = process.env.HOME || `/home/${user}`;
|
|
10861
11099
|
const port = opts.port;
|
|
10862
11100
|
const host = opts.host;
|
|
10863
|
-
const dotEnv =
|
|
10864
|
-
const secretsEnv =
|
|
10865
|
-
const envContent =
|
|
10866
|
-
const secretsContent =
|
|
11101
|
+
const dotEnv = join30(factoryDir, ".env");
|
|
11102
|
+
const secretsEnv = join30(bmDir, "secrets.env.local");
|
|
11103
|
+
const envContent = existsSync32(dotEnv) ? readFileSync28(dotEnv, "utf-8") : "";
|
|
11104
|
+
const secretsContent = existsSync32(secretsEnv) ? readFileSync28(secretsEnv, "utf-8") : "";
|
|
10867
11105
|
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;
|
|
10868
11106
|
if (!hasPassword && opts.host === "0.0.0.0") {
|
|
10869
11107
|
const generated = randomBytes(18).toString("base64url");
|
|
10870
|
-
const target =
|
|
11108
|
+
const target = existsSync32(secretsEnv) ? secretsEnv : dotEnv;
|
|
10871
11109
|
const append = `
|
|
10872
11110
|
# Auto-generated board UI password (deploy)
|
|
10873
11111
|
BEASTMODE_UI_PASSWORD=${generated}
|
|
10874
11112
|
`;
|
|
10875
|
-
|
|
11113
|
+
writeFileSync25(target, (existsSync32(target) ? readFileSync28(target, "utf-8") : "") + append, "utf-8");
|
|
10876
11114
|
info(`Board UI password auto-generated and saved to ${target}`);
|
|
10877
11115
|
success(`Password: ${generated}`);
|
|
10878
11116
|
info("Save this password \u2014 you'll need it to access the board UI.");
|
|
10879
11117
|
}
|
|
10880
11118
|
const envFileLines = [];
|
|
10881
|
-
if (
|
|
11119
|
+
if (existsSync32(secretsEnv)) {
|
|
10882
11120
|
envFileLines.push(`EnvironmentFile=${secretsEnv}`);
|
|
10883
11121
|
} else {
|
|
10884
11122
|
envFileLines.push(`# No secrets.env.local found at time of deploy`);
|
|
@@ -10961,8 +11199,8 @@ BEASTMODE_UI_PASSWORD=${generated}
|
|
|
10961
11199
|
info(`Writing service file to ${svc.path}...`);
|
|
10962
11200
|
try {
|
|
10963
11201
|
const tmpPath = `/tmp/${svc.name}.service`;
|
|
10964
|
-
|
|
10965
|
-
|
|
11202
|
+
writeFileSync25(tmpPath, svc.content, "utf-8");
|
|
11203
|
+
execSync9(`sudo cp ${tmpPath} ${svc.path}`, { stdio: "inherit" });
|
|
10966
11204
|
success(`${svc.name} service file installed`);
|
|
10967
11205
|
} catch {
|
|
10968
11206
|
error(
|
|
@@ -10973,22 +11211,22 @@ ${svc.content}`
|
|
|
10973
11211
|
}
|
|
10974
11212
|
}
|
|
10975
11213
|
info("Reloading systemd...");
|
|
10976
|
-
|
|
11214
|
+
execSync9("sudo systemctl daemon-reload", { stdio: "inherit" });
|
|
10977
11215
|
success("systemd reloaded");
|
|
10978
11216
|
for (const svc of services) {
|
|
10979
11217
|
info(`Enabling ${svc.name}...`);
|
|
10980
|
-
|
|
11218
|
+
execSync9(`sudo systemctl enable ${svc.name}`, { stdio: "inherit" });
|
|
10981
11219
|
success(`${svc.name} enabled (will start on boot)`);
|
|
10982
11220
|
if (opts.start !== false) {
|
|
10983
11221
|
info(`Starting ${svc.name}...`);
|
|
10984
|
-
|
|
11222
|
+
execSync9(`sudo systemctl restart ${svc.name}`, { stdio: "inherit" });
|
|
10985
11223
|
}
|
|
10986
11224
|
}
|
|
10987
11225
|
if (opts.start !== false) {
|
|
10988
11226
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
10989
11227
|
for (const svc of services) {
|
|
10990
11228
|
try {
|
|
10991
|
-
const status =
|
|
11229
|
+
const status = execSync9(`systemctl is-active ${svc.name}`, {
|
|
10992
11230
|
encoding: "utf-8"
|
|
10993
11231
|
}).trim();
|
|
10994
11232
|
if (status === "active") {
|
|
@@ -11001,7 +11239,7 @@ ${svc.content}`
|
|
|
11001
11239
|
}
|
|
11002
11240
|
}
|
|
11003
11241
|
try {
|
|
11004
|
-
const ip =
|
|
11242
|
+
const ip = execSync9("hostname -I | awk '{print $1}'", {
|
|
11005
11243
|
encoding: "utf-8",
|
|
11006
11244
|
shell: "/bin/bash"
|
|
11007
11245
|
}).trim();
|
|
@@ -11037,7 +11275,7 @@ async function deployToAWS(opts) {
|
|
|
11037
11275
|
info(" - SSM parameter (board password)");
|
|
11038
11276
|
console.log();
|
|
11039
11277
|
try {
|
|
11040
|
-
|
|
11278
|
+
execSync9(`aws cloudformation describe-stacks --stack-name ${stackName2}`, {
|
|
11041
11279
|
encoding: "utf-8",
|
|
11042
11280
|
stdio: "pipe"
|
|
11043
11281
|
});
|
|
@@ -11047,13 +11285,13 @@ async function deployToAWS(opts) {
|
|
|
11047
11285
|
}
|
|
11048
11286
|
info("Deleting CloudFormation stack...");
|
|
11049
11287
|
try {
|
|
11050
|
-
|
|
11288
|
+
execSync9(`aws cloudformation delete-stack --stack-name ${stackName2}`, {
|
|
11051
11289
|
encoding: "utf-8",
|
|
11052
11290
|
stdio: "pipe"
|
|
11053
11291
|
});
|
|
11054
11292
|
success("Stack deletion initiated");
|
|
11055
11293
|
info("Waiting for deletion to complete (2-5 minutes)...");
|
|
11056
|
-
|
|
11294
|
+
execSync9(`aws cloudformation wait stack-delete-complete --stack-name ${stackName2}`, {
|
|
11057
11295
|
encoding: "utf-8",
|
|
11058
11296
|
stdio: "pipe",
|
|
11059
11297
|
timeout: 6e5
|
|
@@ -11076,13 +11314,13 @@ async function deployToAWS(opts) {
|
|
|
11076
11314
|
process.exit(1);
|
|
11077
11315
|
}
|
|
11078
11316
|
try {
|
|
11079
|
-
|
|
11317
|
+
execSync9("aws --version", { encoding: "utf-8" });
|
|
11080
11318
|
} catch {
|
|
11081
11319
|
error("AWS CLI not found. Install: https://aws.amazon.com/cli/");
|
|
11082
11320
|
process.exit(1);
|
|
11083
11321
|
}
|
|
11084
11322
|
try {
|
|
11085
|
-
|
|
11323
|
+
execSync9("aws sts get-caller-identity", { encoding: "utf-8", stdio: "pipe" });
|
|
11086
11324
|
success("AWS credentials valid");
|
|
11087
11325
|
} catch {
|
|
11088
11326
|
error("AWS credentials not configured. Run: aws configure");
|
|
@@ -11090,9 +11328,9 @@ async function deployToAWS(opts) {
|
|
|
11090
11328
|
}
|
|
11091
11329
|
const __filename2 = fileURLToPath3(import.meta.url);
|
|
11092
11330
|
const __dirname2 = dirname7(__filename2);
|
|
11093
|
-
const templatePath =
|
|
11094
|
-
const cwdTemplate =
|
|
11095
|
-
const template =
|
|
11331
|
+
const templatePath = join30(__dirname2, "..", "..", "infra", "cloudformation", "beastmode.yaml");
|
|
11332
|
+
const cwdTemplate = join30(process.cwd(), "infra", "cloudformation", "beastmode.yaml");
|
|
11333
|
+
const template = existsSync32(templatePath) ? templatePath : existsSync32(cwdTemplate) ? cwdTemplate : null;
|
|
11096
11334
|
if (!template) {
|
|
11097
11335
|
error("CloudFormation template not found. Expected at infra/cloudformation/beastmode.yaml");
|
|
11098
11336
|
process.exit(1);
|
|
@@ -11120,7 +11358,7 @@ async function deployToAWS(opts) {
|
|
|
11120
11358
|
}
|
|
11121
11359
|
let isUpdate = false;
|
|
11122
11360
|
try {
|
|
11123
|
-
|
|
11361
|
+
execSync9(createCmd, { encoding: "utf-8", stdio: "pipe" });
|
|
11124
11362
|
success("Stack creation initiated");
|
|
11125
11363
|
} catch (e) {
|
|
11126
11364
|
const msg = e.stderr || e.message || "";
|
|
@@ -11129,7 +11367,7 @@ async function deployToAWS(opts) {
|
|
|
11129
11367
|
info("Stack exists \u2014 updating...");
|
|
11130
11368
|
try {
|
|
11131
11369
|
const updateCmd = createCmd.replace("create-stack", "update-stack");
|
|
11132
|
-
|
|
11370
|
+
execSync9(updateCmd, { encoding: "utf-8", stdio: "pipe" });
|
|
11133
11371
|
success("Stack update initiated");
|
|
11134
11372
|
isUpdate = true;
|
|
11135
11373
|
} catch (ue) {
|
|
@@ -11153,7 +11391,7 @@ async function deployToAWS(opts) {
|
|
|
11153
11391
|
const waitAction = isUpdate ? "update" : "create";
|
|
11154
11392
|
info(`Waiting for deployment to complete (3-10 minutes)...`);
|
|
11155
11393
|
try {
|
|
11156
|
-
|
|
11394
|
+
execSync9(`aws cloudformation wait stack-${waitAction}-complete --stack-name ${stackName}`, {
|
|
11157
11395
|
encoding: "utf-8",
|
|
11158
11396
|
stdio: "pipe",
|
|
11159
11397
|
timeout: 9e5
|
|
@@ -11165,7 +11403,7 @@ async function deployToAWS(opts) {
|
|
|
11165
11403
|
process.exit(1);
|
|
11166
11404
|
}
|
|
11167
11405
|
try {
|
|
11168
|
-
const outputsJson =
|
|
11406
|
+
const outputsJson = execSync9(
|
|
11169
11407
|
`aws cloudformation describe-stacks --stack-name ${stackName} --query 'Stacks[0].Outputs' --output json`,
|
|
11170
11408
|
{ encoding: "utf-8", stdio: "pipe" }
|
|
11171
11409
|
);
|
|
@@ -11193,215 +11431,12 @@ async function deployToAWS(opts) {
|
|
|
11193
11431
|
}
|
|
11194
11432
|
}
|
|
11195
11433
|
|
|
11196
|
-
// src/
|
|
11197
|
-
|
|
11198
|
-
import { execSync as execSync9, spawnSync as spawnSync2 } from "child_process";
|
|
11199
|
-
import { writeFileSync as writeFileSync25, chmodSync, mkdirSync as mkdirSync19, existsSync as existsSync32, unlinkSync as unlinkSync4 } from "fs";
|
|
11200
|
-
import { join as join30 } from "path";
|
|
11201
|
-
import { homedir as homedir3, platform as platform2 } from "os";
|
|
11202
|
-
var LAUNCH_AGENT_LABEL = "com.develeap.beastmode.claude-creds";
|
|
11203
|
-
function plistPath() {
|
|
11204
|
-
return join30(homedir3(), "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
|
|
11205
|
-
}
|
|
11206
|
-
function agentLogPath() {
|
|
11207
|
-
return join30(homedir3(), ".beastmode", "logs", "sync-claude-creds.log");
|
|
11208
|
-
}
|
|
11209
|
-
function readKeychainToken() {
|
|
11210
|
-
try {
|
|
11211
|
-
return execSync9(
|
|
11212
|
-
`security find-generic-password -s "Claude Code-credentials" -w`,
|
|
11213
|
-
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
11214
|
-
).trim();
|
|
11215
|
-
} catch {
|
|
11216
|
-
error("Could not read Claude Code credentials from Keychain.");
|
|
11217
|
-
info("Fix: run `claude login` first, then re-run this command.");
|
|
11218
|
-
process.exit(1);
|
|
11219
|
-
}
|
|
11220
|
-
}
|
|
11221
|
-
function writeCredentialsFile(rawJson) {
|
|
11222
|
-
let parsed;
|
|
11223
|
-
try {
|
|
11224
|
-
parsed = JSON.parse(rawJson);
|
|
11225
|
-
} catch {
|
|
11226
|
-
error("Keychain entry is not valid JSON.");
|
|
11227
|
-
info("Fix: run `claude login` again to reset the credential.");
|
|
11228
|
-
process.exit(1);
|
|
11229
|
-
}
|
|
11230
|
-
const oauth = parsed.claudeAiOauth;
|
|
11231
|
-
if (!oauth?.accessToken) {
|
|
11232
|
-
error("Keychain entry missing claudeAiOauth.accessToken.");
|
|
11233
|
-
info("Fix: run `claude login` again to reset the credential.");
|
|
11234
|
-
process.exit(1);
|
|
11235
|
-
}
|
|
11236
|
-
const claudeDir = join30(homedir3(), ".claude");
|
|
11237
|
-
if (!existsSync32(claudeDir)) mkdirSync19(claudeDir, { recursive: true });
|
|
11238
|
-
const credsPath = join30(claudeDir, ".credentials.json");
|
|
11239
|
-
writeFileSync25(credsPath, rawJson + "\n", "utf-8");
|
|
11240
|
-
chmodSync(credsPath, 384);
|
|
11241
|
-
if (oauth.expiresAt) {
|
|
11242
|
-
const hoursLeft = Math.round((oauth.expiresAt - Date.now()) / 36e5);
|
|
11243
|
-
if (hoursLeft < 0) {
|
|
11244
|
-
warn(`Token already expired ${Math.abs(hoursLeft)}h ago \u2014 run \`claude login\` to refresh.`);
|
|
11245
|
-
} else if (hoursLeft < 2) {
|
|
11246
|
-
warn(`Token expires in ${hoursLeft}h \u2014 background agent will re-sync after host refreshes it.`);
|
|
11247
|
-
}
|
|
11248
|
-
}
|
|
11249
|
-
return credsPath;
|
|
11250
|
-
}
|
|
11251
|
-
function buildPlist(intervalSeconds) {
|
|
11252
|
-
const nodePath = process.execPath;
|
|
11253
|
-
const cliEntry = process.argv[1];
|
|
11254
|
-
const logPath = agentLogPath();
|
|
11255
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
11256
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
11257
|
-
<plist version="1.0">
|
|
11258
|
-
<dict>
|
|
11259
|
-
<key>Label</key>
|
|
11260
|
-
<string>${LAUNCH_AGENT_LABEL}</string>
|
|
11261
|
-
<key>ProgramArguments</key>
|
|
11262
|
-
<array>
|
|
11263
|
-
<string>${nodePath}</string>
|
|
11264
|
-
<string>${cliEntry}</string>
|
|
11265
|
-
<string>sync-claude-creds</string>
|
|
11266
|
-
</array>
|
|
11267
|
-
<key>StartInterval</key>
|
|
11268
|
-
<integer>${intervalSeconds}</integer>
|
|
11269
|
-
<key>RunAtLoad</key>
|
|
11270
|
-
<true/>
|
|
11271
|
-
<key>StandardOutPath</key>
|
|
11272
|
-
<string>${logPath}</string>
|
|
11273
|
-
<key>StandardErrorPath</key>
|
|
11274
|
-
<string>${logPath}</string>
|
|
11275
|
-
</dict>
|
|
11276
|
-
</plist>
|
|
11277
|
-
`;
|
|
11278
|
-
}
|
|
11279
|
-
function installAgent(intervalSeconds) {
|
|
11280
|
-
const plist = plistPath();
|
|
11281
|
-
const logPath = agentLogPath();
|
|
11282
|
-
mkdirSync19(join30(homedir3(), "Library", "LaunchAgents"), { recursive: true });
|
|
11283
|
-
mkdirSync19(join30(homedir3(), ".beastmode", "logs"), { recursive: true });
|
|
11284
|
-
const uid = process.getuid?.();
|
|
11285
|
-
if (existsSync32(plist)) {
|
|
11286
|
-
spawnSync2("launchctl", ["bootout", `gui/${uid}`, plist], { stdio: "pipe" });
|
|
11287
|
-
}
|
|
11288
|
-
writeFileSync25(plist, buildPlist(intervalSeconds), "utf-8");
|
|
11289
|
-
writeFileSync25(logPath, "", { flag: "a" });
|
|
11290
|
-
const result = spawnSync2("launchctl", ["bootstrap", `gui/${uid}`, plist], {
|
|
11291
|
-
stdio: "pipe",
|
|
11292
|
-
encoding: "utf-8"
|
|
11293
|
-
});
|
|
11294
|
-
if (result.status !== 0) {
|
|
11295
|
-
error(`launchctl bootstrap failed: ${result.stderr || result.stdout}`);
|
|
11296
|
-
info(`Plist written to: ${plist}`);
|
|
11297
|
-
info(`Try manually: launchctl bootstrap gui/${uid} ${plist}`);
|
|
11298
|
-
process.exit(1);
|
|
11299
|
-
}
|
|
11300
|
-
success(`LaunchAgent installed: ${LAUNCH_AGENT_LABEL}`);
|
|
11301
|
-
info(` Plist: ${plist}`);
|
|
11302
|
-
info(` Interval: every ${Math.round(intervalSeconds / 60)} minutes`);
|
|
11303
|
-
info(` Logs: ${logPath}`);
|
|
11304
|
-
info(` Runs at: load + every ${Math.round(intervalSeconds / 60)}min (while you're logged in)`);
|
|
11305
|
-
console.log();
|
|
11306
|
-
info("The agent also runs once right now (RunAtLoad=true) \u2014 credentials synced.");
|
|
11307
|
-
}
|
|
11308
|
-
function uninstallAgent() {
|
|
11309
|
-
const plist = plistPath();
|
|
11310
|
-
const uid = process.getuid?.();
|
|
11311
|
-
if (!existsSync32(plist)) {
|
|
11312
|
-
info("No LaunchAgent installed \u2014 nothing to remove.");
|
|
11313
|
-
return;
|
|
11314
|
-
}
|
|
11315
|
-
const result = spawnSync2("launchctl", ["bootout", `gui/${uid}`, plist], {
|
|
11316
|
-
stdio: "pipe",
|
|
11317
|
-
encoding: "utf-8"
|
|
11318
|
-
});
|
|
11319
|
-
if (result.status !== 0 && !(result.stderr || "").includes("Could not find")) {
|
|
11320
|
-
warn(`launchctl bootout returned: ${result.stderr || result.stdout}`);
|
|
11321
|
-
}
|
|
11322
|
-
try {
|
|
11323
|
-
unlinkSync4(plist);
|
|
11324
|
-
} catch {
|
|
11325
|
-
}
|
|
11326
|
-
success(`LaunchAgent removed: ${LAUNCH_AGENT_LABEL}`);
|
|
11327
|
-
}
|
|
11328
|
-
function showStatus() {
|
|
11329
|
-
const plist = plistPath();
|
|
11330
|
-
const uid = process.getuid?.();
|
|
11331
|
-
if (!existsSync32(plist)) {
|
|
11332
|
-
info("LaunchAgent not installed.");
|
|
11333
|
-
info("Install with: beastmode sync-claude-creds --install");
|
|
11334
|
-
return;
|
|
11335
|
-
}
|
|
11336
|
-
const result = spawnSync2(
|
|
11337
|
-
"launchctl",
|
|
11338
|
-
["print", `gui/${uid}/${LAUNCH_AGENT_LABEL}`],
|
|
11339
|
-
{ stdio: "pipe", encoding: "utf-8" }
|
|
11340
|
-
);
|
|
11341
|
-
if (result.status === 0) {
|
|
11342
|
-
success(`LaunchAgent loaded: ${LAUNCH_AGENT_LABEL}`);
|
|
11343
|
-
const stateMatch = result.stdout.match(/state = (\S+)/);
|
|
11344
|
-
const lastExitMatch = result.stdout.match(/last exit code = (\S+)/);
|
|
11345
|
-
if (stateMatch) info(` State: ${stateMatch[1]}`);
|
|
11346
|
-
if (lastExitMatch) info(` Last exit code: ${lastExitMatch[1]}`);
|
|
11347
|
-
info(` Plist: ${plist}`);
|
|
11348
|
-
info(` Logs: ${agentLogPath()}`);
|
|
11349
|
-
} else {
|
|
11350
|
-
warn(`Plist exists but agent not loaded. Re-install with: beastmode sync-claude-creds --install`);
|
|
11351
|
-
}
|
|
11352
|
-
}
|
|
11353
|
-
var syncClaudeCredsCommand = new Command18("sync-claude-creds").description(
|
|
11354
|
-
"Sync Claude Code credentials from macOS Keychain to ~/.claude/.credentials.json (for Docker)"
|
|
11355
|
-
).option("--restart", "Restart the daemon container after syncing (one-shot only)").option("--install", "Install a launchd agent that re-syncs every 30 minutes").option("--uninstall", "Remove the launchd agent").option("--status", "Show launchd agent status").option("--interval <seconds>", "Sync interval for --install (default: 1800)", "1800").action(async (opts) => {
|
|
11356
|
-
if (platform2() !== "darwin") {
|
|
11357
|
-
info("Not macOS \u2014 Docker mounts ~/.claude/.credentials.json directly. Nothing to sync.");
|
|
11358
|
-
return;
|
|
11359
|
-
}
|
|
11360
|
-
if (opts.install) {
|
|
11361
|
-
header("Install Claude Credential Sync Agent");
|
|
11362
|
-
console.log();
|
|
11363
|
-
const seconds = parseInt(opts.interval || "1800", 10);
|
|
11364
|
-
if (isNaN(seconds) || seconds < 60) {
|
|
11365
|
-
error(`Invalid --interval: ${opts.interval} (minimum 60 seconds)`);
|
|
11366
|
-
process.exit(1);
|
|
11367
|
-
}
|
|
11368
|
-
installAgent(seconds);
|
|
11369
|
-
return;
|
|
11370
|
-
}
|
|
11371
|
-
if (opts.uninstall) {
|
|
11372
|
-
header("Remove Claude Credential Sync Agent");
|
|
11373
|
-
console.log();
|
|
11374
|
-
uninstallAgent();
|
|
11375
|
-
return;
|
|
11376
|
-
}
|
|
11377
|
-
if (opts.status) {
|
|
11378
|
-
header("Claude Credential Sync Agent Status");
|
|
11379
|
-
console.log();
|
|
11380
|
-
showStatus();
|
|
11381
|
-
return;
|
|
11382
|
-
}
|
|
11383
|
-
header("Sync Claude Code Credentials");
|
|
11384
|
-
console.log();
|
|
11385
|
-
const rawJson = readKeychainToken();
|
|
11386
|
-
const credsPath = writeCredentialsFile(rawJson);
|
|
11387
|
-
success(`Wrote ${credsPath}`);
|
|
11388
|
-
if (opts.restart) {
|
|
11389
|
-
console.log();
|
|
11390
|
-
info("Restarting daemon container...");
|
|
11391
|
-
const result = spawnSync2("docker", ["compose", "restart", "daemon"], { stdio: "inherit" });
|
|
11392
|
-
if (result.status !== 0) {
|
|
11393
|
-
warn("docker compose restart daemon failed \u2014 run it manually.");
|
|
11394
|
-
process.exit(result.status ?? 1);
|
|
11395
|
-
}
|
|
11396
|
-
success("Daemon restarted.");
|
|
11397
|
-
} else {
|
|
11398
|
-
console.log();
|
|
11399
|
-
info("Automate: beastmode sync-claude-creds --install");
|
|
11400
|
-
}
|
|
11401
|
-
});
|
|
11434
|
+
// src/index.ts
|
|
11435
|
+
init_sync_claude_creds();
|
|
11402
11436
|
|
|
11403
11437
|
// src/cli/commands/up.ts
|
|
11404
11438
|
import { Command as Command19 } from "commander";
|
|
11439
|
+
init_display();
|
|
11405
11440
|
async function runUp(opts) {
|
|
11406
11441
|
const cwd = opts.cwd ?? process.cwd();
|
|
11407
11442
|
requireComposeFile(cwd);
|
|
@@ -11428,6 +11463,7 @@ var upCommand = new Command19("up").description("Start BeastMode services").opti
|
|
|
11428
11463
|
// src/cli/commands/down.ts
|
|
11429
11464
|
import { Command as Command20 } from "commander";
|
|
11430
11465
|
import inquirer2 from "inquirer";
|
|
11466
|
+
init_display();
|
|
11431
11467
|
async function runDown(opts) {
|
|
11432
11468
|
const cwd = opts.cwd ?? process.cwd();
|
|
11433
11469
|
requireComposeFile(cwd);
|
|
@@ -11464,6 +11500,7 @@ var downCommand = new Command20("down").description("Stop BeastMode services").o
|
|
|
11464
11500
|
|
|
11465
11501
|
// src/cli/commands/logs-cmd.ts
|
|
11466
11502
|
import { Command as Command21 } from "commander";
|
|
11503
|
+
init_display();
|
|
11467
11504
|
var VALID_SERVICES = ["board", "ui", "daemon"];
|
|
11468
11505
|
function buildLogsArgs(opts) {
|
|
11469
11506
|
const args = ["logs", "-f", "--tail", opts.tail ?? "100"];
|
|
@@ -11491,6 +11528,7 @@ var logsCommand = new Command21("logs").description("Stream BeastMode service lo
|
|
|
11491
11528
|
// src/cli/commands/update.ts
|
|
11492
11529
|
import { Command as Command22 } from "commander";
|
|
11493
11530
|
import { readFileSync as readFileSync29, writeFileSync as writeFileSync26 } from "fs";
|
|
11531
|
+
init_display();
|
|
11494
11532
|
async function runUpdate(opts) {
|
|
11495
11533
|
const cwd = opts.cwd ?? process.cwd();
|
|
11496
11534
|
const composePath = requireComposeFile(cwd);
|