import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { $ } from "bun"; import { getBehaviorDashboardStats, getCostDashboardStats, getDashboardStats, getModelDashboardStats, getOverviewStats, getRecentErrors, getRecentRequests, getRequestDetails, getTotalMessageCount, syncAllSessions, } from "./aggregator"; import embeddedClientArchiveTxt from "./embedded-client.generated.txt"; const getEmbeddedClientArchive = (() => { const txt = embeddedClientArchiveTxt.replaceAll(/[\s\r\n]/g, "").trim(); if (!txt) return null; return () => Buffer.from(txt, "base64"); })(); const CLIENT_DIR = path.join(import.meta.dir, "client"); const STATIC_DIR = path.join(import.meta.dir, "..", "dist", "client"); const IS_BUN_COMPILED = Bun.env.PI_COMPILED || import.meta.url.includes("$bunfs") || import.meta.url.includes("~BUN") || import.meta.url.includes("%7EBUN"); const COMPILED_CLIENT_DIR_ROOT = path.join(os.tmpdir(), "omp-stats-client"); let compiledClientDirPromise: Promise | null = null; function sanitizeArchivePath(archivePath: string): string | null { const normalized = archivePath.replaceAll("\\", "/").replace(/^\.\//, ""); if (!normalized || normalized === ".") return null; if (normalized.includes("..") || path.isAbsolute(normalized)) return null; return normalized; } async function extractEmbeddedClientArchive(archiveBytes: Buffer, outputDir: string): Promise { const archive = new Bun.Archive(archiveBytes); const files = await archive.files(); const extractRoot = path.resolve(outputDir); for (const [archivePath, file] of files) { const sanitizedPath = sanitizeArchivePath(archivePath); if (!sanitizedPath) continue; const destinationPath = path.resolve(extractRoot, sanitizedPath); if (!destinationPath.startsWith(extractRoot + path.sep)) { throw new Error(`Archive entry escapes extraction directory: ${archivePath}`); } await Bun.write(destinationPath, file); } } async function getCompiledClientDir(): Promise { if (!IS_BUN_COMPILED) return STATIC_DIR; if (compiledClientDirPromise) return compiledClientDirPromise; const archiveBytes = getEmbeddedClientArchive?.(); if (!archiveBytes) { throw new Error("Compiled stats client bundle missing. Rebuild binary with embedded stats assets."); } compiledClientDirPromise = (async () => { const bundleHash = Bun.hash(archiveBytes).toString(16); const outputDir = path.join(COMPILED_CLIENT_DIR_ROOT, bundleHash); const markerPath = path.join(outputDir, "index.html"); try { const marker = await fs.stat(markerPath); if (marker.isFile()) return outputDir; } catch {} await fs.rm(outputDir, { recursive: true, force: true }); await fs.mkdir(outputDir, { recursive: true }); await extractEmbeddedClientArchive(archiveBytes, outputDir); return outputDir; })(); return compiledClientDirPromise; } async function getLatestMtime(dir: string): Promise { const entries = await fs.readdir(dir, { withFileTypes: true }); const promises = []; for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { promises.push(getLatestMtime(fullPath)); } else if (entry.isFile()) { promises.push(fs.stat(fullPath).then(stats => stats.mtimeMs)); } } let latest = 0; await Promise.allSettled(promises).then(results => { for (const result of results) { if (result.status === "fulfilled") { latest = Math.max(latest, result.value); } } }); return latest; } const ensureClientBuild = async () => { if (IS_BUN_COMPILED) return; const indexPath = path.join(STATIC_DIR, "index.html"); const cssPath = path.join(STATIC_DIR, "styles.css"); const clientSourceMtime = await getLatestMtime(CLIENT_DIR); const tailwindConfigPath = path.join(import.meta.dir, "..", "tailwind.config.js"); let tailwindConfigMtime = 0; try { const tailwindConfigStats = await fs.stat(tailwindConfigPath); tailwindConfigMtime = tailwindConfigStats.mtimeMs; } catch {} const sourceMtime = Math.max(clientSourceMtime, tailwindConfigMtime); let shouldBuild = true; try { const [indexStats, cssStats] = await Promise.all([fs.stat(indexPath), fs.stat(cssPath)]); if ( indexStats.isFile() && cssStats.isFile() && indexStats.mtimeMs >= sourceMtime && cssStats.mtimeMs >= sourceMtime ) { shouldBuild = false; } } catch { shouldBuild = true; } if (!shouldBuild) return; await fs.rm(STATIC_DIR, { recursive: true, force: true }); console.log("Building stats client..."); const packageRoot = path.join(import.meta.dir, ".."); const buildResult = await $`bun run build.ts`.cwd(packageRoot).quiet().nothrow(); if (buildResult.exitCode !== 0) { const output = buildResult.text().trim(); const details = output ? `\n${output}` : ""; throw new Error(`Failed to build stats client (exit ${buildResult.exitCode})${details}`); } const indexHtml = ` AI Usage Statistics
`; await Bun.write(path.join(STATIC_DIR, "index.html"), indexHtml); }; /** * Handle API requests. */ async function handleApi(req: Request): Promise { const url = new URL(req.url); const path = url.pathname; // Stats reads are DB-only; explicit /api/sync does the expensive session scan. const range = url.searchParams.get("range"); if (path === "/api/stats") { const stats = await getDashboardStats(range); return Response.json(stats); } if (path === "/api/stats/overview") { const stats = await getOverviewStats(range); return Response.json(stats); } if (path === "/api/stats/model-dashboard") { const stats = await getModelDashboardStats(range); return Response.json(stats); } if (path === "/api/stats/costs") { const stats = await getCostDashboardStats(range); return Response.json(stats); } if (path === "/api/stats/behavior") { const stats = await getBehaviorDashboardStats(range); return Response.json(stats); } if (path === "/api/stats/recent") { const limit = url.searchParams.get("limit"); const stats = await getRecentRequests(limit ? parseInt(limit, 10) : undefined); return Response.json(stats); } if (path === "/api/stats/errors") { const limit = url.searchParams.get("limit"); const stats = await getRecentErrors(limit ? parseInt(limit, 10) : undefined); return Response.json(stats); } if (path === "/api/stats/models") { const stats = await getDashboardStats(range); return Response.json(stats.byModel); } if (path === "/api/stats/folders") { const stats = await getDashboardStats(range); return Response.json(stats.byFolder); } if (path === "/api/stats/timeseries") { const stats = await getDashboardStats(range); return Response.json(stats.timeSeries); } if (path.startsWith("/api/request/")) { const id = path.split("/").pop(); if (!id) return new Response("Bad Request", { status: 400 }); const details = await getRequestDetails(parseInt(id, 10)); if (!details) return new Response("Not Found", { status: 404 }); return Response.json(details); } if (path === "/api/sync") { const result = await syncAllSessions(); const count = await getTotalMessageCount(); return Response.json({ ...result, totalMessages: count }); } return new Response("Not Found", { status: 404 }); } /** * Handle static file requests. */ async function handleStatic(requestPath: string): Promise { const staticDir = IS_BUN_COMPILED ? await getCompiledClientDir() : STATIC_DIR; const filePath = requestPath === "/" ? "/index.html" : requestPath; const fullPath = path.join(staticDir, filePath); const file = Bun.file(fullPath); if (await file.exists()) { return new Response(file); } // SPA fallback const index = Bun.file(path.join(staticDir, "index.html")); if (await index.exists()) { return new Response(index); } return new Response("Not Found", { status: 404 }); } /** * Start the HTTP server. */ export async function startServer(port = 3847): Promise<{ port: number; stop: () => void }> { await ensureClientBuild(); const server = Bun.serve({ port, async fetch(req) { const url = new URL(req.url); const path = url.pathname; // CORS headers for local development const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type", }; if (req.method === "OPTIONS") { return new Response(null, { headers: corsHeaders }); } try { let response: Response; if (path.startsWith("/api/")) { response = await handleApi(req); } else { response = await handleStatic(path); } // Add CORS headers to all responses const headers = new Headers(response.headers); for (const [key, value] of Object.entries(corsHeaders)) { headers.set(key, value); } return new Response(response.body, { status: response.status, headers, }); } catch (error) { console.error("Server error:", error); return Response.json( { error: error instanceof Error ? error.message : "Unknown error" }, { status: 500, headers: corsHeaders }, ); } }, }); return { port: server.port ?? port, stop: () => server.stop(), }; }