@beastmode-develeap/beastmode 0.1.361 → 0.1.363
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 +54 -24
- package/dist/index.js.map +1 -1
- package/dist/web/board.html +14 -5
- 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
|
@@ -5282,33 +5282,51 @@ var init_chat_handler = __esm({
|
|
|
5282
5282
|
|
|
5283
5283
|
// src/cli/ui/board-api-routes.ts
|
|
5284
5284
|
import { readFileSync as readFileSync14, writeFileSync as writeFileSync13, existsSync as existsSync16, readdirSync as readdirSync8, unlinkSync as unlinkSync4, mkdirSync as mkdirSync12, statSync as statSync6, rmSync as rmSync3 } from "fs";
|
|
5285
|
-
import { join as join15, basename as basename5, resolve as resolve4, dirname as dirname6, sep } from "path";
|
|
5285
|
+
import { join as join15, basename as basename5, resolve as resolve4, dirname as dirname6, sep, delimiter } from "path";
|
|
5286
5286
|
import { homedir } from "os";
|
|
5287
5287
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
5288
5288
|
import { execSync as execSync3, spawnSync } from "child_process";
|
|
5289
5289
|
import http2 from "http";
|
|
5290
|
+
function _beastmodeRoot() {
|
|
5291
|
+
return process.env.BEASTMODE_ROOT && process.env.BEASTMODE_ROOT.length > 0 ? process.env.BEASTMODE_ROOT : "/app";
|
|
5292
|
+
}
|
|
5290
5293
|
function _getAllowedRoots() {
|
|
5291
|
-
const
|
|
5294
|
+
const candidates = [];
|
|
5295
|
+
const allowlistEnv = process.env.BEASTMODE_FS_ALLOWLIST;
|
|
5296
|
+
if (allowlistEnv && allowlistEnv.length > 0) {
|
|
5297
|
+
for (const entry of allowlistEnv.split(delimiter)) {
|
|
5298
|
+
if (entry && entry.length > 0) candidates.push(entry);
|
|
5299
|
+
}
|
|
5300
|
+
}
|
|
5301
|
+
const root = _beastmodeRoot();
|
|
5302
|
+
candidates.push(join15(root, "project"));
|
|
5303
|
+
candidates.push(root);
|
|
5292
5304
|
const homeRaw = process.env.HOME && process.env.HOME.length > 0 ? process.env.HOME : homedir();
|
|
5293
|
-
|
|
5305
|
+
candidates.push(homeRaw);
|
|
5294
5306
|
const projectDir = process.env.PROJECT_DIR;
|
|
5295
|
-
if (projectDir && projectDir.length > 0)
|
|
5296
|
-
roots.push(resolve4(projectDir));
|
|
5297
|
-
}
|
|
5307
|
+
if (projectDir && projectDir.length > 0) candidates.push(projectDir);
|
|
5298
5308
|
const cloneDir = process.env.BEASTMODE_CLONE_DIR;
|
|
5299
|
-
if (cloneDir && cloneDir.length > 0)
|
|
5300
|
-
roots.push(resolve4(cloneDir));
|
|
5301
|
-
}
|
|
5309
|
+
if (cloneDir && cloneDir.length > 0) candidates.push(cloneDir);
|
|
5302
5310
|
const seen = /* @__PURE__ */ new Set();
|
|
5303
5311
|
const deduped = [];
|
|
5304
|
-
for (const
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5312
|
+
for (const c of candidates) {
|
|
5313
|
+
const r = resolve4(c);
|
|
5314
|
+
if (!existsSync16(r)) continue;
|
|
5315
|
+
if (seen.has(r)) continue;
|
|
5316
|
+
seen.add(r);
|
|
5317
|
+
deduped.push(r);
|
|
5309
5318
|
}
|
|
5310
5319
|
return deduped;
|
|
5311
5320
|
}
|
|
5321
|
+
function _pickDefaultRoot(roots) {
|
|
5322
|
+
const root = _beastmodeRoot();
|
|
5323
|
+
const preferred = [resolve4(join15(root, "project")), resolve4(root)];
|
|
5324
|
+
for (const p of preferred) {
|
|
5325
|
+
if (roots.includes(p)) return p;
|
|
5326
|
+
}
|
|
5327
|
+
if (roots.length > 0) return roots[0];
|
|
5328
|
+
return resolve4(homedir());
|
|
5329
|
+
}
|
|
5312
5330
|
function _isPathAllowed(targetPath, roots) {
|
|
5313
5331
|
if (roots.length === 0) return false;
|
|
5314
5332
|
const target = resolve4(targetPath);
|
|
@@ -6354,10 +6372,15 @@ function getBoardRoutes(factoryDir) {
|
|
|
6354
6372
|
{
|
|
6355
6373
|
// Filesystem browser for the Add Project picker.
|
|
6356
6374
|
//
|
|
6357
|
-
// POST body: { path?: string } — absolute directory to list
|
|
6375
|
+
// POST body: { path?: string } — absolute directory to list. An omitted
|
|
6376
|
+
// or empty path opens at a known-good default root (the mounted project
|
|
6377
|
+
// root, e.g. /app/project), never an empty $HOME (#969 FR-3).
|
|
6358
6378
|
//
|
|
6359
|
-
// Returns { path, parent, entries: [{name, path, is_dir, has_git}] }
|
|
6379
|
+
// Returns { path, parent, entries: [{name, path, is_dir, has_git}], roots }
|
|
6380
|
+
// where `roots` is the existence-gated allowlist for the picker chips.
|
|
6360
6381
|
// Only directories are returned (the picker only selects dirs).
|
|
6382
|
+
// User-input errors (disallowed/missing/not-a-dir) return 4xx via
|
|
6383
|
+
// HttpError, never 500 (#969 FR-2).
|
|
6361
6384
|
// This is behind the board's password auth, so it trusts the caller
|
|
6362
6385
|
// with full filesystem read access — the daemon's trust boundary
|
|
6363
6386
|
// already allows cloning arbitrary repos and reading arbitrary code.
|
|
@@ -6365,17 +6388,21 @@ function getBoardRoutes(factoryDir) {
|
|
|
6365
6388
|
pattern: "/api/filesystem/browse",
|
|
6366
6389
|
handler: (body) => {
|
|
6367
6390
|
const { path: queryPath } = body || {};
|
|
6368
|
-
const startPath = queryPath || homedir();
|
|
6369
|
-
const target = resolve4(startPath);
|
|
6370
6391
|
const allowedRoots = _getAllowedRoots();
|
|
6392
|
+
const startPath = queryPath && queryPath.length > 0 ? queryPath : _pickDefaultRoot(allowedRoots);
|
|
6393
|
+
const target = resolve4(startPath);
|
|
6371
6394
|
if (!_isPathAllowed(target, allowedRoots)) {
|
|
6372
|
-
throw new
|
|
6373
|
-
`Path not allowed: ${target} is outside the configured allowlist`
|
|
6374
|
-
);
|
|
6395
|
+
throw new HttpError(400, {
|
|
6396
|
+
error: `Path not allowed: ${target} is outside the configured allowlist`
|
|
6397
|
+
});
|
|
6398
|
+
}
|
|
6399
|
+
if (!existsSync16(target)) {
|
|
6400
|
+
throw new HttpError(404, { error: `Path not found: ${target}` });
|
|
6375
6401
|
}
|
|
6376
|
-
if (!existsSync16(target)) throw new Error(`Path not found: ${target}`);
|
|
6377
6402
|
const st = statSync6(target);
|
|
6378
|
-
if (!st.isDirectory())
|
|
6403
|
+
if (!st.isDirectory()) {
|
|
6404
|
+
throw new HttpError(400, { error: `Not a directory: ${target}` });
|
|
6405
|
+
}
|
|
6379
6406
|
let entries = [];
|
|
6380
6407
|
try {
|
|
6381
6408
|
entries = readdirSync8(target, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => {
|
|
@@ -6394,7 +6421,10 @@ function getBoardRoutes(factoryDir) {
|
|
|
6394
6421
|
return {
|
|
6395
6422
|
path: target,
|
|
6396
6423
|
parent: parent !== target ? parent : null,
|
|
6397
|
-
entries
|
|
6424
|
+
entries,
|
|
6425
|
+
// FR-3: surface the allowed roots so the picker can render them as
|
|
6426
|
+
// clickable navigation chips.
|
|
6427
|
+
roots: allowedRoots
|
|
6398
6428
|
};
|
|
6399
6429
|
}
|
|
6400
6430
|
},
|