@beastmode-develeap/beastmode 0.1.35 → 0.1.36
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 +96 -2
- 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 });
|