From abbe5e98808e5573533e0c9ac58ef274310dc408 Mon Sep 17 00:00:00 2001 From: duy Date: Thu, 29 Jan 2026 19:49:24 -0800 Subject: [PATCH] feat(boltbot): add dev server and seed script for standalone testing --- extensions/boltbot/dashboard/vite.config.ts | 7 +++ extensions/boltbot/scripts/dev-server.ts | 65 +++++++++++++++++++++ extensions/boltbot/scripts/seed.ts | 62 ++++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 extensions/boltbot/scripts/dev-server.ts create mode 100644 extensions/boltbot/scripts/seed.ts diff --git a/extensions/boltbot/dashboard/vite.config.ts b/extensions/boltbot/dashboard/vite.config.ts index 71d777e5f..4f458073d 100644 --- a/extensions/boltbot/dashboard/vite.config.ts +++ b/extensions/boltbot/dashboard/vite.config.ts @@ -5,4 +5,11 @@ import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ base: "/boltbot/dashboard/", plugins: [react(), tailwindcss()], + server: { + proxy: { + "/boltbot/receipts": "http://localhost:18789", + "/boltbot/receipt": "http://localhost:18789", + "/boltbot/stats": "http://localhost:18789", + }, + }, }); diff --git a/extensions/boltbot/scripts/dev-server.ts b/extensions/boltbot/scripts/dev-server.ts new file mode 100644 index 000000000..70e75d30c --- /dev/null +++ b/extensions/boltbot/scripts/dev-server.ts @@ -0,0 +1,65 @@ +import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; +import { LocalReceiptStore } from "../src/stores/local.js"; + +const PORT = 18789; +const store = new LocalReceiptStore(); + +const CORS_HEADERS = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type", +}; + +function json(res: ServerResponse, status: number, data: unknown) { + res.writeHead(status, { "Content-Type": "application/json", ...CORS_HEADERS }); + res.end(JSON.stringify(data)); +} + +function parseQuery(url: string): Record { + const idx = url.indexOf("?"); + if (idx === -1) return {}; + return Object.fromEntries(new URLSearchParams(url.slice(idx + 1)).entries()); +} + +function pathname(url: string): string { + const idx = url.indexOf("?"); + return idx === -1 ? url : url.slice(0, idx); +} + +const server = createServer(async (req: IncomingMessage, res: ServerResponse) => { + if (req.method === "OPTIONS") { + res.writeHead(204, CORS_HEADERS); + res.end(); + return; + } + + const path = pathname(req.url ?? ""); + const query = parseQuery(req.url ?? ""); + + try { + if (path === "/boltbot/receipts") { + const limit = Math.min(Math.max(parseInt(query.limit ?? "50", 10) || 50, 1), 500); + const offset = Math.max(parseInt(query.offset ?? "0", 10) || 0, 0); + const receipts = await store.list({ limit, offset }); + json(res, 200, { receipts }); + } else if (path === "/boltbot/receipt") { + const id = query.id; + if (!id) return json(res, 400, { error: "missing_id" }); + const receipt = await store.get(id); + if (!receipt) return json(res, 404, { error: "not_found" }); + json(res, 200, { receipt }); + } else if (path === "/boltbot/stats") { + const stats = await store.stats(); + json(res, 200, stats); + } else { + json(res, 404, { error: "not_found" }); + } + } catch (err) { + console.error("Request error:", err); + json(res, 500, { error: "internal_error" }); + } +}); + +server.listen(PORT, () => { + console.log(`boltbot dev server: http://localhost:${PORT}`); +}); diff --git a/extensions/boltbot/scripts/seed.ts b/extensions/boltbot/scripts/seed.ts new file mode 100644 index 000000000..38f0dad07 --- /dev/null +++ b/extensions/boltbot/scripts/seed.ts @@ -0,0 +1,62 @@ +import { randomUUID } from "node:crypto"; +import { LocalReceiptStore } from "../src/stores/local.js"; +import type { ActionReceipt } from "../src/receipt-store.js"; + +const MEDIUM_TOOLS = ["message", "write", "edit", "cron", "sessions_send", "browser", "canvas", "nodes"]; +const HIGH_TOOLS = ["exec", "apply_patch", "gateway", "sessions_spawn", "process"]; + +const ANOMALY_LABELS = ["shell_injection", "data_exfiltration", "unauthorized_gateway"]; + +function pick(arr: T[]): T { + return arr[Math.floor(Math.random() * arr.length)]; +} + +function randInt(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function randomHex64(): string { + const buf = new Uint8Array(32); + crypto.getRandomValues(buf); + return Array.from(buf, (b) => b.toString(16).padStart(2, "0")).join(""); +} + +function pickTool(): { name: string; tier: "medium" | "high" } { + const isHigh = Math.random() < 0.4; + return isHigh + ? { name: pick(HIGH_TOOLS), tier: "high" } + : { name: pick(MEDIUM_TOOLS), tier: "medium" }; +} + +const sessionCount = randInt(3, 5); +const sessionKeys = Array.from({ length: sessionCount }, () => randomUUID()); + +const now = Date.now(); +const DAY_MS = 86_400_000; +const receiptCount = randInt(50, 100); + +const receipts: ActionReceipt[] = Array.from({ length: receiptCount }, () => { + const { name, tier } = pickTool(); + const hasAnomaly = Math.random() < 0.125; + + return { + id: randomUUID(), + timestamp: new Date(now - Math.random() * DAY_MS).toISOString(), + sessionKey: pick(sessionKeys), + tier, + toolName: name, + argumentsHash: randomHex64(), + resultHash: randomHex64(), + success: Math.random() < 0.9, + durationMs: randInt(50, 5000), + anomalies: hasAnomaly ? [pick(ANOMALY_LABELS)] : [], + daCommitment: Math.random() < 0.2 ? randomHex64() : undefined, + }; +}); + +receipts.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); + +const store = new LocalReceiptStore(); +for (const r of receipts) await store.put(r); + +console.log(`Seeded ${receipts.length} receipts across ${sessionCount} sessions`);