@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 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
- sendJson(res, 200, result);
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
- - \${HOME}/.claude:/root/.claude:ro
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.