@beastmode-develeap/beastmode 0.1.265 → 0.1.266
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 +1492 -1055
- package/dist/index.js.map +1 -1
- package/dist/web/board.html +1 -1
- 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
|
@@ -800,8 +800,8 @@ var init_export_adapter = __esm({
|
|
|
800
800
|
});
|
|
801
801
|
|
|
802
802
|
// src/engine/stack-detector.ts
|
|
803
|
-
import { existsSync, readFileSync } from "fs";
|
|
804
|
-
import { join } from "path";
|
|
803
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
804
|
+
import { join, relative, basename, extname } from "path";
|
|
805
805
|
function readFileSafe(path) {
|
|
806
806
|
try {
|
|
807
807
|
return readFileSync(path, "utf-8");
|
|
@@ -875,11 +875,362 @@ function extractGitRemote(projectDir) {
|
|
|
875
875
|
if (httpsMatch) return httpsMatch[1].replace(/\.git$/, "");
|
|
876
876
|
return null;
|
|
877
877
|
}
|
|
878
|
+
function isDirectory(path) {
|
|
879
|
+
try {
|
|
880
|
+
return statSync(path).isDirectory();
|
|
881
|
+
} catch {
|
|
882
|
+
return false;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
function listDirs(parent) {
|
|
886
|
+
try {
|
|
887
|
+
return readdirSync(parent, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
888
|
+
} catch {
|
|
889
|
+
return [];
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
function hasAnyManifest(dir) {
|
|
893
|
+
return PACKAGE_MANIFESTS.some((m) => existsSync(join(dir, m)));
|
|
894
|
+
}
|
|
895
|
+
function expandGlob(rootDir, pattern) {
|
|
896
|
+
let pat = pattern.replace(/^\.\//, "");
|
|
897
|
+
pat = pat.replace(/\/+$/, "");
|
|
898
|
+
if (!pat) return [];
|
|
899
|
+
const parts = pat.split("/");
|
|
900
|
+
let candidates = [""];
|
|
901
|
+
for (const part of parts) {
|
|
902
|
+
const next = [];
|
|
903
|
+
for (const cand of candidates) {
|
|
904
|
+
const abs = cand ? join(rootDir, cand) : rootDir;
|
|
905
|
+
if (part === "*" || part === "**") {
|
|
906
|
+
const children = listDirs(abs).filter((name) => !IGNORE_DIRS.has(name));
|
|
907
|
+
for (const child of children) {
|
|
908
|
+
next.push(cand ? join(cand, child) : child);
|
|
909
|
+
}
|
|
910
|
+
} else if (part.includes("*")) {
|
|
911
|
+
const regex = new RegExp(
|
|
912
|
+
"^" + part.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$"
|
|
913
|
+
);
|
|
914
|
+
const children = listDirs(abs).filter((name) => regex.test(name));
|
|
915
|
+
for (const child of children) {
|
|
916
|
+
next.push(cand ? join(cand, child) : child);
|
|
917
|
+
}
|
|
918
|
+
} else {
|
|
919
|
+
const candidate = cand ? join(cand, part) : part;
|
|
920
|
+
if (isDirectory(join(rootDir, candidate))) {
|
|
921
|
+
next.push(candidate);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
candidates = next;
|
|
926
|
+
}
|
|
927
|
+
return candidates.filter((c) => hasAnyManifest(join(rootDir, c)));
|
|
928
|
+
}
|
|
929
|
+
function parsePnpmWorkspaceYaml(content) {
|
|
930
|
+
const lines = content.split(/\r?\n/);
|
|
931
|
+
const globs = [];
|
|
932
|
+
let inPackages = false;
|
|
933
|
+
for (const raw of lines) {
|
|
934
|
+
const line = raw.replace(/\s+$/, "");
|
|
935
|
+
if (/^packages\s*:/.test(line)) {
|
|
936
|
+
inPackages = true;
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
if (inPackages) {
|
|
940
|
+
if (line && !/^\s/.test(line) && !line.startsWith("-")) {
|
|
941
|
+
inPackages = false;
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
const m = line.match(/^\s*-\s*['"]?([^'"#]+?)['"]?\s*$/);
|
|
945
|
+
if (m) {
|
|
946
|
+
const val = m[1].trim();
|
|
947
|
+
if (val) globs.push(val);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return globs;
|
|
952
|
+
}
|
|
953
|
+
function parseGoWorkUseBlock(content) {
|
|
954
|
+
const dirs = [];
|
|
955
|
+
const singleLineMatches = content.matchAll(/^\s*use\s+(\S+)\s*$/gm);
|
|
956
|
+
for (const m of singleLineMatches) {
|
|
957
|
+
dirs.push(m[1].replace(/^\.\//, ""));
|
|
958
|
+
}
|
|
959
|
+
const blockMatch = content.match(/use\s*\(([\s\S]*?)\)/);
|
|
960
|
+
if (blockMatch) {
|
|
961
|
+
for (const raw of blockMatch[1].split(/\r?\n/)) {
|
|
962
|
+
const line = raw.trim();
|
|
963
|
+
if (!line || line.startsWith("//")) continue;
|
|
964
|
+
const cleaned = line.replace(/\/\/.*$/, "").trim();
|
|
965
|
+
if (cleaned) dirs.push(cleaned.replace(/^\.\//, ""));
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
return dirs;
|
|
969
|
+
}
|
|
970
|
+
function parseCargoWorkspaceMembers(content) {
|
|
971
|
+
const wsIdx = content.search(/^\s*\[workspace\]/m);
|
|
972
|
+
if (wsIdx < 0) return [];
|
|
973
|
+
const rest = content.slice(wsIdx);
|
|
974
|
+
const m = rest.match(/members\s*=\s*\[([\s\S]*?)\]/);
|
|
975
|
+
if (!m) return [];
|
|
976
|
+
const inner = m[1];
|
|
977
|
+
const members = [];
|
|
978
|
+
for (const raw of inner.split(",")) {
|
|
979
|
+
const cleaned = raw.replace(/#.*$/, "").trim().replace(/^['"]|['"]$/g, "");
|
|
980
|
+
if (cleaned) members.push(cleaned);
|
|
981
|
+
}
|
|
982
|
+
return members;
|
|
983
|
+
}
|
|
984
|
+
function parseGradleInclude(content) {
|
|
985
|
+
const modules = [];
|
|
986
|
+
const re = /include\s*(?:\(\s*)?([^)\n]+?)(?:\s*\))?\s*$/gm;
|
|
987
|
+
let match;
|
|
988
|
+
while ((match = re.exec(content)) !== null) {
|
|
989
|
+
const args = match[1];
|
|
990
|
+
const stringMatches = args.matchAll(/['"]([^'"]+)['"]/g);
|
|
991
|
+
for (const sm of stringMatches) {
|
|
992
|
+
const raw = sm[1].trim();
|
|
993
|
+
const path = raw.replace(/^:+/, "").replace(/:/g, "/");
|
|
994
|
+
if (path) modules.push(path);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
return modules;
|
|
998
|
+
}
|
|
999
|
+
function parseMavenModules(content) {
|
|
1000
|
+
const modules = [];
|
|
1001
|
+
const re = /<module>\s*([^<\s]+)\s*<\/module>/g;
|
|
1002
|
+
let match;
|
|
1003
|
+
while ((match = re.exec(content)) !== null) {
|
|
1004
|
+
modules.push(match[1].trim());
|
|
1005
|
+
}
|
|
1006
|
+
return modules;
|
|
1007
|
+
}
|
|
1008
|
+
function detectWorkspaces(projectDir) {
|
|
1009
|
+
const pnpmYaml = readFileSafe(join(projectDir, "pnpm-workspace.yaml"));
|
|
1010
|
+
if (pnpmYaml !== null) {
|
|
1011
|
+
const globs = parsePnpmWorkspaceYaml(pnpmYaml);
|
|
1012
|
+
const packages = [];
|
|
1013
|
+
for (const g of globs) {
|
|
1014
|
+
packages.push(...expandGlob(projectDir, g));
|
|
1015
|
+
}
|
|
1016
|
+
return { type: "pnpm", packages: dedupe(packages) };
|
|
1017
|
+
}
|
|
1018
|
+
if (existsSync(join(projectDir, "nx.json"))) {
|
|
1019
|
+
const packages = [];
|
|
1020
|
+
for (const parent of ["packages", "apps", "libs"]) {
|
|
1021
|
+
const parentDir = join(projectDir, parent);
|
|
1022
|
+
if (!isDirectory(parentDir)) continue;
|
|
1023
|
+
for (const child of listDirs(parentDir)) {
|
|
1024
|
+
const childDir = join(parentDir, child);
|
|
1025
|
+
if (existsSync(join(childDir, "project.json")) || hasAnyManifest(childDir)) {
|
|
1026
|
+
packages.push(join(parent, child));
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return { type: "nx", packages: dedupe(packages) };
|
|
1031
|
+
}
|
|
1032
|
+
const hasTurbo = existsSync(join(projectDir, "turbo.json"));
|
|
1033
|
+
const pkgContent = readFileSafe(join(projectDir, "package.json"));
|
|
1034
|
+
if (pkgContent) {
|
|
1035
|
+
const pkg = parseJsonSafe(pkgContent);
|
|
1036
|
+
if (pkg) {
|
|
1037
|
+
let workspaces;
|
|
1038
|
+
const ws = pkg.workspaces;
|
|
1039
|
+
if (Array.isArray(ws)) {
|
|
1040
|
+
workspaces = ws.filter((v) => typeof v === "string");
|
|
1041
|
+
} else if (ws && typeof ws === "object" && Array.isArray(ws.packages)) {
|
|
1042
|
+
workspaces = ws.packages.filter(
|
|
1043
|
+
(v) => typeof v === "string"
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
if (workspaces && workspaces.length > 0) {
|
|
1047
|
+
const packages = [];
|
|
1048
|
+
for (const g of workspaces) {
|
|
1049
|
+
packages.push(...expandGlob(projectDir, g));
|
|
1050
|
+
}
|
|
1051
|
+
return {
|
|
1052
|
+
type: hasTurbo ? "turbo" : "npm",
|
|
1053
|
+
packages: dedupe(packages)
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
const goWork = readFileSafe(join(projectDir, "go.work"));
|
|
1059
|
+
if (goWork !== null) {
|
|
1060
|
+
const dirs = parseGoWorkUseBlock(goWork);
|
|
1061
|
+
const packages = dirs.filter(
|
|
1062
|
+
(d) => existsSync(join(projectDir, d, "go.mod"))
|
|
1063
|
+
);
|
|
1064
|
+
return { type: "go", packages: dedupe(packages) };
|
|
1065
|
+
}
|
|
1066
|
+
const cargoToml = readFileSafe(join(projectDir, "Cargo.toml"));
|
|
1067
|
+
if (cargoToml !== null && /^\s*\[workspace\]/m.test(cargoToml)) {
|
|
1068
|
+
const members = parseCargoWorkspaceMembers(cargoToml);
|
|
1069
|
+
const packages = [];
|
|
1070
|
+
for (const m of members) {
|
|
1071
|
+
if (m.includes("*")) {
|
|
1072
|
+
packages.push(...expandGlob(projectDir, m));
|
|
1073
|
+
} else if (existsSync(join(projectDir, m, "Cargo.toml"))) {
|
|
1074
|
+
packages.push(m);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return { type: "cargo", packages: dedupe(packages) };
|
|
1078
|
+
}
|
|
1079
|
+
const gradleSettings = readFileSafe(join(projectDir, "settings.gradle")) ?? readFileSafe(join(projectDir, "settings.gradle.kts"));
|
|
1080
|
+
if (gradleSettings !== null) {
|
|
1081
|
+
const modules = parseGradleInclude(gradleSettings);
|
|
1082
|
+
const packages = modules.filter((m) => isDirectory(join(projectDir, m)));
|
|
1083
|
+
if (packages.length > 0) {
|
|
1084
|
+
return { type: "gradle", packages: dedupe(packages) };
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
const pom = readFileSafe(join(projectDir, "pom.xml"));
|
|
1088
|
+
if (pom !== null) {
|
|
1089
|
+
const modules = parseMavenModules(pom);
|
|
1090
|
+
const packages = modules.filter(
|
|
1091
|
+
(m) => existsSync(join(projectDir, m, "pom.xml"))
|
|
1092
|
+
);
|
|
1093
|
+
if (packages.length > 0) {
|
|
1094
|
+
return { type: "maven", packages: dedupe(packages) };
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
function dedupe(items) {
|
|
1100
|
+
return Array.from(new Set(items));
|
|
1101
|
+
}
|
|
1102
|
+
function extractPackageName(packageDir) {
|
|
1103
|
+
const pkgContent = readFileSafe(join(packageDir, "package.json"));
|
|
1104
|
+
if (pkgContent) {
|
|
1105
|
+
const pkg = parseJsonSafe(pkgContent);
|
|
1106
|
+
if (pkg && typeof pkg.name === "string" && pkg.name) return pkg.name;
|
|
1107
|
+
}
|
|
1108
|
+
const cargoToml = readFileSafe(join(packageDir, "Cargo.toml"));
|
|
1109
|
+
if (cargoToml) {
|
|
1110
|
+
const m = cargoToml.match(/\[package\][\s\S]*?name\s*=\s*['"]([^'"]+)['"]/);
|
|
1111
|
+
if (m) return m[1];
|
|
1112
|
+
}
|
|
1113
|
+
const pom = readFileSafe(join(packageDir, "pom.xml"));
|
|
1114
|
+
if (pom) {
|
|
1115
|
+
const m = pom.match(/<artifactId>\s*([^<\s]+)\s*<\/artifactId>/);
|
|
1116
|
+
if (m) return m[1];
|
|
1117
|
+
}
|
|
1118
|
+
const goMod = readFileSafe(join(packageDir, "go.mod"));
|
|
1119
|
+
if (goMod) {
|
|
1120
|
+
const m = goMod.match(/^module\s+(\S+)/m);
|
|
1121
|
+
if (m) {
|
|
1122
|
+
const parts = m[1].split("/");
|
|
1123
|
+
return parts[parts.length - 1];
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return basename(packageDir);
|
|
1127
|
+
}
|
|
1128
|
+
function detectEntryPoints(packageDir, framework) {
|
|
1129
|
+
const entries = [];
|
|
1130
|
+
const pkgContent = readFileSafe(join(packageDir, "package.json"));
|
|
1131
|
+
if (pkgContent) {
|
|
1132
|
+
const pkg = parseJsonSafe(pkgContent);
|
|
1133
|
+
if (pkg) {
|
|
1134
|
+
if (typeof pkg.main === "string" && pkg.main) entries.push(pkg.main);
|
|
1135
|
+
if (typeof pkg.bin === "string" && pkg.bin) {
|
|
1136
|
+
entries.push(pkg.bin);
|
|
1137
|
+
} else if (pkg.bin && typeof pkg.bin === "object") {
|
|
1138
|
+
for (const v of Object.values(pkg.bin)) {
|
|
1139
|
+
if (typeof v === "string") entries.push(v);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (framework === "go") {
|
|
1145
|
+
const cmdDir = join(packageDir, "cmd");
|
|
1146
|
+
if (isDirectory(cmdDir)) {
|
|
1147
|
+
for (const sub of listDirs(cmdDir)) {
|
|
1148
|
+
entries.push(join("cmd", sub));
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
if (framework === "rust") {
|
|
1153
|
+
if (existsSync(join(packageDir, "src", "main.rs"))) {
|
|
1154
|
+
entries.push("src/main.rs");
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
if (framework === "java-maven" || framework === "java-gradle") {
|
|
1158
|
+
if (isDirectory(join(packageDir, "src", "main", "java"))) {
|
|
1159
|
+
entries.push("src/main/java");
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
return dedupe(entries);
|
|
1163
|
+
}
|
|
1164
|
+
function detectPackageStack(packageDir, rootDir) {
|
|
1165
|
+
const framework = detectFramework(packageDir);
|
|
1166
|
+
const preset = STACK_PRESETS[framework] || STACK_PRESETS.unknown;
|
|
1167
|
+
const pm = ["nextjs", "vite", "react", "node"].includes(framework) ? detectPackageManager(packageDir) : preset.language === "python" ? "pip" : "";
|
|
1168
|
+
const resolved = resolveCommands(preset, pm);
|
|
1169
|
+
const name = extractPackageName(packageDir);
|
|
1170
|
+
const entryPoints = detectEntryPoints(packageDir, framework);
|
|
1171
|
+
const relPath = relative(rootDir, packageDir) || ".";
|
|
1172
|
+
return {
|
|
1173
|
+
relative_path: relPath,
|
|
1174
|
+
name,
|
|
1175
|
+
language: resolved.language,
|
|
1176
|
+
framework,
|
|
1177
|
+
package_manager: pm,
|
|
1178
|
+
build_command: resolved.build,
|
|
1179
|
+
dev_command: resolved.dev,
|
|
1180
|
+
test_command: resolved.test,
|
|
1181
|
+
install_command: resolved.install,
|
|
1182
|
+
dev_port: resolved.port !== 0 ? resolved.port : void 0,
|
|
1183
|
+
entry_points: entryPoints
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
function scanPrimaryLanguages(projectDir) {
|
|
1187
|
+
const counts = {};
|
|
1188
|
+
let total = 0;
|
|
1189
|
+
const MAX_DEPTH = 8;
|
|
1190
|
+
function walk(dir, depth) {
|
|
1191
|
+
if (depth > MAX_DEPTH) return;
|
|
1192
|
+
let entries;
|
|
1193
|
+
try {
|
|
1194
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1195
|
+
} catch {
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
for (const entry of entries) {
|
|
1199
|
+
if (entry.name.startsWith(".") && entry.name !== ".") {
|
|
1200
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
1201
|
+
}
|
|
1202
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
1203
|
+
const full = join(dir, entry.name);
|
|
1204
|
+
if (entry.isDirectory()) {
|
|
1205
|
+
walk(full, depth + 1);
|
|
1206
|
+
} else if (entry.isFile()) {
|
|
1207
|
+
const ext = extname(entry.name).toLowerCase();
|
|
1208
|
+
const lang = EXT_LANG_MAP[ext];
|
|
1209
|
+
if (lang) {
|
|
1210
|
+
counts[lang] = (counts[lang] || 0) + 1;
|
|
1211
|
+
total += 1;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
walk(projectDir, 0);
|
|
1217
|
+
if (total === 0) return [];
|
|
1218
|
+
const ranked = Object.entries(counts).map(([language, count]) => ({ language, loc_share: count / total })).sort((a, b) => b.loc_share - a.loc_share).slice(0, 3);
|
|
1219
|
+
return ranked;
|
|
1220
|
+
}
|
|
878
1221
|
function detectStack(projectDir) {
|
|
879
1222
|
const framework = detectFramework(projectDir);
|
|
880
1223
|
const preset = STACK_PRESETS[framework] || STACK_PRESETS.unknown;
|
|
881
1224
|
const pm = ["nextjs", "vite", "react", "node"].includes(framework) ? detectPackageManager(projectDir) : preset.language === "python" ? "pip" : "";
|
|
882
1225
|
const resolved = resolveCommands(preset, pm);
|
|
1226
|
+
const workspace = detectWorkspaces(projectDir);
|
|
1227
|
+
const primaryLanguages = scanPrimaryLanguages(projectDir);
|
|
1228
|
+
let packages;
|
|
1229
|
+
if (workspace && workspace.packages.length > 0) {
|
|
1230
|
+
packages = workspace.packages.map(
|
|
1231
|
+
(pkgRelPath) => detectPackageStack(join(projectDir, pkgRelPath), projectDir)
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
883
1234
|
return {
|
|
884
1235
|
language: resolved.language,
|
|
885
1236
|
framework,
|
|
@@ -896,13 +1247,54 @@ function detectStack(projectDir) {
|
|
|
896
1247
|
dev_port: resolved.port,
|
|
897
1248
|
suggested_plugins: resolved.plugins,
|
|
898
1249
|
suggested_preset: resolved.preset,
|
|
899
|
-
suggested_deploy: resolved.deploy
|
|
1250
|
+
suggested_deploy: resolved.deploy,
|
|
1251
|
+
is_monorepo: workspace !== null,
|
|
1252
|
+
total_packages: packages ? packages.length : 0,
|
|
1253
|
+
primary_languages: primaryLanguages,
|
|
1254
|
+
packages
|
|
900
1255
|
};
|
|
901
1256
|
}
|
|
902
|
-
var STACK_PRESETS;
|
|
1257
|
+
var IGNORE_DIRS, EXT_LANG_MAP, PACKAGE_MANIFESTS, STACK_PRESETS;
|
|
903
1258
|
var init_stack_detector = __esm({
|
|
904
1259
|
"src/engine/stack-detector.ts"() {
|
|
905
1260
|
"use strict";
|
|
1261
|
+
IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
1262
|
+
"node_modules",
|
|
1263
|
+
".git",
|
|
1264
|
+
"vendor",
|
|
1265
|
+
"target",
|
|
1266
|
+
"dist",
|
|
1267
|
+
"build",
|
|
1268
|
+
"__pycache__",
|
|
1269
|
+
".venv",
|
|
1270
|
+
".next",
|
|
1271
|
+
".turbo",
|
|
1272
|
+
"coverage"
|
|
1273
|
+
]);
|
|
1274
|
+
EXT_LANG_MAP = {
|
|
1275
|
+
".ts": "typescript",
|
|
1276
|
+
".tsx": "typescript",
|
|
1277
|
+
".js": "javascript",
|
|
1278
|
+
".jsx": "javascript",
|
|
1279
|
+
".py": "python",
|
|
1280
|
+
".go": "go",
|
|
1281
|
+
".rs": "rust",
|
|
1282
|
+
".java": "java",
|
|
1283
|
+
".kt": "kotlin",
|
|
1284
|
+
".swift": "swift",
|
|
1285
|
+
".rb": "ruby",
|
|
1286
|
+
".cs": "csharp"
|
|
1287
|
+
};
|
|
1288
|
+
PACKAGE_MANIFESTS = [
|
|
1289
|
+
"package.json",
|
|
1290
|
+
"go.mod",
|
|
1291
|
+
"Cargo.toml",
|
|
1292
|
+
"pom.xml",
|
|
1293
|
+
"build.gradle",
|
|
1294
|
+
"build.gradle.kts",
|
|
1295
|
+
"pyproject.toml",
|
|
1296
|
+
"requirements.txt"
|
|
1297
|
+
];
|
|
906
1298
|
STACK_PRESETS = {
|
|
907
1299
|
nextjs: {
|
|
908
1300
|
language: "typescript",
|
|
@@ -2251,15 +2643,15 @@ var init_plugin_installer = __esm({
|
|
|
2251
2643
|
// src/engine/skill-manager.ts
|
|
2252
2644
|
import {
|
|
2253
2645
|
existsSync as existsSync7,
|
|
2254
|
-
readdirSync,
|
|
2646
|
+
readdirSync as readdirSync2,
|
|
2255
2647
|
cpSync as cpSync2,
|
|
2256
2648
|
rmSync as rmSync2,
|
|
2257
2649
|
readFileSync as readFileSync7,
|
|
2258
2650
|
writeFileSync as writeFileSync5,
|
|
2259
2651
|
mkdirSync as mkdirSync5,
|
|
2260
|
-
statSync
|
|
2652
|
+
statSync as statSync2
|
|
2261
2653
|
} from "fs";
|
|
2262
|
-
import { join as join7, basename } from "path";
|
|
2654
|
+
import { join as join7, basename as basename2 } from "path";
|
|
2263
2655
|
function parseSkillFrontmatter(content) {
|
|
2264
2656
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
2265
2657
|
if (!match) return {};
|
|
@@ -2278,7 +2670,7 @@ function addSkill(factoryDir, sourcePath) {
|
|
|
2278
2670
|
if (!existsSync7(sourcePath)) {
|
|
2279
2671
|
throw new Error(`Skill source path does not exist: ${sourcePath}`);
|
|
2280
2672
|
}
|
|
2281
|
-
const stat =
|
|
2673
|
+
const stat = statSync2(sourcePath);
|
|
2282
2674
|
if (!stat.isDirectory()) {
|
|
2283
2675
|
throw new Error(`Skill source must be a directory: ${sourcePath}`);
|
|
2284
2676
|
}
|
|
@@ -2288,7 +2680,7 @@ function addSkill(factoryDir, sourcePath) {
|
|
|
2288
2680
|
`SKILL.md not found in ${sourcePath}. Every skill must have a SKILL.md with frontmatter.`
|
|
2289
2681
|
);
|
|
2290
2682
|
}
|
|
2291
|
-
const skillName =
|
|
2683
|
+
const skillName = basename2(sourcePath);
|
|
2292
2684
|
const destPath = join7(factoryDir, ".beastmode", "skills", skillName);
|
|
2293
2685
|
if (existsSync7(destPath)) {
|
|
2294
2686
|
throw new Error(
|
|
@@ -2335,7 +2727,7 @@ function listSkills(factoryDir) {
|
|
|
2335
2727
|
const bmDir = join7(factoryDir, ".beastmode");
|
|
2336
2728
|
const customSkillsDir = join7(bmDir, "skills");
|
|
2337
2729
|
if (existsSync7(customSkillsDir)) {
|
|
2338
|
-
for (const entry of
|
|
2730
|
+
for (const entry of readdirSync2(customSkillsDir, { withFileTypes: true })) {
|
|
2339
2731
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
2340
2732
|
const skillMdPath = join7(customSkillsDir, entry.name, "SKILL.md");
|
|
2341
2733
|
if (!existsSync7(skillMdPath)) continue;
|
|
@@ -2352,7 +2744,7 @@ function listSkills(factoryDir) {
|
|
|
2352
2744
|
}
|
|
2353
2745
|
const pluginsDir = join7(bmDir, "plugins");
|
|
2354
2746
|
if (existsSync7(pluginsDir)) {
|
|
2355
|
-
for (const pluginEntry of
|
|
2747
|
+
for (const pluginEntry of readdirSync2(pluginsDir, {
|
|
2356
2748
|
withFileTypes: true
|
|
2357
2749
|
})) {
|
|
2358
2750
|
if (!pluginEntry.isDirectory() || pluginEntry.name.startsWith("."))
|
|
@@ -2363,7 +2755,7 @@ function listSkills(factoryDir) {
|
|
|
2363
2755
|
"skills"
|
|
2364
2756
|
);
|
|
2365
2757
|
if (!existsSync7(pluginSkillsDir)) continue;
|
|
2366
|
-
for (const skillEntry of
|
|
2758
|
+
for (const skillEntry of readdirSync2(pluginSkillsDir, {
|
|
2367
2759
|
withFileTypes: true
|
|
2368
2760
|
})) {
|
|
2369
2761
|
if (!skillEntry.isDirectory() || skillEntry.name.startsWith("."))
|
|
@@ -2808,6 +3200,159 @@ var init_bridge = __esm({
|
|
|
2808
3200
|
}
|
|
2809
3201
|
});
|
|
2810
3202
|
|
|
3203
|
+
// src/engine/project-record.ts
|
|
3204
|
+
import { existsSync as existsSync8, writeFileSync as writeFileSync6, renameSync, readFileSync as readFileSync8, readdirSync as readdirSync3, mkdirSync as mkdirSync6 } from "fs";
|
|
3205
|
+
import { join as join8 } from "path";
|
|
3206
|
+
function detectShape(parsed) {
|
|
3207
|
+
if (parsed.github && typeof parsed.github === "object" && parsed.slots && typeof parsed.slots === "object") {
|
|
3208
|
+
return "canonical";
|
|
3209
|
+
}
|
|
3210
|
+
if (parsed.github && typeof parsed.github === "object") {
|
|
3211
|
+
return "cli-legacy";
|
|
3212
|
+
}
|
|
3213
|
+
return "http-legacy";
|
|
3214
|
+
}
|
|
3215
|
+
function normalizeToCanonical(parsed, name) {
|
|
3216
|
+
const shape = detectShape(parsed);
|
|
3217
|
+
if (shape === "canonical") {
|
|
3218
|
+
return parsed;
|
|
3219
|
+
}
|
|
3220
|
+
if (shape === "cli-legacy") {
|
|
3221
|
+
const github = parsed.github;
|
|
3222
|
+
return {
|
|
3223
|
+
name: parsed.name || name,
|
|
3224
|
+
path: parsed.path || "",
|
|
3225
|
+
github: {
|
|
3226
|
+
repo: github.repo || "",
|
|
3227
|
+
default_branch: github.default_branch || "main"
|
|
3228
|
+
},
|
|
3229
|
+
board: parsed.board || {
|
|
3230
|
+
id: null,
|
|
3231
|
+
url: "http://127.0.0.1:8080",
|
|
3232
|
+
auto_created: true
|
|
3233
|
+
},
|
|
3234
|
+
deploy: parsed.deploy || { verify_port: 3001 },
|
|
3235
|
+
pipeline: parsed.pipeline || {},
|
|
3236
|
+
models: parsed.models || {},
|
|
3237
|
+
slots: { max: null },
|
|
3238
|
+
registered_at: parsed.registered_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
return {
|
|
3242
|
+
name: parsed.name || name,
|
|
3243
|
+
path: parsed.path || "",
|
|
3244
|
+
github: {
|
|
3245
|
+
repo: parsed.repo || "",
|
|
3246
|
+
default_branch: "main"
|
|
3247
|
+
},
|
|
3248
|
+
board: { id: null, url: "http://127.0.0.1:8080", auto_created: true },
|
|
3249
|
+
deploy: { verify_port: 3001 },
|
|
3250
|
+
pipeline: parsed.pipeline || {},
|
|
3251
|
+
models: parsed.models || {},
|
|
3252
|
+
slots: { max: null },
|
|
3253
|
+
registered_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
3256
|
+
function createProjectRecord(input) {
|
|
3257
|
+
return {
|
|
3258
|
+
name: input.name,
|
|
3259
|
+
path: input.resolvedPath,
|
|
3260
|
+
github: {
|
|
3261
|
+
repo: input.gitRemote || "",
|
|
3262
|
+
default_branch: "main"
|
|
3263
|
+
},
|
|
3264
|
+
board: {
|
|
3265
|
+
id: input.boardId ?? null,
|
|
3266
|
+
url: process.env.BEASTMODE_BOARD_URL || "http://127.0.0.1:8080",
|
|
3267
|
+
auto_created: !input.boardId
|
|
3268
|
+
},
|
|
3269
|
+
deploy: { verify_port: input.verifyPort ?? 3001 },
|
|
3270
|
+
pipeline: {},
|
|
3271
|
+
models: {},
|
|
3272
|
+
slots: { max: null },
|
|
3273
|
+
registered_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3274
|
+
};
|
|
3275
|
+
}
|
|
3276
|
+
function writeProjectRecord(projectsDir, name, record) {
|
|
3277
|
+
const dir = join8(projectsDir, name);
|
|
3278
|
+
mkdirSync6(dir, { recursive: true });
|
|
3279
|
+
const filePath = join8(dir, "project.json");
|
|
3280
|
+
const tmpPath = `${filePath}.tmp`;
|
|
3281
|
+
writeFileSync6(tmpPath, JSON.stringify(record, null, 2) + "\n");
|
|
3282
|
+
renameSync(tmpPath, filePath);
|
|
3283
|
+
const extPath = join8(dir, "extensions.json");
|
|
3284
|
+
if (!existsSync8(extPath)) {
|
|
3285
|
+
writeFileSync6(
|
|
3286
|
+
extPath,
|
|
3287
|
+
JSON.stringify(
|
|
3288
|
+
{
|
|
3289
|
+
plugins: { add: [], remove: [] },
|
|
3290
|
+
mcps: { add: {}, remove: [] },
|
|
3291
|
+
skills: { add: [], remove: [] }
|
|
3292
|
+
},
|
|
3293
|
+
null,
|
|
3294
|
+
2
|
|
3295
|
+
) + "\n"
|
|
3296
|
+
);
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
function readProjectRecord(projectsDir, name) {
|
|
3300
|
+
const subdirPath = join8(projectsDir, name, "project.json");
|
|
3301
|
+
const flatPath = join8(projectsDir, `${name}.json`);
|
|
3302
|
+
let filePath;
|
|
3303
|
+
let isFlat = false;
|
|
3304
|
+
if (existsSync8(subdirPath)) {
|
|
3305
|
+
filePath = subdirPath;
|
|
3306
|
+
} else if (existsSync8(flatPath)) {
|
|
3307
|
+
filePath = flatPath;
|
|
3308
|
+
isFlat = true;
|
|
3309
|
+
} else {
|
|
3310
|
+
return null;
|
|
3311
|
+
}
|
|
3312
|
+
let parsed;
|
|
3313
|
+
try {
|
|
3314
|
+
parsed = JSON.parse(readFileSync8(filePath, "utf-8"));
|
|
3315
|
+
} catch {
|
|
3316
|
+
return null;
|
|
3317
|
+
}
|
|
3318
|
+
const shape = detectShape(parsed);
|
|
3319
|
+
const record = normalizeToCanonical(parsed, name);
|
|
3320
|
+
if (shape !== "canonical" || isFlat) {
|
|
3321
|
+
writeProjectRecord(projectsDir, name, record);
|
|
3322
|
+
}
|
|
3323
|
+
return record;
|
|
3324
|
+
}
|
|
3325
|
+
function listProjectRecords(projectsDir) {
|
|
3326
|
+
if (!existsSync8(projectsDir)) return [];
|
|
3327
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3328
|
+
const records = [];
|
|
3329
|
+
for (const entry of readdirSync3(projectsDir)) {
|
|
3330
|
+
if (entry.startsWith(".")) continue;
|
|
3331
|
+
if (existsSync8(join8(projectsDir, entry, "project.json"))) {
|
|
3332
|
+
if (!seen.has(entry)) {
|
|
3333
|
+
seen.add(entry);
|
|
3334
|
+
const record = readProjectRecord(projectsDir, entry);
|
|
3335
|
+
if (record) records.push(record);
|
|
3336
|
+
}
|
|
3337
|
+
continue;
|
|
3338
|
+
}
|
|
3339
|
+
if (entry.endsWith(".json")) {
|
|
3340
|
+
const name = entry.slice(0, -5);
|
|
3341
|
+
if (!seen.has(name)) {
|
|
3342
|
+
seen.add(name);
|
|
3343
|
+
const record = readProjectRecord(projectsDir, name);
|
|
3344
|
+
if (record) records.push(record);
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
return records;
|
|
3349
|
+
}
|
|
3350
|
+
var init_project_record = __esm({
|
|
3351
|
+
"src/engine/project-record.ts"() {
|
|
3352
|
+
"use strict";
|
|
3353
|
+
}
|
|
3354
|
+
});
|
|
3355
|
+
|
|
2811
3356
|
// src/engine/index.ts
|
|
2812
3357
|
var engine_exports = {};
|
|
2813
3358
|
__export(engine_exports, {
|
|
@@ -2851,6 +3396,7 @@ __export(engine_exports, {
|
|
|
2851
3396
|
configGet: () => configGet,
|
|
2852
3397
|
configReset: () => configReset,
|
|
2853
3398
|
configSet: () => configSet,
|
|
3399
|
+
createProjectRecord: () => createProjectRecord,
|
|
2854
3400
|
createSkill: () => createSkill,
|
|
2855
3401
|
detectStack: () => detectStack,
|
|
2856
3402
|
exportMarkdown: () => exportMarkdown,
|
|
@@ -2870,6 +3416,7 @@ __export(engine_exports, {
|
|
|
2870
3416
|
listHooks: () => listHooks,
|
|
2871
3417
|
listMcps: () => listMcps,
|
|
2872
3418
|
listPresets: () => listPresets,
|
|
3419
|
+
listProjectRecords: () => listProjectRecords,
|
|
2873
3420
|
listProviders: () => listProviders,
|
|
2874
3421
|
listSkills: () => listSkills,
|
|
2875
3422
|
mapDaemonToFactory: () => mapDaemonToFactory,
|
|
@@ -2879,6 +3426,7 @@ __export(engine_exports, {
|
|
|
2879
3426
|
parseMethodologyManifest: () => parseMethodologyManifest,
|
|
2880
3427
|
parseTemplate: () => parseTemplate,
|
|
2881
3428
|
performUpgrade: () => performUpgrade,
|
|
3429
|
+
readProjectRecord: () => readProjectRecord,
|
|
2882
3430
|
removeHook: () => removeHook,
|
|
2883
3431
|
removeMcp: () => removeMcp,
|
|
2884
3432
|
removePlugin: () => removePlugin,
|
|
@@ -2895,7 +3443,8 @@ __export(engine_exports, {
|
|
|
2895
3443
|
scaffoldFactory: () => scaffoldFactory,
|
|
2896
3444
|
validateFactory: () => validateFactory,
|
|
2897
3445
|
validateProvider: () => validateProvider,
|
|
2898
|
-
validateSecrets: () => validateSecrets
|
|
3446
|
+
validateSecrets: () => validateSecrets,
|
|
3447
|
+
writeProjectRecord: () => writeProjectRecord
|
|
2899
3448
|
});
|
|
2900
3449
|
var init_engine = __esm({
|
|
2901
3450
|
"src/engine/index.ts"() {
|
|
@@ -2925,25 +3474,26 @@ var init_engine = __esm({
|
|
|
2925
3474
|
init_schemas();
|
|
2926
3475
|
init_migrator();
|
|
2927
3476
|
init_bridge();
|
|
3477
|
+
init_project_record();
|
|
2928
3478
|
}
|
|
2929
3479
|
});
|
|
2930
3480
|
|
|
2931
3481
|
// src/cli/utils/file-writer.ts
|
|
2932
|
-
import { mkdirSync as
|
|
3482
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, appendFileSync, existsSync as existsSync9 } from "fs";
|
|
2933
3483
|
import { dirname as dirname3 } from "path";
|
|
2934
3484
|
function executeFileActions(actions) {
|
|
2935
3485
|
for (const action of actions) {
|
|
2936
3486
|
const dir = dirname3(action.path);
|
|
2937
|
-
|
|
3487
|
+
mkdirSync7(dir, { recursive: true });
|
|
2938
3488
|
switch (action.action) {
|
|
2939
3489
|
case "create":
|
|
2940
|
-
if (
|
|
3490
|
+
if (existsSync9(action.path)) {
|
|
2941
3491
|
throw new Error(`File already exists: ${action.path}`);
|
|
2942
3492
|
}
|
|
2943
|
-
|
|
3493
|
+
writeFileSync7(action.path, action.content, "utf-8");
|
|
2944
3494
|
break;
|
|
2945
3495
|
case "overwrite":
|
|
2946
|
-
|
|
3496
|
+
writeFileSync7(action.path, action.content, "utf-8");
|
|
2947
3497
|
break;
|
|
2948
3498
|
case "append":
|
|
2949
3499
|
appendFileSync(action.path, action.content, "utf-8");
|
|
@@ -2988,7 +3538,7 @@ var init_display = __esm({
|
|
|
2988
3538
|
|
|
2989
3539
|
// src/cli/ui/api-routes.ts
|
|
2990
3540
|
import { resolve as resolve3 } from "path";
|
|
2991
|
-
import { existsSync as
|
|
3541
|
+
import { existsSync as existsSync11, writeFileSync as writeFileSync8 } from "fs";
|
|
2992
3542
|
function getRoutes() {
|
|
2993
3543
|
return [
|
|
2994
3544
|
{
|
|
@@ -3003,7 +3553,7 @@ function getRoutes() {
|
|
|
3003
3553
|
const { path: projectPath } = body;
|
|
3004
3554
|
if (!projectPath) throw new Error("Missing required field: path");
|
|
3005
3555
|
const resolved = resolve3(projectPath);
|
|
3006
|
-
if (!
|
|
3556
|
+
if (!existsSync11(resolved)) throw new Error(`Directory not found: ${resolved}`);
|
|
3007
3557
|
return detectStack(resolved);
|
|
3008
3558
|
}
|
|
3009
3559
|
},
|
|
@@ -3061,7 +3611,7 @@ function getRoutes() {
|
|
|
3061
3611
|
if (!name) throw new Error("Missing required field: name");
|
|
3062
3612
|
if (!config) throw new Error("Missing required field: config");
|
|
3063
3613
|
if (!project) throw new Error("Missing required field: project");
|
|
3064
|
-
if (
|
|
3614
|
+
if (existsSync11(name) && existsSync11(resolve3(name, ".beastmode"))) {
|
|
3065
3615
|
throw new Error(`Factory already exists at ./${name}. Use 'beastmode config' to modify.`);
|
|
3066
3616
|
}
|
|
3067
3617
|
const resolvedConfig = resolveDefaults(config, project.stack);
|
|
@@ -3078,7 +3628,7 @@ function getRoutes() {
|
|
|
3078
3628
|
if (secretLines.length > 0) {
|
|
3079
3629
|
const secretsPath = resolve3(name, ".beastmode", "secrets.env.local");
|
|
3080
3630
|
const secretsContent = "# BeastMode secrets \u2014 DO NOT COMMIT\n" + secretLines.join("\n") + "\n";
|
|
3081
|
-
|
|
3631
|
+
writeFileSync8(secretsPath, secretsContent, "utf-8");
|
|
3082
3632
|
}
|
|
3083
3633
|
}
|
|
3084
3634
|
const fileList = actions.filter((a) => !a.path.endsWith(".gitkeep")).map((a) => a.path.replace(`${name}/`, ""));
|
|
@@ -3118,52 +3668,52 @@ var init_api_routes = __esm({
|
|
|
3118
3668
|
});
|
|
3119
3669
|
|
|
3120
3670
|
// src/cli/ui/archival.ts
|
|
3121
|
-
import { existsSync as
|
|
3122
|
-
import { join as
|
|
3671
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync4, renameSync as renameSync2, readFileSync as readFileSync9, statSync as statSync3, writeFileSync as writeFileSync9, unlinkSync } from "fs";
|
|
3672
|
+
import { join as join10 } from "path";
|
|
3123
3673
|
function archiveOldRuns(runsDir, archiveAfterDays) {
|
|
3124
|
-
if (archiveAfterDays <= 0 || !
|
|
3125
|
-
const archiveDir =
|
|
3674
|
+
if (archiveAfterDays <= 0 || !existsSync12(runsDir)) return { archived: 0 };
|
|
3675
|
+
const archiveDir = join10(runsDir, ".archive");
|
|
3126
3676
|
const cutoff = Date.now() - archiveAfterDays * 24 * 60 * 60 * 1e3;
|
|
3127
3677
|
let archived = 0;
|
|
3128
|
-
for (const entry of
|
|
3678
|
+
for (const entry of readdirSync4(runsDir)) {
|
|
3129
3679
|
if (entry.startsWith(".") || !entry.startsWith("run-")) continue;
|
|
3130
|
-
const runDir =
|
|
3131
|
-
if (
|
|
3132
|
-
const cpPath =
|
|
3133
|
-
if (!
|
|
3680
|
+
const runDir = join10(runsDir, entry);
|
|
3681
|
+
if (existsSync12(join10(runDir, "pinned"))) continue;
|
|
3682
|
+
const cpPath = join10(runDir, "checkpoint.json");
|
|
3683
|
+
if (!existsSync12(cpPath)) continue;
|
|
3134
3684
|
try {
|
|
3135
|
-
const cp = JSON.parse(
|
|
3685
|
+
const cp = JSON.parse(readFileSync9(cpPath, "utf-8"));
|
|
3136
3686
|
const stage = (cp.current_stage || "").toLowerCase();
|
|
3137
3687
|
if (stage !== "done" && stage !== "ship") continue;
|
|
3138
3688
|
} catch {
|
|
3139
3689
|
continue;
|
|
3140
3690
|
}
|
|
3141
3691
|
try {
|
|
3142
|
-
const mtime =
|
|
3692
|
+
const mtime = statSync3(cpPath).mtimeMs;
|
|
3143
3693
|
if (mtime > cutoff) continue;
|
|
3144
3694
|
} catch {
|
|
3145
3695
|
continue;
|
|
3146
3696
|
}
|
|
3147
|
-
|
|
3148
|
-
|
|
3697
|
+
mkdirSync8(archiveDir, { recursive: true });
|
|
3698
|
+
renameSync2(runDir, join10(archiveDir, entry));
|
|
3149
3699
|
archived++;
|
|
3150
3700
|
}
|
|
3151
3701
|
return { archived };
|
|
3152
3702
|
}
|
|
3153
3703
|
function pinRun(runsDir, runId) {
|
|
3154
|
-
const runDir =
|
|
3155
|
-
if (!
|
|
3156
|
-
|
|
3704
|
+
const runDir = join10(runsDir, runId);
|
|
3705
|
+
if (!existsSync12(runDir)) return false;
|
|
3706
|
+
writeFileSync9(join10(runDir, "pinned"), (/* @__PURE__ */ new Date()).toISOString());
|
|
3157
3707
|
return true;
|
|
3158
3708
|
}
|
|
3159
3709
|
function unpinRun(runsDir, runId) {
|
|
3160
|
-
const pinFile =
|
|
3161
|
-
if (!
|
|
3710
|
+
const pinFile = join10(runsDir, runId, "pinned");
|
|
3711
|
+
if (!existsSync12(pinFile)) return false;
|
|
3162
3712
|
unlinkSync(pinFile);
|
|
3163
3713
|
return true;
|
|
3164
3714
|
}
|
|
3165
3715
|
function isRunPinned(runsDir, runId) {
|
|
3166
|
-
return
|
|
3716
|
+
return existsSync12(join10(runsDir, runId, "pinned"));
|
|
3167
3717
|
}
|
|
3168
3718
|
var init_archival = __esm({
|
|
3169
3719
|
"src/cli/ui/archival.ts"() {
|
|
@@ -3188,37 +3738,37 @@ __export(inception_exports, {
|
|
|
3188
3738
|
saveArtifact: () => saveArtifact,
|
|
3189
3739
|
saveInceptionState: () => saveInceptionState
|
|
3190
3740
|
});
|
|
3191
|
-
import { existsSync as
|
|
3192
|
-
import { join as
|
|
3741
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, readFileSync as readFileSync10, readdirSync as readdirSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
3742
|
+
import { join as join11, dirname as dirname4 } from "path";
|
|
3193
3743
|
import { fileURLToPath } from "url";
|
|
3194
3744
|
function getMethodologiesDir() {
|
|
3195
3745
|
const candidates = [
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3746
|
+
join11(dirname4(fileURLToPath(import.meta.url)), "methodologies"),
|
|
3747
|
+
join11(dirname4(fileURLToPath(import.meta.url)), "..", "methodologies"),
|
|
3748
|
+
join11(process.cwd(), "src", "cli", "ui", "methodologies"),
|
|
3749
|
+
join11(process.cwd(), "dist", "methodologies"),
|
|
3750
|
+
join11(process.cwd(), "cli", "src", "cli", "ui", "methodologies"),
|
|
3751
|
+
join11(process.cwd(), "cli", "dist", "methodologies")
|
|
3202
3752
|
];
|
|
3203
3753
|
for (const dir of candidates) {
|
|
3204
|
-
if (
|
|
3754
|
+
if (existsSync13(dir)) return dir;
|
|
3205
3755
|
}
|
|
3206
3756
|
return candidates[0];
|
|
3207
3757
|
}
|
|
3208
3758
|
function getMethodology(id) {
|
|
3209
3759
|
const dir = getMethodologiesDir();
|
|
3210
|
-
const filePath =
|
|
3211
|
-
if (!
|
|
3760
|
+
const filePath = join11(dir, `${id.replace(/_/g, "-")}.json`);
|
|
3761
|
+
if (!existsSync13(filePath)) {
|
|
3212
3762
|
throw new Error(`Methodology not found: ${id}`);
|
|
3213
3763
|
}
|
|
3214
|
-
return JSON.parse(
|
|
3764
|
+
return JSON.parse(readFileSync10(filePath, "utf-8"));
|
|
3215
3765
|
}
|
|
3216
3766
|
function listMethodologies() {
|
|
3217
3767
|
const dir = getMethodologiesDir();
|
|
3218
|
-
if (!
|
|
3219
|
-
return
|
|
3768
|
+
if (!existsSync13(dir)) return [];
|
|
3769
|
+
return readdirSync5(dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
3220
3770
|
try {
|
|
3221
|
-
const m = JSON.parse(
|
|
3771
|
+
const m = JSON.parse(readFileSync10(join11(dir, f), "utf-8"));
|
|
3222
3772
|
return { id: m.id, name: m.name, description: m.description };
|
|
3223
3773
|
} catch {
|
|
3224
3774
|
return null;
|
|
@@ -3226,12 +3776,12 @@ function listMethodologies() {
|
|
|
3226
3776
|
}).filter(Boolean);
|
|
3227
3777
|
}
|
|
3228
3778
|
function getProductDir(factoryDir, productName) {
|
|
3229
|
-
return
|
|
3779
|
+
return join11(factoryDir, ".beastmode", "products", productName);
|
|
3230
3780
|
}
|
|
3231
3781
|
function createInceptionState(factoryDir, opts) {
|
|
3232
3782
|
const methodology = getMethodology(opts.methodology);
|
|
3233
3783
|
const productDir = getProductDir(factoryDir, opts.productName);
|
|
3234
|
-
|
|
3784
|
+
mkdirSync9(productDir, { recursive: true });
|
|
3235
3785
|
const state = {
|
|
3236
3786
|
productName: opts.productName,
|
|
3237
3787
|
idea: opts.idea,
|
|
@@ -3248,21 +3798,21 @@ function createInceptionState(factoryDir, opts) {
|
|
|
3248
3798
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3249
3799
|
projectName: null
|
|
3250
3800
|
};
|
|
3251
|
-
|
|
3801
|
+
writeFileSync10(join11(productDir, "inception.json"), JSON.stringify(state, null, 2) + "\n");
|
|
3252
3802
|
return state;
|
|
3253
3803
|
}
|
|
3254
3804
|
function loadInception(factoryDir, productName) {
|
|
3255
|
-
const filePath =
|
|
3256
|
-
if (!
|
|
3805
|
+
const filePath = join11(getProductDir(factoryDir, productName), "inception.json");
|
|
3806
|
+
if (!existsSync13(filePath)) return null;
|
|
3257
3807
|
try {
|
|
3258
|
-
return JSON.parse(
|
|
3808
|
+
return JSON.parse(readFileSync10(filePath, "utf-8"));
|
|
3259
3809
|
} catch {
|
|
3260
3810
|
return null;
|
|
3261
3811
|
}
|
|
3262
3812
|
}
|
|
3263
3813
|
function saveInceptionState(factoryDir, state) {
|
|
3264
3814
|
const productDir = getProductDir(factoryDir, state.productName);
|
|
3265
|
-
|
|
3815
|
+
writeFileSync10(join11(productDir, "inception.json"), JSON.stringify(state, null, 2) + "\n");
|
|
3266
3816
|
}
|
|
3267
3817
|
function advancePhase(factoryDir, productName) {
|
|
3268
3818
|
const state = loadInception(factoryDir, productName);
|
|
@@ -3282,28 +3832,28 @@ function advancePhase(factoryDir, productName) {
|
|
|
3282
3832
|
}
|
|
3283
3833
|
function saveArtifact(factoryDir, productName, filename, content) {
|
|
3284
3834
|
const productDir = getProductDir(factoryDir, productName);
|
|
3285
|
-
|
|
3286
|
-
const filePath =
|
|
3287
|
-
|
|
3288
|
-
|
|
3835
|
+
mkdirSync9(productDir, { recursive: true });
|
|
3836
|
+
const filePath = join11(productDir, filename);
|
|
3837
|
+
mkdirSync9(dirname4(filePath), { recursive: true });
|
|
3838
|
+
writeFileSync10(filePath, content);
|
|
3289
3839
|
}
|
|
3290
3840
|
function loadArtifact(factoryDir, productName, filename) {
|
|
3291
|
-
const filePath =
|
|
3292
|
-
if (!
|
|
3293
|
-
return
|
|
3841
|
+
const filePath = join11(getProductDir(factoryDir, productName), filename);
|
|
3842
|
+
if (!existsSync13(filePath)) return null;
|
|
3843
|
+
return readFileSync10(filePath, "utf-8");
|
|
3294
3844
|
}
|
|
3295
3845
|
function migrateInceptionToSession(factoryDir, productName) {
|
|
3296
|
-
const productDir =
|
|
3297
|
-
const oldInception =
|
|
3298
|
-
if (!
|
|
3299
|
-
const sessionsDir =
|
|
3300
|
-
if (
|
|
3846
|
+
const productDir = join11(factoryDir, ".beastmode", "products", productName);
|
|
3847
|
+
const oldInception = join11(productDir, "inception.json");
|
|
3848
|
+
if (!existsSync13(oldInception)) return false;
|
|
3849
|
+
const sessionsDir = join11(productDir, "sessions");
|
|
3850
|
+
if (existsSync13(sessionsDir) && readdirSync5(sessionsDir).some((d) => d.startsWith("session-"))) {
|
|
3301
3851
|
return false;
|
|
3302
3852
|
}
|
|
3303
|
-
const sessionDir =
|
|
3304
|
-
|
|
3305
|
-
const content =
|
|
3306
|
-
|
|
3853
|
+
const sessionDir = join11(sessionsDir, "session-001");
|
|
3854
|
+
mkdirSync9(sessionDir, { recursive: true });
|
|
3855
|
+
const content = readFileSync10(oldInception, "utf-8");
|
|
3856
|
+
writeFileSync10(join11(sessionDir, "inception.json"), content);
|
|
3307
3857
|
const artifactFiles = [
|
|
3308
3858
|
"prd.md",
|
|
3309
3859
|
"architecture.md",
|
|
@@ -3316,9 +3866,9 @@ function migrateInceptionToSession(factoryDir, productName) {
|
|
|
3316
3866
|
"project-plan.md"
|
|
3317
3867
|
];
|
|
3318
3868
|
for (const file of artifactFiles) {
|
|
3319
|
-
const src =
|
|
3320
|
-
if (
|
|
3321
|
-
|
|
3869
|
+
const src = join11(productDir, file);
|
|
3870
|
+
if (existsSync13(src)) {
|
|
3871
|
+
writeFileSync10(join11(sessionDir, file), readFileSync10(src, "utf-8"));
|
|
3322
3872
|
unlinkSync2(src);
|
|
3323
3873
|
}
|
|
3324
3874
|
}
|
|
@@ -3326,11 +3876,11 @@ function migrateInceptionToSession(factoryDir, productName) {
|
|
|
3326
3876
|
return true;
|
|
3327
3877
|
}
|
|
3328
3878
|
function listProducts(factoryDir) {
|
|
3329
|
-
const productsDir =
|
|
3330
|
-
if (!
|
|
3331
|
-
return
|
|
3879
|
+
const productsDir = join11(factoryDir, ".beastmode", "products");
|
|
3880
|
+
if (!existsSync13(productsDir)) return [];
|
|
3881
|
+
return readdirSync5(productsDir).filter((d) => existsSync13(join11(productsDir, d, "inception.json"))).map((d) => {
|
|
3332
3882
|
try {
|
|
3333
|
-
const state = JSON.parse(
|
|
3883
|
+
const state = JSON.parse(readFileSync10(join11(productsDir, d, "inception.json"), "utf-8"));
|
|
3334
3884
|
return {
|
|
3335
3885
|
name: state.productName || d,
|
|
3336
3886
|
methodology: state.methodology || "unknown",
|
|
@@ -3453,18 +4003,18 @@ __export(strategy_exports, {
|
|
|
3453
4003
|
saveSessionArtifact: () => saveSessionArtifact,
|
|
3454
4004
|
saveStrategySession: () => saveStrategySession
|
|
3455
4005
|
});
|
|
3456
|
-
import { existsSync as
|
|
3457
|
-
import { join as
|
|
4006
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync10, writeFileSync as writeFileSync11, readFileSync as readFileSync11, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
4007
|
+
import { join as join12 } from "path";
|
|
3458
4008
|
function getProductDir2(factoryDir, projectName) {
|
|
3459
|
-
return
|
|
4009
|
+
return join12(factoryDir, ".beastmode", "products", projectName);
|
|
3460
4010
|
}
|
|
3461
4011
|
function getSessionsDir(factoryDir, projectName) {
|
|
3462
|
-
return
|
|
4012
|
+
return join12(getProductDir2(factoryDir, projectName), "sessions");
|
|
3463
4013
|
}
|
|
3464
4014
|
function nextSessionId(factoryDir, projectName) {
|
|
3465
4015
|
const sessionsDir = getSessionsDir(factoryDir, projectName);
|
|
3466
|
-
if (!
|
|
3467
|
-
const existing =
|
|
4016
|
+
if (!existsSync14(sessionsDir)) return "session-001";
|
|
4017
|
+
const existing = readdirSync6(sessionsDir).filter((d) => d.startsWith("session-")).sort();
|
|
3468
4018
|
if (existing.length === 0) return "session-001";
|
|
3469
4019
|
const lastNum = parseInt(existing[existing.length - 1].replace("session-", ""), 10);
|
|
3470
4020
|
return `session-${String(lastNum + 1).padStart(3, "0")}`;
|
|
@@ -3472,8 +4022,8 @@ function nextSessionId(factoryDir, projectName) {
|
|
|
3472
4022
|
function createStrategySession(factoryDir, projectName, opts) {
|
|
3473
4023
|
const methodology = getMethodology(opts.methodology);
|
|
3474
4024
|
const sessionId = nextSessionId(factoryDir, projectName);
|
|
3475
|
-
const sessionDir =
|
|
3476
|
-
|
|
4025
|
+
const sessionDir = join12(getSessionsDir(factoryDir, projectName), sessionId);
|
|
4026
|
+
mkdirSync10(sessionDir, { recursive: true });
|
|
3477
4027
|
const session = {
|
|
3478
4028
|
sessionId,
|
|
3479
4029
|
name: opts.name,
|
|
@@ -3494,100 +4044,100 @@ function createStrategySession(factoryDir, projectName, opts) {
|
|
|
3494
4044
|
sessionType: opts.sessionType || "free-form",
|
|
3495
4045
|
approach: opts.approach || "auto"
|
|
3496
4046
|
};
|
|
3497
|
-
|
|
4047
|
+
writeFileSync11(join12(sessionDir, "inception.json"), JSON.stringify(session, null, 2) + "\n");
|
|
3498
4048
|
return session;
|
|
3499
4049
|
}
|
|
3500
4050
|
function listStrategySessions(factoryDir, projectName) {
|
|
3501
4051
|
const sessionsDir = getSessionsDir(factoryDir, projectName);
|
|
3502
|
-
if (!
|
|
3503
|
-
return
|
|
3504
|
-
const file =
|
|
3505
|
-
if (!
|
|
4052
|
+
if (!existsSync14(sessionsDir)) return [];
|
|
4053
|
+
return readdirSync6(sessionsDir).filter((d) => d.startsWith("session-")).sort().map((d) => {
|
|
4054
|
+
const file = join12(sessionsDir, d, "inception.json");
|
|
4055
|
+
if (!existsSync14(file)) return null;
|
|
3506
4056
|
try {
|
|
3507
|
-
return JSON.parse(
|
|
4057
|
+
return JSON.parse(readFileSync11(file, "utf-8"));
|
|
3508
4058
|
} catch {
|
|
3509
4059
|
return null;
|
|
3510
4060
|
}
|
|
3511
4061
|
}).filter(Boolean);
|
|
3512
4062
|
}
|
|
3513
4063
|
function loadStrategySession(factoryDir, projectName, sessionId) {
|
|
3514
|
-
const file =
|
|
3515
|
-
if (!
|
|
4064
|
+
const file = join12(getSessionsDir(factoryDir, projectName), sessionId, "inception.json");
|
|
4065
|
+
if (!existsSync14(file)) return null;
|
|
3516
4066
|
try {
|
|
3517
|
-
return JSON.parse(
|
|
4067
|
+
return JSON.parse(readFileSync11(file, "utf-8"));
|
|
3518
4068
|
} catch {
|
|
3519
4069
|
return null;
|
|
3520
4070
|
}
|
|
3521
4071
|
}
|
|
3522
4072
|
function saveStrategySession(factoryDir, projectName, sessionId, session) {
|
|
3523
|
-
const dir =
|
|
3524
|
-
|
|
4073
|
+
const dir = join12(getSessionsDir(factoryDir, projectName), sessionId);
|
|
4074
|
+
mkdirSync10(dir, { recursive: true });
|
|
3525
4075
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3526
|
-
|
|
4076
|
+
writeFileSync11(join12(dir, "inception.json"), JSON.stringify(session, null, 2) + "\n");
|
|
3527
4077
|
}
|
|
3528
4078
|
function saveSessionArtifact(factoryDir, projectName, sessionId, filename, content) {
|
|
3529
|
-
const dir =
|
|
3530
|
-
|
|
3531
|
-
|
|
4079
|
+
const dir = join12(getSessionsDir(factoryDir, projectName), sessionId);
|
|
4080
|
+
mkdirSync10(dir, { recursive: true });
|
|
4081
|
+
writeFileSync11(join12(dir, filename), content);
|
|
3532
4082
|
}
|
|
3533
4083
|
function loadSessionArtifact(factoryDir, projectName, sessionId, filename) {
|
|
3534
|
-
const file =
|
|
3535
|
-
if (!
|
|
3536
|
-
return
|
|
4084
|
+
const file = join12(getSessionsDir(factoryDir, projectName), sessionId, filename);
|
|
4085
|
+
if (!existsSync14(file)) return null;
|
|
4086
|
+
return readFileSync11(file, "utf-8");
|
|
3537
4087
|
}
|
|
3538
4088
|
function buildArtifactIndex(factoryDir, projectName) {
|
|
3539
4089
|
const artifacts = [];
|
|
3540
|
-
const brownfieldPath =
|
|
3541
|
-
if (
|
|
3542
|
-
const content =
|
|
4090
|
+
const brownfieldPath = join12(factoryDir, ".beastmode", "projects", projectName, "brownfield.md");
|
|
4091
|
+
if (existsSync14(brownfieldPath)) {
|
|
4092
|
+
const content = readFileSync11(brownfieldPath, "utf-8");
|
|
3543
4093
|
const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#")) || "";
|
|
3544
4094
|
artifacts.push({
|
|
3545
4095
|
type: "brownfield",
|
|
3546
4096
|
path: brownfieldPath,
|
|
3547
4097
|
summary: firstLine.slice(0, 200),
|
|
3548
|
-
date:
|
|
4098
|
+
date: statSync4(brownfieldPath).mtime.toISOString()
|
|
3549
4099
|
});
|
|
3550
4100
|
}
|
|
3551
4101
|
const sessions = listStrategySessions(factoryDir, projectName);
|
|
3552
4102
|
for (const session of sessions) {
|
|
3553
4103
|
artifacts.push({
|
|
3554
4104
|
type: "strategy_session",
|
|
3555
|
-
path:
|
|
4105
|
+
path: join12(getSessionsDir(factoryDir, projectName), session.sessionId),
|
|
3556
4106
|
summary: `${session.name} (${session.methodology}) \u2014 ${session.status}`,
|
|
3557
4107
|
date: session.createdAt,
|
|
3558
4108
|
sessionId: session.sessionId
|
|
3559
4109
|
});
|
|
3560
4110
|
}
|
|
3561
|
-
const runsDir =
|
|
3562
|
-
if (
|
|
3563
|
-
const runDirs =
|
|
4111
|
+
const runsDir = join12(factoryDir, "runs", projectName);
|
|
4112
|
+
if (existsSync14(runsDir)) {
|
|
4113
|
+
const runDirs = readdirSync6(runsDir).filter((d) => d.startsWith("run-")).sort().reverse().slice(0, 10);
|
|
3564
4114
|
for (const runId of runDirs) {
|
|
3565
|
-
const nlspecPath =
|
|
3566
|
-
if (
|
|
3567
|
-
const content =
|
|
4115
|
+
const nlspecPath = join12(runsDir, runId, "nlspec.md");
|
|
4116
|
+
if (existsSync14(nlspecPath)) {
|
|
4117
|
+
const content = readFileSync11(nlspecPath, "utf-8");
|
|
3568
4118
|
const title = content.split("\n").find((l) => l.startsWith("# ")) || runId;
|
|
3569
4119
|
artifacts.push({
|
|
3570
4120
|
type: "nlspec",
|
|
3571
4121
|
path: nlspecPath,
|
|
3572
4122
|
summary: title.replace("# ", "").slice(0, 200),
|
|
3573
|
-
date:
|
|
4123
|
+
date: statSync4(nlspecPath).mtime.toISOString(),
|
|
3574
4124
|
runId
|
|
3575
4125
|
});
|
|
3576
4126
|
}
|
|
3577
4127
|
}
|
|
3578
4128
|
}
|
|
3579
|
-
const learningsDir =
|
|
3580
|
-
if (
|
|
3581
|
-
const learningFiles =
|
|
4129
|
+
const learningsDir = join12(getProductDir2(factoryDir, projectName), "learnings");
|
|
4130
|
+
if (existsSync14(learningsDir)) {
|
|
4131
|
+
const learningFiles = readdirSync6(learningsDir).filter((f) => f.endsWith(".md") && !f.startsWith("."));
|
|
3582
4132
|
for (const file of learningFiles) {
|
|
3583
|
-
const filePath =
|
|
3584
|
-
const content =
|
|
4133
|
+
const filePath = join12(learningsDir, file);
|
|
4134
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
3585
4135
|
const firstHeading = content.split("\n").find((l) => l.startsWith("## ")) || file;
|
|
3586
4136
|
artifacts.push({
|
|
3587
4137
|
type: "learning",
|
|
3588
4138
|
path: filePath,
|
|
3589
4139
|
summary: firstHeading.replace("## ", "").slice(0, 200),
|
|
3590
|
-
date:
|
|
4140
|
+
date: statSync4(filePath).mtime.toISOString()
|
|
3591
4141
|
});
|
|
3592
4142
|
}
|
|
3593
4143
|
}
|
|
@@ -3612,8 +4162,8 @@ var init_strategy = __esm({
|
|
|
3612
4162
|
// src/cli/ui/chat-handler.ts
|
|
3613
4163
|
import { randomUUID } from "crypto";
|
|
3614
4164
|
import { execSync as execSync2 } from "child_process";
|
|
3615
|
-
import { readFileSync as
|
|
3616
|
-
import { join as
|
|
4165
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync12, existsSync as existsSync15, mkdirSync as mkdirSync11, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
|
|
4166
|
+
import { join as join13 } from "path";
|
|
3617
4167
|
import http from "http";
|
|
3618
4168
|
import { WebSocketServer, WebSocket as WsWebSocket } from "ws";
|
|
3619
4169
|
function getRecentTokenUsage() {
|
|
@@ -3723,19 +4273,19 @@ function getToolDefinitions() {
|
|
|
3723
4273
|
];
|
|
3724
4274
|
}
|
|
3725
4275
|
function readJsonSafe(filePath) {
|
|
3726
|
-
if (!
|
|
4276
|
+
if (!existsSync15(filePath)) return null;
|
|
3727
4277
|
try {
|
|
3728
|
-
return JSON.parse(
|
|
4278
|
+
return JSON.parse(readFileSync12(filePath, "utf-8"));
|
|
3729
4279
|
} catch {
|
|
3730
4280
|
return null;
|
|
3731
4281
|
}
|
|
3732
4282
|
}
|
|
3733
4283
|
function getBoardUrl(factoryPath) {
|
|
3734
4284
|
if (process.env.BEASTMODE_BOARD_URL) return process.env.BEASTMODE_BOARD_URL;
|
|
3735
|
-
const configPath =
|
|
3736
|
-
if (
|
|
4285
|
+
const configPath = join13(factoryPath, ".beastmode", "config.json");
|
|
4286
|
+
if (existsSync15(configPath)) {
|
|
3737
4287
|
try {
|
|
3738
|
-
const config = JSON.parse(
|
|
4288
|
+
const config = JSON.parse(readFileSync12(configPath, "utf-8"));
|
|
3739
4289
|
if (config.task_backend?.config?.url) return config.task_backend.config.url;
|
|
3740
4290
|
} catch {
|
|
3741
4291
|
}
|
|
@@ -3746,19 +4296,19 @@ async function executeTool(toolName, toolInput, factoryPath) {
|
|
|
3746
4296
|
try {
|
|
3747
4297
|
switch (toolName) {
|
|
3748
4298
|
case "factory_status": {
|
|
3749
|
-
const bmDir =
|
|
3750
|
-
const factory = readJsonSafe(
|
|
3751
|
-
const projectsDir =
|
|
4299
|
+
const bmDir = join13(factoryPath, ".beastmode");
|
|
4300
|
+
const factory = readJsonSafe(join13(bmDir, "factory.json"));
|
|
4301
|
+
const projectsDir = join13(bmDir, "projects");
|
|
3752
4302
|
let projects = [];
|
|
3753
|
-
if (
|
|
3754
|
-
projects =
|
|
4303
|
+
if (existsSync15(projectsDir)) {
|
|
4304
|
+
projects = readdirSync7(projectsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
|
|
3755
4305
|
}
|
|
3756
|
-
const runsDir =
|
|
4306
|
+
const runsDir = join13(factoryPath, "runs");
|
|
3757
4307
|
let runs = [];
|
|
3758
|
-
if (
|
|
3759
|
-
runs =
|
|
4308
|
+
if (existsSync15(runsDir)) {
|
|
4309
|
+
runs = readdirSync7(runsDir).filter((d) => {
|
|
3760
4310
|
try {
|
|
3761
|
-
return
|
|
4311
|
+
return statSync5(join13(runsDir, d)).isDirectory();
|
|
3762
4312
|
} catch {
|
|
3763
4313
|
return false;
|
|
3764
4314
|
}
|
|
@@ -3766,10 +4316,10 @@ async function executeTool(toolName, toolInput, factoryPath) {
|
|
|
3766
4316
|
}
|
|
3767
4317
|
let daemonStatus = "stopped";
|
|
3768
4318
|
let daemonPid = null;
|
|
3769
|
-
const pidFile =
|
|
3770
|
-
if (
|
|
4319
|
+
const pidFile = join13(bmDir, "daemon.pid");
|
|
4320
|
+
if (existsSync15(pidFile)) {
|
|
3771
4321
|
try {
|
|
3772
|
-
daemonPid = parseInt(
|
|
4322
|
+
daemonPid = parseInt(readFileSync12(pidFile, "utf-8").trim(), 10);
|
|
3773
4323
|
process.kill(daemonPid, 0);
|
|
3774
4324
|
daemonStatus = "running";
|
|
3775
4325
|
} catch {
|
|
@@ -3817,7 +4367,7 @@ async function executeTool(toolName, toolInput, factoryPath) {
|
|
|
3817
4367
|
}, null, 2);
|
|
3818
4368
|
}
|
|
3819
4369
|
case "factory_config": {
|
|
3820
|
-
const configPath =
|
|
4370
|
+
const configPath = join13(factoryPath, ".beastmode", "config.json");
|
|
3821
4371
|
const config = readJsonSafe(configPath);
|
|
3822
4372
|
return config ? JSON.stringify(config, null, 2) : "No config.json found.";
|
|
3823
4373
|
}
|
|
@@ -3872,12 +4422,12 @@ async function executeTool(toolName, toolInput, factoryPath) {
|
|
|
3872
4422
|
}
|
|
3873
4423
|
}
|
|
3874
4424
|
case "list_runs": {
|
|
3875
|
-
const runsDir =
|
|
3876
|
-
if (!
|
|
3877
|
-
const runDirs =
|
|
4425
|
+
const runsDir = join13(factoryPath, "runs");
|
|
4426
|
+
if (!existsSync15(runsDir)) return "No runs directory.";
|
|
4427
|
+
const runDirs = readdirSync7(runsDir).sort().reverse();
|
|
3878
4428
|
const results = [];
|
|
3879
4429
|
for (const id of runDirs.slice(0, 15)) {
|
|
3880
|
-
const cp = readJsonSafe(
|
|
4430
|
+
const cp = readJsonSafe(join13(runsDir, id, "checkpoint.json"));
|
|
3881
4431
|
if (cp) {
|
|
3882
4432
|
const hist = Array.isArray(cp.satisfaction_history) ? cp.satisfaction_history : [];
|
|
3883
4433
|
results.push({
|
|
@@ -3893,36 +4443,36 @@ async function executeTool(toolName, toolInput, factoryPath) {
|
|
|
3893
4443
|
case "run_detail": {
|
|
3894
4444
|
const runId = toolInput.run_id;
|
|
3895
4445
|
if (runId.includes("..")) return "Invalid run_id.";
|
|
3896
|
-
const runDir =
|
|
3897
|
-
if (!
|
|
3898
|
-
const manifest = readJsonSafe(
|
|
3899
|
-
const checkpoint = readJsonSafe(
|
|
3900
|
-
const iterDir =
|
|
4446
|
+
const runDir = join13(factoryPath, "runs", runId);
|
|
4447
|
+
if (!existsSync15(runDir)) return `Run not found: ${runId}`;
|
|
4448
|
+
const manifest = readJsonSafe(join13(runDir, "manifest.json"));
|
|
4449
|
+
const checkpoint = readJsonSafe(join13(runDir, "checkpoint.json"));
|
|
4450
|
+
const iterDir = join13(runDir, "iterations");
|
|
3901
4451
|
const iterations = [];
|
|
3902
|
-
if (
|
|
3903
|
-
for (const d of
|
|
3904
|
-
const sat = readJsonSafe(
|
|
4452
|
+
if (existsSync15(iterDir)) {
|
|
4453
|
+
for (const d of readdirSync7(iterDir).sort()) {
|
|
4454
|
+
const sat = readJsonSafe(join13(iterDir, d, "satisfaction.json"));
|
|
3905
4455
|
iterations.push({ number: parseInt(d, 10), satisfaction: sat });
|
|
3906
4456
|
}
|
|
3907
4457
|
}
|
|
3908
|
-
const files =
|
|
4458
|
+
const files = readdirSync7(runDir);
|
|
3909
4459
|
return JSON.stringify({ id: runId, files, manifest, checkpoint, iterations }, null, 2);
|
|
3910
4460
|
}
|
|
3911
4461
|
case "read_run_file": {
|
|
3912
4462
|
const runId = toolInput.run_id;
|
|
3913
4463
|
const relPath = toolInput.file_path;
|
|
3914
4464
|
if (runId.includes("..") || relPath.includes("..")) return "Invalid path.";
|
|
3915
|
-
const fullPath =
|
|
3916
|
-
if (!
|
|
3917
|
-
const content =
|
|
4465
|
+
const fullPath = join13(factoryPath, "runs", runId, relPath);
|
|
4466
|
+
if (!existsSync15(fullPath)) return `File not found: runs/${runId}/${relPath}`;
|
|
4467
|
+
const content = readFileSync12(fullPath, "utf-8");
|
|
3918
4468
|
return content.length > 1e4 ? content.slice(0, 1e4) + "\n\n... (truncated at 10KB)" : content;
|
|
3919
4469
|
}
|
|
3920
4470
|
case "list_projects": {
|
|
3921
|
-
const projDir =
|
|
3922
|
-
if (!
|
|
3923
|
-
const projects =
|
|
4471
|
+
const projDir = join13(factoryPath, ".beastmode", "projects");
|
|
4472
|
+
if (!existsSync15(projDir)) return "No projects registered.";
|
|
4473
|
+
const projects = readdirSync7(projDir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
3924
4474
|
try {
|
|
3925
|
-
return JSON.parse(
|
|
4475
|
+
return JSON.parse(readFileSync12(join13(projDir, f), "utf-8"));
|
|
3926
4476
|
} catch {
|
|
3927
4477
|
return null;
|
|
3928
4478
|
}
|
|
@@ -3932,25 +4482,25 @@ async function executeTool(toolName, toolInput, factoryPath) {
|
|
|
3932
4482
|
case "read_file": {
|
|
3933
4483
|
const relPath = toolInput.path;
|
|
3934
4484
|
if (relPath.includes("..")) return "Invalid path.";
|
|
3935
|
-
const fullPath =
|
|
4485
|
+
const fullPath = join13(factoryPath, relPath);
|
|
3936
4486
|
if (!fullPath.startsWith(factoryPath)) return "Path traversal not allowed.";
|
|
3937
|
-
if (!
|
|
4487
|
+
if (!existsSync15(fullPath)) return `File not found: ${relPath}`;
|
|
3938
4488
|
try {
|
|
3939
|
-
const entries =
|
|
4489
|
+
const entries = readdirSync7(fullPath);
|
|
3940
4490
|
return `"${relPath}" is a directory with ${entries.length} entries: ${entries.slice(0, 30).join(", ")}`;
|
|
3941
4491
|
} catch {
|
|
3942
4492
|
}
|
|
3943
|
-
const content =
|
|
4493
|
+
const content = readFileSync12(fullPath, "utf-8");
|
|
3944
4494
|
return content.length > 1e4 ? content.slice(0, 1e4) + "\n\n... (truncated at 10KB)" : content;
|
|
3945
4495
|
}
|
|
3946
4496
|
case "list_directory": {
|
|
3947
4497
|
const relPath = toolInput.path || "";
|
|
3948
4498
|
if (relPath.includes("..")) return "Invalid path.";
|
|
3949
|
-
const fullPath =
|
|
4499
|
+
const fullPath = join13(factoryPath, relPath);
|
|
3950
4500
|
if (!fullPath.startsWith(factoryPath)) return "Path traversal not allowed.";
|
|
3951
|
-
if (!
|
|
4501
|
+
if (!existsSync15(fullPath)) return `Directory not found: ${relPath || "/"}`;
|
|
3952
4502
|
try {
|
|
3953
|
-
return
|
|
4503
|
+
return readdirSync7(fullPath).join("\n");
|
|
3954
4504
|
} catch {
|
|
3955
4505
|
return `Not a directory: ${relPath}`;
|
|
3956
4506
|
}
|
|
@@ -3963,8 +4513,8 @@ async function executeTool(toolName, toolInput, factoryPath) {
|
|
|
3963
4513
|
}
|
|
3964
4514
|
}
|
|
3965
4515
|
function getChatHistoryDir(factoryPath) {
|
|
3966
|
-
const dir =
|
|
3967
|
-
if (!
|
|
4516
|
+
const dir = join13(factoryPath, ".beastmode", "chat-history");
|
|
4517
|
+
if (!existsSync15(dir)) mkdirSync11(dir, { recursive: true });
|
|
3968
4518
|
return dir;
|
|
3969
4519
|
}
|
|
3970
4520
|
function saveConversation(factoryPath, sessionId, messages, scope) {
|
|
@@ -3975,13 +4525,13 @@ function saveConversation(factoryPath, sessionId, messages, scope) {
|
|
|
3975
4525
|
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3976
4526
|
messages
|
|
3977
4527
|
};
|
|
3978
|
-
|
|
4528
|
+
writeFileSync12(join13(dir, `${sessionId}.json`), JSON.stringify(data, null, 2));
|
|
3979
4529
|
}
|
|
3980
4530
|
function listConversations(factoryPath) {
|
|
3981
4531
|
const dir = getChatHistoryDir(factoryPath);
|
|
3982
|
-
return
|
|
4532
|
+
return readdirSync7(dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
3983
4533
|
try {
|
|
3984
|
-
const data = JSON.parse(
|
|
4534
|
+
const data = JSON.parse(readFileSync12(join13(dir, f), "utf-8"));
|
|
3985
4535
|
const msgs = data.messages || [];
|
|
3986
4536
|
const firstUser = msgs.find((m) => m.role === "user");
|
|
3987
4537
|
const preview = typeof firstUser?.content === "string" ? firstUser.content.slice(0, 80) : "";
|
|
@@ -3999,10 +4549,10 @@ function listConversations(factoryPath) {
|
|
|
3999
4549
|
}
|
|
4000
4550
|
function loadConversation(factoryPath, sessionId) {
|
|
4001
4551
|
if (sessionId.includes("..")) return [];
|
|
4002
|
-
const file =
|
|
4003
|
-
if (!
|
|
4552
|
+
const file = join13(getChatHistoryDir(factoryPath), `${sessionId}.json`);
|
|
4553
|
+
if (!existsSync15(file)) return [];
|
|
4004
4554
|
try {
|
|
4005
|
-
const data = JSON.parse(
|
|
4555
|
+
const data = JSON.parse(readFileSync12(file, "utf-8"));
|
|
4006
4556
|
return data.messages || [];
|
|
4007
4557
|
} catch {
|
|
4008
4558
|
return [];
|
|
@@ -4010,7 +4560,7 @@ function loadConversation(factoryPath, sessionId) {
|
|
|
4010
4560
|
}
|
|
4011
4561
|
function buildSystemPrompt(factoryPath) {
|
|
4012
4562
|
let factoryName = "BeastMode Factory";
|
|
4013
|
-
const factoryJson = readJsonSafe(
|
|
4563
|
+
const factoryJson = readJsonSafe(join13(factoryPath, ".beastmode", "factory.json"));
|
|
4014
4564
|
if (factoryJson?.factory_name) factoryName = factoryJson.factory_name;
|
|
4015
4565
|
else if (factoryJson?.name) factoryName = factoryJson.name;
|
|
4016
4566
|
return `You are BeastMode Assistant \u2014 a concise, helpful assistant for the "${factoryName}" factory at ${factoryPath}.
|
|
@@ -4557,8 +5107,8 @@ var init_chat_handler = __esm({
|
|
|
4557
5107
|
});
|
|
4558
5108
|
|
|
4559
5109
|
// src/cli/ui/board-api-routes.ts
|
|
4560
|
-
import { readFileSync as
|
|
4561
|
-
import { join as
|
|
5110
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync13, existsSync as existsSync16, readdirSync as readdirSync8, unlinkSync as unlinkSync3, mkdirSync as mkdirSync12, statSync as statSync6, rmSync as rmSync3 } from "fs";
|
|
5111
|
+
import { join as join14, basename as basename4, resolve as resolve4, dirname as dirname5 } from "path";
|
|
4562
5112
|
import { homedir } from "os";
|
|
4563
5113
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4564
5114
|
import { execSync as execSync3, spawnSync } from "child_process";
|
|
@@ -4570,10 +5120,10 @@ function assertSafeName(name) {
|
|
|
4570
5120
|
}
|
|
4571
5121
|
function getBoardUrl2(factoryDir) {
|
|
4572
5122
|
if (process.env.BEASTMODE_BOARD_URL) return process.env.BEASTMODE_BOARD_URL;
|
|
4573
|
-
const configPath =
|
|
4574
|
-
if (
|
|
5123
|
+
const configPath = join14(factoryDir, ".beastmode", "config.json");
|
|
5124
|
+
if (existsSync16(configPath)) {
|
|
4575
5125
|
try {
|
|
4576
|
-
const config = JSON.parse(
|
|
5126
|
+
const config = JSON.parse(readFileSync13(configPath, "utf-8"));
|
|
4577
5127
|
if (config.task_backend?.config?.url) {
|
|
4578
5128
|
return config.task_backend.config.url;
|
|
4579
5129
|
}
|
|
@@ -4712,9 +5262,9 @@ function scopedQuery(query) {
|
|
|
4712
5262
|
return { board: b };
|
|
4713
5263
|
}
|
|
4714
5264
|
function readJsonFile(filePath) {
|
|
4715
|
-
if (!
|
|
5265
|
+
if (!existsSync16(filePath)) return null;
|
|
4716
5266
|
try {
|
|
4717
|
-
return JSON.parse(
|
|
5267
|
+
return JSON.parse(readFileSync13(filePath, "utf-8"));
|
|
4718
5268
|
} catch {
|
|
4719
5269
|
return null;
|
|
4720
5270
|
}
|
|
@@ -4734,25 +5284,25 @@ function deepMerge2(target, source) {
|
|
|
4734
5284
|
return result;
|
|
4735
5285
|
}
|
|
4736
5286
|
function getRunsDir(factoryDir) {
|
|
4737
|
-
const configPath =
|
|
4738
|
-
if (
|
|
5287
|
+
const configPath = join14(factoryDir, ".beastmode", "config.json");
|
|
5288
|
+
if (existsSync16(configPath)) {
|
|
4739
5289
|
try {
|
|
4740
|
-
const config = JSON.parse(
|
|
5290
|
+
const config = JSON.parse(readFileSync13(configPath, "utf-8"));
|
|
4741
5291
|
if (config.runs_path) return config.runs_path;
|
|
4742
5292
|
} catch {
|
|
4743
5293
|
}
|
|
4744
5294
|
}
|
|
4745
|
-
return
|
|
5295
|
+
return join14(factoryDir, "runs");
|
|
4746
5296
|
}
|
|
4747
5297
|
function scanStrandedRuns(runsDir, newThreshold) {
|
|
4748
|
-
if (!
|
|
5298
|
+
if (!existsSync16(runsDir)) return [];
|
|
4749
5299
|
const stranded = [];
|
|
4750
5300
|
const walkRun = (runPath, projectId) => {
|
|
4751
|
-
const ckptPath =
|
|
4752
|
-
if (!
|
|
5301
|
+
const ckptPath = join14(runPath, "checkpoint.json");
|
|
5302
|
+
if (!existsSync16(ckptPath)) return;
|
|
4753
5303
|
let ckpt;
|
|
4754
5304
|
try {
|
|
4755
|
-
ckpt = JSON.parse(
|
|
5305
|
+
ckpt = JSON.parse(readFileSync13(ckptPath, "utf-8"));
|
|
4756
5306
|
} catch {
|
|
4757
5307
|
return;
|
|
4758
5308
|
}
|
|
@@ -4770,12 +5320,12 @@ function scanStrandedRuns(runsDir, newThreshold) {
|
|
|
4770
5320
|
});
|
|
4771
5321
|
};
|
|
4772
5322
|
try {
|
|
4773
|
-
const entries =
|
|
5323
|
+
const entries = readdirSync8(runsDir);
|
|
4774
5324
|
for (const entry of entries) {
|
|
4775
|
-
const entryPath =
|
|
5325
|
+
const entryPath = join14(runsDir, entry);
|
|
4776
5326
|
let stat;
|
|
4777
5327
|
try {
|
|
4778
|
-
stat =
|
|
5328
|
+
stat = statSync6(entryPath);
|
|
4779
5329
|
} catch {
|
|
4780
5330
|
continue;
|
|
4781
5331
|
}
|
|
@@ -4785,15 +5335,15 @@ function scanStrandedRuns(runsDir, newThreshold) {
|
|
|
4785
5335
|
} else if (!entry.startsWith(".")) {
|
|
4786
5336
|
let subEntries;
|
|
4787
5337
|
try {
|
|
4788
|
-
subEntries =
|
|
5338
|
+
subEntries = readdirSync8(entryPath);
|
|
4789
5339
|
} catch {
|
|
4790
5340
|
continue;
|
|
4791
5341
|
}
|
|
4792
5342
|
for (const sub of subEntries) {
|
|
4793
5343
|
if (!sub.startsWith("run-")) continue;
|
|
4794
|
-
const subPath =
|
|
5344
|
+
const subPath = join14(entryPath, sub);
|
|
4795
5345
|
try {
|
|
4796
|
-
if (
|
|
5346
|
+
if (statSync6(subPath).isDirectory()) {
|
|
4797
5347
|
walkRun(subPath, entry);
|
|
4798
5348
|
}
|
|
4799
5349
|
} catch {
|
|
@@ -4808,10 +5358,10 @@ function scanStrandedRuns(runsDir, newThreshold) {
|
|
|
4808
5358
|
return stranded;
|
|
4809
5359
|
}
|
|
4810
5360
|
function _readAnalyzeMeta(projectsDir, name) {
|
|
4811
|
-
const metaPath =
|
|
4812
|
-
if (!
|
|
5361
|
+
const metaPath = join14(projectsDir, name, "codebase-guide.meta.json");
|
|
5362
|
+
if (!existsSync16(metaPath)) return null;
|
|
4813
5363
|
try {
|
|
4814
|
-
return JSON.parse(
|
|
5364
|
+
return JSON.parse(readFileSync13(metaPath, "utf-8"));
|
|
4815
5365
|
} catch {
|
|
4816
5366
|
return null;
|
|
4817
5367
|
}
|
|
@@ -4838,29 +5388,19 @@ function getBoardRoutes(factoryDir) {
|
|
|
4838
5388
|
method: "GET",
|
|
4839
5389
|
pattern: "/api/status",
|
|
4840
5390
|
handler: async () => {
|
|
4841
|
-
const bmDir =
|
|
4842
|
-
const factoryJsonPath =
|
|
4843
|
-
if (!
|
|
5391
|
+
const bmDir = join14(factoryDir, ".beastmode");
|
|
5392
|
+
const factoryJsonPath = join14(bmDir, "factory.json");
|
|
5393
|
+
if (!existsSync16(factoryJsonPath)) {
|
|
4844
5394
|
throw new Error("Not a valid factory: factory.json not found");
|
|
4845
5395
|
}
|
|
4846
|
-
const factoryIdentity = JSON.parse(
|
|
4847
|
-
const projectsDir =
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
for (const entry of readdirSync6(projectsDir)) {
|
|
4851
|
-
if (entry.startsWith(".")) continue;
|
|
4852
|
-
if (existsSync15(join13(projectsDir, entry, "project.json"))) {
|
|
4853
|
-
projectCount++;
|
|
4854
|
-
continue;
|
|
4855
|
-
}
|
|
4856
|
-
if (entry.endsWith(".json")) projectCount++;
|
|
4857
|
-
}
|
|
4858
|
-
}
|
|
4859
|
-
const lockPath = join13(bmDir, "extensions.lock");
|
|
5396
|
+
const factoryIdentity = JSON.parse(readFileSync13(factoryJsonPath, "utf-8"));
|
|
5397
|
+
const projectsDir = join14(bmDir, "projects");
|
|
5398
|
+
const projectCount = listProjectRecords(projectsDir).length;
|
|
5399
|
+
const lockPath = join14(bmDir, "extensions.lock");
|
|
4860
5400
|
let pluginNames = [];
|
|
4861
|
-
if (
|
|
5401
|
+
if (existsSync16(lockPath)) {
|
|
4862
5402
|
try {
|
|
4863
|
-
const lock = JSON.parse(
|
|
5403
|
+
const lock = JSON.parse(readFileSync13(lockPath, "utf-8"));
|
|
4864
5404
|
pluginNames = Object.keys(lock.plugins || {});
|
|
4865
5405
|
} catch {
|
|
4866
5406
|
}
|
|
@@ -4880,24 +5420,24 @@ function getBoardRoutes(factoryDir) {
|
|
|
4880
5420
|
skillCount = listSkills(factoryDir).length;
|
|
4881
5421
|
} catch {
|
|
4882
5422
|
}
|
|
4883
|
-
const runsDir =
|
|
5423
|
+
const runsDir = join14(factoryDir, "runs");
|
|
4884
5424
|
let runDirs = [];
|
|
4885
|
-
if (
|
|
4886
|
-
runDirs =
|
|
5425
|
+
if (existsSync16(runsDir)) {
|
|
5426
|
+
runDirs = readdirSync8(runsDir).filter((d) => {
|
|
4887
5427
|
if (d.startsWith(".")) return false;
|
|
4888
5428
|
try {
|
|
4889
|
-
return
|
|
5429
|
+
return readdirSync8(join14(runsDir, d)).length > 0;
|
|
4890
5430
|
} catch {
|
|
4891
5431
|
return false;
|
|
4892
5432
|
}
|
|
4893
5433
|
}).sort();
|
|
4894
5434
|
}
|
|
4895
|
-
const pidFile =
|
|
5435
|
+
const pidFile = join14(factoryDir, ".beastmode", "daemon.pid");
|
|
4896
5436
|
let daemonPid = null;
|
|
4897
5437
|
let pidAlive = false;
|
|
4898
|
-
if (
|
|
5438
|
+
if (existsSync16(pidFile)) {
|
|
4899
5439
|
try {
|
|
4900
|
-
daemonPid = parseInt(
|
|
5440
|
+
daemonPid = parseInt(readFileSync13(pidFile, "utf-8").trim(), 10);
|
|
4901
5441
|
process.kill(daemonPid, 0);
|
|
4902
5442
|
pidAlive = true;
|
|
4903
5443
|
} catch {
|
|
@@ -4954,10 +5494,10 @@ function getBoardRoutes(factoryDir) {
|
|
|
4954
5494
|
handler: async () => {
|
|
4955
5495
|
const boardUrl = getBoardUrl2(factoryDir);
|
|
4956
5496
|
let ageSecs = null;
|
|
4957
|
-
const heartbeatPath =
|
|
4958
|
-
if (
|
|
5497
|
+
const heartbeatPath = join14(factoryDir, "daemon", "logs", ".heartbeat");
|
|
5498
|
+
if (existsSync16(heartbeatPath)) {
|
|
4959
5499
|
try {
|
|
4960
|
-
const ts = parseInt(
|
|
5500
|
+
const ts = parseInt(readFileSync13(heartbeatPath, "utf-8").trim(), 10);
|
|
4961
5501
|
if (!isNaN(ts)) {
|
|
4962
5502
|
ageSecs = Math.floor(Date.now() / 1e3) - ts;
|
|
4963
5503
|
}
|
|
@@ -4999,11 +5539,11 @@ function getBoardRoutes(factoryDir) {
|
|
|
4999
5539
|
let totalSlots = 3;
|
|
5000
5540
|
let shortPhaseReserve;
|
|
5001
5541
|
let slotsTimestamp;
|
|
5002
|
-
const slotsPath =
|
|
5542
|
+
const slotsPath = join14(factoryDir, "daemon", "logs", ".slots.json");
|
|
5003
5543
|
let usedDaemonSlotsFile = false;
|
|
5004
|
-
if (
|
|
5544
|
+
if (existsSync16(slotsPath)) {
|
|
5005
5545
|
try {
|
|
5006
|
-
const slots = JSON.parse(
|
|
5546
|
+
const slots = JSON.parse(readFileSync13(slotsPath, "utf-8"));
|
|
5007
5547
|
if (typeof slots.busy === "number" && typeof slots.total === "number") {
|
|
5008
5548
|
busySlots = slots.busy;
|
|
5009
5549
|
totalSlots = slots.total;
|
|
@@ -5028,10 +5568,10 @@ function getBoardRoutes(factoryDir) {
|
|
|
5028
5568
|
} catch {
|
|
5029
5569
|
}
|
|
5030
5570
|
for (const name of ["beastmode.docker.json", "beastmode.daemon.json"]) {
|
|
5031
|
-
const configPath =
|
|
5032
|
-
if (!
|
|
5571
|
+
const configPath = join14(factoryDir, "config", name);
|
|
5572
|
+
if (!existsSync16(configPath)) continue;
|
|
5033
5573
|
try {
|
|
5034
|
-
const config = JSON.parse(
|
|
5574
|
+
const config = JSON.parse(readFileSync13(configPath, "utf-8"));
|
|
5035
5575
|
if (typeof config.max_slots === "number") {
|
|
5036
5576
|
totalSlots = config.max_slots;
|
|
5037
5577
|
}
|
|
@@ -5048,11 +5588,11 @@ function getBoardRoutes(factoryDir) {
|
|
|
5048
5588
|
if (shortPhaseReserve === void 0) {
|
|
5049
5589
|
shortPhaseReserve = Math.max(0, Math.min(1, totalSlots - 1));
|
|
5050
5590
|
}
|
|
5051
|
-
const alertsPath =
|
|
5591
|
+
const alertsPath = join14(factoryDir, "daemon", "logs", ".alerts.json");
|
|
5052
5592
|
let alerts = [];
|
|
5053
|
-
if (
|
|
5593
|
+
if (existsSync16(alertsPath)) {
|
|
5054
5594
|
try {
|
|
5055
|
-
const parsed = JSON.parse(
|
|
5595
|
+
const parsed = JSON.parse(readFileSync13(alertsPath, "utf-8"));
|
|
5056
5596
|
if (Array.isArray(parsed)) alerts = parsed;
|
|
5057
5597
|
} catch {
|
|
5058
5598
|
}
|
|
@@ -5267,12 +5807,12 @@ function getBoardRoutes(factoryDir) {
|
|
|
5267
5807
|
method: "GET",
|
|
5268
5808
|
pattern: "/api/extensions/plugins",
|
|
5269
5809
|
handler: () => {
|
|
5270
|
-
const lockPath =
|
|
5271
|
-
if (!
|
|
5810
|
+
const lockPath = join14(factoryDir, ".beastmode", "extensions.lock");
|
|
5811
|
+
if (!existsSync16(lockPath)) {
|
|
5272
5812
|
return { plugins: {} };
|
|
5273
5813
|
}
|
|
5274
5814
|
try {
|
|
5275
|
-
const lock = JSON.parse(
|
|
5815
|
+
const lock = JSON.parse(readFileSync13(lockPath, "utf-8"));
|
|
5276
5816
|
return { plugins: lock.plugins || {} };
|
|
5277
5817
|
} catch {
|
|
5278
5818
|
return { plugins: {} };
|
|
@@ -5429,27 +5969,8 @@ function getBoardRoutes(factoryDir) {
|
|
|
5429
5969
|
method: "GET",
|
|
5430
5970
|
pattern: "/api/projects",
|
|
5431
5971
|
handler: () => {
|
|
5432
|
-
const projectsDir =
|
|
5433
|
-
|
|
5434
|
-
const projects = [];
|
|
5435
|
-
for (const entry of readdirSync6(projectsDir)) {
|
|
5436
|
-
if (entry.startsWith(".")) continue;
|
|
5437
|
-
const subDirPath = join13(projectsDir, entry, "project.json");
|
|
5438
|
-
if (existsSync15(subDirPath)) {
|
|
5439
|
-
try {
|
|
5440
|
-
projects.push(JSON.parse(readFileSync12(subDirPath, "utf-8")));
|
|
5441
|
-
} catch {
|
|
5442
|
-
}
|
|
5443
|
-
continue;
|
|
5444
|
-
}
|
|
5445
|
-
if (entry.endsWith(".json")) {
|
|
5446
|
-
try {
|
|
5447
|
-
projects.push(JSON.parse(readFileSync12(join13(projectsDir, entry), "utf-8")));
|
|
5448
|
-
} catch {
|
|
5449
|
-
}
|
|
5450
|
-
}
|
|
5451
|
-
}
|
|
5452
|
-
return { projects };
|
|
5972
|
+
const projectsDir = join14(factoryDir, ".beastmode", "projects");
|
|
5973
|
+
return { projects: listProjectRecords(projectsDir) };
|
|
5453
5974
|
}
|
|
5454
5975
|
},
|
|
5455
5976
|
{
|
|
@@ -5458,11 +5979,9 @@ function getBoardRoutes(factoryDir) {
|
|
|
5458
5979
|
handler: (_body, params) => {
|
|
5459
5980
|
const { name } = params;
|
|
5460
5981
|
assertSafeName(name);
|
|
5461
|
-
const
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
if (!existsSync15(filePath)) throw new Error(`Project not found: ${name}`);
|
|
5465
|
-
return JSON.parse(readFileSync12(filePath, "utf-8"));
|
|
5982
|
+
const record = readProjectRecord(join14(factoryDir, ".beastmode", "projects"), name);
|
|
5983
|
+
if (!record) throw new Error(`Project not found: ${name}`);
|
|
5984
|
+
return record;
|
|
5466
5985
|
}
|
|
5467
5986
|
},
|
|
5468
5987
|
{
|
|
@@ -5471,12 +5990,12 @@ function getBoardRoutes(factoryDir) {
|
|
|
5471
5990
|
handler: (_body, params) => {
|
|
5472
5991
|
const { name } = params;
|
|
5473
5992
|
assertSafeName(name);
|
|
5474
|
-
const extPath =
|
|
5475
|
-
if (!
|
|
5993
|
+
const extPath = join14(factoryDir, ".beastmode", "projects", name, "extensions.json");
|
|
5994
|
+
if (!existsSync16(extPath)) {
|
|
5476
5995
|
return { plugins: { add: [], remove: [] }, mcps: { add: {}, remove: [] }, skills: { add: [], remove: [] } };
|
|
5477
5996
|
}
|
|
5478
5997
|
try {
|
|
5479
|
-
return JSON.parse(
|
|
5998
|
+
return JSON.parse(readFileSync13(extPath, "utf-8"));
|
|
5480
5999
|
} catch {
|
|
5481
6000
|
return { plugins: { add: [], remove: [] }, mcps: { add: {}, remove: [] }, skills: { add: [], remove: [] } };
|
|
5482
6001
|
}
|
|
@@ -5498,18 +6017,18 @@ function getBoardRoutes(factoryDir) {
|
|
|
5498
6017
|
const { path: queryPath } = body || {};
|
|
5499
6018
|
const startPath = queryPath || homedir();
|
|
5500
6019
|
const target = resolve4(startPath);
|
|
5501
|
-
if (!
|
|
5502
|
-
const st =
|
|
6020
|
+
if (!existsSync16(target)) throw new Error(`Path not found: ${target}`);
|
|
6021
|
+
const st = statSync6(target);
|
|
5503
6022
|
if (!st.isDirectory()) throw new Error(`Not a directory: ${target}`);
|
|
5504
6023
|
let entries = [];
|
|
5505
6024
|
try {
|
|
5506
|
-
entries =
|
|
5507
|
-
const full =
|
|
6025
|
+
entries = readdirSync8(target, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => {
|
|
6026
|
+
const full = join14(target, d.name);
|
|
5508
6027
|
return {
|
|
5509
6028
|
name: d.name,
|
|
5510
6029
|
path: full,
|
|
5511
6030
|
is_dir: true,
|
|
5512
|
-
has_git:
|
|
6031
|
+
has_git: existsSync16(join14(full, ".git"))
|
|
5513
6032
|
};
|
|
5514
6033
|
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
5515
6034
|
} catch {
|
|
@@ -5528,8 +6047,8 @@ function getBoardRoutes(factoryDir) {
|
|
|
5528
6047
|
pattern: "/api/projects",
|
|
5529
6048
|
handler: (body) => {
|
|
5530
6049
|
const { path: projectPath, github_url, clone_target } = body;
|
|
5531
|
-
const projectsDir =
|
|
5532
|
-
if (!
|
|
6050
|
+
const projectsDir = join14(factoryDir, ".beastmode", "projects");
|
|
6051
|
+
if (!existsSync16(projectsDir)) mkdirSync12(projectsDir, { recursive: true });
|
|
5533
6052
|
let resolvedPath;
|
|
5534
6053
|
if (github_url) {
|
|
5535
6054
|
const url = github_url.trim();
|
|
@@ -5538,10 +6057,10 @@ function getBoardRoutes(factoryDir) {
|
|
|
5538
6057
|
}
|
|
5539
6058
|
const repoName = url.replace(/\.git$/, "").split(/[/:]/).filter(Boolean).pop() || "";
|
|
5540
6059
|
if (!repoName) throw new Error("Could not derive repo name from URL");
|
|
5541
|
-
const baseDir = clone_target ? resolve4(clone_target) : process.env.BEASTMODE_CLONE_DIR ? resolve4(process.env.BEASTMODE_CLONE_DIR) :
|
|
5542
|
-
if (!
|
|
5543
|
-
resolvedPath =
|
|
5544
|
-
if (
|
|
6060
|
+
const baseDir = clone_target ? resolve4(clone_target) : process.env.BEASTMODE_CLONE_DIR ? resolve4(process.env.BEASTMODE_CLONE_DIR) : join14(homedir(), "repos");
|
|
6061
|
+
if (!existsSync16(baseDir)) mkdirSync12(baseDir, { recursive: true });
|
|
6062
|
+
resolvedPath = join14(baseDir, repoName);
|
|
6063
|
+
if (existsSync16(resolvedPath)) {
|
|
5545
6064
|
throw new Error(`Target path already exists: ${resolvedPath} \u2014 pick a different clone_target or rename the existing folder`);
|
|
5546
6065
|
}
|
|
5547
6066
|
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || "";
|
|
@@ -5573,42 +6092,41 @@ function getBoardRoutes(factoryDir) {
|
|
|
5573
6092
|
}
|
|
5574
6093
|
} else if (projectPath) {
|
|
5575
6094
|
resolvedPath = resolve4(projectPath);
|
|
5576
|
-
if (!
|
|
6095
|
+
if (!existsSync16(resolvedPath)) throw new Error(`Directory not found: ${resolvedPath}`);
|
|
5577
6096
|
} else {
|
|
5578
6097
|
throw new Error("Missing required field: provide either `path` (local) or `github_url` (clone)");
|
|
5579
6098
|
}
|
|
5580
|
-
const projectName =
|
|
6099
|
+
const projectName = basename4(resolvedPath);
|
|
5581
6100
|
const stack = detectStack(resolvedPath);
|
|
5582
|
-
|
|
6101
|
+
let verifyPort = 3001;
|
|
6102
|
+
for (const existing of listProjectRecords(projectsDir)) {
|
|
6103
|
+
const port = existing.deploy?.verify_port;
|
|
6104
|
+
if (typeof port === "number" && port >= verifyPort) verifyPort = port + 1;
|
|
6105
|
+
}
|
|
6106
|
+
const record = createProjectRecord({
|
|
5583
6107
|
name: projectName,
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
},
|
|
5600
|
-
state_backend: "auto"
|
|
5601
|
-
}
|
|
6108
|
+
resolvedPath,
|
|
6109
|
+
gitRemote: stack.git_remote || void 0,
|
|
6110
|
+
verifyPort
|
|
6111
|
+
});
|
|
6112
|
+
record.stack = {
|
|
6113
|
+
detected: stack.framework,
|
|
6114
|
+
build_command: stack.suggested_commands.build,
|
|
6115
|
+
dev_command: stack.suggested_commands.dev,
|
|
6116
|
+
test_command: stack.suggested_commands.test,
|
|
6117
|
+
install_command: stack.suggested_commands.install,
|
|
6118
|
+
dev_port: stack.dev_port,
|
|
6119
|
+
is_monorepo: stack.is_monorepo,
|
|
6120
|
+
total_packages: stack.total_packages,
|
|
6121
|
+
primary_languages: stack.primary_languages,
|
|
6122
|
+
...stack.packages ? { packages: stack.packages } : {}
|
|
5602
6123
|
};
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
mkdirSync11(terraformDir, { recursive: true });
|
|
5610
|
-
mkdirSync11(ciDir, { recursive: true });
|
|
5611
|
-
return projectConfig;
|
|
6124
|
+
record.infra = { credentials: { mode: "factory" }, state_backend: "auto" };
|
|
6125
|
+
writeProjectRecord(projectsDir, projectName, record);
|
|
6126
|
+
const infraDir = join14(projectsDir, projectName, "infra");
|
|
6127
|
+
mkdirSync12(join14(infraDir, "terraform"), { recursive: true });
|
|
6128
|
+
mkdirSync12(join14(infraDir, "ci"), { recursive: true });
|
|
6129
|
+
return record;
|
|
5612
6130
|
}
|
|
5613
6131
|
},
|
|
5614
6132
|
{
|
|
@@ -5617,14 +6135,14 @@ function getBoardRoutes(factoryDir) {
|
|
|
5617
6135
|
handler: (_body, params) => {
|
|
5618
6136
|
const { name } = params;
|
|
5619
6137
|
assertSafeName(name);
|
|
5620
|
-
const projectsDir =
|
|
5621
|
-
const subDir =
|
|
5622
|
-
const flatPath =
|
|
5623
|
-
const hasSubDir =
|
|
5624
|
-
const hasFlat =
|
|
6138
|
+
const projectsDir = join14(factoryDir, ".beastmode", "projects");
|
|
6139
|
+
const subDir = join14(projectsDir, name);
|
|
6140
|
+
const flatPath = join14(projectsDir, `${name}.json`);
|
|
6141
|
+
const hasSubDir = existsSync16(join14(subDir, "project.json"));
|
|
6142
|
+
const hasFlat = existsSync16(flatPath);
|
|
5625
6143
|
if (!hasSubDir && !hasFlat) throw new Error(`Project not found: ${name}`);
|
|
5626
6144
|
if (hasSubDir) rmSync3(subDir, { recursive: true, force: true });
|
|
5627
|
-
else if (
|
|
6145
|
+
else if (existsSync16(subDir)) rmSync3(subDir, { recursive: true, force: true });
|
|
5628
6146
|
if (hasFlat) unlinkSync3(flatPath);
|
|
5629
6147
|
return { success: true };
|
|
5630
6148
|
}
|
|
@@ -5635,20 +6153,17 @@ function getBoardRoutes(factoryDir) {
|
|
|
5635
6153
|
handler: async (body, params, query) => {
|
|
5636
6154
|
const { name } = params;
|
|
5637
6155
|
assertSafeName(name);
|
|
5638
|
-
const projectsDir =
|
|
5639
|
-
const
|
|
5640
|
-
|
|
5641
|
-
const projPath = existsSync15(subDirConfigPath) ? subDirConfigPath : existsSync15(flatConfigPath) ? flatConfigPath : null;
|
|
5642
|
-
if (!projPath) throw new Error(`Project not found: ${name}`);
|
|
5643
|
-
const projConfig = JSON.parse(readFileSync12(projPath, "utf-8"));
|
|
6156
|
+
const projectsDir = join14(factoryDir, ".beastmode", "projects");
|
|
6157
|
+
const projConfig = readProjectRecord(projectsDir, name);
|
|
6158
|
+
if (!projConfig) throw new Error(`Project not found: ${name}`);
|
|
5644
6159
|
const projectPath = projConfig.path;
|
|
5645
|
-
const subDir =
|
|
5646
|
-
if (!
|
|
6160
|
+
const subDir = join14(projectsDir, name);
|
|
6161
|
+
if (!existsSync16(subDir)) mkdirSync12(subDir, { recursive: true });
|
|
5647
6162
|
const force = query?.force === "true" || query?.force === "1";
|
|
5648
6163
|
const tier = body?.tier;
|
|
5649
6164
|
if (tier === "quick") {
|
|
5650
|
-
const brownfieldPath =
|
|
5651
|
-
if (!
|
|
6165
|
+
const brownfieldPath = join14(subDir, "brownfield.md");
|
|
6166
|
+
if (!existsSync16(brownfieldPath)) {
|
|
5652
6167
|
const { detectStack: detectStackFn } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
5653
6168
|
let stackInfo = "Unknown stack";
|
|
5654
6169
|
try {
|
|
@@ -5659,7 +6174,7 @@ Build: ${cmds?.build || "unknown"}
|
|
|
5659
6174
|
Dev: ${cmds?.dev || "unknown"}`;
|
|
5660
6175
|
} catch {
|
|
5661
6176
|
}
|
|
5662
|
-
|
|
6177
|
+
writeFileSync13(brownfieldPath, `# Brownfield Analysis: ${name}
|
|
5663
6178
|
|
|
5664
6179
|
${stackInfo}
|
|
5665
6180
|
|
|
@@ -5681,8 +6196,8 @@ Path: ${projectPath}
|
|
|
5681
6196
|
}
|
|
5682
6197
|
const writeMeta = (m) => {
|
|
5683
6198
|
try {
|
|
5684
|
-
|
|
5685
|
-
|
|
6199
|
+
writeFileSync13(
|
|
6200
|
+
join14(subDir, "codebase-guide.meta.json"),
|
|
5686
6201
|
JSON.stringify(m, null, 2) + "\n",
|
|
5687
6202
|
"utf-8"
|
|
5688
6203
|
);
|
|
@@ -5691,8 +6206,8 @@ Path: ${projectPath}
|
|
|
5691
6206
|
}
|
|
5692
6207
|
};
|
|
5693
6208
|
if (!_hasClaudeCli()) {
|
|
5694
|
-
const brownfieldPath =
|
|
5695
|
-
if (!
|
|
6209
|
+
const brownfieldPath = join14(subDir, "brownfield.md");
|
|
6210
|
+
if (!existsSync16(brownfieldPath)) {
|
|
5696
6211
|
const { detectStack: detectStackFn } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
5697
6212
|
let stackInfo = "Unknown stack";
|
|
5698
6213
|
try {
|
|
@@ -5703,7 +6218,7 @@ Build: ${cmds?.build || "unknown"}
|
|
|
5703
6218
|
Dev: ${cmds?.dev || "unknown"}`;
|
|
5704
6219
|
} catch {
|
|
5705
6220
|
}
|
|
5706
|
-
|
|
6221
|
+
writeFileSync13(brownfieldPath, `# Brownfield Analysis: ${name}
|
|
5707
6222
|
|
|
5708
6223
|
${stackInfo}
|
|
5709
6224
|
|
|
@@ -5737,9 +6252,9 @@ Path: ${projectPath}
|
|
|
5737
6252
|
"You are the Brownfield Analyst. Analyze the codebase at the current working directory.",
|
|
5738
6253
|
`Write your output in pyramid format (L0/L1/L2) to the directory: ${subDir}`,
|
|
5739
6254
|
"Produce exactly these four files:",
|
|
5740
|
-
` - ${
|
|
5741
|
-
` - ${
|
|
5742
|
-
` - ${
|
|
6255
|
+
` - ${join14(subDir, "codebase-guide.md")} (full L2 analysis: architecture, conventions, safe modification points, fragile areas)`,
|
|
6256
|
+
` - ${join14(subDir, "codebase-guide.l1.md")} (paragraph summary only)`,
|
|
6257
|
+
` - ${join14(subDir, "codebase-guide.l0.txt")} (one-sentence summary only)`,
|
|
5743
6258
|
"Do not write anything else outside the BEASTMODE_OUTPUT_DIR."
|
|
5744
6259
|
].join("\n");
|
|
5745
6260
|
const isRoot = process.getuid?.() === 0;
|
|
@@ -5772,8 +6287,8 @@ Path: ${projectPath}
|
|
|
5772
6287
|
child.on("close", (code) => {
|
|
5773
6288
|
const durationSeconds = (Date.now() - startMs) / 1e3;
|
|
5774
6289
|
const sha = _gitHeadSha(projectPath);
|
|
5775
|
-
const guidePath =
|
|
5776
|
-
const ok = code === 0 &&
|
|
6290
|
+
const guidePath = join14(subDir, "codebase-guide.md");
|
|
6291
|
+
const ok = code === 0 && existsSync16(guidePath);
|
|
5777
6292
|
if (ok) {
|
|
5778
6293
|
writeMeta({
|
|
5779
6294
|
analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -5783,8 +6298,8 @@ Path: ${projectPath}
|
|
|
5783
6298
|
duration_seconds: durationSeconds,
|
|
5784
6299
|
status: "complete"
|
|
5785
6300
|
});
|
|
5786
|
-
const legacy =
|
|
5787
|
-
if (
|
|
6301
|
+
const legacy = join14(subDir, "brownfield.md");
|
|
6302
|
+
if (existsSync16(legacy)) {
|
|
5788
6303
|
try {
|
|
5789
6304
|
unlinkSync3(legacy);
|
|
5790
6305
|
} catch {
|
|
@@ -5860,7 +6375,7 @@ Path: ${projectPath}
|
|
|
5860
6375
|
handler: (_body, params) => {
|
|
5861
6376
|
const { name } = params;
|
|
5862
6377
|
assertSafeName(name);
|
|
5863
|
-
const projectsDir =
|
|
6378
|
+
const projectsDir = join14(factoryDir, ".beastmode", "projects");
|
|
5864
6379
|
const job = _analyzeJobs.get(name);
|
|
5865
6380
|
const meta = _readAnalyzeMeta(projectsDir, name);
|
|
5866
6381
|
if (job && job.status === "running") {
|
|
@@ -5904,13 +6419,13 @@ Path: ${projectPath}
|
|
|
5904
6419
|
pattern: "/api/runs",
|
|
5905
6420
|
handler: () => {
|
|
5906
6421
|
const runsDir = getRunsDir(factoryDir);
|
|
5907
|
-
if (!
|
|
6422
|
+
if (!existsSync16(runsDir)) return { runs: [] };
|
|
5908
6423
|
const runEntries = [];
|
|
5909
|
-
for (const entry of
|
|
6424
|
+
for (const entry of readdirSync8(runsDir)) {
|
|
5910
6425
|
if (entry.startsWith(".")) continue;
|
|
5911
|
-
const entryPath =
|
|
6426
|
+
const entryPath = join14(runsDir, entry);
|
|
5912
6427
|
try {
|
|
5913
|
-
const children =
|
|
6428
|
+
const children = readdirSync8(entryPath);
|
|
5914
6429
|
if (entry.startsWith("run-")) {
|
|
5915
6430
|
if (children.length > 0) {
|
|
5916
6431
|
runEntries.push({ id: entry, dir: entryPath, projectId: "" });
|
|
@@ -5918,9 +6433,9 @@ Path: ${projectPath}
|
|
|
5918
6433
|
} else {
|
|
5919
6434
|
for (const child of children) {
|
|
5920
6435
|
if (!child.startsWith("run-") || child.startsWith(".")) continue;
|
|
5921
|
-
const childPath =
|
|
6436
|
+
const childPath = join14(entryPath, child);
|
|
5922
6437
|
try {
|
|
5923
|
-
const grandchildren =
|
|
6438
|
+
const grandchildren = readdirSync8(childPath);
|
|
5924
6439
|
if (grandchildren.length > 0) {
|
|
5925
6440
|
runEntries.push({ id: child, dir: childPath, projectId: entry });
|
|
5926
6441
|
}
|
|
@@ -5940,9 +6455,9 @@ Path: ${projectPath}
|
|
|
5940
6455
|
}
|
|
5941
6456
|
}
|
|
5942
6457
|
const runs = Array.from(deduped.values()).sort((a, b) => b.id.localeCompare(a.id)).map(({ id, dir, projectId }) => {
|
|
5943
|
-
const manifest = readJsonFile(
|
|
5944
|
-
const checkpoint = readJsonFile(
|
|
5945
|
-
const prodVerif = readJsonFile(
|
|
6458
|
+
const manifest = readJsonFile(join14(dir, "manifest.json"));
|
|
6459
|
+
const checkpoint = readJsonFile(join14(dir, "checkpoint.json"));
|
|
6460
|
+
const prodVerif = readJsonFile(join14(dir, "prod-verification.json"));
|
|
5946
6461
|
const manifestData = manifest;
|
|
5947
6462
|
const cpData = checkpoint;
|
|
5948
6463
|
const prodAcceptFloor = 0.65;
|
|
@@ -5970,27 +6485,27 @@ Path: ${projectPath}
|
|
|
5970
6485
|
handler: (_body, params, query) => {
|
|
5971
6486
|
const { id } = params;
|
|
5972
6487
|
const runsDir = getRunsDir(factoryDir);
|
|
5973
|
-
let runDir =
|
|
5974
|
-
if (!
|
|
5975
|
-
for (const proj of
|
|
6488
|
+
let runDir = join14(runsDir, id);
|
|
6489
|
+
if (!existsSync16(runDir)) {
|
|
6490
|
+
for (const proj of readdirSync8(runsDir)) {
|
|
5976
6491
|
if (proj.startsWith(".") || proj.startsWith("run-")) continue;
|
|
5977
|
-
const candidate =
|
|
5978
|
-
if (
|
|
6492
|
+
const candidate = join14(runsDir, proj, id);
|
|
6493
|
+
if (existsSync16(candidate)) {
|
|
5979
6494
|
runDir = candidate;
|
|
5980
6495
|
break;
|
|
5981
6496
|
}
|
|
5982
6497
|
}
|
|
5983
6498
|
}
|
|
5984
|
-
if (!
|
|
5985
|
-
const manifest = readJsonFile(
|
|
5986
|
-
const checkpoint = readJsonFile(
|
|
5987
|
-
const iterationsDir =
|
|
6499
|
+
if (!existsSync16(runDir)) throw new Error(`Run not found: ${id}`);
|
|
6500
|
+
const manifest = readJsonFile(join14(runDir, "manifest.json"));
|
|
6501
|
+
const checkpoint = readJsonFile(join14(runDir, "checkpoint.json"));
|
|
6502
|
+
const iterationsDir = join14(runDir, "iterations");
|
|
5988
6503
|
const iterations = [];
|
|
5989
|
-
if (
|
|
5990
|
-
const iterDirs =
|
|
6504
|
+
if (existsSync16(iterationsDir)) {
|
|
6505
|
+
const iterDirs = readdirSync8(iterationsDir).sort();
|
|
5991
6506
|
for (const iterDir of iterDirs) {
|
|
5992
6507
|
const satisfaction = readJsonFile(
|
|
5993
|
-
|
|
6508
|
+
join14(iterationsDir, iterDir, "satisfaction.json")
|
|
5994
6509
|
);
|
|
5995
6510
|
iterations.push({
|
|
5996
6511
|
number: parseInt(iterDir, 10),
|
|
@@ -6018,11 +6533,11 @@ Path: ${projectPath}
|
|
|
6018
6533
|
pattern: "/api/runs/archive",
|
|
6019
6534
|
handler: () => {
|
|
6020
6535
|
const runsDir = getRunsDir(factoryDir);
|
|
6021
|
-
const configPath =
|
|
6536
|
+
const configPath = join14(factoryDir, ".beastmode", "config.json");
|
|
6022
6537
|
let days = 7;
|
|
6023
|
-
if (
|
|
6538
|
+
if (existsSync16(configPath)) {
|
|
6024
6539
|
try {
|
|
6025
|
-
days = JSON.parse(
|
|
6540
|
+
days = JSON.parse(readFileSync13(configPath, "utf-8")).archive_after_days || 7;
|
|
6026
6541
|
} catch {
|
|
6027
6542
|
}
|
|
6028
6543
|
}
|
|
@@ -6048,8 +6563,8 @@ Path: ${projectPath}
|
|
|
6048
6563
|
method: "GET",
|
|
6049
6564
|
pattern: "/api/config",
|
|
6050
6565
|
handler: () => {
|
|
6051
|
-
const configPath =
|
|
6052
|
-
const raw =
|
|
6566
|
+
const configPath = join14(factoryDir, ".beastmode", "config.json");
|
|
6567
|
+
const raw = existsSync16(configPath) ? JSON.parse(readFileSync13(configPath, "utf-8")) : generateDefaults();
|
|
6053
6568
|
return raw;
|
|
6054
6569
|
}
|
|
6055
6570
|
},
|
|
@@ -6067,8 +6582,8 @@ Path: ${projectPath}
|
|
|
6067
6582
|
const updatesClean = { ...updates };
|
|
6068
6583
|
delete updatesClean.force;
|
|
6069
6584
|
delete updatesClean._force;
|
|
6070
|
-
const configPath =
|
|
6071
|
-
const current =
|
|
6585
|
+
const configPath = join14(factoryDir, ".beastmode", "config.json");
|
|
6586
|
+
const current = existsSync16(configPath) ? JSON.parse(readFileSync13(configPath, "utf-8")) : generateDefaults();
|
|
6072
6587
|
if (!force) {
|
|
6073
6588
|
const oldPipeline = current.pipeline || {};
|
|
6074
6589
|
const newPipeline = updatesClean.pipeline || {};
|
|
@@ -6099,11 +6614,11 @@ Path: ${projectPath}
|
|
|
6099
6614
|
}
|
|
6100
6615
|
}
|
|
6101
6616
|
const merged = deepMerge2(current, updatesClean);
|
|
6102
|
-
|
|
6617
|
+
writeFileSync13(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
6103
6618
|
const daemonConfigPaths = [
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
].filter(
|
|
6619
|
+
join14(factoryDir, "config", "beastmode.daemon.json"),
|
|
6620
|
+
join14(factoryDir, "config", "beastmode.docker.json")
|
|
6621
|
+
].filter(existsSync16);
|
|
6107
6622
|
const pipelineToDaemonMap = {
|
|
6108
6623
|
satisfaction_threshold: ["verification", "satisfaction_threshold"],
|
|
6109
6624
|
prod_accept_floor: ["verification", "prod_accept_floor"],
|
|
@@ -6119,7 +6634,7 @@ Path: ${projectPath}
|
|
|
6119
6634
|
];
|
|
6120
6635
|
for (const daemonConfigPath of daemonConfigPaths) {
|
|
6121
6636
|
try {
|
|
6122
|
-
const daemonConfig = JSON.parse(
|
|
6637
|
+
const daemonConfig = JSON.parse(readFileSync13(daemonConfigPath, "utf-8"));
|
|
6123
6638
|
let changed = false;
|
|
6124
6639
|
for (const field of daemonFields) {
|
|
6125
6640
|
if (field in merged && merged[field] !== daemonConfig[field]) {
|
|
@@ -6159,7 +6674,7 @@ Path: ${projectPath}
|
|
|
6159
6674
|
}
|
|
6160
6675
|
}
|
|
6161
6676
|
if (changed) {
|
|
6162
|
-
|
|
6677
|
+
writeFileSync13(daemonConfigPath, JSON.stringify(daemonConfig, null, 2) + "\n", "utf-8");
|
|
6163
6678
|
}
|
|
6164
6679
|
} catch {
|
|
6165
6680
|
}
|
|
@@ -6172,10 +6687,10 @@ Path: ${projectPath}
|
|
|
6172
6687
|
pattern: "/api/config/reset",
|
|
6173
6688
|
handler: (body) => {
|
|
6174
6689
|
const { keyPath } = body || {};
|
|
6175
|
-
const configPath =
|
|
6176
|
-
const current =
|
|
6690
|
+
const configPath = join14(factoryDir, ".beastmode", "config.json");
|
|
6691
|
+
const current = existsSync16(configPath) ? JSON.parse(readFileSync13(configPath, "utf-8")) : {};
|
|
6177
6692
|
const result = configReset(current, keyPath);
|
|
6178
|
-
|
|
6693
|
+
writeFileSync13(configPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
|
|
6179
6694
|
return result;
|
|
6180
6695
|
}
|
|
6181
6696
|
},
|
|
@@ -6265,13 +6780,13 @@ Path: ${projectPath}
|
|
|
6265
6780
|
handler: () => {
|
|
6266
6781
|
const runsDir = getRunsDir(factoryDir);
|
|
6267
6782
|
const retroDirs = [];
|
|
6268
|
-
const rootRetroDir =
|
|
6269
|
-
if (
|
|
6270
|
-
if (
|
|
6271
|
-
for (const entry of
|
|
6783
|
+
const rootRetroDir = join14(runsDir, ".retrospectives");
|
|
6784
|
+
if (existsSync16(rootRetroDir)) retroDirs.push(rootRetroDir);
|
|
6785
|
+
if (existsSync16(runsDir)) {
|
|
6786
|
+
for (const entry of readdirSync8(runsDir)) {
|
|
6272
6787
|
if (entry === ".retrospectives" || entry.startsWith(".")) continue;
|
|
6273
|
-
const projectRetroDir =
|
|
6274
|
-
if (
|
|
6788
|
+
const projectRetroDir = join14(runsDir, entry, ".retrospectives");
|
|
6789
|
+
if (existsSync16(projectRetroDir)) retroDirs.push(projectRetroDir);
|
|
6275
6790
|
}
|
|
6276
6791
|
}
|
|
6277
6792
|
if (retroDirs.length === 0) {
|
|
@@ -6280,9 +6795,9 @@ Path: ${projectPath}
|
|
|
6280
6795
|
const retrospectives = [];
|
|
6281
6796
|
const learnings = [];
|
|
6282
6797
|
for (const retroDir of retroDirs) {
|
|
6283
|
-
const retroFiles =
|
|
6798
|
+
const retroFiles = readdirSync8(retroDir).filter((f) => f.startsWith("run-") && f.endsWith(".json")).sort().reverse();
|
|
6284
6799
|
for (const file of retroFiles) {
|
|
6285
|
-
const data = readJsonFile(
|
|
6800
|
+
const data = readJsonFile(join14(retroDir, file));
|
|
6286
6801
|
if (!data) continue;
|
|
6287
6802
|
const retro = data;
|
|
6288
6803
|
retrospectives.push(retro);
|
|
@@ -6304,10 +6819,10 @@ Path: ${projectPath}
|
|
|
6304
6819
|
let wisdom = "";
|
|
6305
6820
|
let wisdomMeta = {};
|
|
6306
6821
|
for (const retroDir of retroDirs) {
|
|
6307
|
-
const wisdomPath =
|
|
6308
|
-
if (
|
|
6309
|
-
wisdom =
|
|
6310
|
-
wisdomMeta = readJsonFile(
|
|
6822
|
+
const wisdomPath = join14(retroDir, "wisdom.md");
|
|
6823
|
+
if (existsSync16(wisdomPath)) {
|
|
6824
|
+
wisdom = readFileSync13(wisdomPath, "utf-8");
|
|
6825
|
+
wisdomMeta = readJsonFile(join14(retroDir, "wisdom-meta.json")) || {};
|
|
6311
6826
|
break;
|
|
6312
6827
|
}
|
|
6313
6828
|
}
|
|
@@ -6319,11 +6834,11 @@ Path: ${projectPath}
|
|
|
6319
6834
|
pattern: "/api/learnings/wisdom/refresh",
|
|
6320
6835
|
handler: () => {
|
|
6321
6836
|
const runsDir = getRunsDir(factoryDir);
|
|
6322
|
-
const retroDir =
|
|
6323
|
-
if (!
|
|
6324
|
-
|
|
6837
|
+
const retroDir = join14(runsDir, ".retrospectives");
|
|
6838
|
+
if (!existsSync16(retroDir)) {
|
|
6839
|
+
mkdirSync12(retroDir, { recursive: true });
|
|
6325
6840
|
}
|
|
6326
|
-
|
|
6841
|
+
writeFileSync13(join14(retroDir, ".refresh-requested"), (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
|
|
6327
6842
|
return { success: true };
|
|
6328
6843
|
}
|
|
6329
6844
|
},
|
|
@@ -6332,29 +6847,29 @@ Path: ${projectPath}
|
|
|
6332
6847
|
pattern: "/api/learnings/:runId/:index/dismiss",
|
|
6333
6848
|
handler: (_body, params) => {
|
|
6334
6849
|
const runsDir = getRunsDir(factoryDir);
|
|
6335
|
-
const candidateDirs = [
|
|
6336
|
-
if (
|
|
6337
|
-
for (const entry of
|
|
6850
|
+
const candidateDirs = [join14(runsDir, ".retrospectives")];
|
|
6851
|
+
if (existsSync16(runsDir)) {
|
|
6852
|
+
for (const entry of readdirSync8(runsDir)) {
|
|
6338
6853
|
if (entry === ".retrospectives" || entry.startsWith(".")) continue;
|
|
6339
|
-
candidateDirs.push(
|
|
6854
|
+
candidateDirs.push(join14(runsDir, entry, ".retrospectives"));
|
|
6340
6855
|
}
|
|
6341
6856
|
}
|
|
6342
6857
|
let filePath = null;
|
|
6343
6858
|
for (const dir of candidateDirs) {
|
|
6344
|
-
const candidate =
|
|
6345
|
-
if (
|
|
6859
|
+
const candidate = join14(dir, `${params.runId}.json`);
|
|
6860
|
+
if (existsSync16(candidate)) {
|
|
6346
6861
|
filePath = candidate;
|
|
6347
6862
|
break;
|
|
6348
6863
|
}
|
|
6349
6864
|
}
|
|
6350
6865
|
if (!filePath) throw new Error(`Retrospective not found: ${params.runId}`);
|
|
6351
|
-
const data = JSON.parse(
|
|
6866
|
+
const data = JSON.parse(readFileSync13(filePath, "utf-8"));
|
|
6352
6867
|
const idx = parseInt(params.index, 10);
|
|
6353
6868
|
if (!Array.isArray(data.learnings) || idx < 0 || idx >= data.learnings.length) {
|
|
6354
6869
|
throw new Error(`Learning index out of range: ${idx}`);
|
|
6355
6870
|
}
|
|
6356
6871
|
data.learnings[idx].dismissed = true;
|
|
6357
|
-
|
|
6872
|
+
writeFileSync13(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
6358
6873
|
return { success: true };
|
|
6359
6874
|
}
|
|
6360
6875
|
},
|
|
@@ -6363,29 +6878,29 @@ Path: ${projectPath}
|
|
|
6363
6878
|
pattern: "/api/learnings/:runId/:index/restore",
|
|
6364
6879
|
handler: (_body, params) => {
|
|
6365
6880
|
const runsDir = getRunsDir(factoryDir);
|
|
6366
|
-
const candidateDirs = [
|
|
6367
|
-
if (
|
|
6368
|
-
for (const entry of
|
|
6881
|
+
const candidateDirs = [join14(runsDir, ".retrospectives")];
|
|
6882
|
+
if (existsSync16(runsDir)) {
|
|
6883
|
+
for (const entry of readdirSync8(runsDir)) {
|
|
6369
6884
|
if (entry === ".retrospectives" || entry.startsWith(".")) continue;
|
|
6370
|
-
candidateDirs.push(
|
|
6885
|
+
candidateDirs.push(join14(runsDir, entry, ".retrospectives"));
|
|
6371
6886
|
}
|
|
6372
6887
|
}
|
|
6373
6888
|
let filePath = null;
|
|
6374
6889
|
for (const dir of candidateDirs) {
|
|
6375
|
-
const candidate =
|
|
6376
|
-
if (
|
|
6890
|
+
const candidate = join14(dir, `${params.runId}.json`);
|
|
6891
|
+
if (existsSync16(candidate)) {
|
|
6377
6892
|
filePath = candidate;
|
|
6378
6893
|
break;
|
|
6379
6894
|
}
|
|
6380
6895
|
}
|
|
6381
6896
|
if (!filePath) throw new Error(`Retrospective not found: ${params.runId}`);
|
|
6382
|
-
const data = JSON.parse(
|
|
6897
|
+
const data = JSON.parse(readFileSync13(filePath, "utf-8"));
|
|
6383
6898
|
const idx = parseInt(params.index, 10);
|
|
6384
6899
|
if (!Array.isArray(data.learnings) || idx < 0 || idx >= data.learnings.length) {
|
|
6385
6900
|
throw new Error(`Learning index out of range: ${idx}`);
|
|
6386
6901
|
}
|
|
6387
6902
|
data.learnings[idx].dismissed = false;
|
|
6388
|
-
|
|
6903
|
+
writeFileSync13(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
6389
6904
|
return { success: true };
|
|
6390
6905
|
}
|
|
6391
6906
|
},
|
|
@@ -6528,22 +7043,22 @@ Path: ${projectPath}
|
|
|
6528
7043
|
} catch {
|
|
6529
7044
|
}
|
|
6530
7045
|
try {
|
|
6531
|
-
const runsDir =
|
|
6532
|
-
if (
|
|
6533
|
-
const runDirs =
|
|
7046
|
+
const runsDir = join14(factoryDir, "runs", project);
|
|
7047
|
+
if (existsSync16(runsDir)) {
|
|
7048
|
+
const runDirs = readdirSync8(runsDir).filter((d) => d.startsWith("run-"));
|
|
6534
7049
|
for (const runId of runDirs) {
|
|
6535
|
-
const ckptPath =
|
|
7050
|
+
const ckptPath = join14(runsDir, runId, "checkpoint.json");
|
|
6536
7051
|
let subtitle = "";
|
|
6537
7052
|
let timestamp = "";
|
|
6538
7053
|
try {
|
|
6539
|
-
const st =
|
|
7054
|
+
const st = statSync6(join14(runsDir, runId));
|
|
6540
7055
|
timestamp = st.mtime.toISOString();
|
|
6541
7056
|
} catch {
|
|
6542
7057
|
timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
6543
7058
|
}
|
|
6544
|
-
if (
|
|
7059
|
+
if (existsSync16(ckptPath)) {
|
|
6545
7060
|
try {
|
|
6546
|
-
const ckpt = JSON.parse(
|
|
7061
|
+
const ckpt = JSON.parse(readFileSync13(ckptPath, "utf-8"));
|
|
6547
7062
|
const history = ckpt.satisfaction_history;
|
|
6548
7063
|
if (history && history.length > 0) {
|
|
6549
7064
|
const latest = history[history.length - 1];
|
|
@@ -6651,8 +7166,8 @@ __export(server_exports, {
|
|
|
6651
7166
|
});
|
|
6652
7167
|
import { createServer } from "http";
|
|
6653
7168
|
import { createHmac } from "crypto";
|
|
6654
|
-
import { readFileSync as
|
|
6655
|
-
import { join as
|
|
7169
|
+
import { readFileSync as readFileSync14, existsSync as existsSync17 } from "fs";
|
|
7170
|
+
import { join as join15, dirname as dirname6 } from "path";
|
|
6656
7171
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6657
7172
|
import { WebSocketServer as WebSocketServer2, WebSocket as WsClient } from "ws";
|
|
6658
7173
|
function proxyBoardWebSocket(req, socket, head, boardWsUrl) {
|
|
@@ -6694,16 +7209,16 @@ function proxyBoardWebSocket(req, socket, head, boardWsUrl) {
|
|
|
6694
7209
|
});
|
|
6695
7210
|
}
|
|
6696
7211
|
function resolveStaticDir() {
|
|
6697
|
-
const devPath =
|
|
6698
|
-
if (
|
|
7212
|
+
const devPath = join15(__dirname, "static");
|
|
7213
|
+
if (existsSync17(join15(devPath, "index.html"))) {
|
|
6699
7214
|
return devPath;
|
|
6700
7215
|
}
|
|
6701
|
-
const distWebPath =
|
|
6702
|
-
if (
|
|
7216
|
+
const distWebPath = join15(__dirname, "web");
|
|
7217
|
+
if (existsSync17(join15(distWebPath, "index.html"))) {
|
|
6703
7218
|
return distWebPath;
|
|
6704
7219
|
}
|
|
6705
|
-
const siblingPath =
|
|
6706
|
-
if (
|
|
7220
|
+
const siblingPath = join15(__dirname, "..", "web");
|
|
7221
|
+
if (existsSync17(join15(siblingPath, "index.html"))) {
|
|
6707
7222
|
return siblingPath;
|
|
6708
7223
|
}
|
|
6709
7224
|
throw new Error(
|
|
@@ -6812,9 +7327,9 @@ async function startServer(options = {}) {
|
|
|
6812
7327
|
const fallback = "<html><body><h1>BeastMode Init Wizard</h1><p>Static files not found.</p></body></html>";
|
|
6813
7328
|
if (!staticDir) return fallback;
|
|
6814
7329
|
try {
|
|
6815
|
-
const indexPath =
|
|
6816
|
-
if (!
|
|
6817
|
-
return
|
|
7330
|
+
const indexPath = join15(staticDir, "index.html");
|
|
7331
|
+
if (!existsSync17(indexPath)) return fallback;
|
|
7332
|
+
return readFileSync14(indexPath, "utf-8");
|
|
6818
7333
|
} catch {
|
|
6819
7334
|
return fallback;
|
|
6820
7335
|
}
|
|
@@ -6822,9 +7337,9 @@ async function startServer(options = {}) {
|
|
|
6822
7337
|
function loadBoardHtml() {
|
|
6823
7338
|
try {
|
|
6824
7339
|
const dir = staticDir || resolveStaticDir();
|
|
6825
|
-
const boardPath =
|
|
6826
|
-
if (!
|
|
6827
|
-
return
|
|
7340
|
+
const boardPath = join15(dir, "board.html");
|
|
7341
|
+
if (!existsSync17(boardPath)) return null;
|
|
7342
|
+
return readFileSync14(boardPath, "utf-8");
|
|
6828
7343
|
} catch {
|
|
6829
7344
|
return null;
|
|
6830
7345
|
}
|
|
@@ -6910,13 +7425,13 @@ async function startServer(options = {}) {
|
|
|
6910
7425
|
let commit_sha = null;
|
|
6911
7426
|
try {
|
|
6912
7427
|
const dir = staticDir || resolveStaticDir();
|
|
6913
|
-
const stampPath =
|
|
6914
|
-
if (
|
|
6915
|
-
stamp =
|
|
7428
|
+
const stampPath = join15(dir, "build-stamp.txt");
|
|
7429
|
+
if (existsSync17(stampPath)) {
|
|
7430
|
+
stamp = readFileSync14(stampPath, "utf-8").trim();
|
|
6916
7431
|
}
|
|
6917
|
-
const commitPath =
|
|
6918
|
-
if (
|
|
6919
|
-
commit_sha =
|
|
7432
|
+
const commitPath = join15(dir, "build-commit.txt");
|
|
7433
|
+
if (existsSync17(commitPath)) {
|
|
7434
|
+
commit_sha = readFileSync14(commitPath, "utf-8").trim() || null;
|
|
6920
7435
|
}
|
|
6921
7436
|
} catch {
|
|
6922
7437
|
}
|
|
@@ -6994,8 +7509,8 @@ async function startServer(options = {}) {
|
|
|
6994
7509
|
}
|
|
6995
7510
|
if (method === "GET" && url.startsWith("/static/")) {
|
|
6996
7511
|
const filename = url.slice("/static/".length);
|
|
6997
|
-
const filePath = staticDir ?
|
|
6998
|
-
if (filePath &&
|
|
7512
|
+
const filePath = staticDir ? join15(staticDir, filename) : "";
|
|
7513
|
+
if (filePath && existsSync17(filePath)) {
|
|
6999
7514
|
const ext = filename.split(".").pop()?.toLowerCase();
|
|
7000
7515
|
const mimeTypes = {
|
|
7001
7516
|
png: "image/png",
|
|
@@ -7006,7 +7521,7 @@ async function startServer(options = {}) {
|
|
|
7006
7521
|
ico: "image/x-icon"
|
|
7007
7522
|
};
|
|
7008
7523
|
const contentType = mimeTypes[ext || ""] || "application/octet-stream";
|
|
7009
|
-
const content =
|
|
7524
|
+
const content = readFileSync14(filePath);
|
|
7010
7525
|
res.writeHead(200, {
|
|
7011
7526
|
"Content-Type": contentType,
|
|
7012
7527
|
"Content-Length": content.length.toString(),
|
|
@@ -7255,30 +7770,29 @@ var init_server = __esm({
|
|
|
7255
7770
|
|
|
7256
7771
|
// src/cli/commands/board.ts
|
|
7257
7772
|
import { Command } from "commander";
|
|
7258
|
-
import { resolve as resolve5, join as
|
|
7259
|
-
import { existsSync as
|
|
7773
|
+
import { resolve as resolve5, join as join16 } from "path";
|
|
7774
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14 } from "fs";
|
|
7260
7775
|
import { execSync as execSync4 } from "child_process";
|
|
7261
7776
|
function findFactoryDir(startDir) {
|
|
7262
7777
|
let dir = startDir || process.cwd();
|
|
7263
7778
|
const root = resolve5("/");
|
|
7264
7779
|
while (dir !== root) {
|
|
7265
|
-
if (
|
|
7780
|
+
if (existsSync18(join16(dir, ".beastmode", "factory.json"))) {
|
|
7266
7781
|
return dir;
|
|
7267
7782
|
}
|
|
7268
7783
|
const parent = resolve5(dir, "..");
|
|
7269
7784
|
if (parent === dir) break;
|
|
7270
7785
|
dir = parent;
|
|
7271
7786
|
}
|
|
7272
|
-
if (
|
|
7787
|
+
if (existsSync18(join16(dir, ".beastmode", "factory.json"))) {
|
|
7273
7788
|
return dir;
|
|
7274
7789
|
}
|
|
7275
7790
|
return null;
|
|
7276
7791
|
}
|
|
7277
7792
|
function inferProjectName(factoryDir) {
|
|
7278
|
-
const projectsDir =
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
if (files.length === 1) return files[0].replace(/\.json$/, "");
|
|
7793
|
+
const projectsDir = join16(factoryDir, ".beastmode", "projects");
|
|
7794
|
+
const records = listProjectRecords(projectsDir);
|
|
7795
|
+
if (records.length === 1) return records[0].name;
|
|
7282
7796
|
return null;
|
|
7283
7797
|
}
|
|
7284
7798
|
function tryExecSync(cmd, timeoutMs = 15e3) {
|
|
@@ -7355,13 +7869,13 @@ async function runBoard(opts) {
|
|
|
7355
7869
|
let factoryDir = findFactoryDir();
|
|
7356
7870
|
if (!factoryDir) {
|
|
7357
7871
|
factoryDir = process.cwd();
|
|
7358
|
-
const bmDir =
|
|
7359
|
-
if (!
|
|
7360
|
-
|
|
7872
|
+
const bmDir = join16(factoryDir, ".beastmode");
|
|
7873
|
+
if (!existsSync18(bmDir)) {
|
|
7874
|
+
mkdirSync13(bmDir, { recursive: true });
|
|
7361
7875
|
}
|
|
7362
|
-
const factoryJsonPath2 =
|
|
7363
|
-
if (!
|
|
7364
|
-
|
|
7876
|
+
const factoryJsonPath2 = join16(bmDir, "factory.json");
|
|
7877
|
+
if (!existsSync18(factoryJsonPath2)) {
|
|
7878
|
+
writeFileSync14(
|
|
7365
7879
|
factoryJsonPath2,
|
|
7366
7880
|
JSON.stringify({ factory_name: "BeastMode", created_at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
7367
7881
|
"utf-8"
|
|
@@ -7369,8 +7883,8 @@ async function runBoard(opts) {
|
|
|
7369
7883
|
info("No factory found \u2014 created minimal stub at .beastmode/factory.json");
|
|
7370
7884
|
}
|
|
7371
7885
|
}
|
|
7372
|
-
const factoryJsonPath =
|
|
7373
|
-
const factoryJson = JSON.parse(
|
|
7886
|
+
const factoryJsonPath = join16(factoryDir, ".beastmode", "factory.json");
|
|
7887
|
+
const factoryJson = JSON.parse(readFileSync15(factoryJsonPath, "utf-8"));
|
|
7374
7888
|
const factoryName = factoryJson.factory_name || "BeastMode Factory";
|
|
7375
7889
|
header(`BeastMode Board \u2014 ${factoryName}`);
|
|
7376
7890
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
@@ -7409,6 +7923,7 @@ var init_board = __esm({
|
|
|
7409
7923
|
"src/cli/commands/board.ts"() {
|
|
7410
7924
|
"use strict";
|
|
7411
7925
|
init_display();
|
|
7926
|
+
init_engine();
|
|
7412
7927
|
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) => {
|
|
7413
7928
|
try {
|
|
7414
7929
|
await runBoard(opts);
|
|
@@ -7442,26 +7957,26 @@ __export(sync_claude_creds_exports, {
|
|
|
7442
7957
|
});
|
|
7443
7958
|
import { Command as Command2 } from "commander";
|
|
7444
7959
|
import { execSync as execSync5, spawnSync as spawnSync2 } from "child_process";
|
|
7445
|
-
import { writeFileSync as
|
|
7446
|
-
import { join as
|
|
7960
|
+
import { writeFileSync as writeFileSync15, readFileSync as readFileSync16, chmodSync, mkdirSync as mkdirSync14, existsSync as existsSync19, unlinkSync as unlinkSync4 } from "fs";
|
|
7961
|
+
import { join as join17 } from "path";
|
|
7447
7962
|
import { homedir as homedir2, platform } from "os";
|
|
7448
7963
|
function systemdUserDir() {
|
|
7449
|
-
return
|
|
7964
|
+
return join17(homedir2(), ".config", "systemd", "user");
|
|
7450
7965
|
}
|
|
7451
7966
|
function systemdServicePath() {
|
|
7452
|
-
return
|
|
7967
|
+
return join17(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.service`);
|
|
7453
7968
|
}
|
|
7454
7969
|
function systemdTimerPath() {
|
|
7455
|
-
return
|
|
7970
|
+
return join17(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.timer`);
|
|
7456
7971
|
}
|
|
7457
7972
|
function linuxCredsPath() {
|
|
7458
|
-
return
|
|
7973
|
+
return join17(homedir2(), ".claude", ".credentials.json");
|
|
7459
7974
|
}
|
|
7460
7975
|
function plistPath() {
|
|
7461
|
-
return
|
|
7976
|
+
return join17(homedir2(), "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
|
|
7462
7977
|
}
|
|
7463
7978
|
function agentLogPath() {
|
|
7464
|
-
return
|
|
7979
|
+
return join17(homedir2(), ".beastmode", "logs", "sync-claude-creds.log");
|
|
7465
7980
|
}
|
|
7466
7981
|
function readKeychainTokenSafe() {
|
|
7467
7982
|
try {
|
|
@@ -7493,10 +8008,10 @@ function writeCredentialsFile(rawJson) {
|
|
|
7493
8008
|
if (!oauth?.accessToken) {
|
|
7494
8009
|
throw new Error("Keychain entry missing claudeAiOauth.accessToken. Fix: run `claude login` again to reset the credential.");
|
|
7495
8010
|
}
|
|
7496
|
-
const claudeDir =
|
|
7497
|
-
if (!
|
|
7498
|
-
const credsPath =
|
|
7499
|
-
|
|
8011
|
+
const claudeDir = join17(homedir2(), ".claude");
|
|
8012
|
+
if (!existsSync19(claudeDir)) mkdirSync14(claudeDir, { recursive: true });
|
|
8013
|
+
const credsPath = join17(claudeDir, ".credentials.json");
|
|
8014
|
+
writeFileSync15(credsPath, rawJson + "\n", "utf-8");
|
|
7500
8015
|
chmodSync(credsPath, 384);
|
|
7501
8016
|
if (oauth.expiresAt) {
|
|
7502
8017
|
const hoursLeft = Math.round((oauth.expiresAt - Date.now()) / 36e5);
|
|
@@ -7509,7 +8024,7 @@ function writeCredentialsFile(rawJson) {
|
|
|
7509
8024
|
return credsPath;
|
|
7510
8025
|
}
|
|
7511
8026
|
function urgencyMarkerHostPath(factoryDir) {
|
|
7512
|
-
return
|
|
8027
|
+
return join17(factoryDir, "daemon", "logs", ".cred-refresh-needed");
|
|
7513
8028
|
}
|
|
7514
8029
|
function removeUrgencyMarker(factoryDir) {
|
|
7515
8030
|
if (!factoryDir) return;
|
|
@@ -7523,7 +8038,7 @@ function buildPlist(intervalSeconds, factoryDir) {
|
|
|
7523
8038
|
const nodePath = process.execPath;
|
|
7524
8039
|
const cliEntry = process.argv[1];
|
|
7525
8040
|
const logPath = agentLogPath();
|
|
7526
|
-
const keychainPath =
|
|
8041
|
+
const keychainPath = join17(homedir2(), "Library", "Keychains", "login.keychain-db");
|
|
7527
8042
|
const watchPaths = [keychainPath];
|
|
7528
8043
|
if (factoryDir) {
|
|
7529
8044
|
watchPaths.push(urgencyMarkerHostPath(factoryDir));
|
|
@@ -7563,15 +8078,15 @@ function installAgent(intervalSeconds, {
|
|
|
7563
8078
|
} = {}) {
|
|
7564
8079
|
const plist = plistPath();
|
|
7565
8080
|
const logPath = agentLogPath();
|
|
7566
|
-
|
|
7567
|
-
|
|
8081
|
+
mkdirSync14(join17(homedir2(), "Library", "LaunchAgents"), { recursive: true });
|
|
8082
|
+
mkdirSync14(join17(homedir2(), ".beastmode", "logs"), { recursive: true });
|
|
7568
8083
|
const uid = process.getuid?.();
|
|
7569
|
-
if (
|
|
8084
|
+
if (existsSync19(plist)) {
|
|
7570
8085
|
spawnSync2("launchctl", ["bootout", `gui/${uid}`, plist], { stdio: "pipe" });
|
|
7571
8086
|
}
|
|
7572
8087
|
const resolvedFactory = factoryDir ?? findFactoryDir() ?? void 0;
|
|
7573
|
-
|
|
7574
|
-
|
|
8088
|
+
writeFileSync15(plist, buildPlist(intervalSeconds, resolvedFactory), "utf-8");
|
|
8089
|
+
writeFileSync15(logPath, "", { flag: "a" });
|
|
7575
8090
|
const result = spawnSync2("launchctl", ["bootstrap", `gui/${uid}`, plist], {
|
|
7576
8091
|
stdio: "pipe",
|
|
7577
8092
|
encoding: "utf-8"
|
|
@@ -7609,7 +8124,7 @@ function syncClaudeCredsOnce() {
|
|
|
7609
8124
|
function uninstallAgent() {
|
|
7610
8125
|
const plist = plistPath();
|
|
7611
8126
|
const uid = process.getuid?.();
|
|
7612
|
-
if (!
|
|
8127
|
+
if (!existsSync19(plist)) {
|
|
7613
8128
|
info("No LaunchAgent installed \u2014 nothing to remove.");
|
|
7614
8129
|
return;
|
|
7615
8130
|
}
|
|
@@ -7629,7 +8144,7 @@ function uninstallAgent() {
|
|
|
7629
8144
|
function showStatus() {
|
|
7630
8145
|
const plist = plistPath();
|
|
7631
8146
|
const uid = process.getuid?.();
|
|
7632
|
-
if (!
|
|
8147
|
+
if (!existsSync19(plist)) {
|
|
7633
8148
|
info("LaunchAgent not installed.");
|
|
7634
8149
|
info("Install with: beastmode sync-claude-creds --install");
|
|
7635
8150
|
return;
|
|
@@ -7653,14 +8168,14 @@ function showStatus() {
|
|
|
7653
8168
|
}
|
|
7654
8169
|
function syncClaudeCredsLinux() {
|
|
7655
8170
|
const credsPath = linuxCredsPath();
|
|
7656
|
-
if (!
|
|
8171
|
+
if (!existsSync19(credsPath)) {
|
|
7657
8172
|
return {
|
|
7658
8173
|
error: `${credsPath} not found \u2014 run \`claude login\` first`
|
|
7659
8174
|
};
|
|
7660
8175
|
}
|
|
7661
8176
|
let parsed;
|
|
7662
8177
|
try {
|
|
7663
|
-
const raw =
|
|
8178
|
+
const raw = readFileSync16(credsPath, "utf-8");
|
|
7664
8179
|
parsed = JSON.parse(raw);
|
|
7665
8180
|
} catch {
|
|
7666
8181
|
return {
|
|
@@ -7689,7 +8204,7 @@ function syncClaudeCredsLinux() {
|
|
|
7689
8204
|
);
|
|
7690
8205
|
}
|
|
7691
8206
|
try {
|
|
7692
|
-
const raw2 =
|
|
8207
|
+
const raw2 = readFileSync16(credsPath, "utf-8");
|
|
7693
8208
|
const parsed2 = JSON.parse(raw2);
|
|
7694
8209
|
if (parsed2.claudeAiOauth?.expiresAt) {
|
|
7695
8210
|
const newMinutes = (parsed2.claudeAiOauth.expiresAt - Date.now()) / 6e4;
|
|
@@ -7743,18 +8258,18 @@ function installAgentLinux(intervalSeconds, { throwOnError = false } = {}) {
|
|
|
7743
8258
|
process.exit(1);
|
|
7744
8259
|
}
|
|
7745
8260
|
const unitDir = systemdUserDir();
|
|
7746
|
-
const logDir =
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
const logPath =
|
|
8261
|
+
const logDir = join17(homedir2(), ".beastmode", "logs");
|
|
8262
|
+
mkdirSync14(unitDir, { recursive: true });
|
|
8263
|
+
mkdirSync14(logDir, { recursive: true });
|
|
8264
|
+
const logPath = join17(logDir, "sync-claude-creds.log");
|
|
7750
8265
|
const nodePath = process.execPath;
|
|
7751
8266
|
const cliEntry = process.argv[1];
|
|
7752
|
-
|
|
8267
|
+
writeFileSync15(
|
|
7753
8268
|
systemdServicePath(),
|
|
7754
8269
|
buildServiceUnit(nodePath, cliEntry, logPath),
|
|
7755
8270
|
"utf-8"
|
|
7756
8271
|
);
|
|
7757
|
-
|
|
8272
|
+
writeFileSync15(
|
|
7758
8273
|
systemdTimerPath(),
|
|
7759
8274
|
buildTimerUnit(intervalSeconds),
|
|
7760
8275
|
"utf-8"
|
|
@@ -7805,14 +8320,14 @@ function uninstallAgentLinux() {
|
|
|
7805
8320
|
const servicePath = systemdServicePath();
|
|
7806
8321
|
const timerPath = systemdTimerPath();
|
|
7807
8322
|
let removed = false;
|
|
7808
|
-
if (
|
|
8323
|
+
if (existsSync19(servicePath)) {
|
|
7809
8324
|
try {
|
|
7810
8325
|
unlinkSync4(servicePath);
|
|
7811
8326
|
removed = true;
|
|
7812
8327
|
} catch {
|
|
7813
8328
|
}
|
|
7814
8329
|
}
|
|
7815
|
-
if (
|
|
8330
|
+
if (existsSync19(timerPath)) {
|
|
7816
8331
|
try {
|
|
7817
8332
|
unlinkSync4(timerPath);
|
|
7818
8333
|
removed = true;
|
|
@@ -7832,7 +8347,7 @@ function statusAgentLinux() {
|
|
|
7832
8347
|
error(unavail);
|
|
7833
8348
|
return;
|
|
7834
8349
|
}
|
|
7835
|
-
if (!
|
|
8350
|
+
if (!existsSync19(systemdTimerPath())) {
|
|
7836
8351
|
info("Systemd timer not installed.");
|
|
7837
8352
|
info("Install with: beastmode sync-claude-creds --install");
|
|
7838
8353
|
return;
|
|
@@ -7991,26 +8506,26 @@ var init_sync_claude_creds = __esm({
|
|
|
7991
8506
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7992
8507
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7993
8508
|
import { z as z2 } from "zod";
|
|
7994
|
-
import { readFileSync as
|
|
7995
|
-
import { join as
|
|
8509
|
+
import { readFileSync as readFileSync29, writeFileSync as writeFileSync25, existsSync as existsSync32, readdirSync as readdirSync12 } from "fs";
|
|
8510
|
+
import { join as join30, resolve as resolve18, basename as basename6 } from "path";
|
|
7996
8511
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
7997
8512
|
function readJsonFile2(filePath) {
|
|
7998
|
-
if (!
|
|
8513
|
+
if (!existsSync32(filePath)) return null;
|
|
7999
8514
|
try {
|
|
8000
|
-
return JSON.parse(
|
|
8515
|
+
return JSON.parse(readFileSync29(filePath, "utf-8"));
|
|
8001
8516
|
} catch {
|
|
8002
8517
|
return null;
|
|
8003
8518
|
}
|
|
8004
8519
|
}
|
|
8005
8520
|
function getFactoryPath() {
|
|
8006
8521
|
const envPath = process.env.BEASTMODE_FACTORY_PATH;
|
|
8007
|
-
if (envPath &&
|
|
8522
|
+
if (envPath && existsSync32(join30(envPath, ".beastmode", "factory.json"))) {
|
|
8008
8523
|
return envPath;
|
|
8009
8524
|
}
|
|
8010
8525
|
let dir = process.cwd();
|
|
8011
8526
|
const root = resolve18("/");
|
|
8012
8527
|
while (dir !== root) {
|
|
8013
|
-
if (
|
|
8528
|
+
if (existsSync32(join30(dir, ".beastmode", "factory.json"))) {
|
|
8014
8529
|
return dir;
|
|
8015
8530
|
}
|
|
8016
8531
|
const parent = resolve18(dir, "..");
|
|
@@ -8022,17 +8537,17 @@ function getFactoryPath() {
|
|
|
8022
8537
|
);
|
|
8023
8538
|
}
|
|
8024
8539
|
function readFactoryStatus(factoryDir) {
|
|
8025
|
-
const bmDir =
|
|
8540
|
+
const bmDir = join30(factoryDir, ".beastmode");
|
|
8026
8541
|
const factoryIdentity = FactoryIdentitySchema.parse(
|
|
8027
|
-
JSON.parse(
|
|
8542
|
+
JSON.parse(readFileSync29(join30(bmDir, "factory.json"), "utf-8"))
|
|
8028
8543
|
);
|
|
8029
|
-
const projectsDir =
|
|
8030
|
-
const projectCount =
|
|
8031
|
-
const lockPath =
|
|
8544
|
+
const projectsDir = join30(bmDir, "projects");
|
|
8545
|
+
const projectCount = listProjectRecords(projectsDir).length;
|
|
8546
|
+
const lockPath = join30(bmDir, "extensions.lock");
|
|
8032
8547
|
let pluginNames = [];
|
|
8033
|
-
if (
|
|
8548
|
+
if (existsSync32(lockPath)) {
|
|
8034
8549
|
try {
|
|
8035
|
-
const lock = JSON.parse(
|
|
8550
|
+
const lock = JSON.parse(readFileSync29(lockPath, "utf-8"));
|
|
8036
8551
|
pluginNames = Object.keys(lock.plugins || {});
|
|
8037
8552
|
} catch {
|
|
8038
8553
|
}
|
|
@@ -8052,23 +8567,23 @@ function readFactoryStatus(factoryDir) {
|
|
|
8052
8567
|
skillCount = listSkills(factoryDir).length;
|
|
8053
8568
|
} catch {
|
|
8054
8569
|
}
|
|
8055
|
-
const runsDir =
|
|
8570
|
+
const runsDir = join30(factoryDir, "runs");
|
|
8056
8571
|
let runDirs = [];
|
|
8057
|
-
if (
|
|
8058
|
-
runDirs =
|
|
8572
|
+
if (existsSync32(runsDir)) {
|
|
8573
|
+
runDirs = readdirSync12(runsDir).filter((d) => {
|
|
8059
8574
|
try {
|
|
8060
|
-
return
|
|
8575
|
+
return readdirSync12(join30(runsDir, d)).length > 0;
|
|
8061
8576
|
} catch {
|
|
8062
8577
|
return false;
|
|
8063
8578
|
}
|
|
8064
8579
|
}).sort();
|
|
8065
8580
|
}
|
|
8066
|
-
const pidFile =
|
|
8581
|
+
const pidFile = join30(bmDir, "daemon.pid");
|
|
8067
8582
|
let daemonPid = null;
|
|
8068
8583
|
let pidAlive = false;
|
|
8069
|
-
if (
|
|
8584
|
+
if (existsSync32(pidFile)) {
|
|
8070
8585
|
try {
|
|
8071
|
-
daemonPid = parseInt(
|
|
8586
|
+
daemonPid = parseInt(readFileSync29(pidFile, "utf-8").trim(), 10);
|
|
8072
8587
|
process.kill(daemonPid, 0);
|
|
8073
8588
|
pidAlive = true;
|
|
8074
8589
|
} catch {
|
|
@@ -8089,18 +8604,18 @@ function readFactoryStatus(factoryDir) {
|
|
|
8089
8604
|
return collectStatus(input);
|
|
8090
8605
|
}
|
|
8091
8606
|
function readBoardItems(factoryDir) {
|
|
8092
|
-
const filePath =
|
|
8093
|
-
if (!
|
|
8607
|
+
const filePath = join30(factoryDir, ".beastmode", "board.json");
|
|
8608
|
+
if (!existsSync32(filePath)) return [];
|
|
8094
8609
|
try {
|
|
8095
|
-
const raw = JSON.parse(
|
|
8610
|
+
const raw = JSON.parse(readFileSync29(filePath, "utf-8"));
|
|
8096
8611
|
return Array.isArray(raw.items) ? raw.items : [];
|
|
8097
8612
|
} catch {
|
|
8098
8613
|
return [];
|
|
8099
8614
|
}
|
|
8100
8615
|
}
|
|
8101
8616
|
function writeBoardItems(factoryDir, items) {
|
|
8102
|
-
const filePath =
|
|
8103
|
-
|
|
8617
|
+
const filePath = join30(factoryDir, ".beastmode", "board.json");
|
|
8618
|
+
writeFileSync25(filePath, JSON.stringify({ items }, null, 2) + "\n", "utf-8");
|
|
8104
8619
|
}
|
|
8105
8620
|
function createMcpServer() {
|
|
8106
8621
|
const server = new McpServer(
|
|
@@ -8123,8 +8638,8 @@ function createMcpServer() {
|
|
|
8123
8638
|
{ key_path: z2.string().describe("Dot-notation key path") },
|
|
8124
8639
|
async ({ key_path }) => {
|
|
8125
8640
|
const factoryDir = getFactoryPath();
|
|
8126
|
-
const configPath =
|
|
8127
|
-
const config =
|
|
8641
|
+
const configPath = join30(factoryDir, ".beastmode", "config.json");
|
|
8642
|
+
const config = existsSync32(configPath) ? JSON.parse(readFileSync29(configPath, "utf-8")) : generateDefaults();
|
|
8128
8643
|
try {
|
|
8129
8644
|
const value = configGet(config, key_path);
|
|
8130
8645
|
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
@@ -8142,11 +8657,11 @@ function createMcpServer() {
|
|
|
8142
8657
|
},
|
|
8143
8658
|
async ({ key_path, value }) => {
|
|
8144
8659
|
const factoryDir = getFactoryPath();
|
|
8145
|
-
const configPath =
|
|
8146
|
-
const config =
|
|
8660
|
+
const configPath = join30(factoryDir, ".beastmode", "config.json");
|
|
8661
|
+
const config = existsSync32(configPath) ? JSON.parse(readFileSync29(configPath, "utf-8")) : generateDefaults();
|
|
8147
8662
|
const coerced = coerceValue(value);
|
|
8148
8663
|
const updated = configSet(config, key_path, coerced);
|
|
8149
|
-
|
|
8664
|
+
writeFileSync25(configPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
8150
8665
|
return { content: [{ type: "text", text: `Set ${key_path} = ${JSON.stringify(coerced)}` }] };
|
|
8151
8666
|
}
|
|
8152
8667
|
);
|
|
@@ -8180,14 +8695,14 @@ function createMcpServer() {
|
|
|
8180
8695
|
{},
|
|
8181
8696
|
async () => {
|
|
8182
8697
|
const factoryDir = getFactoryPath();
|
|
8183
|
-
const runsDir =
|
|
8184
|
-
if (!
|
|
8698
|
+
const runsDir = join30(factoryDir, "runs");
|
|
8699
|
+
if (!existsSync32(runsDir)) {
|
|
8185
8700
|
return { content: [{ type: "text", text: "No runs directory found." }] };
|
|
8186
8701
|
}
|
|
8187
|
-
const runDirs =
|
|
8702
|
+
const runDirs = readdirSync12(runsDir).sort().reverse();
|
|
8188
8703
|
const activeRuns = [];
|
|
8189
8704
|
for (const id of runDirs.slice(0, 10)) {
|
|
8190
|
-
const cp = readJsonFile2(
|
|
8705
|
+
const cp = readJsonFile2(join30(runsDir, id, "checkpoint.json"));
|
|
8191
8706
|
if (cp) {
|
|
8192
8707
|
activeRuns.push({ id, checkpoint: cp });
|
|
8193
8708
|
}
|
|
@@ -8201,17 +8716,17 @@ function createMcpServer() {
|
|
|
8201
8716
|
{ run_id: z2.string().describe("Run ID (directory name)") },
|
|
8202
8717
|
async ({ run_id }) => {
|
|
8203
8718
|
const factoryDir = getFactoryPath();
|
|
8204
|
-
const runDir =
|
|
8205
|
-
if (!
|
|
8719
|
+
const runDir = join30(factoryDir, "runs", run_id);
|
|
8720
|
+
if (!existsSync32(runDir)) {
|
|
8206
8721
|
return { content: [{ type: "text", text: `Run not found: ${run_id}` }], isError: true };
|
|
8207
8722
|
}
|
|
8208
|
-
const manifest = readJsonFile2(
|
|
8209
|
-
const checkpoint = readJsonFile2(
|
|
8210
|
-
const iterationsDir =
|
|
8723
|
+
const manifest = readJsonFile2(join30(runDir, "manifest.json"));
|
|
8724
|
+
const checkpoint = readJsonFile2(join30(runDir, "checkpoint.json"));
|
|
8725
|
+
const iterationsDir = join30(runDir, "iterations");
|
|
8211
8726
|
const iterations = [];
|
|
8212
|
-
if (
|
|
8213
|
-
for (const dir of
|
|
8214
|
-
const satisfaction = readJsonFile2(
|
|
8727
|
+
if (existsSync32(iterationsDir)) {
|
|
8728
|
+
for (const dir of readdirSync12(iterationsDir).sort()) {
|
|
8729
|
+
const satisfaction = readJsonFile2(join30(iterationsDir, dir, "satisfaction.json"));
|
|
8215
8730
|
iterations.push({ number: parseInt(dir, 10), satisfaction });
|
|
8216
8731
|
}
|
|
8217
8732
|
}
|
|
@@ -8257,12 +8772,12 @@ function createMcpServer() {
|
|
|
8257
8772
|
{},
|
|
8258
8773
|
async () => {
|
|
8259
8774
|
const factoryDir = getFactoryPath();
|
|
8260
|
-
const bmDir =
|
|
8775
|
+
const bmDir = join30(factoryDir, ".beastmode");
|
|
8261
8776
|
let plugins = {};
|
|
8262
|
-
const lockPath =
|
|
8263
|
-
if (
|
|
8777
|
+
const lockPath = join30(bmDir, "extensions.lock");
|
|
8778
|
+
if (existsSync32(lockPath)) {
|
|
8264
8779
|
try {
|
|
8265
|
-
const lock = JSON.parse(
|
|
8780
|
+
const lock = JSON.parse(readFileSync29(lockPath, "utf-8"));
|
|
8266
8781
|
plugins = lock.plugins || {};
|
|
8267
8782
|
} catch {
|
|
8268
8783
|
}
|
|
@@ -8296,17 +8811,8 @@ function createMcpServer() {
|
|
|
8296
8811
|
{},
|
|
8297
8812
|
async () => {
|
|
8298
8813
|
const factoryDir = getFactoryPath();
|
|
8299
|
-
const projectsDir =
|
|
8300
|
-
|
|
8301
|
-
return { content: [{ type: "text", text: "[]" }] };
|
|
8302
|
-
}
|
|
8303
|
-
const projects = readdirSync11(projectsDir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
8304
|
-
try {
|
|
8305
|
-
return JSON.parse(readFileSync28(join29(projectsDir, f), "utf-8"));
|
|
8306
|
-
} catch {
|
|
8307
|
-
return null;
|
|
8308
|
-
}
|
|
8309
|
-
}).filter(Boolean);
|
|
8814
|
+
const projectsDir = join30(factoryDir, ".beastmode", "projects");
|
|
8815
|
+
const projects = listProjectRecords(projectsDir);
|
|
8310
8816
|
return { content: [{ type: "text", text: JSON.stringify(projects, null, 2) }] };
|
|
8311
8817
|
}
|
|
8312
8818
|
);
|
|
@@ -8317,34 +8823,25 @@ function createMcpServer() {
|
|
|
8317
8823
|
async ({ path: projectPath }) => {
|
|
8318
8824
|
const factoryDir = getFactoryPath();
|
|
8319
8825
|
const resolvedPath = resolve18(projectPath);
|
|
8320
|
-
if (!
|
|
8826
|
+
if (!existsSync32(resolvedPath)) {
|
|
8321
8827
|
return { content: [{ type: "text", text: `Directory not found: ${resolvedPath}` }], isError: true };
|
|
8322
8828
|
}
|
|
8323
|
-
const projectName =
|
|
8829
|
+
const projectName = basename6(resolvedPath);
|
|
8324
8830
|
const stack = detectStack(resolvedPath);
|
|
8325
|
-
const
|
|
8831
|
+
const projectsDir = join30(factoryDir, ".beastmode", "projects");
|
|
8832
|
+
let verifyPort = 3001;
|
|
8833
|
+
for (const existing of listProjectRecords(projectsDir)) {
|
|
8834
|
+
const port = existing.deploy?.verify_port;
|
|
8835
|
+
if (typeof port === "number" && port >= verifyPort) verifyPort = port + 1;
|
|
8836
|
+
}
|
|
8837
|
+
const record = createProjectRecord({
|
|
8326
8838
|
name: projectName,
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
test_command: stack.suggested_commands.test,
|
|
8334
|
-
install_command: stack.suggested_commands.install,
|
|
8335
|
-
dev_port: stack.dev_port
|
|
8336
|
-
},
|
|
8337
|
-
deploy: { target: stack.suggested_deploy },
|
|
8338
|
-
plugins: stack.suggested_plugins
|
|
8339
|
-
};
|
|
8340
|
-
const projectsDir = join29(factoryDir, ".beastmode", "projects");
|
|
8341
|
-
mkdirSync19(projectsDir, { recursive: true });
|
|
8342
|
-
writeFileSync24(
|
|
8343
|
-
join29(projectsDir, `${projectName}.json`),
|
|
8344
|
-
JSON.stringify(projectConfig, null, 2) + "\n",
|
|
8345
|
-
"utf-8"
|
|
8346
|
-
);
|
|
8347
|
-
return { content: [{ type: "text", text: JSON.stringify(projectConfig, null, 2) }] };
|
|
8839
|
+
resolvedPath,
|
|
8840
|
+
gitRemote: stack.git_remote || void 0,
|
|
8841
|
+
verifyPort
|
|
8842
|
+
});
|
|
8843
|
+
writeProjectRecord(projectsDir, projectName, record);
|
|
8844
|
+
return { content: [{ type: "text", text: JSON.stringify(record, null, 2) }] };
|
|
8348
8845
|
}
|
|
8349
8846
|
);
|
|
8350
8847
|
server.tool(
|
|
@@ -8447,17 +8944,17 @@ init_engine();
|
|
|
8447
8944
|
init_file_writer();
|
|
8448
8945
|
import { Command as Command3 } from "commander";
|
|
8449
8946
|
import inquirer from "inquirer";
|
|
8450
|
-
import { resolve as resolve6, basename as
|
|
8451
|
-
import { existsSync as
|
|
8947
|
+
import { resolve as resolve6, basename as basename5, join as join18 } from "path";
|
|
8948
|
+
import { existsSync as existsSync20, writeFileSync as writeFileSync16, mkdirSync as mkdirSync15, readFileSync as readFileSync17 } from "fs";
|
|
8452
8949
|
|
|
8453
8950
|
// src/cli/utils/docker.ts
|
|
8454
|
-
import { existsSync as
|
|
8455
|
-
import { join as
|
|
8951
|
+
import { existsSync as existsSync10 } from "fs";
|
|
8952
|
+
import { join as join9 } from "path";
|
|
8456
8953
|
import { execSync } from "child_process";
|
|
8457
8954
|
var GHCR_IMAGE_PREFIX = "ghcr.io/develeap/beastmode";
|
|
8458
8955
|
function findComposeFile(dir) {
|
|
8459
|
-
const path =
|
|
8460
|
-
return
|
|
8956
|
+
const path = join9(dir, "docker-compose.yml");
|
|
8957
|
+
return existsSync10(path) ? path : null;
|
|
8461
8958
|
}
|
|
8462
8959
|
function requireComposeFile(dir) {
|
|
8463
8960
|
const path = findComposeFile(dir);
|
|
@@ -8520,7 +9017,7 @@ function loginToGhcr(token) {
|
|
|
8520
9017
|
}
|
|
8521
9018
|
}
|
|
8522
9019
|
function seedConfigFromImage(targetDir, tag) {
|
|
8523
|
-
const configDir =
|
|
9020
|
+
const configDir = join9(targetDir, "config");
|
|
8524
9021
|
const containerName = "bm-config-seed";
|
|
8525
9022
|
try {
|
|
8526
9023
|
try {
|
|
@@ -8700,15 +9197,15 @@ function collect(val, acc) {
|
|
|
8700
9197
|
return acc;
|
|
8701
9198
|
}
|
|
8702
9199
|
function readSecretFile(filePath) {
|
|
8703
|
-
return
|
|
9200
|
+
return readFileSync17(resolve6(filePath), "utf-8").trim();
|
|
8704
9201
|
}
|
|
8705
9202
|
function isSourceRepo(dir) {
|
|
8706
|
-
return
|
|
9203
|
+
return existsSync20(resolve6(dir, "daemon")) && existsSync20(resolve6(dir, "board")) && existsSync20(resolve6(dir, "cli"));
|
|
8707
9204
|
}
|
|
8708
9205
|
async function runInit(name, opts) {
|
|
8709
9206
|
if (opts.ui) {
|
|
8710
9207
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
8711
|
-
const factoryName2 = name ||
|
|
9208
|
+
const factoryName2 = name || basename5(resolve6("."));
|
|
8712
9209
|
const projectPath2 = opts.project ? resolve6(opts.project) : void 0;
|
|
8713
9210
|
info("Starting init wizard...");
|
|
8714
9211
|
const uiServer = await startServer2({
|
|
@@ -8741,13 +9238,13 @@ async function runInit(name, opts) {
|
|
|
8741
9238
|
const totalSteps = 5;
|
|
8742
9239
|
header("BeastMode \u2014 Dark Factory Init");
|
|
8743
9240
|
info("Creating your Custom Dark Factory\n");
|
|
8744
|
-
const factoryName = name ||
|
|
8745
|
-
if (
|
|
9241
|
+
const factoryName = name || basename5(resolve6("."));
|
|
9242
|
+
if (existsSync20(factoryName) && existsSync20(resolve6(factoryName, ".beastmode"))) {
|
|
8746
9243
|
throw new Error(`Factory already exists at ./${factoryName}. Use 'beastmode config' to modify.`);
|
|
8747
9244
|
}
|
|
8748
9245
|
if (opts.from) {
|
|
8749
|
-
const { readFileSync:
|
|
8750
|
-
const templateContent =
|
|
9246
|
+
const { readFileSync: readFileSync37 } = await import("fs");
|
|
9247
|
+
const templateContent = readFileSync37(resolve6(opts.from), "utf-8");
|
|
8751
9248
|
const { parseTemplate: parseTemplate2 } = await Promise.resolve().then(() => (init_template_importer(), template_importer_exports));
|
|
8752
9249
|
const template = parseTemplate2(templateContent);
|
|
8753
9250
|
info(`Importing from template: ${opts.from}`);
|
|
@@ -8757,7 +9254,7 @@ async function runInit(name, opts) {
|
|
|
8757
9254
|
}
|
|
8758
9255
|
const templateProjectPath = resolve6(opts.project);
|
|
8759
9256
|
const templateStack = detectStack(templateProjectPath);
|
|
8760
|
-
const templateProjectName =
|
|
9257
|
+
const templateProjectName = basename5(templateProjectPath);
|
|
8761
9258
|
const actions2 = scaffoldFactory(factoryName, template.config, {
|
|
8762
9259
|
name: templateProjectName,
|
|
8763
9260
|
repo: templateStack.git_remote || void 0,
|
|
@@ -8792,7 +9289,7 @@ async function runInit(name, opts) {
|
|
|
8792
9289
|
name: "project",
|
|
8793
9290
|
message: "Path to your project:",
|
|
8794
9291
|
default: ".",
|
|
8795
|
-
validate: (input) =>
|
|
9292
|
+
validate: (input) => existsSync20(resolve6(input)) || `Directory not found: ${input}`
|
|
8796
9293
|
}
|
|
8797
9294
|
]);
|
|
8798
9295
|
projectPath = resolve6(answer.project);
|
|
@@ -8952,7 +9449,7 @@ async function runInit(name, opts) {
|
|
|
8952
9449
|
success("Board UI password set");
|
|
8953
9450
|
}
|
|
8954
9451
|
step(5, totalSteps, "Boot");
|
|
8955
|
-
const projectName =
|
|
9452
|
+
const projectName = basename5(projectPath);
|
|
8956
9453
|
const actions = scaffoldFactory(factoryName, config, {
|
|
8957
9454
|
name: projectName,
|
|
8958
9455
|
repo: stack.git_remote || void 0,
|
|
@@ -9098,14 +9595,14 @@ async function runImageModeInit(name, opts) {
|
|
|
9098
9595
|
let projectPath;
|
|
9099
9596
|
if (opts.project) {
|
|
9100
9597
|
projectPath = resolve6(opts.project);
|
|
9101
|
-
if (!
|
|
9598
|
+
if (!existsSync20(projectPath)) {
|
|
9102
9599
|
error(`--project path does not exist: ${projectPath}`);
|
|
9103
9600
|
process.exit(1);
|
|
9104
9601
|
}
|
|
9105
9602
|
success(`Project path: ${projectPath}`);
|
|
9106
9603
|
} else if (opts.yes) {
|
|
9107
9604
|
const cwd = resolve6(".");
|
|
9108
|
-
if (!
|
|
9605
|
+
if (!existsSync20(join18(cwd, ".git"))) {
|
|
9109
9606
|
error(
|
|
9110
9607
|
"--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."
|
|
9111
9608
|
);
|
|
@@ -9122,8 +9619,8 @@ async function runImageModeInit(name, opts) {
|
|
|
9122
9619
|
default: ".",
|
|
9123
9620
|
validate: (input) => {
|
|
9124
9621
|
const p = resolve6(input);
|
|
9125
|
-
if (!
|
|
9126
|
-
if (!
|
|
9622
|
+
if (!existsSync20(p)) return `Directory not found: ${p}`;
|
|
9623
|
+
if (!existsSync20(join18(p, ".git"))) {
|
|
9127
9624
|
return `Not a git repo: ${p} (run 'git init' first, or pick a different path)`;
|
|
9128
9625
|
}
|
|
9129
9626
|
return true;
|
|
@@ -9162,9 +9659,9 @@ async function runImageModeInit(name, opts) {
|
|
|
9162
9659
|
success("Authenticated to ghcr.io");
|
|
9163
9660
|
}
|
|
9164
9661
|
step(3, totalSteps, "Generate");
|
|
9165
|
-
|
|
9662
|
+
mkdirSync15(targetDir, { recursive: true });
|
|
9166
9663
|
const composeContent = generateComposeYaml("latest");
|
|
9167
|
-
|
|
9664
|
+
writeFileSync16(join18(targetDir, "docker-compose.yml"), composeContent, "utf-8");
|
|
9168
9665
|
success("docker-compose.yml");
|
|
9169
9666
|
const envLines = [
|
|
9170
9667
|
"# BeastMode environment \u2014 DO NOT COMMIT",
|
|
@@ -9204,10 +9701,10 @@ async function runImageModeInit(name, opts) {
|
|
|
9204
9701
|
"# PROJECT_REPO=owner/repo",
|
|
9205
9702
|
""
|
|
9206
9703
|
];
|
|
9207
|
-
|
|
9704
|
+
writeFileSync16(join18(targetDir, ".env"), envLines.join("\n"), "utf-8");
|
|
9208
9705
|
success(`.env (PROJECT_DIR=${projectPath}, two-PAT model)`);
|
|
9209
9706
|
for (const dir of ["data", "runs", "daemon/logs", ".beastmode", "config"]) {
|
|
9210
|
-
|
|
9707
|
+
mkdirSync15(join18(targetDir, dir), { recursive: true });
|
|
9211
9708
|
}
|
|
9212
9709
|
step(4, totalSteps, "Pull Images");
|
|
9213
9710
|
try {
|
|
@@ -9296,20 +9793,20 @@ async function runImageModeInit(name, opts) {
|
|
|
9296
9793
|
init_export_adapter();
|
|
9297
9794
|
init_display();
|
|
9298
9795
|
import { Command as Command4 } from "commander";
|
|
9299
|
-
import { readFileSync as
|
|
9300
|
-
import { resolve as resolve7, join as
|
|
9796
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync17, existsSync as existsSync21, readdirSync as readdirSync9 } from "fs";
|
|
9797
|
+
import { resolve as resolve7, join as join19 } from "path";
|
|
9301
9798
|
function exportFactory(factoryDir, outputPath) {
|
|
9302
|
-
const bmDir =
|
|
9303
|
-
const configPath =
|
|
9304
|
-
if (!
|
|
9799
|
+
const bmDir = join19(factoryDir, ".beastmode");
|
|
9800
|
+
const configPath = join19(bmDir, "config.json");
|
|
9801
|
+
if (!existsSync21(configPath)) {
|
|
9305
9802
|
throw new Error(`No factory found at ${factoryDir}`);
|
|
9306
9803
|
}
|
|
9307
|
-
const config = JSON.parse(
|
|
9308
|
-
const identity = JSON.parse(
|
|
9804
|
+
const config = JSON.parse(readFileSync18(configPath, "utf-8"));
|
|
9805
|
+
const identity = JSON.parse(readFileSync18(join19(bmDir, "factory.json"), "utf-8"));
|
|
9309
9806
|
let plugins = [];
|
|
9310
|
-
const lockPath =
|
|
9311
|
-
if (
|
|
9312
|
-
const lock = JSON.parse(
|
|
9807
|
+
const lockPath = join19(bmDir, "extensions.lock");
|
|
9808
|
+
if (existsSync21(lockPath)) {
|
|
9809
|
+
const lock = JSON.parse(readFileSync18(lockPath, "utf-8"));
|
|
9313
9810
|
plugins = Object.keys(lock.plugins || {});
|
|
9314
9811
|
}
|
|
9315
9812
|
const template = {
|
|
@@ -9318,7 +9815,7 @@ function exportFactory(factoryDir, outputPath) {
|
|
|
9318
9815
|
config,
|
|
9319
9816
|
plugins
|
|
9320
9817
|
};
|
|
9321
|
-
|
|
9818
|
+
writeFileSync17(outputPath, JSON.stringify(template, null, 2) + "\n", "utf-8");
|
|
9322
9819
|
}
|
|
9323
9820
|
function findRunDir(runId) {
|
|
9324
9821
|
const candidates = [
|
|
@@ -9326,7 +9823,7 @@ function findRunDir(runId) {
|
|
|
9326
9823
|
resolve7(".", runId)
|
|
9327
9824
|
];
|
|
9328
9825
|
for (const candidate of candidates) {
|
|
9329
|
-
if (
|
|
9826
|
+
if (existsSync21(candidate)) {
|
|
9330
9827
|
return candidate;
|
|
9331
9828
|
}
|
|
9332
9829
|
}
|
|
@@ -9340,23 +9837,23 @@ function createArtifactExportCommand(artifact) {
|
|
|
9340
9837
|
const runDir = findRunDir(opts.run);
|
|
9341
9838
|
let sourceContent;
|
|
9342
9839
|
if (artifact === "scenarios") {
|
|
9343
|
-
const scenariosDir =
|
|
9344
|
-
if (!
|
|
9840
|
+
const scenariosDir = join19(runDir, "scenarios");
|
|
9841
|
+
if (!existsSync21(scenariosDir)) {
|
|
9345
9842
|
throw new Error(`No scenarios directory found in run ${opts.run}`);
|
|
9346
9843
|
}
|
|
9347
|
-
const files =
|
|
9348
|
-
sourceContent = files.map((f) =>
|
|
9844
|
+
const files = readdirSync9(scenariosDir).filter((f) => f.endsWith(".md")).sort();
|
|
9845
|
+
sourceContent = files.map((f) => readFileSync18(join19(scenariosDir, f), "utf-8")).join("\n\n---\n\n");
|
|
9349
9846
|
} else {
|
|
9350
9847
|
const artifactFile = artifact === "nlspec" ? "nlspec.md" : "plan.md";
|
|
9351
|
-
const artifactPath =
|
|
9352
|
-
if (!
|
|
9848
|
+
const artifactPath = join19(runDir, artifactFile);
|
|
9849
|
+
if (!existsSync21(artifactPath)) {
|
|
9353
9850
|
throw new Error(`${artifactFile} not found in run ${opts.run}`);
|
|
9354
9851
|
}
|
|
9355
|
-
sourceContent =
|
|
9852
|
+
sourceContent = readFileSync18(artifactPath, "utf-8");
|
|
9356
9853
|
}
|
|
9357
9854
|
const result = runExportAdapter(opts.adapter, sourceContent);
|
|
9358
9855
|
if (opts.output) {
|
|
9359
|
-
|
|
9856
|
+
writeFileSync17(resolve7(opts.output), result, "utf-8");
|
|
9360
9857
|
header(`Exported ${artifact}`);
|
|
9361
9858
|
success(`Written to: ${opts.output}`);
|
|
9362
9859
|
} else {
|
|
@@ -9580,15 +10077,15 @@ init_presets();
|
|
|
9580
10077
|
init_display();
|
|
9581
10078
|
import { Command as Command8 } from "commander";
|
|
9582
10079
|
import { resolve as resolve11 } from "path";
|
|
9583
|
-
import { readFileSync as
|
|
9584
|
-
import { join as
|
|
10080
|
+
import { readFileSync as readFileSync19, existsSync as existsSync22 } from "fs";
|
|
10081
|
+
import { join as join20 } from "path";
|
|
9585
10082
|
function listPluginsAction(factoryDir) {
|
|
9586
|
-
const lockPath =
|
|
9587
|
-
if (!
|
|
10083
|
+
const lockPath = join20(factoryDir, ".beastmode", "extensions.lock");
|
|
10084
|
+
if (!existsSync22(lockPath)) {
|
|
9588
10085
|
console.log(" No plugins installed (extensions.lock not found).");
|
|
9589
10086
|
return;
|
|
9590
10087
|
}
|
|
9591
|
-
const lock = JSON.parse(
|
|
10088
|
+
const lock = JSON.parse(readFileSync19(lockPath, "utf-8"));
|
|
9592
10089
|
const plugins = lock.plugins || {};
|
|
9593
10090
|
const names = Object.keys(plugins);
|
|
9594
10091
|
if (names.length === 0) {
|
|
@@ -9687,31 +10184,31 @@ var listCommand = new Command8("list").description("List installed extensions an
|
|
|
9687
10184
|
init_import_adapter();
|
|
9688
10185
|
init_display();
|
|
9689
10186
|
import { Command as Command9 } from "commander";
|
|
9690
|
-
import { readFileSync as
|
|
9691
|
-
import { resolve as resolve12, join as
|
|
10187
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync18, mkdirSync as mkdirSync16, existsSync as existsSync23 } from "fs";
|
|
10188
|
+
import { resolve as resolve12, join as join21 } from "path";
|
|
9692
10189
|
var VALID_ARTIFACTS = ["nlspec", "plan", "scenarios"];
|
|
9693
10190
|
function createArtifactCommand(artifact) {
|
|
9694
10191
|
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) => {
|
|
9695
10192
|
try {
|
|
9696
10193
|
const sourcePath = resolve12(opts.from);
|
|
9697
|
-
if (!
|
|
10194
|
+
if (!existsSync23(sourcePath)) {
|
|
9698
10195
|
throw new Error(`Source file not found: ${sourcePath}`);
|
|
9699
10196
|
}
|
|
9700
|
-
const sourceContent =
|
|
10197
|
+
const sourceContent = readFileSync20(sourcePath, "utf-8");
|
|
9701
10198
|
const result = runImportAdapter(opts.adapter, sourceContent);
|
|
9702
10199
|
if (opts.output) {
|
|
9703
10200
|
const outputPath = resolve12(opts.output);
|
|
9704
10201
|
if (artifact === "scenarios" && opts.adapter.endsWith(":test-cases")) {
|
|
9705
10202
|
const scenarios = JSON.parse(result);
|
|
9706
|
-
|
|
10203
|
+
mkdirSync16(outputPath, { recursive: true });
|
|
9707
10204
|
for (const scenario of scenarios) {
|
|
9708
|
-
const filePath =
|
|
9709
|
-
|
|
10205
|
+
const filePath = join21(outputPath, `${scenario.name}.md`);
|
|
10206
|
+
writeFileSync18(filePath, scenario.content, "utf-8");
|
|
9710
10207
|
success(` ${scenario.name}.md`);
|
|
9711
10208
|
}
|
|
9712
10209
|
header(`Imported ${scenarios.length} scenarios to ${opts.output}`);
|
|
9713
10210
|
} else {
|
|
9714
|
-
|
|
10211
|
+
writeFileSync18(outputPath, result, "utf-8");
|
|
9715
10212
|
header(`Imported ${artifact}`);
|
|
9716
10213
|
success(`Written to: ${opts.output}`);
|
|
9717
10214
|
}
|
|
@@ -9731,55 +10228,56 @@ for (const artifact of VALID_ARTIFACTS) {
|
|
|
9731
10228
|
|
|
9732
10229
|
// src/cli/commands/status.ts
|
|
9733
10230
|
init_status_checker();
|
|
10231
|
+
init_engine();
|
|
9734
10232
|
init_schemas();
|
|
9735
10233
|
init_display();
|
|
9736
10234
|
import { Command as Command10 } from "commander";
|
|
9737
|
-
import { existsSync as
|
|
10235
|
+
import { existsSync as existsSync24, readFileSync as readFileSync21, readdirSync as readdirSync10 } from "fs";
|
|
9738
10236
|
import { execSync as execSync6 } from "child_process";
|
|
9739
|
-
import { join as
|
|
10237
|
+
import { join as join22, resolve as resolve13 } from "path";
|
|
9740
10238
|
function statusAction(factoryDir, opts) {
|
|
9741
|
-
const bmDir =
|
|
9742
|
-
if (!
|
|
10239
|
+
const bmDir = join22(factoryDir, ".beastmode");
|
|
10240
|
+
if (!existsSync24(bmDir)) {
|
|
9743
10241
|
throw new Error(`No factory found at ${factoryDir}`);
|
|
9744
10242
|
}
|
|
9745
|
-
const factoryJsonPath =
|
|
9746
|
-
const rawIdentity = JSON.parse(
|
|
10243
|
+
const factoryJsonPath = join22(bmDir, "factory.json");
|
|
10244
|
+
const rawIdentity = JSON.parse(readFileSync21(factoryJsonPath, "utf-8"));
|
|
9747
10245
|
const factoryIdentity = FactoryIdentitySchema.parse(rawIdentity);
|
|
9748
|
-
const projectsDir =
|
|
9749
|
-
const projectCount =
|
|
9750
|
-
const pluginsDir =
|
|
9751
|
-
const pluginNames =
|
|
9752
|
-
const mcpPath =
|
|
10246
|
+
const projectsDir = join22(bmDir, "projects");
|
|
10247
|
+
const projectCount = listProjectRecords(projectsDir).length;
|
|
10248
|
+
const pluginsDir = join22(bmDir, "plugins");
|
|
10249
|
+
const pluginNames = existsSync24(pluginsDir) ? readdirSync10(pluginsDir) : [];
|
|
10250
|
+
const mcpPath = join22(bmDir, "mcp-servers.json");
|
|
9753
10251
|
let mcpServers = {};
|
|
9754
|
-
if (
|
|
10252
|
+
if (existsSync24(mcpPath)) {
|
|
9755
10253
|
try {
|
|
9756
|
-
const raw = JSON.parse(
|
|
10254
|
+
const raw = JSON.parse(readFileSync21(mcpPath, "utf-8"));
|
|
9757
10255
|
mcpServers = raw.servers || {};
|
|
9758
10256
|
} catch {
|
|
9759
10257
|
}
|
|
9760
10258
|
}
|
|
9761
|
-
const hooksPath =
|
|
10259
|
+
const hooksPath = join22(bmDir, "hooks.json");
|
|
9762
10260
|
let hooks = {};
|
|
9763
|
-
if (
|
|
10261
|
+
if (existsSync24(hooksPath)) {
|
|
9764
10262
|
try {
|
|
9765
|
-
const raw = JSON.parse(
|
|
10263
|
+
const raw = JSON.parse(readFileSync21(hooksPath, "utf-8"));
|
|
9766
10264
|
hooks = raw.hooks || {};
|
|
9767
10265
|
} catch {
|
|
9768
10266
|
}
|
|
9769
10267
|
}
|
|
9770
|
-
const skillsDir =
|
|
9771
|
-
const skillCount =
|
|
9772
|
-
const runsDir =
|
|
10268
|
+
const skillsDir = join22(bmDir, "skills");
|
|
10269
|
+
const skillCount = existsSync24(skillsDir) ? readdirSync10(skillsDir).length : 0;
|
|
10270
|
+
const runsDir = join22(factoryDir, "runs");
|
|
9773
10271
|
let runDirs = [];
|
|
9774
|
-
if (
|
|
9775
|
-
runDirs =
|
|
10272
|
+
if (existsSync24(runsDir)) {
|
|
10273
|
+
runDirs = readdirSync10(runsDir).filter((d) => d.startsWith("run-")).sort();
|
|
9776
10274
|
}
|
|
9777
|
-
const pidPath =
|
|
10275
|
+
const pidPath = join22(bmDir, "daemon.pid");
|
|
9778
10276
|
let daemonPid = null;
|
|
9779
10277
|
let pidAlive = false;
|
|
9780
|
-
if (
|
|
10278
|
+
if (existsSync24(pidPath)) {
|
|
9781
10279
|
try {
|
|
9782
|
-
daemonPid = parseInt(
|
|
10280
|
+
daemonPid = parseInt(readFileSync21(pidPath, "utf-8").trim(), 10);
|
|
9783
10281
|
process.kill(daemonPid, 0);
|
|
9784
10282
|
pidAlive = true;
|
|
9785
10283
|
} catch {
|
|
@@ -9853,19 +10351,19 @@ var statusCommand = new Command10("status").description("Show factory status ove
|
|
|
9853
10351
|
init_config_manager();
|
|
9854
10352
|
init_display();
|
|
9855
10353
|
import { Command as Command11 } from "commander";
|
|
9856
|
-
import { existsSync as
|
|
9857
|
-
import { join as
|
|
10354
|
+
import { existsSync as existsSync25, readFileSync as readFileSync22, writeFileSync as writeFileSync19 } from "fs";
|
|
10355
|
+
import { join as join23, resolve as resolve14 } from "path";
|
|
9858
10356
|
import { execSync as execSync7 } from "child_process";
|
|
9859
10357
|
function readConfig2(factoryDir) {
|
|
9860
|
-
const configPath =
|
|
9861
|
-
if (!
|
|
10358
|
+
const configPath = join23(factoryDir, ".beastmode", "config.json");
|
|
10359
|
+
if (!existsSync25(configPath)) {
|
|
9862
10360
|
throw new Error("No config.json found. Run beastmode init first.");
|
|
9863
10361
|
}
|
|
9864
|
-
return JSON.parse(
|
|
10362
|
+
return JSON.parse(readFileSync22(configPath, "utf-8"));
|
|
9865
10363
|
}
|
|
9866
10364
|
function writeConfig2(factoryDir, config) {
|
|
9867
|
-
const configPath =
|
|
9868
|
-
|
|
10365
|
+
const configPath = join23(factoryDir, ".beastmode", "config.json");
|
|
10366
|
+
writeFileSync19(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
9869
10367
|
}
|
|
9870
10368
|
function configGetAction(factoryDir, key) {
|
|
9871
10369
|
const config = readConfig2(factoryDir);
|
|
@@ -9908,8 +10406,8 @@ var configSetCmd = new Command11("set").description("Set a config value by dot-n
|
|
|
9908
10406
|
});
|
|
9909
10407
|
var configEditCmd = new Command11("edit").description("Open config.json in $EDITOR").action(() => {
|
|
9910
10408
|
const factoryDir = resolve14(".");
|
|
9911
|
-
const configPath =
|
|
9912
|
-
if (!
|
|
10409
|
+
const configPath = join23(factoryDir, ".beastmode", "config.json");
|
|
10410
|
+
if (!existsSync25(configPath)) {
|
|
9913
10411
|
error("No config.json found. Run beastmode init first.");
|
|
9914
10412
|
process.exit(1);
|
|
9915
10413
|
}
|
|
@@ -9939,13 +10437,14 @@ var configCommand = new Command11("config").description("Manage factory configur
|
|
|
9939
10437
|
|
|
9940
10438
|
// src/cli/commands/doctor.ts
|
|
9941
10439
|
init_doctor();
|
|
10440
|
+
init_engine();
|
|
9942
10441
|
init_schemas();
|
|
9943
10442
|
init_version();
|
|
9944
10443
|
init_plugin_resolver();
|
|
9945
10444
|
init_display();
|
|
9946
10445
|
import { Command as Command12 } from "commander";
|
|
9947
|
-
import { existsSync as
|
|
9948
|
-
import { join as
|
|
10446
|
+
import { existsSync as existsSync26, readFileSync as readFileSync23, readdirSync as readdirSync11 } from "fs";
|
|
10447
|
+
import { join as join24, resolve as resolve15 } from "path";
|
|
9949
10448
|
import { execSync as execSync8, spawnSync as spawnSync3 } from "child_process";
|
|
9950
10449
|
import { homedir as homedir3, platform as platform2 } from "os";
|
|
9951
10450
|
import * as http3 from "http";
|
|
@@ -9986,8 +10485,8 @@ async function checkClaudeAuth(key) {
|
|
|
9986
10485
|
});
|
|
9987
10486
|
}
|
|
9988
10487
|
if (claudeInstalled && platform2() === "darwin" && !key) {
|
|
9989
|
-
const credsPath =
|
|
9990
|
-
if (!
|
|
10488
|
+
const credsPath = join24(homedir3(), ".claude", ".credentials.json");
|
|
10489
|
+
if (!existsSync26(credsPath)) {
|
|
9991
10490
|
results.push({
|
|
9992
10491
|
label: "Claude creds (Docker)",
|
|
9993
10492
|
status: "warn",
|
|
@@ -10089,16 +10588,16 @@ async function checkProjectGithubToken(env, factoryDir) {
|
|
|
10089
10588
|
}
|
|
10090
10589
|
let ownerRepo = null;
|
|
10091
10590
|
if (factoryDir) {
|
|
10092
|
-
const envPath =
|
|
10093
|
-
if (
|
|
10591
|
+
const envPath = join24(factoryDir, ".env");
|
|
10592
|
+
if (existsSync26(envPath)) {
|
|
10094
10593
|
try {
|
|
10095
|
-
const content =
|
|
10594
|
+
const content = readFileSync23(envPath, "utf-8");
|
|
10096
10595
|
const dirLine = content.split("\n").find(
|
|
10097
10596
|
(l) => l.trim().startsWith("PROJECT_DIR=") && !l.trim().startsWith("#")
|
|
10098
10597
|
);
|
|
10099
10598
|
if (dirLine) {
|
|
10100
10599
|
const projectDir = dirLine.split("=").slice(1).join("=").trim();
|
|
10101
|
-
if (projectDir &&
|
|
10600
|
+
if (projectDir && existsSync26(join24(projectDir, ".git"))) {
|
|
10102
10601
|
const remote = tryExec(
|
|
10103
10602
|
`git -C "${projectDir}" remote get-url origin 2>/dev/null`
|
|
10104
10603
|
);
|
|
@@ -10314,8 +10813,8 @@ function checkProjectDirEnv(factoryDir) {
|
|
|
10314
10813
|
detail: "no factory in scope"
|
|
10315
10814
|
};
|
|
10316
10815
|
}
|
|
10317
|
-
const envPath =
|
|
10318
|
-
if (!
|
|
10816
|
+
const envPath = join24(factoryDir, ".env");
|
|
10817
|
+
if (!existsSync26(envPath)) {
|
|
10319
10818
|
return {
|
|
10320
10819
|
label: "PROJECT_DIR (.env)",
|
|
10321
10820
|
status: "fail",
|
|
@@ -10325,7 +10824,7 @@ function checkProjectDirEnv(factoryDir) {
|
|
|
10325
10824
|
}
|
|
10326
10825
|
let content;
|
|
10327
10826
|
try {
|
|
10328
|
-
content =
|
|
10827
|
+
content = readFileSync23(envPath, "utf-8");
|
|
10329
10828
|
} catch {
|
|
10330
10829
|
return {
|
|
10331
10830
|
label: "PROJECT_DIR (.env)",
|
|
@@ -10354,7 +10853,7 @@ function checkProjectDirEnv(factoryDir) {
|
|
|
10354
10853
|
fix: "Re-run `beastmode init --project <path>` to populate it"
|
|
10355
10854
|
};
|
|
10356
10855
|
}
|
|
10357
|
-
if (!
|
|
10856
|
+
if (!existsSync26(value)) {
|
|
10358
10857
|
return {
|
|
10359
10858
|
label: "PROJECT_DIR (.env)",
|
|
10360
10859
|
status: "fail",
|
|
@@ -10362,7 +10861,7 @@ function checkProjectDirEnv(factoryDir) {
|
|
|
10362
10861
|
fix: `Clone your project to ${value} or re-run 'beastmode init --project <path>'`
|
|
10363
10862
|
};
|
|
10364
10863
|
}
|
|
10365
|
-
if (!
|
|
10864
|
+
if (!existsSync26(join24(value, ".git"))) {
|
|
10366
10865
|
return {
|
|
10367
10866
|
label: "PROJECT_DIR (.env)",
|
|
10368
10867
|
status: "fail",
|
|
@@ -10393,8 +10892,8 @@ async function checkFactoryContainers(factoryDir) {
|
|
|
10393
10892
|
detail: "no factory in scope"
|
|
10394
10893
|
};
|
|
10395
10894
|
}
|
|
10396
|
-
const composePath =
|
|
10397
|
-
if (!
|
|
10895
|
+
const composePath = join24(factoryDir, "docker-compose.yml");
|
|
10896
|
+
if (!existsSync26(composePath)) {
|
|
10398
10897
|
return {
|
|
10399
10898
|
label: "Factory containers",
|
|
10400
10899
|
status: "warn",
|
|
@@ -10676,9 +11175,9 @@ function checkProjectDirectory(factoryDir) {
|
|
|
10676
11175
|
fix: "Run beastmode init to create a factory"
|
|
10677
11176
|
};
|
|
10678
11177
|
}
|
|
10679
|
-
const bmDir =
|
|
10680
|
-
const projectsDir =
|
|
10681
|
-
if (!
|
|
11178
|
+
const bmDir = join24(factoryDir, ".beastmode");
|
|
11179
|
+
const projectsDir = join24(bmDir, "projects");
|
|
11180
|
+
if (!existsSync26(projectsDir)) {
|
|
10682
11181
|
return {
|
|
10683
11182
|
label: "Project directory",
|
|
10684
11183
|
status: "warn",
|
|
@@ -10686,8 +11185,8 @@ function checkProjectDirectory(factoryDir) {
|
|
|
10686
11185
|
fix: "Run: beastmode add project <path>"
|
|
10687
11186
|
};
|
|
10688
11187
|
}
|
|
10689
|
-
const
|
|
10690
|
-
if (
|
|
11188
|
+
const projectRecords = listProjectRecords(projectsDir);
|
|
11189
|
+
if (projectRecords.length === 0) {
|
|
10691
11190
|
return {
|
|
10692
11191
|
label: "Project directory",
|
|
10693
11192
|
status: "warn",
|
|
@@ -10697,37 +11196,31 @@ function checkProjectDirectory(factoryDir) {
|
|
|
10697
11196
|
}
|
|
10698
11197
|
const results = [];
|
|
10699
11198
|
let anyFail = false;
|
|
10700
|
-
for (const
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
if (!projPath || !existsSync25(projPath)) {
|
|
10706
|
-
results.push(`${projName}: path not found`);
|
|
10707
|
-
anyFail = true;
|
|
10708
|
-
continue;
|
|
10709
|
-
}
|
|
10710
|
-
const isGit = existsSync25(join23(projPath, ".git"));
|
|
10711
|
-
const manifests = [
|
|
10712
|
-
"package.json",
|
|
10713
|
-
"Cargo.toml",
|
|
10714
|
-
"go.mod",
|
|
10715
|
-
"pyproject.toml",
|
|
10716
|
-
"requirements.txt",
|
|
10717
|
-
"pom.xml",
|
|
10718
|
-
"build.gradle",
|
|
10719
|
-
"build.gradle.kts"
|
|
10720
|
-
];
|
|
10721
|
-
const manifest = manifests.find((m) => existsSync25(join23(projPath, m)));
|
|
10722
|
-
const framework = proj.stack?.detected || manifest?.replace(".json", "") || "unknown";
|
|
10723
|
-
results.push(
|
|
10724
|
-
`${projName}: ${projPath} (${framework})${isGit ? "" : " [no .git]"}`
|
|
10725
|
-
);
|
|
10726
|
-
if (!isGit) anyFail = true;
|
|
10727
|
-
} catch {
|
|
10728
|
-
results.push(`${file}: unreadable`);
|
|
11199
|
+
for (const proj of projectRecords) {
|
|
11200
|
+
const projPath = proj.path || "";
|
|
11201
|
+
const projName = proj.name;
|
|
11202
|
+
if (!projPath || !existsSync26(projPath)) {
|
|
11203
|
+
results.push(`${projName}: path not found`);
|
|
10729
11204
|
anyFail = true;
|
|
11205
|
+
continue;
|
|
10730
11206
|
}
|
|
11207
|
+
const isGit = existsSync26(join24(projPath, ".git"));
|
|
11208
|
+
const manifests = [
|
|
11209
|
+
"package.json",
|
|
11210
|
+
"Cargo.toml",
|
|
11211
|
+
"go.mod",
|
|
11212
|
+
"pyproject.toml",
|
|
11213
|
+
"requirements.txt",
|
|
11214
|
+
"pom.xml",
|
|
11215
|
+
"build.gradle",
|
|
11216
|
+
"build.gradle.kts"
|
|
11217
|
+
];
|
|
11218
|
+
const manifest = manifests.find((m) => existsSync26(join24(projPath, m)));
|
|
11219
|
+
const framework = proj.stack?.detected || manifest?.replace(".json", "") || "unknown";
|
|
11220
|
+
results.push(
|
|
11221
|
+
`${projName}: ${projPath} (${framework})${isGit ? "" : " [no .git]"}`
|
|
11222
|
+
);
|
|
11223
|
+
if (!isGit) anyFail = true;
|
|
10731
11224
|
}
|
|
10732
11225
|
if (anyFail) {
|
|
10733
11226
|
return {
|
|
@@ -10772,25 +11265,22 @@ function checkStack(factoryDir) {
|
|
|
10772
11265
|
detail: "no factory \u2014 run beastmode init"
|
|
10773
11266
|
};
|
|
10774
11267
|
}
|
|
10775
|
-
const projectsDir =
|
|
10776
|
-
if (!
|
|
11268
|
+
const projectsDir = join24(factoryDir, ".beastmode", "projects");
|
|
11269
|
+
if (!existsSync26(projectsDir)) {
|
|
10777
11270
|
return { label: "Stack", status: "warn", detail: "no projects configured" };
|
|
10778
11271
|
}
|
|
10779
|
-
const
|
|
10780
|
-
if (
|
|
11272
|
+
const projectRecords = listProjectRecords(projectsDir);
|
|
11273
|
+
if (projectRecords.length === 0) {
|
|
10781
11274
|
return { label: "Stack", status: "warn", detail: "no projects configured" };
|
|
10782
11275
|
}
|
|
10783
11276
|
const stacks = [];
|
|
10784
|
-
for (const
|
|
10785
|
-
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
stacks.push(`${name}: ${detected} \u2014 ${build}, port ${port}`);
|
|
10792
|
-
} catch {
|
|
10793
|
-
}
|
|
11277
|
+
for (const proj of projectRecords) {
|
|
11278
|
+
const name = proj.name;
|
|
11279
|
+
const stackInfo = proj.stack;
|
|
11280
|
+
const detected = stackInfo?.detected || "unknown";
|
|
11281
|
+
const build = stackInfo?.build_command || "";
|
|
11282
|
+
const port = stackInfo?.dev_port || 3e3;
|
|
11283
|
+
stacks.push(`${name}: ${detected} \u2014 ${build}, port ${port}`);
|
|
10794
11284
|
}
|
|
10795
11285
|
if (stacks.length === 0) {
|
|
10796
11286
|
return { label: "Stack", status: "warn", detail: "no readable project configs" };
|
|
@@ -10827,12 +11317,12 @@ function checkBoardPassword(env, factoryDir) {
|
|
|
10827
11317
|
return { label: "Board password", status: "pass", detail: "set" };
|
|
10828
11318
|
}
|
|
10829
11319
|
if (factoryDir) {
|
|
10830
|
-
const dotEnv =
|
|
10831
|
-
const secretsEnv =
|
|
11320
|
+
const dotEnv = join24(factoryDir, ".env");
|
|
11321
|
+
const secretsEnv = join24(factoryDir, ".beastmode", "secrets.env.local");
|
|
10832
11322
|
for (const filePath of [dotEnv, secretsEnv]) {
|
|
10833
|
-
if (
|
|
11323
|
+
if (existsSync26(filePath)) {
|
|
10834
11324
|
try {
|
|
10835
|
-
const content =
|
|
11325
|
+
const content = readFileSync23(filePath, "utf-8");
|
|
10836
11326
|
const lines = content.split("\n");
|
|
10837
11327
|
const line = lines.find((l) => l.startsWith("BEASTMODE_UI_PASSWORD="));
|
|
10838
11328
|
if (line) {
|
|
@@ -10854,12 +11344,12 @@ function checkBoardPassword(env, factoryDir) {
|
|
|
10854
11344
|
};
|
|
10855
11345
|
}
|
|
10856
11346
|
function doctorAction(factoryDir, env) {
|
|
10857
|
-
const bmDir =
|
|
10858
|
-
const factoryDirExists =
|
|
11347
|
+
const bmDir = join24(factoryDir, ".beastmode");
|
|
11348
|
+
const factoryDirExists = existsSync26(bmDir);
|
|
10859
11349
|
let factoryIdentity = null;
|
|
10860
11350
|
if (factoryDirExists) {
|
|
10861
11351
|
try {
|
|
10862
|
-
const raw = JSON.parse(
|
|
11352
|
+
const raw = JSON.parse(readFileSync23(join24(bmDir, "factory.json"), "utf-8"));
|
|
10863
11353
|
factoryIdentity = FactoryIdentitySchema.parse(raw);
|
|
10864
11354
|
} catch {
|
|
10865
11355
|
}
|
|
@@ -10867,10 +11357,10 @@ function doctorAction(factoryDir, env) {
|
|
|
10867
11357
|
let config = null;
|
|
10868
11358
|
let configParseError = null;
|
|
10869
11359
|
if (factoryDirExists) {
|
|
10870
|
-
const configPath =
|
|
10871
|
-
if (
|
|
11360
|
+
const configPath = join24(bmDir, "config.json");
|
|
11361
|
+
if (existsSync26(configPath)) {
|
|
10872
11362
|
try {
|
|
10873
|
-
const raw = JSON.parse(
|
|
11363
|
+
const raw = JSON.parse(readFileSync23(configPath, "utf-8"));
|
|
10874
11364
|
const result = FactoryConfigSchema.safeParse(raw);
|
|
10875
11365
|
if (result.success) {
|
|
10876
11366
|
config = raw;
|
|
@@ -10886,34 +11376,26 @@ function doctorAction(factoryDir, env) {
|
|
|
10886
11376
|
}
|
|
10887
11377
|
const projectPaths = [];
|
|
10888
11378
|
if (factoryDirExists) {
|
|
10889
|
-
const projectsDir =
|
|
10890
|
-
|
|
10891
|
-
|
|
10892
|
-
|
|
10893
|
-
|
|
10894
|
-
|
|
10895
|
-
|
|
10896
|
-
|
|
10897
|
-
name: proj.name || file.replace(".json", ""),
|
|
10898
|
-
path: proj.path,
|
|
10899
|
-
exists: existsSync25(proj.path)
|
|
10900
|
-
});
|
|
10901
|
-
}
|
|
10902
|
-
} catch {
|
|
10903
|
-
}
|
|
10904
|
-
}
|
|
11379
|
+
const projectsDir = join24(bmDir, "projects");
|
|
11380
|
+
for (const proj of listProjectRecords(projectsDir)) {
|
|
11381
|
+
if (proj.path) {
|
|
11382
|
+
projectPaths.push({
|
|
11383
|
+
name: proj.name,
|
|
11384
|
+
path: proj.path,
|
|
11385
|
+
exists: existsSync26(proj.path)
|
|
11386
|
+
});
|
|
10905
11387
|
}
|
|
10906
11388
|
}
|
|
10907
11389
|
}
|
|
10908
11390
|
const installedPlugins = [];
|
|
10909
11391
|
if (factoryDirExists) {
|
|
10910
|
-
const pluginsDir =
|
|
10911
|
-
if (
|
|
10912
|
-
for (const pluginName of
|
|
10913
|
-
const manifestPath =
|
|
10914
|
-
if (
|
|
11392
|
+
const pluginsDir = join24(bmDir, "plugins");
|
|
11393
|
+
if (existsSync26(pluginsDir)) {
|
|
11394
|
+
for (const pluginName of readdirSync11(pluginsDir)) {
|
|
11395
|
+
const manifestPath = join24(pluginsDir, pluginName, "manifest.json");
|
|
11396
|
+
if (existsSync26(manifestPath)) {
|
|
10915
11397
|
try {
|
|
10916
|
-
const manifest = JSON.parse(
|
|
11398
|
+
const manifest = JSON.parse(readFileSync23(manifestPath, "utf-8"));
|
|
10917
11399
|
installedPlugins.push({
|
|
10918
11400
|
name: pluginName,
|
|
10919
11401
|
engine_version: manifest.engine_version || "*"
|
|
@@ -10941,8 +11423,8 @@ var SYSTEMD_CREDS_TIMER = "beastmode-claude-creds.timer";
|
|
|
10941
11423
|
var LAUNCH_AGENT_LABEL2 = "com.develeap.beastmode.claude-creds";
|
|
10942
11424
|
function checkCredentialSyncAgent() {
|
|
10943
11425
|
const results = [];
|
|
10944
|
-
const credsPath =
|
|
10945
|
-
if (!
|
|
11426
|
+
const credsPath = join24(homedir3(), ".claude", ".credentials.json");
|
|
11427
|
+
if (!existsSync26(credsPath)) {
|
|
10946
11428
|
results.push({
|
|
10947
11429
|
label: "Claude credentials file",
|
|
10948
11430
|
status: "fail",
|
|
@@ -10957,7 +11439,7 @@ function checkCredentialSyncAgent() {
|
|
|
10957
11439
|
detail: credsPath
|
|
10958
11440
|
});
|
|
10959
11441
|
try {
|
|
10960
|
-
const creds = JSON.parse(
|
|
11442
|
+
const creds = JSON.parse(readFileSync23(credsPath, "utf-8"));
|
|
10961
11443
|
const expiresAt = creds?.claudeAiOauth?.expiresAt;
|
|
10962
11444
|
if (typeof expiresAt === "number") {
|
|
10963
11445
|
const hoursLeft = Math.round((expiresAt - Date.now()) / 36e5);
|
|
@@ -11273,7 +11755,7 @@ var doctorCommand = new Command12("doctor").description("Health check \u2014 val
|
|
|
11273
11755
|
console.log();
|
|
11274
11756
|
const env = process.env;
|
|
11275
11757
|
const factoryDir = findFactoryDir() ?? resolve15(".");
|
|
11276
|
-
const hasFactory =
|
|
11758
|
+
const hasFactory = existsSync26(join24(factoryDir, ".beastmode"));
|
|
11277
11759
|
const checks = [];
|
|
11278
11760
|
checks.push(...await checkClaudeAuth(env.ANTHROPIC_API_KEY));
|
|
11279
11761
|
checks.push(...checkCredentialSyncAgent());
|
|
@@ -11349,12 +11831,12 @@ init_schemas();
|
|
|
11349
11831
|
init_version();
|
|
11350
11832
|
init_display();
|
|
11351
11833
|
import { Command as Command13 } from "commander";
|
|
11352
|
-
import { existsSync as
|
|
11353
|
-
import { join as
|
|
11834
|
+
import { existsSync as existsSync28, readFileSync as readFileSync25, writeFileSync as writeFileSync21 } from "fs";
|
|
11835
|
+
import { join as join26, resolve as resolve16 } from "path";
|
|
11354
11836
|
|
|
11355
11837
|
// src/cli/utils/regenerate.ts
|
|
11356
|
-
import { existsSync as
|
|
11357
|
-
import { join as
|
|
11838
|
+
import { existsSync as existsSync27, readFileSync as readFileSync24, writeFileSync as writeFileSync20, copyFileSync } from "fs";
|
|
11839
|
+
import { join as join25 } from "path";
|
|
11358
11840
|
var RECOGNIZED_KEYS = /* @__PURE__ */ new Set([
|
|
11359
11841
|
"PROJECT_DIR",
|
|
11360
11842
|
"PROJECT_GITHUB_TOKEN",
|
|
@@ -11468,19 +11950,19 @@ function buildNewEnv(values) {
|
|
|
11468
11950
|
return lines.join("\n");
|
|
11469
11951
|
}
|
|
11470
11952
|
function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
|
|
11471
|
-
const envPath =
|
|
11472
|
-
const composePath =
|
|
11473
|
-
if (!
|
|
11953
|
+
const envPath = join25(factoryDir, ".env");
|
|
11954
|
+
const composePath = join25(factoryDir, "docker-compose.yml");
|
|
11955
|
+
if (!existsSync27(envPath)) {
|
|
11474
11956
|
throw new Error(
|
|
11475
11957
|
`.env not found at ${envPath}. This does not look like a beastmode factory \u2014 run 'beastmode init' first.`
|
|
11476
11958
|
);
|
|
11477
11959
|
}
|
|
11478
|
-
if (!
|
|
11960
|
+
if (!existsSync27(composePath)) {
|
|
11479
11961
|
throw new Error(
|
|
11480
11962
|
`docker-compose.yml not found at ${composePath}. This does not look like a beastmode factory \u2014 run 'beastmode init' first.`
|
|
11481
11963
|
);
|
|
11482
11964
|
}
|
|
11483
|
-
const existingEnv =
|
|
11965
|
+
const existingEnv = readFileSync24(envPath, "utf-8");
|
|
11484
11966
|
const values = parseExistingEnv(existingEnv);
|
|
11485
11967
|
const missing = [];
|
|
11486
11968
|
if (!values.projectDir) missing.push("PROJECT_DIR");
|
|
@@ -11498,7 +11980,7 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
|
|
|
11498
11980
|
}
|
|
11499
11981
|
const newEnv = buildNewEnv(values);
|
|
11500
11982
|
const newCompose = generateComposeYaml("latest");
|
|
11501
|
-
const existingCompose =
|
|
11983
|
+
const existingCompose = readFileSync24(composePath, "utf-8");
|
|
11502
11984
|
const envChanged = newEnv !== existingEnv;
|
|
11503
11985
|
const composeChanged = newCompose !== existingCompose;
|
|
11504
11986
|
if (!envChanged && !composeChanged) {
|
|
@@ -11516,12 +11998,12 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
|
|
|
11516
11998
|
if (envChanged) {
|
|
11517
11999
|
envBackupPath = `${envPath}.backup.${timestamp}`;
|
|
11518
12000
|
copyFileSync(envPath, envBackupPath);
|
|
11519
|
-
|
|
12001
|
+
writeFileSync20(envPath, newEnv, "utf-8");
|
|
11520
12002
|
}
|
|
11521
12003
|
if (composeChanged) {
|
|
11522
12004
|
composeBackupPath = `${composePath}.backup.${timestamp}`;
|
|
11523
12005
|
copyFileSync(composePath, composeBackupPath);
|
|
11524
|
-
|
|
12006
|
+
writeFileSync20(composePath, newCompose, "utf-8");
|
|
11525
12007
|
}
|
|
11526
12008
|
return {
|
|
11527
12009
|
envChanged,
|
|
@@ -11534,18 +12016,18 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
|
|
|
11534
12016
|
|
|
11535
12017
|
// src/cli/commands/upgrade.ts
|
|
11536
12018
|
function readIdentity(factoryDir) {
|
|
11537
|
-
const path =
|
|
11538
|
-
if (!
|
|
12019
|
+
const path = join26(factoryDir, ".beastmode", "factory.json");
|
|
12020
|
+
if (!existsSync28(path)) {
|
|
11539
12021
|
throw new Error("No factory.json found. Run beastmode init first.");
|
|
11540
12022
|
}
|
|
11541
|
-
return FactoryIdentitySchema.parse(JSON.parse(
|
|
12023
|
+
return FactoryIdentitySchema.parse(JSON.parse(readFileSync25(path, "utf-8")));
|
|
11542
12024
|
}
|
|
11543
12025
|
function readConfig3(factoryDir) {
|
|
11544
|
-
const path =
|
|
11545
|
-
if (!
|
|
12026
|
+
const path = join26(factoryDir, ".beastmode", "config.json");
|
|
12027
|
+
if (!existsSync28(path)) {
|
|
11546
12028
|
return {};
|
|
11547
12029
|
}
|
|
11548
|
-
return JSON.parse(
|
|
12030
|
+
return JSON.parse(readFileSync25(path, "utf-8"));
|
|
11549
12031
|
}
|
|
11550
12032
|
function upgradeCheckAction(factoryDir) {
|
|
11551
12033
|
const identity = readIdentity(factoryDir);
|
|
@@ -11557,12 +12039,12 @@ function upgradeAction(factoryDir, migrateOnly = false) {
|
|
|
11557
12039
|
const targetVersion = migrateOnly ? identity.engine_version : ENGINE_VERSION;
|
|
11558
12040
|
const result = performUpgrade(identity, config, targetVersion, SCHEMA_VERSION);
|
|
11559
12041
|
if (result.changes.length > 0) {
|
|
11560
|
-
|
|
11561
|
-
|
|
12042
|
+
writeFileSync21(
|
|
12043
|
+
join26(factoryDir, ".beastmode", "factory.json"),
|
|
11562
12044
|
JSON.stringify(result.updatedIdentity, null, 2) + "\n"
|
|
11563
12045
|
);
|
|
11564
|
-
|
|
11565
|
-
|
|
12046
|
+
writeFileSync21(
|
|
12047
|
+
join26(factoryDir, ".beastmode", "config.json"),
|
|
11566
12048
|
JSON.stringify(result.updatedConfig, null, 2) + "\n"
|
|
11567
12049
|
);
|
|
11568
12050
|
}
|
|
@@ -11652,8 +12134,8 @@ init_board();
|
|
|
11652
12134
|
init_display();
|
|
11653
12135
|
init_migrator();
|
|
11654
12136
|
import { Command as Command14 } from "commander";
|
|
11655
|
-
import { resolve as resolve17, join as
|
|
11656
|
-
import { existsSync as
|
|
12137
|
+
import { resolve as resolve17, join as join27 } from "path";
|
|
12138
|
+
import { existsSync as existsSync29, readFileSync as readFileSync26, mkdirSync as mkdirSync17, writeFileSync as writeFileSync22 } from "fs";
|
|
11657
12139
|
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) => {
|
|
11658
12140
|
try {
|
|
11659
12141
|
await runMigrate(opts);
|
|
@@ -11665,7 +12147,7 @@ var migrateCommand = new Command14("migrate").description("Migrate a daemon conf
|
|
|
11665
12147
|
async function runMigrate(opts) {
|
|
11666
12148
|
const cwd = process.cwd();
|
|
11667
12149
|
const configPath = opts.config ? resolve17(opts.config) : resolve17(cwd, "config", "beastmode.daemon.json");
|
|
11668
|
-
if (!
|
|
12150
|
+
if (!existsSync29(configPath)) {
|
|
11669
12151
|
throw new Error(
|
|
11670
12152
|
`Daemon config not found at ${configPath}
|
|
11671
12153
|
Use --config <path> to specify a different location.`
|
|
@@ -11673,25 +12155,25 @@ async function runMigrate(opts) {
|
|
|
11673
12155
|
}
|
|
11674
12156
|
header("BeastMode Migrate");
|
|
11675
12157
|
info(`Reading daemon config from: ${configPath}`);
|
|
11676
|
-
const configContent =
|
|
12158
|
+
const configContent = readFileSync26(configPath, "utf-8");
|
|
11677
12159
|
const daemonConfig = parseDaemonConfig(configContent);
|
|
11678
|
-
const runsDir =
|
|
12160
|
+
const runsDir = join27(cwd, "runs");
|
|
11679
12161
|
let runDirs = [];
|
|
11680
12162
|
const checkpoints = /* @__PURE__ */ new Map();
|
|
11681
|
-
if (
|
|
12163
|
+
if (existsSync29(runsDir)) {
|
|
11682
12164
|
const { readdirSync: readdirSync14 } = await import("fs");
|
|
11683
12165
|
runDirs = readdirSync14(runsDir).filter((d) => {
|
|
11684
12166
|
try {
|
|
11685
|
-
return readdirSync14(
|
|
12167
|
+
return readdirSync14(join27(runsDir, d)).length > 0;
|
|
11686
12168
|
} catch {
|
|
11687
12169
|
return false;
|
|
11688
12170
|
}
|
|
11689
12171
|
});
|
|
11690
12172
|
for (const dir of runDirs) {
|
|
11691
|
-
const cpPath =
|
|
11692
|
-
if (
|
|
12173
|
+
const cpPath = join27(runsDir, dir, "checkpoint.json");
|
|
12174
|
+
if (existsSync29(cpPath)) {
|
|
11693
12175
|
try {
|
|
11694
|
-
const cp = JSON.parse(
|
|
12176
|
+
const cp = JSON.parse(readFileSync26(cpPath, "utf-8"));
|
|
11695
12177
|
checkpoints.set(dir, cp);
|
|
11696
12178
|
} catch {
|
|
11697
12179
|
}
|
|
@@ -11748,28 +12230,28 @@ async function runMigrate(opts) {
|
|
|
11748
12230
|
warn("Dry run \u2014 no files written.");
|
|
11749
12231
|
return;
|
|
11750
12232
|
}
|
|
11751
|
-
const bmDir =
|
|
11752
|
-
if (
|
|
12233
|
+
const bmDir = join27(cwd, ".beastmode");
|
|
12234
|
+
if (existsSync29(bmDir)) {
|
|
11753
12235
|
throw new Error(
|
|
11754
12236
|
"A .beastmode/ directory already exists. Remove it first to re-migrate."
|
|
11755
12237
|
);
|
|
11756
12238
|
}
|
|
11757
12239
|
for (const file of files) {
|
|
11758
|
-
const fullPath =
|
|
12240
|
+
const fullPath = join27(cwd, file.path);
|
|
11759
12241
|
const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
|
|
11760
|
-
|
|
11761
|
-
|
|
12242
|
+
mkdirSync17(dir, { recursive: true });
|
|
12243
|
+
writeFileSync22(fullPath, file.content, "utf-8");
|
|
11762
12244
|
}
|
|
11763
|
-
const runsSymlinkTarget =
|
|
11764
|
-
const bmRunsPath =
|
|
11765
|
-
if (
|
|
12245
|
+
const runsSymlinkTarget = join27(cwd, "runs");
|
|
12246
|
+
const bmRunsPath = join27(cwd, "runs");
|
|
12247
|
+
if (existsSync29(runsSymlinkTarget)) {
|
|
11766
12248
|
info("Existing runs/ directory preserved in-place.");
|
|
11767
12249
|
}
|
|
11768
|
-
const boardPath =
|
|
11769
|
-
if (!
|
|
11770
|
-
|
|
12250
|
+
const boardPath = join27(bmDir, "board.json");
|
|
12251
|
+
if (!existsSync29(boardPath)) {
|
|
12252
|
+
writeFileSync22(boardPath, JSON.stringify({ items: [] }, null, 2), "utf-8");
|
|
11771
12253
|
}
|
|
11772
|
-
|
|
12254
|
+
mkdirSync17(join27(bmDir, ".cache"), { recursive: true });
|
|
11773
12255
|
console.log();
|
|
11774
12256
|
success("Migration complete!");
|
|
11775
12257
|
info(`Factory created at: ${bmDir}`);
|
|
@@ -11791,8 +12273,8 @@ init_board();
|
|
|
11791
12273
|
init_bridge();
|
|
11792
12274
|
init_schemas();
|
|
11793
12275
|
import { Command as Command15 } from "commander";
|
|
11794
|
-
import { join as
|
|
11795
|
-
import { existsSync as
|
|
12276
|
+
import { join as join28 } from "path";
|
|
12277
|
+
import { existsSync as existsSync30, readFileSync as readFileSync27, writeFileSync as writeFileSync23, mkdirSync as mkdirSync18 } from "fs";
|
|
11796
12278
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
11797
12279
|
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) => {
|
|
11798
12280
|
try {
|
|
@@ -11812,18 +12294,18 @@ async function runPipeline(projectName, opts) {
|
|
|
11812
12294
|
"No BeastMode factory found. Run 'beastmode init' first."
|
|
11813
12295
|
);
|
|
11814
12296
|
}
|
|
11815
|
-
const bmDir =
|
|
12297
|
+
const bmDir = join28(factoryDir, ".beastmode");
|
|
11816
12298
|
header("BeastMode Run");
|
|
11817
|
-
const configPath =
|
|
11818
|
-
if (!
|
|
12299
|
+
const configPath = join28(bmDir, "config.json");
|
|
12300
|
+
if (!existsSync30(configPath)) {
|
|
11819
12301
|
throw new Error("Factory config not found. Run 'beastmode init' first.");
|
|
11820
12302
|
}
|
|
11821
12303
|
const factoryConfig = FactoryConfigSchema.parse(
|
|
11822
|
-
JSON.parse(
|
|
12304
|
+
JSON.parse(readFileSync27(configPath, "utf-8"))
|
|
11823
12305
|
);
|
|
11824
12306
|
let projectConfig = null;
|
|
11825
|
-
const projectsDir =
|
|
11826
|
-
if (
|
|
12307
|
+
const projectsDir = join28(bmDir, "projects");
|
|
12308
|
+
if (existsSync30(projectsDir)) {
|
|
11827
12309
|
const { readdirSync: readdirSync14 } = await import("fs");
|
|
11828
12310
|
const projectFiles = readdirSync14(projectsDir).filter(
|
|
11829
12311
|
(f) => f.endsWith(".json")
|
|
@@ -11836,20 +12318,20 @@ async function runPipeline(projectName, opts) {
|
|
|
11836
12318
|
throw new Error(`Project not found: ${projectName}`);
|
|
11837
12319
|
}
|
|
11838
12320
|
projectConfig = ProjectConfigSchema.parse(
|
|
11839
|
-
JSON.parse(
|
|
12321
|
+
JSON.parse(readFileSync27(join28(projectsDir, file), "utf-8"))
|
|
11840
12322
|
);
|
|
11841
12323
|
} else if (projectFiles.length > 0) {
|
|
11842
12324
|
projectConfig = ProjectConfigSchema.parse(
|
|
11843
|
-
JSON.parse(
|
|
12325
|
+
JSON.parse(readFileSync27(join28(projectsDir, projectFiles[0]), "utf-8"))
|
|
11844
12326
|
);
|
|
11845
12327
|
info(`Using project: ${projectConfig.name}`);
|
|
11846
12328
|
}
|
|
11847
12329
|
}
|
|
11848
|
-
const boardPath =
|
|
12330
|
+
const boardPath = join28(bmDir, "board.json");
|
|
11849
12331
|
let boardItems = [];
|
|
11850
|
-
if (
|
|
12332
|
+
if (existsSync30(boardPath)) {
|
|
11851
12333
|
try {
|
|
11852
|
-
const raw = JSON.parse(
|
|
12334
|
+
const raw = JSON.parse(readFileSync27(boardPath, "utf-8"));
|
|
11853
12335
|
boardItems = Array.isArray(raw.items) ? raw.items : [];
|
|
11854
12336
|
} catch {
|
|
11855
12337
|
boardItems = [];
|
|
@@ -11866,13 +12348,13 @@ async function runPipeline(projectName, opts) {
|
|
|
11866
12348
|
updated_at: now
|
|
11867
12349
|
};
|
|
11868
12350
|
boardItems.push(task);
|
|
11869
|
-
|
|
12351
|
+
writeFileSync23(boardPath, JSON.stringify({ items: boardItems }, null, 2), "utf-8");
|
|
11870
12352
|
info(`Created task: ${task.title} (${taskId})`);
|
|
11871
|
-
const cacheDir =
|
|
11872
|
-
|
|
11873
|
-
const daemonConfigPath =
|
|
12353
|
+
const cacheDir = join28(bmDir, ".cache");
|
|
12354
|
+
mkdirSync18(cacheDir, { recursive: true });
|
|
12355
|
+
const daemonConfigPath = join28(cacheDir, "daemon.json");
|
|
11874
12356
|
const daemonConfig = generateDaemonConfig(factoryConfig, projectConfig, factoryDir);
|
|
11875
|
-
|
|
12357
|
+
writeFileSync23(daemonConfigPath, JSON.stringify(daemonConfig, null, 2), "utf-8");
|
|
11876
12358
|
info(`Generated daemon config at: ${daemonConfigPath}`);
|
|
11877
12359
|
const { execSync: execSync14 } = await import("child_process");
|
|
11878
12360
|
let pythonAvailable = false;
|
|
@@ -11896,7 +12378,7 @@ async function runPipeline(projectName, opts) {
|
|
|
11896
12378
|
const daemonPaths = findPythonDaemonPaths(envPath, factoryDir);
|
|
11897
12379
|
let daemonFound = false;
|
|
11898
12380
|
for (const p of daemonPaths) {
|
|
11899
|
-
if (
|
|
12381
|
+
if (existsSync30(p)) {
|
|
11900
12382
|
daemonFound = true;
|
|
11901
12383
|
break;
|
|
11902
12384
|
}
|
|
@@ -11923,7 +12405,7 @@ async function runPipeline(projectName, opts) {
|
|
|
11923
12405
|
const startTime = Date.now();
|
|
11924
12406
|
const pollInterval = setInterval(() => {
|
|
11925
12407
|
try {
|
|
11926
|
-
const board = JSON.parse(
|
|
12408
|
+
const board = JSON.parse(readFileSync27(boardPath, "utf-8"));
|
|
11927
12409
|
const items = Array.isArray(board.items) ? board.items : [];
|
|
11928
12410
|
const taskItem = items.find((i) => i.id === taskId);
|
|
11929
12411
|
if (taskItem) {
|
|
@@ -11969,8 +12451,8 @@ init_board();
|
|
|
11969
12451
|
init_bridge();
|
|
11970
12452
|
init_schemas();
|
|
11971
12453
|
import { Command as Command16 } from "commander";
|
|
11972
|
-
import { join as
|
|
11973
|
-
import { existsSync as
|
|
12454
|
+
import { join as join29 } from "path";
|
|
12455
|
+
import { existsSync as existsSync31, readFileSync as readFileSync28, writeFileSync as writeFileSync24, mkdirSync as mkdirSync19 } from "fs";
|
|
11974
12456
|
var daemonCommand = new Command16("daemon").description("Start the BeastMode daemon via bridge").option("--dry-run", "Generate config but don't start daemon").option(
|
|
11975
12457
|
"--log-level <level>",
|
|
11976
12458
|
"Log level (DEBUG, INFO, WARNING, ERROR)",
|
|
@@ -11990,38 +12472,38 @@ async function runDaemon(opts) {
|
|
|
11990
12472
|
"No BeastMode factory found. Run 'beastmode init' first."
|
|
11991
12473
|
);
|
|
11992
12474
|
}
|
|
11993
|
-
const bmDir =
|
|
12475
|
+
const bmDir = join29(factoryDir, ".beastmode");
|
|
11994
12476
|
header("BeastMode Daemon");
|
|
11995
|
-
const configPath =
|
|
11996
|
-
if (!
|
|
12477
|
+
const configPath = join29(bmDir, "config.json");
|
|
12478
|
+
if (!existsSync31(configPath)) {
|
|
11997
12479
|
throw new Error("Factory config not found. Run 'beastmode init' first.");
|
|
11998
12480
|
}
|
|
11999
12481
|
const factoryConfig = FactoryConfigSchema.parse(
|
|
12000
|
-
JSON.parse(
|
|
12482
|
+
JSON.parse(readFileSync28(configPath, "utf-8"))
|
|
12001
12483
|
);
|
|
12002
12484
|
let projectConfig = null;
|
|
12003
|
-
const projectsDir =
|
|
12004
|
-
if (
|
|
12485
|
+
const projectsDir = join29(bmDir, "projects");
|
|
12486
|
+
if (existsSync31(projectsDir)) {
|
|
12005
12487
|
const { readdirSync: readdirSync14 } = await import("fs");
|
|
12006
12488
|
const projectFiles = readdirSync14(projectsDir).filter(
|
|
12007
12489
|
(f) => f.endsWith(".json")
|
|
12008
12490
|
);
|
|
12009
12491
|
if (projectFiles.length > 0) {
|
|
12010
12492
|
projectConfig = ProjectConfigSchema.parse(
|
|
12011
|
-
JSON.parse(
|
|
12493
|
+
JSON.parse(readFileSync28(join29(projectsDir, projectFiles[0]), "utf-8"))
|
|
12012
12494
|
);
|
|
12013
12495
|
info(`Using project: ${projectConfig.name}`);
|
|
12014
12496
|
}
|
|
12015
12497
|
}
|
|
12016
|
-
const cacheDir =
|
|
12017
|
-
|
|
12018
|
-
const daemonConfigPath =
|
|
12498
|
+
const cacheDir = join29(bmDir, ".cache");
|
|
12499
|
+
mkdirSync19(cacheDir, { recursive: true });
|
|
12500
|
+
const daemonConfigPath = join29(cacheDir, "daemon.json");
|
|
12019
12501
|
const daemonConfig = generateDaemonConfig(
|
|
12020
12502
|
factoryConfig,
|
|
12021
12503
|
projectConfig,
|
|
12022
12504
|
factoryDir
|
|
12023
12505
|
);
|
|
12024
|
-
|
|
12506
|
+
writeFileSync24(
|
|
12025
12507
|
daemonConfigPath,
|
|
12026
12508
|
JSON.stringify(daemonConfig, null, 2),
|
|
12027
12509
|
"utf-8"
|
|
@@ -12060,7 +12542,7 @@ async function runDaemon(opts) {
|
|
|
12060
12542
|
});
|
|
12061
12543
|
info(`Starting daemon: ${pythonCmd} ${cmd.args.join(" ")}`);
|
|
12062
12544
|
console.log();
|
|
12063
|
-
const pidFile =
|
|
12545
|
+
const pidFile = join29(bmDir, "daemon.pid");
|
|
12064
12546
|
const { spawn: spawn4 } = await import("child_process");
|
|
12065
12547
|
const child = spawn4(pythonCmd, cmd.args, {
|
|
12066
12548
|
stdio: "inherit",
|
|
@@ -12071,7 +12553,7 @@ async function runDaemon(opts) {
|
|
|
12071
12553
|
}
|
|
12072
12554
|
});
|
|
12073
12555
|
if (child.pid) {
|
|
12074
|
-
|
|
12556
|
+
writeFileSync24(pidFile, String(child.pid), "utf-8");
|
|
12075
12557
|
}
|
|
12076
12558
|
const signalHandler = (signal) => {
|
|
12077
12559
|
info(`Forwarding ${signal} to daemon...`);
|
|
@@ -12110,8 +12592,8 @@ var mcpCommand = new Command17("mcp").description("Start the BeastMode MCP serve
|
|
|
12110
12592
|
// src/cli/commands/deploy.ts
|
|
12111
12593
|
init_display();
|
|
12112
12594
|
import { Command as Command18 } from "commander";
|
|
12113
|
-
import { resolve as resolve19, join as
|
|
12114
|
-
import { existsSync as
|
|
12595
|
+
import { resolve as resolve19, join as join31 } from "path";
|
|
12596
|
+
import { existsSync as existsSync33, writeFileSync as writeFileSync26, readFileSync as readFileSync30 } from "fs";
|
|
12115
12597
|
import { execSync as execSync9 } from "child_process";
|
|
12116
12598
|
import { randomBytes } from "crypto";
|
|
12117
12599
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -12166,8 +12648,8 @@ async function runDeploy(opts) {
|
|
|
12166
12648
|
process.exit(1);
|
|
12167
12649
|
}
|
|
12168
12650
|
const factoryDir = resolve19(".");
|
|
12169
|
-
const bmDir =
|
|
12170
|
-
if (!
|
|
12651
|
+
const bmDir = join31(factoryDir, ".beastmode");
|
|
12652
|
+
if (!existsSync33(bmDir)) {
|
|
12171
12653
|
error(
|
|
12172
12654
|
"No .beastmode directory found. Run 'beastmode init' or 'beastmode migrate' first."
|
|
12173
12655
|
);
|
|
@@ -12191,33 +12673,33 @@ async function runDeploy(opts) {
|
|
|
12191
12673
|
"../../index.js"
|
|
12192
12674
|
);
|
|
12193
12675
|
}
|
|
12194
|
-
const boardVenvPython =
|
|
12195
|
-
const daemonVenvPython =
|
|
12196
|
-
const boardPython =
|
|
12197
|
-
const daemonPython =
|
|
12676
|
+
const boardVenvPython = join31(factoryDir, "board", ".venv", "bin", "python");
|
|
12677
|
+
const daemonVenvPython = join31(factoryDir, "daemon", ".venv", "bin", "python");
|
|
12678
|
+
const boardPython = existsSync33(boardVenvPython) ? boardVenvPython : "python3";
|
|
12679
|
+
const daemonPython = existsSync33(daemonVenvPython) ? daemonVenvPython : "python3";
|
|
12198
12680
|
const user = execSync9("whoami", { encoding: "utf-8" }).trim();
|
|
12199
12681
|
const home = process.env.HOME || `/home/${user}`;
|
|
12200
12682
|
const port = opts.port;
|
|
12201
12683
|
const host = opts.host;
|
|
12202
|
-
const dotEnv =
|
|
12203
|
-
const secretsEnv =
|
|
12204
|
-
const envContent =
|
|
12205
|
-
const secretsContent =
|
|
12684
|
+
const dotEnv = join31(factoryDir, ".env");
|
|
12685
|
+
const secretsEnv = join31(bmDir, "secrets.env.local");
|
|
12686
|
+
const envContent = existsSync33(dotEnv) ? readFileSync30(dotEnv, "utf-8") : "";
|
|
12687
|
+
const secretsContent = existsSync33(secretsEnv) ? readFileSync30(secretsEnv, "utf-8") : "";
|
|
12206
12688
|
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;
|
|
12207
12689
|
if (!hasPassword && opts.host === "0.0.0.0") {
|
|
12208
12690
|
const generated = randomBytes(18).toString("base64url");
|
|
12209
|
-
const target =
|
|
12691
|
+
const target = existsSync33(secretsEnv) ? secretsEnv : dotEnv;
|
|
12210
12692
|
const append = `
|
|
12211
12693
|
# Auto-generated board UI password (deploy)
|
|
12212
12694
|
BEASTMODE_UI_PASSWORD=${generated}
|
|
12213
12695
|
`;
|
|
12214
|
-
|
|
12696
|
+
writeFileSync26(target, (existsSync33(target) ? readFileSync30(target, "utf-8") : "") + append, "utf-8");
|
|
12215
12697
|
info(`Board UI password auto-generated and saved to ${target}`);
|
|
12216
12698
|
success(`Password: ${generated}`);
|
|
12217
12699
|
info("Save this password \u2014 you'll need it to access the board UI.");
|
|
12218
12700
|
}
|
|
12219
12701
|
const envFileLines = [];
|
|
12220
|
-
if (
|
|
12702
|
+
if (existsSync33(secretsEnv)) {
|
|
12221
12703
|
envFileLines.push(`EnvironmentFile=${secretsEnv}`);
|
|
12222
12704
|
} else {
|
|
12223
12705
|
envFileLines.push(`# No secrets.env.local found at time of deploy`);
|
|
@@ -12300,7 +12782,7 @@ BEASTMODE_UI_PASSWORD=${generated}
|
|
|
12300
12782
|
info(`Writing service file to ${svc.path}...`);
|
|
12301
12783
|
try {
|
|
12302
12784
|
const tmpPath = `/tmp/${svc.name}.service`;
|
|
12303
|
-
|
|
12785
|
+
writeFileSync26(tmpPath, svc.content, "utf-8");
|
|
12304
12786
|
execSync9(`sudo cp ${tmpPath} ${svc.path}`, { stdio: "inherit" });
|
|
12305
12787
|
success(`${svc.name} service file installed`);
|
|
12306
12788
|
} catch {
|
|
@@ -12429,9 +12911,9 @@ async function deployToAWS(opts) {
|
|
|
12429
12911
|
}
|
|
12430
12912
|
const __filename2 = fileURLToPath3(import.meta.url);
|
|
12431
12913
|
const __dirname2 = dirname7(__filename2);
|
|
12432
|
-
const templatePath =
|
|
12433
|
-
const cwdTemplate =
|
|
12434
|
-
const template =
|
|
12914
|
+
const templatePath = join31(__dirname2, "..", "..", "infra", "cloudformation", "beastmode.yaml");
|
|
12915
|
+
const cwdTemplate = join31(process.cwd(), "infra", "cloudformation", "beastmode.yaml");
|
|
12916
|
+
const template = existsSync33(templatePath) ? templatePath : existsSync33(cwdTemplate) ? cwdTemplate : null;
|
|
12435
12917
|
if (!template) {
|
|
12436
12918
|
error("CloudFormation template not found. Expected at infra/cloudformation/beastmode.yaml");
|
|
12437
12919
|
process.exit(1);
|
|
@@ -12685,19 +13167,19 @@ var logsCommand = new Command21("logs").description("Stream BeastMode service lo
|
|
|
12685
13167
|
|
|
12686
13168
|
// src/cli/commands/update.ts
|
|
12687
13169
|
import { Command as Command22 } from "commander";
|
|
12688
|
-
import { readFileSync as
|
|
13170
|
+
import { readFileSync as readFileSync31, writeFileSync as writeFileSync27 } from "fs";
|
|
12689
13171
|
init_display();
|
|
12690
13172
|
async function runUpdate(opts) {
|
|
12691
13173
|
const cwd = opts.cwd ?? process.cwd();
|
|
12692
13174
|
const composePath = requireComposeFile(cwd);
|
|
12693
13175
|
if (opts.tag) {
|
|
12694
|
-
let content =
|
|
13176
|
+
let content = readFileSync31(composePath, "utf-8");
|
|
12695
13177
|
const tagPattern = new RegExp(
|
|
12696
13178
|
`(${GHCR_IMAGE_PREFIX.replace(/[/]/g, "\\/")}\\/(?:board|daemon|ui)):([\\w.\\-]+)`,
|
|
12697
13179
|
"g"
|
|
12698
13180
|
);
|
|
12699
13181
|
content = content.replace(tagPattern, `$1:${opts.tag}`);
|
|
12700
|
-
|
|
13182
|
+
writeFileSync27(composePath, content, "utf-8");
|
|
12701
13183
|
}
|
|
12702
13184
|
runCompose(["pull"], { cwd, inherit: true });
|
|
12703
13185
|
runCompose(["up", "-d"], { cwd, inherit: true });
|
|
@@ -12722,23 +13204,23 @@ var updateCommand = new Command22("update").description("Pull latest BeastMode i
|
|
|
12722
13204
|
init_display();
|
|
12723
13205
|
import { Command as Command23 } from "commander";
|
|
12724
13206
|
import { spawn as spawn3 } from "child_process";
|
|
12725
|
-
import { existsSync as
|
|
12726
|
-
import { basename as
|
|
13207
|
+
import { existsSync as existsSync38, readdirSync as readdirSync13, readFileSync as readFileSync35 } from "fs";
|
|
13208
|
+
import { basename as basename7, join as join37, resolve as resolve20 } from "path";
|
|
12727
13209
|
|
|
12728
13210
|
// src/cli/runner-image-builder.ts
|
|
12729
13211
|
import { execSync as execSync10 } from "child_process";
|
|
12730
13212
|
import { createHash } from "crypto";
|
|
12731
13213
|
import {
|
|
12732
|
-
existsSync as
|
|
12733
|
-
mkdirSync as
|
|
12734
|
-
readFileSync as
|
|
12735
|
-
writeFileSync as
|
|
13214
|
+
existsSync as existsSync35,
|
|
13215
|
+
mkdirSync as mkdirSync21,
|
|
13216
|
+
readFileSync as readFileSync33,
|
|
13217
|
+
writeFileSync as writeFileSync28
|
|
12736
13218
|
} from "fs";
|
|
12737
|
-
import { join as
|
|
13219
|
+
import { join as join33 } from "path";
|
|
12738
13220
|
|
|
12739
13221
|
// src/cli/stack-detect.ts
|
|
12740
|
-
import { existsSync as
|
|
12741
|
-
import { join as
|
|
13222
|
+
import { existsSync as existsSync34, readFileSync as readFileSync32 } from "fs";
|
|
13223
|
+
import { join as join32 } from "path";
|
|
12742
13224
|
var NODE_LOCKFILES = [
|
|
12743
13225
|
"package-lock.json",
|
|
12744
13226
|
"pnpm-lock.yaml",
|
|
@@ -12776,7 +13258,7 @@ var STACK_LOCKFILES = {
|
|
|
12776
13258
|
};
|
|
12777
13259
|
function readFileSafe2(path) {
|
|
12778
13260
|
try {
|
|
12779
|
-
return
|
|
13261
|
+
return readFileSync32(path, "utf-8");
|
|
12780
13262
|
} catch {
|
|
12781
13263
|
return null;
|
|
12782
13264
|
}
|
|
@@ -12789,7 +13271,7 @@ function parseJsonSafe2(content) {
|
|
|
12789
13271
|
}
|
|
12790
13272
|
}
|
|
12791
13273
|
function detectRunnerStack(projectDir) {
|
|
12792
|
-
const pkgContent = readFileSafe2(
|
|
13274
|
+
const pkgContent = readFileSafe2(join32(projectDir, "package.json"));
|
|
12793
13275
|
if (pkgContent) {
|
|
12794
13276
|
const pkg = parseJsonSafe2(pkgContent);
|
|
12795
13277
|
if (pkg) {
|
|
@@ -12803,36 +13285,36 @@ function detectRunnerStack(projectDir) {
|
|
|
12803
13285
|
}
|
|
12804
13286
|
return { name: "node", language: "node" };
|
|
12805
13287
|
}
|
|
12806
|
-
if (
|
|
13288
|
+
if (existsSync34(join32(projectDir, "manage.py"))) {
|
|
12807
13289
|
return { name: "django", language: "python" };
|
|
12808
13290
|
}
|
|
12809
|
-
const pyproject = readFileSafe2(
|
|
13291
|
+
const pyproject = readFileSafe2(join32(projectDir, "pyproject.toml"));
|
|
12810
13292
|
if (pyproject) {
|
|
12811
13293
|
if (pyproject.toLowerCase().includes("fastapi")) {
|
|
12812
13294
|
return { name: "fastapi", language: "python" };
|
|
12813
13295
|
}
|
|
12814
13296
|
return { name: "python", language: "python" };
|
|
12815
13297
|
}
|
|
12816
|
-
if (
|
|
13298
|
+
if (existsSync34(join32(projectDir, "requirements.txt"))) {
|
|
12817
13299
|
return { name: "python", language: "python" };
|
|
12818
13300
|
}
|
|
12819
|
-
if (
|
|
13301
|
+
if (existsSync34(join32(projectDir, "go.mod"))) {
|
|
12820
13302
|
return { name: "go", language: "go" };
|
|
12821
13303
|
}
|
|
12822
|
-
if (
|
|
13304
|
+
if (existsSync34(join32(projectDir, "Cargo.toml"))) {
|
|
12823
13305
|
return { name: "rust", language: "rust" };
|
|
12824
13306
|
}
|
|
12825
|
-
if (
|
|
13307
|
+
if (existsSync34(join32(projectDir, "pom.xml"))) {
|
|
12826
13308
|
return { name: "java-maven", language: "java" };
|
|
12827
13309
|
}
|
|
12828
|
-
if (
|
|
13310
|
+
if (existsSync34(join32(projectDir, "build.gradle")) || existsSync34(join32(projectDir, "build.gradle.kts"))) {
|
|
12829
13311
|
return { name: "java-gradle", language: "java" };
|
|
12830
13312
|
}
|
|
12831
13313
|
return { name: "node", language: "node" };
|
|
12832
13314
|
}
|
|
12833
13315
|
function findLockfiles(projectDir, stackName) {
|
|
12834
13316
|
const candidates = STACK_LOCKFILES[stackName] ?? [];
|
|
12835
|
-
return candidates.filter((f) =>
|
|
13317
|
+
return candidates.filter((f) => existsSync34(join32(projectDir, f)));
|
|
12836
13318
|
}
|
|
12837
13319
|
|
|
12838
13320
|
// src/cli/runner-image-builder.ts
|
|
@@ -12934,12 +13416,12 @@ function generateDockerfile(stack, lockfiles) {
|
|
|
12934
13416
|
function computeLockfileHash(projectDir, lockfiles) {
|
|
12935
13417
|
const hash = createHash("sha256");
|
|
12936
13418
|
for (const lf of lockfiles) {
|
|
12937
|
-
const path =
|
|
13419
|
+
const path = join33(projectDir, lf);
|
|
12938
13420
|
hash.update(lf);
|
|
12939
13421
|
hash.update("\0");
|
|
12940
|
-
if (
|
|
13422
|
+
if (existsSync35(path)) {
|
|
12941
13423
|
try {
|
|
12942
|
-
hash.update(
|
|
13424
|
+
hash.update(readFileSync33(path));
|
|
12943
13425
|
} catch {
|
|
12944
13426
|
hash.update("<unreadable>");
|
|
12945
13427
|
}
|
|
@@ -12951,10 +13433,10 @@ function computeLockfileHash(projectDir, lockfiles) {
|
|
|
12951
13433
|
return hash.digest("hex").slice(0, 16);
|
|
12952
13434
|
}
|
|
12953
13435
|
function readLastBuild(projectDir) {
|
|
12954
|
-
const path =
|
|
12955
|
-
if (!
|
|
13436
|
+
const path = join33(projectDir, RUNNER_STATE_DIR, LAST_BUILD_FILE);
|
|
13437
|
+
if (!existsSync35(path)) return null;
|
|
12956
13438
|
try {
|
|
12957
|
-
const data = JSON.parse(
|
|
13439
|
+
const data = JSON.parse(readFileSync33(path, "utf-8"));
|
|
12958
13440
|
if (data && typeof data === "object" && typeof data.lockfileHash === "string" && typeof data.imageTag === "string" && typeof data.builtAt === "string") {
|
|
12959
13441
|
return data;
|
|
12960
13442
|
}
|
|
@@ -12973,17 +13455,17 @@ function imageTagFor(projectName, hash) {
|
|
|
12973
13455
|
return `beastmode-runner-${safeName}:${hash}`;
|
|
12974
13456
|
}
|
|
12975
13457
|
function writeStateFile(projectDir, fileName, payload) {
|
|
12976
|
-
const dir =
|
|
12977
|
-
|
|
12978
|
-
|
|
12979
|
-
|
|
13458
|
+
const dir = join33(projectDir, RUNNER_STATE_DIR);
|
|
13459
|
+
mkdirSync21(dir, { recursive: true });
|
|
13460
|
+
writeFileSync28(
|
|
13461
|
+
join33(dir, fileName),
|
|
12980
13462
|
JSON.stringify(payload, null, 2) + "\n",
|
|
12981
13463
|
"utf-8"
|
|
12982
13464
|
);
|
|
12983
13465
|
}
|
|
12984
13466
|
function buildRunnerImage(opts) {
|
|
12985
13467
|
const { projectDir, projectName, stack, lockfiles, force, dryRun } = opts;
|
|
12986
|
-
if (!
|
|
13468
|
+
if (!existsSync35(projectDir)) {
|
|
12987
13469
|
throw new Error(`Project directory not found: ${projectDir}`);
|
|
12988
13470
|
}
|
|
12989
13471
|
const lockfileHash = computeLockfileHash(projectDir, lockfiles);
|
|
@@ -13102,11 +13584,11 @@ function resolveGitHubConfig() {
|
|
|
13102
13584
|
// src/cli/runner-helpers.ts
|
|
13103
13585
|
import { execSync as execSync11, spawn, spawnSync as spawnSync5 } from "child_process";
|
|
13104
13586
|
import { promises as fs } from "fs";
|
|
13105
|
-
import { join as
|
|
13587
|
+
import { join as join34 } from "path";
|
|
13106
13588
|
init_display();
|
|
13107
13589
|
async function writeRunnerMeta(dir, meta) {
|
|
13108
13590
|
await fs.mkdir(dir, { recursive: true });
|
|
13109
|
-
const path =
|
|
13591
|
+
const path = join34(dir, "runner-meta.json");
|
|
13110
13592
|
await fs.writeFile(path, JSON.stringify(meta, null, 2) + "\n", "utf-8");
|
|
13111
13593
|
}
|
|
13112
13594
|
function resolveRepoSlug() {
|
|
@@ -13307,13 +13789,13 @@ import {
|
|
|
13307
13789
|
} from "child_process";
|
|
13308
13790
|
import {
|
|
13309
13791
|
createWriteStream,
|
|
13310
|
-
existsSync as
|
|
13311
|
-
mkdirSync as
|
|
13792
|
+
existsSync as existsSync36,
|
|
13793
|
+
mkdirSync as mkdirSync22,
|
|
13312
13794
|
unlinkSync as unlinkSync5,
|
|
13313
|
-
writeFileSync as
|
|
13795
|
+
writeFileSync as writeFileSync29
|
|
13314
13796
|
} from "fs";
|
|
13315
13797
|
import { homedir as homedir4 } from "os";
|
|
13316
|
-
import { join as
|
|
13798
|
+
import { join as join35, dirname as dirname8 } from "path";
|
|
13317
13799
|
import { Readable } from "stream";
|
|
13318
13800
|
import { pipeline } from "stream/promises";
|
|
13319
13801
|
init_display();
|
|
@@ -13343,8 +13825,8 @@ function runnerDownloadUrl(os, arch) {
|
|
|
13343
13825
|
return `https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-${ghOs}-${arch}-${RUNNER_VERSION}.tar.gz`;
|
|
13344
13826
|
}
|
|
13345
13827
|
async function downloadAndExtractRunner(installDir, os, arch) {
|
|
13346
|
-
const runShPath =
|
|
13347
|
-
if (
|
|
13828
|
+
const runShPath = join35(installDir, "run.sh");
|
|
13829
|
+
if (existsSync36(runShPath)) {
|
|
13348
13830
|
info(
|
|
13349
13831
|
`Runner binary already present at ${runShPath} \u2014 skipping download.`
|
|
13350
13832
|
);
|
|
@@ -13354,7 +13836,7 @@ async function downloadAndExtractRunner(installDir, os, arch) {
|
|
|
13354
13836
|
return;
|
|
13355
13837
|
}
|
|
13356
13838
|
const url = runnerDownloadUrl(os, arch);
|
|
13357
|
-
const tarball =
|
|
13839
|
+
const tarball = join35(installDir, "runner.tar.gz");
|
|
13358
13840
|
setupStep(
|
|
13359
13841
|
`Downloading GitHub Actions runner v${RUNNER_VERSION} (${os}/${arch})...`
|
|
13360
13842
|
);
|
|
@@ -13405,7 +13887,7 @@ async function configureRunner(installDir, repoUrl, token, name, labels) {
|
|
|
13405
13887
|
"--unattended",
|
|
13406
13888
|
"--replace"
|
|
13407
13889
|
];
|
|
13408
|
-
const result = spawnSync6(
|
|
13890
|
+
const result = spawnSync6(join35(installDir, "config.sh"), args, {
|
|
13409
13891
|
cwd: installDir,
|
|
13410
13892
|
stdio: ["ignore", "inherit", "pipe"],
|
|
13411
13893
|
encoding: "utf-8"
|
|
@@ -13433,7 +13915,7 @@ function startRunnerForeground(installDir) {
|
|
|
13433
13915
|
"Use --service to install as a launchd agent for persistent operation."
|
|
13434
13916
|
);
|
|
13435
13917
|
}
|
|
13436
|
-
spawn2(
|
|
13918
|
+
spawn2(join35(installDir, "run.sh"), [], {
|
|
13437
13919
|
cwd: installDir,
|
|
13438
13920
|
stdio: "inherit"
|
|
13439
13921
|
});
|
|
@@ -13458,10 +13940,10 @@ WantedBy=default.target
|
|
|
13458
13940
|
}
|
|
13459
13941
|
async function installSystemdService(installDir, name) {
|
|
13460
13942
|
const unitName = `beastmode-runner-${name}.service`;
|
|
13461
|
-
const unitDir =
|
|
13462
|
-
|
|
13463
|
-
|
|
13464
|
-
|
|
13943
|
+
const unitDir = join35(homedir4(), ".config", "systemd", "user");
|
|
13944
|
+
mkdirSync22(unitDir, { recursive: true });
|
|
13945
|
+
writeFileSync29(
|
|
13946
|
+
join35(unitDir, unitName),
|
|
13465
13947
|
systemdUnitContent(installDir, name),
|
|
13466
13948
|
"utf-8"
|
|
13467
13949
|
);
|
|
@@ -13515,15 +13997,15 @@ function launchdPlistContent(installDir, name) {
|
|
|
13515
13997
|
}
|
|
13516
13998
|
async function installLaunchdService(installDir, name) {
|
|
13517
13999
|
const label = `com.beastmode.runner.${name}`;
|
|
13518
|
-
const plistPath2 =
|
|
14000
|
+
const plistPath2 = join35(
|
|
13519
14001
|
homedir4(),
|
|
13520
14002
|
"Library",
|
|
13521
14003
|
"LaunchAgents",
|
|
13522
14004
|
`${label}.plist`
|
|
13523
14005
|
);
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
14006
|
+
mkdirSync22(dirname8(plistPath2), { recursive: true });
|
|
14007
|
+
mkdirSync22(join35(installDir, "logs"), { recursive: true });
|
|
14008
|
+
writeFileSync29(plistPath2, launchdPlistContent(installDir, name), "utf-8");
|
|
13527
14009
|
try {
|
|
13528
14010
|
execSync12(`launchctl load ${plistPath2}`, { stdio: "inherit" });
|
|
13529
14011
|
} catch (err) {
|
|
@@ -13564,7 +14046,7 @@ async function nativeRunnerSetup(opts) {
|
|
|
13564
14046
|
}
|
|
13565
14047
|
const ghConfig = resolveGitHubConfig();
|
|
13566
14048
|
const repoSlug = opts.repo ?? resolveRepoSlug();
|
|
13567
|
-
const installDir =
|
|
14049
|
+
const installDir = join35(homedir4(), ".beastmode", "runners", opts.name);
|
|
13568
14050
|
if (opts.dryRun) {
|
|
13569
14051
|
info(
|
|
13570
14052
|
`[dry-run] Would install native runner '${opts.name}' for repo ${repoSlug}`
|
|
@@ -13574,7 +14056,7 @@ async function nativeRunnerSetup(opts) {
|
|
|
13574
14056
|
info(`[dry-run] Mode: ${opts.service ? "service" : "foreground"}`);
|
|
13575
14057
|
return;
|
|
13576
14058
|
}
|
|
13577
|
-
|
|
14059
|
+
mkdirSync22(installDir, { recursive: true });
|
|
13578
14060
|
setupStep("Generating registration token via GitHub API...");
|
|
13579
14061
|
const { token: regToken } = await createRegistrationToken(ghConfig);
|
|
13580
14062
|
await downloadAndExtractRunner(installDir, platformOs, arch);
|
|
@@ -13612,13 +14094,13 @@ async function nativeRunnerSetup(opts) {
|
|
|
13612
14094
|
|
|
13613
14095
|
// src/cli/workflow-switcher.ts
|
|
13614
14096
|
import {
|
|
13615
|
-
existsSync as
|
|
13616
|
-
mkdirSync as
|
|
13617
|
-
readFileSync as
|
|
14097
|
+
existsSync as existsSync37,
|
|
14098
|
+
mkdirSync as mkdirSync23,
|
|
14099
|
+
readFileSync as readFileSync34,
|
|
13618
14100
|
unlinkSync as unlinkSync6,
|
|
13619
|
-
writeFileSync as
|
|
14101
|
+
writeFileSync as writeFileSync30
|
|
13620
14102
|
} from "fs";
|
|
13621
|
-
import { dirname as dirname9, join as
|
|
14103
|
+
import { dirname as dirname9, join as join36 } from "path";
|
|
13622
14104
|
var TARGET_LABEL = "[self-hosted, beastmode]";
|
|
13623
14105
|
var TARGET_WORKFLOWS = [
|
|
13624
14106
|
".github/workflows/test.yml",
|
|
@@ -13657,8 +14139,8 @@ function restoreRunsOn(content, originals) {
|
|
|
13657
14139
|
return newLines.join("\n");
|
|
13658
14140
|
}
|
|
13659
14141
|
async function switchWorkflows(projectDir) {
|
|
13660
|
-
const statePath =
|
|
13661
|
-
if (
|
|
14142
|
+
const statePath = join36(projectDir, STATE_FILE);
|
|
14143
|
+
if (existsSync37(statePath)) {
|
|
13662
14144
|
return { alreadySwitched: true, files: [] };
|
|
13663
14145
|
}
|
|
13664
14146
|
const state = {
|
|
@@ -13668,40 +14150,40 @@ async function switchWorkflows(projectDir) {
|
|
|
13668
14150
|
};
|
|
13669
14151
|
const resultFiles = [];
|
|
13670
14152
|
for (const relPath of TARGET_WORKFLOWS) {
|
|
13671
|
-
const absPath =
|
|
13672
|
-
if (!
|
|
14153
|
+
const absPath = join36(projectDir, relPath);
|
|
14154
|
+
if (!existsSync37(absPath)) {
|
|
13673
14155
|
throw new Error(`Workflow file not found: ${relPath}`);
|
|
13674
14156
|
}
|
|
13675
|
-
const content =
|
|
14157
|
+
const content = readFileSync34(absPath, "utf-8");
|
|
13676
14158
|
const { newContent, originals } = replaceRunsOn(content, TARGET_LABEL);
|
|
13677
14159
|
if (originals.length === 0) {
|
|
13678
14160
|
throw new Error(`No runs-on found in ${relPath}`);
|
|
13679
14161
|
}
|
|
13680
|
-
|
|
14162
|
+
writeFileSync30(absPath, newContent, "utf-8");
|
|
13681
14163
|
state.files.push({ relativePath: relPath, originals });
|
|
13682
14164
|
resultFiles.push({ relativePath: relPath, jobCount: originals.length });
|
|
13683
14165
|
}
|
|
13684
|
-
|
|
13685
|
-
|
|
14166
|
+
mkdirSync23(dirname9(statePath), { recursive: true });
|
|
14167
|
+
writeFileSync30(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
13686
14168
|
return { alreadySwitched: false, files: resultFiles };
|
|
13687
14169
|
}
|
|
13688
14170
|
async function restoreWorkflows(projectDir) {
|
|
13689
|
-
const statePath =
|
|
13690
|
-
if (!
|
|
14171
|
+
const statePath = join36(projectDir, STATE_FILE);
|
|
14172
|
+
if (!existsSync37(statePath)) {
|
|
13691
14173
|
return { nothingToRestore: true, files: [] };
|
|
13692
14174
|
}
|
|
13693
14175
|
const state = JSON.parse(
|
|
13694
|
-
|
|
14176
|
+
readFileSync34(statePath, "utf-8")
|
|
13695
14177
|
);
|
|
13696
14178
|
const resultFiles = [];
|
|
13697
14179
|
for (const fileState of state.files) {
|
|
13698
|
-
const absPath =
|
|
13699
|
-
if (!
|
|
14180
|
+
const absPath = join36(projectDir, fileState.relativePath);
|
|
14181
|
+
if (!existsSync37(absPath)) {
|
|
13700
14182
|
throw new Error(`Workflow file not found: ${fileState.relativePath}`);
|
|
13701
14183
|
}
|
|
13702
|
-
const content =
|
|
14184
|
+
const content = readFileSync34(absPath, "utf-8");
|
|
13703
14185
|
const newContent = restoreRunsOn(content, fileState.originals);
|
|
13704
|
-
|
|
14186
|
+
writeFileSync30(absPath, newContent, "utf-8");
|
|
13705
14187
|
resultFiles.push({
|
|
13706
14188
|
relativePath: fileState.relativePath,
|
|
13707
14189
|
jobCount: fileState.originals.length
|
|
@@ -13714,14 +14196,14 @@ async function restoreWorkflows(projectDir) {
|
|
|
13714
14196
|
// src/cli/commands/runner-cmd.ts
|
|
13715
14197
|
var runnerCommand = new Command23("runner").description("Manage self-hosted GitHub Actions runners");
|
|
13716
14198
|
function resolveProjectName(projectDir) {
|
|
13717
|
-
const projectsDir =
|
|
13718
|
-
if (
|
|
14199
|
+
const projectsDir = join37(projectDir, ".beastmode", "projects");
|
|
14200
|
+
if (existsSync38(projectsDir)) {
|
|
13719
14201
|
try {
|
|
13720
|
-
for (const entry of
|
|
14202
|
+
for (const entry of readdirSync13(projectsDir)) {
|
|
13721
14203
|
if (!entry.endsWith(".json")) continue;
|
|
13722
|
-
const file =
|
|
14204
|
+
const file = join37(projectsDir, entry);
|
|
13723
14205
|
try {
|
|
13724
|
-
const raw =
|
|
14206
|
+
const raw = readFileSync35(file, "utf-8");
|
|
13725
14207
|
const data = JSON.parse(raw);
|
|
13726
14208
|
if (typeof data.path === "string" && resolve20(data.path) === resolve20(projectDir) && typeof data.name === "string" && data.name.length > 0) {
|
|
13727
14209
|
return data.name;
|
|
@@ -13732,7 +14214,7 @@ function resolveProjectName(projectDir) {
|
|
|
13732
14214
|
} catch {
|
|
13733
14215
|
}
|
|
13734
14216
|
}
|
|
13735
|
-
return
|
|
14217
|
+
return basename7(resolve20(projectDir));
|
|
13736
14218
|
}
|
|
13737
14219
|
async function runnerSetupAction(opts) {
|
|
13738
14220
|
if (opts.service !== void 0 && !opts.native) {
|
|
@@ -14057,7 +14539,7 @@ runnerCommand.command("restore-workflows").description("Restore workflows to ori
|
|
|
14057
14539
|
});
|
|
14058
14540
|
async function runnerBuildImageAction(opts) {
|
|
14059
14541
|
const projectDir = resolve20(opts.projectDir);
|
|
14060
|
-
if (!
|
|
14542
|
+
if (!existsSync38(projectDir)) {
|
|
14061
14543
|
throw new Error(`Project directory not found: ${projectDir}`);
|
|
14062
14544
|
}
|
|
14063
14545
|
const stack = detectRunnerStack(projectDir);
|
|
@@ -14107,27 +14589,16 @@ runnerCommand.command("build-image").description(
|
|
|
14107
14589
|
// src/cli/commands/project-cmd.ts
|
|
14108
14590
|
init_engine();
|
|
14109
14591
|
import { Command as Command24 } from "commander";
|
|
14110
|
-
import { existsSync as
|
|
14111
|
-
import { join as
|
|
14592
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync24, readFileSync as readFileSync36, renameSync as renameSync3 } from "fs";
|
|
14593
|
+
import { join as join38, resolve as resolve21, basename as basename8 } from "path";
|
|
14112
14594
|
import { execSync as execSync13 } from "child_process";
|
|
14113
14595
|
var DEFAULT_MAX_PROJECTS = 5;
|
|
14114
14596
|
var MIN_DISK_WARNING_GB = 10;
|
|
14115
|
-
function countExistingProjects(factoryDir) {
|
|
14116
|
-
const projectsDir = join37(factoryDir, ".beastmode", "projects");
|
|
14117
|
-
if (!existsSync38(projectsDir)) return 0;
|
|
14118
|
-
let count = 0;
|
|
14119
|
-
for (const entry of readdirSync13(projectsDir)) {
|
|
14120
|
-
if (entry.startsWith(".")) continue;
|
|
14121
|
-
if (existsSync38(join37(projectsDir, entry, "project.json"))) count++;
|
|
14122
|
-
else if (entry.endsWith(".json")) count++;
|
|
14123
|
-
}
|
|
14124
|
-
return count;
|
|
14125
|
-
}
|
|
14126
14597
|
function getMaxProjects(factoryDir) {
|
|
14127
|
-
const configPath =
|
|
14128
|
-
if (
|
|
14598
|
+
const configPath = join38(factoryDir, ".beastmode", "config.json");
|
|
14599
|
+
if (existsSync39(configPath)) {
|
|
14129
14600
|
try {
|
|
14130
|
-
const config = JSON.parse(
|
|
14601
|
+
const config = JSON.parse(readFileSync36(configPath, "utf-8"));
|
|
14131
14602
|
if (typeof config.max_projects === "number") return config.max_projects;
|
|
14132
14603
|
} catch {
|
|
14133
14604
|
}
|
|
@@ -14146,11 +14617,13 @@ function getFreeDiskGB() {
|
|
|
14146
14617
|
}
|
|
14147
14618
|
function projectAddAction(factoryDir, projectPath, opts) {
|
|
14148
14619
|
const resolvedPath = resolve21(projectPath);
|
|
14149
|
-
if (!
|
|
14150
|
-
const projectName = opts.name ||
|
|
14151
|
-
const projectsDir =
|
|
14152
|
-
if (
|
|
14153
|
-
|
|
14620
|
+
if (!existsSync39(resolvedPath)) throw new Error(`Directory not found: ${resolvedPath}`);
|
|
14621
|
+
const projectName = opts.name || basename8(resolvedPath);
|
|
14622
|
+
const projectsDir = join38(factoryDir, ".beastmode", "projects");
|
|
14623
|
+
if (existsSync39(join38(projectsDir, projectName, "project.json"))) {
|
|
14624
|
+
throw new Error(`Project already exists: ${projectName}`);
|
|
14625
|
+
}
|
|
14626
|
+
const currentCount = listProjectRecords(projectsDir).length;
|
|
14154
14627
|
const maxProjects = getMaxProjects(factoryDir);
|
|
14155
14628
|
if (currentCount >= maxProjects) {
|
|
14156
14629
|
throw new Error(
|
|
@@ -14163,86 +14636,57 @@ function projectAddAction(factoryDir, projectPath, opts) {
|
|
|
14163
14636
|
`Warning: only ${freeGB}GB free disk space. Each project with worktrees may use 1-2GB. Consider freeing space.`
|
|
14164
14637
|
);
|
|
14165
14638
|
}
|
|
14166
|
-
|
|
14167
|
-
let
|
|
14639
|
+
let detectedStack = null;
|
|
14640
|
+
let gitRemote;
|
|
14168
14641
|
try {
|
|
14169
|
-
|
|
14642
|
+
detectedStack = detectStack(resolvedPath);
|
|
14643
|
+
if (detectedStack.git_remote) gitRemote = detectedStack.git_remote;
|
|
14170
14644
|
} catch {
|
|
14171
14645
|
}
|
|
14172
14646
|
let verifyPort = 3001;
|
|
14173
|
-
const
|
|
14174
|
-
|
|
14175
|
-
|
|
14176
|
-
|
|
14177
|
-
|
|
14178
|
-
|
|
14179
|
-
|
|
14180
|
-
|
|
14181
|
-
|
|
14182
|
-
|
|
14183
|
-
|
|
14184
|
-
|
|
14185
|
-
|
|
14186
|
-
|
|
14187
|
-
|
|
14647
|
+
for (const existing of listProjectRecords(projectsDir)) {
|
|
14648
|
+
const port = existing.deploy?.verify_port;
|
|
14649
|
+
if (typeof port === "number" && port >= verifyPort) verifyPort = port + 1;
|
|
14650
|
+
}
|
|
14651
|
+
const boardId = opts.boardId ? parseInt(opts.boardId, 10) : null;
|
|
14652
|
+
const record = createProjectRecord({ name: projectName, resolvedPath, boardId, verifyPort, gitRemote });
|
|
14653
|
+
if (detectedStack) {
|
|
14654
|
+
record.stack = {
|
|
14655
|
+
detected: detectedStack.framework,
|
|
14656
|
+
build_command: detectedStack.suggested_commands.build,
|
|
14657
|
+
dev_command: detectedStack.suggested_commands.dev,
|
|
14658
|
+
test_command: detectedStack.suggested_commands.test,
|
|
14659
|
+
install_command: detectedStack.suggested_commands.install,
|
|
14660
|
+
dev_port: detectedStack.dev_port,
|
|
14661
|
+
is_monorepo: detectedStack.is_monorepo,
|
|
14662
|
+
total_packages: detectedStack.total_packages,
|
|
14663
|
+
primary_languages: detectedStack.primary_languages,
|
|
14664
|
+
...detectedStack.packages ? { packages: detectedStack.packages } : {}
|
|
14665
|
+
};
|
|
14188
14666
|
}
|
|
14189
|
-
|
|
14190
|
-
|
|
14191
|
-
|
|
14192
|
-
|
|
14193
|
-
repo: stack.git_remote || "",
|
|
14194
|
-
default_branch: "main"
|
|
14195
|
-
},
|
|
14196
|
-
board: {
|
|
14197
|
-
id: opts.boardId ? parseInt(opts.boardId, 10) : null,
|
|
14198
|
-
url: "http://127.0.0.1:8080",
|
|
14199
|
-
auto_created: !opts.boardId
|
|
14200
|
-
},
|
|
14201
|
-
deploy: { verify_port: verifyPort },
|
|
14202
|
-
pipeline: {},
|
|
14203
|
-
models: {},
|
|
14204
|
-
slots: { max: null },
|
|
14205
|
-
registered_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
14206
|
-
};
|
|
14207
|
-
writeFileSync30(join37(projectsDir, "project.json"), JSON.stringify(projectConfig, null, 2) + "\n");
|
|
14208
|
-
writeFileSync30(join37(projectsDir, "extensions.json"), JSON.stringify({
|
|
14209
|
-
plugins: { add: [], remove: [] },
|
|
14210
|
-
mcps: { add: {}, remove: [] },
|
|
14211
|
-
skills: { add: [], remove: [] }
|
|
14212
|
-
}, null, 2) + "\n");
|
|
14213
|
-
const runsDir = join37(factoryDir, "runs", projectName);
|
|
14214
|
-
if (!existsSync38(runsDir)) mkdirSync23(runsDir, { recursive: true });
|
|
14215
|
-
if (!opts.boardId) {
|
|
14667
|
+
writeProjectRecord(projectsDir, projectName, record);
|
|
14668
|
+
const runsDir = join38(factoryDir, "runs", projectName);
|
|
14669
|
+
if (!existsSync39(runsDir)) mkdirSync24(runsDir, { recursive: true });
|
|
14670
|
+
if (!boardId) {
|
|
14216
14671
|
void (async () => {
|
|
14217
14672
|
try {
|
|
14218
|
-
const boardUrl =
|
|
14673
|
+
const boardUrl = record.board.url;
|
|
14219
14674
|
const http4 = await import("http");
|
|
14220
14675
|
const postData = JSON.stringify({ project_name: projectName });
|
|
14221
|
-
await new Promise((
|
|
14676
|
+
await new Promise((res) => {
|
|
14222
14677
|
const url = new URL("/api/boards", boardUrl);
|
|
14223
|
-
const req = http4.request(
|
|
14224
|
-
|
|
14225
|
-
|
|
14226
|
-
|
|
14227
|
-
|
|
14228
|
-
|
|
14229
|
-
|
|
14230
|
-
let data = "";
|
|
14231
|
-
res.on("data", (chunk) => {
|
|
14232
|
-
data += chunk.toString();
|
|
14233
|
-
});
|
|
14234
|
-
res.on("end", () => {
|
|
14235
|
-
try {
|
|
14236
|
-
const result = JSON.parse(data);
|
|
14237
|
-
if (result.created) {
|
|
14238
|
-
projectConfig.board.auto_created = true;
|
|
14239
|
-
}
|
|
14240
|
-
} catch {
|
|
14678
|
+
const req = http4.request(
|
|
14679
|
+
url,
|
|
14680
|
+
{
|
|
14681
|
+
method: "POST",
|
|
14682
|
+
headers: {
|
|
14683
|
+
"Content-Type": "application/json",
|
|
14684
|
+
"Content-Length": Buffer.byteLength(postData).toString()
|
|
14241
14685
|
}
|
|
14242
|
-
|
|
14243
|
-
|
|
14244
|
-
|
|
14245
|
-
req.on("error", () =>
|
|
14686
|
+
},
|
|
14687
|
+
() => res()
|
|
14688
|
+
);
|
|
14689
|
+
req.on("error", () => res());
|
|
14246
14690
|
req.end(postData);
|
|
14247
14691
|
});
|
|
14248
14692
|
} catch {
|
|
@@ -14251,28 +14695,21 @@ function projectAddAction(factoryDir, projectPath, opts) {
|
|
|
14251
14695
|
}
|
|
14252
14696
|
}
|
|
14253
14697
|
function projectListAction(factoryDir) {
|
|
14254
|
-
const projectsDir =
|
|
14255
|
-
|
|
14256
|
-
return readdirSync13(projectsDir).filter((d) => !d.startsWith(".") && existsSync38(join37(projectsDir, d, "project.json"))).map((d) => {
|
|
14257
|
-
try {
|
|
14258
|
-
return JSON.parse(readFileSync35(join37(projectsDir, d, "project.json"), "utf-8"));
|
|
14259
|
-
} catch {
|
|
14260
|
-
return null;
|
|
14261
|
-
}
|
|
14262
|
-
}).filter(Boolean);
|
|
14698
|
+
const projectsDir = join38(factoryDir, ".beastmode", "projects");
|
|
14699
|
+
return listProjectRecords(projectsDir);
|
|
14263
14700
|
}
|
|
14264
14701
|
function projectRemoveAction(factoryDir, name) {
|
|
14265
|
-
const projectDir =
|
|
14266
|
-
if (!
|
|
14267
|
-
const
|
|
14268
|
-
|
|
14269
|
-
|
|
14702
|
+
const projectDir = join38(factoryDir, ".beastmode", "projects", name);
|
|
14703
|
+
if (!existsSync39(projectDir)) throw new Error(`Project not found: ${name}`);
|
|
14704
|
+
const archivedBase = join38(factoryDir, ".beastmode", "projects", ".archived");
|
|
14705
|
+
mkdirSync24(archivedBase, { recursive: true });
|
|
14706
|
+
renameSync3(projectDir, join38(archivedBase, name));
|
|
14270
14707
|
}
|
|
14271
14708
|
var projectCommand = new Command24("project").description("Manage projects in this factory");
|
|
14272
14709
|
projectCommand.command("add <path>").description("Register a project").option("--name <name>", "Override project name").option("--board-id <id>", "Link to existing board ID").action((path, opts) => {
|
|
14273
14710
|
const factoryDir = resolve21(".");
|
|
14274
14711
|
projectAddAction(factoryDir, path, opts);
|
|
14275
|
-
console.log(`Project registered: ${opts.name ||
|
|
14712
|
+
console.log(`Project registered: ${opts.name || basename8(resolve21(path))}`);
|
|
14276
14713
|
});
|
|
14277
14714
|
projectCommand.command("list").description("List registered projects").action(() => {
|
|
14278
14715
|
const projects = projectListAction(resolve21("."));
|
|
@@ -14281,7 +14718,7 @@ projectCommand.command("list").description("List registered projects").action(()
|
|
|
14281
14718
|
return;
|
|
14282
14719
|
}
|
|
14283
14720
|
for (const p of projects) {
|
|
14284
|
-
console.log(` ${p
|
|
14721
|
+
console.log(` ${p["name"]} \u2014 ${p["path"]}`);
|
|
14285
14722
|
}
|
|
14286
14723
|
});
|
|
14287
14724
|
projectCommand.command("remove <name>").description("Archive a project").action((name) => {
|