@beastmode-develeap/beastmode 0.1.145 → 0.1.146
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 +1219 -464
- package/dist/index.js.map +1 -1
- package/dist/web/board.html +1139 -43
- package/dist/web/build-commit.txt +1 -1
- package/dist/web/build-stamp.txt +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6959,28 +6959,214 @@ var init_server = __esm({
|
|
|
6959
6959
|
}
|
|
6960
6960
|
});
|
|
6961
6961
|
|
|
6962
|
+
// src/cli/commands/board.ts
|
|
6963
|
+
import { Command } from "commander";
|
|
6964
|
+
import { resolve as resolve5, join as join15 } from "path";
|
|
6965
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14, mkdirSync as mkdirSync12, writeFileSync as writeFileSync13, readdirSync as readdirSync7 } from "fs";
|
|
6966
|
+
import { execSync as execSync4 } from "child_process";
|
|
6967
|
+
function findFactoryDir(startDir) {
|
|
6968
|
+
let dir = startDir || process.cwd();
|
|
6969
|
+
const root = resolve5("/");
|
|
6970
|
+
while (dir !== root) {
|
|
6971
|
+
if (existsSync17(join15(dir, ".beastmode", "factory.json"))) {
|
|
6972
|
+
return dir;
|
|
6973
|
+
}
|
|
6974
|
+
const parent = resolve5(dir, "..");
|
|
6975
|
+
if (parent === dir) break;
|
|
6976
|
+
dir = parent;
|
|
6977
|
+
}
|
|
6978
|
+
if (existsSync17(join15(dir, ".beastmode", "factory.json"))) {
|
|
6979
|
+
return dir;
|
|
6980
|
+
}
|
|
6981
|
+
return null;
|
|
6982
|
+
}
|
|
6983
|
+
function inferProjectName(factoryDir) {
|
|
6984
|
+
const projectsDir = join15(factoryDir, ".beastmode", "projects");
|
|
6985
|
+
if (!existsSync17(projectsDir)) return null;
|
|
6986
|
+
const files = readdirSync7(projectsDir).filter((f) => f.endsWith(".json"));
|
|
6987
|
+
if (files.length === 1) return files[0].replace(/\.json$/, "");
|
|
6988
|
+
return null;
|
|
6989
|
+
}
|
|
6990
|
+
function tryExecSync(cmd, timeoutMs = 15e3) {
|
|
6991
|
+
try {
|
|
6992
|
+
return execSync4(cmd, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: timeoutMs }).trim();
|
|
6993
|
+
} catch {
|
|
6994
|
+
return null;
|
|
6995
|
+
}
|
|
6996
|
+
}
|
|
6997
|
+
async function setItemStatus(itemId, status, opts) {
|
|
6998
|
+
if (!itemId || !/^\d+$/.test(itemId)) {
|
|
6999
|
+
throw new Error(`Invalid item_id: ${itemId} (expected numeric id)`);
|
|
7000
|
+
}
|
|
7001
|
+
const trimmedStatus = status.trim();
|
|
7002
|
+
if (!trimmedStatus) {
|
|
7003
|
+
throw new Error("status cannot be empty");
|
|
7004
|
+
}
|
|
7005
|
+
let project = opts.project;
|
|
7006
|
+
if (!project) {
|
|
7007
|
+
const factoryDir = findFactoryDir();
|
|
7008
|
+
if (!factoryDir) {
|
|
7009
|
+
throw new Error("No .beastmode factory found in cwd tree. Pass --project <name> explicitly.");
|
|
7010
|
+
}
|
|
7011
|
+
const inferred = inferProjectName(factoryDir);
|
|
7012
|
+
if (!inferred) {
|
|
7013
|
+
throw new Error(
|
|
7014
|
+
"Could not determine project name from factory (0 or >1 projects configured). Pass --project <name>."
|
|
7015
|
+
);
|
|
7016
|
+
}
|
|
7017
|
+
project = inferred;
|
|
7018
|
+
}
|
|
7019
|
+
const payload = JSON.stringify({ status: trimmedStatus });
|
|
7020
|
+
if (opts.boardUrl) {
|
|
7021
|
+
const url2 = `${opts.boardUrl.replace(/\/+$/, "")}/api/items/${itemId}?board=${encodeURIComponent(project)}`;
|
|
7022
|
+
const cmd2 = `curl -sS -X PATCH '${url2}' -H 'Content-Type: application/json' -d '${payload.replace(/'/g, "'\\''")}'`;
|
|
7023
|
+
const out2 = tryExecSync(cmd2);
|
|
7024
|
+
if (!out2) throw new Error(`curl failed hitting ${url2}`);
|
|
7025
|
+
info(out2);
|
|
7026
|
+
return;
|
|
7027
|
+
}
|
|
7028
|
+
const container = tryExecSync(`docker ps --filter 'label=com.docker.compose.service=daemon' --format '{{.Names}}' | head -n1`) || tryExecSync(`docker ps --filter 'label=com.docker.compose.service=ui' --format '{{.Names}}' | head -n1`);
|
|
7029
|
+
if (!container) {
|
|
7030
|
+
throw new Error(
|
|
7031
|
+
"No running daemon or ui container found. Start the factory with `docker compose up -d`, or pass --board-url."
|
|
7032
|
+
);
|
|
7033
|
+
}
|
|
7034
|
+
const escapedPayload = payload.replace(/'/g, "'\\''");
|
|
7035
|
+
const url = `http://board:8080/api/items/${itemId}?board=${encodeURIComponent(project)}`;
|
|
7036
|
+
const cmd = `docker exec ${container} curl -sS -X PATCH '${url}' -H 'Content-Type: application/json' -d '${escapedPayload}'`;
|
|
7037
|
+
const out = tryExecSync(cmd);
|
|
7038
|
+
if (!out) {
|
|
7039
|
+
throw new Error(`docker exec curl failed hitting ${url} via ${container}`);
|
|
7040
|
+
}
|
|
7041
|
+
try {
|
|
7042
|
+
const parsed = JSON.parse(out);
|
|
7043
|
+
if (parsed.detail) {
|
|
7044
|
+
throw new Error(`Board rejected update: ${parsed.detail}`);
|
|
7045
|
+
}
|
|
7046
|
+
if (parsed.id && parsed.status) {
|
|
7047
|
+
info(`Item ${parsed.id} \u2192 ${parsed.status} (project=${project})`);
|
|
7048
|
+
return;
|
|
7049
|
+
}
|
|
7050
|
+
} catch (e) {
|
|
7051
|
+
if (e instanceof Error && e.message.startsWith("Board rejected update:")) throw e;
|
|
7052
|
+
}
|
|
7053
|
+
info(out);
|
|
7054
|
+
}
|
|
7055
|
+
async function runBoard(opts) {
|
|
7056
|
+
let factoryDir = findFactoryDir();
|
|
7057
|
+
if (!factoryDir) {
|
|
7058
|
+
factoryDir = process.cwd();
|
|
7059
|
+
const bmDir = join15(factoryDir, ".beastmode");
|
|
7060
|
+
if (!existsSync17(bmDir)) {
|
|
7061
|
+
mkdirSync12(bmDir, { recursive: true });
|
|
7062
|
+
}
|
|
7063
|
+
const factoryJsonPath2 = join15(bmDir, "factory.json");
|
|
7064
|
+
if (!existsSync17(factoryJsonPath2)) {
|
|
7065
|
+
writeFileSync13(
|
|
7066
|
+
factoryJsonPath2,
|
|
7067
|
+
JSON.stringify({ factory_name: "BeastMode", created_at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
7068
|
+
"utf-8"
|
|
7069
|
+
);
|
|
7070
|
+
info("No factory found \u2014 created minimal stub at .beastmode/factory.json");
|
|
7071
|
+
}
|
|
7072
|
+
}
|
|
7073
|
+
const factoryJsonPath = join15(factoryDir, ".beastmode", "factory.json");
|
|
7074
|
+
const factoryJson = JSON.parse(readFileSync14(factoryJsonPath, "utf-8"));
|
|
7075
|
+
const factoryName = factoryJson.factory_name || "BeastMode Factory";
|
|
7076
|
+
header(`BeastMode Board \u2014 ${factoryName}`);
|
|
7077
|
+
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
7078
|
+
const port = parseInt(opts.port, 10) || 7669;
|
|
7079
|
+
const uiServer = await startServer2({
|
|
7080
|
+
port,
|
|
7081
|
+
host: opts.host,
|
|
7082
|
+
factoryName,
|
|
7083
|
+
factoryPath: factoryDir,
|
|
7084
|
+
mode: "board"
|
|
7085
|
+
});
|
|
7086
|
+
info(`Board running at ${uiServer.url}/board`);
|
|
7087
|
+
info("Press Ctrl+C to stop.\n");
|
|
7088
|
+
const skipOpen = process.env.BEASTMODE_NO_OPEN === "1" || !!process.env.DOCKER_CONTAINER;
|
|
7089
|
+
if (!skipOpen) {
|
|
7090
|
+
try {
|
|
7091
|
+
const open = (await import("open")).default;
|
|
7092
|
+
await open(`${uiServer.url}/board`);
|
|
7093
|
+
} catch {
|
|
7094
|
+
info(`Open ${uiServer.url}/board in your browser.`);
|
|
7095
|
+
}
|
|
7096
|
+
} else {
|
|
7097
|
+
info(`Open ${uiServer.url}/board in your browser.`);
|
|
7098
|
+
}
|
|
7099
|
+
await new Promise((resolvePromise) => {
|
|
7100
|
+
uiServer.server.on("close", resolvePromise);
|
|
7101
|
+
process.on("SIGINT", async () => {
|
|
7102
|
+
info("\nShutting down board server...");
|
|
7103
|
+
await uiServer.shutdown();
|
|
7104
|
+
resolvePromise();
|
|
7105
|
+
});
|
|
7106
|
+
});
|
|
7107
|
+
}
|
|
7108
|
+
var boardCommand;
|
|
7109
|
+
var init_board = __esm({
|
|
7110
|
+
"src/cli/commands/board.ts"() {
|
|
7111
|
+
"use strict";
|
|
7112
|
+
init_display();
|
|
7113
|
+
boardCommand = new Command("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) => {
|
|
7114
|
+
try {
|
|
7115
|
+
await runBoard(opts);
|
|
7116
|
+
} catch (err) {
|
|
7117
|
+
error(err.message);
|
|
7118
|
+
process.exit(1);
|
|
7119
|
+
}
|
|
7120
|
+
});
|
|
7121
|
+
boardCommand.command("set-status <item_id> <status>").description(
|
|
7122
|
+
"Safely change a board item's status via the board HTTP API (never sqlite3 against the live DB \u2014 Gap 24)"
|
|
7123
|
+
).option("--project <name>", "Project/board name (defaults to the factory's only project, or errors if ambiguous)").option("--board-url <url>", "Override board HTTP URL (default: use running board container via docker exec)").action(async (itemId, status, opts) => {
|
|
7124
|
+
try {
|
|
7125
|
+
await setItemStatus(itemId, status, opts);
|
|
7126
|
+
} catch (err) {
|
|
7127
|
+
error(err.message);
|
|
7128
|
+
process.exit(1);
|
|
7129
|
+
}
|
|
7130
|
+
});
|
|
7131
|
+
}
|
|
7132
|
+
});
|
|
7133
|
+
|
|
6962
7134
|
// src/cli/commands/sync-claude-creds.ts
|
|
6963
7135
|
var sync_claude_creds_exports = {};
|
|
6964
7136
|
__export(sync_claude_creds_exports, {
|
|
6965
7137
|
installAgent: () => installAgent,
|
|
7138
|
+
installAgentLinux: () => installAgentLinux,
|
|
6966
7139
|
readKeychainTokenSafe: () => readKeychainTokenSafe,
|
|
6967
7140
|
syncClaudeCredsCommand: () => syncClaudeCredsCommand,
|
|
7141
|
+
syncClaudeCredsLinux: () => syncClaudeCredsLinux,
|
|
6968
7142
|
syncClaudeCredsOnce: () => syncClaudeCredsOnce
|
|
6969
7143
|
});
|
|
6970
|
-
import { Command } from "commander";
|
|
6971
|
-
import { execSync as
|
|
6972
|
-
import { writeFileSync as
|
|
6973
|
-
import { join as
|
|
7144
|
+
import { Command as Command2 } from "commander";
|
|
7145
|
+
import { execSync as execSync5, spawnSync as spawnSync2 } from "child_process";
|
|
7146
|
+
import { writeFileSync as writeFileSync14, readFileSync as readFileSync15, chmodSync, mkdirSync as mkdirSync13, existsSync as existsSync18, unlinkSync as unlinkSync4 } from "fs";
|
|
7147
|
+
import { join as join16 } from "path";
|
|
6974
7148
|
import { homedir as homedir2, platform } from "os";
|
|
7149
|
+
function systemdUserDir() {
|
|
7150
|
+
return join16(homedir2(), ".config", "systemd", "user");
|
|
7151
|
+
}
|
|
7152
|
+
function systemdServicePath() {
|
|
7153
|
+
return join16(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.service`);
|
|
7154
|
+
}
|
|
7155
|
+
function systemdTimerPath() {
|
|
7156
|
+
return join16(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.timer`);
|
|
7157
|
+
}
|
|
7158
|
+
function linuxCredsPath() {
|
|
7159
|
+
return join16(homedir2(), ".claude", ".credentials.json");
|
|
7160
|
+
}
|
|
6975
7161
|
function plistPath() {
|
|
6976
|
-
return
|
|
7162
|
+
return join16(homedir2(), "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
|
|
6977
7163
|
}
|
|
6978
7164
|
function agentLogPath() {
|
|
6979
|
-
return
|
|
7165
|
+
return join16(homedir2(), ".beastmode", "logs", "sync-claude-creds.log");
|
|
6980
7166
|
}
|
|
6981
7167
|
function readKeychainTokenSafe() {
|
|
6982
7168
|
try {
|
|
6983
|
-
return
|
|
7169
|
+
return execSync5(
|
|
6984
7170
|
`security find-generic-password -s "Claude Code-credentials" -w`,
|
|
6985
7171
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
6986
7172
|
).trim();
|
|
@@ -7008,10 +7194,10 @@ function writeCredentialsFile(rawJson) {
|
|
|
7008
7194
|
if (!oauth?.accessToken) {
|
|
7009
7195
|
throw new Error("Keychain entry missing claudeAiOauth.accessToken. Fix: run `claude login` again to reset the credential.");
|
|
7010
7196
|
}
|
|
7011
|
-
const claudeDir =
|
|
7012
|
-
if (!
|
|
7013
|
-
const credsPath =
|
|
7014
|
-
|
|
7197
|
+
const claudeDir = join16(homedir2(), ".claude");
|
|
7198
|
+
if (!existsSync18(claudeDir)) mkdirSync13(claudeDir, { recursive: true });
|
|
7199
|
+
const credsPath = join16(claudeDir, ".credentials.json");
|
|
7200
|
+
writeFileSync14(credsPath, rawJson + "\n", "utf-8");
|
|
7015
7201
|
chmodSync(credsPath, 384);
|
|
7016
7202
|
if (oauth.expiresAt) {
|
|
7017
7203
|
const hoursLeft = Math.round((oauth.expiresAt - Date.now()) / 36e5);
|
|
@@ -7023,10 +7209,31 @@ function writeCredentialsFile(rawJson) {
|
|
|
7023
7209
|
}
|
|
7024
7210
|
return credsPath;
|
|
7025
7211
|
}
|
|
7026
|
-
function
|
|
7212
|
+
function urgencyMarkerHostPath(factoryDir) {
|
|
7213
|
+
return join16(factoryDir, "daemon", "logs", ".cred-refresh-needed");
|
|
7214
|
+
}
|
|
7215
|
+
function removeUrgencyMarker(factoryDir) {
|
|
7216
|
+
if (!factoryDir) return;
|
|
7217
|
+
const markerPath = urgencyMarkerHostPath(factoryDir);
|
|
7218
|
+
try {
|
|
7219
|
+
unlinkSync4(markerPath);
|
|
7220
|
+
} catch {
|
|
7221
|
+
}
|
|
7222
|
+
}
|
|
7223
|
+
function buildPlist(intervalSeconds, factoryDir) {
|
|
7027
7224
|
const nodePath = process.execPath;
|
|
7028
7225
|
const cliEntry = process.argv[1];
|
|
7029
7226
|
const logPath = agentLogPath();
|
|
7227
|
+
const keychainPath = join16(homedir2(), "Library", "Keychains", "login.keychain-db");
|
|
7228
|
+
const watchPaths = [keychainPath];
|
|
7229
|
+
if (factoryDir) {
|
|
7230
|
+
watchPaths.push(urgencyMarkerHostPath(factoryDir));
|
|
7231
|
+
}
|
|
7232
|
+
const watchPathsXml = ` <key>WatchPaths</key>
|
|
7233
|
+
<array>
|
|
7234
|
+
${watchPaths.map((p) => ` <string>${p}</string>`).join("\n")}
|
|
7235
|
+
</array>
|
|
7236
|
+
`;
|
|
7030
7237
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
7031
7238
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
7032
7239
|
<plist version="1.0">
|
|
@@ -7043,7 +7250,7 @@ function buildPlist(intervalSeconds) {
|
|
|
7043
7250
|
<integer>${intervalSeconds}</integer>
|
|
7044
7251
|
<key>RunAtLoad</key>
|
|
7045
7252
|
<true/>
|
|
7046
|
-
<key>StandardOutPath</key>
|
|
7253
|
+
${watchPathsXml} <key>StandardOutPath</key>
|
|
7047
7254
|
<string>${logPath}</string>
|
|
7048
7255
|
<key>StandardErrorPath</key>
|
|
7049
7256
|
<string>${logPath}</string>
|
|
@@ -7051,17 +7258,21 @@ function buildPlist(intervalSeconds) {
|
|
|
7051
7258
|
</plist>
|
|
7052
7259
|
`;
|
|
7053
7260
|
}
|
|
7054
|
-
function installAgent(intervalSeconds, {
|
|
7261
|
+
function installAgent(intervalSeconds, {
|
|
7262
|
+
throwOnError = false,
|
|
7263
|
+
factoryDir
|
|
7264
|
+
} = {}) {
|
|
7055
7265
|
const plist = plistPath();
|
|
7056
7266
|
const logPath = agentLogPath();
|
|
7057
|
-
|
|
7058
|
-
|
|
7267
|
+
mkdirSync13(join16(homedir2(), "Library", "LaunchAgents"), { recursive: true });
|
|
7268
|
+
mkdirSync13(join16(homedir2(), ".beastmode", "logs"), { recursive: true });
|
|
7059
7269
|
const uid = process.getuid?.();
|
|
7060
|
-
if (
|
|
7270
|
+
if (existsSync18(plist)) {
|
|
7061
7271
|
spawnSync2("launchctl", ["bootout", `gui/${uid}`, plist], { stdio: "pipe" });
|
|
7062
7272
|
}
|
|
7063
|
-
|
|
7064
|
-
|
|
7273
|
+
const resolvedFactory = factoryDir ?? findFactoryDir() ?? void 0;
|
|
7274
|
+
writeFileSync14(plist, buildPlist(intervalSeconds, resolvedFactory), "utf-8");
|
|
7275
|
+
writeFileSync14(logPath, "", { flag: "a" });
|
|
7065
7276
|
const result = spawnSync2("launchctl", ["bootstrap", `gui/${uid}`, plist], {
|
|
7066
7277
|
stdio: "pipe",
|
|
7067
7278
|
encoding: "utf-8"
|
|
@@ -7080,7 +7291,7 @@ function installAgent(intervalSeconds, { throwOnError = false } = {}) {
|
|
|
7080
7291
|
info(` Plist: ${plist}`);
|
|
7081
7292
|
info(` Interval: every ${Math.round(intervalSeconds / 60)} minutes`);
|
|
7082
7293
|
info(` Logs: ${logPath}`);
|
|
7083
|
-
info(`
|
|
7294
|
+
info(` Triggers: load + every ${Math.round(intervalSeconds / 60)}min + Keychain change`);
|
|
7084
7295
|
console.log();
|
|
7085
7296
|
info("The agent also runs once right now (RunAtLoad=true) \u2014 credentials synced.");
|
|
7086
7297
|
}
|
|
@@ -7099,7 +7310,7 @@ function syncClaudeCredsOnce() {
|
|
|
7099
7310
|
function uninstallAgent() {
|
|
7100
7311
|
const plist = plistPath();
|
|
7101
7312
|
const uid = process.getuid?.();
|
|
7102
|
-
if (!
|
|
7313
|
+
if (!existsSync18(plist)) {
|
|
7103
7314
|
info("No LaunchAgent installed \u2014 nothing to remove.");
|
|
7104
7315
|
return;
|
|
7105
7316
|
}
|
|
@@ -7119,7 +7330,7 @@ function uninstallAgent() {
|
|
|
7119
7330
|
function showStatus() {
|
|
7120
7331
|
const plist = plistPath();
|
|
7121
7332
|
const uid = process.getuid?.();
|
|
7122
|
-
if (!
|
|
7333
|
+
if (!existsSync18(plist)) {
|
|
7123
7334
|
info("LaunchAgent not installed.");
|
|
7124
7335
|
info("Install with: beastmode sync-claude-creds --install");
|
|
7125
7336
|
return;
|
|
@@ -7141,18 +7352,257 @@ function showStatus() {
|
|
|
7141
7352
|
warn(`Plist exists but agent not loaded. Re-install with: beastmode sync-claude-creds --install`);
|
|
7142
7353
|
}
|
|
7143
7354
|
}
|
|
7144
|
-
|
|
7355
|
+
function syncClaudeCredsLinux() {
|
|
7356
|
+
const credsPath = linuxCredsPath();
|
|
7357
|
+
if (!existsSync18(credsPath)) {
|
|
7358
|
+
return {
|
|
7359
|
+
error: `${credsPath} not found \u2014 run \`claude login\` first`
|
|
7360
|
+
};
|
|
7361
|
+
}
|
|
7362
|
+
let parsed;
|
|
7363
|
+
try {
|
|
7364
|
+
const raw = readFileSync15(credsPath, "utf-8");
|
|
7365
|
+
parsed = JSON.parse(raw);
|
|
7366
|
+
} catch {
|
|
7367
|
+
return {
|
|
7368
|
+
error: `${credsPath} exists but is not valid JSON \u2014 run \`claude login\` to reset`
|
|
7369
|
+
};
|
|
7370
|
+
}
|
|
7371
|
+
const oauth = parsed.claudeAiOauth;
|
|
7372
|
+
if (!oauth?.accessToken) {
|
|
7373
|
+
return {
|
|
7374
|
+
error: `${credsPath} missing claudeAiOauth.accessToken \u2014 run \`claude login\``
|
|
7375
|
+
};
|
|
7376
|
+
}
|
|
7377
|
+
if (oauth.expiresAt) {
|
|
7378
|
+
const minutesLeft = (oauth.expiresAt - Date.now()) / 6e4;
|
|
7379
|
+
if (minutesLeft < 60) {
|
|
7380
|
+
info(
|
|
7381
|
+
`Token expiring in ${Math.round(minutesLeft)}min \u2014 triggering refresh via \`claude --print ping\`...`
|
|
7382
|
+
);
|
|
7383
|
+
const result = spawnSync2("claude", ["--print", "ping"], {
|
|
7384
|
+
stdio: "pipe",
|
|
7385
|
+
timeout: 15e3
|
|
7386
|
+
});
|
|
7387
|
+
if (result.status !== 0) {
|
|
7388
|
+
warn(
|
|
7389
|
+
`claude --print ping failed (exit ${result.status ?? "?"}) \u2014 token may still be valid`
|
|
7390
|
+
);
|
|
7391
|
+
}
|
|
7392
|
+
try {
|
|
7393
|
+
const raw2 = readFileSync15(credsPath, "utf-8");
|
|
7394
|
+
const parsed2 = JSON.parse(raw2);
|
|
7395
|
+
if (parsed2.claudeAiOauth?.expiresAt) {
|
|
7396
|
+
const newMinutes = (parsed2.claudeAiOauth.expiresAt - Date.now()) / 6e4;
|
|
7397
|
+
info(`Token now valid for ${Math.round(newMinutes)} more minutes`);
|
|
7398
|
+
}
|
|
7399
|
+
} catch {
|
|
7400
|
+
}
|
|
7401
|
+
}
|
|
7402
|
+
}
|
|
7403
|
+
return { path: credsPath };
|
|
7404
|
+
}
|
|
7405
|
+
function buildServiceUnit(nodePath, cliEntry, logPath) {
|
|
7406
|
+
return `[Unit]
|
|
7407
|
+
Description=BeastMode Claude credential sync
|
|
7408
|
+
|
|
7409
|
+
[Service]
|
|
7410
|
+
Type=oneshot
|
|
7411
|
+
ExecStart=${nodePath} ${cliEntry} sync-claude-creds
|
|
7412
|
+
StandardOutput=append:${logPath}
|
|
7413
|
+
StandardError=append:${logPath}
|
|
7414
|
+
`;
|
|
7415
|
+
}
|
|
7416
|
+
function buildTimerUnit(intervalSeconds) {
|
|
7417
|
+
return `[Unit]
|
|
7418
|
+
Description=BeastMode Claude credential sync timer
|
|
7419
|
+
|
|
7420
|
+
[Timer]
|
|
7421
|
+
OnBootSec=30s
|
|
7422
|
+
OnUnitActiveSec=${intervalSeconds}s
|
|
7423
|
+
Persistent=true
|
|
7424
|
+
|
|
7425
|
+
[Install]
|
|
7426
|
+
WantedBy=timers.target
|
|
7427
|
+
`;
|
|
7428
|
+
}
|
|
7429
|
+
function ensureSystemctlAvailable() {
|
|
7430
|
+
const result = spawnSync2("systemctl", ["--version"], {
|
|
7431
|
+
stdio: "pipe",
|
|
7432
|
+
encoding: "utf-8"
|
|
7433
|
+
});
|
|
7434
|
+
if (result.status !== 0) {
|
|
7435
|
+
return "systemctl not available \u2014 systemd user units not supported on this system";
|
|
7436
|
+
}
|
|
7437
|
+
return null;
|
|
7438
|
+
}
|
|
7439
|
+
function installAgentLinux(intervalSeconds, { throwOnError = false } = {}) {
|
|
7440
|
+
const unavail = ensureSystemctlAvailable();
|
|
7441
|
+
if (unavail) {
|
|
7442
|
+
if (throwOnError) throw new Error(unavail);
|
|
7443
|
+
error(unavail);
|
|
7444
|
+
process.exit(1);
|
|
7445
|
+
}
|
|
7446
|
+
const unitDir = systemdUserDir();
|
|
7447
|
+
const logDir = join16(homedir2(), ".beastmode", "logs");
|
|
7448
|
+
mkdirSync13(unitDir, { recursive: true });
|
|
7449
|
+
mkdirSync13(logDir, { recursive: true });
|
|
7450
|
+
const logPath = join16(logDir, "sync-claude-creds.log");
|
|
7451
|
+
const nodePath = process.execPath;
|
|
7452
|
+
const cliEntry = process.argv[1];
|
|
7453
|
+
writeFileSync14(
|
|
7454
|
+
systemdServicePath(),
|
|
7455
|
+
buildServiceUnit(nodePath, cliEntry, logPath),
|
|
7456
|
+
"utf-8"
|
|
7457
|
+
);
|
|
7458
|
+
writeFileSync14(
|
|
7459
|
+
systemdTimerPath(),
|
|
7460
|
+
buildTimerUnit(intervalSeconds),
|
|
7461
|
+
"utf-8"
|
|
7462
|
+
);
|
|
7463
|
+
const reload = spawnSync2("systemctl", ["--user", "daemon-reload"], {
|
|
7464
|
+
stdio: "pipe",
|
|
7465
|
+
encoding: "utf-8"
|
|
7466
|
+
});
|
|
7467
|
+
if (reload.status !== 0) {
|
|
7468
|
+
const msg = `systemctl --user daemon-reload failed: ${reload.stderr || reload.stdout}`;
|
|
7469
|
+
if (throwOnError) throw new Error(msg);
|
|
7470
|
+
error(msg);
|
|
7471
|
+
process.exit(1);
|
|
7472
|
+
}
|
|
7473
|
+
const enable = spawnSync2(
|
|
7474
|
+
"systemctl",
|
|
7475
|
+
["--user", "enable", "--now", `${SYSTEMD_UNIT_NAME}.timer`],
|
|
7476
|
+
{ stdio: "pipe", encoding: "utf-8" }
|
|
7477
|
+
);
|
|
7478
|
+
if (enable.status !== 0) {
|
|
7479
|
+
const msg = `systemctl --user enable --now failed: ${enable.stderr || enable.stdout}`;
|
|
7480
|
+
if (throwOnError) throw new Error(msg);
|
|
7481
|
+
error(msg);
|
|
7482
|
+
info(`Service: ${systemdServicePath()}`);
|
|
7483
|
+
info(`Timer: ${systemdTimerPath()}`);
|
|
7484
|
+
process.exit(1);
|
|
7485
|
+
}
|
|
7486
|
+
success(`Systemd timer installed: ${SYSTEMD_UNIT_NAME}.timer`);
|
|
7487
|
+
info(` Service: ${systemdServicePath()}`);
|
|
7488
|
+
info(` Timer: ${systemdTimerPath()}`);
|
|
7489
|
+
info(` Interval: every ${Math.round(intervalSeconds / 60)} minutes`);
|
|
7490
|
+
info(` Logs: ${logPath}`);
|
|
7491
|
+
}
|
|
7492
|
+
function uninstallAgentLinux() {
|
|
7493
|
+
const unavail = ensureSystemctlAvailable();
|
|
7494
|
+
if (unavail) {
|
|
7495
|
+
error(unavail);
|
|
7496
|
+
process.exit(1);
|
|
7497
|
+
}
|
|
7498
|
+
spawnSync2("systemctl", ["--user", "stop", `${SYSTEMD_UNIT_NAME}.timer`], {
|
|
7499
|
+
stdio: "pipe"
|
|
7500
|
+
});
|
|
7501
|
+
spawnSync2(
|
|
7502
|
+
"systemctl",
|
|
7503
|
+
["--user", "disable", `${SYSTEMD_UNIT_NAME}.timer`],
|
|
7504
|
+
{ stdio: "pipe" }
|
|
7505
|
+
);
|
|
7506
|
+
const servicePath = systemdServicePath();
|
|
7507
|
+
const timerPath = systemdTimerPath();
|
|
7508
|
+
let removed = false;
|
|
7509
|
+
if (existsSync18(servicePath)) {
|
|
7510
|
+
try {
|
|
7511
|
+
unlinkSync4(servicePath);
|
|
7512
|
+
removed = true;
|
|
7513
|
+
} catch {
|
|
7514
|
+
}
|
|
7515
|
+
}
|
|
7516
|
+
if (existsSync18(timerPath)) {
|
|
7517
|
+
try {
|
|
7518
|
+
unlinkSync4(timerPath);
|
|
7519
|
+
removed = true;
|
|
7520
|
+
} catch {
|
|
7521
|
+
}
|
|
7522
|
+
}
|
|
7523
|
+
spawnSync2("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
7524
|
+
if (removed) {
|
|
7525
|
+
success(`Systemd timer uninstalled: ${SYSTEMD_UNIT_NAME}.timer`);
|
|
7526
|
+
} else {
|
|
7527
|
+
info("No systemd unit files found \u2014 nothing to remove.");
|
|
7528
|
+
}
|
|
7529
|
+
}
|
|
7530
|
+
function statusAgentLinux() {
|
|
7531
|
+
const unavail = ensureSystemctlAvailable();
|
|
7532
|
+
if (unavail) {
|
|
7533
|
+
error(unavail);
|
|
7534
|
+
return;
|
|
7535
|
+
}
|
|
7536
|
+
if (!existsSync18(systemdTimerPath())) {
|
|
7537
|
+
info("Systemd timer not installed.");
|
|
7538
|
+
info("Install with: beastmode sync-claude-creds --install");
|
|
7539
|
+
return;
|
|
7540
|
+
}
|
|
7541
|
+
const isActive = spawnSync2(
|
|
7542
|
+
"systemctl",
|
|
7543
|
+
["--user", "is-active", `${SYSTEMD_UNIT_NAME}.timer`],
|
|
7544
|
+
{ stdio: "pipe", encoding: "utf-8" }
|
|
7545
|
+
);
|
|
7546
|
+
const active = (isActive.stdout || "").trim();
|
|
7547
|
+
if (active === "active") {
|
|
7548
|
+
success(`Systemd timer active: ${SYSTEMD_UNIT_NAME}.timer`);
|
|
7549
|
+
} else {
|
|
7550
|
+
warn(`Systemd timer not active (state: ${active || "unknown"})`);
|
|
7551
|
+
}
|
|
7552
|
+
const show = spawnSync2(
|
|
7553
|
+
"systemctl",
|
|
7554
|
+
[
|
|
7555
|
+
"--user",
|
|
7556
|
+
"show",
|
|
7557
|
+
`${SYSTEMD_UNIT_NAME}.service`,
|
|
7558
|
+
"--property=ExecMainStartTimestamp,ExecMainStatus"
|
|
7559
|
+
],
|
|
7560
|
+
{ stdio: "pipe", encoding: "utf-8" }
|
|
7561
|
+
);
|
|
7562
|
+
if (show.status === 0 && show.stdout) {
|
|
7563
|
+
for (const line of show.stdout.split("\n")) {
|
|
7564
|
+
const trimmed = line.trim();
|
|
7565
|
+
if (trimmed) info(` ${trimmed}`);
|
|
7566
|
+
}
|
|
7567
|
+
}
|
|
7568
|
+
info(` Service: ${systemdServicePath()}`);
|
|
7569
|
+
info(` Timer: ${systemdTimerPath()}`);
|
|
7570
|
+
}
|
|
7571
|
+
function restartAgentLinux() {
|
|
7572
|
+
const unavail = ensureSystemctlAvailable();
|
|
7573
|
+
if (unavail) {
|
|
7574
|
+
error(unavail);
|
|
7575
|
+
process.exit(1);
|
|
7576
|
+
}
|
|
7577
|
+
const result = spawnSync2(
|
|
7578
|
+
"systemctl",
|
|
7579
|
+
["--user", "restart", `${SYSTEMD_UNIT_NAME}.timer`],
|
|
7580
|
+
{ stdio: "pipe", encoding: "utf-8" }
|
|
7581
|
+
);
|
|
7582
|
+
if (result.status !== 0) {
|
|
7583
|
+
error(
|
|
7584
|
+
`systemctl --user restart failed: ${result.stderr || result.stdout}`
|
|
7585
|
+
);
|
|
7586
|
+
process.exit(1);
|
|
7587
|
+
}
|
|
7588
|
+
success(`Restarted: ${SYSTEMD_UNIT_NAME}.timer`);
|
|
7589
|
+
}
|
|
7590
|
+
var LAUNCH_AGENT_LABEL, SYSTEMD_UNIT_NAME, syncClaudeCredsCommand;
|
|
7145
7591
|
var init_sync_claude_creds = __esm({
|
|
7146
7592
|
"src/cli/commands/sync-claude-creds.ts"() {
|
|
7147
7593
|
"use strict";
|
|
7148
7594
|
init_display();
|
|
7595
|
+
init_board();
|
|
7149
7596
|
LAUNCH_AGENT_LABEL = "com.develeap.beastmode.claude-creds";
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7597
|
+
SYSTEMD_UNIT_NAME = "beastmode-claude-creds";
|
|
7598
|
+
syncClaudeCredsCommand = new Command2("sync-claude-creds").description(
|
|
7599
|
+
"Sync/validate Claude credentials for Docker (macOS: extract from Keychain; Linux: validate ~/.claude/.credentials.json + refresh)"
|
|
7600
|
+
).option("--restart", "Restart the daemon container after syncing (one-shot only)").option("--install", "Install a watcher (launchd on macOS, systemd user timer on Linux) that re-syncs periodically").option("--uninstall", "Remove the watcher").option("--status", "Show watcher status").option("--interval <seconds>", "Sync interval for --install (default: 1800)", "1800").action(async (opts) => {
|
|
7601
|
+
const plat = platform();
|
|
7602
|
+
if (plat !== "darwin" && plat !== "linux") {
|
|
7603
|
+
error(`Unsupported platform: ${plat}`);
|
|
7604
|
+
info("Supported: macOS (darwin), Linux");
|
|
7605
|
+
process.exit(1);
|
|
7156
7606
|
}
|
|
7157
7607
|
if (opts.install) {
|
|
7158
7608
|
header("Install Claude Credential Sync Agent");
|
|
@@ -7162,45 +7612,78 @@ var init_sync_claude_creds = __esm({
|
|
|
7162
7612
|
error(`Invalid --interval: ${opts.interval} (minimum 60 seconds)`);
|
|
7163
7613
|
process.exit(1);
|
|
7164
7614
|
}
|
|
7165
|
-
|
|
7615
|
+
if (plat === "darwin") {
|
|
7616
|
+
installAgent(seconds);
|
|
7617
|
+
} else {
|
|
7618
|
+
installAgentLinux(seconds);
|
|
7619
|
+
}
|
|
7166
7620
|
return;
|
|
7167
7621
|
}
|
|
7168
7622
|
if (opts.uninstall) {
|
|
7169
7623
|
header("Remove Claude Credential Sync Agent");
|
|
7170
7624
|
console.log();
|
|
7171
|
-
|
|
7625
|
+
if (plat === "darwin") {
|
|
7626
|
+
uninstallAgent();
|
|
7627
|
+
} else {
|
|
7628
|
+
uninstallAgentLinux();
|
|
7629
|
+
}
|
|
7172
7630
|
return;
|
|
7173
7631
|
}
|
|
7174
7632
|
if (opts.status) {
|
|
7175
7633
|
header("Claude Credential Sync Agent Status");
|
|
7176
7634
|
console.log();
|
|
7177
|
-
|
|
7635
|
+
if (plat === "darwin") {
|
|
7636
|
+
showStatus();
|
|
7637
|
+
} else {
|
|
7638
|
+
statusAgentLinux();
|
|
7639
|
+
}
|
|
7178
7640
|
return;
|
|
7179
7641
|
}
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
credsPath = writeCredentialsFile(rawJson);
|
|
7186
|
-
} catch (e) {
|
|
7187
|
-
error(e instanceof Error ? e.message : String(e));
|
|
7188
|
-
process.exit(1);
|
|
7642
|
+
if (opts.restart && plat === "linux") {
|
|
7643
|
+
header("Restart Claude Credential Sync Timer");
|
|
7644
|
+
console.log();
|
|
7645
|
+
restartAgentLinux();
|
|
7646
|
+
return;
|
|
7189
7647
|
}
|
|
7190
|
-
|
|
7191
|
-
|
|
7648
|
+
if (plat === "darwin") {
|
|
7649
|
+
header("Sync Claude Code Credentials");
|
|
7192
7650
|
console.log();
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7651
|
+
const rawJson = readKeychainToken();
|
|
7652
|
+
let credsPath;
|
|
7653
|
+
try {
|
|
7654
|
+
credsPath = writeCredentialsFile(rawJson);
|
|
7655
|
+
} catch (e) {
|
|
7656
|
+
error(e instanceof Error ? e.message : String(e));
|
|
7657
|
+
process.exit(1);
|
|
7198
7658
|
}
|
|
7199
|
-
success(
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7659
|
+
success(`Wrote ${credsPath}`);
|
|
7660
|
+
removeUrgencyMarker(findFactoryDir() ?? void 0);
|
|
7661
|
+
if (opts.restart) {
|
|
7662
|
+
console.log();
|
|
7663
|
+
info("Restarting daemon container...");
|
|
7664
|
+
const result2 = spawnSync2("docker", ["compose", "restart", "daemon"], { stdio: "inherit" });
|
|
7665
|
+
if (result2.status !== 0) {
|
|
7666
|
+
warn("docker compose restart daemon failed \u2014 run it manually.");
|
|
7667
|
+
process.exit(result2.status ?? 1);
|
|
7668
|
+
}
|
|
7669
|
+
success("Daemon restarted.");
|
|
7670
|
+
} else {
|
|
7671
|
+
console.log();
|
|
7672
|
+
info("Automate: beastmode sync-claude-creds --install");
|
|
7673
|
+
}
|
|
7674
|
+
return;
|
|
7675
|
+
}
|
|
7676
|
+
header("Validate Claude Credentials");
|
|
7677
|
+
console.log();
|
|
7678
|
+
const result = syncClaudeCredsLinux();
|
|
7679
|
+
if ("error" in result) {
|
|
7680
|
+
error(result.error);
|
|
7681
|
+
process.exit(1);
|
|
7203
7682
|
}
|
|
7683
|
+
success(`Credentials valid: ${result.path}`);
|
|
7684
|
+
removeUrgencyMarker(findFactoryDir() ?? void 0);
|
|
7685
|
+
console.log();
|
|
7686
|
+
info("Automate: beastmode sync-claude-creds --install");
|
|
7204
7687
|
});
|
|
7205
7688
|
}
|
|
7206
7689
|
});
|
|
@@ -7209,13 +7692,13 @@ var init_sync_claude_creds = __esm({
|
|
|
7209
7692
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7210
7693
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7211
7694
|
import { z as z2 } from "zod";
|
|
7212
|
-
import { readFileSync as
|
|
7695
|
+
import { readFileSync as readFileSync28, writeFileSync as writeFileSync24, existsSync as existsSync31, readdirSync as readdirSync11, mkdirSync as mkdirSync19 } from "fs";
|
|
7213
7696
|
import { join as join29, resolve as resolve18, basename as basename5 } from "path";
|
|
7214
7697
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
7215
7698
|
function readJsonFile2(filePath) {
|
|
7216
7699
|
if (!existsSync31(filePath)) return null;
|
|
7217
7700
|
try {
|
|
7218
|
-
return JSON.parse(
|
|
7701
|
+
return JSON.parse(readFileSync28(filePath, "utf-8"));
|
|
7219
7702
|
} catch {
|
|
7220
7703
|
return null;
|
|
7221
7704
|
}
|
|
@@ -7242,7 +7725,7 @@ function getFactoryPath() {
|
|
|
7242
7725
|
function readFactoryStatus(factoryDir) {
|
|
7243
7726
|
const bmDir = join29(factoryDir, ".beastmode");
|
|
7244
7727
|
const factoryIdentity = FactoryIdentitySchema.parse(
|
|
7245
|
-
JSON.parse(
|
|
7728
|
+
JSON.parse(readFileSync28(join29(bmDir, "factory.json"), "utf-8"))
|
|
7246
7729
|
);
|
|
7247
7730
|
const projectsDir = join29(bmDir, "projects");
|
|
7248
7731
|
const projectCount = existsSync31(projectsDir) ? readdirSync11(projectsDir).filter((f) => f.endsWith(".json")).length : 0;
|
|
@@ -7250,7 +7733,7 @@ function readFactoryStatus(factoryDir) {
|
|
|
7250
7733
|
let pluginNames = [];
|
|
7251
7734
|
if (existsSync31(lockPath)) {
|
|
7252
7735
|
try {
|
|
7253
|
-
const lock = JSON.parse(
|
|
7736
|
+
const lock = JSON.parse(readFileSync28(lockPath, "utf-8"));
|
|
7254
7737
|
pluginNames = Object.keys(lock.plugins || {});
|
|
7255
7738
|
} catch {
|
|
7256
7739
|
}
|
|
@@ -7286,7 +7769,7 @@ function readFactoryStatus(factoryDir) {
|
|
|
7286
7769
|
let pidAlive = false;
|
|
7287
7770
|
if (existsSync31(pidFile)) {
|
|
7288
7771
|
try {
|
|
7289
|
-
daemonPid = parseInt(
|
|
7772
|
+
daemonPid = parseInt(readFileSync28(pidFile, "utf-8").trim(), 10);
|
|
7290
7773
|
process.kill(daemonPid, 0);
|
|
7291
7774
|
pidAlive = true;
|
|
7292
7775
|
} catch {
|
|
@@ -7310,7 +7793,7 @@ function readBoardItems(factoryDir) {
|
|
|
7310
7793
|
const filePath = join29(factoryDir, ".beastmode", "board.json");
|
|
7311
7794
|
if (!existsSync31(filePath)) return [];
|
|
7312
7795
|
try {
|
|
7313
|
-
const raw = JSON.parse(
|
|
7796
|
+
const raw = JSON.parse(readFileSync28(filePath, "utf-8"));
|
|
7314
7797
|
return Array.isArray(raw.items) ? raw.items : [];
|
|
7315
7798
|
} catch {
|
|
7316
7799
|
return [];
|
|
@@ -7342,7 +7825,7 @@ function createMcpServer() {
|
|
|
7342
7825
|
async ({ key_path }) => {
|
|
7343
7826
|
const factoryDir = getFactoryPath();
|
|
7344
7827
|
const configPath = join29(factoryDir, ".beastmode", "config.json");
|
|
7345
|
-
const config = existsSync31(configPath) ? JSON.parse(
|
|
7828
|
+
const config = existsSync31(configPath) ? JSON.parse(readFileSync28(configPath, "utf-8")) : generateDefaults();
|
|
7346
7829
|
try {
|
|
7347
7830
|
const value = configGet(config, key_path);
|
|
7348
7831
|
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
@@ -7361,7 +7844,7 @@ function createMcpServer() {
|
|
|
7361
7844
|
async ({ key_path, value }) => {
|
|
7362
7845
|
const factoryDir = getFactoryPath();
|
|
7363
7846
|
const configPath = join29(factoryDir, ".beastmode", "config.json");
|
|
7364
|
-
const config = existsSync31(configPath) ? JSON.parse(
|
|
7847
|
+
const config = existsSync31(configPath) ? JSON.parse(readFileSync28(configPath, "utf-8")) : generateDefaults();
|
|
7365
7848
|
const coerced = coerceValue(value);
|
|
7366
7849
|
const updated = configSet(config, key_path, coerced);
|
|
7367
7850
|
writeFileSync24(configPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
@@ -7480,7 +7963,7 @@ function createMcpServer() {
|
|
|
7480
7963
|
const lockPath = join29(bmDir, "extensions.lock");
|
|
7481
7964
|
if (existsSync31(lockPath)) {
|
|
7482
7965
|
try {
|
|
7483
|
-
const lock = JSON.parse(
|
|
7966
|
+
const lock = JSON.parse(readFileSync28(lockPath, "utf-8"));
|
|
7484
7967
|
plugins = lock.plugins || {};
|
|
7485
7968
|
} catch {
|
|
7486
7969
|
}
|
|
@@ -7520,7 +8003,7 @@ function createMcpServer() {
|
|
|
7520
8003
|
}
|
|
7521
8004
|
const projects = readdirSync11(projectsDir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
7522
8005
|
try {
|
|
7523
|
-
return JSON.parse(
|
|
8006
|
+
return JSON.parse(readFileSync28(join29(projectsDir, f), "utf-8"));
|
|
7524
8007
|
} catch {
|
|
7525
8008
|
return null;
|
|
7526
8009
|
}
|
|
@@ -7658,15 +8141,15 @@ var init_mcp = __esm({
|
|
|
7658
8141
|
});
|
|
7659
8142
|
|
|
7660
8143
|
// src/index.ts
|
|
7661
|
-
import { Command as
|
|
8144
|
+
import { Command as Command24 } from "commander";
|
|
7662
8145
|
|
|
7663
8146
|
// src/cli/commands/init.ts
|
|
7664
8147
|
init_engine();
|
|
7665
8148
|
init_file_writer();
|
|
7666
|
-
import { Command as
|
|
8149
|
+
import { Command as Command3 } from "commander";
|
|
7667
8150
|
import inquirer from "inquirer";
|
|
7668
|
-
import { resolve as
|
|
7669
|
-
import { existsSync as
|
|
8151
|
+
import { resolve as resolve6, basename as basename4, join as join17 } from "path";
|
|
8152
|
+
import { existsSync as existsSync19, writeFileSync as writeFileSync15, mkdirSync as mkdirSync14, readFileSync as readFileSync16 } from "fs";
|
|
7670
8153
|
|
|
7671
8154
|
// src/cli/utils/docker.ts
|
|
7672
8155
|
import { existsSync as existsSync9 } from "fs";
|
|
@@ -7887,7 +8370,7 @@ function generateComposeYaml(tag) {
|
|
|
7887
8370
|
|
|
7888
8371
|
// src/cli/commands/init.ts
|
|
7889
8372
|
init_display();
|
|
7890
|
-
var initCommand = new
|
|
8373
|
+
var initCommand = new Command3("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(
|
|
7891
8374
|
"--project-github-token <token>",
|
|
7892
8375
|
"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."
|
|
7893
8376
|
).option(
|
|
@@ -7918,16 +8401,16 @@ function collect(val, acc) {
|
|
|
7918
8401
|
return acc;
|
|
7919
8402
|
}
|
|
7920
8403
|
function readSecretFile(filePath) {
|
|
7921
|
-
return
|
|
8404
|
+
return readFileSync16(resolve6(filePath), "utf-8").trim();
|
|
7922
8405
|
}
|
|
7923
8406
|
function isSourceRepo(dir) {
|
|
7924
|
-
return
|
|
8407
|
+
return existsSync19(resolve6(dir, "daemon")) && existsSync19(resolve6(dir, "board")) && existsSync19(resolve6(dir, "cli"));
|
|
7925
8408
|
}
|
|
7926
8409
|
async function runInit(name, opts) {
|
|
7927
8410
|
if (opts.ui) {
|
|
7928
8411
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
7929
|
-
const factoryName2 = name || basename4(
|
|
7930
|
-
const projectPath2 = opts.project ?
|
|
8412
|
+
const factoryName2 = name || basename4(resolve6("."));
|
|
8413
|
+
const projectPath2 = opts.project ? resolve6(opts.project) : void 0;
|
|
7931
8414
|
info("Starting init wizard...");
|
|
7932
8415
|
const uiServer = await startServer2({
|
|
7933
8416
|
port: 7669,
|
|
@@ -7952,20 +8435,20 @@ async function runInit(name, opts) {
|
|
|
7952
8435
|
});
|
|
7953
8436
|
return;
|
|
7954
8437
|
}
|
|
7955
|
-
if (!isSourceRepo(
|
|
8438
|
+
if (!isSourceRepo(resolve6("."))) {
|
|
7956
8439
|
await runImageModeInit(name, opts);
|
|
7957
8440
|
return;
|
|
7958
8441
|
}
|
|
7959
8442
|
const totalSteps = 5;
|
|
7960
8443
|
header("BeastMode \u2014 Dark Factory Init");
|
|
7961
8444
|
info("Creating your Custom Dark Factory\n");
|
|
7962
|
-
const factoryName = name || basename4(
|
|
7963
|
-
if (
|
|
8445
|
+
const factoryName = name || basename4(resolve6("."));
|
|
8446
|
+
if (existsSync19(factoryName) && existsSync19(resolve6(factoryName, ".beastmode"))) {
|
|
7964
8447
|
throw new Error(`Factory already exists at ./${factoryName}. Use 'beastmode config' to modify.`);
|
|
7965
8448
|
}
|
|
7966
8449
|
if (opts.from) {
|
|
7967
|
-
const { readFileSync:
|
|
7968
|
-
const templateContent =
|
|
8450
|
+
const { readFileSync: readFileSync31 } = await import("fs");
|
|
8451
|
+
const templateContent = readFileSync31(resolve6(opts.from), "utf-8");
|
|
7969
8452
|
const { parseTemplate: parseTemplate2 } = await Promise.resolve().then(() => (init_template_importer(), template_importer_exports));
|
|
7970
8453
|
const template = parseTemplate2(templateContent);
|
|
7971
8454
|
info(`Importing from template: ${opts.from}`);
|
|
@@ -7973,7 +8456,7 @@ async function runInit(name, opts) {
|
|
|
7973
8456
|
if (!opts.project) {
|
|
7974
8457
|
throw new Error("--project is required when using --from");
|
|
7975
8458
|
}
|
|
7976
|
-
const templateProjectPath =
|
|
8459
|
+
const templateProjectPath = resolve6(opts.project);
|
|
7977
8460
|
const templateStack = detectStack(templateProjectPath);
|
|
7978
8461
|
const templateProjectName = basename4(templateProjectPath);
|
|
7979
8462
|
const actions2 = scaffoldFactory(factoryName, template.config, {
|
|
@@ -7999,9 +8482,9 @@ async function runInit(name, opts) {
|
|
|
7999
8482
|
step(1, totalSteps, "Detect");
|
|
8000
8483
|
let projectPath;
|
|
8001
8484
|
if (opts.project) {
|
|
8002
|
-
projectPath =
|
|
8485
|
+
projectPath = resolve6(opts.project);
|
|
8003
8486
|
} else if (opts.yes) {
|
|
8004
|
-
projectPath =
|
|
8487
|
+
projectPath = resolve6(".");
|
|
8005
8488
|
info("No --project specified, using current directory");
|
|
8006
8489
|
} else {
|
|
8007
8490
|
const answer = await inquirer.prompt([
|
|
@@ -8010,10 +8493,10 @@ async function runInit(name, opts) {
|
|
|
8010
8493
|
name: "project",
|
|
8011
8494
|
message: "Path to your project:",
|
|
8012
8495
|
default: ".",
|
|
8013
|
-
validate: (input) =>
|
|
8496
|
+
validate: (input) => existsSync19(resolve6(input)) || `Directory not found: ${input}`
|
|
8014
8497
|
}
|
|
8015
8498
|
]);
|
|
8016
|
-
projectPath =
|
|
8499
|
+
projectPath = resolve6(answer.project);
|
|
8017
8500
|
}
|
|
8018
8501
|
const stack = detectStack(projectPath);
|
|
8019
8502
|
success(`Detected: ${stack.framework} (${stack.language}) with ${stack.package_manager || "no package manager"}`);
|
|
@@ -8195,7 +8678,7 @@ async function runInit(name, opts) {
|
|
|
8195
8678
|
collectedSecrets.push(`BEASTMODE_UI_PASSWORD=${uiPassword}`);
|
|
8196
8679
|
}
|
|
8197
8680
|
if (collectedSecrets.length > 0) {
|
|
8198
|
-
const secretsPath =
|
|
8681
|
+
const secretsPath = resolve6(factoryName, ".beastmode", "secrets.env.local");
|
|
8199
8682
|
const secretsContent = "# BeastMode secrets \u2014 DO NOT COMMIT\n" + collectedSecrets.join("\n") + "\n";
|
|
8200
8683
|
const { writeFileSync: writeSecrets } = await import("fs");
|
|
8201
8684
|
writeSecrets(secretsPath, secretsContent, "utf-8");
|
|
@@ -8219,6 +8702,25 @@ async function runInit(name, opts) {
|
|
|
8219
8702
|
} catch (e) {
|
|
8220
8703
|
warn(`Claude-creds setup skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
8221
8704
|
}
|
|
8705
|
+
} else if (process.platform === "linux") {
|
|
8706
|
+
try {
|
|
8707
|
+
const { syncClaudeCredsLinux: syncClaudeCredsLinux2, installAgentLinux: installAgentLinux2 } = await Promise.resolve().then(() => (init_sync_claude_creds(), sync_claude_creds_exports));
|
|
8708
|
+
const sync = syncClaudeCredsLinux2();
|
|
8709
|
+
if ("error" in sync) {
|
|
8710
|
+
warn(`Claude creds check skipped: ${sync.error}`);
|
|
8711
|
+
info(" Run `claude login` then `beastmode sync-claude-creds --install`.");
|
|
8712
|
+
} else {
|
|
8713
|
+
success(`Claude creds validated: ${sync.path}`);
|
|
8714
|
+
try {
|
|
8715
|
+
installAgentLinux2(1800, { throwOnError: true });
|
|
8716
|
+
} catch (e) {
|
|
8717
|
+
warn(`Claude-creds timer install failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
8718
|
+
info(" You can retry with: beastmode sync-claude-creds --install");
|
|
8719
|
+
}
|
|
8720
|
+
}
|
|
8721
|
+
} catch (e) {
|
|
8722
|
+
warn(`Claude-creds setup skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
8723
|
+
}
|
|
8222
8724
|
}
|
|
8223
8725
|
console.log();
|
|
8224
8726
|
header(`Factory "${factoryName}" is ready!`);
|
|
@@ -8238,7 +8740,7 @@ async function runImageModeInit(name, opts) {
|
|
|
8238
8740
|
header("BeastMode \u2014 Factory Init (Image Mode)");
|
|
8239
8741
|
info("Setting up BeastMode from pre-built Docker images\n");
|
|
8240
8742
|
const factoryName = name || ".";
|
|
8241
|
-
const targetDir =
|
|
8743
|
+
const targetDir = resolve6(factoryName === "." ? "." : factoryName);
|
|
8242
8744
|
step(1, totalSteps, "Credentials");
|
|
8243
8745
|
let projectGithubToken = opts.projectGithubToken || (opts.projectGithubTokenFile ? readSecretFile(opts.projectGithubTokenFile) : "") || opts.githubToken || (opts.githubTokenFile ? readSecretFile(opts.githubTokenFile) : "") || process.env.PROJECT_GITHUB_TOKEN || process.env.GITHUB_TOKEN || "";
|
|
8244
8746
|
if (!projectGithubToken && !opts.yes) {
|
|
@@ -8296,15 +8798,15 @@ async function runImageModeInit(name, opts) {
|
|
|
8296
8798
|
}
|
|
8297
8799
|
let projectPath;
|
|
8298
8800
|
if (opts.project) {
|
|
8299
|
-
projectPath =
|
|
8300
|
-
if (!
|
|
8801
|
+
projectPath = resolve6(opts.project);
|
|
8802
|
+
if (!existsSync19(projectPath)) {
|
|
8301
8803
|
error(`--project path does not exist: ${projectPath}`);
|
|
8302
8804
|
process.exit(1);
|
|
8303
8805
|
}
|
|
8304
8806
|
success(`Project path: ${projectPath}`);
|
|
8305
8807
|
} else if (opts.yes) {
|
|
8306
|
-
const cwd =
|
|
8307
|
-
if (!
|
|
8808
|
+
const cwd = resolve6(".");
|
|
8809
|
+
if (!existsSync19(join17(cwd, ".git"))) {
|
|
8308
8810
|
error(
|
|
8309
8811
|
"--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."
|
|
8310
8812
|
);
|
|
@@ -8320,16 +8822,16 @@ async function runImageModeInit(name, opts) {
|
|
|
8320
8822
|
message: "Path to your project (must be a git clone with an origin remote):",
|
|
8321
8823
|
default: ".",
|
|
8322
8824
|
validate: (input) => {
|
|
8323
|
-
const p =
|
|
8324
|
-
if (!
|
|
8325
|
-
if (!
|
|
8825
|
+
const p = resolve6(input);
|
|
8826
|
+
if (!existsSync19(p)) return `Directory not found: ${p}`;
|
|
8827
|
+
if (!existsSync19(join17(p, ".git"))) {
|
|
8326
8828
|
return `Not a git repo: ${p} (run 'git init' first, or pick a different path)`;
|
|
8327
8829
|
}
|
|
8328
8830
|
return true;
|
|
8329
8831
|
}
|
|
8330
8832
|
}
|
|
8331
8833
|
]);
|
|
8332
|
-
projectPath =
|
|
8834
|
+
projectPath = resolve6(answer.project);
|
|
8333
8835
|
success(`Project path: ${projectPath}`);
|
|
8334
8836
|
}
|
|
8335
8837
|
step(2, totalSteps, "Docker Registry");
|
|
@@ -8361,9 +8863,9 @@ async function runImageModeInit(name, opts) {
|
|
|
8361
8863
|
success("Authenticated to ghcr.io");
|
|
8362
8864
|
}
|
|
8363
8865
|
step(3, totalSteps, "Generate");
|
|
8364
|
-
|
|
8866
|
+
mkdirSync14(targetDir, { recursive: true });
|
|
8365
8867
|
const composeContent = generateComposeYaml("latest");
|
|
8366
|
-
|
|
8868
|
+
writeFileSync15(join17(targetDir, "docker-compose.yml"), composeContent, "utf-8");
|
|
8367
8869
|
success("docker-compose.yml");
|
|
8368
8870
|
const envLines = [
|
|
8369
8871
|
"# BeastMode environment \u2014 DO NOT COMMIT",
|
|
@@ -8403,10 +8905,10 @@ async function runImageModeInit(name, opts) {
|
|
|
8403
8905
|
"# PROJECT_REPO=owner/repo",
|
|
8404
8906
|
""
|
|
8405
8907
|
];
|
|
8406
|
-
|
|
8908
|
+
writeFileSync15(join17(targetDir, ".env"), envLines.join("\n"), "utf-8");
|
|
8407
8909
|
success(`.env (PROJECT_DIR=${projectPath}, two-PAT model)`);
|
|
8408
8910
|
for (const dir of ["data", "runs", "daemon/logs", ".beastmode", "config"]) {
|
|
8409
|
-
|
|
8911
|
+
mkdirSync14(join17(targetDir, dir), { recursive: true });
|
|
8410
8912
|
}
|
|
8411
8913
|
step(4, totalSteps, "Pull Images");
|
|
8412
8914
|
try {
|
|
@@ -8456,6 +8958,25 @@ async function runImageModeInit(name, opts) {
|
|
|
8456
8958
|
} catch (e) {
|
|
8457
8959
|
warn(`Claude-creds setup skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
8458
8960
|
}
|
|
8961
|
+
} else if (process.platform === "linux") {
|
|
8962
|
+
try {
|
|
8963
|
+
const { syncClaudeCredsLinux: syncClaudeCredsLinux2, installAgentLinux: installAgentLinux2 } = await Promise.resolve().then(() => (init_sync_claude_creds(), sync_claude_creds_exports));
|
|
8964
|
+
const sync = syncClaudeCredsLinux2();
|
|
8965
|
+
if ("error" in sync) {
|
|
8966
|
+
warn(`Claude creds check skipped: ${sync.error}`);
|
|
8967
|
+
info(" Run `claude login` then `beastmode sync-claude-creds --install`.");
|
|
8968
|
+
} else {
|
|
8969
|
+
success(`Claude creds validated: ${sync.path}`);
|
|
8970
|
+
try {
|
|
8971
|
+
installAgentLinux2(1800, { throwOnError: true });
|
|
8972
|
+
} catch (e) {
|
|
8973
|
+
warn(`Claude-creds timer install failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
8974
|
+
info(" You can retry with: beastmode sync-claude-creds --install");
|
|
8975
|
+
}
|
|
8976
|
+
}
|
|
8977
|
+
} catch (e) {
|
|
8978
|
+
warn(`Claude-creds setup skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
8979
|
+
}
|
|
8459
8980
|
}
|
|
8460
8981
|
console.log();
|
|
8461
8982
|
header("BeastMode is ready!");
|
|
@@ -8475,21 +8996,21 @@ async function runImageModeInit(name, opts) {
|
|
|
8475
8996
|
// src/cli/commands/export-config.ts
|
|
8476
8997
|
init_export_adapter();
|
|
8477
8998
|
init_display();
|
|
8478
|
-
import { Command as
|
|
8479
|
-
import { readFileSync as
|
|
8480
|
-
import { resolve as
|
|
8999
|
+
import { Command as Command4 } from "commander";
|
|
9000
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync16, existsSync as existsSync20, readdirSync as readdirSync8 } from "fs";
|
|
9001
|
+
import { resolve as resolve7, join as join18 } from "path";
|
|
8481
9002
|
function exportFactory(factoryDir, outputPath) {
|
|
8482
|
-
const bmDir =
|
|
8483
|
-
const configPath =
|
|
8484
|
-
if (!
|
|
9003
|
+
const bmDir = join18(factoryDir, ".beastmode");
|
|
9004
|
+
const configPath = join18(bmDir, "config.json");
|
|
9005
|
+
if (!existsSync20(configPath)) {
|
|
8485
9006
|
throw new Error(`No factory found at ${factoryDir}`);
|
|
8486
9007
|
}
|
|
8487
|
-
const config = JSON.parse(
|
|
8488
|
-
const identity = JSON.parse(
|
|
9008
|
+
const config = JSON.parse(readFileSync17(configPath, "utf-8"));
|
|
9009
|
+
const identity = JSON.parse(readFileSync17(join18(bmDir, "factory.json"), "utf-8"));
|
|
8489
9010
|
let plugins = [];
|
|
8490
|
-
const lockPath =
|
|
8491
|
-
if (
|
|
8492
|
-
const lock = JSON.parse(
|
|
9011
|
+
const lockPath = join18(bmDir, "extensions.lock");
|
|
9012
|
+
if (existsSync20(lockPath)) {
|
|
9013
|
+
const lock = JSON.parse(readFileSync17(lockPath, "utf-8"));
|
|
8493
9014
|
plugins = Object.keys(lock.plugins || {});
|
|
8494
9015
|
}
|
|
8495
9016
|
const template = {
|
|
@@ -8498,15 +9019,15 @@ function exportFactory(factoryDir, outputPath) {
|
|
|
8498
9019
|
config,
|
|
8499
9020
|
plugins
|
|
8500
9021
|
};
|
|
8501
|
-
|
|
9022
|
+
writeFileSync16(outputPath, JSON.stringify(template, null, 2) + "\n", "utf-8");
|
|
8502
9023
|
}
|
|
8503
9024
|
function findRunDir(runId) {
|
|
8504
9025
|
const candidates = [
|
|
8505
|
-
|
|
8506
|
-
|
|
9026
|
+
resolve7(".", "runs", runId),
|
|
9027
|
+
resolve7(".", runId)
|
|
8507
9028
|
];
|
|
8508
9029
|
for (const candidate of candidates) {
|
|
8509
|
-
if (
|
|
9030
|
+
if (existsSync20(candidate)) {
|
|
8510
9031
|
return candidate;
|
|
8511
9032
|
}
|
|
8512
9033
|
}
|
|
@@ -8515,28 +9036,28 @@ function findRunDir(runId) {
|
|
|
8515
9036
|
);
|
|
8516
9037
|
}
|
|
8517
9038
|
function createArtifactExportCommand(artifact) {
|
|
8518
|
-
return new
|
|
9039
|
+
return new Command4(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) => {
|
|
8519
9040
|
try {
|
|
8520
9041
|
const runDir = findRunDir(opts.run);
|
|
8521
9042
|
let sourceContent;
|
|
8522
9043
|
if (artifact === "scenarios") {
|
|
8523
|
-
const scenariosDir =
|
|
8524
|
-
if (!
|
|
9044
|
+
const scenariosDir = join18(runDir, "scenarios");
|
|
9045
|
+
if (!existsSync20(scenariosDir)) {
|
|
8525
9046
|
throw new Error(`No scenarios directory found in run ${opts.run}`);
|
|
8526
9047
|
}
|
|
8527
|
-
const files =
|
|
8528
|
-
sourceContent = files.map((f) =>
|
|
9048
|
+
const files = readdirSync8(scenariosDir).filter((f) => f.endsWith(".md")).sort();
|
|
9049
|
+
sourceContent = files.map((f) => readFileSync17(join18(scenariosDir, f), "utf-8")).join("\n\n---\n\n");
|
|
8529
9050
|
} else {
|
|
8530
9051
|
const artifactFile = artifact === "nlspec" ? "nlspec.md" : "plan.md";
|
|
8531
|
-
const artifactPath =
|
|
8532
|
-
if (!
|
|
9052
|
+
const artifactPath = join18(runDir, artifactFile);
|
|
9053
|
+
if (!existsSync20(artifactPath)) {
|
|
8533
9054
|
throw new Error(`${artifactFile} not found in run ${opts.run}`);
|
|
8534
9055
|
}
|
|
8535
|
-
sourceContent =
|
|
9056
|
+
sourceContent = readFileSync17(artifactPath, "utf-8");
|
|
8536
9057
|
}
|
|
8537
9058
|
const result = runExportAdapter(opts.adapter, sourceContent);
|
|
8538
9059
|
if (opts.output) {
|
|
8539
|
-
|
|
9060
|
+
writeFileSync16(resolve7(opts.output), result, "utf-8");
|
|
8540
9061
|
header(`Exported ${artifact}`);
|
|
8541
9062
|
success(`Written to: ${opts.output}`);
|
|
8542
9063
|
} else {
|
|
@@ -8548,12 +9069,12 @@ function createArtifactExportCommand(artifact) {
|
|
|
8548
9069
|
}
|
|
8549
9070
|
});
|
|
8550
9071
|
}
|
|
8551
|
-
var exportCommand = new
|
|
9072
|
+
var exportCommand = new Command4("export").description("Export factory config or pipeline artifacts");
|
|
8552
9073
|
exportCommand.addCommand(
|
|
8553
|
-
new
|
|
9074
|
+
new Command4("config").description("Export factory configuration template (secrets excluded)").option("--output <path>", "Output file path", "beastmode-template.json").action((opts) => {
|
|
8554
9075
|
try {
|
|
8555
|
-
const factoryDir =
|
|
8556
|
-
exportFactory(factoryDir,
|
|
9076
|
+
const factoryDir = resolve7(".");
|
|
9077
|
+
exportFactory(factoryDir, resolve7(opts.output));
|
|
8557
9078
|
header("Factory template exported");
|
|
8558
9079
|
success(`Written to: ${opts.output}`);
|
|
8559
9080
|
info("Share with teammates: beastmode init my-factory --from " + opts.output);
|
|
@@ -8570,10 +9091,10 @@ exportCommand.addCommand(createArtifactExportCommand("scenarios"));
|
|
|
8570
9091
|
// src/cli/commands/validate.ts
|
|
8571
9092
|
init_config_validator();
|
|
8572
9093
|
init_display();
|
|
8573
|
-
import { Command as
|
|
8574
|
-
import { resolve as
|
|
8575
|
-
var validateCommand = new
|
|
8576
|
-
const factoryDir =
|
|
9094
|
+
import { Command as Command5 } from "commander";
|
|
9095
|
+
import { resolve as resolve8 } from "path";
|
|
9096
|
+
var validateCommand = new Command5("validate").description("Validate factory health").option("--verbose", "Show all checks").action((opts) => {
|
|
9097
|
+
const factoryDir = resolve8(".");
|
|
8577
9098
|
const result = validateFactory(factoryDir, process.env);
|
|
8578
9099
|
header("Factory Validation");
|
|
8579
9100
|
if (result.errors.length > 0) {
|
|
@@ -8602,8 +9123,8 @@ init_mcp_manager();
|
|
|
8602
9123
|
init_hook_manager();
|
|
8603
9124
|
init_skill_manager();
|
|
8604
9125
|
init_display();
|
|
8605
|
-
import { Command as
|
|
8606
|
-
import { resolve as
|
|
9126
|
+
import { Command as Command6 } from "commander";
|
|
9127
|
+
import { resolve as resolve9 } from "path";
|
|
8607
9128
|
async function addPluginAction(factoryDir, source, options) {
|
|
8608
9129
|
await installPlugin(factoryDir, source);
|
|
8609
9130
|
}
|
|
@@ -8617,7 +9138,7 @@ function addMcpAction(factoryDir, name, command, args, config) {
|
|
|
8617
9138
|
});
|
|
8618
9139
|
}
|
|
8619
9140
|
function addSkillAction(factoryDir, sourcePath) {
|
|
8620
|
-
addSkill(factoryDir,
|
|
9141
|
+
addSkill(factoryDir, resolve9(sourcePath));
|
|
8621
9142
|
}
|
|
8622
9143
|
function addHookAction(factoryDir, event, name, options) {
|
|
8623
9144
|
addHook(factoryDir, event, {
|
|
@@ -8629,10 +9150,10 @@ function addHookAction(factoryDir, event, name, options) {
|
|
|
8629
9150
|
source: "user"
|
|
8630
9151
|
});
|
|
8631
9152
|
}
|
|
8632
|
-
var addCommand = new
|
|
8633
|
-
new
|
|
9153
|
+
var addCommand = new Command6("add").description("Add extensions to the factory").addCommand(
|
|
9154
|
+
new Command6("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) => {
|
|
8634
9155
|
try {
|
|
8635
|
-
const factoryDir =
|
|
9156
|
+
const factoryDir = resolve9(".");
|
|
8636
9157
|
await addPluginAction(factoryDir, source, opts);
|
|
8637
9158
|
success(`Plugin installed from ${source}`);
|
|
8638
9159
|
} catch (err) {
|
|
@@ -8641,10 +9162,10 @@ var addCommand = new Command5("add").description("Add extensions to the factory"
|
|
|
8641
9162
|
}
|
|
8642
9163
|
})
|
|
8643
9164
|
).addCommand(
|
|
8644
|
-
new
|
|
9165
|
+
new Command6("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(
|
|
8645
9166
|
(name, opts) => {
|
|
8646
9167
|
try {
|
|
8647
|
-
const factoryDir =
|
|
9168
|
+
const factoryDir = resolve9(".");
|
|
8648
9169
|
const config = opts.config ? JSON.parse(opts.config) : void 0;
|
|
8649
9170
|
addMcpAction(factoryDir, name, opts.command, opts.args, config);
|
|
8650
9171
|
success(`MCP server '${name}' added`);
|
|
@@ -8655,9 +9176,9 @@ var addCommand = new Command5("add").description("Add extensions to the factory"
|
|
|
8655
9176
|
}
|
|
8656
9177
|
)
|
|
8657
9178
|
).addCommand(
|
|
8658
|
-
new
|
|
9179
|
+
new Command6("skill").description("Add a custom skill from a directory").argument("<path>", "Path to skill directory (must contain SKILL.md)").action((path) => {
|
|
8659
9180
|
try {
|
|
8660
|
-
const factoryDir =
|
|
9181
|
+
const factoryDir = resolve9(".");
|
|
8661
9182
|
addSkillAction(factoryDir, path);
|
|
8662
9183
|
success(`Skill added from ${path}`);
|
|
8663
9184
|
} catch (err) {
|
|
@@ -8666,10 +9187,10 @@ var addCommand = new Command5("add").description("Add extensions to the factory"
|
|
|
8666
9187
|
}
|
|
8667
9188
|
})
|
|
8668
9189
|
).addCommand(
|
|
8669
|
-
new
|
|
9190
|
+
new Command6("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(
|
|
8670
9191
|
(event, name, opts) => {
|
|
8671
9192
|
try {
|
|
8672
|
-
const factoryDir =
|
|
9193
|
+
const factoryDir = resolve9(".");
|
|
8673
9194
|
const config = opts.config ? JSON.parse(opts.config) : void 0;
|
|
8674
9195
|
addHookAction(factoryDir, event, name, {
|
|
8675
9196
|
command: opts.command,
|
|
@@ -8692,8 +9213,8 @@ init_mcp_manager();
|
|
|
8692
9213
|
init_hook_manager();
|
|
8693
9214
|
init_skill_manager();
|
|
8694
9215
|
init_display();
|
|
8695
|
-
import { Command as
|
|
8696
|
-
import { resolve as
|
|
9216
|
+
import { Command as Command7 } from "commander";
|
|
9217
|
+
import { resolve as resolve10 } from "path";
|
|
8697
9218
|
function removePluginAction(factoryDir, name, options) {
|
|
8698
9219
|
removePlugin(factoryDir, name, options);
|
|
8699
9220
|
}
|
|
@@ -8706,10 +9227,10 @@ function removeSkillAction(factoryDir, name) {
|
|
|
8706
9227
|
function removeHookAction(factoryDir, event, name) {
|
|
8707
9228
|
removeHook(factoryDir, event, name);
|
|
8708
9229
|
}
|
|
8709
|
-
var removeCommand = new
|
|
8710
|
-
new
|
|
9230
|
+
var removeCommand = new Command7("remove").description("Remove extensions from the factory").addCommand(
|
|
9231
|
+
new Command7("plugin").description("Remove an installed plugin").argument("<name>", "Plugin name").option("--force", "Force removal even if other plugins depend on it").action((name, opts) => {
|
|
8711
9232
|
try {
|
|
8712
|
-
const factoryDir =
|
|
9233
|
+
const factoryDir = resolve10(".");
|
|
8713
9234
|
removePluginAction(factoryDir, name, opts);
|
|
8714
9235
|
success(`Plugin '${name}' removed`);
|
|
8715
9236
|
} catch (err) {
|
|
@@ -8718,9 +9239,9 @@ var removeCommand = new Command6("remove").description("Remove extensions from t
|
|
|
8718
9239
|
}
|
|
8719
9240
|
})
|
|
8720
9241
|
).addCommand(
|
|
8721
|
-
new
|
|
9242
|
+
new Command7("mcp").description("Remove an MCP server").argument("<name>", "Server name").action((name) => {
|
|
8722
9243
|
try {
|
|
8723
|
-
const factoryDir =
|
|
9244
|
+
const factoryDir = resolve10(".");
|
|
8724
9245
|
removeMcpAction(factoryDir, name);
|
|
8725
9246
|
success(`MCP server '${name}' removed`);
|
|
8726
9247
|
} catch (err) {
|
|
@@ -8729,9 +9250,9 @@ var removeCommand = new Command6("remove").description("Remove extensions from t
|
|
|
8729
9250
|
}
|
|
8730
9251
|
})
|
|
8731
9252
|
).addCommand(
|
|
8732
|
-
new
|
|
9253
|
+
new Command7("skill").description("Remove a custom skill").argument("<name>", "Skill name").action((name) => {
|
|
8733
9254
|
try {
|
|
8734
|
-
const factoryDir =
|
|
9255
|
+
const factoryDir = resolve10(".");
|
|
8735
9256
|
removeSkillAction(factoryDir, name);
|
|
8736
9257
|
success(`Skill '${name}' removed`);
|
|
8737
9258
|
} catch (err) {
|
|
@@ -8740,9 +9261,9 @@ var removeCommand = new Command6("remove").description("Remove extensions from t
|
|
|
8740
9261
|
}
|
|
8741
9262
|
})
|
|
8742
9263
|
).addCommand(
|
|
8743
|
-
new
|
|
9264
|
+
new Command7("hook").description("Remove a pipeline hook").argument("<event>", "Hook event (e.g., pre-build, post-verify)").argument("<name>", "Hook name").action((event, name) => {
|
|
8744
9265
|
try {
|
|
8745
|
-
const factoryDir =
|
|
9266
|
+
const factoryDir = resolve10(".");
|
|
8746
9267
|
removeHookAction(factoryDir, event, name);
|
|
8747
9268
|
success(`Hook '${name}' removed from ${event}`);
|
|
8748
9269
|
} catch (err) {
|
|
@@ -8758,17 +9279,17 @@ init_hook_manager();
|
|
|
8758
9279
|
init_skill_manager();
|
|
8759
9280
|
init_presets();
|
|
8760
9281
|
init_display();
|
|
8761
|
-
import { Command as
|
|
8762
|
-
import { resolve as
|
|
8763
|
-
import { readFileSync as
|
|
8764
|
-
import { join as
|
|
9282
|
+
import { Command as Command8 } from "commander";
|
|
9283
|
+
import { resolve as resolve11 } from "path";
|
|
9284
|
+
import { readFileSync as readFileSync18, existsSync as existsSync21 } from "fs";
|
|
9285
|
+
import { join as join19 } from "path";
|
|
8765
9286
|
function listPluginsAction(factoryDir) {
|
|
8766
|
-
const lockPath =
|
|
8767
|
-
if (!
|
|
9287
|
+
const lockPath = join19(factoryDir, ".beastmode", "extensions.lock");
|
|
9288
|
+
if (!existsSync21(lockPath)) {
|
|
8768
9289
|
console.log(" No plugins installed (extensions.lock not found).");
|
|
8769
9290
|
return;
|
|
8770
9291
|
}
|
|
8771
|
-
const lock = JSON.parse(
|
|
9292
|
+
const lock = JSON.parse(readFileSync18(lockPath, "utf-8"));
|
|
8772
9293
|
const plugins = lock.plugins || {};
|
|
8773
9294
|
const names = Object.keys(plugins);
|
|
8774
9295
|
if (names.length === 0) {
|
|
@@ -8841,24 +9362,24 @@ function listPresetsAction() {
|
|
|
8841
9362
|
console.log(` ${name} \u2014 ${preset.description}`);
|
|
8842
9363
|
}
|
|
8843
9364
|
}
|
|
8844
|
-
var listCommand = new
|
|
8845
|
-
new
|
|
8846
|
-
listPluginsAction(
|
|
9365
|
+
var listCommand = new Command8("list").description("List installed extensions and resources").addCommand(
|
|
9366
|
+
new Command8("plugins").description("List installed plugins").action(() => {
|
|
9367
|
+
listPluginsAction(resolve11("."));
|
|
8847
9368
|
})
|
|
8848
9369
|
).addCommand(
|
|
8849
|
-
new
|
|
8850
|
-
listMcpsAction(
|
|
9370
|
+
new Command8("mcps").description("List configured MCP servers").action(() => {
|
|
9371
|
+
listMcpsAction(resolve11("."));
|
|
8851
9372
|
})
|
|
8852
9373
|
).addCommand(
|
|
8853
|
-
new
|
|
8854
|
-
listSkillsAction(
|
|
9374
|
+
new Command8("skills").description("List available skills").action(() => {
|
|
9375
|
+
listSkillsAction(resolve11("."));
|
|
8855
9376
|
})
|
|
8856
9377
|
).addCommand(
|
|
8857
|
-
new
|
|
8858
|
-
listHooksAction(
|
|
9378
|
+
new Command8("hooks").description("List configured hooks").action(() => {
|
|
9379
|
+
listHooksAction(resolve11("."));
|
|
8859
9380
|
})
|
|
8860
9381
|
).addCommand(
|
|
8861
|
-
new
|
|
9382
|
+
new Command8("presets").description("List available pipeline presets").action(() => {
|
|
8862
9383
|
listPresetsAction();
|
|
8863
9384
|
})
|
|
8864
9385
|
);
|
|
@@ -8866,32 +9387,32 @@ var listCommand = new Command7("list").description("List installed extensions an
|
|
|
8866
9387
|
// src/cli/commands/import-cmd.ts
|
|
8867
9388
|
init_import_adapter();
|
|
8868
9389
|
init_display();
|
|
8869
|
-
import { Command as
|
|
8870
|
-
import { readFileSync as
|
|
8871
|
-
import { resolve as
|
|
9390
|
+
import { Command as Command9 } from "commander";
|
|
9391
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync17, mkdirSync as mkdirSync15, existsSync as existsSync22 } from "fs";
|
|
9392
|
+
import { resolve as resolve12, join as join20 } from "path";
|
|
8872
9393
|
var VALID_ARTIFACTS = ["nlspec", "plan", "scenarios"];
|
|
8873
9394
|
function createArtifactCommand(artifact) {
|
|
8874
|
-
return new
|
|
9395
|
+
return new Command9(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) => {
|
|
8875
9396
|
try {
|
|
8876
|
-
const sourcePath =
|
|
8877
|
-
if (!
|
|
9397
|
+
const sourcePath = resolve12(opts.from);
|
|
9398
|
+
if (!existsSync22(sourcePath)) {
|
|
8878
9399
|
throw new Error(`Source file not found: ${sourcePath}`);
|
|
8879
9400
|
}
|
|
8880
|
-
const sourceContent =
|
|
9401
|
+
const sourceContent = readFileSync19(sourcePath, "utf-8");
|
|
8881
9402
|
const result = runImportAdapter(opts.adapter, sourceContent);
|
|
8882
9403
|
if (opts.output) {
|
|
8883
|
-
const outputPath =
|
|
9404
|
+
const outputPath = resolve12(opts.output);
|
|
8884
9405
|
if (artifact === "scenarios" && opts.adapter.endsWith(":test-cases")) {
|
|
8885
9406
|
const scenarios = JSON.parse(result);
|
|
8886
|
-
|
|
9407
|
+
mkdirSync15(outputPath, { recursive: true });
|
|
8887
9408
|
for (const scenario of scenarios) {
|
|
8888
|
-
const filePath =
|
|
8889
|
-
|
|
9409
|
+
const filePath = join20(outputPath, `${scenario.name}.md`);
|
|
9410
|
+
writeFileSync17(filePath, scenario.content, "utf-8");
|
|
8890
9411
|
success(` ${scenario.name}.md`);
|
|
8891
9412
|
}
|
|
8892
9413
|
header(`Imported ${scenarios.length} scenarios to ${opts.output}`);
|
|
8893
9414
|
} else {
|
|
8894
|
-
|
|
9415
|
+
writeFileSync17(outputPath, result, "utf-8");
|
|
8895
9416
|
header(`Imported ${artifact}`);
|
|
8896
9417
|
success(`Written to: ${opts.output}`);
|
|
8897
9418
|
}
|
|
@@ -8904,7 +9425,7 @@ function createArtifactCommand(artifact) {
|
|
|
8904
9425
|
}
|
|
8905
9426
|
});
|
|
8906
9427
|
}
|
|
8907
|
-
var importCommand = new
|
|
9428
|
+
var importCommand = new Command9("import").description("Import external artifacts into BeastMode format");
|
|
8908
9429
|
for (const artifact of VALID_ARTIFACTS) {
|
|
8909
9430
|
importCommand.addCommand(createArtifactCommand(artifact));
|
|
8910
9431
|
}
|
|
@@ -8913,53 +9434,53 @@ for (const artifact of VALID_ARTIFACTS) {
|
|
|
8913
9434
|
init_status_checker();
|
|
8914
9435
|
init_schemas();
|
|
8915
9436
|
init_display();
|
|
8916
|
-
import { Command as
|
|
8917
|
-
import { existsSync as
|
|
8918
|
-
import { execSync as
|
|
8919
|
-
import { join as
|
|
9437
|
+
import { Command as Command10 } from "commander";
|
|
9438
|
+
import { existsSync as existsSync23, readFileSync as readFileSync20, readdirSync as readdirSync9 } from "fs";
|
|
9439
|
+
import { execSync as execSync6 } from "child_process";
|
|
9440
|
+
import { join as join21, resolve as resolve13 } from "path";
|
|
8920
9441
|
function statusAction(factoryDir, opts) {
|
|
8921
|
-
const bmDir =
|
|
8922
|
-
if (!
|
|
9442
|
+
const bmDir = join21(factoryDir, ".beastmode");
|
|
9443
|
+
if (!existsSync23(bmDir)) {
|
|
8923
9444
|
throw new Error(`No factory found at ${factoryDir}`);
|
|
8924
9445
|
}
|
|
8925
|
-
const factoryJsonPath =
|
|
8926
|
-
const rawIdentity = JSON.parse(
|
|
9446
|
+
const factoryJsonPath = join21(bmDir, "factory.json");
|
|
9447
|
+
const rawIdentity = JSON.parse(readFileSync20(factoryJsonPath, "utf-8"));
|
|
8927
9448
|
const factoryIdentity = FactoryIdentitySchema.parse(rawIdentity);
|
|
8928
|
-
const projectsDir =
|
|
8929
|
-
const projectCount =
|
|
8930
|
-
const pluginsDir =
|
|
8931
|
-
const pluginNames =
|
|
8932
|
-
const mcpPath =
|
|
9449
|
+
const projectsDir = join21(bmDir, "projects");
|
|
9450
|
+
const projectCount = existsSync23(projectsDir) ? readdirSync9(projectsDir).filter((f) => f.endsWith(".json")).length : 0;
|
|
9451
|
+
const pluginsDir = join21(bmDir, "plugins");
|
|
9452
|
+
const pluginNames = existsSync23(pluginsDir) ? readdirSync9(pluginsDir) : [];
|
|
9453
|
+
const mcpPath = join21(bmDir, "mcp-servers.json");
|
|
8933
9454
|
let mcpServers = {};
|
|
8934
|
-
if (
|
|
9455
|
+
if (existsSync23(mcpPath)) {
|
|
8935
9456
|
try {
|
|
8936
|
-
const raw = JSON.parse(
|
|
9457
|
+
const raw = JSON.parse(readFileSync20(mcpPath, "utf-8"));
|
|
8937
9458
|
mcpServers = raw.servers || {};
|
|
8938
9459
|
} catch {
|
|
8939
9460
|
}
|
|
8940
9461
|
}
|
|
8941
|
-
const hooksPath =
|
|
9462
|
+
const hooksPath = join21(bmDir, "hooks.json");
|
|
8942
9463
|
let hooks = {};
|
|
8943
|
-
if (
|
|
9464
|
+
if (existsSync23(hooksPath)) {
|
|
8944
9465
|
try {
|
|
8945
|
-
const raw = JSON.parse(
|
|
9466
|
+
const raw = JSON.parse(readFileSync20(hooksPath, "utf-8"));
|
|
8946
9467
|
hooks = raw.hooks || {};
|
|
8947
9468
|
} catch {
|
|
8948
9469
|
}
|
|
8949
9470
|
}
|
|
8950
|
-
const skillsDir =
|
|
8951
|
-
const skillCount =
|
|
8952
|
-
const runsDir =
|
|
9471
|
+
const skillsDir = join21(bmDir, "skills");
|
|
9472
|
+
const skillCount = existsSync23(skillsDir) ? readdirSync9(skillsDir).length : 0;
|
|
9473
|
+
const runsDir = join21(factoryDir, "runs");
|
|
8953
9474
|
let runDirs = [];
|
|
8954
|
-
if (
|
|
8955
|
-
runDirs =
|
|
9475
|
+
if (existsSync23(runsDir)) {
|
|
9476
|
+
runDirs = readdirSync9(runsDir).filter((d) => d.startsWith("run-")).sort();
|
|
8956
9477
|
}
|
|
8957
|
-
const pidPath =
|
|
9478
|
+
const pidPath = join21(bmDir, "daemon.pid");
|
|
8958
9479
|
let daemonPid = null;
|
|
8959
9480
|
let pidAlive = false;
|
|
8960
|
-
if (
|
|
9481
|
+
if (existsSync23(pidPath)) {
|
|
8961
9482
|
try {
|
|
8962
|
-
daemonPid = parseInt(
|
|
9483
|
+
daemonPid = parseInt(readFileSync20(pidPath, "utf-8").trim(), 10);
|
|
8963
9484
|
process.kill(daemonPid, 0);
|
|
8964
9485
|
pidAlive = true;
|
|
8965
9486
|
} catch {
|
|
@@ -8968,7 +9489,7 @@ function statusAction(factoryDir, opts) {
|
|
|
8968
9489
|
}
|
|
8969
9490
|
if (!pidAlive) {
|
|
8970
9491
|
try {
|
|
8971
|
-
const out =
|
|
9492
|
+
const out = execSync6("pgrep -f 'beastmode_daemon' 2>/dev/null || true", {
|
|
8972
9493
|
encoding: "utf-8",
|
|
8973
9494
|
timeout: 3e3
|
|
8974
9495
|
}).trim();
|
|
@@ -8995,8 +9516,8 @@ function statusAction(factoryDir, opts) {
|
|
|
8995
9516
|
};
|
|
8996
9517
|
return collectStatus(input);
|
|
8997
9518
|
}
|
|
8998
|
-
var statusCommand = new
|
|
8999
|
-
const factoryDir =
|
|
9519
|
+
var statusCommand = new Command10("status").description("Show factory status overview").option("--json", "Output as JSON").option("--watch", "Enable watch mode (display layer)").action((opts) => {
|
|
9520
|
+
const factoryDir = resolve13(".");
|
|
9000
9521
|
try {
|
|
9001
9522
|
const status = statusAction(factoryDir, { json: !!opts.json });
|
|
9002
9523
|
if (opts.json) {
|
|
@@ -9032,20 +9553,20 @@ var statusCommand = new Command9("status").description("Show factory status over
|
|
|
9032
9553
|
// src/cli/commands/config-cmd.ts
|
|
9033
9554
|
init_config_manager();
|
|
9034
9555
|
init_display();
|
|
9035
|
-
import { Command as
|
|
9036
|
-
import { existsSync as
|
|
9037
|
-
import { join as
|
|
9038
|
-
import { execSync as
|
|
9556
|
+
import { Command as Command11 } from "commander";
|
|
9557
|
+
import { existsSync as existsSync24, readFileSync as readFileSync21, writeFileSync as writeFileSync18 } from "fs";
|
|
9558
|
+
import { join as join22, resolve as resolve14 } from "path";
|
|
9559
|
+
import { execSync as execSync7 } from "child_process";
|
|
9039
9560
|
function readConfig2(factoryDir) {
|
|
9040
|
-
const configPath =
|
|
9041
|
-
if (!
|
|
9561
|
+
const configPath = join22(factoryDir, ".beastmode", "config.json");
|
|
9562
|
+
if (!existsSync24(configPath)) {
|
|
9042
9563
|
throw new Error("No config.json found. Run beastmode init first.");
|
|
9043
9564
|
}
|
|
9044
|
-
return JSON.parse(
|
|
9565
|
+
return JSON.parse(readFileSync21(configPath, "utf-8"));
|
|
9045
9566
|
}
|
|
9046
9567
|
function writeConfig2(factoryDir, config) {
|
|
9047
|
-
const configPath =
|
|
9048
|
-
|
|
9568
|
+
const configPath = join22(factoryDir, ".beastmode", "config.json");
|
|
9569
|
+
writeFileSync18(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
9049
9570
|
}
|
|
9050
9571
|
function configGetAction(factoryDir, key) {
|
|
9051
9572
|
const config = readConfig2(factoryDir);
|
|
@@ -9062,8 +9583,8 @@ function configResetAction(factoryDir, key) {
|
|
|
9062
9583
|
const updated = configReset(config, key);
|
|
9063
9584
|
writeConfig2(factoryDir, updated);
|
|
9064
9585
|
}
|
|
9065
|
-
var configGetCmd = new
|
|
9066
|
-
const factoryDir =
|
|
9586
|
+
var configGetCmd = new Command11("get").description("Get a config value by dot-notation key").argument("<key>", "Config key (e.g., pipeline.preset)").action((key) => {
|
|
9587
|
+
const factoryDir = resolve14(".");
|
|
9067
9588
|
try {
|
|
9068
9589
|
const value = configGetAction(factoryDir, key);
|
|
9069
9590
|
if (typeof value === "object" && value !== null) {
|
|
@@ -9076,8 +9597,8 @@ var configGetCmd = new Command10("get").description("Get a config value by dot-n
|
|
|
9076
9597
|
process.exit(1);
|
|
9077
9598
|
}
|
|
9078
9599
|
});
|
|
9079
|
-
var configSetCmd = new
|
|
9080
|
-
const factoryDir =
|
|
9600
|
+
var configSetCmd = new Command11("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) => {
|
|
9601
|
+
const factoryDir = resolve14(".");
|
|
9081
9602
|
try {
|
|
9082
9603
|
configSetAction(factoryDir, key, value);
|
|
9083
9604
|
success(`Set ${key} = ${value}`);
|
|
@@ -9086,218 +9607,51 @@ var configSetCmd = new Command10("set").description("Set a config value by dot-n
|
|
|
9086
9607
|
process.exit(1);
|
|
9087
9608
|
}
|
|
9088
9609
|
});
|
|
9089
|
-
var configEditCmd = new
|
|
9090
|
-
const factoryDir =
|
|
9091
|
-
const configPath =
|
|
9092
|
-
if (!
|
|
9610
|
+
var configEditCmd = new Command11("edit").description("Open config.json in $EDITOR").action(() => {
|
|
9611
|
+
const factoryDir = resolve14(".");
|
|
9612
|
+
const configPath = join22(factoryDir, ".beastmode", "config.json");
|
|
9613
|
+
if (!existsSync24(configPath)) {
|
|
9093
9614
|
error("No config.json found. Run beastmode init first.");
|
|
9094
9615
|
process.exit(1);
|
|
9095
9616
|
}
|
|
9096
|
-
const editor = process.env.EDITOR || "vi";
|
|
9097
|
-
try {
|
|
9098
|
-
|
|
9099
|
-
} catch {
|
|
9100
|
-
error(`Failed to open editor: ${editor}`);
|
|
9101
|
-
process.exit(1);
|
|
9102
|
-
}
|
|
9103
|
-
});
|
|
9104
|
-
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) => {
|
|
9105
|
-
const factoryDir = resolve13(".");
|
|
9106
|
-
try {
|
|
9107
|
-
configResetAction(factoryDir, key);
|
|
9108
|
-
if (key) {
|
|
9109
|
-
success(`Reset ${key} to default`);
|
|
9110
|
-
} else {
|
|
9111
|
-
success("Reset all config to defaults");
|
|
9112
|
-
}
|
|
9113
|
-
} catch (err) {
|
|
9114
|
-
error(err.message);
|
|
9115
|
-
process.exit(1);
|
|
9116
|
-
}
|
|
9117
|
-
});
|
|
9118
|
-
var configCommand = new Command10("config").description("Manage factory configuration").addCommand(configGetCmd).addCommand(configSetCmd).addCommand(configEditCmd).addCommand(configResetCmd);
|
|
9119
|
-
|
|
9120
|
-
// src/cli/commands/doctor.ts
|
|
9121
|
-
init_doctor();
|
|
9122
|
-
init_schemas();
|
|
9123
|
-
init_version();
|
|
9124
|
-
init_plugin_resolver();
|
|
9125
|
-
init_display();
|
|
9126
|
-
import { Command as Command12 } from "commander";
|
|
9127
|
-
import { existsSync as existsSync25, readFileSync as readFileSync21, readdirSync as readdirSync10 } from "fs";
|
|
9128
|
-
import { join as join23, resolve as resolve15 } from "path";
|
|
9129
|
-
import { execSync as execSync8 } from "child_process";
|
|
9130
|
-
import { homedir as homedir3, platform as platform2 } from "os";
|
|
9131
|
-
import * as http3 from "http";
|
|
9132
|
-
import * as https from "https";
|
|
9133
|
-
|
|
9134
|
-
// src/cli/commands/board.ts
|
|
9135
|
-
init_display();
|
|
9136
|
-
import { Command as Command11 } from "commander";
|
|
9137
|
-
import { resolve as resolve14, join as join22 } from "path";
|
|
9138
|
-
import { existsSync as existsSync24, readFileSync as readFileSync20, mkdirSync as mkdirSync15, writeFileSync as writeFileSync18, readdirSync as readdirSync9 } from "fs";
|
|
9139
|
-
import { execSync as execSync7 } from "child_process";
|
|
9140
|
-
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) => {
|
|
9141
|
-
try {
|
|
9142
|
-
await runBoard(opts);
|
|
9143
|
-
} catch (err) {
|
|
9144
|
-
error(err.message);
|
|
9145
|
-
process.exit(1);
|
|
9146
|
-
}
|
|
9147
|
-
});
|
|
9148
|
-
boardCommand.command("set-status <item_id> <status>").description(
|
|
9149
|
-
"Safely change a board item's status via the board HTTP API (never sqlite3 against the live DB \u2014 Gap 24)"
|
|
9150
|
-
).option("--project <name>", "Project/board name (defaults to the factory's only project, or errors if ambiguous)").option("--board-url <url>", "Override board HTTP URL (default: use running board container via docker exec)").action(async (itemId, status, opts) => {
|
|
9151
|
-
try {
|
|
9152
|
-
await setItemStatus(itemId, status, opts);
|
|
9153
|
-
} catch (err) {
|
|
9154
|
-
error(err.message);
|
|
9155
|
-
process.exit(1);
|
|
9156
|
-
}
|
|
9157
|
-
});
|
|
9158
|
-
function findFactoryDir(startDir) {
|
|
9159
|
-
let dir = startDir || process.cwd();
|
|
9160
|
-
const root = resolve14("/");
|
|
9161
|
-
while (dir !== root) {
|
|
9162
|
-
if (existsSync24(join22(dir, ".beastmode", "factory.json"))) {
|
|
9163
|
-
return dir;
|
|
9164
|
-
}
|
|
9165
|
-
const parent = resolve14(dir, "..");
|
|
9166
|
-
if (parent === dir) break;
|
|
9167
|
-
dir = parent;
|
|
9168
|
-
}
|
|
9169
|
-
if (existsSync24(join22(dir, ".beastmode", "factory.json"))) {
|
|
9170
|
-
return dir;
|
|
9171
|
-
}
|
|
9172
|
-
return null;
|
|
9173
|
-
}
|
|
9174
|
-
function inferProjectName(factoryDir) {
|
|
9175
|
-
const projectsDir = join22(factoryDir, ".beastmode", "projects");
|
|
9176
|
-
if (!existsSync24(projectsDir)) return null;
|
|
9177
|
-
const files = readdirSync9(projectsDir).filter((f) => f.endsWith(".json"));
|
|
9178
|
-
if (files.length === 1) return files[0].replace(/\.json$/, "");
|
|
9179
|
-
return null;
|
|
9180
|
-
}
|
|
9181
|
-
function tryExecSync(cmd, timeoutMs = 15e3) {
|
|
9182
|
-
try {
|
|
9183
|
-
return execSync7(cmd, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: timeoutMs }).trim();
|
|
9184
|
-
} catch {
|
|
9185
|
-
return null;
|
|
9186
|
-
}
|
|
9187
|
-
}
|
|
9188
|
-
async function setItemStatus(itemId, status, opts) {
|
|
9189
|
-
if (!itemId || !/^\d+$/.test(itemId)) {
|
|
9190
|
-
throw new Error(`Invalid item_id: ${itemId} (expected numeric id)`);
|
|
9191
|
-
}
|
|
9192
|
-
const trimmedStatus = status.trim();
|
|
9193
|
-
if (!trimmedStatus) {
|
|
9194
|
-
throw new Error("status cannot be empty");
|
|
9195
|
-
}
|
|
9196
|
-
let project = opts.project;
|
|
9197
|
-
if (!project) {
|
|
9198
|
-
const factoryDir = findFactoryDir();
|
|
9199
|
-
if (!factoryDir) {
|
|
9200
|
-
throw new Error("No .beastmode factory found in cwd tree. Pass --project <name> explicitly.");
|
|
9201
|
-
}
|
|
9202
|
-
const inferred = inferProjectName(factoryDir);
|
|
9203
|
-
if (!inferred) {
|
|
9204
|
-
throw new Error(
|
|
9205
|
-
"Could not determine project name from factory (0 or >1 projects configured). Pass --project <name>."
|
|
9206
|
-
);
|
|
9207
|
-
}
|
|
9208
|
-
project = inferred;
|
|
9209
|
-
}
|
|
9210
|
-
const payload = JSON.stringify({ status: trimmedStatus });
|
|
9211
|
-
if (opts.boardUrl) {
|
|
9212
|
-
const url2 = `${opts.boardUrl.replace(/\/+$/, "")}/api/items/${itemId}?board=${encodeURIComponent(project)}`;
|
|
9213
|
-
const cmd2 = `curl -sS -X PATCH '${url2}' -H 'Content-Type: application/json' -d '${payload.replace(/'/g, "'\\''")}'`;
|
|
9214
|
-
const out2 = tryExecSync(cmd2);
|
|
9215
|
-
if (!out2) throw new Error(`curl failed hitting ${url2}`);
|
|
9216
|
-
info(out2);
|
|
9217
|
-
return;
|
|
9218
|
-
}
|
|
9219
|
-
const container = tryExecSync(`docker ps --filter 'label=com.docker.compose.service=daemon' --format '{{.Names}}' | head -n1`) || tryExecSync(`docker ps --filter 'label=com.docker.compose.service=ui' --format '{{.Names}}' | head -n1`);
|
|
9220
|
-
if (!container) {
|
|
9221
|
-
throw new Error(
|
|
9222
|
-
"No running daemon or ui container found. Start the factory with `docker compose up -d`, or pass --board-url."
|
|
9223
|
-
);
|
|
9224
|
-
}
|
|
9225
|
-
const escapedPayload = payload.replace(/'/g, "'\\''");
|
|
9226
|
-
const url = `http://board:8080/api/items/${itemId}?board=${encodeURIComponent(project)}`;
|
|
9227
|
-
const cmd = `docker exec ${container} curl -sS -X PATCH '${url}' -H 'Content-Type: application/json' -d '${escapedPayload}'`;
|
|
9228
|
-
const out = tryExecSync(cmd);
|
|
9229
|
-
if (!out) {
|
|
9230
|
-
throw new Error(`docker exec curl failed hitting ${url} via ${container}`);
|
|
9231
|
-
}
|
|
9232
|
-
try {
|
|
9233
|
-
const parsed = JSON.parse(out);
|
|
9234
|
-
if (parsed.detail) {
|
|
9235
|
-
throw new Error(`Board rejected update: ${parsed.detail}`);
|
|
9236
|
-
}
|
|
9237
|
-
if (parsed.id && parsed.status) {
|
|
9238
|
-
info(`Item ${parsed.id} \u2192 ${parsed.status} (project=${project})`);
|
|
9239
|
-
return;
|
|
9240
|
-
}
|
|
9241
|
-
} catch (e) {
|
|
9242
|
-
if (e instanceof Error && e.message.startsWith("Board rejected update:")) throw e;
|
|
9243
|
-
}
|
|
9244
|
-
info(out);
|
|
9245
|
-
}
|
|
9246
|
-
async function runBoard(opts) {
|
|
9247
|
-
let factoryDir = findFactoryDir();
|
|
9248
|
-
if (!factoryDir) {
|
|
9249
|
-
factoryDir = process.cwd();
|
|
9250
|
-
const bmDir = join22(factoryDir, ".beastmode");
|
|
9251
|
-
if (!existsSync24(bmDir)) {
|
|
9252
|
-
mkdirSync15(bmDir, { recursive: true });
|
|
9253
|
-
}
|
|
9254
|
-
const factoryJsonPath2 = join22(bmDir, "factory.json");
|
|
9255
|
-
if (!existsSync24(factoryJsonPath2)) {
|
|
9256
|
-
writeFileSync18(
|
|
9257
|
-
factoryJsonPath2,
|
|
9258
|
-
JSON.stringify({ factory_name: "BeastMode", created_at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
9259
|
-
"utf-8"
|
|
9260
|
-
);
|
|
9261
|
-
info("No factory found \u2014 created minimal stub at .beastmode/factory.json");
|
|
9262
|
-
}
|
|
9617
|
+
const editor = process.env.EDITOR || "vi";
|
|
9618
|
+
try {
|
|
9619
|
+
execSync7(`${editor} ${configPath}`, { stdio: "inherit" });
|
|
9620
|
+
} catch {
|
|
9621
|
+
error(`Failed to open editor: ${editor}`);
|
|
9622
|
+
process.exit(1);
|
|
9263
9623
|
}
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
const
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
factoryName,
|
|
9274
|
-
factoryPath: factoryDir,
|
|
9275
|
-
mode: "board"
|
|
9276
|
-
});
|
|
9277
|
-
info(`Board running at ${uiServer.url}/board`);
|
|
9278
|
-
info("Press Ctrl+C to stop.\n");
|
|
9279
|
-
const skipOpen = process.env.BEASTMODE_NO_OPEN === "1" || !!process.env.DOCKER_CONTAINER;
|
|
9280
|
-
if (!skipOpen) {
|
|
9281
|
-
try {
|
|
9282
|
-
const open = (await import("open")).default;
|
|
9283
|
-
await open(`${uiServer.url}/board`);
|
|
9284
|
-
} catch {
|
|
9285
|
-
info(`Open ${uiServer.url}/board in your browser.`);
|
|
9624
|
+
});
|
|
9625
|
+
var configResetCmd = new Command11("reset").description("Reset config to defaults (all or specific key)").argument("[key]", "Optional key to reset (resets all if omitted)").action((key) => {
|
|
9626
|
+
const factoryDir = resolve14(".");
|
|
9627
|
+
try {
|
|
9628
|
+
configResetAction(factoryDir, key);
|
|
9629
|
+
if (key) {
|
|
9630
|
+
success(`Reset ${key} to default`);
|
|
9631
|
+
} else {
|
|
9632
|
+
success("Reset all config to defaults");
|
|
9286
9633
|
}
|
|
9287
|
-
}
|
|
9288
|
-
|
|
9634
|
+
} catch (err) {
|
|
9635
|
+
error(err.message);
|
|
9636
|
+
process.exit(1);
|
|
9289
9637
|
}
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
process.on("SIGINT", async () => {
|
|
9293
|
-
info("\nShutting down board server...");
|
|
9294
|
-
await uiServer.shutdown();
|
|
9295
|
-
resolvePromise();
|
|
9296
|
-
});
|
|
9297
|
-
});
|
|
9298
|
-
}
|
|
9638
|
+
});
|
|
9639
|
+
var configCommand = new Command11("config").description("Manage factory configuration").addCommand(configGetCmd).addCommand(configSetCmd).addCommand(configEditCmd).addCommand(configResetCmd);
|
|
9299
9640
|
|
|
9300
9641
|
// src/cli/commands/doctor.ts
|
|
9642
|
+
init_doctor();
|
|
9643
|
+
init_schemas();
|
|
9644
|
+
init_version();
|
|
9645
|
+
init_plugin_resolver();
|
|
9646
|
+
init_display();
|
|
9647
|
+
import { Command as Command12 } from "commander";
|
|
9648
|
+
import { existsSync as existsSync25, readFileSync as readFileSync22, readdirSync as readdirSync10 } from "fs";
|
|
9649
|
+
import { join as join23, resolve as resolve15 } from "path";
|
|
9650
|
+
import { execSync as execSync8, spawnSync as spawnSync3 } from "child_process";
|
|
9651
|
+
import { homedir as homedir3, platform as platform2 } from "os";
|
|
9652
|
+
import * as http3 from "http";
|
|
9653
|
+
import * as https from "https";
|
|
9654
|
+
init_board();
|
|
9301
9655
|
function tryExec(cmd, timeout = 8e3) {
|
|
9302
9656
|
try {
|
|
9303
9657
|
return execSync8(cmd, { encoding: "utf-8", timeout, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
@@ -9439,7 +9793,7 @@ async function checkProjectGithubToken(env, factoryDir) {
|
|
|
9439
9793
|
const envPath = join23(factoryDir, ".env");
|
|
9440
9794
|
if (existsSync25(envPath)) {
|
|
9441
9795
|
try {
|
|
9442
|
-
const content =
|
|
9796
|
+
const content = readFileSync22(envPath, "utf-8");
|
|
9443
9797
|
const dirLine = content.split("\n").find(
|
|
9444
9798
|
(l) => l.trim().startsWith("PROJECT_DIR=") && !l.trim().startsWith("#")
|
|
9445
9799
|
);
|
|
@@ -9672,7 +10026,7 @@ function checkProjectDirEnv(factoryDir) {
|
|
|
9672
10026
|
}
|
|
9673
10027
|
let content;
|
|
9674
10028
|
try {
|
|
9675
|
-
content =
|
|
10029
|
+
content = readFileSync22(envPath, "utf-8");
|
|
9676
10030
|
} catch {
|
|
9677
10031
|
return {
|
|
9678
10032
|
label: "PROJECT_DIR (.env)",
|
|
@@ -10046,7 +10400,7 @@ function checkProjectDirectory(factoryDir) {
|
|
|
10046
10400
|
let anyFail = false;
|
|
10047
10401
|
for (const file of projectFiles) {
|
|
10048
10402
|
try {
|
|
10049
|
-
const proj = JSON.parse(
|
|
10403
|
+
const proj = JSON.parse(readFileSync22(join23(projectsDir, file), "utf-8"));
|
|
10050
10404
|
const projPath = proj.path || "";
|
|
10051
10405
|
const projName = proj.name || file.replace(".json", "");
|
|
10052
10406
|
if (!projPath || !existsSync25(projPath)) {
|
|
@@ -10130,7 +10484,7 @@ function checkStack(factoryDir) {
|
|
|
10130
10484
|
const stacks = [];
|
|
10131
10485
|
for (const file of projectFiles) {
|
|
10132
10486
|
try {
|
|
10133
|
-
const proj = JSON.parse(
|
|
10487
|
+
const proj = JSON.parse(readFileSync22(join23(projectsDir, file), "utf-8"));
|
|
10134
10488
|
const name = proj.name || file.replace(".json", "");
|
|
10135
10489
|
const detected = proj.stack?.detected || "unknown";
|
|
10136
10490
|
const build = proj.stack?.build_command || "";
|
|
@@ -10179,7 +10533,7 @@ function checkBoardPassword(env, factoryDir) {
|
|
|
10179
10533
|
for (const filePath of [dotEnv, secretsEnv]) {
|
|
10180
10534
|
if (existsSync25(filePath)) {
|
|
10181
10535
|
try {
|
|
10182
|
-
const content =
|
|
10536
|
+
const content = readFileSync22(filePath, "utf-8");
|
|
10183
10537
|
const lines = content.split("\n");
|
|
10184
10538
|
const line = lines.find((l) => l.startsWith("BEASTMODE_UI_PASSWORD="));
|
|
10185
10539
|
if (line) {
|
|
@@ -10206,7 +10560,7 @@ function doctorAction(factoryDir, env) {
|
|
|
10206
10560
|
let factoryIdentity = null;
|
|
10207
10561
|
if (factoryDirExists) {
|
|
10208
10562
|
try {
|
|
10209
|
-
const raw = JSON.parse(
|
|
10563
|
+
const raw = JSON.parse(readFileSync22(join23(bmDir, "factory.json"), "utf-8"));
|
|
10210
10564
|
factoryIdentity = FactoryIdentitySchema.parse(raw);
|
|
10211
10565
|
} catch {
|
|
10212
10566
|
}
|
|
@@ -10217,7 +10571,7 @@ function doctorAction(factoryDir, env) {
|
|
|
10217
10571
|
const configPath = join23(bmDir, "config.json");
|
|
10218
10572
|
if (existsSync25(configPath)) {
|
|
10219
10573
|
try {
|
|
10220
|
-
const raw = JSON.parse(
|
|
10574
|
+
const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
|
|
10221
10575
|
const result = FactoryConfigSchema.safeParse(raw);
|
|
10222
10576
|
if (result.success) {
|
|
10223
10577
|
config = raw;
|
|
@@ -10238,7 +10592,7 @@ function doctorAction(factoryDir, env) {
|
|
|
10238
10592
|
for (const file of readdirSync10(projectsDir)) {
|
|
10239
10593
|
if (file.endsWith(".json")) {
|
|
10240
10594
|
try {
|
|
10241
|
-
const proj = JSON.parse(
|
|
10595
|
+
const proj = JSON.parse(readFileSync22(join23(projectsDir, file), "utf-8"));
|
|
10242
10596
|
if (proj.path) {
|
|
10243
10597
|
projectPaths.push({
|
|
10244
10598
|
name: proj.name || file.replace(".json", ""),
|
|
@@ -10260,7 +10614,7 @@ function doctorAction(factoryDir, env) {
|
|
|
10260
10614
|
const manifestPath = join23(pluginsDir, pluginName, "manifest.json");
|
|
10261
10615
|
if (existsSync25(manifestPath)) {
|
|
10262
10616
|
try {
|
|
10263
|
-
const manifest = JSON.parse(
|
|
10617
|
+
const manifest = JSON.parse(readFileSync22(manifestPath, "utf-8"));
|
|
10264
10618
|
installedPlugins.push({
|
|
10265
10619
|
name: pluginName,
|
|
10266
10620
|
engine_version: manifest.engine_version || "*"
|
|
@@ -10284,10 +10638,325 @@ function doctorAction(factoryDir, env) {
|
|
|
10284
10638
|
};
|
|
10285
10639
|
return runDiagnostics(input);
|
|
10286
10640
|
}
|
|
10641
|
+
var SYSTEMD_CREDS_TIMER = "beastmode-claude-creds.timer";
|
|
10642
|
+
var LAUNCH_AGENT_LABEL2 = "com.develeap.beastmode.claude-creds";
|
|
10643
|
+
function checkCredentialSyncAgent() {
|
|
10644
|
+
const results = [];
|
|
10645
|
+
const credsPath = join23(homedir3(), ".claude", ".credentials.json");
|
|
10646
|
+
if (!existsSync25(credsPath)) {
|
|
10647
|
+
results.push({
|
|
10648
|
+
label: "Claude credentials file",
|
|
10649
|
+
status: "fail",
|
|
10650
|
+
detail: "~/.claude/.credentials.json not found",
|
|
10651
|
+
fix: platform2() === "darwin" ? "Run `claude login` then `beastmode sync-claude-creds --install`" : "Run `claude login`"
|
|
10652
|
+
});
|
|
10653
|
+
return results;
|
|
10654
|
+
}
|
|
10655
|
+
results.push({
|
|
10656
|
+
label: "Claude credentials file",
|
|
10657
|
+
status: "pass",
|
|
10658
|
+
detail: credsPath
|
|
10659
|
+
});
|
|
10660
|
+
try {
|
|
10661
|
+
const creds = JSON.parse(readFileSync22(credsPath, "utf-8"));
|
|
10662
|
+
const expiresAt = creds?.claudeAiOauth?.expiresAt;
|
|
10663
|
+
if (typeof expiresAt === "number") {
|
|
10664
|
+
const hoursLeft = Math.round((expiresAt - Date.now()) / 36e5);
|
|
10665
|
+
if (hoursLeft < 0) {
|
|
10666
|
+
results.push({
|
|
10667
|
+
label: "Claude token expiry",
|
|
10668
|
+
status: "fail",
|
|
10669
|
+
detail: `Token expired ${Math.abs(hoursLeft)}h ago`,
|
|
10670
|
+
fix: "Run `claude login` (then `beastmode sync-claude-creds` on macOS)"
|
|
10671
|
+
});
|
|
10672
|
+
} else if (hoursLeft < 2) {
|
|
10673
|
+
results.push({
|
|
10674
|
+
label: "Claude token expiry",
|
|
10675
|
+
status: "warn",
|
|
10676
|
+
detail: `Token expires in ${hoursLeft}h`,
|
|
10677
|
+
fix: "Token will auto-refresh if the sync agent is running"
|
|
10678
|
+
});
|
|
10679
|
+
} else {
|
|
10680
|
+
results.push({
|
|
10681
|
+
label: "Claude token expiry",
|
|
10682
|
+
status: "pass",
|
|
10683
|
+
detail: `Token valid (${hoursLeft}h remaining)`
|
|
10684
|
+
});
|
|
10685
|
+
}
|
|
10686
|
+
} else {
|
|
10687
|
+
results.push({
|
|
10688
|
+
label: "Claude token expiry",
|
|
10689
|
+
status: "warn",
|
|
10690
|
+
detail: "No expiry timestamp in credentials \u2014 cannot check freshness"
|
|
10691
|
+
});
|
|
10692
|
+
}
|
|
10693
|
+
} catch {
|
|
10694
|
+
results.push({
|
|
10695
|
+
label: "Claude token expiry",
|
|
10696
|
+
status: "warn",
|
|
10697
|
+
detail: "credentials file exists but could not be parsed"
|
|
10698
|
+
});
|
|
10699
|
+
}
|
|
10700
|
+
if (platform2() === "darwin") {
|
|
10701
|
+
const uid = process.getuid?.();
|
|
10702
|
+
const result = spawnSync3(
|
|
10703
|
+
"launchctl",
|
|
10704
|
+
["print", `gui/${uid}/${LAUNCH_AGENT_LABEL2}`],
|
|
10705
|
+
{ stdio: "pipe", encoding: "utf-8" }
|
|
10706
|
+
);
|
|
10707
|
+
if (result.status === 0) {
|
|
10708
|
+
results.push({
|
|
10709
|
+
label: "Credential sync agent",
|
|
10710
|
+
status: "pass",
|
|
10711
|
+
detail: "launchd LaunchAgent loaded"
|
|
10712
|
+
});
|
|
10713
|
+
} else {
|
|
10714
|
+
results.push({
|
|
10715
|
+
label: "Credential sync agent",
|
|
10716
|
+
status: "warn",
|
|
10717
|
+
detail: "launchd LaunchAgent not loaded",
|
|
10718
|
+
fix: "Run `beastmode sync-claude-creds --install`"
|
|
10719
|
+
});
|
|
10720
|
+
}
|
|
10721
|
+
} else if (platform2() === "linux") {
|
|
10722
|
+
const result = spawnSync3(
|
|
10723
|
+
"systemctl",
|
|
10724
|
+
["--user", "is-active", SYSTEMD_CREDS_TIMER],
|
|
10725
|
+
{ stdio: "pipe", encoding: "utf-8" }
|
|
10726
|
+
);
|
|
10727
|
+
if ((result.stdout || "").trim() === "active") {
|
|
10728
|
+
results.push({
|
|
10729
|
+
label: "Credential sync agent",
|
|
10730
|
+
status: "pass",
|
|
10731
|
+
detail: `${SYSTEMD_CREDS_TIMER} active`
|
|
10732
|
+
});
|
|
10733
|
+
} else {
|
|
10734
|
+
results.push({
|
|
10735
|
+
label: "Credential sync agent",
|
|
10736
|
+
status: "warn",
|
|
10737
|
+
detail: `${SYSTEMD_CREDS_TIMER} not active`,
|
|
10738
|
+
fix: "Run `beastmode sync-claude-creds --install`"
|
|
10739
|
+
});
|
|
10740
|
+
}
|
|
10741
|
+
}
|
|
10742
|
+
return results;
|
|
10743
|
+
}
|
|
10744
|
+
async function checkDaemonCredentialHealth() {
|
|
10745
|
+
const ports = [8420, 8080];
|
|
10746
|
+
let parsed = null;
|
|
10747
|
+
let lastStatus = null;
|
|
10748
|
+
for (const port of ports) {
|
|
10749
|
+
const data2 = await new Promise(
|
|
10750
|
+
(resolveProm) => {
|
|
10751
|
+
const req = http3.get(
|
|
10752
|
+
{
|
|
10753
|
+
host: "127.0.0.1",
|
|
10754
|
+
port,
|
|
10755
|
+
path: "/api/credentials/status",
|
|
10756
|
+
timeout: 5e3
|
|
10757
|
+
},
|
|
10758
|
+
(res) => {
|
|
10759
|
+
let body = "";
|
|
10760
|
+
res.on("data", (chunk) => {
|
|
10761
|
+
body += chunk;
|
|
10762
|
+
});
|
|
10763
|
+
res.on("end", () => {
|
|
10764
|
+
resolveProm({ status: res.statusCode ?? null, body });
|
|
10765
|
+
});
|
|
10766
|
+
}
|
|
10767
|
+
);
|
|
10768
|
+
req.on("error", () => resolveProm({ status: null, body: "" }));
|
|
10769
|
+
req.on("timeout", () => {
|
|
10770
|
+
req.destroy();
|
|
10771
|
+
resolveProm({ status: null, body: "" });
|
|
10772
|
+
});
|
|
10773
|
+
}
|
|
10774
|
+
);
|
|
10775
|
+
if (data2.status !== null) {
|
|
10776
|
+
lastStatus = data2.status;
|
|
10777
|
+
if (data2.status >= 200 && data2.status < 300) {
|
|
10778
|
+
try {
|
|
10779
|
+
parsed = JSON.parse(data2.body);
|
|
10780
|
+
} catch {
|
|
10781
|
+
parsed = null;
|
|
10782
|
+
}
|
|
10783
|
+
break;
|
|
10784
|
+
}
|
|
10785
|
+
}
|
|
10786
|
+
}
|
|
10787
|
+
if (parsed === null) {
|
|
10788
|
+
return [
|
|
10789
|
+
{
|
|
10790
|
+
label: "Daemon credential broker",
|
|
10791
|
+
status: "info",
|
|
10792
|
+
detail: lastStatus === null ? "Could not reach board API \u2014 Docker Compose may not be running (skipped)" : `Board API returned HTTP ${lastStatus}`
|
|
10793
|
+
}
|
|
10794
|
+
];
|
|
10795
|
+
}
|
|
10796
|
+
const data = parsed;
|
|
10797
|
+
if (data.healthy === null) {
|
|
10798
|
+
return [
|
|
10799
|
+
{
|
|
10800
|
+
label: "Daemon credential broker",
|
|
10801
|
+
status: "warn",
|
|
10802
|
+
detail: "No credential status reported \u2014 daemon may not be running"
|
|
10803
|
+
}
|
|
10804
|
+
];
|
|
10805
|
+
}
|
|
10806
|
+
if (data.stale === true) {
|
|
10807
|
+
return [
|
|
10808
|
+
{
|
|
10809
|
+
label: "Daemon credential broker",
|
|
10810
|
+
status: "warn",
|
|
10811
|
+
detail: `Last status report is stale (reported at ${data.reported_at})`
|
|
10812
|
+
}
|
|
10813
|
+
];
|
|
10814
|
+
}
|
|
10815
|
+
if (data.degraded === true) {
|
|
10816
|
+
return [
|
|
10817
|
+
{
|
|
10818
|
+
label: "Daemon credential broker",
|
|
10819
|
+
status: "fail",
|
|
10820
|
+
detail: `Broker DEGRADED \u2014 ${data.consecutive_failures ?? "?"} consecutive auth failures`,
|
|
10821
|
+
fix: "Run `claude login` then `beastmode sync-claude-creds`"
|
|
10822
|
+
}
|
|
10823
|
+
];
|
|
10824
|
+
}
|
|
10825
|
+
if (data.token_expired === true) {
|
|
10826
|
+
return [
|
|
10827
|
+
{
|
|
10828
|
+
label: "Daemon credential broker",
|
|
10829
|
+
status: "fail",
|
|
10830
|
+
detail: "Broker reports token is expired",
|
|
10831
|
+
fix: "Run `claude login` (then `beastmode sync-claude-creds` on macOS)"
|
|
10832
|
+
}
|
|
10833
|
+
];
|
|
10834
|
+
}
|
|
10835
|
+
return [
|
|
10836
|
+
{
|
|
10837
|
+
label: "Daemon credential broker",
|
|
10838
|
+
status: "pass",
|
|
10839
|
+
detail: `Healthy \u2014 token expires ${data.token_expires_at ?? "unknown"}`
|
|
10840
|
+
}
|
|
10841
|
+
];
|
|
10842
|
+
}
|
|
10843
|
+
async function checkDaemonCredHealth() {
|
|
10844
|
+
const ports = [8420, 8080];
|
|
10845
|
+
let parsed = null;
|
|
10846
|
+
let lastStatus = null;
|
|
10847
|
+
for (const port of ports) {
|
|
10848
|
+
const data2 = await new Promise(
|
|
10849
|
+
(resolveProm) => {
|
|
10850
|
+
const req = http3.get(
|
|
10851
|
+
{
|
|
10852
|
+
host: "127.0.0.1",
|
|
10853
|
+
port,
|
|
10854
|
+
path: "/api/daemon/cred-health",
|
|
10855
|
+
timeout: 5e3
|
|
10856
|
+
},
|
|
10857
|
+
(res) => {
|
|
10858
|
+
let body = "";
|
|
10859
|
+
res.on("data", (chunk) => {
|
|
10860
|
+
body += chunk;
|
|
10861
|
+
});
|
|
10862
|
+
res.on("end", () => {
|
|
10863
|
+
resolveProm({ status: res.statusCode ?? null, body });
|
|
10864
|
+
});
|
|
10865
|
+
}
|
|
10866
|
+
);
|
|
10867
|
+
req.on("error", () => resolveProm({ status: null, body: "" }));
|
|
10868
|
+
req.on("timeout", () => {
|
|
10869
|
+
req.destroy();
|
|
10870
|
+
resolveProm({ status: null, body: "" });
|
|
10871
|
+
});
|
|
10872
|
+
}
|
|
10873
|
+
);
|
|
10874
|
+
if (data2.status !== null) {
|
|
10875
|
+
lastStatus = data2.status;
|
|
10876
|
+
try {
|
|
10877
|
+
parsed = JSON.parse(data2.body);
|
|
10878
|
+
} catch {
|
|
10879
|
+
parsed = null;
|
|
10880
|
+
}
|
|
10881
|
+
break;
|
|
10882
|
+
}
|
|
10883
|
+
}
|
|
10884
|
+
if (lastStatus === null) {
|
|
10885
|
+
return [
|
|
10886
|
+
{
|
|
10887
|
+
label: "Daemon cred-health endpoint",
|
|
10888
|
+
status: "info",
|
|
10889
|
+
detail: "Board API not reachable \u2014 Docker Compose may not be running (skipped)"
|
|
10890
|
+
}
|
|
10891
|
+
];
|
|
10892
|
+
}
|
|
10893
|
+
if (lastStatus === 503) {
|
|
10894
|
+
const detail = parsed && typeof parsed.detail === "string" ? String(parsed.detail) : "Daemon hasn't reported credential state yet";
|
|
10895
|
+
return [
|
|
10896
|
+
{
|
|
10897
|
+
label: "Daemon cred-health endpoint",
|
|
10898
|
+
status: "warn",
|
|
10899
|
+
detail
|
|
10900
|
+
}
|
|
10901
|
+
];
|
|
10902
|
+
}
|
|
10903
|
+
if (lastStatus !== 200 || parsed === null) {
|
|
10904
|
+
return [
|
|
10905
|
+
{
|
|
10906
|
+
label: "Daemon cred-health endpoint",
|
|
10907
|
+
status: "warn",
|
|
10908
|
+
detail: `Unexpected HTTP ${lastStatus} from /api/daemon/cred-health`
|
|
10909
|
+
}
|
|
10910
|
+
];
|
|
10911
|
+
}
|
|
10912
|
+
const data = parsed;
|
|
10913
|
+
const status = String(data.status ?? "unknown");
|
|
10914
|
+
const urgent = data.urgency_marker_present === true;
|
|
10915
|
+
if (status === "failed") {
|
|
10916
|
+
return [
|
|
10917
|
+
{
|
|
10918
|
+
label: "Daemon cred-health endpoint",
|
|
10919
|
+
status: "fail",
|
|
10920
|
+
detail: `Broker FAILED${urgent ? " (urgency marker present)" : ""}`,
|
|
10921
|
+
fix: "Run `claude login` then `beastmode sync-claude-creds`"
|
|
10922
|
+
}
|
|
10923
|
+
];
|
|
10924
|
+
}
|
|
10925
|
+
if (status === "degraded") {
|
|
10926
|
+
return [
|
|
10927
|
+
{
|
|
10928
|
+
label: "Daemon cred-health endpoint",
|
|
10929
|
+
status: "warn",
|
|
10930
|
+
detail: `Broker degraded${urgent ? " \u2014 urgency marker present" : ""}`,
|
|
10931
|
+
fix: "Ensure the credential sync agent is running on the host"
|
|
10932
|
+
}
|
|
10933
|
+
];
|
|
10934
|
+
}
|
|
10935
|
+
if (status === "healthy") {
|
|
10936
|
+
const hours = data.token_hours_remaining;
|
|
10937
|
+
const detail = typeof hours === "number" ? `Healthy \u2014 token has ${hours.toFixed(1)}h remaining` : "Healthy";
|
|
10938
|
+
return [
|
|
10939
|
+
{
|
|
10940
|
+
label: "Daemon cred-health endpoint",
|
|
10941
|
+
status: "pass",
|
|
10942
|
+
detail
|
|
10943
|
+
}
|
|
10944
|
+
];
|
|
10945
|
+
}
|
|
10946
|
+
return [
|
|
10947
|
+
{
|
|
10948
|
+
label: "Daemon cred-health endpoint",
|
|
10949
|
+
status: "warn",
|
|
10950
|
+
detail: `Unknown broker status: ${status}`
|
|
10951
|
+
}
|
|
10952
|
+
];
|
|
10953
|
+
}
|
|
10287
10954
|
function printCheck(check) {
|
|
10288
10955
|
const labelPad = check.label.padEnd(22);
|
|
10289
10956
|
if (check.status === "pass") {
|
|
10290
10957
|
success(`${labelPad} ${check.detail}`);
|
|
10958
|
+
} else if (check.status === "info") {
|
|
10959
|
+
info(`${labelPad} ${check.detail}`);
|
|
10291
10960
|
} else if (check.status === "warn") {
|
|
10292
10961
|
warn(`${labelPad} ${check.detail}`);
|
|
10293
10962
|
if (check.fix) {
|
|
@@ -10308,6 +10977,9 @@ var doctorCommand = new Command12("doctor").description("Health check \u2014 val
|
|
|
10308
10977
|
const hasFactory = existsSync25(join23(factoryDir, ".beastmode"));
|
|
10309
10978
|
const checks = [];
|
|
10310
10979
|
checks.push(...await checkClaudeAuth(env.ANTHROPIC_API_KEY));
|
|
10980
|
+
checks.push(...checkCredentialSyncAgent());
|
|
10981
|
+
checks.push(...await checkDaemonCredentialHealth());
|
|
10982
|
+
checks.push(...await checkDaemonCredHealth());
|
|
10311
10983
|
checks.push(checkGithubToken(env));
|
|
10312
10984
|
checks.push(await checkProjectGithubToken(env, hasFactory ? factoryDir : null));
|
|
10313
10985
|
checks.push(checkGhcrPullToken(env));
|
|
@@ -10378,11 +11050,11 @@ init_schemas();
|
|
|
10378
11050
|
init_version();
|
|
10379
11051
|
init_display();
|
|
10380
11052
|
import { Command as Command13 } from "commander";
|
|
10381
|
-
import { existsSync as existsSync27, readFileSync as
|
|
11053
|
+
import { existsSync as existsSync27, readFileSync as readFileSync24, writeFileSync as writeFileSync20 } from "fs";
|
|
10382
11054
|
import { join as join25, resolve as resolve16 } from "path";
|
|
10383
11055
|
|
|
10384
11056
|
// src/cli/utils/regenerate.ts
|
|
10385
|
-
import { existsSync as existsSync26, readFileSync as
|
|
11057
|
+
import { existsSync as existsSync26, readFileSync as readFileSync23, writeFileSync as writeFileSync19, copyFileSync } from "fs";
|
|
10386
11058
|
import { join as join24 } from "path";
|
|
10387
11059
|
var RECOGNIZED_KEYS = /* @__PURE__ */ new Set([
|
|
10388
11060
|
"PROJECT_DIR",
|
|
@@ -10509,7 +11181,7 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
|
|
|
10509
11181
|
`docker-compose.yml not found at ${composePath}. This does not look like a beastmode factory \u2014 run 'beastmode init' first.`
|
|
10510
11182
|
);
|
|
10511
11183
|
}
|
|
10512
|
-
const existingEnv =
|
|
11184
|
+
const existingEnv = readFileSync23(envPath, "utf-8");
|
|
10513
11185
|
const values = parseExistingEnv(existingEnv);
|
|
10514
11186
|
const missing = [];
|
|
10515
11187
|
if (!values.projectDir) missing.push("PROJECT_DIR");
|
|
@@ -10527,7 +11199,7 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
|
|
|
10527
11199
|
}
|
|
10528
11200
|
const newEnv = buildNewEnv(values);
|
|
10529
11201
|
const newCompose = generateComposeYaml("latest");
|
|
10530
|
-
const existingCompose =
|
|
11202
|
+
const existingCompose = readFileSync23(composePath, "utf-8");
|
|
10531
11203
|
const envChanged = newEnv !== existingEnv;
|
|
10532
11204
|
const composeChanged = newCompose !== existingCompose;
|
|
10533
11205
|
if (!envChanged && !composeChanged) {
|
|
@@ -10567,14 +11239,14 @@ function readIdentity(factoryDir) {
|
|
|
10567
11239
|
if (!existsSync27(path)) {
|
|
10568
11240
|
throw new Error("No factory.json found. Run beastmode init first.");
|
|
10569
11241
|
}
|
|
10570
|
-
return FactoryIdentitySchema.parse(JSON.parse(
|
|
11242
|
+
return FactoryIdentitySchema.parse(JSON.parse(readFileSync24(path, "utf-8")));
|
|
10571
11243
|
}
|
|
10572
11244
|
function readConfig3(factoryDir) {
|
|
10573
11245
|
const path = join25(factoryDir, ".beastmode", "config.json");
|
|
10574
11246
|
if (!existsSync27(path)) {
|
|
10575
11247
|
return {};
|
|
10576
11248
|
}
|
|
10577
|
-
return JSON.parse(
|
|
11249
|
+
return JSON.parse(readFileSync24(path, "utf-8"));
|
|
10578
11250
|
}
|
|
10579
11251
|
function upgradeCheckAction(factoryDir) {
|
|
10580
11252
|
const identity = readIdentity(factoryDir);
|
|
@@ -10674,12 +11346,15 @@ var upgradeCommand = new Command13("upgrade").description("Upgrade engine versio
|
|
|
10674
11346
|
}
|
|
10675
11347
|
});
|
|
10676
11348
|
|
|
11349
|
+
// src/index.ts
|
|
11350
|
+
init_board();
|
|
11351
|
+
|
|
10677
11352
|
// src/cli/commands/migrate.ts
|
|
10678
11353
|
init_display();
|
|
10679
11354
|
init_migrator();
|
|
10680
11355
|
import { Command as Command14 } from "commander";
|
|
10681
11356
|
import { resolve as resolve17, join as join26 } from "path";
|
|
10682
|
-
import { existsSync as existsSync28, readFileSync as
|
|
11357
|
+
import { existsSync as existsSync28, readFileSync as readFileSync25, mkdirSync as mkdirSync16, writeFileSync as writeFileSync21 } from "fs";
|
|
10683
11358
|
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) => {
|
|
10684
11359
|
try {
|
|
10685
11360
|
await runMigrate(opts);
|
|
@@ -10699,7 +11374,7 @@ async function runMigrate(opts) {
|
|
|
10699
11374
|
}
|
|
10700
11375
|
header("BeastMode Migrate");
|
|
10701
11376
|
info(`Reading daemon config from: ${configPath}`);
|
|
10702
|
-
const configContent =
|
|
11377
|
+
const configContent = readFileSync25(configPath, "utf-8");
|
|
10703
11378
|
const daemonConfig = parseDaemonConfig(configContent);
|
|
10704
11379
|
const runsDir = join26(cwd, "runs");
|
|
10705
11380
|
let runDirs = [];
|
|
@@ -10717,7 +11392,7 @@ async function runMigrate(opts) {
|
|
|
10717
11392
|
const cpPath = join26(runsDir, dir, "checkpoint.json");
|
|
10718
11393
|
if (existsSync28(cpPath)) {
|
|
10719
11394
|
try {
|
|
10720
|
-
const cp = JSON.parse(
|
|
11395
|
+
const cp = JSON.parse(readFileSync25(cpPath, "utf-8"));
|
|
10721
11396
|
checkpoints.set(dir, cp);
|
|
10722
11397
|
} catch {
|
|
10723
11398
|
}
|
|
@@ -10813,12 +11488,13 @@ async function runMigrate(opts) {
|
|
|
10813
11488
|
|
|
10814
11489
|
// src/cli/commands/run.ts
|
|
10815
11490
|
init_display();
|
|
11491
|
+
init_board();
|
|
11492
|
+
init_bridge();
|
|
11493
|
+
init_schemas();
|
|
10816
11494
|
import { Command as Command15 } from "commander";
|
|
10817
11495
|
import { join as join27 } from "path";
|
|
10818
|
-
import { existsSync as existsSync29, readFileSync as
|
|
11496
|
+
import { existsSync as existsSync29, readFileSync as readFileSync26, writeFileSync as writeFileSync22, mkdirSync as mkdirSync17 } from "fs";
|
|
10819
11497
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
10820
|
-
init_bridge();
|
|
10821
|
-
init_schemas();
|
|
10822
11498
|
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) => {
|
|
10823
11499
|
try {
|
|
10824
11500
|
await runPipeline(project, opts);
|
|
@@ -10844,7 +11520,7 @@ async function runPipeline(projectName, opts) {
|
|
|
10844
11520
|
throw new Error("Factory config not found. Run 'beastmode init' first.");
|
|
10845
11521
|
}
|
|
10846
11522
|
const factoryConfig = FactoryConfigSchema.parse(
|
|
10847
|
-
JSON.parse(
|
|
11523
|
+
JSON.parse(readFileSync26(configPath, "utf-8"))
|
|
10848
11524
|
);
|
|
10849
11525
|
let projectConfig = null;
|
|
10850
11526
|
const projectsDir = join27(bmDir, "projects");
|
|
@@ -10861,11 +11537,11 @@ async function runPipeline(projectName, opts) {
|
|
|
10861
11537
|
throw new Error(`Project not found: ${projectName}`);
|
|
10862
11538
|
}
|
|
10863
11539
|
projectConfig = ProjectConfigSchema.parse(
|
|
10864
|
-
JSON.parse(
|
|
11540
|
+
JSON.parse(readFileSync26(join27(projectsDir, file), "utf-8"))
|
|
10865
11541
|
);
|
|
10866
11542
|
} else if (projectFiles.length > 0) {
|
|
10867
11543
|
projectConfig = ProjectConfigSchema.parse(
|
|
10868
|
-
JSON.parse(
|
|
11544
|
+
JSON.parse(readFileSync26(join27(projectsDir, projectFiles[0]), "utf-8"))
|
|
10869
11545
|
);
|
|
10870
11546
|
info(`Using project: ${projectConfig.name}`);
|
|
10871
11547
|
}
|
|
@@ -10874,7 +11550,7 @@ async function runPipeline(projectName, opts) {
|
|
|
10874
11550
|
let boardItems = [];
|
|
10875
11551
|
if (existsSync29(boardPath)) {
|
|
10876
11552
|
try {
|
|
10877
|
-
const raw = JSON.parse(
|
|
11553
|
+
const raw = JSON.parse(readFileSync26(boardPath, "utf-8"));
|
|
10878
11554
|
boardItems = Array.isArray(raw.items) ? raw.items : [];
|
|
10879
11555
|
} catch {
|
|
10880
11556
|
boardItems = [];
|
|
@@ -10948,7 +11624,7 @@ async function runPipeline(projectName, opts) {
|
|
|
10948
11624
|
const startTime = Date.now();
|
|
10949
11625
|
const pollInterval = setInterval(() => {
|
|
10950
11626
|
try {
|
|
10951
|
-
const board = JSON.parse(
|
|
11627
|
+
const board = JSON.parse(readFileSync26(boardPath, "utf-8"));
|
|
10952
11628
|
const items = Array.isArray(board.items) ? board.items : [];
|
|
10953
11629
|
const taskItem = items.find((i) => i.id === taskId);
|
|
10954
11630
|
if (taskItem) {
|
|
@@ -10990,11 +11666,12 @@ async function runPipeline(projectName, opts) {
|
|
|
10990
11666
|
|
|
10991
11667
|
// src/cli/commands/daemon-cmd.ts
|
|
10992
11668
|
init_display();
|
|
10993
|
-
|
|
10994
|
-
import { join as join28 } from "path";
|
|
10995
|
-
import { existsSync as existsSync30, readFileSync as readFileSync26, writeFileSync as writeFileSync23, mkdirSync as mkdirSync18 } from "fs";
|
|
11669
|
+
init_board();
|
|
10996
11670
|
init_bridge();
|
|
10997
11671
|
init_schemas();
|
|
11672
|
+
import { Command as Command16 } from "commander";
|
|
11673
|
+
import { join as join28 } from "path";
|
|
11674
|
+
import { existsSync as existsSync30, readFileSync as readFileSync27, writeFileSync as writeFileSync23, mkdirSync as mkdirSync18 } from "fs";
|
|
10998
11675
|
var daemonCommand = new Command16("daemon").description("Start the BeastMode daemon via bridge").option("--dry-run", "Generate config but don't start daemon").option(
|
|
10999
11676
|
"--log-level <level>",
|
|
11000
11677
|
"Log level (DEBUG, INFO, WARNING, ERROR)",
|
|
@@ -11021,7 +11698,7 @@ async function runDaemon(opts) {
|
|
|
11021
11698
|
throw new Error("Factory config not found. Run 'beastmode init' first.");
|
|
11022
11699
|
}
|
|
11023
11700
|
const factoryConfig = FactoryConfigSchema.parse(
|
|
11024
|
-
JSON.parse(
|
|
11701
|
+
JSON.parse(readFileSync27(configPath, "utf-8"))
|
|
11025
11702
|
);
|
|
11026
11703
|
let projectConfig = null;
|
|
11027
11704
|
const projectsDir = join28(bmDir, "projects");
|
|
@@ -11032,7 +11709,7 @@ async function runDaemon(opts) {
|
|
|
11032
11709
|
);
|
|
11033
11710
|
if (projectFiles.length > 0) {
|
|
11034
11711
|
projectConfig = ProjectConfigSchema.parse(
|
|
11035
|
-
JSON.parse(
|
|
11712
|
+
JSON.parse(readFileSync27(join28(projectsDir, projectFiles[0]), "utf-8"))
|
|
11036
11713
|
);
|
|
11037
11714
|
info(`Using project: ${projectConfig.name}`);
|
|
11038
11715
|
}
|
|
@@ -11135,7 +11812,7 @@ var mcpCommand = new Command17("mcp").description("Start the BeastMode MCP serve
|
|
|
11135
11812
|
init_display();
|
|
11136
11813
|
import { Command as Command18 } from "commander";
|
|
11137
11814
|
import { resolve as resolve19, join as join30 } from "path";
|
|
11138
|
-
import { existsSync as existsSync32, writeFileSync as writeFileSync25, readFileSync as
|
|
11815
|
+
import { existsSync as existsSync32, writeFileSync as writeFileSync25, readFileSync as readFileSync29 } from "fs";
|
|
11139
11816
|
import { execSync as execSync9 } from "child_process";
|
|
11140
11817
|
import { randomBytes } from "crypto";
|
|
11141
11818
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -11225,8 +11902,8 @@ async function runDeploy(opts) {
|
|
|
11225
11902
|
const host = opts.host;
|
|
11226
11903
|
const dotEnv = join30(factoryDir, ".env");
|
|
11227
11904
|
const secretsEnv = join30(bmDir, "secrets.env.local");
|
|
11228
|
-
const envContent = existsSync32(dotEnv) ?
|
|
11229
|
-
const secretsContent = existsSync32(secretsEnv) ?
|
|
11905
|
+
const envContent = existsSync32(dotEnv) ? readFileSync29(dotEnv, "utf-8") : "";
|
|
11906
|
+
const secretsContent = existsSync32(secretsEnv) ? readFileSync29(secretsEnv, "utf-8") : "";
|
|
11230
11907
|
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;
|
|
11231
11908
|
if (!hasPassword && opts.host === "0.0.0.0") {
|
|
11232
11909
|
const generated = randomBytes(18).toString("base64url");
|
|
@@ -11235,7 +11912,7 @@ async function runDeploy(opts) {
|
|
|
11235
11912
|
# Auto-generated board UI password (deploy)
|
|
11236
11913
|
BEASTMODE_UI_PASSWORD=${generated}
|
|
11237
11914
|
`;
|
|
11238
|
-
writeFileSync25(target, (existsSync32(target) ?
|
|
11915
|
+
writeFileSync25(target, (existsSync32(target) ? readFileSync29(target, "utf-8") : "") + append, "utf-8");
|
|
11239
11916
|
info(`Board UI password auto-generated and saved to ${target}`);
|
|
11240
11917
|
success(`Password: ${generated}`);
|
|
11241
11918
|
info("Save this password \u2014 you'll need it to access the board UI.");
|
|
@@ -11561,7 +12238,63 @@ init_sync_claude_creds();
|
|
|
11561
12238
|
|
|
11562
12239
|
// src/cli/commands/up.ts
|
|
11563
12240
|
import { Command as Command19 } from "commander";
|
|
12241
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
11564
12242
|
init_display();
|
|
12243
|
+
var LAUNCH_AGENT_LABEL3 = "com.develeap.beastmode.claude-creds";
|
|
12244
|
+
var SYSTEMD_CREDS_TIMER2 = "beastmode-claude-creds.timer";
|
|
12245
|
+
async function ensureCredWatcher(cwd) {
|
|
12246
|
+
const plat = process.platform;
|
|
12247
|
+
if (plat !== "darwin" && plat !== "linux") return;
|
|
12248
|
+
if (plat === "darwin") {
|
|
12249
|
+
const uid = process.getuid?.();
|
|
12250
|
+
const probe = spawnSync4(
|
|
12251
|
+
"launchctl",
|
|
12252
|
+
["print", `gui/${uid}/${LAUNCH_AGENT_LABEL3}`],
|
|
12253
|
+
{ stdio: "pipe", encoding: "utf-8" }
|
|
12254
|
+
);
|
|
12255
|
+
if (probe.status === 0) return;
|
|
12256
|
+
} else {
|
|
12257
|
+
const probe = spawnSync4(
|
|
12258
|
+
"systemctl",
|
|
12259
|
+
["--user", "is-active", SYSTEMD_CREDS_TIMER2],
|
|
12260
|
+
{ stdio: "pipe", encoding: "utf-8" }
|
|
12261
|
+
);
|
|
12262
|
+
if ((probe.stdout || "").trim() === "active") return;
|
|
12263
|
+
}
|
|
12264
|
+
info("Installing credential sync watcher for Docker daemon...");
|
|
12265
|
+
try {
|
|
12266
|
+
if (plat === "darwin") {
|
|
12267
|
+
const { syncClaudeCredsOnce: syncClaudeCredsOnce2, installAgent: installAgent2 } = await Promise.resolve().then(() => (init_sync_claude_creds(), sync_claude_creds_exports));
|
|
12268
|
+
const sync = syncClaudeCredsOnce2();
|
|
12269
|
+
if ("error" in sync) {
|
|
12270
|
+
warn(`Credential sync skipped: ${sync.error}`);
|
|
12271
|
+
info(
|
|
12272
|
+
" Run `beastmode sync-claude-creds --install` after `claude login`."
|
|
12273
|
+
);
|
|
12274
|
+
return;
|
|
12275
|
+
}
|
|
12276
|
+
success(`Credentials synced: ${sync.path}`);
|
|
12277
|
+
installAgent2(1800, { throwOnError: true, factoryDir: cwd });
|
|
12278
|
+
} else {
|
|
12279
|
+
const { syncClaudeCredsLinux: syncClaudeCredsLinux2, installAgentLinux: installAgentLinux2 } = await Promise.resolve().then(() => (init_sync_claude_creds(), sync_claude_creds_exports));
|
|
12280
|
+
const sync = syncClaudeCredsLinux2();
|
|
12281
|
+
if ("error" in sync) {
|
|
12282
|
+
warn(`Credential sync skipped: ${sync.error}`);
|
|
12283
|
+
info(
|
|
12284
|
+
" Run `beastmode sync-claude-creds --install` after `claude login`."
|
|
12285
|
+
);
|
|
12286
|
+
return;
|
|
12287
|
+
}
|
|
12288
|
+
success(`Credentials valid: ${sync.path}`);
|
|
12289
|
+
installAgentLinux2(1800, { throwOnError: true });
|
|
12290
|
+
}
|
|
12291
|
+
} catch (e) {
|
|
12292
|
+
warn(
|
|
12293
|
+
`Credential watcher install failed: ${e instanceof Error ? e.message : String(e)}`
|
|
12294
|
+
);
|
|
12295
|
+
info(" Service startup continues \u2014 re-try with `beastmode sync-claude-creds --install`.");
|
|
12296
|
+
}
|
|
12297
|
+
}
|
|
11565
12298
|
async function runUp(opts) {
|
|
11566
12299
|
const cwd = opts.cwd ?? process.cwd();
|
|
11567
12300
|
requireComposeFile(cwd);
|
|
@@ -11569,6 +12302,7 @@ async function runUp(opts) {
|
|
|
11569
12302
|
runCompose(["pull"], { cwd, inherit: true });
|
|
11570
12303
|
}
|
|
11571
12304
|
runCompose(["up", "-d"], { cwd, inherit: true });
|
|
12305
|
+
await ensureCredWatcher(cwd);
|
|
11572
12306
|
}
|
|
11573
12307
|
var upCommand = new Command19("up").description("Start BeastMode services").option("--pull", "Pull latest images before starting").action(async (opts) => {
|
|
11574
12308
|
try {
|
|
@@ -11652,13 +12386,13 @@ var logsCommand = new Command21("logs").description("Stream BeastMode service lo
|
|
|
11652
12386
|
|
|
11653
12387
|
// src/cli/commands/update.ts
|
|
11654
12388
|
import { Command as Command22 } from "commander";
|
|
11655
|
-
import { readFileSync as
|
|
12389
|
+
import { readFileSync as readFileSync30, writeFileSync as writeFileSync26 } from "fs";
|
|
11656
12390
|
init_display();
|
|
11657
12391
|
async function runUpdate(opts) {
|
|
11658
12392
|
const cwd = opts.cwd ?? process.cwd();
|
|
11659
12393
|
const composePath = requireComposeFile(cwd);
|
|
11660
12394
|
if (opts.tag) {
|
|
11661
|
-
let content =
|
|
12395
|
+
let content = readFileSync30(composePath, "utf-8");
|
|
11662
12396
|
const tagPattern = new RegExp(
|
|
11663
12397
|
`(${GHCR_IMAGE_PREFIX.replace(/[/]/g, "\\/")}\\/(?:board|daemon|ui)):([\\w.\\-]+)`,
|
|
11664
12398
|
"g"
|
|
@@ -11685,9 +12419,29 @@ var updateCommand = new Command22("update").description("Pull latest BeastMode i
|
|
|
11685
12419
|
}
|
|
11686
12420
|
});
|
|
11687
12421
|
|
|
12422
|
+
// src/cli/commands/runner-cmd.ts
|
|
12423
|
+
init_display();
|
|
12424
|
+
import { Command as Command23 } from "commander";
|
|
12425
|
+
var runnerCommand = new Command23("runner").description("Manage self-hosted GitHub Actions runners");
|
|
12426
|
+
runnerCommand.command("setup").description("Set up a self-hosted GitHub Actions runner").option("--native", "Use native install instead of Docker").action(() => {
|
|
12427
|
+
warn("runner setup is not implemented yet (Story 2)");
|
|
12428
|
+
});
|
|
12429
|
+
runnerCommand.command("status").description("Show runner status (container + GitHub registration)").action(() => {
|
|
12430
|
+
warn("runner status is not implemented yet (Story 3)");
|
|
12431
|
+
});
|
|
12432
|
+
runnerCommand.command("remove").description("Remove the runner (container + GitHub deregistration)").action(() => {
|
|
12433
|
+
warn("runner remove is not implemented yet (Story 3)");
|
|
12434
|
+
});
|
|
12435
|
+
runnerCommand.command("switch-workflows").description("Switch workflow runs-on to self-hosted runner").action(() => {
|
|
12436
|
+
warn("runner switch-workflows is not implemented yet (Story 4)");
|
|
12437
|
+
});
|
|
12438
|
+
runnerCommand.command("restore-workflows").description("Restore workflows to original runs-on values").action(() => {
|
|
12439
|
+
warn("runner restore-workflows is not implemented yet (Story 4)");
|
|
12440
|
+
});
|
|
12441
|
+
|
|
11688
12442
|
// src/index.ts
|
|
11689
12443
|
init_version();
|
|
11690
|
-
var program = new
|
|
12444
|
+
var program = new Command24();
|
|
11691
12445
|
program.name("beastmode").description("BeastMode Dark Factory \u2014 turn intent into verified software").version(ENGINE_VERSION);
|
|
11692
12446
|
program.addCommand(initCommand);
|
|
11693
12447
|
program.addCommand(boardCommand);
|
|
@@ -11711,5 +12465,6 @@ program.addCommand(upCommand);
|
|
|
11711
12465
|
program.addCommand(downCommand);
|
|
11712
12466
|
program.addCommand(logsCommand);
|
|
11713
12467
|
program.addCommand(updateCommand);
|
|
12468
|
+
program.addCommand(runnerCommand);
|
|
11714
12469
|
program.parse();
|
|
11715
12470
|
//# sourceMappingURL=index.js.map
|