@beastmode-develeap/beastmode 0.1.265 → 0.1.267
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 +1533 -1093
- 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
|
@@ -95,17 +95,36 @@ var init_schemas = __esm({
|
|
|
95
95
|
});
|
|
96
96
|
DeployConfigSchema = z.object({
|
|
97
97
|
target: z.string().default("pr-only"),
|
|
98
|
+
verify_port: z.number().int().default(3001),
|
|
98
99
|
config: z.record(z.unknown()).default({})
|
|
99
100
|
});
|
|
100
101
|
ProjectConfigSchema = z.object({
|
|
101
102
|
name: z.string(),
|
|
102
|
-
repo: z.string().optional(),
|
|
103
103
|
path: z.string(),
|
|
104
|
+
github: z.object({
|
|
105
|
+
repo: z.string().default(""),
|
|
106
|
+
default_branch: z.string().default("main")
|
|
107
|
+
}).default({}),
|
|
108
|
+
board: z.object({
|
|
109
|
+
id: z.number().nullable().default(null),
|
|
110
|
+
url: z.string().default("http://127.0.0.1:8080"),
|
|
111
|
+
auto_created: z.boolean().default(false)
|
|
112
|
+
}).default({}),
|
|
104
113
|
stack: StackConfigSchema,
|
|
105
114
|
deploy: DeployConfigSchema.default({}),
|
|
106
115
|
pipeline: z.record(z.unknown()).default({}),
|
|
107
116
|
models: z.record(z.string()).default({}),
|
|
108
|
-
plugins: z.array(z.string()).default([])
|
|
117
|
+
plugins: z.array(z.string()).default([]),
|
|
118
|
+
slots: z.object({
|
|
119
|
+
max: z.number().nullable().default(null)
|
|
120
|
+
}).default({}),
|
|
121
|
+
infra: z.object({
|
|
122
|
+
credentials: z.object({
|
|
123
|
+
mode: z.string().default("factory")
|
|
124
|
+
}).default({}),
|
|
125
|
+
state_backend: z.string().default("auto")
|
|
126
|
+
}).default({}),
|
|
127
|
+
registered_at: z.string().default("")
|
|
109
128
|
});
|
|
110
129
|
FactoryIdentitySchema = z.object({
|
|
111
130
|
factory_name: z.string(),
|
|
@@ -800,8 +819,8 @@ var init_export_adapter = __esm({
|
|
|
800
819
|
});
|
|
801
820
|
|
|
802
821
|
// src/engine/stack-detector.ts
|
|
803
|
-
import { existsSync, readFileSync } from "fs";
|
|
804
|
-
import { join } from "path";
|
|
822
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
823
|
+
import { join, relative, basename, extname } from "path";
|
|
805
824
|
function readFileSafe(path) {
|
|
806
825
|
try {
|
|
807
826
|
return readFileSync(path, "utf-8");
|
|
@@ -875,11 +894,362 @@ function extractGitRemote(projectDir) {
|
|
|
875
894
|
if (httpsMatch) return httpsMatch[1].replace(/\.git$/, "");
|
|
876
895
|
return null;
|
|
877
896
|
}
|
|
897
|
+
function isDirectory(path) {
|
|
898
|
+
try {
|
|
899
|
+
return statSync(path).isDirectory();
|
|
900
|
+
} catch {
|
|
901
|
+
return false;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
function listDirs(parent) {
|
|
905
|
+
try {
|
|
906
|
+
return readdirSync(parent, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
907
|
+
} catch {
|
|
908
|
+
return [];
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
function hasAnyManifest(dir) {
|
|
912
|
+
return PACKAGE_MANIFESTS.some((m) => existsSync(join(dir, m)));
|
|
913
|
+
}
|
|
914
|
+
function expandGlob(rootDir, pattern) {
|
|
915
|
+
let pat = pattern.replace(/^\.\//, "");
|
|
916
|
+
pat = pat.replace(/\/+$/, "");
|
|
917
|
+
if (!pat) return [];
|
|
918
|
+
const parts = pat.split("/");
|
|
919
|
+
let candidates = [""];
|
|
920
|
+
for (const part of parts) {
|
|
921
|
+
const next = [];
|
|
922
|
+
for (const cand of candidates) {
|
|
923
|
+
const abs = cand ? join(rootDir, cand) : rootDir;
|
|
924
|
+
if (part === "*" || part === "**") {
|
|
925
|
+
const children = listDirs(abs).filter((name) => !IGNORE_DIRS.has(name));
|
|
926
|
+
for (const child of children) {
|
|
927
|
+
next.push(cand ? join(cand, child) : child);
|
|
928
|
+
}
|
|
929
|
+
} else if (part.includes("*")) {
|
|
930
|
+
const regex = new RegExp(
|
|
931
|
+
"^" + part.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$"
|
|
932
|
+
);
|
|
933
|
+
const children = listDirs(abs).filter((name) => regex.test(name));
|
|
934
|
+
for (const child of children) {
|
|
935
|
+
next.push(cand ? join(cand, child) : child);
|
|
936
|
+
}
|
|
937
|
+
} else {
|
|
938
|
+
const candidate = cand ? join(cand, part) : part;
|
|
939
|
+
if (isDirectory(join(rootDir, candidate))) {
|
|
940
|
+
next.push(candidate);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
candidates = next;
|
|
945
|
+
}
|
|
946
|
+
return candidates.filter((c) => hasAnyManifest(join(rootDir, c)));
|
|
947
|
+
}
|
|
948
|
+
function parsePnpmWorkspaceYaml(content) {
|
|
949
|
+
const lines = content.split(/\r?\n/);
|
|
950
|
+
const globs = [];
|
|
951
|
+
let inPackages = false;
|
|
952
|
+
for (const raw of lines) {
|
|
953
|
+
const line = raw.replace(/\s+$/, "");
|
|
954
|
+
if (/^packages\s*:/.test(line)) {
|
|
955
|
+
inPackages = true;
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
if (inPackages) {
|
|
959
|
+
if (line && !/^\s/.test(line) && !line.startsWith("-")) {
|
|
960
|
+
inPackages = false;
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
const m = line.match(/^\s*-\s*['"]?([^'"#]+?)['"]?\s*$/);
|
|
964
|
+
if (m) {
|
|
965
|
+
const val = m[1].trim();
|
|
966
|
+
if (val) globs.push(val);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return globs;
|
|
971
|
+
}
|
|
972
|
+
function parseGoWorkUseBlock(content) {
|
|
973
|
+
const dirs = [];
|
|
974
|
+
const singleLineMatches = content.matchAll(/^\s*use\s+(\S+)\s*$/gm);
|
|
975
|
+
for (const m of singleLineMatches) {
|
|
976
|
+
dirs.push(m[1].replace(/^\.\//, ""));
|
|
977
|
+
}
|
|
978
|
+
const blockMatch = content.match(/use\s*\(([\s\S]*?)\)/);
|
|
979
|
+
if (blockMatch) {
|
|
980
|
+
for (const raw of blockMatch[1].split(/\r?\n/)) {
|
|
981
|
+
const line = raw.trim();
|
|
982
|
+
if (!line || line.startsWith("//")) continue;
|
|
983
|
+
const cleaned = line.replace(/\/\/.*$/, "").trim();
|
|
984
|
+
if (cleaned) dirs.push(cleaned.replace(/^\.\//, ""));
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return dirs;
|
|
988
|
+
}
|
|
989
|
+
function parseCargoWorkspaceMembers(content) {
|
|
990
|
+
const wsIdx = content.search(/^\s*\[workspace\]/m);
|
|
991
|
+
if (wsIdx < 0) return [];
|
|
992
|
+
const rest = content.slice(wsIdx);
|
|
993
|
+
const m = rest.match(/members\s*=\s*\[([\s\S]*?)\]/);
|
|
994
|
+
if (!m) return [];
|
|
995
|
+
const inner = m[1];
|
|
996
|
+
const members = [];
|
|
997
|
+
for (const raw of inner.split(",")) {
|
|
998
|
+
const cleaned = raw.replace(/#.*$/, "").trim().replace(/^['"]|['"]$/g, "");
|
|
999
|
+
if (cleaned) members.push(cleaned);
|
|
1000
|
+
}
|
|
1001
|
+
return members;
|
|
1002
|
+
}
|
|
1003
|
+
function parseGradleInclude(content) {
|
|
1004
|
+
const modules = [];
|
|
1005
|
+
const re = /include\s*(?:\(\s*)?([^)\n]+?)(?:\s*\))?\s*$/gm;
|
|
1006
|
+
let match;
|
|
1007
|
+
while ((match = re.exec(content)) !== null) {
|
|
1008
|
+
const args = match[1];
|
|
1009
|
+
const stringMatches = args.matchAll(/['"]([^'"]+)['"]/g);
|
|
1010
|
+
for (const sm of stringMatches) {
|
|
1011
|
+
const raw = sm[1].trim();
|
|
1012
|
+
const path = raw.replace(/^:+/, "").replace(/:/g, "/");
|
|
1013
|
+
if (path) modules.push(path);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return modules;
|
|
1017
|
+
}
|
|
1018
|
+
function parseMavenModules(content) {
|
|
1019
|
+
const modules = [];
|
|
1020
|
+
const re = /<module>\s*([^<\s]+)\s*<\/module>/g;
|
|
1021
|
+
let match;
|
|
1022
|
+
while ((match = re.exec(content)) !== null) {
|
|
1023
|
+
modules.push(match[1].trim());
|
|
1024
|
+
}
|
|
1025
|
+
return modules;
|
|
1026
|
+
}
|
|
1027
|
+
function detectWorkspaces(projectDir) {
|
|
1028
|
+
const pnpmYaml = readFileSafe(join(projectDir, "pnpm-workspace.yaml"));
|
|
1029
|
+
if (pnpmYaml !== null) {
|
|
1030
|
+
const globs = parsePnpmWorkspaceYaml(pnpmYaml);
|
|
1031
|
+
const packages = [];
|
|
1032
|
+
for (const g of globs) {
|
|
1033
|
+
packages.push(...expandGlob(projectDir, g));
|
|
1034
|
+
}
|
|
1035
|
+
return { type: "pnpm", packages: dedupe(packages) };
|
|
1036
|
+
}
|
|
1037
|
+
if (existsSync(join(projectDir, "nx.json"))) {
|
|
1038
|
+
const packages = [];
|
|
1039
|
+
for (const parent of ["packages", "apps", "libs"]) {
|
|
1040
|
+
const parentDir = join(projectDir, parent);
|
|
1041
|
+
if (!isDirectory(parentDir)) continue;
|
|
1042
|
+
for (const child of listDirs(parentDir)) {
|
|
1043
|
+
const childDir = join(parentDir, child);
|
|
1044
|
+
if (existsSync(join(childDir, "project.json")) || hasAnyManifest(childDir)) {
|
|
1045
|
+
packages.push(join(parent, child));
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return { type: "nx", packages: dedupe(packages) };
|
|
1050
|
+
}
|
|
1051
|
+
const hasTurbo = existsSync(join(projectDir, "turbo.json"));
|
|
1052
|
+
const pkgContent = readFileSafe(join(projectDir, "package.json"));
|
|
1053
|
+
if (pkgContent) {
|
|
1054
|
+
const pkg = parseJsonSafe(pkgContent);
|
|
1055
|
+
if (pkg) {
|
|
1056
|
+
let workspaces;
|
|
1057
|
+
const ws = pkg.workspaces;
|
|
1058
|
+
if (Array.isArray(ws)) {
|
|
1059
|
+
workspaces = ws.filter((v) => typeof v === "string");
|
|
1060
|
+
} else if (ws && typeof ws === "object" && Array.isArray(ws.packages)) {
|
|
1061
|
+
workspaces = ws.packages.filter(
|
|
1062
|
+
(v) => typeof v === "string"
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
if (workspaces && workspaces.length > 0) {
|
|
1066
|
+
const packages = [];
|
|
1067
|
+
for (const g of workspaces) {
|
|
1068
|
+
packages.push(...expandGlob(projectDir, g));
|
|
1069
|
+
}
|
|
1070
|
+
return {
|
|
1071
|
+
type: hasTurbo ? "turbo" : "npm",
|
|
1072
|
+
packages: dedupe(packages)
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
const goWork = readFileSafe(join(projectDir, "go.work"));
|
|
1078
|
+
if (goWork !== null) {
|
|
1079
|
+
const dirs = parseGoWorkUseBlock(goWork);
|
|
1080
|
+
const packages = dirs.filter(
|
|
1081
|
+
(d) => existsSync(join(projectDir, d, "go.mod"))
|
|
1082
|
+
);
|
|
1083
|
+
return { type: "go", packages: dedupe(packages) };
|
|
1084
|
+
}
|
|
1085
|
+
const cargoToml = readFileSafe(join(projectDir, "Cargo.toml"));
|
|
1086
|
+
if (cargoToml !== null && /^\s*\[workspace\]/m.test(cargoToml)) {
|
|
1087
|
+
const members = parseCargoWorkspaceMembers(cargoToml);
|
|
1088
|
+
const packages = [];
|
|
1089
|
+
for (const m of members) {
|
|
1090
|
+
if (m.includes("*")) {
|
|
1091
|
+
packages.push(...expandGlob(projectDir, m));
|
|
1092
|
+
} else if (existsSync(join(projectDir, m, "Cargo.toml"))) {
|
|
1093
|
+
packages.push(m);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return { type: "cargo", packages: dedupe(packages) };
|
|
1097
|
+
}
|
|
1098
|
+
const gradleSettings = readFileSafe(join(projectDir, "settings.gradle")) ?? readFileSafe(join(projectDir, "settings.gradle.kts"));
|
|
1099
|
+
if (gradleSettings !== null) {
|
|
1100
|
+
const modules = parseGradleInclude(gradleSettings);
|
|
1101
|
+
const packages = modules.filter((m) => isDirectory(join(projectDir, m)));
|
|
1102
|
+
if (packages.length > 0) {
|
|
1103
|
+
return { type: "gradle", packages: dedupe(packages) };
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
const pom = readFileSafe(join(projectDir, "pom.xml"));
|
|
1107
|
+
if (pom !== null) {
|
|
1108
|
+
const modules = parseMavenModules(pom);
|
|
1109
|
+
const packages = modules.filter(
|
|
1110
|
+
(m) => existsSync(join(projectDir, m, "pom.xml"))
|
|
1111
|
+
);
|
|
1112
|
+
if (packages.length > 0) {
|
|
1113
|
+
return { type: "maven", packages: dedupe(packages) };
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
function dedupe(items) {
|
|
1119
|
+
return Array.from(new Set(items));
|
|
1120
|
+
}
|
|
1121
|
+
function extractPackageName(packageDir) {
|
|
1122
|
+
const pkgContent = readFileSafe(join(packageDir, "package.json"));
|
|
1123
|
+
if (pkgContent) {
|
|
1124
|
+
const pkg = parseJsonSafe(pkgContent);
|
|
1125
|
+
if (pkg && typeof pkg.name === "string" && pkg.name) return pkg.name;
|
|
1126
|
+
}
|
|
1127
|
+
const cargoToml = readFileSafe(join(packageDir, "Cargo.toml"));
|
|
1128
|
+
if (cargoToml) {
|
|
1129
|
+
const m = cargoToml.match(/\[package\][\s\S]*?name\s*=\s*['"]([^'"]+)['"]/);
|
|
1130
|
+
if (m) return m[1];
|
|
1131
|
+
}
|
|
1132
|
+
const pom = readFileSafe(join(packageDir, "pom.xml"));
|
|
1133
|
+
if (pom) {
|
|
1134
|
+
const m = pom.match(/<artifactId>\s*([^<\s]+)\s*<\/artifactId>/);
|
|
1135
|
+
if (m) return m[1];
|
|
1136
|
+
}
|
|
1137
|
+
const goMod = readFileSafe(join(packageDir, "go.mod"));
|
|
1138
|
+
if (goMod) {
|
|
1139
|
+
const m = goMod.match(/^module\s+(\S+)/m);
|
|
1140
|
+
if (m) {
|
|
1141
|
+
const parts = m[1].split("/");
|
|
1142
|
+
return parts[parts.length - 1];
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return basename(packageDir);
|
|
1146
|
+
}
|
|
1147
|
+
function detectEntryPoints(packageDir, framework) {
|
|
1148
|
+
const entries = [];
|
|
1149
|
+
const pkgContent = readFileSafe(join(packageDir, "package.json"));
|
|
1150
|
+
if (pkgContent) {
|
|
1151
|
+
const pkg = parseJsonSafe(pkgContent);
|
|
1152
|
+
if (pkg) {
|
|
1153
|
+
if (typeof pkg.main === "string" && pkg.main) entries.push(pkg.main);
|
|
1154
|
+
if (typeof pkg.bin === "string" && pkg.bin) {
|
|
1155
|
+
entries.push(pkg.bin);
|
|
1156
|
+
} else if (pkg.bin && typeof pkg.bin === "object") {
|
|
1157
|
+
for (const v of Object.values(pkg.bin)) {
|
|
1158
|
+
if (typeof v === "string") entries.push(v);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
if (framework === "go") {
|
|
1164
|
+
const cmdDir = join(packageDir, "cmd");
|
|
1165
|
+
if (isDirectory(cmdDir)) {
|
|
1166
|
+
for (const sub of listDirs(cmdDir)) {
|
|
1167
|
+
entries.push(join("cmd", sub));
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
if (framework === "rust") {
|
|
1172
|
+
if (existsSync(join(packageDir, "src", "main.rs"))) {
|
|
1173
|
+
entries.push("src/main.rs");
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (framework === "java-maven" || framework === "java-gradle") {
|
|
1177
|
+
if (isDirectory(join(packageDir, "src", "main", "java"))) {
|
|
1178
|
+
entries.push("src/main/java");
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
return dedupe(entries);
|
|
1182
|
+
}
|
|
1183
|
+
function detectPackageStack(packageDir, rootDir) {
|
|
1184
|
+
const framework = detectFramework(packageDir);
|
|
1185
|
+
const preset = STACK_PRESETS[framework] || STACK_PRESETS.unknown;
|
|
1186
|
+
const pm = ["nextjs", "vite", "react", "node"].includes(framework) ? detectPackageManager(packageDir) : preset.language === "python" ? "pip" : "";
|
|
1187
|
+
const resolved = resolveCommands(preset, pm);
|
|
1188
|
+
const name = extractPackageName(packageDir);
|
|
1189
|
+
const entryPoints = detectEntryPoints(packageDir, framework);
|
|
1190
|
+
const relPath = relative(rootDir, packageDir) || ".";
|
|
1191
|
+
return {
|
|
1192
|
+
relative_path: relPath,
|
|
1193
|
+
name,
|
|
1194
|
+
language: resolved.language,
|
|
1195
|
+
framework,
|
|
1196
|
+
package_manager: pm,
|
|
1197
|
+
build_command: resolved.build,
|
|
1198
|
+
dev_command: resolved.dev,
|
|
1199
|
+
test_command: resolved.test,
|
|
1200
|
+
install_command: resolved.install,
|
|
1201
|
+
dev_port: resolved.port !== 0 ? resolved.port : void 0,
|
|
1202
|
+
entry_points: entryPoints
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
function scanPrimaryLanguages(projectDir) {
|
|
1206
|
+
const counts = {};
|
|
1207
|
+
let total = 0;
|
|
1208
|
+
const MAX_DEPTH = 8;
|
|
1209
|
+
function walk(dir, depth) {
|
|
1210
|
+
if (depth > MAX_DEPTH) return;
|
|
1211
|
+
let entries;
|
|
1212
|
+
try {
|
|
1213
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1214
|
+
} catch {
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
for (const entry of entries) {
|
|
1218
|
+
if (entry.name.startsWith(".") && entry.name !== ".") {
|
|
1219
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
1220
|
+
}
|
|
1221
|
+
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
1222
|
+
const full = join(dir, entry.name);
|
|
1223
|
+
if (entry.isDirectory()) {
|
|
1224
|
+
walk(full, depth + 1);
|
|
1225
|
+
} else if (entry.isFile()) {
|
|
1226
|
+
const ext = extname(entry.name).toLowerCase();
|
|
1227
|
+
const lang = EXT_LANG_MAP[ext];
|
|
1228
|
+
if (lang) {
|
|
1229
|
+
counts[lang] = (counts[lang] || 0) + 1;
|
|
1230
|
+
total += 1;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
walk(projectDir, 0);
|
|
1236
|
+
if (total === 0) return [];
|
|
1237
|
+
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);
|
|
1238
|
+
return ranked;
|
|
1239
|
+
}
|
|
878
1240
|
function detectStack(projectDir) {
|
|
879
1241
|
const framework = detectFramework(projectDir);
|
|
880
1242
|
const preset = STACK_PRESETS[framework] || STACK_PRESETS.unknown;
|
|
881
1243
|
const pm = ["nextjs", "vite", "react", "node"].includes(framework) ? detectPackageManager(projectDir) : preset.language === "python" ? "pip" : "";
|
|
882
1244
|
const resolved = resolveCommands(preset, pm);
|
|
1245
|
+
const workspace = detectWorkspaces(projectDir);
|
|
1246
|
+
const primaryLanguages = scanPrimaryLanguages(projectDir);
|
|
1247
|
+
let packages;
|
|
1248
|
+
if (workspace && workspace.packages.length > 0) {
|
|
1249
|
+
packages = workspace.packages.map(
|
|
1250
|
+
(pkgRelPath) => detectPackageStack(join(projectDir, pkgRelPath), projectDir)
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
883
1253
|
return {
|
|
884
1254
|
language: resolved.language,
|
|
885
1255
|
framework,
|
|
@@ -896,13 +1266,54 @@ function detectStack(projectDir) {
|
|
|
896
1266
|
dev_port: resolved.port,
|
|
897
1267
|
suggested_plugins: resolved.plugins,
|
|
898
1268
|
suggested_preset: resolved.preset,
|
|
899
|
-
suggested_deploy: resolved.deploy
|
|
1269
|
+
suggested_deploy: resolved.deploy,
|
|
1270
|
+
is_monorepo: workspace !== null,
|
|
1271
|
+
total_packages: packages ? packages.length : 0,
|
|
1272
|
+
primary_languages: primaryLanguages,
|
|
1273
|
+
packages
|
|
900
1274
|
};
|
|
901
1275
|
}
|
|
902
|
-
var STACK_PRESETS;
|
|
1276
|
+
var IGNORE_DIRS, EXT_LANG_MAP, PACKAGE_MANIFESTS, STACK_PRESETS;
|
|
903
1277
|
var init_stack_detector = __esm({
|
|
904
1278
|
"src/engine/stack-detector.ts"() {
|
|
905
1279
|
"use strict";
|
|
1280
|
+
IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
1281
|
+
"node_modules",
|
|
1282
|
+
".git",
|
|
1283
|
+
"vendor",
|
|
1284
|
+
"target",
|
|
1285
|
+
"dist",
|
|
1286
|
+
"build",
|
|
1287
|
+
"__pycache__",
|
|
1288
|
+
".venv",
|
|
1289
|
+
".next",
|
|
1290
|
+
".turbo",
|
|
1291
|
+
"coverage"
|
|
1292
|
+
]);
|
|
1293
|
+
EXT_LANG_MAP = {
|
|
1294
|
+
".ts": "typescript",
|
|
1295
|
+
".tsx": "typescript",
|
|
1296
|
+
".js": "javascript",
|
|
1297
|
+
".jsx": "javascript",
|
|
1298
|
+
".py": "python",
|
|
1299
|
+
".go": "go",
|
|
1300
|
+
".rs": "rust",
|
|
1301
|
+
".java": "java",
|
|
1302
|
+
".kt": "kotlin",
|
|
1303
|
+
".swift": "swift",
|
|
1304
|
+
".rb": "ruby",
|
|
1305
|
+
".cs": "csharp"
|
|
1306
|
+
};
|
|
1307
|
+
PACKAGE_MANIFESTS = [
|
|
1308
|
+
"package.json",
|
|
1309
|
+
"go.mod",
|
|
1310
|
+
"Cargo.toml",
|
|
1311
|
+
"pom.xml",
|
|
1312
|
+
"build.gradle",
|
|
1313
|
+
"build.gradle.kts",
|
|
1314
|
+
"pyproject.toml",
|
|
1315
|
+
"requirements.txt"
|
|
1316
|
+
];
|
|
906
1317
|
STACK_PRESETS = {
|
|
907
1318
|
nextjs: {
|
|
908
1319
|
language: "typescript",
|
|
@@ -2251,15 +2662,15 @@ var init_plugin_installer = __esm({
|
|
|
2251
2662
|
// src/engine/skill-manager.ts
|
|
2252
2663
|
import {
|
|
2253
2664
|
existsSync as existsSync7,
|
|
2254
|
-
readdirSync,
|
|
2665
|
+
readdirSync as readdirSync2,
|
|
2255
2666
|
cpSync as cpSync2,
|
|
2256
2667
|
rmSync as rmSync2,
|
|
2257
2668
|
readFileSync as readFileSync7,
|
|
2258
2669
|
writeFileSync as writeFileSync5,
|
|
2259
2670
|
mkdirSync as mkdirSync5,
|
|
2260
|
-
statSync
|
|
2671
|
+
statSync as statSync2
|
|
2261
2672
|
} from "fs";
|
|
2262
|
-
import { join as join7, basename } from "path";
|
|
2673
|
+
import { join as join7, basename as basename2 } from "path";
|
|
2263
2674
|
function parseSkillFrontmatter(content) {
|
|
2264
2675
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
2265
2676
|
if (!match) return {};
|
|
@@ -2278,7 +2689,7 @@ function addSkill(factoryDir, sourcePath) {
|
|
|
2278
2689
|
if (!existsSync7(sourcePath)) {
|
|
2279
2690
|
throw new Error(`Skill source path does not exist: ${sourcePath}`);
|
|
2280
2691
|
}
|
|
2281
|
-
const stat =
|
|
2692
|
+
const stat = statSync2(sourcePath);
|
|
2282
2693
|
if (!stat.isDirectory()) {
|
|
2283
2694
|
throw new Error(`Skill source must be a directory: ${sourcePath}`);
|
|
2284
2695
|
}
|
|
@@ -2288,7 +2699,7 @@ function addSkill(factoryDir, sourcePath) {
|
|
|
2288
2699
|
`SKILL.md not found in ${sourcePath}. Every skill must have a SKILL.md with frontmatter.`
|
|
2289
2700
|
);
|
|
2290
2701
|
}
|
|
2291
|
-
const skillName =
|
|
2702
|
+
const skillName = basename2(sourcePath);
|
|
2292
2703
|
const destPath = join7(factoryDir, ".beastmode", "skills", skillName);
|
|
2293
2704
|
if (existsSync7(destPath)) {
|
|
2294
2705
|
throw new Error(
|
|
@@ -2335,7 +2746,7 @@ function listSkills(factoryDir) {
|
|
|
2335
2746
|
const bmDir = join7(factoryDir, ".beastmode");
|
|
2336
2747
|
const customSkillsDir = join7(bmDir, "skills");
|
|
2337
2748
|
if (existsSync7(customSkillsDir)) {
|
|
2338
|
-
for (const entry of
|
|
2749
|
+
for (const entry of readdirSync2(customSkillsDir, { withFileTypes: true })) {
|
|
2339
2750
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
2340
2751
|
const skillMdPath = join7(customSkillsDir, entry.name, "SKILL.md");
|
|
2341
2752
|
if (!existsSync7(skillMdPath)) continue;
|
|
@@ -2352,7 +2763,7 @@ function listSkills(factoryDir) {
|
|
|
2352
2763
|
}
|
|
2353
2764
|
const pluginsDir = join7(bmDir, "plugins");
|
|
2354
2765
|
if (existsSync7(pluginsDir)) {
|
|
2355
|
-
for (const pluginEntry of
|
|
2766
|
+
for (const pluginEntry of readdirSync2(pluginsDir, {
|
|
2356
2767
|
withFileTypes: true
|
|
2357
2768
|
})) {
|
|
2358
2769
|
if (!pluginEntry.isDirectory() || pluginEntry.name.startsWith("."))
|
|
@@ -2363,7 +2774,7 @@ function listSkills(factoryDir) {
|
|
|
2363
2774
|
"skills"
|
|
2364
2775
|
);
|
|
2365
2776
|
if (!existsSync7(pluginSkillsDir)) continue;
|
|
2366
|
-
for (const skillEntry of
|
|
2777
|
+
for (const skillEntry of readdirSync2(pluginSkillsDir, {
|
|
2367
2778
|
withFileTypes: true
|
|
2368
2779
|
})) {
|
|
2369
2780
|
if (!skillEntry.isDirectory() || skillEntry.name.startsWith("."))
|
|
@@ -2462,7 +2873,10 @@ function mapDaemonToFactory(daemon) {
|
|
|
2462
2873
|
const hasDeployConfig = Object.keys(deployConfig).length > 0;
|
|
2463
2874
|
const projectRaw = {
|
|
2464
2875
|
name: repoName,
|
|
2465
|
-
|
|
2876
|
+
github: {
|
|
2877
|
+
repo: daemon.github?.project_repo || "",
|
|
2878
|
+
default_branch: daemon.github?.base_branch || "main"
|
|
2879
|
+
},
|
|
2466
2880
|
path: daemon.repos?.project || "",
|
|
2467
2881
|
stack: {
|
|
2468
2882
|
detected: stack.name || "node",
|
|
@@ -2666,7 +3080,7 @@ function generateDaemonConfig(factoryConfig, projectConfig, factoryPath) {
|
|
|
2666
3080
|
taskBackendValue = backendAdapter;
|
|
2667
3081
|
}
|
|
2668
3082
|
const boardUrl = process.env.BEASTMODE_BOARD_URL || taskBackend.config?.url || "http://127.0.0.1:8080";
|
|
2669
|
-
const projectRepo = projectConfig?.repo || "";
|
|
3083
|
+
const projectRepo = projectConfig?.github?.repo || (projectConfig?.repo ?? "");
|
|
2670
3084
|
const projectPath = projectConfig?.path || "";
|
|
2671
3085
|
const projectOrg = projectRepo.split("/")[0] || "beastmode-agent";
|
|
2672
3086
|
const stack = projectConfig?.stack;
|
|
@@ -2808,6 +3222,159 @@ var init_bridge = __esm({
|
|
|
2808
3222
|
}
|
|
2809
3223
|
});
|
|
2810
3224
|
|
|
3225
|
+
// src/engine/project-record.ts
|
|
3226
|
+
import { existsSync as existsSync8, writeFileSync as writeFileSync6, renameSync, readFileSync as readFileSync8, readdirSync as readdirSync3, mkdirSync as mkdirSync6 } from "fs";
|
|
3227
|
+
import { join as join8 } from "path";
|
|
3228
|
+
function detectShape(parsed) {
|
|
3229
|
+
if (parsed.github && typeof parsed.github === "object" && parsed.slots && typeof parsed.slots === "object") {
|
|
3230
|
+
return "canonical";
|
|
3231
|
+
}
|
|
3232
|
+
if (parsed.github && typeof parsed.github === "object") {
|
|
3233
|
+
return "cli-legacy";
|
|
3234
|
+
}
|
|
3235
|
+
return "http-legacy";
|
|
3236
|
+
}
|
|
3237
|
+
function normalizeToCanonical(parsed, name) {
|
|
3238
|
+
const shape = detectShape(parsed);
|
|
3239
|
+
if (shape === "canonical") {
|
|
3240
|
+
return parsed;
|
|
3241
|
+
}
|
|
3242
|
+
if (shape === "cli-legacy") {
|
|
3243
|
+
const github = parsed.github;
|
|
3244
|
+
return {
|
|
3245
|
+
name: parsed.name || name,
|
|
3246
|
+
path: parsed.path || "",
|
|
3247
|
+
github: {
|
|
3248
|
+
repo: github.repo || "",
|
|
3249
|
+
default_branch: github.default_branch || "main"
|
|
3250
|
+
},
|
|
3251
|
+
board: parsed.board || {
|
|
3252
|
+
id: null,
|
|
3253
|
+
url: "http://127.0.0.1:8080",
|
|
3254
|
+
auto_created: true
|
|
3255
|
+
},
|
|
3256
|
+
deploy: parsed.deploy || { verify_port: 3001 },
|
|
3257
|
+
pipeline: parsed.pipeline || {},
|
|
3258
|
+
models: parsed.models || {},
|
|
3259
|
+
slots: { max: null },
|
|
3260
|
+
registered_at: parsed.registered_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3263
|
+
return {
|
|
3264
|
+
name: parsed.name || name,
|
|
3265
|
+
path: parsed.path || "",
|
|
3266
|
+
github: {
|
|
3267
|
+
repo: parsed.repo || "",
|
|
3268
|
+
default_branch: "main"
|
|
3269
|
+
},
|
|
3270
|
+
board: { id: null, url: "http://127.0.0.1:8080", auto_created: true },
|
|
3271
|
+
deploy: { verify_port: 3001 },
|
|
3272
|
+
pipeline: parsed.pipeline || {},
|
|
3273
|
+
models: parsed.models || {},
|
|
3274
|
+
slots: { max: null },
|
|
3275
|
+
registered_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3278
|
+
function createProjectRecord(input) {
|
|
3279
|
+
return {
|
|
3280
|
+
name: input.name,
|
|
3281
|
+
path: input.resolvedPath,
|
|
3282
|
+
github: {
|
|
3283
|
+
repo: input.gitRemote || "",
|
|
3284
|
+
default_branch: "main"
|
|
3285
|
+
},
|
|
3286
|
+
board: {
|
|
3287
|
+
id: input.boardId ?? null,
|
|
3288
|
+
url: process.env.BEASTMODE_BOARD_URL || "http://127.0.0.1:8080",
|
|
3289
|
+
auto_created: !input.boardId
|
|
3290
|
+
},
|
|
3291
|
+
deploy: { verify_port: input.verifyPort ?? 3001 },
|
|
3292
|
+
pipeline: {},
|
|
3293
|
+
models: {},
|
|
3294
|
+
slots: { max: null },
|
|
3295
|
+
registered_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3296
|
+
};
|
|
3297
|
+
}
|
|
3298
|
+
function writeProjectRecord(projectsDir, name, record) {
|
|
3299
|
+
const dir = join8(projectsDir, name);
|
|
3300
|
+
mkdirSync6(dir, { recursive: true });
|
|
3301
|
+
const filePath = join8(dir, "project.json");
|
|
3302
|
+
const tmpPath = `${filePath}.tmp`;
|
|
3303
|
+
writeFileSync6(tmpPath, JSON.stringify(record, null, 2) + "\n");
|
|
3304
|
+
renameSync(tmpPath, filePath);
|
|
3305
|
+
const extPath = join8(dir, "extensions.json");
|
|
3306
|
+
if (!existsSync8(extPath)) {
|
|
3307
|
+
writeFileSync6(
|
|
3308
|
+
extPath,
|
|
3309
|
+
JSON.stringify(
|
|
3310
|
+
{
|
|
3311
|
+
plugins: { add: [], remove: [] },
|
|
3312
|
+
mcps: { add: {}, remove: [] },
|
|
3313
|
+
skills: { add: [], remove: [] }
|
|
3314
|
+
},
|
|
3315
|
+
null,
|
|
3316
|
+
2
|
|
3317
|
+
) + "\n"
|
|
3318
|
+
);
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
function readProjectRecord(projectsDir, name) {
|
|
3322
|
+
const subdirPath = join8(projectsDir, name, "project.json");
|
|
3323
|
+
const flatPath = join8(projectsDir, `${name}.json`);
|
|
3324
|
+
let filePath;
|
|
3325
|
+
let isFlat = false;
|
|
3326
|
+
if (existsSync8(subdirPath)) {
|
|
3327
|
+
filePath = subdirPath;
|
|
3328
|
+
} else if (existsSync8(flatPath)) {
|
|
3329
|
+
filePath = flatPath;
|
|
3330
|
+
isFlat = true;
|
|
3331
|
+
} else {
|
|
3332
|
+
return null;
|
|
3333
|
+
}
|
|
3334
|
+
let parsed;
|
|
3335
|
+
try {
|
|
3336
|
+
parsed = JSON.parse(readFileSync8(filePath, "utf-8"));
|
|
3337
|
+
} catch {
|
|
3338
|
+
return null;
|
|
3339
|
+
}
|
|
3340
|
+
const shape = detectShape(parsed);
|
|
3341
|
+
const record = normalizeToCanonical(parsed, name);
|
|
3342
|
+
if (shape !== "canonical" || isFlat) {
|
|
3343
|
+
writeProjectRecord(projectsDir, name, record);
|
|
3344
|
+
}
|
|
3345
|
+
return record;
|
|
3346
|
+
}
|
|
3347
|
+
function listProjectRecords(projectsDir) {
|
|
3348
|
+
if (!existsSync8(projectsDir)) return [];
|
|
3349
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3350
|
+
const records = [];
|
|
3351
|
+
for (const entry of readdirSync3(projectsDir)) {
|
|
3352
|
+
if (entry.startsWith(".")) continue;
|
|
3353
|
+
if (existsSync8(join8(projectsDir, entry, "project.json"))) {
|
|
3354
|
+
if (!seen.has(entry)) {
|
|
3355
|
+
seen.add(entry);
|
|
3356
|
+
const record = readProjectRecord(projectsDir, entry);
|
|
3357
|
+
if (record) records.push(record);
|
|
3358
|
+
}
|
|
3359
|
+
continue;
|
|
3360
|
+
}
|
|
3361
|
+
if (entry.endsWith(".json")) {
|
|
3362
|
+
const name = entry.slice(0, -5);
|
|
3363
|
+
if (!seen.has(name)) {
|
|
3364
|
+
seen.add(name);
|
|
3365
|
+
const record = readProjectRecord(projectsDir, name);
|
|
3366
|
+
if (record) records.push(record);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
return records;
|
|
3371
|
+
}
|
|
3372
|
+
var init_project_record = __esm({
|
|
3373
|
+
"src/engine/project-record.ts"() {
|
|
3374
|
+
"use strict";
|
|
3375
|
+
}
|
|
3376
|
+
});
|
|
3377
|
+
|
|
2811
3378
|
// src/engine/index.ts
|
|
2812
3379
|
var engine_exports = {};
|
|
2813
3380
|
__export(engine_exports, {
|
|
@@ -2851,6 +3418,7 @@ __export(engine_exports, {
|
|
|
2851
3418
|
configGet: () => configGet,
|
|
2852
3419
|
configReset: () => configReset,
|
|
2853
3420
|
configSet: () => configSet,
|
|
3421
|
+
createProjectRecord: () => createProjectRecord,
|
|
2854
3422
|
createSkill: () => createSkill,
|
|
2855
3423
|
detectStack: () => detectStack,
|
|
2856
3424
|
exportMarkdown: () => exportMarkdown,
|
|
@@ -2870,6 +3438,7 @@ __export(engine_exports, {
|
|
|
2870
3438
|
listHooks: () => listHooks,
|
|
2871
3439
|
listMcps: () => listMcps,
|
|
2872
3440
|
listPresets: () => listPresets,
|
|
3441
|
+
listProjectRecords: () => listProjectRecords,
|
|
2873
3442
|
listProviders: () => listProviders,
|
|
2874
3443
|
listSkills: () => listSkills,
|
|
2875
3444
|
mapDaemonToFactory: () => mapDaemonToFactory,
|
|
@@ -2879,6 +3448,7 @@ __export(engine_exports, {
|
|
|
2879
3448
|
parseMethodologyManifest: () => parseMethodologyManifest,
|
|
2880
3449
|
parseTemplate: () => parseTemplate,
|
|
2881
3450
|
performUpgrade: () => performUpgrade,
|
|
3451
|
+
readProjectRecord: () => readProjectRecord,
|
|
2882
3452
|
removeHook: () => removeHook,
|
|
2883
3453
|
removeMcp: () => removeMcp,
|
|
2884
3454
|
removePlugin: () => removePlugin,
|
|
@@ -2895,7 +3465,8 @@ __export(engine_exports, {
|
|
|
2895
3465
|
scaffoldFactory: () => scaffoldFactory,
|
|
2896
3466
|
validateFactory: () => validateFactory,
|
|
2897
3467
|
validateProvider: () => validateProvider,
|
|
2898
|
-
validateSecrets: () => validateSecrets
|
|
3468
|
+
validateSecrets: () => validateSecrets,
|
|
3469
|
+
writeProjectRecord: () => writeProjectRecord
|
|
2899
3470
|
});
|
|
2900
3471
|
var init_engine = __esm({
|
|
2901
3472
|
"src/engine/index.ts"() {
|
|
@@ -2925,25 +3496,26 @@ var init_engine = __esm({
|
|
|
2925
3496
|
init_schemas();
|
|
2926
3497
|
init_migrator();
|
|
2927
3498
|
init_bridge();
|
|
3499
|
+
init_project_record();
|
|
2928
3500
|
}
|
|
2929
3501
|
});
|
|
2930
3502
|
|
|
2931
3503
|
// src/cli/utils/file-writer.ts
|
|
2932
|
-
import { mkdirSync as
|
|
3504
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, appendFileSync, existsSync as existsSync9 } from "fs";
|
|
2933
3505
|
import { dirname as dirname3 } from "path";
|
|
2934
3506
|
function executeFileActions(actions) {
|
|
2935
3507
|
for (const action of actions) {
|
|
2936
3508
|
const dir = dirname3(action.path);
|
|
2937
|
-
|
|
3509
|
+
mkdirSync7(dir, { recursive: true });
|
|
2938
3510
|
switch (action.action) {
|
|
2939
3511
|
case "create":
|
|
2940
|
-
if (
|
|
3512
|
+
if (existsSync9(action.path)) {
|
|
2941
3513
|
throw new Error(`File already exists: ${action.path}`);
|
|
2942
3514
|
}
|
|
2943
|
-
|
|
3515
|
+
writeFileSync7(action.path, action.content, "utf-8");
|
|
2944
3516
|
break;
|
|
2945
3517
|
case "overwrite":
|
|
2946
|
-
|
|
3518
|
+
writeFileSync7(action.path, action.content, "utf-8");
|
|
2947
3519
|
break;
|
|
2948
3520
|
case "append":
|
|
2949
3521
|
appendFileSync(action.path, action.content, "utf-8");
|
|
@@ -2988,7 +3560,7 @@ var init_display = __esm({
|
|
|
2988
3560
|
|
|
2989
3561
|
// src/cli/ui/api-routes.ts
|
|
2990
3562
|
import { resolve as resolve3 } from "path";
|
|
2991
|
-
import { existsSync as
|
|
3563
|
+
import { existsSync as existsSync11, writeFileSync as writeFileSync8 } from "fs";
|
|
2992
3564
|
function getRoutes() {
|
|
2993
3565
|
return [
|
|
2994
3566
|
{
|
|
@@ -3003,7 +3575,7 @@ function getRoutes() {
|
|
|
3003
3575
|
const { path: projectPath } = body;
|
|
3004
3576
|
if (!projectPath) throw new Error("Missing required field: path");
|
|
3005
3577
|
const resolved = resolve3(projectPath);
|
|
3006
|
-
if (!
|
|
3578
|
+
if (!existsSync11(resolved)) throw new Error(`Directory not found: ${resolved}`);
|
|
3007
3579
|
return detectStack(resolved);
|
|
3008
3580
|
}
|
|
3009
3581
|
},
|
|
@@ -3061,7 +3633,7 @@ function getRoutes() {
|
|
|
3061
3633
|
if (!name) throw new Error("Missing required field: name");
|
|
3062
3634
|
if (!config) throw new Error("Missing required field: config");
|
|
3063
3635
|
if (!project) throw new Error("Missing required field: project");
|
|
3064
|
-
if (
|
|
3636
|
+
if (existsSync11(name) && existsSync11(resolve3(name, ".beastmode"))) {
|
|
3065
3637
|
throw new Error(`Factory already exists at ./${name}. Use 'beastmode config' to modify.`);
|
|
3066
3638
|
}
|
|
3067
3639
|
const resolvedConfig = resolveDefaults(config, project.stack);
|
|
@@ -3078,7 +3650,7 @@ function getRoutes() {
|
|
|
3078
3650
|
if (secretLines.length > 0) {
|
|
3079
3651
|
const secretsPath = resolve3(name, ".beastmode", "secrets.env.local");
|
|
3080
3652
|
const secretsContent = "# BeastMode secrets \u2014 DO NOT COMMIT\n" + secretLines.join("\n") + "\n";
|
|
3081
|
-
|
|
3653
|
+
writeFileSync8(secretsPath, secretsContent, "utf-8");
|
|
3082
3654
|
}
|
|
3083
3655
|
}
|
|
3084
3656
|
const fileList = actions.filter((a) => !a.path.endsWith(".gitkeep")).map((a) => a.path.replace(`${name}/`, ""));
|
|
@@ -3118,52 +3690,52 @@ var init_api_routes = __esm({
|
|
|
3118
3690
|
});
|
|
3119
3691
|
|
|
3120
3692
|
// src/cli/ui/archival.ts
|
|
3121
|
-
import { existsSync as
|
|
3122
|
-
import { join as
|
|
3693
|
+
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";
|
|
3694
|
+
import { join as join10 } from "path";
|
|
3123
3695
|
function archiveOldRuns(runsDir, archiveAfterDays) {
|
|
3124
|
-
if (archiveAfterDays <= 0 || !
|
|
3125
|
-
const archiveDir =
|
|
3696
|
+
if (archiveAfterDays <= 0 || !existsSync12(runsDir)) return { archived: 0 };
|
|
3697
|
+
const archiveDir = join10(runsDir, ".archive");
|
|
3126
3698
|
const cutoff = Date.now() - archiveAfterDays * 24 * 60 * 60 * 1e3;
|
|
3127
3699
|
let archived = 0;
|
|
3128
|
-
for (const entry of
|
|
3700
|
+
for (const entry of readdirSync4(runsDir)) {
|
|
3129
3701
|
if (entry.startsWith(".") || !entry.startsWith("run-")) continue;
|
|
3130
|
-
const runDir =
|
|
3131
|
-
if (
|
|
3132
|
-
const cpPath =
|
|
3133
|
-
if (!
|
|
3702
|
+
const runDir = join10(runsDir, entry);
|
|
3703
|
+
if (existsSync12(join10(runDir, "pinned"))) continue;
|
|
3704
|
+
const cpPath = join10(runDir, "checkpoint.json");
|
|
3705
|
+
if (!existsSync12(cpPath)) continue;
|
|
3134
3706
|
try {
|
|
3135
|
-
const cp = JSON.parse(
|
|
3707
|
+
const cp = JSON.parse(readFileSync9(cpPath, "utf-8"));
|
|
3136
3708
|
const stage = (cp.current_stage || "").toLowerCase();
|
|
3137
3709
|
if (stage !== "done" && stage !== "ship") continue;
|
|
3138
3710
|
} catch {
|
|
3139
3711
|
continue;
|
|
3140
3712
|
}
|
|
3141
3713
|
try {
|
|
3142
|
-
const mtime =
|
|
3714
|
+
const mtime = statSync3(cpPath).mtimeMs;
|
|
3143
3715
|
if (mtime > cutoff) continue;
|
|
3144
3716
|
} catch {
|
|
3145
3717
|
continue;
|
|
3146
3718
|
}
|
|
3147
|
-
|
|
3148
|
-
|
|
3719
|
+
mkdirSync8(archiveDir, { recursive: true });
|
|
3720
|
+
renameSync2(runDir, join10(archiveDir, entry));
|
|
3149
3721
|
archived++;
|
|
3150
3722
|
}
|
|
3151
3723
|
return { archived };
|
|
3152
3724
|
}
|
|
3153
3725
|
function pinRun(runsDir, runId) {
|
|
3154
|
-
const runDir =
|
|
3155
|
-
if (!
|
|
3156
|
-
|
|
3726
|
+
const runDir = join10(runsDir, runId);
|
|
3727
|
+
if (!existsSync12(runDir)) return false;
|
|
3728
|
+
writeFileSync9(join10(runDir, "pinned"), (/* @__PURE__ */ new Date()).toISOString());
|
|
3157
3729
|
return true;
|
|
3158
3730
|
}
|
|
3159
3731
|
function unpinRun(runsDir, runId) {
|
|
3160
|
-
const pinFile =
|
|
3161
|
-
if (!
|
|
3732
|
+
const pinFile = join10(runsDir, runId, "pinned");
|
|
3733
|
+
if (!existsSync12(pinFile)) return false;
|
|
3162
3734
|
unlinkSync(pinFile);
|
|
3163
3735
|
return true;
|
|
3164
3736
|
}
|
|
3165
3737
|
function isRunPinned(runsDir, runId) {
|
|
3166
|
-
return
|
|
3738
|
+
return existsSync12(join10(runsDir, runId, "pinned"));
|
|
3167
3739
|
}
|
|
3168
3740
|
var init_archival = __esm({
|
|
3169
3741
|
"src/cli/ui/archival.ts"() {
|
|
@@ -3188,37 +3760,37 @@ __export(inception_exports, {
|
|
|
3188
3760
|
saveArtifact: () => saveArtifact,
|
|
3189
3761
|
saveInceptionState: () => saveInceptionState
|
|
3190
3762
|
});
|
|
3191
|
-
import { existsSync as
|
|
3192
|
-
import { join as
|
|
3763
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, readFileSync as readFileSync10, readdirSync as readdirSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
3764
|
+
import { join as join11, dirname as dirname4 } from "path";
|
|
3193
3765
|
import { fileURLToPath } from "url";
|
|
3194
3766
|
function getMethodologiesDir() {
|
|
3195
3767
|
const candidates = [
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3768
|
+
join11(dirname4(fileURLToPath(import.meta.url)), "methodologies"),
|
|
3769
|
+
join11(dirname4(fileURLToPath(import.meta.url)), "..", "methodologies"),
|
|
3770
|
+
join11(process.cwd(), "src", "cli", "ui", "methodologies"),
|
|
3771
|
+
join11(process.cwd(), "dist", "methodologies"),
|
|
3772
|
+
join11(process.cwd(), "cli", "src", "cli", "ui", "methodologies"),
|
|
3773
|
+
join11(process.cwd(), "cli", "dist", "methodologies")
|
|
3202
3774
|
];
|
|
3203
3775
|
for (const dir of candidates) {
|
|
3204
|
-
if (
|
|
3776
|
+
if (existsSync13(dir)) return dir;
|
|
3205
3777
|
}
|
|
3206
3778
|
return candidates[0];
|
|
3207
3779
|
}
|
|
3208
3780
|
function getMethodology(id) {
|
|
3209
3781
|
const dir = getMethodologiesDir();
|
|
3210
|
-
const filePath =
|
|
3211
|
-
if (!
|
|
3782
|
+
const filePath = join11(dir, `${id.replace(/_/g, "-")}.json`);
|
|
3783
|
+
if (!existsSync13(filePath)) {
|
|
3212
3784
|
throw new Error(`Methodology not found: ${id}`);
|
|
3213
3785
|
}
|
|
3214
|
-
return JSON.parse(
|
|
3786
|
+
return JSON.parse(readFileSync10(filePath, "utf-8"));
|
|
3215
3787
|
}
|
|
3216
3788
|
function listMethodologies() {
|
|
3217
3789
|
const dir = getMethodologiesDir();
|
|
3218
|
-
if (!
|
|
3219
|
-
return
|
|
3790
|
+
if (!existsSync13(dir)) return [];
|
|
3791
|
+
return readdirSync5(dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
3220
3792
|
try {
|
|
3221
|
-
const m = JSON.parse(
|
|
3793
|
+
const m = JSON.parse(readFileSync10(join11(dir, f), "utf-8"));
|
|
3222
3794
|
return { id: m.id, name: m.name, description: m.description };
|
|
3223
3795
|
} catch {
|
|
3224
3796
|
return null;
|
|
@@ -3226,12 +3798,12 @@ function listMethodologies() {
|
|
|
3226
3798
|
}).filter(Boolean);
|
|
3227
3799
|
}
|
|
3228
3800
|
function getProductDir(factoryDir, productName) {
|
|
3229
|
-
return
|
|
3801
|
+
return join11(factoryDir, ".beastmode", "products", productName);
|
|
3230
3802
|
}
|
|
3231
3803
|
function createInceptionState(factoryDir, opts) {
|
|
3232
3804
|
const methodology = getMethodology(opts.methodology);
|
|
3233
3805
|
const productDir = getProductDir(factoryDir, opts.productName);
|
|
3234
|
-
|
|
3806
|
+
mkdirSync9(productDir, { recursive: true });
|
|
3235
3807
|
const state = {
|
|
3236
3808
|
productName: opts.productName,
|
|
3237
3809
|
idea: opts.idea,
|
|
@@ -3248,21 +3820,21 @@ function createInceptionState(factoryDir, opts) {
|
|
|
3248
3820
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3249
3821
|
projectName: null
|
|
3250
3822
|
};
|
|
3251
|
-
|
|
3823
|
+
writeFileSync10(join11(productDir, "inception.json"), JSON.stringify(state, null, 2) + "\n");
|
|
3252
3824
|
return state;
|
|
3253
3825
|
}
|
|
3254
3826
|
function loadInception(factoryDir, productName) {
|
|
3255
|
-
const filePath =
|
|
3256
|
-
if (!
|
|
3827
|
+
const filePath = join11(getProductDir(factoryDir, productName), "inception.json");
|
|
3828
|
+
if (!existsSync13(filePath)) return null;
|
|
3257
3829
|
try {
|
|
3258
|
-
return JSON.parse(
|
|
3830
|
+
return JSON.parse(readFileSync10(filePath, "utf-8"));
|
|
3259
3831
|
} catch {
|
|
3260
3832
|
return null;
|
|
3261
3833
|
}
|
|
3262
3834
|
}
|
|
3263
3835
|
function saveInceptionState(factoryDir, state) {
|
|
3264
3836
|
const productDir = getProductDir(factoryDir, state.productName);
|
|
3265
|
-
|
|
3837
|
+
writeFileSync10(join11(productDir, "inception.json"), JSON.stringify(state, null, 2) + "\n");
|
|
3266
3838
|
}
|
|
3267
3839
|
function advancePhase(factoryDir, productName) {
|
|
3268
3840
|
const state = loadInception(factoryDir, productName);
|
|
@@ -3282,28 +3854,28 @@ function advancePhase(factoryDir, productName) {
|
|
|
3282
3854
|
}
|
|
3283
3855
|
function saveArtifact(factoryDir, productName, filename, content) {
|
|
3284
3856
|
const productDir = getProductDir(factoryDir, productName);
|
|
3285
|
-
|
|
3286
|
-
const filePath =
|
|
3287
|
-
|
|
3288
|
-
|
|
3857
|
+
mkdirSync9(productDir, { recursive: true });
|
|
3858
|
+
const filePath = join11(productDir, filename);
|
|
3859
|
+
mkdirSync9(dirname4(filePath), { recursive: true });
|
|
3860
|
+
writeFileSync10(filePath, content);
|
|
3289
3861
|
}
|
|
3290
3862
|
function loadArtifact(factoryDir, productName, filename) {
|
|
3291
|
-
const filePath =
|
|
3292
|
-
if (!
|
|
3293
|
-
return
|
|
3863
|
+
const filePath = join11(getProductDir(factoryDir, productName), filename);
|
|
3864
|
+
if (!existsSync13(filePath)) return null;
|
|
3865
|
+
return readFileSync10(filePath, "utf-8");
|
|
3294
3866
|
}
|
|
3295
3867
|
function migrateInceptionToSession(factoryDir, productName) {
|
|
3296
|
-
const productDir =
|
|
3297
|
-
const oldInception =
|
|
3298
|
-
if (!
|
|
3299
|
-
const sessionsDir =
|
|
3300
|
-
if (
|
|
3868
|
+
const productDir = join11(factoryDir, ".beastmode", "products", productName);
|
|
3869
|
+
const oldInception = join11(productDir, "inception.json");
|
|
3870
|
+
if (!existsSync13(oldInception)) return false;
|
|
3871
|
+
const sessionsDir = join11(productDir, "sessions");
|
|
3872
|
+
if (existsSync13(sessionsDir) && readdirSync5(sessionsDir).some((d) => d.startsWith("session-"))) {
|
|
3301
3873
|
return false;
|
|
3302
3874
|
}
|
|
3303
|
-
const sessionDir =
|
|
3304
|
-
|
|
3305
|
-
const content =
|
|
3306
|
-
|
|
3875
|
+
const sessionDir = join11(sessionsDir, "session-001");
|
|
3876
|
+
mkdirSync9(sessionDir, { recursive: true });
|
|
3877
|
+
const content = readFileSync10(oldInception, "utf-8");
|
|
3878
|
+
writeFileSync10(join11(sessionDir, "inception.json"), content);
|
|
3307
3879
|
const artifactFiles = [
|
|
3308
3880
|
"prd.md",
|
|
3309
3881
|
"architecture.md",
|
|
@@ -3316,9 +3888,9 @@ function migrateInceptionToSession(factoryDir, productName) {
|
|
|
3316
3888
|
"project-plan.md"
|
|
3317
3889
|
];
|
|
3318
3890
|
for (const file of artifactFiles) {
|
|
3319
|
-
const src =
|
|
3320
|
-
if (
|
|
3321
|
-
|
|
3891
|
+
const src = join11(productDir, file);
|
|
3892
|
+
if (existsSync13(src)) {
|
|
3893
|
+
writeFileSync10(join11(sessionDir, file), readFileSync10(src, "utf-8"));
|
|
3322
3894
|
unlinkSync2(src);
|
|
3323
3895
|
}
|
|
3324
3896
|
}
|
|
@@ -3326,11 +3898,11 @@ function migrateInceptionToSession(factoryDir, productName) {
|
|
|
3326
3898
|
return true;
|
|
3327
3899
|
}
|
|
3328
3900
|
function listProducts(factoryDir) {
|
|
3329
|
-
const productsDir =
|
|
3330
|
-
if (!
|
|
3331
|
-
return
|
|
3901
|
+
const productsDir = join11(factoryDir, ".beastmode", "products");
|
|
3902
|
+
if (!existsSync13(productsDir)) return [];
|
|
3903
|
+
return readdirSync5(productsDir).filter((d) => existsSync13(join11(productsDir, d, "inception.json"))).map((d) => {
|
|
3332
3904
|
try {
|
|
3333
|
-
const state = JSON.parse(
|
|
3905
|
+
const state = JSON.parse(readFileSync10(join11(productsDir, d, "inception.json"), "utf-8"));
|
|
3334
3906
|
return {
|
|
3335
3907
|
name: state.productName || d,
|
|
3336
3908
|
methodology: state.methodology || "unknown",
|
|
@@ -3453,18 +4025,18 @@ __export(strategy_exports, {
|
|
|
3453
4025
|
saveSessionArtifact: () => saveSessionArtifact,
|
|
3454
4026
|
saveStrategySession: () => saveStrategySession
|
|
3455
4027
|
});
|
|
3456
|
-
import { existsSync as
|
|
3457
|
-
import { join as
|
|
4028
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync10, writeFileSync as writeFileSync11, readFileSync as readFileSync11, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
4029
|
+
import { join as join12 } from "path";
|
|
3458
4030
|
function getProductDir2(factoryDir, projectName) {
|
|
3459
|
-
return
|
|
4031
|
+
return join12(factoryDir, ".beastmode", "products", projectName);
|
|
3460
4032
|
}
|
|
3461
4033
|
function getSessionsDir(factoryDir, projectName) {
|
|
3462
|
-
return
|
|
4034
|
+
return join12(getProductDir2(factoryDir, projectName), "sessions");
|
|
3463
4035
|
}
|
|
3464
4036
|
function nextSessionId(factoryDir, projectName) {
|
|
3465
4037
|
const sessionsDir = getSessionsDir(factoryDir, projectName);
|
|
3466
|
-
if (!
|
|
3467
|
-
const existing =
|
|
4038
|
+
if (!existsSync14(sessionsDir)) return "session-001";
|
|
4039
|
+
const existing = readdirSync6(sessionsDir).filter((d) => d.startsWith("session-")).sort();
|
|
3468
4040
|
if (existing.length === 0) return "session-001";
|
|
3469
4041
|
const lastNum = parseInt(existing[existing.length - 1].replace("session-", ""), 10);
|
|
3470
4042
|
return `session-${String(lastNum + 1).padStart(3, "0")}`;
|
|
@@ -3472,8 +4044,8 @@ function nextSessionId(factoryDir, projectName) {
|
|
|
3472
4044
|
function createStrategySession(factoryDir, projectName, opts) {
|
|
3473
4045
|
const methodology = getMethodology(opts.methodology);
|
|
3474
4046
|
const sessionId = nextSessionId(factoryDir, projectName);
|
|
3475
|
-
const sessionDir =
|
|
3476
|
-
|
|
4047
|
+
const sessionDir = join12(getSessionsDir(factoryDir, projectName), sessionId);
|
|
4048
|
+
mkdirSync10(sessionDir, { recursive: true });
|
|
3477
4049
|
const session = {
|
|
3478
4050
|
sessionId,
|
|
3479
4051
|
name: opts.name,
|
|
@@ -3494,100 +4066,100 @@ function createStrategySession(factoryDir, projectName, opts) {
|
|
|
3494
4066
|
sessionType: opts.sessionType || "free-form",
|
|
3495
4067
|
approach: opts.approach || "auto"
|
|
3496
4068
|
};
|
|
3497
|
-
|
|
4069
|
+
writeFileSync11(join12(sessionDir, "inception.json"), JSON.stringify(session, null, 2) + "\n");
|
|
3498
4070
|
return session;
|
|
3499
4071
|
}
|
|
3500
4072
|
function listStrategySessions(factoryDir, projectName) {
|
|
3501
4073
|
const sessionsDir = getSessionsDir(factoryDir, projectName);
|
|
3502
|
-
if (!
|
|
3503
|
-
return
|
|
3504
|
-
const file =
|
|
3505
|
-
if (!
|
|
4074
|
+
if (!existsSync14(sessionsDir)) return [];
|
|
4075
|
+
return readdirSync6(sessionsDir).filter((d) => d.startsWith("session-")).sort().map((d) => {
|
|
4076
|
+
const file = join12(sessionsDir, d, "inception.json");
|
|
4077
|
+
if (!existsSync14(file)) return null;
|
|
3506
4078
|
try {
|
|
3507
|
-
return JSON.parse(
|
|
4079
|
+
return JSON.parse(readFileSync11(file, "utf-8"));
|
|
3508
4080
|
} catch {
|
|
3509
4081
|
return null;
|
|
3510
4082
|
}
|
|
3511
4083
|
}).filter(Boolean);
|
|
3512
4084
|
}
|
|
3513
4085
|
function loadStrategySession(factoryDir, projectName, sessionId) {
|
|
3514
|
-
const file =
|
|
3515
|
-
if (!
|
|
4086
|
+
const file = join12(getSessionsDir(factoryDir, projectName), sessionId, "inception.json");
|
|
4087
|
+
if (!existsSync14(file)) return null;
|
|
3516
4088
|
try {
|
|
3517
|
-
return JSON.parse(
|
|
4089
|
+
return JSON.parse(readFileSync11(file, "utf-8"));
|
|
3518
4090
|
} catch {
|
|
3519
4091
|
return null;
|
|
3520
4092
|
}
|
|
3521
4093
|
}
|
|
3522
4094
|
function saveStrategySession(factoryDir, projectName, sessionId, session) {
|
|
3523
|
-
const dir =
|
|
3524
|
-
|
|
4095
|
+
const dir = join12(getSessionsDir(factoryDir, projectName), sessionId);
|
|
4096
|
+
mkdirSync10(dir, { recursive: true });
|
|
3525
4097
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3526
|
-
|
|
4098
|
+
writeFileSync11(join12(dir, "inception.json"), JSON.stringify(session, null, 2) + "\n");
|
|
3527
4099
|
}
|
|
3528
4100
|
function saveSessionArtifact(factoryDir, projectName, sessionId, filename, content) {
|
|
3529
|
-
const dir =
|
|
3530
|
-
|
|
3531
|
-
|
|
4101
|
+
const dir = join12(getSessionsDir(factoryDir, projectName), sessionId);
|
|
4102
|
+
mkdirSync10(dir, { recursive: true });
|
|
4103
|
+
writeFileSync11(join12(dir, filename), content);
|
|
3532
4104
|
}
|
|
3533
4105
|
function loadSessionArtifact(factoryDir, projectName, sessionId, filename) {
|
|
3534
|
-
const file =
|
|
3535
|
-
if (!
|
|
3536
|
-
return
|
|
4106
|
+
const file = join12(getSessionsDir(factoryDir, projectName), sessionId, filename);
|
|
4107
|
+
if (!existsSync14(file)) return null;
|
|
4108
|
+
return readFileSync11(file, "utf-8");
|
|
3537
4109
|
}
|
|
3538
4110
|
function buildArtifactIndex(factoryDir, projectName) {
|
|
3539
4111
|
const artifacts = [];
|
|
3540
|
-
const brownfieldPath =
|
|
3541
|
-
if (
|
|
3542
|
-
const content =
|
|
4112
|
+
const brownfieldPath = join12(factoryDir, ".beastmode", "projects", projectName, "brownfield.md");
|
|
4113
|
+
if (existsSync14(brownfieldPath)) {
|
|
4114
|
+
const content = readFileSync11(brownfieldPath, "utf-8");
|
|
3543
4115
|
const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#")) || "";
|
|
3544
4116
|
artifacts.push({
|
|
3545
4117
|
type: "brownfield",
|
|
3546
4118
|
path: brownfieldPath,
|
|
3547
4119
|
summary: firstLine.slice(0, 200),
|
|
3548
|
-
date:
|
|
4120
|
+
date: statSync4(brownfieldPath).mtime.toISOString()
|
|
3549
4121
|
});
|
|
3550
4122
|
}
|
|
3551
4123
|
const sessions = listStrategySessions(factoryDir, projectName);
|
|
3552
4124
|
for (const session of sessions) {
|
|
3553
4125
|
artifacts.push({
|
|
3554
4126
|
type: "strategy_session",
|
|
3555
|
-
path:
|
|
4127
|
+
path: join12(getSessionsDir(factoryDir, projectName), session.sessionId),
|
|
3556
4128
|
summary: `${session.name} (${session.methodology}) \u2014 ${session.status}`,
|
|
3557
4129
|
date: session.createdAt,
|
|
3558
4130
|
sessionId: session.sessionId
|
|
3559
4131
|
});
|
|
3560
4132
|
}
|
|
3561
|
-
const runsDir =
|
|
3562
|
-
if (
|
|
3563
|
-
const runDirs =
|
|
4133
|
+
const runsDir = join12(factoryDir, "runs", projectName);
|
|
4134
|
+
if (existsSync14(runsDir)) {
|
|
4135
|
+
const runDirs = readdirSync6(runsDir).filter((d) => d.startsWith("run-")).sort().reverse().slice(0, 10);
|
|
3564
4136
|
for (const runId of runDirs) {
|
|
3565
|
-
const nlspecPath =
|
|
3566
|
-
if (
|
|
3567
|
-
const content =
|
|
4137
|
+
const nlspecPath = join12(runsDir, runId, "nlspec.md");
|
|
4138
|
+
if (existsSync14(nlspecPath)) {
|
|
4139
|
+
const content = readFileSync11(nlspecPath, "utf-8");
|
|
3568
4140
|
const title = content.split("\n").find((l) => l.startsWith("# ")) || runId;
|
|
3569
4141
|
artifacts.push({
|
|
3570
4142
|
type: "nlspec",
|
|
3571
4143
|
path: nlspecPath,
|
|
3572
4144
|
summary: title.replace("# ", "").slice(0, 200),
|
|
3573
|
-
date:
|
|
4145
|
+
date: statSync4(nlspecPath).mtime.toISOString(),
|
|
3574
4146
|
runId
|
|
3575
4147
|
});
|
|
3576
4148
|
}
|
|
3577
4149
|
}
|
|
3578
4150
|
}
|
|
3579
|
-
const learningsDir =
|
|
3580
|
-
if (
|
|
3581
|
-
const learningFiles =
|
|
4151
|
+
const learningsDir = join12(getProductDir2(factoryDir, projectName), "learnings");
|
|
4152
|
+
if (existsSync14(learningsDir)) {
|
|
4153
|
+
const learningFiles = readdirSync6(learningsDir).filter((f) => f.endsWith(".md") && !f.startsWith("."));
|
|
3582
4154
|
for (const file of learningFiles) {
|
|
3583
|
-
const filePath =
|
|
3584
|
-
const content =
|
|
4155
|
+
const filePath = join12(learningsDir, file);
|
|
4156
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
3585
4157
|
const firstHeading = content.split("\n").find((l) => l.startsWith("## ")) || file;
|
|
3586
4158
|
artifacts.push({
|
|
3587
4159
|
type: "learning",
|
|
3588
4160
|
path: filePath,
|
|
3589
4161
|
summary: firstHeading.replace("## ", "").slice(0, 200),
|
|
3590
|
-
date:
|
|
4162
|
+
date: statSync4(filePath).mtime.toISOString()
|
|
3591
4163
|
});
|
|
3592
4164
|
}
|
|
3593
4165
|
}
|
|
@@ -3612,8 +4184,8 @@ var init_strategy = __esm({
|
|
|
3612
4184
|
// src/cli/ui/chat-handler.ts
|
|
3613
4185
|
import { randomUUID } from "crypto";
|
|
3614
4186
|
import { execSync as execSync2 } from "child_process";
|
|
3615
|
-
import { readFileSync as
|
|
3616
|
-
import { join as
|
|
4187
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync12, existsSync as existsSync15, mkdirSync as mkdirSync11, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
|
|
4188
|
+
import { join as join13 } from "path";
|
|
3617
4189
|
import http from "http";
|
|
3618
4190
|
import { WebSocketServer, WebSocket as WsWebSocket } from "ws";
|
|
3619
4191
|
function getRecentTokenUsage() {
|
|
@@ -3723,19 +4295,19 @@ function getToolDefinitions() {
|
|
|
3723
4295
|
];
|
|
3724
4296
|
}
|
|
3725
4297
|
function readJsonSafe(filePath) {
|
|
3726
|
-
if (!
|
|
4298
|
+
if (!existsSync15(filePath)) return null;
|
|
3727
4299
|
try {
|
|
3728
|
-
return JSON.parse(
|
|
4300
|
+
return JSON.parse(readFileSync12(filePath, "utf-8"));
|
|
3729
4301
|
} catch {
|
|
3730
4302
|
return null;
|
|
3731
4303
|
}
|
|
3732
4304
|
}
|
|
3733
4305
|
function getBoardUrl(factoryPath) {
|
|
3734
4306
|
if (process.env.BEASTMODE_BOARD_URL) return process.env.BEASTMODE_BOARD_URL;
|
|
3735
|
-
const configPath =
|
|
3736
|
-
if (
|
|
4307
|
+
const configPath = join13(factoryPath, ".beastmode", "config.json");
|
|
4308
|
+
if (existsSync15(configPath)) {
|
|
3737
4309
|
try {
|
|
3738
|
-
const config = JSON.parse(
|
|
4310
|
+
const config = JSON.parse(readFileSync12(configPath, "utf-8"));
|
|
3739
4311
|
if (config.task_backend?.config?.url) return config.task_backend.config.url;
|
|
3740
4312
|
} catch {
|
|
3741
4313
|
}
|
|
@@ -3746,19 +4318,19 @@ async function executeTool(toolName, toolInput, factoryPath) {
|
|
|
3746
4318
|
try {
|
|
3747
4319
|
switch (toolName) {
|
|
3748
4320
|
case "factory_status": {
|
|
3749
|
-
const bmDir =
|
|
3750
|
-
const factory = readJsonSafe(
|
|
3751
|
-
const projectsDir =
|
|
4321
|
+
const bmDir = join13(factoryPath, ".beastmode");
|
|
4322
|
+
const factory = readJsonSafe(join13(bmDir, "factory.json"));
|
|
4323
|
+
const projectsDir = join13(bmDir, "projects");
|
|
3752
4324
|
let projects = [];
|
|
3753
|
-
if (
|
|
3754
|
-
projects =
|
|
4325
|
+
if (existsSync15(projectsDir)) {
|
|
4326
|
+
projects = readdirSync7(projectsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
|
|
3755
4327
|
}
|
|
3756
|
-
const runsDir =
|
|
4328
|
+
const runsDir = join13(factoryPath, "runs");
|
|
3757
4329
|
let runs = [];
|
|
3758
|
-
if (
|
|
3759
|
-
runs =
|
|
4330
|
+
if (existsSync15(runsDir)) {
|
|
4331
|
+
runs = readdirSync7(runsDir).filter((d) => {
|
|
3760
4332
|
try {
|
|
3761
|
-
return
|
|
4333
|
+
return statSync5(join13(runsDir, d)).isDirectory();
|
|
3762
4334
|
} catch {
|
|
3763
4335
|
return false;
|
|
3764
4336
|
}
|
|
@@ -3766,10 +4338,10 @@ async function executeTool(toolName, toolInput, factoryPath) {
|
|
|
3766
4338
|
}
|
|
3767
4339
|
let daemonStatus = "stopped";
|
|
3768
4340
|
let daemonPid = null;
|
|
3769
|
-
const pidFile =
|
|
3770
|
-
if (
|
|
4341
|
+
const pidFile = join13(bmDir, "daemon.pid");
|
|
4342
|
+
if (existsSync15(pidFile)) {
|
|
3771
4343
|
try {
|
|
3772
|
-
daemonPid = parseInt(
|
|
4344
|
+
daemonPid = parseInt(readFileSync12(pidFile, "utf-8").trim(), 10);
|
|
3773
4345
|
process.kill(daemonPid, 0);
|
|
3774
4346
|
daemonStatus = "running";
|
|
3775
4347
|
} catch {
|
|
@@ -3817,7 +4389,7 @@ async function executeTool(toolName, toolInput, factoryPath) {
|
|
|
3817
4389
|
}, null, 2);
|
|
3818
4390
|
}
|
|
3819
4391
|
case "factory_config": {
|
|
3820
|
-
const configPath =
|
|
4392
|
+
const configPath = join13(factoryPath, ".beastmode", "config.json");
|
|
3821
4393
|
const config = readJsonSafe(configPath);
|
|
3822
4394
|
return config ? JSON.stringify(config, null, 2) : "No config.json found.";
|
|
3823
4395
|
}
|
|
@@ -3872,12 +4444,12 @@ async function executeTool(toolName, toolInput, factoryPath) {
|
|
|
3872
4444
|
}
|
|
3873
4445
|
}
|
|
3874
4446
|
case "list_runs": {
|
|
3875
|
-
const runsDir =
|
|
3876
|
-
if (!
|
|
3877
|
-
const runDirs =
|
|
4447
|
+
const runsDir = join13(factoryPath, "runs");
|
|
4448
|
+
if (!existsSync15(runsDir)) return "No runs directory.";
|
|
4449
|
+
const runDirs = readdirSync7(runsDir).sort().reverse();
|
|
3878
4450
|
const results = [];
|
|
3879
4451
|
for (const id of runDirs.slice(0, 15)) {
|
|
3880
|
-
const cp = readJsonSafe(
|
|
4452
|
+
const cp = readJsonSafe(join13(runsDir, id, "checkpoint.json"));
|
|
3881
4453
|
if (cp) {
|
|
3882
4454
|
const hist = Array.isArray(cp.satisfaction_history) ? cp.satisfaction_history : [];
|
|
3883
4455
|
results.push({
|
|
@@ -3893,36 +4465,36 @@ async function executeTool(toolName, toolInput, factoryPath) {
|
|
|
3893
4465
|
case "run_detail": {
|
|
3894
4466
|
const runId = toolInput.run_id;
|
|
3895
4467
|
if (runId.includes("..")) return "Invalid run_id.";
|
|
3896
|
-
const runDir =
|
|
3897
|
-
if (!
|
|
3898
|
-
const manifest = readJsonSafe(
|
|
3899
|
-
const checkpoint = readJsonSafe(
|
|
3900
|
-
const iterDir =
|
|
4468
|
+
const runDir = join13(factoryPath, "runs", runId);
|
|
4469
|
+
if (!existsSync15(runDir)) return `Run not found: ${runId}`;
|
|
4470
|
+
const manifest = readJsonSafe(join13(runDir, "manifest.json"));
|
|
4471
|
+
const checkpoint = readJsonSafe(join13(runDir, "checkpoint.json"));
|
|
4472
|
+
const iterDir = join13(runDir, "iterations");
|
|
3901
4473
|
const iterations = [];
|
|
3902
|
-
if (
|
|
3903
|
-
for (const d of
|
|
3904
|
-
const sat = readJsonSafe(
|
|
4474
|
+
if (existsSync15(iterDir)) {
|
|
4475
|
+
for (const d of readdirSync7(iterDir).sort()) {
|
|
4476
|
+
const sat = readJsonSafe(join13(iterDir, d, "satisfaction.json"));
|
|
3905
4477
|
iterations.push({ number: parseInt(d, 10), satisfaction: sat });
|
|
3906
4478
|
}
|
|
3907
4479
|
}
|
|
3908
|
-
const files =
|
|
4480
|
+
const files = readdirSync7(runDir);
|
|
3909
4481
|
return JSON.stringify({ id: runId, files, manifest, checkpoint, iterations }, null, 2);
|
|
3910
4482
|
}
|
|
3911
4483
|
case "read_run_file": {
|
|
3912
4484
|
const runId = toolInput.run_id;
|
|
3913
4485
|
const relPath = toolInput.file_path;
|
|
3914
4486
|
if (runId.includes("..") || relPath.includes("..")) return "Invalid path.";
|
|
3915
|
-
const fullPath =
|
|
3916
|
-
if (!
|
|
3917
|
-
const content =
|
|
4487
|
+
const fullPath = join13(factoryPath, "runs", runId, relPath);
|
|
4488
|
+
if (!existsSync15(fullPath)) return `File not found: runs/${runId}/${relPath}`;
|
|
4489
|
+
const content = readFileSync12(fullPath, "utf-8");
|
|
3918
4490
|
return content.length > 1e4 ? content.slice(0, 1e4) + "\n\n... (truncated at 10KB)" : content;
|
|
3919
4491
|
}
|
|
3920
4492
|
case "list_projects": {
|
|
3921
|
-
const projDir =
|
|
3922
|
-
if (!
|
|
3923
|
-
const projects =
|
|
4493
|
+
const projDir = join13(factoryPath, ".beastmode", "projects");
|
|
4494
|
+
if (!existsSync15(projDir)) return "No projects registered.";
|
|
4495
|
+
const projects = readdirSync7(projDir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
3924
4496
|
try {
|
|
3925
|
-
return JSON.parse(
|
|
4497
|
+
return JSON.parse(readFileSync12(join13(projDir, f), "utf-8"));
|
|
3926
4498
|
} catch {
|
|
3927
4499
|
return null;
|
|
3928
4500
|
}
|
|
@@ -3932,25 +4504,25 @@ async function executeTool(toolName, toolInput, factoryPath) {
|
|
|
3932
4504
|
case "read_file": {
|
|
3933
4505
|
const relPath = toolInput.path;
|
|
3934
4506
|
if (relPath.includes("..")) return "Invalid path.";
|
|
3935
|
-
const fullPath =
|
|
4507
|
+
const fullPath = join13(factoryPath, relPath);
|
|
3936
4508
|
if (!fullPath.startsWith(factoryPath)) return "Path traversal not allowed.";
|
|
3937
|
-
if (!
|
|
4509
|
+
if (!existsSync15(fullPath)) return `File not found: ${relPath}`;
|
|
3938
4510
|
try {
|
|
3939
|
-
const entries =
|
|
4511
|
+
const entries = readdirSync7(fullPath);
|
|
3940
4512
|
return `"${relPath}" is a directory with ${entries.length} entries: ${entries.slice(0, 30).join(", ")}`;
|
|
3941
4513
|
} catch {
|
|
3942
4514
|
}
|
|
3943
|
-
const content =
|
|
4515
|
+
const content = readFileSync12(fullPath, "utf-8");
|
|
3944
4516
|
return content.length > 1e4 ? content.slice(0, 1e4) + "\n\n... (truncated at 10KB)" : content;
|
|
3945
4517
|
}
|
|
3946
4518
|
case "list_directory": {
|
|
3947
4519
|
const relPath = toolInput.path || "";
|
|
3948
4520
|
if (relPath.includes("..")) return "Invalid path.";
|
|
3949
|
-
const fullPath =
|
|
4521
|
+
const fullPath = join13(factoryPath, relPath);
|
|
3950
4522
|
if (!fullPath.startsWith(factoryPath)) return "Path traversal not allowed.";
|
|
3951
|
-
if (!
|
|
4523
|
+
if (!existsSync15(fullPath)) return `Directory not found: ${relPath || "/"}`;
|
|
3952
4524
|
try {
|
|
3953
|
-
return
|
|
4525
|
+
return readdirSync7(fullPath).join("\n");
|
|
3954
4526
|
} catch {
|
|
3955
4527
|
return `Not a directory: ${relPath}`;
|
|
3956
4528
|
}
|
|
@@ -3963,8 +4535,8 @@ async function executeTool(toolName, toolInput, factoryPath) {
|
|
|
3963
4535
|
}
|
|
3964
4536
|
}
|
|
3965
4537
|
function getChatHistoryDir(factoryPath) {
|
|
3966
|
-
const dir =
|
|
3967
|
-
if (!
|
|
4538
|
+
const dir = join13(factoryPath, ".beastmode", "chat-history");
|
|
4539
|
+
if (!existsSync15(dir)) mkdirSync11(dir, { recursive: true });
|
|
3968
4540
|
return dir;
|
|
3969
4541
|
}
|
|
3970
4542
|
function saveConversation(factoryPath, sessionId, messages, scope) {
|
|
@@ -3975,13 +4547,13 @@ function saveConversation(factoryPath, sessionId, messages, scope) {
|
|
|
3975
4547
|
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3976
4548
|
messages
|
|
3977
4549
|
};
|
|
3978
|
-
|
|
4550
|
+
writeFileSync12(join13(dir, `${sessionId}.json`), JSON.stringify(data, null, 2));
|
|
3979
4551
|
}
|
|
3980
4552
|
function listConversations(factoryPath) {
|
|
3981
4553
|
const dir = getChatHistoryDir(factoryPath);
|
|
3982
|
-
return
|
|
4554
|
+
return readdirSync7(dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
3983
4555
|
try {
|
|
3984
|
-
const data = JSON.parse(
|
|
4556
|
+
const data = JSON.parse(readFileSync12(join13(dir, f), "utf-8"));
|
|
3985
4557
|
const msgs = data.messages || [];
|
|
3986
4558
|
const firstUser = msgs.find((m) => m.role === "user");
|
|
3987
4559
|
const preview = typeof firstUser?.content === "string" ? firstUser.content.slice(0, 80) : "";
|
|
@@ -3999,10 +4571,10 @@ function listConversations(factoryPath) {
|
|
|
3999
4571
|
}
|
|
4000
4572
|
function loadConversation(factoryPath, sessionId) {
|
|
4001
4573
|
if (sessionId.includes("..")) return [];
|
|
4002
|
-
const file =
|
|
4003
|
-
if (!
|
|
4574
|
+
const file = join13(getChatHistoryDir(factoryPath), `${sessionId}.json`);
|
|
4575
|
+
if (!existsSync15(file)) return [];
|
|
4004
4576
|
try {
|
|
4005
|
-
const data = JSON.parse(
|
|
4577
|
+
const data = JSON.parse(readFileSync12(file, "utf-8"));
|
|
4006
4578
|
return data.messages || [];
|
|
4007
4579
|
} catch {
|
|
4008
4580
|
return [];
|
|
@@ -4010,7 +4582,7 @@ function loadConversation(factoryPath, sessionId) {
|
|
|
4010
4582
|
}
|
|
4011
4583
|
function buildSystemPrompt(factoryPath) {
|
|
4012
4584
|
let factoryName = "BeastMode Factory";
|
|
4013
|
-
const factoryJson = readJsonSafe(
|
|
4585
|
+
const factoryJson = readJsonSafe(join13(factoryPath, ".beastmode", "factory.json"));
|
|
4014
4586
|
if (factoryJson?.factory_name) factoryName = factoryJson.factory_name;
|
|
4015
4587
|
else if (factoryJson?.name) factoryName = factoryJson.name;
|
|
4016
4588
|
return `You are BeastMode Assistant \u2014 a concise, helpful assistant for the "${factoryName}" factory at ${factoryPath}.
|
|
@@ -4557,8 +5129,8 @@ var init_chat_handler = __esm({
|
|
|
4557
5129
|
});
|
|
4558
5130
|
|
|
4559
5131
|
// src/cli/ui/board-api-routes.ts
|
|
4560
|
-
import { readFileSync as
|
|
4561
|
-
import { join as
|
|
5132
|
+
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";
|
|
5133
|
+
import { join as join14, basename as basename4, resolve as resolve4, dirname as dirname5 } from "path";
|
|
4562
5134
|
import { homedir } from "os";
|
|
4563
5135
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
4564
5136
|
import { execSync as execSync3, spawnSync } from "child_process";
|
|
@@ -4570,10 +5142,10 @@ function assertSafeName(name) {
|
|
|
4570
5142
|
}
|
|
4571
5143
|
function getBoardUrl2(factoryDir) {
|
|
4572
5144
|
if (process.env.BEASTMODE_BOARD_URL) return process.env.BEASTMODE_BOARD_URL;
|
|
4573
|
-
const configPath =
|
|
4574
|
-
if (
|
|
5145
|
+
const configPath = join14(factoryDir, ".beastmode", "config.json");
|
|
5146
|
+
if (existsSync16(configPath)) {
|
|
4575
5147
|
try {
|
|
4576
|
-
const config = JSON.parse(
|
|
5148
|
+
const config = JSON.parse(readFileSync13(configPath, "utf-8"));
|
|
4577
5149
|
if (config.task_backend?.config?.url) {
|
|
4578
5150
|
return config.task_backend.config.url;
|
|
4579
5151
|
}
|
|
@@ -4712,9 +5284,9 @@ function scopedQuery(query) {
|
|
|
4712
5284
|
return { board: b };
|
|
4713
5285
|
}
|
|
4714
5286
|
function readJsonFile(filePath) {
|
|
4715
|
-
if (!
|
|
5287
|
+
if (!existsSync16(filePath)) return null;
|
|
4716
5288
|
try {
|
|
4717
|
-
return JSON.parse(
|
|
5289
|
+
return JSON.parse(readFileSync13(filePath, "utf-8"));
|
|
4718
5290
|
} catch {
|
|
4719
5291
|
return null;
|
|
4720
5292
|
}
|
|
@@ -4734,25 +5306,25 @@ function deepMerge2(target, source) {
|
|
|
4734
5306
|
return result;
|
|
4735
5307
|
}
|
|
4736
5308
|
function getRunsDir(factoryDir) {
|
|
4737
|
-
const configPath =
|
|
4738
|
-
if (
|
|
5309
|
+
const configPath = join14(factoryDir, ".beastmode", "config.json");
|
|
5310
|
+
if (existsSync16(configPath)) {
|
|
4739
5311
|
try {
|
|
4740
|
-
const config = JSON.parse(
|
|
5312
|
+
const config = JSON.parse(readFileSync13(configPath, "utf-8"));
|
|
4741
5313
|
if (config.runs_path) return config.runs_path;
|
|
4742
5314
|
} catch {
|
|
4743
5315
|
}
|
|
4744
5316
|
}
|
|
4745
|
-
return
|
|
5317
|
+
return join14(factoryDir, "runs");
|
|
4746
5318
|
}
|
|
4747
5319
|
function scanStrandedRuns(runsDir, newThreshold) {
|
|
4748
|
-
if (!
|
|
5320
|
+
if (!existsSync16(runsDir)) return [];
|
|
4749
5321
|
const stranded = [];
|
|
4750
5322
|
const walkRun = (runPath, projectId) => {
|
|
4751
|
-
const ckptPath =
|
|
4752
|
-
if (!
|
|
5323
|
+
const ckptPath = join14(runPath, "checkpoint.json");
|
|
5324
|
+
if (!existsSync16(ckptPath)) return;
|
|
4753
5325
|
let ckpt;
|
|
4754
5326
|
try {
|
|
4755
|
-
ckpt = JSON.parse(
|
|
5327
|
+
ckpt = JSON.parse(readFileSync13(ckptPath, "utf-8"));
|
|
4756
5328
|
} catch {
|
|
4757
5329
|
return;
|
|
4758
5330
|
}
|
|
@@ -4770,12 +5342,12 @@ function scanStrandedRuns(runsDir, newThreshold) {
|
|
|
4770
5342
|
});
|
|
4771
5343
|
};
|
|
4772
5344
|
try {
|
|
4773
|
-
const entries =
|
|
5345
|
+
const entries = readdirSync8(runsDir);
|
|
4774
5346
|
for (const entry of entries) {
|
|
4775
|
-
const entryPath =
|
|
5347
|
+
const entryPath = join14(runsDir, entry);
|
|
4776
5348
|
let stat;
|
|
4777
5349
|
try {
|
|
4778
|
-
stat =
|
|
5350
|
+
stat = statSync6(entryPath);
|
|
4779
5351
|
} catch {
|
|
4780
5352
|
continue;
|
|
4781
5353
|
}
|
|
@@ -4785,15 +5357,15 @@ function scanStrandedRuns(runsDir, newThreshold) {
|
|
|
4785
5357
|
} else if (!entry.startsWith(".")) {
|
|
4786
5358
|
let subEntries;
|
|
4787
5359
|
try {
|
|
4788
|
-
subEntries =
|
|
5360
|
+
subEntries = readdirSync8(entryPath);
|
|
4789
5361
|
} catch {
|
|
4790
5362
|
continue;
|
|
4791
5363
|
}
|
|
4792
5364
|
for (const sub of subEntries) {
|
|
4793
5365
|
if (!sub.startsWith("run-")) continue;
|
|
4794
|
-
const subPath =
|
|
5366
|
+
const subPath = join14(entryPath, sub);
|
|
4795
5367
|
try {
|
|
4796
|
-
if (
|
|
5368
|
+
if (statSync6(subPath).isDirectory()) {
|
|
4797
5369
|
walkRun(subPath, entry);
|
|
4798
5370
|
}
|
|
4799
5371
|
} catch {
|
|
@@ -4808,10 +5380,10 @@ function scanStrandedRuns(runsDir, newThreshold) {
|
|
|
4808
5380
|
return stranded;
|
|
4809
5381
|
}
|
|
4810
5382
|
function _readAnalyzeMeta(projectsDir, name) {
|
|
4811
|
-
const metaPath =
|
|
4812
|
-
if (!
|
|
5383
|
+
const metaPath = join14(projectsDir, name, "codebase-guide.meta.json");
|
|
5384
|
+
if (!existsSync16(metaPath)) return null;
|
|
4813
5385
|
try {
|
|
4814
|
-
return JSON.parse(
|
|
5386
|
+
return JSON.parse(readFileSync13(metaPath, "utf-8"));
|
|
4815
5387
|
} catch {
|
|
4816
5388
|
return null;
|
|
4817
5389
|
}
|
|
@@ -4838,29 +5410,19 @@ function getBoardRoutes(factoryDir) {
|
|
|
4838
5410
|
method: "GET",
|
|
4839
5411
|
pattern: "/api/status",
|
|
4840
5412
|
handler: async () => {
|
|
4841
|
-
const bmDir =
|
|
4842
|
-
const factoryJsonPath =
|
|
4843
|
-
if (!
|
|
5413
|
+
const bmDir = join14(factoryDir, ".beastmode");
|
|
5414
|
+
const factoryJsonPath = join14(bmDir, "factory.json");
|
|
5415
|
+
if (!existsSync16(factoryJsonPath)) {
|
|
4844
5416
|
throw new Error("Not a valid factory: factory.json not found");
|
|
4845
5417
|
}
|
|
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");
|
|
5418
|
+
const factoryIdentity = JSON.parse(readFileSync13(factoryJsonPath, "utf-8"));
|
|
5419
|
+
const projectsDir = join14(bmDir, "projects");
|
|
5420
|
+
const projectCount = listProjectRecords(projectsDir).length;
|
|
5421
|
+
const lockPath = join14(bmDir, "extensions.lock");
|
|
4860
5422
|
let pluginNames = [];
|
|
4861
|
-
if (
|
|
5423
|
+
if (existsSync16(lockPath)) {
|
|
4862
5424
|
try {
|
|
4863
|
-
const lock = JSON.parse(
|
|
5425
|
+
const lock = JSON.parse(readFileSync13(lockPath, "utf-8"));
|
|
4864
5426
|
pluginNames = Object.keys(lock.plugins || {});
|
|
4865
5427
|
} catch {
|
|
4866
5428
|
}
|
|
@@ -4880,24 +5442,24 @@ function getBoardRoutes(factoryDir) {
|
|
|
4880
5442
|
skillCount = listSkills(factoryDir).length;
|
|
4881
5443
|
} catch {
|
|
4882
5444
|
}
|
|
4883
|
-
const runsDir =
|
|
5445
|
+
const runsDir = join14(factoryDir, "runs");
|
|
4884
5446
|
let runDirs = [];
|
|
4885
|
-
if (
|
|
4886
|
-
runDirs =
|
|
5447
|
+
if (existsSync16(runsDir)) {
|
|
5448
|
+
runDirs = readdirSync8(runsDir).filter((d) => {
|
|
4887
5449
|
if (d.startsWith(".")) return false;
|
|
4888
5450
|
try {
|
|
4889
|
-
return
|
|
5451
|
+
return readdirSync8(join14(runsDir, d)).length > 0;
|
|
4890
5452
|
} catch {
|
|
4891
5453
|
return false;
|
|
4892
5454
|
}
|
|
4893
5455
|
}).sort();
|
|
4894
5456
|
}
|
|
4895
|
-
const pidFile =
|
|
5457
|
+
const pidFile = join14(factoryDir, ".beastmode", "daemon.pid");
|
|
4896
5458
|
let daemonPid = null;
|
|
4897
5459
|
let pidAlive = false;
|
|
4898
|
-
if (
|
|
5460
|
+
if (existsSync16(pidFile)) {
|
|
4899
5461
|
try {
|
|
4900
|
-
daemonPid = parseInt(
|
|
5462
|
+
daemonPid = parseInt(readFileSync13(pidFile, "utf-8").trim(), 10);
|
|
4901
5463
|
process.kill(daemonPid, 0);
|
|
4902
5464
|
pidAlive = true;
|
|
4903
5465
|
} catch {
|
|
@@ -4954,10 +5516,10 @@ function getBoardRoutes(factoryDir) {
|
|
|
4954
5516
|
handler: async () => {
|
|
4955
5517
|
const boardUrl = getBoardUrl2(factoryDir);
|
|
4956
5518
|
let ageSecs = null;
|
|
4957
|
-
const heartbeatPath =
|
|
4958
|
-
if (
|
|
5519
|
+
const heartbeatPath = join14(factoryDir, "daemon", "logs", ".heartbeat");
|
|
5520
|
+
if (existsSync16(heartbeatPath)) {
|
|
4959
5521
|
try {
|
|
4960
|
-
const ts = parseInt(
|
|
5522
|
+
const ts = parseInt(readFileSync13(heartbeatPath, "utf-8").trim(), 10);
|
|
4961
5523
|
if (!isNaN(ts)) {
|
|
4962
5524
|
ageSecs = Math.floor(Date.now() / 1e3) - ts;
|
|
4963
5525
|
}
|
|
@@ -4999,11 +5561,11 @@ function getBoardRoutes(factoryDir) {
|
|
|
4999
5561
|
let totalSlots = 3;
|
|
5000
5562
|
let shortPhaseReserve;
|
|
5001
5563
|
let slotsTimestamp;
|
|
5002
|
-
const slotsPath =
|
|
5564
|
+
const slotsPath = join14(factoryDir, "daemon", "logs", ".slots.json");
|
|
5003
5565
|
let usedDaemonSlotsFile = false;
|
|
5004
|
-
if (
|
|
5566
|
+
if (existsSync16(slotsPath)) {
|
|
5005
5567
|
try {
|
|
5006
|
-
const slots = JSON.parse(
|
|
5568
|
+
const slots = JSON.parse(readFileSync13(slotsPath, "utf-8"));
|
|
5007
5569
|
if (typeof slots.busy === "number" && typeof slots.total === "number") {
|
|
5008
5570
|
busySlots = slots.busy;
|
|
5009
5571
|
totalSlots = slots.total;
|
|
@@ -5028,10 +5590,10 @@ function getBoardRoutes(factoryDir) {
|
|
|
5028
5590
|
} catch {
|
|
5029
5591
|
}
|
|
5030
5592
|
for (const name of ["beastmode.docker.json", "beastmode.daemon.json"]) {
|
|
5031
|
-
const configPath =
|
|
5032
|
-
if (!
|
|
5593
|
+
const configPath = join14(factoryDir, "config", name);
|
|
5594
|
+
if (!existsSync16(configPath)) continue;
|
|
5033
5595
|
try {
|
|
5034
|
-
const config = JSON.parse(
|
|
5596
|
+
const config = JSON.parse(readFileSync13(configPath, "utf-8"));
|
|
5035
5597
|
if (typeof config.max_slots === "number") {
|
|
5036
5598
|
totalSlots = config.max_slots;
|
|
5037
5599
|
}
|
|
@@ -5048,11 +5610,11 @@ function getBoardRoutes(factoryDir) {
|
|
|
5048
5610
|
if (shortPhaseReserve === void 0) {
|
|
5049
5611
|
shortPhaseReserve = Math.max(0, Math.min(1, totalSlots - 1));
|
|
5050
5612
|
}
|
|
5051
|
-
const alertsPath =
|
|
5613
|
+
const alertsPath = join14(factoryDir, "daemon", "logs", ".alerts.json");
|
|
5052
5614
|
let alerts = [];
|
|
5053
|
-
if (
|
|
5615
|
+
if (existsSync16(alertsPath)) {
|
|
5054
5616
|
try {
|
|
5055
|
-
const parsed = JSON.parse(
|
|
5617
|
+
const parsed = JSON.parse(readFileSync13(alertsPath, "utf-8"));
|
|
5056
5618
|
if (Array.isArray(parsed)) alerts = parsed;
|
|
5057
5619
|
} catch {
|
|
5058
5620
|
}
|
|
@@ -5267,12 +5829,12 @@ function getBoardRoutes(factoryDir) {
|
|
|
5267
5829
|
method: "GET",
|
|
5268
5830
|
pattern: "/api/extensions/plugins",
|
|
5269
5831
|
handler: () => {
|
|
5270
|
-
const lockPath =
|
|
5271
|
-
if (!
|
|
5832
|
+
const lockPath = join14(factoryDir, ".beastmode", "extensions.lock");
|
|
5833
|
+
if (!existsSync16(lockPath)) {
|
|
5272
5834
|
return { plugins: {} };
|
|
5273
5835
|
}
|
|
5274
5836
|
try {
|
|
5275
|
-
const lock = JSON.parse(
|
|
5837
|
+
const lock = JSON.parse(readFileSync13(lockPath, "utf-8"));
|
|
5276
5838
|
return { plugins: lock.plugins || {} };
|
|
5277
5839
|
} catch {
|
|
5278
5840
|
return { plugins: {} };
|
|
@@ -5429,27 +5991,8 @@ function getBoardRoutes(factoryDir) {
|
|
|
5429
5991
|
method: "GET",
|
|
5430
5992
|
pattern: "/api/projects",
|
|
5431
5993
|
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 };
|
|
5994
|
+
const projectsDir = join14(factoryDir, ".beastmode", "projects");
|
|
5995
|
+
return { projects: listProjectRecords(projectsDir) };
|
|
5453
5996
|
}
|
|
5454
5997
|
},
|
|
5455
5998
|
{
|
|
@@ -5458,11 +6001,9 @@ function getBoardRoutes(factoryDir) {
|
|
|
5458
6001
|
handler: (_body, params) => {
|
|
5459
6002
|
const { name } = params;
|
|
5460
6003
|
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"));
|
|
6004
|
+
const record = readProjectRecord(join14(factoryDir, ".beastmode", "projects"), name);
|
|
6005
|
+
if (!record) throw new Error(`Project not found: ${name}`);
|
|
6006
|
+
return record;
|
|
5466
6007
|
}
|
|
5467
6008
|
},
|
|
5468
6009
|
{
|
|
@@ -5471,12 +6012,12 @@ function getBoardRoutes(factoryDir) {
|
|
|
5471
6012
|
handler: (_body, params) => {
|
|
5472
6013
|
const { name } = params;
|
|
5473
6014
|
assertSafeName(name);
|
|
5474
|
-
const extPath =
|
|
5475
|
-
if (!
|
|
6015
|
+
const extPath = join14(factoryDir, ".beastmode", "projects", name, "extensions.json");
|
|
6016
|
+
if (!existsSync16(extPath)) {
|
|
5476
6017
|
return { plugins: { add: [], remove: [] }, mcps: { add: {}, remove: [] }, skills: { add: [], remove: [] } };
|
|
5477
6018
|
}
|
|
5478
6019
|
try {
|
|
5479
|
-
return JSON.parse(
|
|
6020
|
+
return JSON.parse(readFileSync13(extPath, "utf-8"));
|
|
5480
6021
|
} catch {
|
|
5481
6022
|
return { plugins: { add: [], remove: [] }, mcps: { add: {}, remove: [] }, skills: { add: [], remove: [] } };
|
|
5482
6023
|
}
|
|
@@ -5498,18 +6039,18 @@ function getBoardRoutes(factoryDir) {
|
|
|
5498
6039
|
const { path: queryPath } = body || {};
|
|
5499
6040
|
const startPath = queryPath || homedir();
|
|
5500
6041
|
const target = resolve4(startPath);
|
|
5501
|
-
if (!
|
|
5502
|
-
const st =
|
|
6042
|
+
if (!existsSync16(target)) throw new Error(`Path not found: ${target}`);
|
|
6043
|
+
const st = statSync6(target);
|
|
5503
6044
|
if (!st.isDirectory()) throw new Error(`Not a directory: ${target}`);
|
|
5504
6045
|
let entries = [];
|
|
5505
6046
|
try {
|
|
5506
|
-
entries =
|
|
5507
|
-
const full =
|
|
6047
|
+
entries = readdirSync8(target, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => {
|
|
6048
|
+
const full = join14(target, d.name);
|
|
5508
6049
|
return {
|
|
5509
6050
|
name: d.name,
|
|
5510
6051
|
path: full,
|
|
5511
6052
|
is_dir: true,
|
|
5512
|
-
has_git:
|
|
6053
|
+
has_git: existsSync16(join14(full, ".git"))
|
|
5513
6054
|
};
|
|
5514
6055
|
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
5515
6056
|
} catch {
|
|
@@ -5528,8 +6069,8 @@ function getBoardRoutes(factoryDir) {
|
|
|
5528
6069
|
pattern: "/api/projects",
|
|
5529
6070
|
handler: (body) => {
|
|
5530
6071
|
const { path: projectPath, github_url, clone_target } = body;
|
|
5531
|
-
const projectsDir =
|
|
5532
|
-
if (!
|
|
6072
|
+
const projectsDir = join14(factoryDir, ".beastmode", "projects");
|
|
6073
|
+
if (!existsSync16(projectsDir)) mkdirSync12(projectsDir, { recursive: true });
|
|
5533
6074
|
let resolvedPath;
|
|
5534
6075
|
if (github_url) {
|
|
5535
6076
|
const url = github_url.trim();
|
|
@@ -5538,10 +6079,10 @@ function getBoardRoutes(factoryDir) {
|
|
|
5538
6079
|
}
|
|
5539
6080
|
const repoName = url.replace(/\.git$/, "").split(/[/:]/).filter(Boolean).pop() || "";
|
|
5540
6081
|
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 (
|
|
6082
|
+
const baseDir = clone_target ? resolve4(clone_target) : process.env.BEASTMODE_CLONE_DIR ? resolve4(process.env.BEASTMODE_CLONE_DIR) : join14(homedir(), "repos");
|
|
6083
|
+
if (!existsSync16(baseDir)) mkdirSync12(baseDir, { recursive: true });
|
|
6084
|
+
resolvedPath = join14(baseDir, repoName);
|
|
6085
|
+
if (existsSync16(resolvedPath)) {
|
|
5545
6086
|
throw new Error(`Target path already exists: ${resolvedPath} \u2014 pick a different clone_target or rename the existing folder`);
|
|
5546
6087
|
}
|
|
5547
6088
|
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || "";
|
|
@@ -5573,42 +6114,41 @@ function getBoardRoutes(factoryDir) {
|
|
|
5573
6114
|
}
|
|
5574
6115
|
} else if (projectPath) {
|
|
5575
6116
|
resolvedPath = resolve4(projectPath);
|
|
5576
|
-
if (!
|
|
6117
|
+
if (!existsSync16(resolvedPath)) throw new Error(`Directory not found: ${resolvedPath}`);
|
|
5577
6118
|
} else {
|
|
5578
6119
|
throw new Error("Missing required field: provide either `path` (local) or `github_url` (clone)");
|
|
5579
6120
|
}
|
|
5580
|
-
const projectName =
|
|
6121
|
+
const projectName = basename4(resolvedPath);
|
|
5581
6122
|
const stack = detectStack(resolvedPath);
|
|
5582
|
-
|
|
6123
|
+
let verifyPort = 3001;
|
|
6124
|
+
for (const existing of listProjectRecords(projectsDir)) {
|
|
6125
|
+
const port = existing.deploy?.verify_port;
|
|
6126
|
+
if (typeof port === "number" && port >= verifyPort) verifyPort = port + 1;
|
|
6127
|
+
}
|
|
6128
|
+
const record = createProjectRecord({
|
|
5583
6129
|
name: projectName,
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
},
|
|
5600
|
-
state_backend: "auto"
|
|
5601
|
-
}
|
|
6130
|
+
resolvedPath,
|
|
6131
|
+
gitRemote: stack.git_remote || void 0,
|
|
6132
|
+
verifyPort
|
|
6133
|
+
});
|
|
6134
|
+
record.stack = {
|
|
6135
|
+
detected: stack.framework,
|
|
6136
|
+
build_command: stack.suggested_commands.build,
|
|
6137
|
+
dev_command: stack.suggested_commands.dev,
|
|
6138
|
+
test_command: stack.suggested_commands.test,
|
|
6139
|
+
install_command: stack.suggested_commands.install,
|
|
6140
|
+
dev_port: stack.dev_port,
|
|
6141
|
+
is_monorepo: stack.is_monorepo,
|
|
6142
|
+
total_packages: stack.total_packages,
|
|
6143
|
+
primary_languages: stack.primary_languages,
|
|
6144
|
+
...stack.packages ? { packages: stack.packages } : {}
|
|
5602
6145
|
};
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
mkdirSync11(terraformDir, { recursive: true });
|
|
5610
|
-
mkdirSync11(ciDir, { recursive: true });
|
|
5611
|
-
return projectConfig;
|
|
6146
|
+
record.infra = { credentials: { mode: "factory" }, state_backend: "auto" };
|
|
6147
|
+
writeProjectRecord(projectsDir, projectName, record);
|
|
6148
|
+
const infraDir = join14(projectsDir, projectName, "infra");
|
|
6149
|
+
mkdirSync12(join14(infraDir, "terraform"), { recursive: true });
|
|
6150
|
+
mkdirSync12(join14(infraDir, "ci"), { recursive: true });
|
|
6151
|
+
return record;
|
|
5612
6152
|
}
|
|
5613
6153
|
},
|
|
5614
6154
|
{
|
|
@@ -5617,14 +6157,14 @@ function getBoardRoutes(factoryDir) {
|
|
|
5617
6157
|
handler: (_body, params) => {
|
|
5618
6158
|
const { name } = params;
|
|
5619
6159
|
assertSafeName(name);
|
|
5620
|
-
const projectsDir =
|
|
5621
|
-
const subDir =
|
|
5622
|
-
const flatPath =
|
|
5623
|
-
const hasSubDir =
|
|
5624
|
-
const hasFlat =
|
|
6160
|
+
const projectsDir = join14(factoryDir, ".beastmode", "projects");
|
|
6161
|
+
const subDir = join14(projectsDir, name);
|
|
6162
|
+
const flatPath = join14(projectsDir, `${name}.json`);
|
|
6163
|
+
const hasSubDir = existsSync16(join14(subDir, "project.json"));
|
|
6164
|
+
const hasFlat = existsSync16(flatPath);
|
|
5625
6165
|
if (!hasSubDir && !hasFlat) throw new Error(`Project not found: ${name}`);
|
|
5626
6166
|
if (hasSubDir) rmSync3(subDir, { recursive: true, force: true });
|
|
5627
|
-
else if (
|
|
6167
|
+
else if (existsSync16(subDir)) rmSync3(subDir, { recursive: true, force: true });
|
|
5628
6168
|
if (hasFlat) unlinkSync3(flatPath);
|
|
5629
6169
|
return { success: true };
|
|
5630
6170
|
}
|
|
@@ -5635,20 +6175,17 @@ function getBoardRoutes(factoryDir) {
|
|
|
5635
6175
|
handler: async (body, params, query) => {
|
|
5636
6176
|
const { name } = params;
|
|
5637
6177
|
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"));
|
|
6178
|
+
const projectsDir = join14(factoryDir, ".beastmode", "projects");
|
|
6179
|
+
const projConfig = readProjectRecord(projectsDir, name);
|
|
6180
|
+
if (!projConfig) throw new Error(`Project not found: ${name}`);
|
|
5644
6181
|
const projectPath = projConfig.path;
|
|
5645
|
-
const subDir =
|
|
5646
|
-
if (!
|
|
6182
|
+
const subDir = join14(projectsDir, name);
|
|
6183
|
+
if (!existsSync16(subDir)) mkdirSync12(subDir, { recursive: true });
|
|
5647
6184
|
const force = query?.force === "true" || query?.force === "1";
|
|
5648
6185
|
const tier = body?.tier;
|
|
5649
6186
|
if (tier === "quick") {
|
|
5650
|
-
const brownfieldPath =
|
|
5651
|
-
if (!
|
|
6187
|
+
const brownfieldPath = join14(subDir, "brownfield.md");
|
|
6188
|
+
if (!existsSync16(brownfieldPath)) {
|
|
5652
6189
|
const { detectStack: detectStackFn } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
5653
6190
|
let stackInfo = "Unknown stack";
|
|
5654
6191
|
try {
|
|
@@ -5659,7 +6196,7 @@ Build: ${cmds?.build || "unknown"}
|
|
|
5659
6196
|
Dev: ${cmds?.dev || "unknown"}`;
|
|
5660
6197
|
} catch {
|
|
5661
6198
|
}
|
|
5662
|
-
|
|
6199
|
+
writeFileSync13(brownfieldPath, `# Brownfield Analysis: ${name}
|
|
5663
6200
|
|
|
5664
6201
|
${stackInfo}
|
|
5665
6202
|
|
|
@@ -5681,8 +6218,8 @@ Path: ${projectPath}
|
|
|
5681
6218
|
}
|
|
5682
6219
|
const writeMeta = (m) => {
|
|
5683
6220
|
try {
|
|
5684
|
-
|
|
5685
|
-
|
|
6221
|
+
writeFileSync13(
|
|
6222
|
+
join14(subDir, "codebase-guide.meta.json"),
|
|
5686
6223
|
JSON.stringify(m, null, 2) + "\n",
|
|
5687
6224
|
"utf-8"
|
|
5688
6225
|
);
|
|
@@ -5691,8 +6228,8 @@ Path: ${projectPath}
|
|
|
5691
6228
|
}
|
|
5692
6229
|
};
|
|
5693
6230
|
if (!_hasClaudeCli()) {
|
|
5694
|
-
const brownfieldPath =
|
|
5695
|
-
if (!
|
|
6231
|
+
const brownfieldPath = join14(subDir, "brownfield.md");
|
|
6232
|
+
if (!existsSync16(brownfieldPath)) {
|
|
5696
6233
|
const { detectStack: detectStackFn } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
5697
6234
|
let stackInfo = "Unknown stack";
|
|
5698
6235
|
try {
|
|
@@ -5703,7 +6240,7 @@ Build: ${cmds?.build || "unknown"}
|
|
|
5703
6240
|
Dev: ${cmds?.dev || "unknown"}`;
|
|
5704
6241
|
} catch {
|
|
5705
6242
|
}
|
|
5706
|
-
|
|
6243
|
+
writeFileSync13(brownfieldPath, `# Brownfield Analysis: ${name}
|
|
5707
6244
|
|
|
5708
6245
|
${stackInfo}
|
|
5709
6246
|
|
|
@@ -5737,9 +6274,9 @@ Path: ${projectPath}
|
|
|
5737
6274
|
"You are the Brownfield Analyst. Analyze the codebase at the current working directory.",
|
|
5738
6275
|
`Write your output in pyramid format (L0/L1/L2) to the directory: ${subDir}`,
|
|
5739
6276
|
"Produce exactly these four files:",
|
|
5740
|
-
` - ${
|
|
5741
|
-
` - ${
|
|
5742
|
-
` - ${
|
|
6277
|
+
` - ${join14(subDir, "codebase-guide.md")} (full L2 analysis: architecture, conventions, safe modification points, fragile areas)`,
|
|
6278
|
+
` - ${join14(subDir, "codebase-guide.l1.md")} (paragraph summary only)`,
|
|
6279
|
+
` - ${join14(subDir, "codebase-guide.l0.txt")} (one-sentence summary only)`,
|
|
5743
6280
|
"Do not write anything else outside the BEASTMODE_OUTPUT_DIR."
|
|
5744
6281
|
].join("\n");
|
|
5745
6282
|
const isRoot = process.getuid?.() === 0;
|
|
@@ -5772,8 +6309,8 @@ Path: ${projectPath}
|
|
|
5772
6309
|
child.on("close", (code) => {
|
|
5773
6310
|
const durationSeconds = (Date.now() - startMs) / 1e3;
|
|
5774
6311
|
const sha = _gitHeadSha(projectPath);
|
|
5775
|
-
const guidePath =
|
|
5776
|
-
const ok = code === 0 &&
|
|
6312
|
+
const guidePath = join14(subDir, "codebase-guide.md");
|
|
6313
|
+
const ok = code === 0 && existsSync16(guidePath);
|
|
5777
6314
|
if (ok) {
|
|
5778
6315
|
writeMeta({
|
|
5779
6316
|
analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -5783,8 +6320,8 @@ Path: ${projectPath}
|
|
|
5783
6320
|
duration_seconds: durationSeconds,
|
|
5784
6321
|
status: "complete"
|
|
5785
6322
|
});
|
|
5786
|
-
const legacy =
|
|
5787
|
-
if (
|
|
6323
|
+
const legacy = join14(subDir, "brownfield.md");
|
|
6324
|
+
if (existsSync16(legacy)) {
|
|
5788
6325
|
try {
|
|
5789
6326
|
unlinkSync3(legacy);
|
|
5790
6327
|
} catch {
|
|
@@ -5860,7 +6397,7 @@ Path: ${projectPath}
|
|
|
5860
6397
|
handler: (_body, params) => {
|
|
5861
6398
|
const { name } = params;
|
|
5862
6399
|
assertSafeName(name);
|
|
5863
|
-
const projectsDir =
|
|
6400
|
+
const projectsDir = join14(factoryDir, ".beastmode", "projects");
|
|
5864
6401
|
const job = _analyzeJobs.get(name);
|
|
5865
6402
|
const meta = _readAnalyzeMeta(projectsDir, name);
|
|
5866
6403
|
if (job && job.status === "running") {
|
|
@@ -5904,13 +6441,13 @@ Path: ${projectPath}
|
|
|
5904
6441
|
pattern: "/api/runs",
|
|
5905
6442
|
handler: () => {
|
|
5906
6443
|
const runsDir = getRunsDir(factoryDir);
|
|
5907
|
-
if (!
|
|
6444
|
+
if (!existsSync16(runsDir)) return { runs: [] };
|
|
5908
6445
|
const runEntries = [];
|
|
5909
|
-
for (const entry of
|
|
6446
|
+
for (const entry of readdirSync8(runsDir)) {
|
|
5910
6447
|
if (entry.startsWith(".")) continue;
|
|
5911
|
-
const entryPath =
|
|
6448
|
+
const entryPath = join14(runsDir, entry);
|
|
5912
6449
|
try {
|
|
5913
|
-
const children =
|
|
6450
|
+
const children = readdirSync8(entryPath);
|
|
5914
6451
|
if (entry.startsWith("run-")) {
|
|
5915
6452
|
if (children.length > 0) {
|
|
5916
6453
|
runEntries.push({ id: entry, dir: entryPath, projectId: "" });
|
|
@@ -5918,9 +6455,9 @@ Path: ${projectPath}
|
|
|
5918
6455
|
} else {
|
|
5919
6456
|
for (const child of children) {
|
|
5920
6457
|
if (!child.startsWith("run-") || child.startsWith(".")) continue;
|
|
5921
|
-
const childPath =
|
|
6458
|
+
const childPath = join14(entryPath, child);
|
|
5922
6459
|
try {
|
|
5923
|
-
const grandchildren =
|
|
6460
|
+
const grandchildren = readdirSync8(childPath);
|
|
5924
6461
|
if (grandchildren.length > 0) {
|
|
5925
6462
|
runEntries.push({ id: child, dir: childPath, projectId: entry });
|
|
5926
6463
|
}
|
|
@@ -5940,9 +6477,9 @@ Path: ${projectPath}
|
|
|
5940
6477
|
}
|
|
5941
6478
|
}
|
|
5942
6479
|
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(
|
|
6480
|
+
const manifest = readJsonFile(join14(dir, "manifest.json"));
|
|
6481
|
+
const checkpoint = readJsonFile(join14(dir, "checkpoint.json"));
|
|
6482
|
+
const prodVerif = readJsonFile(join14(dir, "prod-verification.json"));
|
|
5946
6483
|
const manifestData = manifest;
|
|
5947
6484
|
const cpData = checkpoint;
|
|
5948
6485
|
const prodAcceptFloor = 0.65;
|
|
@@ -5970,27 +6507,27 @@ Path: ${projectPath}
|
|
|
5970
6507
|
handler: (_body, params, query) => {
|
|
5971
6508
|
const { id } = params;
|
|
5972
6509
|
const runsDir = getRunsDir(factoryDir);
|
|
5973
|
-
let runDir =
|
|
5974
|
-
if (!
|
|
5975
|
-
for (const proj of
|
|
6510
|
+
let runDir = join14(runsDir, id);
|
|
6511
|
+
if (!existsSync16(runDir)) {
|
|
6512
|
+
for (const proj of readdirSync8(runsDir)) {
|
|
5976
6513
|
if (proj.startsWith(".") || proj.startsWith("run-")) continue;
|
|
5977
|
-
const candidate =
|
|
5978
|
-
if (
|
|
6514
|
+
const candidate = join14(runsDir, proj, id);
|
|
6515
|
+
if (existsSync16(candidate)) {
|
|
5979
6516
|
runDir = candidate;
|
|
5980
6517
|
break;
|
|
5981
6518
|
}
|
|
5982
6519
|
}
|
|
5983
6520
|
}
|
|
5984
|
-
if (!
|
|
5985
|
-
const manifest = readJsonFile(
|
|
5986
|
-
const checkpoint = readJsonFile(
|
|
5987
|
-
const iterationsDir =
|
|
6521
|
+
if (!existsSync16(runDir)) throw new Error(`Run not found: ${id}`);
|
|
6522
|
+
const manifest = readJsonFile(join14(runDir, "manifest.json"));
|
|
6523
|
+
const checkpoint = readJsonFile(join14(runDir, "checkpoint.json"));
|
|
6524
|
+
const iterationsDir = join14(runDir, "iterations");
|
|
5988
6525
|
const iterations = [];
|
|
5989
|
-
if (
|
|
5990
|
-
const iterDirs =
|
|
6526
|
+
if (existsSync16(iterationsDir)) {
|
|
6527
|
+
const iterDirs = readdirSync8(iterationsDir).sort();
|
|
5991
6528
|
for (const iterDir of iterDirs) {
|
|
5992
6529
|
const satisfaction = readJsonFile(
|
|
5993
|
-
|
|
6530
|
+
join14(iterationsDir, iterDir, "satisfaction.json")
|
|
5994
6531
|
);
|
|
5995
6532
|
iterations.push({
|
|
5996
6533
|
number: parseInt(iterDir, 10),
|
|
@@ -6018,11 +6555,11 @@ Path: ${projectPath}
|
|
|
6018
6555
|
pattern: "/api/runs/archive",
|
|
6019
6556
|
handler: () => {
|
|
6020
6557
|
const runsDir = getRunsDir(factoryDir);
|
|
6021
|
-
const configPath =
|
|
6558
|
+
const configPath = join14(factoryDir, ".beastmode", "config.json");
|
|
6022
6559
|
let days = 7;
|
|
6023
|
-
if (
|
|
6560
|
+
if (existsSync16(configPath)) {
|
|
6024
6561
|
try {
|
|
6025
|
-
days = JSON.parse(
|
|
6562
|
+
days = JSON.parse(readFileSync13(configPath, "utf-8")).archive_after_days || 7;
|
|
6026
6563
|
} catch {
|
|
6027
6564
|
}
|
|
6028
6565
|
}
|
|
@@ -6048,8 +6585,8 @@ Path: ${projectPath}
|
|
|
6048
6585
|
method: "GET",
|
|
6049
6586
|
pattern: "/api/config",
|
|
6050
6587
|
handler: () => {
|
|
6051
|
-
const configPath =
|
|
6052
|
-
const raw =
|
|
6588
|
+
const configPath = join14(factoryDir, ".beastmode", "config.json");
|
|
6589
|
+
const raw = existsSync16(configPath) ? JSON.parse(readFileSync13(configPath, "utf-8")) : generateDefaults();
|
|
6053
6590
|
return raw;
|
|
6054
6591
|
}
|
|
6055
6592
|
},
|
|
@@ -6067,8 +6604,8 @@ Path: ${projectPath}
|
|
|
6067
6604
|
const updatesClean = { ...updates };
|
|
6068
6605
|
delete updatesClean.force;
|
|
6069
6606
|
delete updatesClean._force;
|
|
6070
|
-
const configPath =
|
|
6071
|
-
const current =
|
|
6607
|
+
const configPath = join14(factoryDir, ".beastmode", "config.json");
|
|
6608
|
+
const current = existsSync16(configPath) ? JSON.parse(readFileSync13(configPath, "utf-8")) : generateDefaults();
|
|
6072
6609
|
if (!force) {
|
|
6073
6610
|
const oldPipeline = current.pipeline || {};
|
|
6074
6611
|
const newPipeline = updatesClean.pipeline || {};
|
|
@@ -6099,11 +6636,11 @@ Path: ${projectPath}
|
|
|
6099
6636
|
}
|
|
6100
6637
|
}
|
|
6101
6638
|
const merged = deepMerge2(current, updatesClean);
|
|
6102
|
-
|
|
6639
|
+
writeFileSync13(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
6103
6640
|
const daemonConfigPaths = [
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
].filter(
|
|
6641
|
+
join14(factoryDir, "config", "beastmode.daemon.json"),
|
|
6642
|
+
join14(factoryDir, "config", "beastmode.docker.json")
|
|
6643
|
+
].filter(existsSync16);
|
|
6107
6644
|
const pipelineToDaemonMap = {
|
|
6108
6645
|
satisfaction_threshold: ["verification", "satisfaction_threshold"],
|
|
6109
6646
|
prod_accept_floor: ["verification", "prod_accept_floor"],
|
|
@@ -6119,7 +6656,7 @@ Path: ${projectPath}
|
|
|
6119
6656
|
];
|
|
6120
6657
|
for (const daemonConfigPath of daemonConfigPaths) {
|
|
6121
6658
|
try {
|
|
6122
|
-
const daemonConfig = JSON.parse(
|
|
6659
|
+
const daemonConfig = JSON.parse(readFileSync13(daemonConfigPath, "utf-8"));
|
|
6123
6660
|
let changed = false;
|
|
6124
6661
|
for (const field of daemonFields) {
|
|
6125
6662
|
if (field in merged && merged[field] !== daemonConfig[field]) {
|
|
@@ -6159,7 +6696,7 @@ Path: ${projectPath}
|
|
|
6159
6696
|
}
|
|
6160
6697
|
}
|
|
6161
6698
|
if (changed) {
|
|
6162
|
-
|
|
6699
|
+
writeFileSync13(daemonConfigPath, JSON.stringify(daemonConfig, null, 2) + "\n", "utf-8");
|
|
6163
6700
|
}
|
|
6164
6701
|
} catch {
|
|
6165
6702
|
}
|
|
@@ -6172,10 +6709,10 @@ Path: ${projectPath}
|
|
|
6172
6709
|
pattern: "/api/config/reset",
|
|
6173
6710
|
handler: (body) => {
|
|
6174
6711
|
const { keyPath } = body || {};
|
|
6175
|
-
const configPath =
|
|
6176
|
-
const current =
|
|
6712
|
+
const configPath = join14(factoryDir, ".beastmode", "config.json");
|
|
6713
|
+
const current = existsSync16(configPath) ? JSON.parse(readFileSync13(configPath, "utf-8")) : {};
|
|
6177
6714
|
const result = configReset(current, keyPath);
|
|
6178
|
-
|
|
6715
|
+
writeFileSync13(configPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
|
|
6179
6716
|
return result;
|
|
6180
6717
|
}
|
|
6181
6718
|
},
|
|
@@ -6265,13 +6802,13 @@ Path: ${projectPath}
|
|
|
6265
6802
|
handler: () => {
|
|
6266
6803
|
const runsDir = getRunsDir(factoryDir);
|
|
6267
6804
|
const retroDirs = [];
|
|
6268
|
-
const rootRetroDir =
|
|
6269
|
-
if (
|
|
6270
|
-
if (
|
|
6271
|
-
for (const entry of
|
|
6805
|
+
const rootRetroDir = join14(runsDir, ".retrospectives");
|
|
6806
|
+
if (existsSync16(rootRetroDir)) retroDirs.push(rootRetroDir);
|
|
6807
|
+
if (existsSync16(runsDir)) {
|
|
6808
|
+
for (const entry of readdirSync8(runsDir)) {
|
|
6272
6809
|
if (entry === ".retrospectives" || entry.startsWith(".")) continue;
|
|
6273
|
-
const projectRetroDir =
|
|
6274
|
-
if (
|
|
6810
|
+
const projectRetroDir = join14(runsDir, entry, ".retrospectives");
|
|
6811
|
+
if (existsSync16(projectRetroDir)) retroDirs.push(projectRetroDir);
|
|
6275
6812
|
}
|
|
6276
6813
|
}
|
|
6277
6814
|
if (retroDirs.length === 0) {
|
|
@@ -6280,9 +6817,9 @@ Path: ${projectPath}
|
|
|
6280
6817
|
const retrospectives = [];
|
|
6281
6818
|
const learnings = [];
|
|
6282
6819
|
for (const retroDir of retroDirs) {
|
|
6283
|
-
const retroFiles =
|
|
6820
|
+
const retroFiles = readdirSync8(retroDir).filter((f) => f.startsWith("run-") && f.endsWith(".json")).sort().reverse();
|
|
6284
6821
|
for (const file of retroFiles) {
|
|
6285
|
-
const data = readJsonFile(
|
|
6822
|
+
const data = readJsonFile(join14(retroDir, file));
|
|
6286
6823
|
if (!data) continue;
|
|
6287
6824
|
const retro = data;
|
|
6288
6825
|
retrospectives.push(retro);
|
|
@@ -6304,10 +6841,10 @@ Path: ${projectPath}
|
|
|
6304
6841
|
let wisdom = "";
|
|
6305
6842
|
let wisdomMeta = {};
|
|
6306
6843
|
for (const retroDir of retroDirs) {
|
|
6307
|
-
const wisdomPath =
|
|
6308
|
-
if (
|
|
6309
|
-
wisdom =
|
|
6310
|
-
wisdomMeta = readJsonFile(
|
|
6844
|
+
const wisdomPath = join14(retroDir, "wisdom.md");
|
|
6845
|
+
if (existsSync16(wisdomPath)) {
|
|
6846
|
+
wisdom = readFileSync13(wisdomPath, "utf-8");
|
|
6847
|
+
wisdomMeta = readJsonFile(join14(retroDir, "wisdom-meta.json")) || {};
|
|
6311
6848
|
break;
|
|
6312
6849
|
}
|
|
6313
6850
|
}
|
|
@@ -6319,11 +6856,11 @@ Path: ${projectPath}
|
|
|
6319
6856
|
pattern: "/api/learnings/wisdom/refresh",
|
|
6320
6857
|
handler: () => {
|
|
6321
6858
|
const runsDir = getRunsDir(factoryDir);
|
|
6322
|
-
const retroDir =
|
|
6323
|
-
if (!
|
|
6324
|
-
|
|
6859
|
+
const retroDir = join14(runsDir, ".retrospectives");
|
|
6860
|
+
if (!existsSync16(retroDir)) {
|
|
6861
|
+
mkdirSync12(retroDir, { recursive: true });
|
|
6325
6862
|
}
|
|
6326
|
-
|
|
6863
|
+
writeFileSync13(join14(retroDir, ".refresh-requested"), (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
|
|
6327
6864
|
return { success: true };
|
|
6328
6865
|
}
|
|
6329
6866
|
},
|
|
@@ -6332,29 +6869,29 @@ Path: ${projectPath}
|
|
|
6332
6869
|
pattern: "/api/learnings/:runId/:index/dismiss",
|
|
6333
6870
|
handler: (_body, params) => {
|
|
6334
6871
|
const runsDir = getRunsDir(factoryDir);
|
|
6335
|
-
const candidateDirs = [
|
|
6336
|
-
if (
|
|
6337
|
-
for (const entry of
|
|
6872
|
+
const candidateDirs = [join14(runsDir, ".retrospectives")];
|
|
6873
|
+
if (existsSync16(runsDir)) {
|
|
6874
|
+
for (const entry of readdirSync8(runsDir)) {
|
|
6338
6875
|
if (entry === ".retrospectives" || entry.startsWith(".")) continue;
|
|
6339
|
-
candidateDirs.push(
|
|
6876
|
+
candidateDirs.push(join14(runsDir, entry, ".retrospectives"));
|
|
6340
6877
|
}
|
|
6341
6878
|
}
|
|
6342
6879
|
let filePath = null;
|
|
6343
6880
|
for (const dir of candidateDirs) {
|
|
6344
|
-
const candidate =
|
|
6345
|
-
if (
|
|
6881
|
+
const candidate = join14(dir, `${params.runId}.json`);
|
|
6882
|
+
if (existsSync16(candidate)) {
|
|
6346
6883
|
filePath = candidate;
|
|
6347
6884
|
break;
|
|
6348
6885
|
}
|
|
6349
6886
|
}
|
|
6350
6887
|
if (!filePath) throw new Error(`Retrospective not found: ${params.runId}`);
|
|
6351
|
-
const data = JSON.parse(
|
|
6888
|
+
const data = JSON.parse(readFileSync13(filePath, "utf-8"));
|
|
6352
6889
|
const idx = parseInt(params.index, 10);
|
|
6353
6890
|
if (!Array.isArray(data.learnings) || idx < 0 || idx >= data.learnings.length) {
|
|
6354
6891
|
throw new Error(`Learning index out of range: ${idx}`);
|
|
6355
6892
|
}
|
|
6356
6893
|
data.learnings[idx].dismissed = true;
|
|
6357
|
-
|
|
6894
|
+
writeFileSync13(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
6358
6895
|
return { success: true };
|
|
6359
6896
|
}
|
|
6360
6897
|
},
|
|
@@ -6363,29 +6900,29 @@ Path: ${projectPath}
|
|
|
6363
6900
|
pattern: "/api/learnings/:runId/:index/restore",
|
|
6364
6901
|
handler: (_body, params) => {
|
|
6365
6902
|
const runsDir = getRunsDir(factoryDir);
|
|
6366
|
-
const candidateDirs = [
|
|
6367
|
-
if (
|
|
6368
|
-
for (const entry of
|
|
6903
|
+
const candidateDirs = [join14(runsDir, ".retrospectives")];
|
|
6904
|
+
if (existsSync16(runsDir)) {
|
|
6905
|
+
for (const entry of readdirSync8(runsDir)) {
|
|
6369
6906
|
if (entry === ".retrospectives" || entry.startsWith(".")) continue;
|
|
6370
|
-
candidateDirs.push(
|
|
6907
|
+
candidateDirs.push(join14(runsDir, entry, ".retrospectives"));
|
|
6371
6908
|
}
|
|
6372
6909
|
}
|
|
6373
6910
|
let filePath = null;
|
|
6374
6911
|
for (const dir of candidateDirs) {
|
|
6375
|
-
const candidate =
|
|
6376
|
-
if (
|
|
6912
|
+
const candidate = join14(dir, `${params.runId}.json`);
|
|
6913
|
+
if (existsSync16(candidate)) {
|
|
6377
6914
|
filePath = candidate;
|
|
6378
6915
|
break;
|
|
6379
6916
|
}
|
|
6380
6917
|
}
|
|
6381
6918
|
if (!filePath) throw new Error(`Retrospective not found: ${params.runId}`);
|
|
6382
|
-
const data = JSON.parse(
|
|
6919
|
+
const data = JSON.parse(readFileSync13(filePath, "utf-8"));
|
|
6383
6920
|
const idx = parseInt(params.index, 10);
|
|
6384
6921
|
if (!Array.isArray(data.learnings) || idx < 0 || idx >= data.learnings.length) {
|
|
6385
6922
|
throw new Error(`Learning index out of range: ${idx}`);
|
|
6386
6923
|
}
|
|
6387
6924
|
data.learnings[idx].dismissed = false;
|
|
6388
|
-
|
|
6925
|
+
writeFileSync13(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
6389
6926
|
return { success: true };
|
|
6390
6927
|
}
|
|
6391
6928
|
},
|
|
@@ -6528,22 +7065,22 @@ Path: ${projectPath}
|
|
|
6528
7065
|
} catch {
|
|
6529
7066
|
}
|
|
6530
7067
|
try {
|
|
6531
|
-
const runsDir =
|
|
6532
|
-
if (
|
|
6533
|
-
const runDirs =
|
|
7068
|
+
const runsDir = join14(factoryDir, "runs", project);
|
|
7069
|
+
if (existsSync16(runsDir)) {
|
|
7070
|
+
const runDirs = readdirSync8(runsDir).filter((d) => d.startsWith("run-"));
|
|
6534
7071
|
for (const runId of runDirs) {
|
|
6535
|
-
const ckptPath =
|
|
7072
|
+
const ckptPath = join14(runsDir, runId, "checkpoint.json");
|
|
6536
7073
|
let subtitle = "";
|
|
6537
7074
|
let timestamp = "";
|
|
6538
7075
|
try {
|
|
6539
|
-
const st =
|
|
7076
|
+
const st = statSync6(join14(runsDir, runId));
|
|
6540
7077
|
timestamp = st.mtime.toISOString();
|
|
6541
7078
|
} catch {
|
|
6542
7079
|
timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
6543
7080
|
}
|
|
6544
|
-
if (
|
|
7081
|
+
if (existsSync16(ckptPath)) {
|
|
6545
7082
|
try {
|
|
6546
|
-
const ckpt = JSON.parse(
|
|
7083
|
+
const ckpt = JSON.parse(readFileSync13(ckptPath, "utf-8"));
|
|
6547
7084
|
const history = ckpt.satisfaction_history;
|
|
6548
7085
|
if (history && history.length > 0) {
|
|
6549
7086
|
const latest = history[history.length - 1];
|
|
@@ -6651,8 +7188,8 @@ __export(server_exports, {
|
|
|
6651
7188
|
});
|
|
6652
7189
|
import { createServer } from "http";
|
|
6653
7190
|
import { createHmac } from "crypto";
|
|
6654
|
-
import { readFileSync as
|
|
6655
|
-
import { join as
|
|
7191
|
+
import { readFileSync as readFileSync14, existsSync as existsSync17 } from "fs";
|
|
7192
|
+
import { join as join15, dirname as dirname6 } from "path";
|
|
6656
7193
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6657
7194
|
import { WebSocketServer as WebSocketServer2, WebSocket as WsClient } from "ws";
|
|
6658
7195
|
function proxyBoardWebSocket(req, socket, head, boardWsUrl) {
|
|
@@ -6694,16 +7231,16 @@ function proxyBoardWebSocket(req, socket, head, boardWsUrl) {
|
|
|
6694
7231
|
});
|
|
6695
7232
|
}
|
|
6696
7233
|
function resolveStaticDir() {
|
|
6697
|
-
const devPath =
|
|
6698
|
-
if (
|
|
7234
|
+
const devPath = join15(__dirname, "static");
|
|
7235
|
+
if (existsSync17(join15(devPath, "index.html"))) {
|
|
6699
7236
|
return devPath;
|
|
6700
7237
|
}
|
|
6701
|
-
const distWebPath =
|
|
6702
|
-
if (
|
|
7238
|
+
const distWebPath = join15(__dirname, "web");
|
|
7239
|
+
if (existsSync17(join15(distWebPath, "index.html"))) {
|
|
6703
7240
|
return distWebPath;
|
|
6704
7241
|
}
|
|
6705
|
-
const siblingPath =
|
|
6706
|
-
if (
|
|
7242
|
+
const siblingPath = join15(__dirname, "..", "web");
|
|
7243
|
+
if (existsSync17(join15(siblingPath, "index.html"))) {
|
|
6707
7244
|
return siblingPath;
|
|
6708
7245
|
}
|
|
6709
7246
|
throw new Error(
|
|
@@ -6812,9 +7349,9 @@ async function startServer(options = {}) {
|
|
|
6812
7349
|
const fallback = "<html><body><h1>BeastMode Init Wizard</h1><p>Static files not found.</p></body></html>";
|
|
6813
7350
|
if (!staticDir) return fallback;
|
|
6814
7351
|
try {
|
|
6815
|
-
const indexPath =
|
|
6816
|
-
if (!
|
|
6817
|
-
return
|
|
7352
|
+
const indexPath = join15(staticDir, "index.html");
|
|
7353
|
+
if (!existsSync17(indexPath)) return fallback;
|
|
7354
|
+
return readFileSync14(indexPath, "utf-8");
|
|
6818
7355
|
} catch {
|
|
6819
7356
|
return fallback;
|
|
6820
7357
|
}
|
|
@@ -6822,9 +7359,9 @@ async function startServer(options = {}) {
|
|
|
6822
7359
|
function loadBoardHtml() {
|
|
6823
7360
|
try {
|
|
6824
7361
|
const dir = staticDir || resolveStaticDir();
|
|
6825
|
-
const boardPath =
|
|
6826
|
-
if (!
|
|
6827
|
-
return
|
|
7362
|
+
const boardPath = join15(dir, "board.html");
|
|
7363
|
+
if (!existsSync17(boardPath)) return null;
|
|
7364
|
+
return readFileSync14(boardPath, "utf-8");
|
|
6828
7365
|
} catch {
|
|
6829
7366
|
return null;
|
|
6830
7367
|
}
|
|
@@ -6910,13 +7447,13 @@ async function startServer(options = {}) {
|
|
|
6910
7447
|
let commit_sha = null;
|
|
6911
7448
|
try {
|
|
6912
7449
|
const dir = staticDir || resolveStaticDir();
|
|
6913
|
-
const stampPath =
|
|
6914
|
-
if (
|
|
6915
|
-
stamp =
|
|
7450
|
+
const stampPath = join15(dir, "build-stamp.txt");
|
|
7451
|
+
if (existsSync17(stampPath)) {
|
|
7452
|
+
stamp = readFileSync14(stampPath, "utf-8").trim();
|
|
6916
7453
|
}
|
|
6917
|
-
const commitPath =
|
|
6918
|
-
if (
|
|
6919
|
-
commit_sha =
|
|
7454
|
+
const commitPath = join15(dir, "build-commit.txt");
|
|
7455
|
+
if (existsSync17(commitPath)) {
|
|
7456
|
+
commit_sha = readFileSync14(commitPath, "utf-8").trim() || null;
|
|
6920
7457
|
}
|
|
6921
7458
|
} catch {
|
|
6922
7459
|
}
|
|
@@ -6994,8 +7531,8 @@ async function startServer(options = {}) {
|
|
|
6994
7531
|
}
|
|
6995
7532
|
if (method === "GET" && url.startsWith("/static/")) {
|
|
6996
7533
|
const filename = url.slice("/static/".length);
|
|
6997
|
-
const filePath = staticDir ?
|
|
6998
|
-
if (filePath &&
|
|
7534
|
+
const filePath = staticDir ? join15(staticDir, filename) : "";
|
|
7535
|
+
if (filePath && existsSync17(filePath)) {
|
|
6999
7536
|
const ext = filename.split(".").pop()?.toLowerCase();
|
|
7000
7537
|
const mimeTypes = {
|
|
7001
7538
|
png: "image/png",
|
|
@@ -7006,7 +7543,7 @@ async function startServer(options = {}) {
|
|
|
7006
7543
|
ico: "image/x-icon"
|
|
7007
7544
|
};
|
|
7008
7545
|
const contentType = mimeTypes[ext || ""] || "application/octet-stream";
|
|
7009
|
-
const content =
|
|
7546
|
+
const content = readFileSync14(filePath);
|
|
7010
7547
|
res.writeHead(200, {
|
|
7011
7548
|
"Content-Type": contentType,
|
|
7012
7549
|
"Content-Length": content.length.toString(),
|
|
@@ -7255,30 +7792,29 @@ var init_server = __esm({
|
|
|
7255
7792
|
|
|
7256
7793
|
// src/cli/commands/board.ts
|
|
7257
7794
|
import { Command } from "commander";
|
|
7258
|
-
import { resolve as resolve5, join as
|
|
7259
|
-
import { existsSync as
|
|
7795
|
+
import { resolve as resolve5, join as join16 } from "path";
|
|
7796
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14 } from "fs";
|
|
7260
7797
|
import { execSync as execSync4 } from "child_process";
|
|
7261
7798
|
function findFactoryDir(startDir) {
|
|
7262
7799
|
let dir = startDir || process.cwd();
|
|
7263
7800
|
const root = resolve5("/");
|
|
7264
7801
|
while (dir !== root) {
|
|
7265
|
-
if (
|
|
7802
|
+
if (existsSync18(join16(dir, ".beastmode", "factory.json"))) {
|
|
7266
7803
|
return dir;
|
|
7267
7804
|
}
|
|
7268
7805
|
const parent = resolve5(dir, "..");
|
|
7269
7806
|
if (parent === dir) break;
|
|
7270
7807
|
dir = parent;
|
|
7271
7808
|
}
|
|
7272
|
-
if (
|
|
7809
|
+
if (existsSync18(join16(dir, ".beastmode", "factory.json"))) {
|
|
7273
7810
|
return dir;
|
|
7274
7811
|
}
|
|
7275
7812
|
return null;
|
|
7276
7813
|
}
|
|
7277
7814
|
function inferProjectName(factoryDir) {
|
|
7278
|
-
const projectsDir =
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
if (files.length === 1) return files[0].replace(/\.json$/, "");
|
|
7815
|
+
const projectsDir = join16(factoryDir, ".beastmode", "projects");
|
|
7816
|
+
const records = listProjectRecords(projectsDir);
|
|
7817
|
+
if (records.length === 1) return records[0].name;
|
|
7282
7818
|
return null;
|
|
7283
7819
|
}
|
|
7284
7820
|
function tryExecSync(cmd, timeoutMs = 15e3) {
|
|
@@ -7355,13 +7891,13 @@ async function runBoard(opts) {
|
|
|
7355
7891
|
let factoryDir = findFactoryDir();
|
|
7356
7892
|
if (!factoryDir) {
|
|
7357
7893
|
factoryDir = process.cwd();
|
|
7358
|
-
const bmDir =
|
|
7359
|
-
if (!
|
|
7360
|
-
|
|
7894
|
+
const bmDir = join16(factoryDir, ".beastmode");
|
|
7895
|
+
if (!existsSync18(bmDir)) {
|
|
7896
|
+
mkdirSync13(bmDir, { recursive: true });
|
|
7361
7897
|
}
|
|
7362
|
-
const factoryJsonPath2 =
|
|
7363
|
-
if (!
|
|
7364
|
-
|
|
7898
|
+
const factoryJsonPath2 = join16(bmDir, "factory.json");
|
|
7899
|
+
if (!existsSync18(factoryJsonPath2)) {
|
|
7900
|
+
writeFileSync14(
|
|
7365
7901
|
factoryJsonPath2,
|
|
7366
7902
|
JSON.stringify({ factory_name: "BeastMode", created_at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
7367
7903
|
"utf-8"
|
|
@@ -7369,8 +7905,8 @@ async function runBoard(opts) {
|
|
|
7369
7905
|
info("No factory found \u2014 created minimal stub at .beastmode/factory.json");
|
|
7370
7906
|
}
|
|
7371
7907
|
}
|
|
7372
|
-
const factoryJsonPath =
|
|
7373
|
-
const factoryJson = JSON.parse(
|
|
7908
|
+
const factoryJsonPath = join16(factoryDir, ".beastmode", "factory.json");
|
|
7909
|
+
const factoryJson = JSON.parse(readFileSync15(factoryJsonPath, "utf-8"));
|
|
7374
7910
|
const factoryName = factoryJson.factory_name || "BeastMode Factory";
|
|
7375
7911
|
header(`BeastMode Board \u2014 ${factoryName}`);
|
|
7376
7912
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
@@ -7409,6 +7945,7 @@ var init_board = __esm({
|
|
|
7409
7945
|
"src/cli/commands/board.ts"() {
|
|
7410
7946
|
"use strict";
|
|
7411
7947
|
init_display();
|
|
7948
|
+
init_engine();
|
|
7412
7949
|
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
7950
|
try {
|
|
7414
7951
|
await runBoard(opts);
|
|
@@ -7442,26 +7979,26 @@ __export(sync_claude_creds_exports, {
|
|
|
7442
7979
|
});
|
|
7443
7980
|
import { Command as Command2 } from "commander";
|
|
7444
7981
|
import { execSync as execSync5, spawnSync as spawnSync2 } from "child_process";
|
|
7445
|
-
import { writeFileSync as
|
|
7446
|
-
import { join as
|
|
7982
|
+
import { writeFileSync as writeFileSync15, readFileSync as readFileSync16, chmodSync, mkdirSync as mkdirSync14, existsSync as existsSync19, unlinkSync as unlinkSync4 } from "fs";
|
|
7983
|
+
import { join as join17 } from "path";
|
|
7447
7984
|
import { homedir as homedir2, platform } from "os";
|
|
7448
7985
|
function systemdUserDir() {
|
|
7449
|
-
return
|
|
7986
|
+
return join17(homedir2(), ".config", "systemd", "user");
|
|
7450
7987
|
}
|
|
7451
7988
|
function systemdServicePath() {
|
|
7452
|
-
return
|
|
7989
|
+
return join17(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.service`);
|
|
7453
7990
|
}
|
|
7454
7991
|
function systemdTimerPath() {
|
|
7455
|
-
return
|
|
7992
|
+
return join17(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.timer`);
|
|
7456
7993
|
}
|
|
7457
7994
|
function linuxCredsPath() {
|
|
7458
|
-
return
|
|
7995
|
+
return join17(homedir2(), ".claude", ".credentials.json");
|
|
7459
7996
|
}
|
|
7460
7997
|
function plistPath() {
|
|
7461
|
-
return
|
|
7998
|
+
return join17(homedir2(), "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
|
|
7462
7999
|
}
|
|
7463
8000
|
function agentLogPath() {
|
|
7464
|
-
return
|
|
8001
|
+
return join17(homedir2(), ".beastmode", "logs", "sync-claude-creds.log");
|
|
7465
8002
|
}
|
|
7466
8003
|
function readKeychainTokenSafe() {
|
|
7467
8004
|
try {
|
|
@@ -7493,10 +8030,10 @@ function writeCredentialsFile(rawJson) {
|
|
|
7493
8030
|
if (!oauth?.accessToken) {
|
|
7494
8031
|
throw new Error("Keychain entry missing claudeAiOauth.accessToken. Fix: run `claude login` again to reset the credential.");
|
|
7495
8032
|
}
|
|
7496
|
-
const claudeDir =
|
|
7497
|
-
if (!
|
|
7498
|
-
const credsPath =
|
|
7499
|
-
|
|
8033
|
+
const claudeDir = join17(homedir2(), ".claude");
|
|
8034
|
+
if (!existsSync19(claudeDir)) mkdirSync14(claudeDir, { recursive: true });
|
|
8035
|
+
const credsPath = join17(claudeDir, ".credentials.json");
|
|
8036
|
+
writeFileSync15(credsPath, rawJson + "\n", "utf-8");
|
|
7500
8037
|
chmodSync(credsPath, 384);
|
|
7501
8038
|
if (oauth.expiresAt) {
|
|
7502
8039
|
const hoursLeft = Math.round((oauth.expiresAt - Date.now()) / 36e5);
|
|
@@ -7509,7 +8046,7 @@ function writeCredentialsFile(rawJson) {
|
|
|
7509
8046
|
return credsPath;
|
|
7510
8047
|
}
|
|
7511
8048
|
function urgencyMarkerHostPath(factoryDir) {
|
|
7512
|
-
return
|
|
8049
|
+
return join17(factoryDir, "daemon", "logs", ".cred-refresh-needed");
|
|
7513
8050
|
}
|
|
7514
8051
|
function removeUrgencyMarker(factoryDir) {
|
|
7515
8052
|
if (!factoryDir) return;
|
|
@@ -7523,7 +8060,7 @@ function buildPlist(intervalSeconds, factoryDir) {
|
|
|
7523
8060
|
const nodePath = process.execPath;
|
|
7524
8061
|
const cliEntry = process.argv[1];
|
|
7525
8062
|
const logPath = agentLogPath();
|
|
7526
|
-
const keychainPath =
|
|
8063
|
+
const keychainPath = join17(homedir2(), "Library", "Keychains", "login.keychain-db");
|
|
7527
8064
|
const watchPaths = [keychainPath];
|
|
7528
8065
|
if (factoryDir) {
|
|
7529
8066
|
watchPaths.push(urgencyMarkerHostPath(factoryDir));
|
|
@@ -7563,15 +8100,15 @@ function installAgent(intervalSeconds, {
|
|
|
7563
8100
|
} = {}) {
|
|
7564
8101
|
const plist = plistPath();
|
|
7565
8102
|
const logPath = agentLogPath();
|
|
7566
|
-
|
|
7567
|
-
|
|
8103
|
+
mkdirSync14(join17(homedir2(), "Library", "LaunchAgents"), { recursive: true });
|
|
8104
|
+
mkdirSync14(join17(homedir2(), ".beastmode", "logs"), { recursive: true });
|
|
7568
8105
|
const uid = process.getuid?.();
|
|
7569
|
-
if (
|
|
8106
|
+
if (existsSync19(plist)) {
|
|
7570
8107
|
spawnSync2("launchctl", ["bootout", `gui/${uid}`, plist], { stdio: "pipe" });
|
|
7571
8108
|
}
|
|
7572
8109
|
const resolvedFactory = factoryDir ?? findFactoryDir() ?? void 0;
|
|
7573
|
-
|
|
7574
|
-
|
|
8110
|
+
writeFileSync15(plist, buildPlist(intervalSeconds, resolvedFactory), "utf-8");
|
|
8111
|
+
writeFileSync15(logPath, "", { flag: "a" });
|
|
7575
8112
|
const result = spawnSync2("launchctl", ["bootstrap", `gui/${uid}`, plist], {
|
|
7576
8113
|
stdio: "pipe",
|
|
7577
8114
|
encoding: "utf-8"
|
|
@@ -7609,7 +8146,7 @@ function syncClaudeCredsOnce() {
|
|
|
7609
8146
|
function uninstallAgent() {
|
|
7610
8147
|
const plist = plistPath();
|
|
7611
8148
|
const uid = process.getuid?.();
|
|
7612
|
-
if (!
|
|
8149
|
+
if (!existsSync19(plist)) {
|
|
7613
8150
|
info("No LaunchAgent installed \u2014 nothing to remove.");
|
|
7614
8151
|
return;
|
|
7615
8152
|
}
|
|
@@ -7629,7 +8166,7 @@ function uninstallAgent() {
|
|
|
7629
8166
|
function showStatus() {
|
|
7630
8167
|
const plist = plistPath();
|
|
7631
8168
|
const uid = process.getuid?.();
|
|
7632
|
-
if (!
|
|
8169
|
+
if (!existsSync19(plist)) {
|
|
7633
8170
|
info("LaunchAgent not installed.");
|
|
7634
8171
|
info("Install with: beastmode sync-claude-creds --install");
|
|
7635
8172
|
return;
|
|
@@ -7653,14 +8190,14 @@ function showStatus() {
|
|
|
7653
8190
|
}
|
|
7654
8191
|
function syncClaudeCredsLinux() {
|
|
7655
8192
|
const credsPath = linuxCredsPath();
|
|
7656
|
-
if (!
|
|
8193
|
+
if (!existsSync19(credsPath)) {
|
|
7657
8194
|
return {
|
|
7658
8195
|
error: `${credsPath} not found \u2014 run \`claude login\` first`
|
|
7659
8196
|
};
|
|
7660
8197
|
}
|
|
7661
8198
|
let parsed;
|
|
7662
8199
|
try {
|
|
7663
|
-
const raw =
|
|
8200
|
+
const raw = readFileSync16(credsPath, "utf-8");
|
|
7664
8201
|
parsed = JSON.parse(raw);
|
|
7665
8202
|
} catch {
|
|
7666
8203
|
return {
|
|
@@ -7689,7 +8226,7 @@ function syncClaudeCredsLinux() {
|
|
|
7689
8226
|
);
|
|
7690
8227
|
}
|
|
7691
8228
|
try {
|
|
7692
|
-
const raw2 =
|
|
8229
|
+
const raw2 = readFileSync16(credsPath, "utf-8");
|
|
7693
8230
|
const parsed2 = JSON.parse(raw2);
|
|
7694
8231
|
if (parsed2.claudeAiOauth?.expiresAt) {
|
|
7695
8232
|
const newMinutes = (parsed2.claudeAiOauth.expiresAt - Date.now()) / 6e4;
|
|
@@ -7743,18 +8280,18 @@ function installAgentLinux(intervalSeconds, { throwOnError = false } = {}) {
|
|
|
7743
8280
|
process.exit(1);
|
|
7744
8281
|
}
|
|
7745
8282
|
const unitDir = systemdUserDir();
|
|
7746
|
-
const logDir =
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
const logPath =
|
|
8283
|
+
const logDir = join17(homedir2(), ".beastmode", "logs");
|
|
8284
|
+
mkdirSync14(unitDir, { recursive: true });
|
|
8285
|
+
mkdirSync14(logDir, { recursive: true });
|
|
8286
|
+
const logPath = join17(logDir, "sync-claude-creds.log");
|
|
7750
8287
|
const nodePath = process.execPath;
|
|
7751
8288
|
const cliEntry = process.argv[1];
|
|
7752
|
-
|
|
8289
|
+
writeFileSync15(
|
|
7753
8290
|
systemdServicePath(),
|
|
7754
8291
|
buildServiceUnit(nodePath, cliEntry, logPath),
|
|
7755
8292
|
"utf-8"
|
|
7756
8293
|
);
|
|
7757
|
-
|
|
8294
|
+
writeFileSync15(
|
|
7758
8295
|
systemdTimerPath(),
|
|
7759
8296
|
buildTimerUnit(intervalSeconds),
|
|
7760
8297
|
"utf-8"
|
|
@@ -7805,14 +8342,14 @@ function uninstallAgentLinux() {
|
|
|
7805
8342
|
const servicePath = systemdServicePath();
|
|
7806
8343
|
const timerPath = systemdTimerPath();
|
|
7807
8344
|
let removed = false;
|
|
7808
|
-
if (
|
|
8345
|
+
if (existsSync19(servicePath)) {
|
|
7809
8346
|
try {
|
|
7810
8347
|
unlinkSync4(servicePath);
|
|
7811
8348
|
removed = true;
|
|
7812
8349
|
} catch {
|
|
7813
8350
|
}
|
|
7814
8351
|
}
|
|
7815
|
-
if (
|
|
8352
|
+
if (existsSync19(timerPath)) {
|
|
7816
8353
|
try {
|
|
7817
8354
|
unlinkSync4(timerPath);
|
|
7818
8355
|
removed = true;
|
|
@@ -7832,7 +8369,7 @@ function statusAgentLinux() {
|
|
|
7832
8369
|
error(unavail);
|
|
7833
8370
|
return;
|
|
7834
8371
|
}
|
|
7835
|
-
if (!
|
|
8372
|
+
if (!existsSync19(systemdTimerPath())) {
|
|
7836
8373
|
info("Systemd timer not installed.");
|
|
7837
8374
|
info("Install with: beastmode sync-claude-creds --install");
|
|
7838
8375
|
return;
|
|
@@ -7991,26 +8528,26 @@ var init_sync_claude_creds = __esm({
|
|
|
7991
8528
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7992
8529
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7993
8530
|
import { z as z2 } from "zod";
|
|
7994
|
-
import { readFileSync as
|
|
7995
|
-
import { join as
|
|
8531
|
+
import { readFileSync as readFileSync29, writeFileSync as writeFileSync25, existsSync as existsSync32, readdirSync as readdirSync12 } from "fs";
|
|
8532
|
+
import { join as join30, resolve as resolve18, basename as basename6 } from "path";
|
|
7996
8533
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
7997
8534
|
function readJsonFile2(filePath) {
|
|
7998
|
-
if (!
|
|
8535
|
+
if (!existsSync32(filePath)) return null;
|
|
7999
8536
|
try {
|
|
8000
|
-
return JSON.parse(
|
|
8537
|
+
return JSON.parse(readFileSync29(filePath, "utf-8"));
|
|
8001
8538
|
} catch {
|
|
8002
8539
|
return null;
|
|
8003
8540
|
}
|
|
8004
8541
|
}
|
|
8005
8542
|
function getFactoryPath() {
|
|
8006
8543
|
const envPath = process.env.BEASTMODE_FACTORY_PATH;
|
|
8007
|
-
if (envPath &&
|
|
8544
|
+
if (envPath && existsSync32(join30(envPath, ".beastmode", "factory.json"))) {
|
|
8008
8545
|
return envPath;
|
|
8009
8546
|
}
|
|
8010
8547
|
let dir = process.cwd();
|
|
8011
8548
|
const root = resolve18("/");
|
|
8012
8549
|
while (dir !== root) {
|
|
8013
|
-
if (
|
|
8550
|
+
if (existsSync32(join30(dir, ".beastmode", "factory.json"))) {
|
|
8014
8551
|
return dir;
|
|
8015
8552
|
}
|
|
8016
8553
|
const parent = resolve18(dir, "..");
|
|
@@ -8022,17 +8559,17 @@ function getFactoryPath() {
|
|
|
8022
8559
|
);
|
|
8023
8560
|
}
|
|
8024
8561
|
function readFactoryStatus(factoryDir) {
|
|
8025
|
-
const bmDir =
|
|
8562
|
+
const bmDir = join30(factoryDir, ".beastmode");
|
|
8026
8563
|
const factoryIdentity = FactoryIdentitySchema.parse(
|
|
8027
|
-
JSON.parse(
|
|
8564
|
+
JSON.parse(readFileSync29(join30(bmDir, "factory.json"), "utf-8"))
|
|
8028
8565
|
);
|
|
8029
|
-
const projectsDir =
|
|
8030
|
-
const projectCount =
|
|
8031
|
-
const lockPath =
|
|
8566
|
+
const projectsDir = join30(bmDir, "projects");
|
|
8567
|
+
const projectCount = listProjectRecords(projectsDir).length;
|
|
8568
|
+
const lockPath = join30(bmDir, "extensions.lock");
|
|
8032
8569
|
let pluginNames = [];
|
|
8033
|
-
if (
|
|
8570
|
+
if (existsSync32(lockPath)) {
|
|
8034
8571
|
try {
|
|
8035
|
-
const lock = JSON.parse(
|
|
8572
|
+
const lock = JSON.parse(readFileSync29(lockPath, "utf-8"));
|
|
8036
8573
|
pluginNames = Object.keys(lock.plugins || {});
|
|
8037
8574
|
} catch {
|
|
8038
8575
|
}
|
|
@@ -8052,23 +8589,23 @@ function readFactoryStatus(factoryDir) {
|
|
|
8052
8589
|
skillCount = listSkills(factoryDir).length;
|
|
8053
8590
|
} catch {
|
|
8054
8591
|
}
|
|
8055
|
-
const runsDir =
|
|
8592
|
+
const runsDir = join30(factoryDir, "runs");
|
|
8056
8593
|
let runDirs = [];
|
|
8057
|
-
if (
|
|
8058
|
-
runDirs =
|
|
8594
|
+
if (existsSync32(runsDir)) {
|
|
8595
|
+
runDirs = readdirSync12(runsDir).filter((d) => {
|
|
8059
8596
|
try {
|
|
8060
|
-
return
|
|
8597
|
+
return readdirSync12(join30(runsDir, d)).length > 0;
|
|
8061
8598
|
} catch {
|
|
8062
8599
|
return false;
|
|
8063
8600
|
}
|
|
8064
8601
|
}).sort();
|
|
8065
8602
|
}
|
|
8066
|
-
const pidFile =
|
|
8603
|
+
const pidFile = join30(bmDir, "daemon.pid");
|
|
8067
8604
|
let daemonPid = null;
|
|
8068
8605
|
let pidAlive = false;
|
|
8069
|
-
if (
|
|
8606
|
+
if (existsSync32(pidFile)) {
|
|
8070
8607
|
try {
|
|
8071
|
-
daemonPid = parseInt(
|
|
8608
|
+
daemonPid = parseInt(readFileSync29(pidFile, "utf-8").trim(), 10);
|
|
8072
8609
|
process.kill(daemonPid, 0);
|
|
8073
8610
|
pidAlive = true;
|
|
8074
8611
|
} catch {
|
|
@@ -8089,18 +8626,18 @@ function readFactoryStatus(factoryDir) {
|
|
|
8089
8626
|
return collectStatus(input);
|
|
8090
8627
|
}
|
|
8091
8628
|
function readBoardItems(factoryDir) {
|
|
8092
|
-
const filePath =
|
|
8093
|
-
if (!
|
|
8629
|
+
const filePath = join30(factoryDir, ".beastmode", "board.json");
|
|
8630
|
+
if (!existsSync32(filePath)) return [];
|
|
8094
8631
|
try {
|
|
8095
|
-
const raw = JSON.parse(
|
|
8632
|
+
const raw = JSON.parse(readFileSync29(filePath, "utf-8"));
|
|
8096
8633
|
return Array.isArray(raw.items) ? raw.items : [];
|
|
8097
8634
|
} catch {
|
|
8098
8635
|
return [];
|
|
8099
8636
|
}
|
|
8100
8637
|
}
|
|
8101
8638
|
function writeBoardItems(factoryDir, items) {
|
|
8102
|
-
const filePath =
|
|
8103
|
-
|
|
8639
|
+
const filePath = join30(factoryDir, ".beastmode", "board.json");
|
|
8640
|
+
writeFileSync25(filePath, JSON.stringify({ items }, null, 2) + "\n", "utf-8");
|
|
8104
8641
|
}
|
|
8105
8642
|
function createMcpServer() {
|
|
8106
8643
|
const server = new McpServer(
|
|
@@ -8123,8 +8660,8 @@ function createMcpServer() {
|
|
|
8123
8660
|
{ key_path: z2.string().describe("Dot-notation key path") },
|
|
8124
8661
|
async ({ key_path }) => {
|
|
8125
8662
|
const factoryDir = getFactoryPath();
|
|
8126
|
-
const configPath =
|
|
8127
|
-
const config =
|
|
8663
|
+
const configPath = join30(factoryDir, ".beastmode", "config.json");
|
|
8664
|
+
const config = existsSync32(configPath) ? JSON.parse(readFileSync29(configPath, "utf-8")) : generateDefaults();
|
|
8128
8665
|
try {
|
|
8129
8666
|
const value = configGet(config, key_path);
|
|
8130
8667
|
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
@@ -8142,11 +8679,11 @@ function createMcpServer() {
|
|
|
8142
8679
|
},
|
|
8143
8680
|
async ({ key_path, value }) => {
|
|
8144
8681
|
const factoryDir = getFactoryPath();
|
|
8145
|
-
const configPath =
|
|
8146
|
-
const config =
|
|
8682
|
+
const configPath = join30(factoryDir, ".beastmode", "config.json");
|
|
8683
|
+
const config = existsSync32(configPath) ? JSON.parse(readFileSync29(configPath, "utf-8")) : generateDefaults();
|
|
8147
8684
|
const coerced = coerceValue(value);
|
|
8148
8685
|
const updated = configSet(config, key_path, coerced);
|
|
8149
|
-
|
|
8686
|
+
writeFileSync25(configPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
8150
8687
|
return { content: [{ type: "text", text: `Set ${key_path} = ${JSON.stringify(coerced)}` }] };
|
|
8151
8688
|
}
|
|
8152
8689
|
);
|
|
@@ -8180,14 +8717,14 @@ function createMcpServer() {
|
|
|
8180
8717
|
{},
|
|
8181
8718
|
async () => {
|
|
8182
8719
|
const factoryDir = getFactoryPath();
|
|
8183
|
-
const runsDir =
|
|
8184
|
-
if (!
|
|
8720
|
+
const runsDir = join30(factoryDir, "runs");
|
|
8721
|
+
if (!existsSync32(runsDir)) {
|
|
8185
8722
|
return { content: [{ type: "text", text: "No runs directory found." }] };
|
|
8186
8723
|
}
|
|
8187
|
-
const runDirs =
|
|
8724
|
+
const runDirs = readdirSync12(runsDir).sort().reverse();
|
|
8188
8725
|
const activeRuns = [];
|
|
8189
8726
|
for (const id of runDirs.slice(0, 10)) {
|
|
8190
|
-
const cp = readJsonFile2(
|
|
8727
|
+
const cp = readJsonFile2(join30(runsDir, id, "checkpoint.json"));
|
|
8191
8728
|
if (cp) {
|
|
8192
8729
|
activeRuns.push({ id, checkpoint: cp });
|
|
8193
8730
|
}
|
|
@@ -8201,17 +8738,17 @@ function createMcpServer() {
|
|
|
8201
8738
|
{ run_id: z2.string().describe("Run ID (directory name)") },
|
|
8202
8739
|
async ({ run_id }) => {
|
|
8203
8740
|
const factoryDir = getFactoryPath();
|
|
8204
|
-
const runDir =
|
|
8205
|
-
if (!
|
|
8741
|
+
const runDir = join30(factoryDir, "runs", run_id);
|
|
8742
|
+
if (!existsSync32(runDir)) {
|
|
8206
8743
|
return { content: [{ type: "text", text: `Run not found: ${run_id}` }], isError: true };
|
|
8207
8744
|
}
|
|
8208
|
-
const manifest = readJsonFile2(
|
|
8209
|
-
const checkpoint = readJsonFile2(
|
|
8210
|
-
const iterationsDir =
|
|
8745
|
+
const manifest = readJsonFile2(join30(runDir, "manifest.json"));
|
|
8746
|
+
const checkpoint = readJsonFile2(join30(runDir, "checkpoint.json"));
|
|
8747
|
+
const iterationsDir = join30(runDir, "iterations");
|
|
8211
8748
|
const iterations = [];
|
|
8212
|
-
if (
|
|
8213
|
-
for (const dir of
|
|
8214
|
-
const satisfaction = readJsonFile2(
|
|
8749
|
+
if (existsSync32(iterationsDir)) {
|
|
8750
|
+
for (const dir of readdirSync12(iterationsDir).sort()) {
|
|
8751
|
+
const satisfaction = readJsonFile2(join30(iterationsDir, dir, "satisfaction.json"));
|
|
8215
8752
|
iterations.push({ number: parseInt(dir, 10), satisfaction });
|
|
8216
8753
|
}
|
|
8217
8754
|
}
|
|
@@ -8257,12 +8794,12 @@ function createMcpServer() {
|
|
|
8257
8794
|
{},
|
|
8258
8795
|
async () => {
|
|
8259
8796
|
const factoryDir = getFactoryPath();
|
|
8260
|
-
const bmDir =
|
|
8797
|
+
const bmDir = join30(factoryDir, ".beastmode");
|
|
8261
8798
|
let plugins = {};
|
|
8262
|
-
const lockPath =
|
|
8263
|
-
if (
|
|
8799
|
+
const lockPath = join30(bmDir, "extensions.lock");
|
|
8800
|
+
if (existsSync32(lockPath)) {
|
|
8264
8801
|
try {
|
|
8265
|
-
const lock = JSON.parse(
|
|
8802
|
+
const lock = JSON.parse(readFileSync29(lockPath, "utf-8"));
|
|
8266
8803
|
plugins = lock.plugins || {};
|
|
8267
8804
|
} catch {
|
|
8268
8805
|
}
|
|
@@ -8296,17 +8833,8 @@ function createMcpServer() {
|
|
|
8296
8833
|
{},
|
|
8297
8834
|
async () => {
|
|
8298
8835
|
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);
|
|
8836
|
+
const projectsDir = join30(factoryDir, ".beastmode", "projects");
|
|
8837
|
+
const projects = listProjectRecords(projectsDir);
|
|
8310
8838
|
return { content: [{ type: "text", text: JSON.stringify(projects, null, 2) }] };
|
|
8311
8839
|
}
|
|
8312
8840
|
);
|
|
@@ -8317,34 +8845,25 @@ function createMcpServer() {
|
|
|
8317
8845
|
async ({ path: projectPath }) => {
|
|
8318
8846
|
const factoryDir = getFactoryPath();
|
|
8319
8847
|
const resolvedPath = resolve18(projectPath);
|
|
8320
|
-
if (!
|
|
8848
|
+
if (!existsSync32(resolvedPath)) {
|
|
8321
8849
|
return { content: [{ type: "text", text: `Directory not found: ${resolvedPath}` }], isError: true };
|
|
8322
8850
|
}
|
|
8323
|
-
const projectName =
|
|
8851
|
+
const projectName = basename6(resolvedPath);
|
|
8324
8852
|
const stack = detectStack(resolvedPath);
|
|
8325
|
-
const
|
|
8853
|
+
const projectsDir = join30(factoryDir, ".beastmode", "projects");
|
|
8854
|
+
let verifyPort = 3001;
|
|
8855
|
+
for (const existing of listProjectRecords(projectsDir)) {
|
|
8856
|
+
const port = existing.deploy?.verify_port;
|
|
8857
|
+
if (typeof port === "number" && port >= verifyPort) verifyPort = port + 1;
|
|
8858
|
+
}
|
|
8859
|
+
const record = createProjectRecord({
|
|
8326
8860
|
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) }] };
|
|
8861
|
+
resolvedPath,
|
|
8862
|
+
gitRemote: stack.git_remote || void 0,
|
|
8863
|
+
verifyPort
|
|
8864
|
+
});
|
|
8865
|
+
writeProjectRecord(projectsDir, projectName, record);
|
|
8866
|
+
return { content: [{ type: "text", text: JSON.stringify(record, null, 2) }] };
|
|
8348
8867
|
}
|
|
8349
8868
|
);
|
|
8350
8869
|
server.tool(
|
|
@@ -8447,17 +8966,17 @@ init_engine();
|
|
|
8447
8966
|
init_file_writer();
|
|
8448
8967
|
import { Command as Command3 } from "commander";
|
|
8449
8968
|
import inquirer from "inquirer";
|
|
8450
|
-
import { resolve as resolve6, basename as
|
|
8451
|
-
import { existsSync as
|
|
8969
|
+
import { resolve as resolve6, basename as basename5, join as join18 } from "path";
|
|
8970
|
+
import { existsSync as existsSync20, writeFileSync as writeFileSync16, mkdirSync as mkdirSync15, readFileSync as readFileSync17 } from "fs";
|
|
8452
8971
|
|
|
8453
8972
|
// src/cli/utils/docker.ts
|
|
8454
|
-
import { existsSync as
|
|
8455
|
-
import { join as
|
|
8973
|
+
import { existsSync as existsSync10 } from "fs";
|
|
8974
|
+
import { join as join9 } from "path";
|
|
8456
8975
|
import { execSync } from "child_process";
|
|
8457
8976
|
var GHCR_IMAGE_PREFIX = "ghcr.io/develeap/beastmode";
|
|
8458
8977
|
function findComposeFile(dir) {
|
|
8459
|
-
const path =
|
|
8460
|
-
return
|
|
8978
|
+
const path = join9(dir, "docker-compose.yml");
|
|
8979
|
+
return existsSync10(path) ? path : null;
|
|
8461
8980
|
}
|
|
8462
8981
|
function requireComposeFile(dir) {
|
|
8463
8982
|
const path = findComposeFile(dir);
|
|
@@ -8520,7 +9039,7 @@ function loginToGhcr(token) {
|
|
|
8520
9039
|
}
|
|
8521
9040
|
}
|
|
8522
9041
|
function seedConfigFromImage(targetDir, tag) {
|
|
8523
|
-
const configDir =
|
|
9042
|
+
const configDir = join9(targetDir, "config");
|
|
8524
9043
|
const containerName = "bm-config-seed";
|
|
8525
9044
|
try {
|
|
8526
9045
|
try {
|
|
@@ -8700,15 +9219,15 @@ function collect(val, acc) {
|
|
|
8700
9219
|
return acc;
|
|
8701
9220
|
}
|
|
8702
9221
|
function readSecretFile(filePath) {
|
|
8703
|
-
return
|
|
9222
|
+
return readFileSync17(resolve6(filePath), "utf-8").trim();
|
|
8704
9223
|
}
|
|
8705
9224
|
function isSourceRepo(dir) {
|
|
8706
|
-
return
|
|
9225
|
+
return existsSync20(resolve6(dir, "daemon")) && existsSync20(resolve6(dir, "board")) && existsSync20(resolve6(dir, "cli"));
|
|
8707
9226
|
}
|
|
8708
9227
|
async function runInit(name, opts) {
|
|
8709
9228
|
if (opts.ui) {
|
|
8710
9229
|
const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
8711
|
-
const factoryName2 = name ||
|
|
9230
|
+
const factoryName2 = name || basename5(resolve6("."));
|
|
8712
9231
|
const projectPath2 = opts.project ? resolve6(opts.project) : void 0;
|
|
8713
9232
|
info("Starting init wizard...");
|
|
8714
9233
|
const uiServer = await startServer2({
|
|
@@ -8741,8 +9260,8 @@ async function runInit(name, opts) {
|
|
|
8741
9260
|
const totalSteps = 5;
|
|
8742
9261
|
header("BeastMode \u2014 Dark Factory Init");
|
|
8743
9262
|
info("Creating your Custom Dark Factory\n");
|
|
8744
|
-
const factoryName = name ||
|
|
8745
|
-
if (
|
|
9263
|
+
const factoryName = name || basename5(resolve6("."));
|
|
9264
|
+
if (existsSync20(factoryName) && existsSync20(resolve6(factoryName, ".beastmode"))) {
|
|
8746
9265
|
throw new Error(`Factory already exists at ./${factoryName}. Use 'beastmode config' to modify.`);
|
|
8747
9266
|
}
|
|
8748
9267
|
if (opts.from) {
|
|
@@ -8757,7 +9276,7 @@ async function runInit(name, opts) {
|
|
|
8757
9276
|
}
|
|
8758
9277
|
const templateProjectPath = resolve6(opts.project);
|
|
8759
9278
|
const templateStack = detectStack(templateProjectPath);
|
|
8760
|
-
const templateProjectName =
|
|
9279
|
+
const templateProjectName = basename5(templateProjectPath);
|
|
8761
9280
|
const actions2 = scaffoldFactory(factoryName, template.config, {
|
|
8762
9281
|
name: templateProjectName,
|
|
8763
9282
|
repo: templateStack.git_remote || void 0,
|
|
@@ -8792,7 +9311,7 @@ async function runInit(name, opts) {
|
|
|
8792
9311
|
name: "project",
|
|
8793
9312
|
message: "Path to your project:",
|
|
8794
9313
|
default: ".",
|
|
8795
|
-
validate: (input) =>
|
|
9314
|
+
validate: (input) => existsSync20(resolve6(input)) || `Directory not found: ${input}`
|
|
8796
9315
|
}
|
|
8797
9316
|
]);
|
|
8798
9317
|
projectPath = resolve6(answer.project);
|
|
@@ -8952,7 +9471,7 @@ async function runInit(name, opts) {
|
|
|
8952
9471
|
success("Board UI password set");
|
|
8953
9472
|
}
|
|
8954
9473
|
step(5, totalSteps, "Boot");
|
|
8955
|
-
const projectName =
|
|
9474
|
+
const projectName = basename5(projectPath);
|
|
8956
9475
|
const actions = scaffoldFactory(factoryName, config, {
|
|
8957
9476
|
name: projectName,
|
|
8958
9477
|
repo: stack.git_remote || void 0,
|
|
@@ -9098,14 +9617,14 @@ async function runImageModeInit(name, opts) {
|
|
|
9098
9617
|
let projectPath;
|
|
9099
9618
|
if (opts.project) {
|
|
9100
9619
|
projectPath = resolve6(opts.project);
|
|
9101
|
-
if (!
|
|
9620
|
+
if (!existsSync20(projectPath)) {
|
|
9102
9621
|
error(`--project path does not exist: ${projectPath}`);
|
|
9103
9622
|
process.exit(1);
|
|
9104
9623
|
}
|
|
9105
9624
|
success(`Project path: ${projectPath}`);
|
|
9106
9625
|
} else if (opts.yes) {
|
|
9107
9626
|
const cwd = resolve6(".");
|
|
9108
|
-
if (!
|
|
9627
|
+
if (!existsSync20(join18(cwd, ".git"))) {
|
|
9109
9628
|
error(
|
|
9110
9629
|
"--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
9630
|
);
|
|
@@ -9122,8 +9641,8 @@ async function runImageModeInit(name, opts) {
|
|
|
9122
9641
|
default: ".",
|
|
9123
9642
|
validate: (input) => {
|
|
9124
9643
|
const p = resolve6(input);
|
|
9125
|
-
if (!
|
|
9126
|
-
if (!
|
|
9644
|
+
if (!existsSync20(p)) return `Directory not found: ${p}`;
|
|
9645
|
+
if (!existsSync20(join18(p, ".git"))) {
|
|
9127
9646
|
return `Not a git repo: ${p} (run 'git init' first, or pick a different path)`;
|
|
9128
9647
|
}
|
|
9129
9648
|
return true;
|
|
@@ -9162,9 +9681,9 @@ async function runImageModeInit(name, opts) {
|
|
|
9162
9681
|
success("Authenticated to ghcr.io");
|
|
9163
9682
|
}
|
|
9164
9683
|
step(3, totalSteps, "Generate");
|
|
9165
|
-
|
|
9684
|
+
mkdirSync15(targetDir, { recursive: true });
|
|
9166
9685
|
const composeContent = generateComposeYaml("latest");
|
|
9167
|
-
|
|
9686
|
+
writeFileSync16(join18(targetDir, "docker-compose.yml"), composeContent, "utf-8");
|
|
9168
9687
|
success("docker-compose.yml");
|
|
9169
9688
|
const envLines = [
|
|
9170
9689
|
"# BeastMode environment \u2014 DO NOT COMMIT",
|
|
@@ -9204,10 +9723,10 @@ async function runImageModeInit(name, opts) {
|
|
|
9204
9723
|
"# PROJECT_REPO=owner/repo",
|
|
9205
9724
|
""
|
|
9206
9725
|
];
|
|
9207
|
-
|
|
9726
|
+
writeFileSync16(join18(targetDir, ".env"), envLines.join("\n"), "utf-8");
|
|
9208
9727
|
success(`.env (PROJECT_DIR=${projectPath}, two-PAT model)`);
|
|
9209
9728
|
for (const dir of ["data", "runs", "daemon/logs", ".beastmode", "config"]) {
|
|
9210
|
-
|
|
9729
|
+
mkdirSync15(join18(targetDir, dir), { recursive: true });
|
|
9211
9730
|
}
|
|
9212
9731
|
step(4, totalSteps, "Pull Images");
|
|
9213
9732
|
try {
|
|
@@ -9296,20 +9815,20 @@ async function runImageModeInit(name, opts) {
|
|
|
9296
9815
|
init_export_adapter();
|
|
9297
9816
|
init_display();
|
|
9298
9817
|
import { Command as Command4 } from "commander";
|
|
9299
|
-
import { readFileSync as
|
|
9300
|
-
import { resolve as resolve7, join as
|
|
9818
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync17, existsSync as existsSync21, readdirSync as readdirSync9 } from "fs";
|
|
9819
|
+
import { resolve as resolve7, join as join19 } from "path";
|
|
9301
9820
|
function exportFactory(factoryDir, outputPath) {
|
|
9302
|
-
const bmDir =
|
|
9303
|
-
const configPath =
|
|
9304
|
-
if (!
|
|
9821
|
+
const bmDir = join19(factoryDir, ".beastmode");
|
|
9822
|
+
const configPath = join19(bmDir, "config.json");
|
|
9823
|
+
if (!existsSync21(configPath)) {
|
|
9305
9824
|
throw new Error(`No factory found at ${factoryDir}`);
|
|
9306
9825
|
}
|
|
9307
|
-
const config = JSON.parse(
|
|
9308
|
-
const identity = JSON.parse(
|
|
9826
|
+
const config = JSON.parse(readFileSync18(configPath, "utf-8"));
|
|
9827
|
+
const identity = JSON.parse(readFileSync18(join19(bmDir, "factory.json"), "utf-8"));
|
|
9309
9828
|
let plugins = [];
|
|
9310
|
-
const lockPath =
|
|
9311
|
-
if (
|
|
9312
|
-
const lock = JSON.parse(
|
|
9829
|
+
const lockPath = join19(bmDir, "extensions.lock");
|
|
9830
|
+
if (existsSync21(lockPath)) {
|
|
9831
|
+
const lock = JSON.parse(readFileSync18(lockPath, "utf-8"));
|
|
9313
9832
|
plugins = Object.keys(lock.plugins || {});
|
|
9314
9833
|
}
|
|
9315
9834
|
const template = {
|
|
@@ -9318,7 +9837,7 @@ function exportFactory(factoryDir, outputPath) {
|
|
|
9318
9837
|
config,
|
|
9319
9838
|
plugins
|
|
9320
9839
|
};
|
|
9321
|
-
|
|
9840
|
+
writeFileSync17(outputPath, JSON.stringify(template, null, 2) + "\n", "utf-8");
|
|
9322
9841
|
}
|
|
9323
9842
|
function findRunDir(runId) {
|
|
9324
9843
|
const candidates = [
|
|
@@ -9326,7 +9845,7 @@ function findRunDir(runId) {
|
|
|
9326
9845
|
resolve7(".", runId)
|
|
9327
9846
|
];
|
|
9328
9847
|
for (const candidate of candidates) {
|
|
9329
|
-
if (
|
|
9848
|
+
if (existsSync21(candidate)) {
|
|
9330
9849
|
return candidate;
|
|
9331
9850
|
}
|
|
9332
9851
|
}
|
|
@@ -9340,23 +9859,23 @@ function createArtifactExportCommand(artifact) {
|
|
|
9340
9859
|
const runDir = findRunDir(opts.run);
|
|
9341
9860
|
let sourceContent;
|
|
9342
9861
|
if (artifact === "scenarios") {
|
|
9343
|
-
const scenariosDir =
|
|
9344
|
-
if (!
|
|
9862
|
+
const scenariosDir = join19(runDir, "scenarios");
|
|
9863
|
+
if (!existsSync21(scenariosDir)) {
|
|
9345
9864
|
throw new Error(`No scenarios directory found in run ${opts.run}`);
|
|
9346
9865
|
}
|
|
9347
|
-
const files =
|
|
9348
|
-
sourceContent = files.map((f) =>
|
|
9866
|
+
const files = readdirSync9(scenariosDir).filter((f) => f.endsWith(".md")).sort();
|
|
9867
|
+
sourceContent = files.map((f) => readFileSync18(join19(scenariosDir, f), "utf-8")).join("\n\n---\n\n");
|
|
9349
9868
|
} else {
|
|
9350
9869
|
const artifactFile = artifact === "nlspec" ? "nlspec.md" : "plan.md";
|
|
9351
|
-
const artifactPath =
|
|
9352
|
-
if (!
|
|
9870
|
+
const artifactPath = join19(runDir, artifactFile);
|
|
9871
|
+
if (!existsSync21(artifactPath)) {
|
|
9353
9872
|
throw new Error(`${artifactFile} not found in run ${opts.run}`);
|
|
9354
9873
|
}
|
|
9355
|
-
sourceContent =
|
|
9874
|
+
sourceContent = readFileSync18(artifactPath, "utf-8");
|
|
9356
9875
|
}
|
|
9357
9876
|
const result = runExportAdapter(opts.adapter, sourceContent);
|
|
9358
9877
|
if (opts.output) {
|
|
9359
|
-
|
|
9878
|
+
writeFileSync17(resolve7(opts.output), result, "utf-8");
|
|
9360
9879
|
header(`Exported ${artifact}`);
|
|
9361
9880
|
success(`Written to: ${opts.output}`);
|
|
9362
9881
|
} else {
|
|
@@ -9580,15 +10099,15 @@ init_presets();
|
|
|
9580
10099
|
init_display();
|
|
9581
10100
|
import { Command as Command8 } from "commander";
|
|
9582
10101
|
import { resolve as resolve11 } from "path";
|
|
9583
|
-
import { readFileSync as
|
|
9584
|
-
import { join as
|
|
10102
|
+
import { readFileSync as readFileSync19, existsSync as existsSync22 } from "fs";
|
|
10103
|
+
import { join as join20 } from "path";
|
|
9585
10104
|
function listPluginsAction(factoryDir) {
|
|
9586
|
-
const lockPath =
|
|
9587
|
-
if (!
|
|
10105
|
+
const lockPath = join20(factoryDir, ".beastmode", "extensions.lock");
|
|
10106
|
+
if (!existsSync22(lockPath)) {
|
|
9588
10107
|
console.log(" No plugins installed (extensions.lock not found).");
|
|
9589
10108
|
return;
|
|
9590
10109
|
}
|
|
9591
|
-
const lock = JSON.parse(
|
|
10110
|
+
const lock = JSON.parse(readFileSync19(lockPath, "utf-8"));
|
|
9592
10111
|
const plugins = lock.plugins || {};
|
|
9593
10112
|
const names = Object.keys(plugins);
|
|
9594
10113
|
if (names.length === 0) {
|
|
@@ -9687,31 +10206,31 @@ var listCommand = new Command8("list").description("List installed extensions an
|
|
|
9687
10206
|
init_import_adapter();
|
|
9688
10207
|
init_display();
|
|
9689
10208
|
import { Command as Command9 } from "commander";
|
|
9690
|
-
import { readFileSync as
|
|
9691
|
-
import { resolve as resolve12, join as
|
|
10209
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync18, mkdirSync as mkdirSync16, existsSync as existsSync23 } from "fs";
|
|
10210
|
+
import { resolve as resolve12, join as join21 } from "path";
|
|
9692
10211
|
var VALID_ARTIFACTS = ["nlspec", "plan", "scenarios"];
|
|
9693
10212
|
function createArtifactCommand(artifact) {
|
|
9694
10213
|
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
10214
|
try {
|
|
9696
10215
|
const sourcePath = resolve12(opts.from);
|
|
9697
|
-
if (!
|
|
10216
|
+
if (!existsSync23(sourcePath)) {
|
|
9698
10217
|
throw new Error(`Source file not found: ${sourcePath}`);
|
|
9699
10218
|
}
|
|
9700
|
-
const sourceContent =
|
|
10219
|
+
const sourceContent = readFileSync20(sourcePath, "utf-8");
|
|
9701
10220
|
const result = runImportAdapter(opts.adapter, sourceContent);
|
|
9702
10221
|
if (opts.output) {
|
|
9703
10222
|
const outputPath = resolve12(opts.output);
|
|
9704
10223
|
if (artifact === "scenarios" && opts.adapter.endsWith(":test-cases")) {
|
|
9705
10224
|
const scenarios = JSON.parse(result);
|
|
9706
|
-
|
|
10225
|
+
mkdirSync16(outputPath, { recursive: true });
|
|
9707
10226
|
for (const scenario of scenarios) {
|
|
9708
|
-
const filePath =
|
|
9709
|
-
|
|
10227
|
+
const filePath = join21(outputPath, `${scenario.name}.md`);
|
|
10228
|
+
writeFileSync18(filePath, scenario.content, "utf-8");
|
|
9710
10229
|
success(` ${scenario.name}.md`);
|
|
9711
10230
|
}
|
|
9712
10231
|
header(`Imported ${scenarios.length} scenarios to ${opts.output}`);
|
|
9713
10232
|
} else {
|
|
9714
|
-
|
|
10233
|
+
writeFileSync18(outputPath, result, "utf-8");
|
|
9715
10234
|
header(`Imported ${artifact}`);
|
|
9716
10235
|
success(`Written to: ${opts.output}`);
|
|
9717
10236
|
}
|
|
@@ -9732,54 +10251,55 @@ for (const artifact of VALID_ARTIFACTS) {
|
|
|
9732
10251
|
// src/cli/commands/status.ts
|
|
9733
10252
|
init_status_checker();
|
|
9734
10253
|
init_schemas();
|
|
10254
|
+
init_project_record();
|
|
9735
10255
|
init_display();
|
|
9736
10256
|
import { Command as Command10 } from "commander";
|
|
9737
|
-
import { existsSync as
|
|
10257
|
+
import { existsSync as existsSync24, readFileSync as readFileSync21, readdirSync as readdirSync10 } from "fs";
|
|
9738
10258
|
import { execSync as execSync6 } from "child_process";
|
|
9739
|
-
import { join as
|
|
10259
|
+
import { join as join22, resolve as resolve13 } from "path";
|
|
9740
10260
|
function statusAction(factoryDir, opts) {
|
|
9741
|
-
const bmDir =
|
|
9742
|
-
if (!
|
|
10261
|
+
const bmDir = join22(factoryDir, ".beastmode");
|
|
10262
|
+
if (!existsSync24(bmDir)) {
|
|
9743
10263
|
throw new Error(`No factory found at ${factoryDir}`);
|
|
9744
10264
|
}
|
|
9745
|
-
const factoryJsonPath =
|
|
9746
|
-
const rawIdentity = JSON.parse(
|
|
10265
|
+
const factoryJsonPath = join22(bmDir, "factory.json");
|
|
10266
|
+
const rawIdentity = JSON.parse(readFileSync21(factoryJsonPath, "utf-8"));
|
|
9747
10267
|
const factoryIdentity = FactoryIdentitySchema.parse(rawIdentity);
|
|
9748
|
-
const projectsDir =
|
|
9749
|
-
const projectCount =
|
|
9750
|
-
const pluginsDir =
|
|
9751
|
-
const pluginNames =
|
|
9752
|
-
const mcpPath =
|
|
10268
|
+
const projectsDir = join22(bmDir, "projects");
|
|
10269
|
+
const projectCount = listProjectRecords(projectsDir).length;
|
|
10270
|
+
const pluginsDir = join22(bmDir, "plugins");
|
|
10271
|
+
const pluginNames = existsSync24(pluginsDir) ? readdirSync10(pluginsDir) : [];
|
|
10272
|
+
const mcpPath = join22(bmDir, "mcp-servers.json");
|
|
9753
10273
|
let mcpServers = {};
|
|
9754
|
-
if (
|
|
10274
|
+
if (existsSync24(mcpPath)) {
|
|
9755
10275
|
try {
|
|
9756
|
-
const raw = JSON.parse(
|
|
10276
|
+
const raw = JSON.parse(readFileSync21(mcpPath, "utf-8"));
|
|
9757
10277
|
mcpServers = raw.servers || {};
|
|
9758
10278
|
} catch {
|
|
9759
10279
|
}
|
|
9760
10280
|
}
|
|
9761
|
-
const hooksPath =
|
|
10281
|
+
const hooksPath = join22(bmDir, "hooks.json");
|
|
9762
10282
|
let hooks = {};
|
|
9763
|
-
if (
|
|
10283
|
+
if (existsSync24(hooksPath)) {
|
|
9764
10284
|
try {
|
|
9765
|
-
const raw = JSON.parse(
|
|
10285
|
+
const raw = JSON.parse(readFileSync21(hooksPath, "utf-8"));
|
|
9766
10286
|
hooks = raw.hooks || {};
|
|
9767
10287
|
} catch {
|
|
9768
10288
|
}
|
|
9769
10289
|
}
|
|
9770
|
-
const skillsDir =
|
|
9771
|
-
const skillCount =
|
|
9772
|
-
const runsDir =
|
|
10290
|
+
const skillsDir = join22(bmDir, "skills");
|
|
10291
|
+
const skillCount = existsSync24(skillsDir) ? readdirSync10(skillsDir).length : 0;
|
|
10292
|
+
const runsDir = join22(factoryDir, "runs");
|
|
9773
10293
|
let runDirs = [];
|
|
9774
|
-
if (
|
|
9775
|
-
runDirs =
|
|
10294
|
+
if (existsSync24(runsDir)) {
|
|
10295
|
+
runDirs = readdirSync10(runsDir).filter((d) => d.startsWith("run-")).sort();
|
|
9776
10296
|
}
|
|
9777
|
-
const pidPath =
|
|
10297
|
+
const pidPath = join22(bmDir, "daemon.pid");
|
|
9778
10298
|
let daemonPid = null;
|
|
9779
10299
|
let pidAlive = false;
|
|
9780
|
-
if (
|
|
10300
|
+
if (existsSync24(pidPath)) {
|
|
9781
10301
|
try {
|
|
9782
|
-
daemonPid = parseInt(
|
|
10302
|
+
daemonPid = parseInt(readFileSync21(pidPath, "utf-8").trim(), 10);
|
|
9783
10303
|
process.kill(daemonPid, 0);
|
|
9784
10304
|
pidAlive = true;
|
|
9785
10305
|
} catch {
|
|
@@ -9853,19 +10373,19 @@ var statusCommand = new Command10("status").description("Show factory status ove
|
|
|
9853
10373
|
init_config_manager();
|
|
9854
10374
|
init_display();
|
|
9855
10375
|
import { Command as Command11 } from "commander";
|
|
9856
|
-
import { existsSync as
|
|
9857
|
-
import { join as
|
|
10376
|
+
import { existsSync as existsSync25, readFileSync as readFileSync22, writeFileSync as writeFileSync19 } from "fs";
|
|
10377
|
+
import { join as join23, resolve as resolve14 } from "path";
|
|
9858
10378
|
import { execSync as execSync7 } from "child_process";
|
|
9859
10379
|
function readConfig2(factoryDir) {
|
|
9860
|
-
const configPath =
|
|
9861
|
-
if (!
|
|
10380
|
+
const configPath = join23(factoryDir, ".beastmode", "config.json");
|
|
10381
|
+
if (!existsSync25(configPath)) {
|
|
9862
10382
|
throw new Error("No config.json found. Run beastmode init first.");
|
|
9863
10383
|
}
|
|
9864
|
-
return JSON.parse(
|
|
10384
|
+
return JSON.parse(readFileSync22(configPath, "utf-8"));
|
|
9865
10385
|
}
|
|
9866
10386
|
function writeConfig2(factoryDir, config) {
|
|
9867
|
-
const configPath =
|
|
9868
|
-
|
|
10387
|
+
const configPath = join23(factoryDir, ".beastmode", "config.json");
|
|
10388
|
+
writeFileSync19(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
9869
10389
|
}
|
|
9870
10390
|
function configGetAction(factoryDir, key) {
|
|
9871
10391
|
const config = readConfig2(factoryDir);
|
|
@@ -9908,8 +10428,8 @@ var configSetCmd = new Command11("set").description("Set a config value by dot-n
|
|
|
9908
10428
|
});
|
|
9909
10429
|
var configEditCmd = new Command11("edit").description("Open config.json in $EDITOR").action(() => {
|
|
9910
10430
|
const factoryDir = resolve14(".");
|
|
9911
|
-
const configPath =
|
|
9912
|
-
if (!
|
|
10431
|
+
const configPath = join23(factoryDir, ".beastmode", "config.json");
|
|
10432
|
+
if (!existsSync25(configPath)) {
|
|
9913
10433
|
error("No config.json found. Run beastmode init first.");
|
|
9914
10434
|
process.exit(1);
|
|
9915
10435
|
}
|
|
@@ -9939,13 +10459,14 @@ var configCommand = new Command11("config").description("Manage factory configur
|
|
|
9939
10459
|
|
|
9940
10460
|
// src/cli/commands/doctor.ts
|
|
9941
10461
|
init_doctor();
|
|
10462
|
+
init_engine();
|
|
9942
10463
|
init_schemas();
|
|
9943
10464
|
init_version();
|
|
9944
10465
|
init_plugin_resolver();
|
|
9945
10466
|
init_display();
|
|
9946
10467
|
import { Command as Command12 } from "commander";
|
|
9947
|
-
import { existsSync as
|
|
9948
|
-
import { join as
|
|
10468
|
+
import { existsSync as existsSync26, readFileSync as readFileSync23, readdirSync as readdirSync11 } from "fs";
|
|
10469
|
+
import { join as join24, resolve as resolve15 } from "path";
|
|
9949
10470
|
import { execSync as execSync8, spawnSync as spawnSync3 } from "child_process";
|
|
9950
10471
|
import { homedir as homedir3, platform as platform2 } from "os";
|
|
9951
10472
|
import * as http3 from "http";
|
|
@@ -9986,8 +10507,8 @@ async function checkClaudeAuth(key) {
|
|
|
9986
10507
|
});
|
|
9987
10508
|
}
|
|
9988
10509
|
if (claudeInstalled && platform2() === "darwin" && !key) {
|
|
9989
|
-
const credsPath =
|
|
9990
|
-
if (!
|
|
10510
|
+
const credsPath = join24(homedir3(), ".claude", ".credentials.json");
|
|
10511
|
+
if (!existsSync26(credsPath)) {
|
|
9991
10512
|
results.push({
|
|
9992
10513
|
label: "Claude creds (Docker)",
|
|
9993
10514
|
status: "warn",
|
|
@@ -10089,16 +10610,16 @@ async function checkProjectGithubToken(env, factoryDir) {
|
|
|
10089
10610
|
}
|
|
10090
10611
|
let ownerRepo = null;
|
|
10091
10612
|
if (factoryDir) {
|
|
10092
|
-
const envPath =
|
|
10093
|
-
if (
|
|
10613
|
+
const envPath = join24(factoryDir, ".env");
|
|
10614
|
+
if (existsSync26(envPath)) {
|
|
10094
10615
|
try {
|
|
10095
|
-
const content =
|
|
10616
|
+
const content = readFileSync23(envPath, "utf-8");
|
|
10096
10617
|
const dirLine = content.split("\n").find(
|
|
10097
10618
|
(l) => l.trim().startsWith("PROJECT_DIR=") && !l.trim().startsWith("#")
|
|
10098
10619
|
);
|
|
10099
10620
|
if (dirLine) {
|
|
10100
10621
|
const projectDir = dirLine.split("=").slice(1).join("=").trim();
|
|
10101
|
-
if (projectDir &&
|
|
10622
|
+
if (projectDir && existsSync26(join24(projectDir, ".git"))) {
|
|
10102
10623
|
const remote = tryExec(
|
|
10103
10624
|
`git -C "${projectDir}" remote get-url origin 2>/dev/null`
|
|
10104
10625
|
);
|
|
@@ -10314,8 +10835,8 @@ function checkProjectDirEnv(factoryDir) {
|
|
|
10314
10835
|
detail: "no factory in scope"
|
|
10315
10836
|
};
|
|
10316
10837
|
}
|
|
10317
|
-
const envPath =
|
|
10318
|
-
if (!
|
|
10838
|
+
const envPath = join24(factoryDir, ".env");
|
|
10839
|
+
if (!existsSync26(envPath)) {
|
|
10319
10840
|
return {
|
|
10320
10841
|
label: "PROJECT_DIR (.env)",
|
|
10321
10842
|
status: "fail",
|
|
@@ -10325,7 +10846,7 @@ function checkProjectDirEnv(factoryDir) {
|
|
|
10325
10846
|
}
|
|
10326
10847
|
let content;
|
|
10327
10848
|
try {
|
|
10328
|
-
content =
|
|
10849
|
+
content = readFileSync23(envPath, "utf-8");
|
|
10329
10850
|
} catch {
|
|
10330
10851
|
return {
|
|
10331
10852
|
label: "PROJECT_DIR (.env)",
|
|
@@ -10354,7 +10875,7 @@ function checkProjectDirEnv(factoryDir) {
|
|
|
10354
10875
|
fix: "Re-run `beastmode init --project <path>` to populate it"
|
|
10355
10876
|
};
|
|
10356
10877
|
}
|
|
10357
|
-
if (!
|
|
10878
|
+
if (!existsSync26(value)) {
|
|
10358
10879
|
return {
|
|
10359
10880
|
label: "PROJECT_DIR (.env)",
|
|
10360
10881
|
status: "fail",
|
|
@@ -10362,7 +10883,7 @@ function checkProjectDirEnv(factoryDir) {
|
|
|
10362
10883
|
fix: `Clone your project to ${value} or re-run 'beastmode init --project <path>'`
|
|
10363
10884
|
};
|
|
10364
10885
|
}
|
|
10365
|
-
if (!
|
|
10886
|
+
if (!existsSync26(join24(value, ".git"))) {
|
|
10366
10887
|
return {
|
|
10367
10888
|
label: "PROJECT_DIR (.env)",
|
|
10368
10889
|
status: "fail",
|
|
@@ -10393,8 +10914,8 @@ async function checkFactoryContainers(factoryDir) {
|
|
|
10393
10914
|
detail: "no factory in scope"
|
|
10394
10915
|
};
|
|
10395
10916
|
}
|
|
10396
|
-
const composePath =
|
|
10397
|
-
if (!
|
|
10917
|
+
const composePath = join24(factoryDir, "docker-compose.yml");
|
|
10918
|
+
if (!existsSync26(composePath)) {
|
|
10398
10919
|
return {
|
|
10399
10920
|
label: "Factory containers",
|
|
10400
10921
|
status: "warn",
|
|
@@ -10676,9 +11197,9 @@ function checkProjectDirectory(factoryDir) {
|
|
|
10676
11197
|
fix: "Run beastmode init to create a factory"
|
|
10677
11198
|
};
|
|
10678
11199
|
}
|
|
10679
|
-
const bmDir =
|
|
10680
|
-
const projectsDir =
|
|
10681
|
-
if (!
|
|
11200
|
+
const bmDir = join24(factoryDir, ".beastmode");
|
|
11201
|
+
const projectsDir = join24(bmDir, "projects");
|
|
11202
|
+
if (!existsSync26(projectsDir)) {
|
|
10682
11203
|
return {
|
|
10683
11204
|
label: "Project directory",
|
|
10684
11205
|
status: "warn",
|
|
@@ -10686,8 +11207,8 @@ function checkProjectDirectory(factoryDir) {
|
|
|
10686
11207
|
fix: "Run: beastmode add project <path>"
|
|
10687
11208
|
};
|
|
10688
11209
|
}
|
|
10689
|
-
const
|
|
10690
|
-
if (
|
|
11210
|
+
const projectRecords = listProjectRecords(projectsDir);
|
|
11211
|
+
if (projectRecords.length === 0) {
|
|
10691
11212
|
return {
|
|
10692
11213
|
label: "Project directory",
|
|
10693
11214
|
status: "warn",
|
|
@@ -10697,37 +11218,31 @@ function checkProjectDirectory(factoryDir) {
|
|
|
10697
11218
|
}
|
|
10698
11219
|
const results = [];
|
|
10699
11220
|
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`);
|
|
11221
|
+
for (const proj of projectRecords) {
|
|
11222
|
+
const projPath = proj.path || "";
|
|
11223
|
+
const projName = proj.name;
|
|
11224
|
+
if (!projPath || !existsSync26(projPath)) {
|
|
11225
|
+
results.push(`${projName}: path not found`);
|
|
10729
11226
|
anyFail = true;
|
|
11227
|
+
continue;
|
|
10730
11228
|
}
|
|
11229
|
+
const isGit = existsSync26(join24(projPath, ".git"));
|
|
11230
|
+
const manifests = [
|
|
11231
|
+
"package.json",
|
|
11232
|
+
"Cargo.toml",
|
|
11233
|
+
"go.mod",
|
|
11234
|
+
"pyproject.toml",
|
|
11235
|
+
"requirements.txt",
|
|
11236
|
+
"pom.xml",
|
|
11237
|
+
"build.gradle",
|
|
11238
|
+
"build.gradle.kts"
|
|
11239
|
+
];
|
|
11240
|
+
const manifest = manifests.find((m) => existsSync26(join24(projPath, m)));
|
|
11241
|
+
const framework = proj.stack?.detected || manifest?.replace(".json", "") || "unknown";
|
|
11242
|
+
results.push(
|
|
11243
|
+
`${projName}: ${projPath} (${framework})${isGit ? "" : " [no .git]"}`
|
|
11244
|
+
);
|
|
11245
|
+
if (!isGit) anyFail = true;
|
|
10731
11246
|
}
|
|
10732
11247
|
if (anyFail) {
|
|
10733
11248
|
return {
|
|
@@ -10772,25 +11287,22 @@ function checkStack(factoryDir) {
|
|
|
10772
11287
|
detail: "no factory \u2014 run beastmode init"
|
|
10773
11288
|
};
|
|
10774
11289
|
}
|
|
10775
|
-
const projectsDir =
|
|
10776
|
-
if (!
|
|
11290
|
+
const projectsDir = join24(factoryDir, ".beastmode", "projects");
|
|
11291
|
+
if (!existsSync26(projectsDir)) {
|
|
10777
11292
|
return { label: "Stack", status: "warn", detail: "no projects configured" };
|
|
10778
11293
|
}
|
|
10779
|
-
const
|
|
10780
|
-
if (
|
|
11294
|
+
const projectRecords = listProjectRecords(projectsDir);
|
|
11295
|
+
if (projectRecords.length === 0) {
|
|
10781
11296
|
return { label: "Stack", status: "warn", detail: "no projects configured" };
|
|
10782
11297
|
}
|
|
10783
11298
|
const stacks = [];
|
|
10784
|
-
for (const
|
|
10785
|
-
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
stacks.push(`${name}: ${detected} \u2014 ${build}, port ${port}`);
|
|
10792
|
-
} catch {
|
|
10793
|
-
}
|
|
11299
|
+
for (const proj of projectRecords) {
|
|
11300
|
+
const name = proj.name;
|
|
11301
|
+
const stackInfo = proj.stack;
|
|
11302
|
+
const detected = stackInfo?.detected || "unknown";
|
|
11303
|
+
const build = stackInfo?.build_command || "";
|
|
11304
|
+
const port = stackInfo?.dev_port || 3e3;
|
|
11305
|
+
stacks.push(`${name}: ${detected} \u2014 ${build}, port ${port}`);
|
|
10794
11306
|
}
|
|
10795
11307
|
if (stacks.length === 0) {
|
|
10796
11308
|
return { label: "Stack", status: "warn", detail: "no readable project configs" };
|
|
@@ -10827,12 +11339,12 @@ function checkBoardPassword(env, factoryDir) {
|
|
|
10827
11339
|
return { label: "Board password", status: "pass", detail: "set" };
|
|
10828
11340
|
}
|
|
10829
11341
|
if (factoryDir) {
|
|
10830
|
-
const dotEnv =
|
|
10831
|
-
const secretsEnv =
|
|
11342
|
+
const dotEnv = join24(factoryDir, ".env");
|
|
11343
|
+
const secretsEnv = join24(factoryDir, ".beastmode", "secrets.env.local");
|
|
10832
11344
|
for (const filePath of [dotEnv, secretsEnv]) {
|
|
10833
|
-
if (
|
|
11345
|
+
if (existsSync26(filePath)) {
|
|
10834
11346
|
try {
|
|
10835
|
-
const content =
|
|
11347
|
+
const content = readFileSync23(filePath, "utf-8");
|
|
10836
11348
|
const lines = content.split("\n");
|
|
10837
11349
|
const line = lines.find((l) => l.startsWith("BEASTMODE_UI_PASSWORD="));
|
|
10838
11350
|
if (line) {
|
|
@@ -10854,12 +11366,12 @@ function checkBoardPassword(env, factoryDir) {
|
|
|
10854
11366
|
};
|
|
10855
11367
|
}
|
|
10856
11368
|
function doctorAction(factoryDir, env) {
|
|
10857
|
-
const bmDir =
|
|
10858
|
-
const factoryDirExists =
|
|
11369
|
+
const bmDir = join24(factoryDir, ".beastmode");
|
|
11370
|
+
const factoryDirExists = existsSync26(bmDir);
|
|
10859
11371
|
let factoryIdentity = null;
|
|
10860
11372
|
if (factoryDirExists) {
|
|
10861
11373
|
try {
|
|
10862
|
-
const raw = JSON.parse(
|
|
11374
|
+
const raw = JSON.parse(readFileSync23(join24(bmDir, "factory.json"), "utf-8"));
|
|
10863
11375
|
factoryIdentity = FactoryIdentitySchema.parse(raw);
|
|
10864
11376
|
} catch {
|
|
10865
11377
|
}
|
|
@@ -10867,10 +11379,10 @@ function doctorAction(factoryDir, env) {
|
|
|
10867
11379
|
let config = null;
|
|
10868
11380
|
let configParseError = null;
|
|
10869
11381
|
if (factoryDirExists) {
|
|
10870
|
-
const configPath =
|
|
10871
|
-
if (
|
|
11382
|
+
const configPath = join24(bmDir, "config.json");
|
|
11383
|
+
if (existsSync26(configPath)) {
|
|
10872
11384
|
try {
|
|
10873
|
-
const raw = JSON.parse(
|
|
11385
|
+
const raw = JSON.parse(readFileSync23(configPath, "utf-8"));
|
|
10874
11386
|
const result = FactoryConfigSchema.safeParse(raw);
|
|
10875
11387
|
if (result.success) {
|
|
10876
11388
|
config = raw;
|
|
@@ -10886,34 +11398,26 @@ function doctorAction(factoryDir, env) {
|
|
|
10886
11398
|
}
|
|
10887
11399
|
const projectPaths = [];
|
|
10888
11400
|
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
|
-
}
|
|
11401
|
+
const projectsDir = join24(bmDir, "projects");
|
|
11402
|
+
for (const proj of listProjectRecords(projectsDir)) {
|
|
11403
|
+
if (proj.path) {
|
|
11404
|
+
projectPaths.push({
|
|
11405
|
+
name: proj.name,
|
|
11406
|
+
path: proj.path,
|
|
11407
|
+
exists: existsSync26(proj.path)
|
|
11408
|
+
});
|
|
10905
11409
|
}
|
|
10906
11410
|
}
|
|
10907
11411
|
}
|
|
10908
11412
|
const installedPlugins = [];
|
|
10909
11413
|
if (factoryDirExists) {
|
|
10910
|
-
const pluginsDir =
|
|
10911
|
-
if (
|
|
10912
|
-
for (const pluginName of
|
|
10913
|
-
const manifestPath =
|
|
10914
|
-
if (
|
|
11414
|
+
const pluginsDir = join24(bmDir, "plugins");
|
|
11415
|
+
if (existsSync26(pluginsDir)) {
|
|
11416
|
+
for (const pluginName of readdirSync11(pluginsDir)) {
|
|
11417
|
+
const manifestPath = join24(pluginsDir, pluginName, "manifest.json");
|
|
11418
|
+
if (existsSync26(manifestPath)) {
|
|
10915
11419
|
try {
|
|
10916
|
-
const manifest = JSON.parse(
|
|
11420
|
+
const manifest = JSON.parse(readFileSync23(manifestPath, "utf-8"));
|
|
10917
11421
|
installedPlugins.push({
|
|
10918
11422
|
name: pluginName,
|
|
10919
11423
|
engine_version: manifest.engine_version || "*"
|
|
@@ -10941,8 +11445,8 @@ var SYSTEMD_CREDS_TIMER = "beastmode-claude-creds.timer";
|
|
|
10941
11445
|
var LAUNCH_AGENT_LABEL2 = "com.develeap.beastmode.claude-creds";
|
|
10942
11446
|
function checkCredentialSyncAgent() {
|
|
10943
11447
|
const results = [];
|
|
10944
|
-
const credsPath =
|
|
10945
|
-
if (!
|
|
11448
|
+
const credsPath = join24(homedir3(), ".claude", ".credentials.json");
|
|
11449
|
+
if (!existsSync26(credsPath)) {
|
|
10946
11450
|
results.push({
|
|
10947
11451
|
label: "Claude credentials file",
|
|
10948
11452
|
status: "fail",
|
|
@@ -10957,7 +11461,7 @@ function checkCredentialSyncAgent() {
|
|
|
10957
11461
|
detail: credsPath
|
|
10958
11462
|
});
|
|
10959
11463
|
try {
|
|
10960
|
-
const creds = JSON.parse(
|
|
11464
|
+
const creds = JSON.parse(readFileSync23(credsPath, "utf-8"));
|
|
10961
11465
|
const expiresAt = creds?.claudeAiOauth?.expiresAt;
|
|
10962
11466
|
if (typeof expiresAt === "number") {
|
|
10963
11467
|
const hoursLeft = Math.round((expiresAt - Date.now()) / 36e5);
|
|
@@ -11273,7 +11777,7 @@ var doctorCommand = new Command12("doctor").description("Health check \u2014 val
|
|
|
11273
11777
|
console.log();
|
|
11274
11778
|
const env = process.env;
|
|
11275
11779
|
const factoryDir = findFactoryDir() ?? resolve15(".");
|
|
11276
|
-
const hasFactory =
|
|
11780
|
+
const hasFactory = existsSync26(join24(factoryDir, ".beastmode"));
|
|
11277
11781
|
const checks = [];
|
|
11278
11782
|
checks.push(...await checkClaudeAuth(env.ANTHROPIC_API_KEY));
|
|
11279
11783
|
checks.push(...checkCredentialSyncAgent());
|
|
@@ -11349,12 +11853,12 @@ init_schemas();
|
|
|
11349
11853
|
init_version();
|
|
11350
11854
|
init_display();
|
|
11351
11855
|
import { Command as Command13 } from "commander";
|
|
11352
|
-
import { existsSync as
|
|
11353
|
-
import { join as
|
|
11856
|
+
import { existsSync as existsSync28, readFileSync as readFileSync25, writeFileSync as writeFileSync21 } from "fs";
|
|
11857
|
+
import { join as join26, resolve as resolve16 } from "path";
|
|
11354
11858
|
|
|
11355
11859
|
// src/cli/utils/regenerate.ts
|
|
11356
|
-
import { existsSync as
|
|
11357
|
-
import { join as
|
|
11860
|
+
import { existsSync as existsSync27, readFileSync as readFileSync24, writeFileSync as writeFileSync20, copyFileSync } from "fs";
|
|
11861
|
+
import { join as join25 } from "path";
|
|
11358
11862
|
var RECOGNIZED_KEYS = /* @__PURE__ */ new Set([
|
|
11359
11863
|
"PROJECT_DIR",
|
|
11360
11864
|
"PROJECT_GITHUB_TOKEN",
|
|
@@ -11468,19 +11972,19 @@ function buildNewEnv(values) {
|
|
|
11468
11972
|
return lines.join("\n");
|
|
11469
11973
|
}
|
|
11470
11974
|
function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
|
|
11471
|
-
const envPath =
|
|
11472
|
-
const composePath =
|
|
11473
|
-
if (!
|
|
11975
|
+
const envPath = join25(factoryDir, ".env");
|
|
11976
|
+
const composePath = join25(factoryDir, "docker-compose.yml");
|
|
11977
|
+
if (!existsSync27(envPath)) {
|
|
11474
11978
|
throw new Error(
|
|
11475
11979
|
`.env not found at ${envPath}. This does not look like a beastmode factory \u2014 run 'beastmode init' first.`
|
|
11476
11980
|
);
|
|
11477
11981
|
}
|
|
11478
|
-
if (!
|
|
11982
|
+
if (!existsSync27(composePath)) {
|
|
11479
11983
|
throw new Error(
|
|
11480
11984
|
`docker-compose.yml not found at ${composePath}. This does not look like a beastmode factory \u2014 run 'beastmode init' first.`
|
|
11481
11985
|
);
|
|
11482
11986
|
}
|
|
11483
|
-
const existingEnv =
|
|
11987
|
+
const existingEnv = readFileSync24(envPath, "utf-8");
|
|
11484
11988
|
const values = parseExistingEnv(existingEnv);
|
|
11485
11989
|
const missing = [];
|
|
11486
11990
|
if (!values.projectDir) missing.push("PROJECT_DIR");
|
|
@@ -11498,7 +12002,7 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
|
|
|
11498
12002
|
}
|
|
11499
12003
|
const newEnv = buildNewEnv(values);
|
|
11500
12004
|
const newCompose = generateComposeYaml("latest");
|
|
11501
|
-
const existingCompose =
|
|
12005
|
+
const existingCompose = readFileSync24(composePath, "utf-8");
|
|
11502
12006
|
const envChanged = newEnv !== existingEnv;
|
|
11503
12007
|
const composeChanged = newCompose !== existingCompose;
|
|
11504
12008
|
if (!envChanged && !composeChanged) {
|
|
@@ -11516,12 +12020,12 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
|
|
|
11516
12020
|
if (envChanged) {
|
|
11517
12021
|
envBackupPath = `${envPath}.backup.${timestamp}`;
|
|
11518
12022
|
copyFileSync(envPath, envBackupPath);
|
|
11519
|
-
|
|
12023
|
+
writeFileSync20(envPath, newEnv, "utf-8");
|
|
11520
12024
|
}
|
|
11521
12025
|
if (composeChanged) {
|
|
11522
12026
|
composeBackupPath = `${composePath}.backup.${timestamp}`;
|
|
11523
12027
|
copyFileSync(composePath, composeBackupPath);
|
|
11524
|
-
|
|
12028
|
+
writeFileSync20(composePath, newCompose, "utf-8");
|
|
11525
12029
|
}
|
|
11526
12030
|
return {
|
|
11527
12031
|
envChanged,
|
|
@@ -11534,18 +12038,18 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
|
|
|
11534
12038
|
|
|
11535
12039
|
// src/cli/commands/upgrade.ts
|
|
11536
12040
|
function readIdentity(factoryDir) {
|
|
11537
|
-
const path =
|
|
11538
|
-
if (!
|
|
12041
|
+
const path = join26(factoryDir, ".beastmode", "factory.json");
|
|
12042
|
+
if (!existsSync28(path)) {
|
|
11539
12043
|
throw new Error("No factory.json found. Run beastmode init first.");
|
|
11540
12044
|
}
|
|
11541
|
-
return FactoryIdentitySchema.parse(JSON.parse(
|
|
12045
|
+
return FactoryIdentitySchema.parse(JSON.parse(readFileSync25(path, "utf-8")));
|
|
11542
12046
|
}
|
|
11543
12047
|
function readConfig3(factoryDir) {
|
|
11544
|
-
const path =
|
|
11545
|
-
if (!
|
|
12048
|
+
const path = join26(factoryDir, ".beastmode", "config.json");
|
|
12049
|
+
if (!existsSync28(path)) {
|
|
11546
12050
|
return {};
|
|
11547
12051
|
}
|
|
11548
|
-
return JSON.parse(
|
|
12052
|
+
return JSON.parse(readFileSync25(path, "utf-8"));
|
|
11549
12053
|
}
|
|
11550
12054
|
function upgradeCheckAction(factoryDir) {
|
|
11551
12055
|
const identity = readIdentity(factoryDir);
|
|
@@ -11557,12 +12061,12 @@ function upgradeAction(factoryDir, migrateOnly = false) {
|
|
|
11557
12061
|
const targetVersion = migrateOnly ? identity.engine_version : ENGINE_VERSION;
|
|
11558
12062
|
const result = performUpgrade(identity, config, targetVersion, SCHEMA_VERSION);
|
|
11559
12063
|
if (result.changes.length > 0) {
|
|
11560
|
-
|
|
11561
|
-
|
|
12064
|
+
writeFileSync21(
|
|
12065
|
+
join26(factoryDir, ".beastmode", "factory.json"),
|
|
11562
12066
|
JSON.stringify(result.updatedIdentity, null, 2) + "\n"
|
|
11563
12067
|
);
|
|
11564
|
-
|
|
11565
|
-
|
|
12068
|
+
writeFileSync21(
|
|
12069
|
+
join26(factoryDir, ".beastmode", "config.json"),
|
|
11566
12070
|
JSON.stringify(result.updatedConfig, null, 2) + "\n"
|
|
11567
12071
|
);
|
|
11568
12072
|
}
|
|
@@ -11652,8 +12156,8 @@ init_board();
|
|
|
11652
12156
|
init_display();
|
|
11653
12157
|
init_migrator();
|
|
11654
12158
|
import { Command as Command14 } from "commander";
|
|
11655
|
-
import { resolve as resolve17, join as
|
|
11656
|
-
import { existsSync as
|
|
12159
|
+
import { resolve as resolve17, join as join27 } from "path";
|
|
12160
|
+
import { existsSync as existsSync29, readFileSync as readFileSync26, mkdirSync as mkdirSync17, writeFileSync as writeFileSync22 } from "fs";
|
|
11657
12161
|
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
12162
|
try {
|
|
11659
12163
|
await runMigrate(opts);
|
|
@@ -11665,7 +12169,7 @@ var migrateCommand = new Command14("migrate").description("Migrate a daemon conf
|
|
|
11665
12169
|
async function runMigrate(opts) {
|
|
11666
12170
|
const cwd = process.cwd();
|
|
11667
12171
|
const configPath = opts.config ? resolve17(opts.config) : resolve17(cwd, "config", "beastmode.daemon.json");
|
|
11668
|
-
if (!
|
|
12172
|
+
if (!existsSync29(configPath)) {
|
|
11669
12173
|
throw new Error(
|
|
11670
12174
|
`Daemon config not found at ${configPath}
|
|
11671
12175
|
Use --config <path> to specify a different location.`
|
|
@@ -11673,25 +12177,25 @@ async function runMigrate(opts) {
|
|
|
11673
12177
|
}
|
|
11674
12178
|
header("BeastMode Migrate");
|
|
11675
12179
|
info(`Reading daemon config from: ${configPath}`);
|
|
11676
|
-
const configContent =
|
|
12180
|
+
const configContent = readFileSync26(configPath, "utf-8");
|
|
11677
12181
|
const daemonConfig = parseDaemonConfig(configContent);
|
|
11678
|
-
const runsDir =
|
|
12182
|
+
const runsDir = join27(cwd, "runs");
|
|
11679
12183
|
let runDirs = [];
|
|
11680
12184
|
const checkpoints = /* @__PURE__ */ new Map();
|
|
11681
|
-
if (
|
|
11682
|
-
const { readdirSync:
|
|
11683
|
-
runDirs =
|
|
12185
|
+
if (existsSync29(runsDir)) {
|
|
12186
|
+
const { readdirSync: readdirSync13 } = await import("fs");
|
|
12187
|
+
runDirs = readdirSync13(runsDir).filter((d) => {
|
|
11684
12188
|
try {
|
|
11685
|
-
return
|
|
12189
|
+
return readdirSync13(join27(runsDir, d)).length > 0;
|
|
11686
12190
|
} catch {
|
|
11687
12191
|
return false;
|
|
11688
12192
|
}
|
|
11689
12193
|
});
|
|
11690
12194
|
for (const dir of runDirs) {
|
|
11691
|
-
const cpPath =
|
|
11692
|
-
if (
|
|
12195
|
+
const cpPath = join27(runsDir, dir, "checkpoint.json");
|
|
12196
|
+
if (existsSync29(cpPath)) {
|
|
11693
12197
|
try {
|
|
11694
|
-
const cp = JSON.parse(
|
|
12198
|
+
const cp = JSON.parse(readFileSync26(cpPath, "utf-8"));
|
|
11695
12199
|
checkpoints.set(dir, cp);
|
|
11696
12200
|
} catch {
|
|
11697
12201
|
}
|
|
@@ -11748,28 +12252,28 @@ async function runMigrate(opts) {
|
|
|
11748
12252
|
warn("Dry run \u2014 no files written.");
|
|
11749
12253
|
return;
|
|
11750
12254
|
}
|
|
11751
|
-
const bmDir =
|
|
11752
|
-
if (
|
|
12255
|
+
const bmDir = join27(cwd, ".beastmode");
|
|
12256
|
+
if (existsSync29(bmDir)) {
|
|
11753
12257
|
throw new Error(
|
|
11754
12258
|
"A .beastmode/ directory already exists. Remove it first to re-migrate."
|
|
11755
12259
|
);
|
|
11756
12260
|
}
|
|
11757
12261
|
for (const file of files) {
|
|
11758
|
-
const fullPath =
|
|
12262
|
+
const fullPath = join27(cwd, file.path);
|
|
11759
12263
|
const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
|
|
11760
|
-
|
|
11761
|
-
|
|
12264
|
+
mkdirSync17(dir, { recursive: true });
|
|
12265
|
+
writeFileSync22(fullPath, file.content, "utf-8");
|
|
11762
12266
|
}
|
|
11763
|
-
const runsSymlinkTarget =
|
|
11764
|
-
const bmRunsPath =
|
|
11765
|
-
if (
|
|
12267
|
+
const runsSymlinkTarget = join27(cwd, "runs");
|
|
12268
|
+
const bmRunsPath = join27(cwd, "runs");
|
|
12269
|
+
if (existsSync29(runsSymlinkTarget)) {
|
|
11766
12270
|
info("Existing runs/ directory preserved in-place.");
|
|
11767
12271
|
}
|
|
11768
|
-
const boardPath =
|
|
11769
|
-
if (!
|
|
11770
|
-
|
|
12272
|
+
const boardPath = join27(bmDir, "board.json");
|
|
12273
|
+
if (!existsSync29(boardPath)) {
|
|
12274
|
+
writeFileSync22(boardPath, JSON.stringify({ items: [] }, null, 2), "utf-8");
|
|
11771
12275
|
}
|
|
11772
|
-
|
|
12276
|
+
mkdirSync17(join27(bmDir, ".cache"), { recursive: true });
|
|
11773
12277
|
console.log();
|
|
11774
12278
|
success("Migration complete!");
|
|
11775
12279
|
info(`Factory created at: ${bmDir}`);
|
|
@@ -11790,9 +12294,10 @@ init_display();
|
|
|
11790
12294
|
init_board();
|
|
11791
12295
|
init_bridge();
|
|
11792
12296
|
init_schemas();
|
|
12297
|
+
init_engine();
|
|
11793
12298
|
import { Command as Command15 } from "commander";
|
|
11794
|
-
import { join as
|
|
11795
|
-
import { existsSync as
|
|
12299
|
+
import { join as join28 } from "path";
|
|
12300
|
+
import { existsSync as existsSync30, readFileSync as readFileSync27, writeFileSync as writeFileSync23, mkdirSync as mkdirSync18 } from "fs";
|
|
11796
12301
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
11797
12302
|
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
12303
|
try {
|
|
@@ -11812,44 +12317,35 @@ async function runPipeline(projectName, opts) {
|
|
|
11812
12317
|
"No BeastMode factory found. Run 'beastmode init' first."
|
|
11813
12318
|
);
|
|
11814
12319
|
}
|
|
11815
|
-
const bmDir =
|
|
12320
|
+
const bmDir = join28(factoryDir, ".beastmode");
|
|
11816
12321
|
header("BeastMode Run");
|
|
11817
|
-
const configPath =
|
|
11818
|
-
if (!
|
|
12322
|
+
const configPath = join28(bmDir, "config.json");
|
|
12323
|
+
if (!existsSync30(configPath)) {
|
|
11819
12324
|
throw new Error("Factory config not found. Run 'beastmode init' first.");
|
|
11820
12325
|
}
|
|
11821
12326
|
const factoryConfig = FactoryConfigSchema.parse(
|
|
11822
|
-
JSON.parse(
|
|
12327
|
+
JSON.parse(readFileSync27(configPath, "utf-8"))
|
|
11823
12328
|
);
|
|
11824
12329
|
let projectConfig = null;
|
|
11825
|
-
const projectsDir =
|
|
11826
|
-
if (
|
|
11827
|
-
const
|
|
11828
|
-
|
|
11829
|
-
(
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
throw new Error(`Project not found: ${projectName}`);
|
|
11837
|
-
}
|
|
11838
|
-
projectConfig = ProjectConfigSchema.parse(
|
|
11839
|
-
JSON.parse(readFileSync26(join27(projectsDir, file), "utf-8"))
|
|
11840
|
-
);
|
|
11841
|
-
} else if (projectFiles.length > 0) {
|
|
11842
|
-
projectConfig = ProjectConfigSchema.parse(
|
|
11843
|
-
JSON.parse(readFileSync26(join27(projectsDir, projectFiles[0]), "utf-8"))
|
|
11844
|
-
);
|
|
12330
|
+
const projectsDir = join28(bmDir, "projects");
|
|
12331
|
+
if (projectName) {
|
|
12332
|
+
const record = readProjectRecord(projectsDir, projectName);
|
|
12333
|
+
if (!record) {
|
|
12334
|
+
throw new Error(`Project not found: ${projectName}`);
|
|
12335
|
+
}
|
|
12336
|
+
projectConfig = record;
|
|
12337
|
+
} else {
|
|
12338
|
+
const projects = listProjectRecords(projectsDir);
|
|
12339
|
+
if (projects.length > 0) {
|
|
12340
|
+
projectConfig = projects[0];
|
|
11845
12341
|
info(`Using project: ${projectConfig.name}`);
|
|
11846
12342
|
}
|
|
11847
12343
|
}
|
|
11848
|
-
const boardPath =
|
|
12344
|
+
const boardPath = join28(bmDir, "board.json");
|
|
11849
12345
|
let boardItems = [];
|
|
11850
|
-
if (
|
|
12346
|
+
if (existsSync30(boardPath)) {
|
|
11851
12347
|
try {
|
|
11852
|
-
const raw = JSON.parse(
|
|
12348
|
+
const raw = JSON.parse(readFileSync27(boardPath, "utf-8"));
|
|
11853
12349
|
boardItems = Array.isArray(raw.items) ? raw.items : [];
|
|
11854
12350
|
} catch {
|
|
11855
12351
|
boardItems = [];
|
|
@@ -11866,13 +12362,13 @@ async function runPipeline(projectName, opts) {
|
|
|
11866
12362
|
updated_at: now
|
|
11867
12363
|
};
|
|
11868
12364
|
boardItems.push(task);
|
|
11869
|
-
|
|
12365
|
+
writeFileSync23(boardPath, JSON.stringify({ items: boardItems }, null, 2), "utf-8");
|
|
11870
12366
|
info(`Created task: ${task.title} (${taskId})`);
|
|
11871
|
-
const cacheDir =
|
|
11872
|
-
|
|
11873
|
-
const daemonConfigPath =
|
|
12367
|
+
const cacheDir = join28(bmDir, ".cache");
|
|
12368
|
+
mkdirSync18(cacheDir, { recursive: true });
|
|
12369
|
+
const daemonConfigPath = join28(cacheDir, "daemon.json");
|
|
11874
12370
|
const daemonConfig = generateDaemonConfig(factoryConfig, projectConfig, factoryDir);
|
|
11875
|
-
|
|
12371
|
+
writeFileSync23(daemonConfigPath, JSON.stringify(daemonConfig, null, 2), "utf-8");
|
|
11876
12372
|
info(`Generated daemon config at: ${daemonConfigPath}`);
|
|
11877
12373
|
const { execSync: execSync14 } = await import("child_process");
|
|
11878
12374
|
let pythonAvailable = false;
|
|
@@ -11896,7 +12392,7 @@ async function runPipeline(projectName, opts) {
|
|
|
11896
12392
|
const daemonPaths = findPythonDaemonPaths(envPath, factoryDir);
|
|
11897
12393
|
let daemonFound = false;
|
|
11898
12394
|
for (const p of daemonPaths) {
|
|
11899
|
-
if (
|
|
12395
|
+
if (existsSync30(p)) {
|
|
11900
12396
|
daemonFound = true;
|
|
11901
12397
|
break;
|
|
11902
12398
|
}
|
|
@@ -11923,7 +12419,7 @@ async function runPipeline(projectName, opts) {
|
|
|
11923
12419
|
const startTime = Date.now();
|
|
11924
12420
|
const pollInterval = setInterval(() => {
|
|
11925
12421
|
try {
|
|
11926
|
-
const board = JSON.parse(
|
|
12422
|
+
const board = JSON.parse(readFileSync27(boardPath, "utf-8"));
|
|
11927
12423
|
const items = Array.isArray(board.items) ? board.items : [];
|
|
11928
12424
|
const taskItem = items.find((i) => i.id === taskId);
|
|
11929
12425
|
if (taskItem) {
|
|
@@ -11968,9 +12464,10 @@ init_display();
|
|
|
11968
12464
|
init_board();
|
|
11969
12465
|
init_bridge();
|
|
11970
12466
|
init_schemas();
|
|
12467
|
+
init_engine();
|
|
11971
12468
|
import { Command as Command16 } from "commander";
|
|
11972
|
-
import { join as
|
|
11973
|
-
import { existsSync as
|
|
12469
|
+
import { join as join29 } from "path";
|
|
12470
|
+
import { existsSync as existsSync31, readFileSync as readFileSync28, writeFileSync as writeFileSync24, mkdirSync as mkdirSync19 } from "fs";
|
|
11974
12471
|
var daemonCommand = new Command16("daemon").description("Start the BeastMode daemon via bridge").option("--dry-run", "Generate config but don't start daemon").option(
|
|
11975
12472
|
"--log-level <level>",
|
|
11976
12473
|
"Log level (DEBUG, INFO, WARNING, ERROR)",
|
|
@@ -11990,38 +12487,31 @@ async function runDaemon(opts) {
|
|
|
11990
12487
|
"No BeastMode factory found. Run 'beastmode init' first."
|
|
11991
12488
|
);
|
|
11992
12489
|
}
|
|
11993
|
-
const bmDir =
|
|
12490
|
+
const bmDir = join29(factoryDir, ".beastmode");
|
|
11994
12491
|
header("BeastMode Daemon");
|
|
11995
|
-
const configPath =
|
|
11996
|
-
if (!
|
|
12492
|
+
const configPath = join29(bmDir, "config.json");
|
|
12493
|
+
if (!existsSync31(configPath)) {
|
|
11997
12494
|
throw new Error("Factory config not found. Run 'beastmode init' first.");
|
|
11998
12495
|
}
|
|
11999
12496
|
const factoryConfig = FactoryConfigSchema.parse(
|
|
12000
|
-
JSON.parse(
|
|
12497
|
+
JSON.parse(readFileSync28(configPath, "utf-8"))
|
|
12001
12498
|
);
|
|
12002
12499
|
let projectConfig = null;
|
|
12003
|
-
const projectsDir =
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
);
|
|
12013
|
-
info(`Using project: ${projectConfig.name}`);
|
|
12014
|
-
}
|
|
12015
|
-
}
|
|
12016
|
-
const cacheDir = join28(bmDir, ".cache");
|
|
12017
|
-
mkdirSync18(cacheDir, { recursive: true });
|
|
12018
|
-
const daemonConfigPath = join28(cacheDir, "daemon.json");
|
|
12500
|
+
const projectsDir = join29(bmDir, "projects");
|
|
12501
|
+
const projects = listProjectRecords(projectsDir);
|
|
12502
|
+
if (projects.length > 0) {
|
|
12503
|
+
projectConfig = projects[0];
|
|
12504
|
+
info(`Using project: ${projectConfig.name}`);
|
|
12505
|
+
}
|
|
12506
|
+
const cacheDir = join29(bmDir, ".cache");
|
|
12507
|
+
mkdirSync19(cacheDir, { recursive: true });
|
|
12508
|
+
const daemonConfigPath = join29(cacheDir, "daemon.json");
|
|
12019
12509
|
const daemonConfig = generateDaemonConfig(
|
|
12020
12510
|
factoryConfig,
|
|
12021
12511
|
projectConfig,
|
|
12022
12512
|
factoryDir
|
|
12023
12513
|
);
|
|
12024
|
-
|
|
12514
|
+
writeFileSync24(
|
|
12025
12515
|
daemonConfigPath,
|
|
12026
12516
|
JSON.stringify(daemonConfig, null, 2),
|
|
12027
12517
|
"utf-8"
|
|
@@ -12060,7 +12550,7 @@ async function runDaemon(opts) {
|
|
|
12060
12550
|
});
|
|
12061
12551
|
info(`Starting daemon: ${pythonCmd} ${cmd.args.join(" ")}`);
|
|
12062
12552
|
console.log();
|
|
12063
|
-
const pidFile =
|
|
12553
|
+
const pidFile = join29(bmDir, "daemon.pid");
|
|
12064
12554
|
const { spawn: spawn4 } = await import("child_process");
|
|
12065
12555
|
const child = spawn4(pythonCmd, cmd.args, {
|
|
12066
12556
|
stdio: "inherit",
|
|
@@ -12071,7 +12561,7 @@ async function runDaemon(opts) {
|
|
|
12071
12561
|
}
|
|
12072
12562
|
});
|
|
12073
12563
|
if (child.pid) {
|
|
12074
|
-
|
|
12564
|
+
writeFileSync24(pidFile, String(child.pid), "utf-8");
|
|
12075
12565
|
}
|
|
12076
12566
|
const signalHandler = (signal) => {
|
|
12077
12567
|
info(`Forwarding ${signal} to daemon...`);
|
|
@@ -12110,8 +12600,8 @@ var mcpCommand = new Command17("mcp").description("Start the BeastMode MCP serve
|
|
|
12110
12600
|
// src/cli/commands/deploy.ts
|
|
12111
12601
|
init_display();
|
|
12112
12602
|
import { Command as Command18 } from "commander";
|
|
12113
|
-
import { resolve as resolve19, join as
|
|
12114
|
-
import { existsSync as
|
|
12603
|
+
import { resolve as resolve19, join as join31 } from "path";
|
|
12604
|
+
import { existsSync as existsSync33, writeFileSync as writeFileSync26, readFileSync as readFileSync30 } from "fs";
|
|
12115
12605
|
import { execSync as execSync9 } from "child_process";
|
|
12116
12606
|
import { randomBytes } from "crypto";
|
|
12117
12607
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -12166,8 +12656,8 @@ async function runDeploy(opts) {
|
|
|
12166
12656
|
process.exit(1);
|
|
12167
12657
|
}
|
|
12168
12658
|
const factoryDir = resolve19(".");
|
|
12169
|
-
const bmDir =
|
|
12170
|
-
if (!
|
|
12659
|
+
const bmDir = join31(factoryDir, ".beastmode");
|
|
12660
|
+
if (!existsSync33(bmDir)) {
|
|
12171
12661
|
error(
|
|
12172
12662
|
"No .beastmode directory found. Run 'beastmode init' or 'beastmode migrate' first."
|
|
12173
12663
|
);
|
|
@@ -12191,33 +12681,33 @@ async function runDeploy(opts) {
|
|
|
12191
12681
|
"../../index.js"
|
|
12192
12682
|
);
|
|
12193
12683
|
}
|
|
12194
|
-
const boardVenvPython =
|
|
12195
|
-
const daemonVenvPython =
|
|
12196
|
-
const boardPython =
|
|
12197
|
-
const daemonPython =
|
|
12684
|
+
const boardVenvPython = join31(factoryDir, "board", ".venv", "bin", "python");
|
|
12685
|
+
const daemonVenvPython = join31(factoryDir, "daemon", ".venv", "bin", "python");
|
|
12686
|
+
const boardPython = existsSync33(boardVenvPython) ? boardVenvPython : "python3";
|
|
12687
|
+
const daemonPython = existsSync33(daemonVenvPython) ? daemonVenvPython : "python3";
|
|
12198
12688
|
const user = execSync9("whoami", { encoding: "utf-8" }).trim();
|
|
12199
12689
|
const home = process.env.HOME || `/home/${user}`;
|
|
12200
12690
|
const port = opts.port;
|
|
12201
12691
|
const host = opts.host;
|
|
12202
|
-
const dotEnv =
|
|
12203
|
-
const secretsEnv =
|
|
12204
|
-
const envContent =
|
|
12205
|
-
const secretsContent =
|
|
12692
|
+
const dotEnv = join31(factoryDir, ".env");
|
|
12693
|
+
const secretsEnv = join31(bmDir, "secrets.env.local");
|
|
12694
|
+
const envContent = existsSync33(dotEnv) ? readFileSync30(dotEnv, "utf-8") : "";
|
|
12695
|
+
const secretsContent = existsSync33(secretsEnv) ? readFileSync30(secretsEnv, "utf-8") : "";
|
|
12206
12696
|
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
12697
|
if (!hasPassword && opts.host === "0.0.0.0") {
|
|
12208
12698
|
const generated = randomBytes(18).toString("base64url");
|
|
12209
|
-
const target =
|
|
12699
|
+
const target = existsSync33(secretsEnv) ? secretsEnv : dotEnv;
|
|
12210
12700
|
const append = `
|
|
12211
12701
|
# Auto-generated board UI password (deploy)
|
|
12212
12702
|
BEASTMODE_UI_PASSWORD=${generated}
|
|
12213
12703
|
`;
|
|
12214
|
-
|
|
12704
|
+
writeFileSync26(target, (existsSync33(target) ? readFileSync30(target, "utf-8") : "") + append, "utf-8");
|
|
12215
12705
|
info(`Board UI password auto-generated and saved to ${target}`);
|
|
12216
12706
|
success(`Password: ${generated}`);
|
|
12217
12707
|
info("Save this password \u2014 you'll need it to access the board UI.");
|
|
12218
12708
|
}
|
|
12219
12709
|
const envFileLines = [];
|
|
12220
|
-
if (
|
|
12710
|
+
if (existsSync33(secretsEnv)) {
|
|
12221
12711
|
envFileLines.push(`EnvironmentFile=${secretsEnv}`);
|
|
12222
12712
|
} else {
|
|
12223
12713
|
envFileLines.push(`# No secrets.env.local found at time of deploy`);
|
|
@@ -12300,7 +12790,7 @@ BEASTMODE_UI_PASSWORD=${generated}
|
|
|
12300
12790
|
info(`Writing service file to ${svc.path}...`);
|
|
12301
12791
|
try {
|
|
12302
12792
|
const tmpPath = `/tmp/${svc.name}.service`;
|
|
12303
|
-
|
|
12793
|
+
writeFileSync26(tmpPath, svc.content, "utf-8");
|
|
12304
12794
|
execSync9(`sudo cp ${tmpPath} ${svc.path}`, { stdio: "inherit" });
|
|
12305
12795
|
success(`${svc.name} service file installed`);
|
|
12306
12796
|
} catch {
|
|
@@ -12429,9 +12919,9 @@ async function deployToAWS(opts) {
|
|
|
12429
12919
|
}
|
|
12430
12920
|
const __filename2 = fileURLToPath3(import.meta.url);
|
|
12431
12921
|
const __dirname2 = dirname7(__filename2);
|
|
12432
|
-
const templatePath =
|
|
12433
|
-
const cwdTemplate =
|
|
12434
|
-
const template =
|
|
12922
|
+
const templatePath = join31(__dirname2, "..", "..", "infra", "cloudformation", "beastmode.yaml");
|
|
12923
|
+
const cwdTemplate = join31(process.cwd(), "infra", "cloudformation", "beastmode.yaml");
|
|
12924
|
+
const template = existsSync33(templatePath) ? templatePath : existsSync33(cwdTemplate) ? cwdTemplate : null;
|
|
12435
12925
|
if (!template) {
|
|
12436
12926
|
error("CloudFormation template not found. Expected at infra/cloudformation/beastmode.yaml");
|
|
12437
12927
|
process.exit(1);
|
|
@@ -12685,19 +13175,19 @@ var logsCommand = new Command21("logs").description("Stream BeastMode service lo
|
|
|
12685
13175
|
|
|
12686
13176
|
// src/cli/commands/update.ts
|
|
12687
13177
|
import { Command as Command22 } from "commander";
|
|
12688
|
-
import { readFileSync as
|
|
13178
|
+
import { readFileSync as readFileSync31, writeFileSync as writeFileSync27 } from "fs";
|
|
12689
13179
|
init_display();
|
|
12690
13180
|
async function runUpdate(opts) {
|
|
12691
13181
|
const cwd = opts.cwd ?? process.cwd();
|
|
12692
13182
|
const composePath = requireComposeFile(cwd);
|
|
12693
13183
|
if (opts.tag) {
|
|
12694
|
-
let content =
|
|
13184
|
+
let content = readFileSync31(composePath, "utf-8");
|
|
12695
13185
|
const tagPattern = new RegExp(
|
|
12696
13186
|
`(${GHCR_IMAGE_PREFIX.replace(/[/]/g, "\\/")}\\/(?:board|daemon|ui)):([\\w.\\-]+)`,
|
|
12697
13187
|
"g"
|
|
12698
13188
|
);
|
|
12699
13189
|
content = content.replace(tagPattern, `$1:${opts.tag}`);
|
|
12700
|
-
|
|
13190
|
+
writeFileSync27(composePath, content, "utf-8");
|
|
12701
13191
|
}
|
|
12702
13192
|
runCompose(["pull"], { cwd, inherit: true });
|
|
12703
13193
|
runCompose(["up", "-d"], { cwd, inherit: true });
|
|
@@ -12719,26 +13209,27 @@ var updateCommand = new Command22("update").description("Pull latest BeastMode i
|
|
|
12719
13209
|
});
|
|
12720
13210
|
|
|
12721
13211
|
// src/cli/commands/runner-cmd.ts
|
|
13212
|
+
init_engine();
|
|
12722
13213
|
init_display();
|
|
12723
13214
|
import { Command as Command23 } from "commander";
|
|
12724
13215
|
import { spawn as spawn3 } from "child_process";
|
|
12725
|
-
import { existsSync as
|
|
12726
|
-
import { basename as
|
|
13216
|
+
import { existsSync as existsSync38 } from "fs";
|
|
13217
|
+
import { basename as basename7, join as join37, resolve as resolve20 } from "path";
|
|
12727
13218
|
|
|
12728
13219
|
// src/cli/runner-image-builder.ts
|
|
12729
13220
|
import { execSync as execSync10 } from "child_process";
|
|
12730
13221
|
import { createHash } from "crypto";
|
|
12731
13222
|
import {
|
|
12732
|
-
existsSync as
|
|
12733
|
-
mkdirSync as
|
|
12734
|
-
readFileSync as
|
|
12735
|
-
writeFileSync as
|
|
13223
|
+
existsSync as existsSync35,
|
|
13224
|
+
mkdirSync as mkdirSync21,
|
|
13225
|
+
readFileSync as readFileSync33,
|
|
13226
|
+
writeFileSync as writeFileSync28
|
|
12736
13227
|
} from "fs";
|
|
12737
|
-
import { join as
|
|
13228
|
+
import { join as join33 } from "path";
|
|
12738
13229
|
|
|
12739
13230
|
// src/cli/stack-detect.ts
|
|
12740
|
-
import { existsSync as
|
|
12741
|
-
import { join as
|
|
13231
|
+
import { existsSync as existsSync34, readFileSync as readFileSync32 } from "fs";
|
|
13232
|
+
import { join as join32 } from "path";
|
|
12742
13233
|
var NODE_LOCKFILES = [
|
|
12743
13234
|
"package-lock.json",
|
|
12744
13235
|
"pnpm-lock.yaml",
|
|
@@ -12776,7 +13267,7 @@ var STACK_LOCKFILES = {
|
|
|
12776
13267
|
};
|
|
12777
13268
|
function readFileSafe2(path) {
|
|
12778
13269
|
try {
|
|
12779
|
-
return
|
|
13270
|
+
return readFileSync32(path, "utf-8");
|
|
12780
13271
|
} catch {
|
|
12781
13272
|
return null;
|
|
12782
13273
|
}
|
|
@@ -12789,7 +13280,7 @@ function parseJsonSafe2(content) {
|
|
|
12789
13280
|
}
|
|
12790
13281
|
}
|
|
12791
13282
|
function detectRunnerStack(projectDir) {
|
|
12792
|
-
const pkgContent = readFileSafe2(
|
|
13283
|
+
const pkgContent = readFileSafe2(join32(projectDir, "package.json"));
|
|
12793
13284
|
if (pkgContent) {
|
|
12794
13285
|
const pkg = parseJsonSafe2(pkgContent);
|
|
12795
13286
|
if (pkg) {
|
|
@@ -12803,36 +13294,36 @@ function detectRunnerStack(projectDir) {
|
|
|
12803
13294
|
}
|
|
12804
13295
|
return { name: "node", language: "node" };
|
|
12805
13296
|
}
|
|
12806
|
-
if (
|
|
13297
|
+
if (existsSync34(join32(projectDir, "manage.py"))) {
|
|
12807
13298
|
return { name: "django", language: "python" };
|
|
12808
13299
|
}
|
|
12809
|
-
const pyproject = readFileSafe2(
|
|
13300
|
+
const pyproject = readFileSafe2(join32(projectDir, "pyproject.toml"));
|
|
12810
13301
|
if (pyproject) {
|
|
12811
13302
|
if (pyproject.toLowerCase().includes("fastapi")) {
|
|
12812
13303
|
return { name: "fastapi", language: "python" };
|
|
12813
13304
|
}
|
|
12814
13305
|
return { name: "python", language: "python" };
|
|
12815
13306
|
}
|
|
12816
|
-
if (
|
|
13307
|
+
if (existsSync34(join32(projectDir, "requirements.txt"))) {
|
|
12817
13308
|
return { name: "python", language: "python" };
|
|
12818
13309
|
}
|
|
12819
|
-
if (
|
|
13310
|
+
if (existsSync34(join32(projectDir, "go.mod"))) {
|
|
12820
13311
|
return { name: "go", language: "go" };
|
|
12821
13312
|
}
|
|
12822
|
-
if (
|
|
13313
|
+
if (existsSync34(join32(projectDir, "Cargo.toml"))) {
|
|
12823
13314
|
return { name: "rust", language: "rust" };
|
|
12824
13315
|
}
|
|
12825
|
-
if (
|
|
13316
|
+
if (existsSync34(join32(projectDir, "pom.xml"))) {
|
|
12826
13317
|
return { name: "java-maven", language: "java" };
|
|
12827
13318
|
}
|
|
12828
|
-
if (
|
|
13319
|
+
if (existsSync34(join32(projectDir, "build.gradle")) || existsSync34(join32(projectDir, "build.gradle.kts"))) {
|
|
12829
13320
|
return { name: "java-gradle", language: "java" };
|
|
12830
13321
|
}
|
|
12831
13322
|
return { name: "node", language: "node" };
|
|
12832
13323
|
}
|
|
12833
13324
|
function findLockfiles(projectDir, stackName) {
|
|
12834
13325
|
const candidates = STACK_LOCKFILES[stackName] ?? [];
|
|
12835
|
-
return candidates.filter((f) =>
|
|
13326
|
+
return candidates.filter((f) => existsSync34(join32(projectDir, f)));
|
|
12836
13327
|
}
|
|
12837
13328
|
|
|
12838
13329
|
// src/cli/runner-image-builder.ts
|
|
@@ -12934,12 +13425,12 @@ function generateDockerfile(stack, lockfiles) {
|
|
|
12934
13425
|
function computeLockfileHash(projectDir, lockfiles) {
|
|
12935
13426
|
const hash = createHash("sha256");
|
|
12936
13427
|
for (const lf of lockfiles) {
|
|
12937
|
-
const path =
|
|
13428
|
+
const path = join33(projectDir, lf);
|
|
12938
13429
|
hash.update(lf);
|
|
12939
13430
|
hash.update("\0");
|
|
12940
|
-
if (
|
|
13431
|
+
if (existsSync35(path)) {
|
|
12941
13432
|
try {
|
|
12942
|
-
hash.update(
|
|
13433
|
+
hash.update(readFileSync33(path));
|
|
12943
13434
|
} catch {
|
|
12944
13435
|
hash.update("<unreadable>");
|
|
12945
13436
|
}
|
|
@@ -12951,10 +13442,10 @@ function computeLockfileHash(projectDir, lockfiles) {
|
|
|
12951
13442
|
return hash.digest("hex").slice(0, 16);
|
|
12952
13443
|
}
|
|
12953
13444
|
function readLastBuild(projectDir) {
|
|
12954
|
-
const path =
|
|
12955
|
-
if (!
|
|
13445
|
+
const path = join33(projectDir, RUNNER_STATE_DIR, LAST_BUILD_FILE);
|
|
13446
|
+
if (!existsSync35(path)) return null;
|
|
12956
13447
|
try {
|
|
12957
|
-
const data = JSON.parse(
|
|
13448
|
+
const data = JSON.parse(readFileSync33(path, "utf-8"));
|
|
12958
13449
|
if (data && typeof data === "object" && typeof data.lockfileHash === "string" && typeof data.imageTag === "string" && typeof data.builtAt === "string") {
|
|
12959
13450
|
return data;
|
|
12960
13451
|
}
|
|
@@ -12973,17 +13464,17 @@ function imageTagFor(projectName, hash) {
|
|
|
12973
13464
|
return `beastmode-runner-${safeName}:${hash}`;
|
|
12974
13465
|
}
|
|
12975
13466
|
function writeStateFile(projectDir, fileName, payload) {
|
|
12976
|
-
const dir =
|
|
12977
|
-
|
|
12978
|
-
|
|
12979
|
-
|
|
13467
|
+
const dir = join33(projectDir, RUNNER_STATE_DIR);
|
|
13468
|
+
mkdirSync21(dir, { recursive: true });
|
|
13469
|
+
writeFileSync28(
|
|
13470
|
+
join33(dir, fileName),
|
|
12980
13471
|
JSON.stringify(payload, null, 2) + "\n",
|
|
12981
13472
|
"utf-8"
|
|
12982
13473
|
);
|
|
12983
13474
|
}
|
|
12984
13475
|
function buildRunnerImage(opts) {
|
|
12985
13476
|
const { projectDir, projectName, stack, lockfiles, force, dryRun } = opts;
|
|
12986
|
-
if (!
|
|
13477
|
+
if (!existsSync35(projectDir)) {
|
|
12987
13478
|
throw new Error(`Project directory not found: ${projectDir}`);
|
|
12988
13479
|
}
|
|
12989
13480
|
const lockfileHash = computeLockfileHash(projectDir, lockfiles);
|
|
@@ -13102,11 +13593,11 @@ function resolveGitHubConfig() {
|
|
|
13102
13593
|
// src/cli/runner-helpers.ts
|
|
13103
13594
|
import { execSync as execSync11, spawn, spawnSync as spawnSync5 } from "child_process";
|
|
13104
13595
|
import { promises as fs } from "fs";
|
|
13105
|
-
import { join as
|
|
13596
|
+
import { join as join34 } from "path";
|
|
13106
13597
|
init_display();
|
|
13107
13598
|
async function writeRunnerMeta(dir, meta) {
|
|
13108
13599
|
await fs.mkdir(dir, { recursive: true });
|
|
13109
|
-
const path =
|
|
13600
|
+
const path = join34(dir, "runner-meta.json");
|
|
13110
13601
|
await fs.writeFile(path, JSON.stringify(meta, null, 2) + "\n", "utf-8");
|
|
13111
13602
|
}
|
|
13112
13603
|
function resolveRepoSlug() {
|
|
@@ -13307,13 +13798,13 @@ import {
|
|
|
13307
13798
|
} from "child_process";
|
|
13308
13799
|
import {
|
|
13309
13800
|
createWriteStream,
|
|
13310
|
-
existsSync as
|
|
13311
|
-
mkdirSync as
|
|
13801
|
+
existsSync as existsSync36,
|
|
13802
|
+
mkdirSync as mkdirSync22,
|
|
13312
13803
|
unlinkSync as unlinkSync5,
|
|
13313
|
-
writeFileSync as
|
|
13804
|
+
writeFileSync as writeFileSync29
|
|
13314
13805
|
} from "fs";
|
|
13315
13806
|
import { homedir as homedir4 } from "os";
|
|
13316
|
-
import { join as
|
|
13807
|
+
import { join as join35, dirname as dirname8 } from "path";
|
|
13317
13808
|
import { Readable } from "stream";
|
|
13318
13809
|
import { pipeline } from "stream/promises";
|
|
13319
13810
|
init_display();
|
|
@@ -13343,8 +13834,8 @@ function runnerDownloadUrl(os, arch) {
|
|
|
13343
13834
|
return `https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-${ghOs}-${arch}-${RUNNER_VERSION}.tar.gz`;
|
|
13344
13835
|
}
|
|
13345
13836
|
async function downloadAndExtractRunner(installDir, os, arch) {
|
|
13346
|
-
const runShPath =
|
|
13347
|
-
if (
|
|
13837
|
+
const runShPath = join35(installDir, "run.sh");
|
|
13838
|
+
if (existsSync36(runShPath)) {
|
|
13348
13839
|
info(
|
|
13349
13840
|
`Runner binary already present at ${runShPath} \u2014 skipping download.`
|
|
13350
13841
|
);
|
|
@@ -13354,7 +13845,7 @@ async function downloadAndExtractRunner(installDir, os, arch) {
|
|
|
13354
13845
|
return;
|
|
13355
13846
|
}
|
|
13356
13847
|
const url = runnerDownloadUrl(os, arch);
|
|
13357
|
-
const tarball =
|
|
13848
|
+
const tarball = join35(installDir, "runner.tar.gz");
|
|
13358
13849
|
setupStep(
|
|
13359
13850
|
`Downloading GitHub Actions runner v${RUNNER_VERSION} (${os}/${arch})...`
|
|
13360
13851
|
);
|
|
@@ -13405,7 +13896,7 @@ async function configureRunner(installDir, repoUrl, token, name, labels) {
|
|
|
13405
13896
|
"--unattended",
|
|
13406
13897
|
"--replace"
|
|
13407
13898
|
];
|
|
13408
|
-
const result = spawnSync6(
|
|
13899
|
+
const result = spawnSync6(join35(installDir, "config.sh"), args, {
|
|
13409
13900
|
cwd: installDir,
|
|
13410
13901
|
stdio: ["ignore", "inherit", "pipe"],
|
|
13411
13902
|
encoding: "utf-8"
|
|
@@ -13433,7 +13924,7 @@ function startRunnerForeground(installDir) {
|
|
|
13433
13924
|
"Use --service to install as a launchd agent for persistent operation."
|
|
13434
13925
|
);
|
|
13435
13926
|
}
|
|
13436
|
-
spawn2(
|
|
13927
|
+
spawn2(join35(installDir, "run.sh"), [], {
|
|
13437
13928
|
cwd: installDir,
|
|
13438
13929
|
stdio: "inherit"
|
|
13439
13930
|
});
|
|
@@ -13458,10 +13949,10 @@ WantedBy=default.target
|
|
|
13458
13949
|
}
|
|
13459
13950
|
async function installSystemdService(installDir, name) {
|
|
13460
13951
|
const unitName = `beastmode-runner-${name}.service`;
|
|
13461
|
-
const unitDir =
|
|
13462
|
-
|
|
13463
|
-
|
|
13464
|
-
|
|
13952
|
+
const unitDir = join35(homedir4(), ".config", "systemd", "user");
|
|
13953
|
+
mkdirSync22(unitDir, { recursive: true });
|
|
13954
|
+
writeFileSync29(
|
|
13955
|
+
join35(unitDir, unitName),
|
|
13465
13956
|
systemdUnitContent(installDir, name),
|
|
13466
13957
|
"utf-8"
|
|
13467
13958
|
);
|
|
@@ -13515,15 +14006,15 @@ function launchdPlistContent(installDir, name) {
|
|
|
13515
14006
|
}
|
|
13516
14007
|
async function installLaunchdService(installDir, name) {
|
|
13517
14008
|
const label = `com.beastmode.runner.${name}`;
|
|
13518
|
-
const plistPath2 =
|
|
14009
|
+
const plistPath2 = join35(
|
|
13519
14010
|
homedir4(),
|
|
13520
14011
|
"Library",
|
|
13521
14012
|
"LaunchAgents",
|
|
13522
14013
|
`${label}.plist`
|
|
13523
14014
|
);
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
14015
|
+
mkdirSync22(dirname8(plistPath2), { recursive: true });
|
|
14016
|
+
mkdirSync22(join35(installDir, "logs"), { recursive: true });
|
|
14017
|
+
writeFileSync29(plistPath2, launchdPlistContent(installDir, name), "utf-8");
|
|
13527
14018
|
try {
|
|
13528
14019
|
execSync12(`launchctl load ${plistPath2}`, { stdio: "inherit" });
|
|
13529
14020
|
} catch (err) {
|
|
@@ -13564,7 +14055,7 @@ async function nativeRunnerSetup(opts) {
|
|
|
13564
14055
|
}
|
|
13565
14056
|
const ghConfig = resolveGitHubConfig();
|
|
13566
14057
|
const repoSlug = opts.repo ?? resolveRepoSlug();
|
|
13567
|
-
const installDir =
|
|
14058
|
+
const installDir = join35(homedir4(), ".beastmode", "runners", opts.name);
|
|
13568
14059
|
if (opts.dryRun) {
|
|
13569
14060
|
info(
|
|
13570
14061
|
`[dry-run] Would install native runner '${opts.name}' for repo ${repoSlug}`
|
|
@@ -13574,7 +14065,7 @@ async function nativeRunnerSetup(opts) {
|
|
|
13574
14065
|
info(`[dry-run] Mode: ${opts.service ? "service" : "foreground"}`);
|
|
13575
14066
|
return;
|
|
13576
14067
|
}
|
|
13577
|
-
|
|
14068
|
+
mkdirSync22(installDir, { recursive: true });
|
|
13578
14069
|
setupStep("Generating registration token via GitHub API...");
|
|
13579
14070
|
const { token: regToken } = await createRegistrationToken(ghConfig);
|
|
13580
14071
|
await downloadAndExtractRunner(installDir, platformOs, arch);
|
|
@@ -13612,13 +14103,13 @@ async function nativeRunnerSetup(opts) {
|
|
|
13612
14103
|
|
|
13613
14104
|
// src/cli/workflow-switcher.ts
|
|
13614
14105
|
import {
|
|
13615
|
-
existsSync as
|
|
13616
|
-
mkdirSync as
|
|
13617
|
-
readFileSync as
|
|
14106
|
+
existsSync as existsSync37,
|
|
14107
|
+
mkdirSync as mkdirSync23,
|
|
14108
|
+
readFileSync as readFileSync34,
|
|
13618
14109
|
unlinkSync as unlinkSync6,
|
|
13619
|
-
writeFileSync as
|
|
14110
|
+
writeFileSync as writeFileSync30
|
|
13620
14111
|
} from "fs";
|
|
13621
|
-
import { dirname as dirname9, join as
|
|
14112
|
+
import { dirname as dirname9, join as join36 } from "path";
|
|
13622
14113
|
var TARGET_LABEL = "[self-hosted, beastmode]";
|
|
13623
14114
|
var TARGET_WORKFLOWS = [
|
|
13624
14115
|
".github/workflows/test.yml",
|
|
@@ -13657,8 +14148,8 @@ function restoreRunsOn(content, originals) {
|
|
|
13657
14148
|
return newLines.join("\n");
|
|
13658
14149
|
}
|
|
13659
14150
|
async function switchWorkflows(projectDir) {
|
|
13660
|
-
const statePath =
|
|
13661
|
-
if (
|
|
14151
|
+
const statePath = join36(projectDir, STATE_FILE);
|
|
14152
|
+
if (existsSync37(statePath)) {
|
|
13662
14153
|
return { alreadySwitched: true, files: [] };
|
|
13663
14154
|
}
|
|
13664
14155
|
const state = {
|
|
@@ -13668,40 +14159,40 @@ async function switchWorkflows(projectDir) {
|
|
|
13668
14159
|
};
|
|
13669
14160
|
const resultFiles = [];
|
|
13670
14161
|
for (const relPath of TARGET_WORKFLOWS) {
|
|
13671
|
-
const absPath =
|
|
13672
|
-
if (!
|
|
14162
|
+
const absPath = join36(projectDir, relPath);
|
|
14163
|
+
if (!existsSync37(absPath)) {
|
|
13673
14164
|
throw new Error(`Workflow file not found: ${relPath}`);
|
|
13674
14165
|
}
|
|
13675
|
-
const content =
|
|
14166
|
+
const content = readFileSync34(absPath, "utf-8");
|
|
13676
14167
|
const { newContent, originals } = replaceRunsOn(content, TARGET_LABEL);
|
|
13677
14168
|
if (originals.length === 0) {
|
|
13678
14169
|
throw new Error(`No runs-on found in ${relPath}`);
|
|
13679
14170
|
}
|
|
13680
|
-
|
|
14171
|
+
writeFileSync30(absPath, newContent, "utf-8");
|
|
13681
14172
|
state.files.push({ relativePath: relPath, originals });
|
|
13682
14173
|
resultFiles.push({ relativePath: relPath, jobCount: originals.length });
|
|
13683
14174
|
}
|
|
13684
|
-
|
|
13685
|
-
|
|
14175
|
+
mkdirSync23(dirname9(statePath), { recursive: true });
|
|
14176
|
+
writeFileSync30(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
13686
14177
|
return { alreadySwitched: false, files: resultFiles };
|
|
13687
14178
|
}
|
|
13688
14179
|
async function restoreWorkflows(projectDir) {
|
|
13689
|
-
const statePath =
|
|
13690
|
-
if (!
|
|
14180
|
+
const statePath = join36(projectDir, STATE_FILE);
|
|
14181
|
+
if (!existsSync37(statePath)) {
|
|
13691
14182
|
return { nothingToRestore: true, files: [] };
|
|
13692
14183
|
}
|
|
13693
14184
|
const state = JSON.parse(
|
|
13694
|
-
|
|
14185
|
+
readFileSync34(statePath, "utf-8")
|
|
13695
14186
|
);
|
|
13696
14187
|
const resultFiles = [];
|
|
13697
14188
|
for (const fileState of state.files) {
|
|
13698
|
-
const absPath =
|
|
13699
|
-
if (!
|
|
14189
|
+
const absPath = join36(projectDir, fileState.relativePath);
|
|
14190
|
+
if (!existsSync37(absPath)) {
|
|
13700
14191
|
throw new Error(`Workflow file not found: ${fileState.relativePath}`);
|
|
13701
14192
|
}
|
|
13702
|
-
const content =
|
|
14193
|
+
const content = readFileSync34(absPath, "utf-8");
|
|
13703
14194
|
const newContent = restoreRunsOn(content, fileState.originals);
|
|
13704
|
-
|
|
14195
|
+
writeFileSync30(absPath, newContent, "utf-8");
|
|
13705
14196
|
resultFiles.push({
|
|
13706
14197
|
relativePath: fileState.relativePath,
|
|
13707
14198
|
jobCount: fileState.originals.length
|
|
@@ -13714,25 +14205,19 @@ async function restoreWorkflows(projectDir) {
|
|
|
13714
14205
|
// src/cli/commands/runner-cmd.ts
|
|
13715
14206
|
var runnerCommand = new Command23("runner").description("Manage self-hosted GitHub Actions runners");
|
|
13716
14207
|
function resolveProjectName(projectDir) {
|
|
13717
|
-
const projectsDir =
|
|
13718
|
-
if (
|
|
14208
|
+
const projectsDir = join37(projectDir, ".beastmode", "projects");
|
|
14209
|
+
if (existsSync38(projectsDir)) {
|
|
13719
14210
|
try {
|
|
13720
|
-
|
|
13721
|
-
|
|
13722
|
-
|
|
13723
|
-
|
|
13724
|
-
|
|
13725
|
-
|
|
13726
|
-
if (typeof data.path === "string" && resolve20(data.path) === resolve20(projectDir) && typeof data.name === "string" && data.name.length > 0) {
|
|
13727
|
-
return data.name;
|
|
13728
|
-
}
|
|
13729
|
-
} catch {
|
|
13730
|
-
}
|
|
13731
|
-
}
|
|
14211
|
+
const records = listProjectRecords(projectsDir);
|
|
14212
|
+
const target = resolve20(projectDir);
|
|
14213
|
+
const match = records.find(
|
|
14214
|
+
(r) => typeof r.path === "string" && resolve20(r.path) === target
|
|
14215
|
+
);
|
|
14216
|
+
if (match && match.name) return match.name;
|
|
13732
14217
|
} catch {
|
|
13733
14218
|
}
|
|
13734
14219
|
}
|
|
13735
|
-
return
|
|
14220
|
+
return basename7(resolve20(projectDir));
|
|
13736
14221
|
}
|
|
13737
14222
|
async function runnerSetupAction(opts) {
|
|
13738
14223
|
if (opts.service !== void 0 && !opts.native) {
|
|
@@ -14057,7 +14542,7 @@ runnerCommand.command("restore-workflows").description("Restore workflows to ori
|
|
|
14057
14542
|
});
|
|
14058
14543
|
async function runnerBuildImageAction(opts) {
|
|
14059
14544
|
const projectDir = resolve20(opts.projectDir);
|
|
14060
|
-
if (!
|
|
14545
|
+
if (!existsSync38(projectDir)) {
|
|
14061
14546
|
throw new Error(`Project directory not found: ${projectDir}`);
|
|
14062
14547
|
}
|
|
14063
14548
|
const stack = detectRunnerStack(projectDir);
|
|
@@ -14107,25 +14592,14 @@ runnerCommand.command("build-image").description(
|
|
|
14107
14592
|
// src/cli/commands/project-cmd.ts
|
|
14108
14593
|
init_engine();
|
|
14109
14594
|
import { Command as Command24 } from "commander";
|
|
14110
|
-
import { existsSync as
|
|
14111
|
-
import { join as
|
|
14595
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync24, readFileSync as readFileSync35, renameSync as renameSync3 } from "fs";
|
|
14596
|
+
import { join as join38, resolve as resolve21, basename as basename8 } from "path";
|
|
14112
14597
|
import { execSync as execSync13 } from "child_process";
|
|
14113
14598
|
var DEFAULT_MAX_PROJECTS = 5;
|
|
14114
14599
|
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
14600
|
function getMaxProjects(factoryDir) {
|
|
14127
|
-
const configPath =
|
|
14128
|
-
if (
|
|
14601
|
+
const configPath = join38(factoryDir, ".beastmode", "config.json");
|
|
14602
|
+
if (existsSync39(configPath)) {
|
|
14129
14603
|
try {
|
|
14130
14604
|
const config = JSON.parse(readFileSync35(configPath, "utf-8"));
|
|
14131
14605
|
if (typeof config.max_projects === "number") return config.max_projects;
|
|
@@ -14146,11 +14620,13 @@ function getFreeDiskGB() {
|
|
|
14146
14620
|
}
|
|
14147
14621
|
function projectAddAction(factoryDir, projectPath, opts) {
|
|
14148
14622
|
const resolvedPath = resolve21(projectPath);
|
|
14149
|
-
if (!
|
|
14150
|
-
const projectName = opts.name ||
|
|
14151
|
-
const projectsDir =
|
|
14152
|
-
if (
|
|
14153
|
-
|
|
14623
|
+
if (!existsSync39(resolvedPath)) throw new Error(`Directory not found: ${resolvedPath}`);
|
|
14624
|
+
const projectName = opts.name || basename8(resolvedPath);
|
|
14625
|
+
const projectsDir = join38(factoryDir, ".beastmode", "projects");
|
|
14626
|
+
if (existsSync39(join38(projectsDir, projectName, "project.json"))) {
|
|
14627
|
+
throw new Error(`Project already exists: ${projectName}`);
|
|
14628
|
+
}
|
|
14629
|
+
const currentCount = listProjectRecords(projectsDir).length;
|
|
14154
14630
|
const maxProjects = getMaxProjects(factoryDir);
|
|
14155
14631
|
if (currentCount >= maxProjects) {
|
|
14156
14632
|
throw new Error(
|
|
@@ -14163,86 +14639,57 @@ function projectAddAction(factoryDir, projectPath, opts) {
|
|
|
14163
14639
|
`Warning: only ${freeGB}GB free disk space. Each project with worktrees may use 1-2GB. Consider freeing space.`
|
|
14164
14640
|
);
|
|
14165
14641
|
}
|
|
14166
|
-
|
|
14167
|
-
let
|
|
14642
|
+
let detectedStack = null;
|
|
14643
|
+
let gitRemote;
|
|
14168
14644
|
try {
|
|
14169
|
-
|
|
14645
|
+
detectedStack = detectStack(resolvedPath);
|
|
14646
|
+
if (detectedStack.git_remote) gitRemote = detectedStack.git_remote;
|
|
14170
14647
|
} catch {
|
|
14171
14648
|
}
|
|
14172
14649
|
let verifyPort = 3001;
|
|
14173
|
-
const
|
|
14174
|
-
|
|
14175
|
-
|
|
14176
|
-
|
|
14177
|
-
|
|
14178
|
-
|
|
14179
|
-
|
|
14180
|
-
|
|
14181
|
-
|
|
14182
|
-
|
|
14183
|
-
|
|
14184
|
-
|
|
14185
|
-
|
|
14186
|
-
|
|
14187
|
-
|
|
14650
|
+
for (const existing of listProjectRecords(projectsDir)) {
|
|
14651
|
+
const port = existing.deploy?.verify_port;
|
|
14652
|
+
if (typeof port === "number" && port >= verifyPort) verifyPort = port + 1;
|
|
14653
|
+
}
|
|
14654
|
+
const boardId = opts.boardId ? parseInt(opts.boardId, 10) : null;
|
|
14655
|
+
const record = createProjectRecord({ name: projectName, resolvedPath, boardId, verifyPort, gitRemote });
|
|
14656
|
+
if (detectedStack) {
|
|
14657
|
+
record.stack = {
|
|
14658
|
+
detected: detectedStack.framework,
|
|
14659
|
+
build_command: detectedStack.suggested_commands.build,
|
|
14660
|
+
dev_command: detectedStack.suggested_commands.dev,
|
|
14661
|
+
test_command: detectedStack.suggested_commands.test,
|
|
14662
|
+
install_command: detectedStack.suggested_commands.install,
|
|
14663
|
+
dev_port: detectedStack.dev_port,
|
|
14664
|
+
is_monorepo: detectedStack.is_monorepo,
|
|
14665
|
+
total_packages: detectedStack.total_packages,
|
|
14666
|
+
primary_languages: detectedStack.primary_languages,
|
|
14667
|
+
...detectedStack.packages ? { packages: detectedStack.packages } : {}
|
|
14668
|
+
};
|
|
14188
14669
|
}
|
|
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) {
|
|
14670
|
+
writeProjectRecord(projectsDir, projectName, record);
|
|
14671
|
+
const runsDir = join38(factoryDir, "runs", projectName);
|
|
14672
|
+
if (!existsSync39(runsDir)) mkdirSync24(runsDir, { recursive: true });
|
|
14673
|
+
if (!boardId) {
|
|
14216
14674
|
void (async () => {
|
|
14217
14675
|
try {
|
|
14218
|
-
const boardUrl =
|
|
14676
|
+
const boardUrl = record.board.url;
|
|
14219
14677
|
const http4 = await import("http");
|
|
14220
14678
|
const postData = JSON.stringify({ project_name: projectName });
|
|
14221
|
-
await new Promise((
|
|
14679
|
+
await new Promise((res) => {
|
|
14222
14680
|
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 {
|
|
14681
|
+
const req = http4.request(
|
|
14682
|
+
url,
|
|
14683
|
+
{
|
|
14684
|
+
method: "POST",
|
|
14685
|
+
headers: {
|
|
14686
|
+
"Content-Type": "application/json",
|
|
14687
|
+
"Content-Length": Buffer.byteLength(postData).toString()
|
|
14241
14688
|
}
|
|
14242
|
-
|
|
14243
|
-
|
|
14244
|
-
|
|
14245
|
-
req.on("error", () =>
|
|
14689
|
+
},
|
|
14690
|
+
() => res()
|
|
14691
|
+
);
|
|
14692
|
+
req.on("error", () => res());
|
|
14246
14693
|
req.end(postData);
|
|
14247
14694
|
});
|
|
14248
14695
|
} catch {
|
|
@@ -14251,28 +14698,21 @@ function projectAddAction(factoryDir, projectPath, opts) {
|
|
|
14251
14698
|
}
|
|
14252
14699
|
}
|
|
14253
14700
|
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);
|
|
14701
|
+
const projectsDir = join38(factoryDir, ".beastmode", "projects");
|
|
14702
|
+
return listProjectRecords(projectsDir);
|
|
14263
14703
|
}
|
|
14264
14704
|
function projectRemoveAction(factoryDir, name) {
|
|
14265
|
-
const projectDir =
|
|
14266
|
-
if (!
|
|
14267
|
-
const
|
|
14268
|
-
|
|
14269
|
-
|
|
14705
|
+
const projectDir = join38(factoryDir, ".beastmode", "projects", name);
|
|
14706
|
+
if (!existsSync39(projectDir)) throw new Error(`Project not found: ${name}`);
|
|
14707
|
+
const archivedBase = join38(factoryDir, ".beastmode", "projects", ".archived");
|
|
14708
|
+
mkdirSync24(archivedBase, { recursive: true });
|
|
14709
|
+
renameSync3(projectDir, join38(archivedBase, name));
|
|
14270
14710
|
}
|
|
14271
14711
|
var projectCommand = new Command24("project").description("Manage projects in this factory");
|
|
14272
14712
|
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
14713
|
const factoryDir = resolve21(".");
|
|
14274
14714
|
projectAddAction(factoryDir, path, opts);
|
|
14275
|
-
console.log(`Project registered: ${opts.name ||
|
|
14715
|
+
console.log(`Project registered: ${opts.name || basename8(resolve21(path))}`);
|
|
14276
14716
|
});
|
|
14277
14717
|
projectCommand.command("list").description("List registered projects").action(() => {
|
|
14278
14718
|
const projects = projectListAction(resolve21("."));
|
|
@@ -14281,7 +14721,7 @@ projectCommand.command("list").description("List registered projects").action(()
|
|
|
14281
14721
|
return;
|
|
14282
14722
|
}
|
|
14283
14723
|
for (const p of projects) {
|
|
14284
|
-
console.log(` ${p
|
|
14724
|
+
console.log(` ${p["name"]} \u2014 ${p["path"]}`);
|
|
14285
14725
|
}
|
|
14286
14726
|
});
|
|
14287
14727
|
projectCommand.command("remove <name>").description("Archive a project").action((name) => {
|