feat(boltbot): add dev server and seed script for standalone testing

This commit is contained in:
duy 2026-01-29 19:49:24 -08:00
parent be30d2f088
commit abbe5e9880
3 changed files with 134 additions and 0 deletions

View File

@ -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",
},
},
});

View File

@ -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<string, string> {
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}`);
});

View File

@ -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<T>(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`);