@beastmode-develeap/beastmode 0.1.35 → 0.1.37
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 +112 -3
- package/dist/index.js.map +1 -1
- package/dist/web/board.html +64 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4583,6 +4583,47 @@ function proxyToBoard(boardUrl, method, path, body, query) {
|
|
|
4583
4583
|
req.end();
|
|
4584
4584
|
});
|
|
4585
4585
|
}
|
|
4586
|
+
function proxyBinaryToBoard(boardUrl, path, query) {
|
|
4587
|
+
return new Promise((resolve20, reject) => {
|
|
4588
|
+
const url = new URL(path, boardUrl);
|
|
4589
|
+
if (query) {
|
|
4590
|
+
for (const [k, v] of Object.entries(query)) {
|
|
4591
|
+
if (v !== void 0 && v !== null && v !== "") {
|
|
4592
|
+
url.searchParams.set(k, v);
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4596
|
+
const req = http2.request(
|
|
4597
|
+
url,
|
|
4598
|
+
{ method: "GET" },
|
|
4599
|
+
(res) => {
|
|
4600
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
4601
|
+
reject(
|
|
4602
|
+
new Error(
|
|
4603
|
+
`Board proxy error: HTTP ${res.statusCode} for ${path}`
|
|
4604
|
+
)
|
|
4605
|
+
);
|
|
4606
|
+
return;
|
|
4607
|
+
}
|
|
4608
|
+
const chunks = [];
|
|
4609
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
4610
|
+
res.on("end", () => {
|
|
4611
|
+
const body = Buffer.concat(chunks);
|
|
4612
|
+
const contentType = res.headers["content-type"] || "application/octet-stream";
|
|
4613
|
+
const cd = res.headers["content-disposition"] || "";
|
|
4614
|
+
const match = /filename="([^"]+)"/.exec(cd);
|
|
4615
|
+
const filename = match ? match[1] : void 0;
|
|
4616
|
+
resolve20(new BinaryResponse(body, contentType, filename));
|
|
4617
|
+
});
|
|
4618
|
+
}
|
|
4619
|
+
);
|
|
4620
|
+
req.on(
|
|
4621
|
+
"error",
|
|
4622
|
+
(err) => reject(new Error(`Board proxy error: ${err.message}`))
|
|
4623
|
+
);
|
|
4624
|
+
req.end();
|
|
4625
|
+
});
|
|
4626
|
+
}
|
|
4586
4627
|
function scopedQuery(query) {
|
|
4587
4628
|
if (!query) return void 0;
|
|
4588
4629
|
const b = query.board;
|
|
@@ -4974,6 +5015,40 @@ function getBoardRoutes(factoryDir) {
|
|
|
4974
5015
|
return proxyToBoard(boardUrl, "POST", `/api/items/${params.id}/updates`, body, scopedQuery(query));
|
|
4975
5016
|
}
|
|
4976
5017
|
},
|
|
5018
|
+
// ── Attachments (Gap 8a — 2026-04-15) ──
|
|
5019
|
+
// The board service stores per-item attachments (screenshots from
|
|
5020
|
+
// verify_production.py, uploaded files from the "new task"
|
|
5021
|
+
// dialog, etc.) at /app/data/attachments/<project>/<item>/<id>_<name>.
|
|
5022
|
+
// Before this proxy existed, the UI had no way to list or fetch
|
|
5023
|
+
// them, which meant Story 4's Done Evidence message said "2
|
|
5024
|
+
// screenshots attached" but the UI showed nothing. See
|
|
5025
|
+
// docs/zero-to-productive-readiness.md Gap 8a.
|
|
5026
|
+
{
|
|
5027
|
+
method: "GET",
|
|
5028
|
+
pattern: "/api/board/items/:id/attachments",
|
|
5029
|
+
handler: async (_body, params, query) => {
|
|
5030
|
+
const boardUrl = getBoardUrl2(factoryDir);
|
|
5031
|
+
return proxyToBoard(
|
|
5032
|
+
boardUrl,
|
|
5033
|
+
"GET",
|
|
5034
|
+
`/api/items/${params.id}/attachments`,
|
|
5035
|
+
void 0,
|
|
5036
|
+
scopedQuery(query)
|
|
5037
|
+
);
|
|
5038
|
+
}
|
|
5039
|
+
},
|
|
5040
|
+
{
|
|
5041
|
+
method: "GET",
|
|
5042
|
+
pattern: "/api/board/attachments/:id/download",
|
|
5043
|
+
handler: async (_body, params, query) => {
|
|
5044
|
+
const boardUrl = getBoardUrl2(factoryDir);
|
|
5045
|
+
return proxyBinaryToBoard(
|
|
5046
|
+
boardUrl,
|
|
5047
|
+
`/api/attachments/${params.id}/download`,
|
|
5048
|
+
scopedQuery(query)
|
|
5049
|
+
);
|
|
5050
|
+
}
|
|
5051
|
+
},
|
|
4977
5052
|
// ── Replies ──
|
|
4978
5053
|
{
|
|
4979
5054
|
method: "GET",
|
|
@@ -6103,13 +6178,20 @@ function matchBoardRoute(routes, method, url) {
|
|
|
6103
6178
|
}
|
|
6104
6179
|
return null;
|
|
6105
6180
|
}
|
|
6106
|
-
var _TERMINAL_STAGES;
|
|
6181
|
+
var BinaryResponse, _TERMINAL_STAGES;
|
|
6107
6182
|
var init_board_api_routes = __esm({
|
|
6108
6183
|
"src/cli/ui/board-api-routes.ts"() {
|
|
6109
6184
|
"use strict";
|
|
6110
6185
|
init_archival();
|
|
6111
6186
|
init_chat_handler();
|
|
6112
6187
|
init_engine();
|
|
6188
|
+
BinaryResponse = class {
|
|
6189
|
+
constructor(body, contentType, filename) {
|
|
6190
|
+
this.body = body;
|
|
6191
|
+
this.contentType = contentType;
|
|
6192
|
+
this.filename = filename;
|
|
6193
|
+
}
|
|
6194
|
+
};
|
|
6113
6195
|
_TERMINAL_STAGES = /* @__PURE__ */ new Set([
|
|
6114
6196
|
"done",
|
|
6115
6197
|
"shipped",
|
|
@@ -6350,7 +6432,19 @@ async function startServer(options = {}) {
|
|
|
6350
6432
|
body = await parseBody(req);
|
|
6351
6433
|
}
|
|
6352
6434
|
const result = await boardMatch.route.handler(body, boardMatch.params, query);
|
|
6353
|
-
|
|
6435
|
+
if (result instanceof BinaryResponse) {
|
|
6436
|
+
const headers = {
|
|
6437
|
+
"Content-Type": result.contentType,
|
|
6438
|
+
"Content-Length": result.body.length
|
|
6439
|
+
};
|
|
6440
|
+
if (result.filename) {
|
|
6441
|
+
headers["Content-Disposition"] = `inline; filename="${result.filename.replace(/"/g, "")}"`;
|
|
6442
|
+
}
|
|
6443
|
+
res.writeHead(200, headers);
|
|
6444
|
+
res.end(result.body);
|
|
6445
|
+
} else {
|
|
6446
|
+
sendJson(res, 200, result);
|
|
6447
|
+
}
|
|
6354
6448
|
} catch (err) {
|
|
6355
6449
|
const message = err instanceof Error ? err.message : "Internal server error";
|
|
6356
6450
|
sendJson(res, 500, { error: message });
|
|
@@ -7224,7 +7318,13 @@ function generateComposeYaml(tag) {
|
|
|
7224
7318
|
- ./config:/app/config
|
|
7225
7319
|
- ./runs:/app/runs
|
|
7226
7320
|
- ./daemon/logs:/app/daemon/logs:ro
|
|
7227
|
-
|
|
7321
|
+
# Claude credentials for the chat feature. Target /home/appuser/.claude
|
|
7322
|
+
# matches the ui image's USER appuser (cli/Dockerfile sets HOME to
|
|
7323
|
+
# /home/appuser); the entrypoint restores .claude.json from the
|
|
7324
|
+
# newest backup in this dir at startup so chat works across container
|
|
7325
|
+
# restarts. Pre-2026-04-15 this used /root/.claude, which silently
|
|
7326
|
+
# broke chat because appuser couldn't read those files.
|
|
7327
|
+
- \${HOME}/.claude:/home/appuser/.claude:ro
|
|
7228
7328
|
depends_on:
|
|
7229
7329
|
board:
|
|
7230
7330
|
condition: service_healthy
|
|
@@ -7243,6 +7343,15 @@ function generateComposeYaml(tag) {
|
|
|
7243
7343
|
- BEASTMODE_ROOT=/app
|
|
7244
7344
|
- BEASTMODE_BOARD_URL=http://board:8080
|
|
7245
7345
|
volumes:
|
|
7346
|
+
# Factory state (projects, extensions, deploy strategies). Shared
|
|
7347
|
+
# with the ui container so Settings UI edits AND daemon-side
|
|
7348
|
+
# template seeding (from project-templates/) both land in the same
|
|
7349
|
+
# bind-mounted location. Pre-2026-04-15 this mount was missing
|
|
7350
|
+
# from the daemon service, which silently caused the daemon's
|
|
7351
|
+
# template-seeding logic to write to ephemeral in-container
|
|
7352
|
+
# storage \u2014 the seeded files were invisible to the ui and lost
|
|
7353
|
+
# on restart.
|
|
7354
|
+
- ./.beastmode:/app/.beastmode
|
|
7246
7355
|
# Daemon config files. Mounted from the host so Settings UI writes
|
|
7247
7356
|
# (to beastmode.docker.json) are picked up on daemon restart without
|
|
7248
7357
|
# rebuilding the image.
|