#!/usr/bin/env node var __getOwnPropNames = Object.getOwnPropertyNames; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; // src/utils.js var require_utils = __commonJS({ "src/utils.js"(exports2, module2) { var { exec } = require("child_process"); var path2 = require("path"); var { promisify } = require("util"); var execAsync = promisify(exec); var pkg = require(path2.join(__dirname, "..", "package.json")); function getVersion2() { return pkg.version; } async function runCmd(cmd, options = {}) { const systemPath = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"; const envPath = process.env.PATH || ""; const opts = { encoding: "utf8", timeout: 1e4, env: { ...process.env, PATH: envPath.includes("/usr/sbin") ? envPath : `${systemPath}:${envPath}` }, ...options }; try { const { stdout } = await execAsync(cmd, opts); return stdout.trim(); } catch (e) { if (options.fallback !== void 0) return options.fallback; throw e; } } function formatBytes(bytes) { if (bytes >= 1099511627776) return (bytes / 1099511627776).toFixed(1) + " TB"; if (bytes >= 1073741824) return (bytes / 1073741824).toFixed(1) + " GB"; if (bytes >= 1048576) return (bytes / 1048576).toFixed(1) + " MB"; if (bytes >= 1024) return (bytes / 1024).toFixed(1) + " KB"; return bytes + " B"; } function formatTimeAgo(date) { const now = /* @__PURE__ */ new Date(); const diffMs = now - date; const diffMins = Math.round(diffMs / 6e4); if (diffMins < 1) return "just now"; if (diffMins < 60) return `${diffMins}m ago`; if (diffMins < 1440) return `${Math.round(diffMins / 60)}h ago`; return `${Math.round(diffMins / 1440)}d ago`; } function formatNumber(n) { return n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } function formatTokens(n) { if (n >= 1e6) return (n / 1e6).toFixed(1) + "M"; if (n >= 1e3) return (n / 1e3).toFixed(1) + "k"; return n.toString(); } module2.exports = { getVersion: getVersion2, runCmd, formatBytes, formatTimeAgo, formatNumber, formatTokens }; } }); // src/config.js var require_config = __commonJS({ "src/config.js"(exports2, module2) { var fs2 = require("fs"); var path2 = require("path"); var os = require("os"); var HOME = os.homedir(); function getOpenClawDir2(profile = null) { const effectiveProfile = profile || process.env.OPENCLAW_PROFILE || ""; return effectiveProfile ? path2.join(HOME, `.openclaw-${effectiveProfile}`) : path2.join(HOME, ".openclaw"); } function detectWorkspace() { const profile = process.env.OPENCLAW_PROFILE || ""; const openclawDir = getOpenClawDir2(); const defaultWorkspace = path2.join(openclawDir, "workspace"); const profileCandidates = profile ? [ // Profile-specific workspace in home (e.g., ~/.openclaw--workspace) path2.join(HOME, `.openclaw-${profile}-workspace`), path2.join(HOME, `.${profile}-workspace`) ] : []; const candidates = [ // Environment variable (highest priority) process.env.OPENCLAW_WORKSPACE, // OpenClaw's default workspace location process.env.OPENCLAW_HOME, // Gateway config workspace (check early - this is where OpenClaw actually runs) getWorkspaceFromGatewayConfig(), // Profile-specific paths (if profile is set) ...profileCandidates, // Standard OpenClaw workspace location (profile-aware: ~/.openclaw/workspace or ~/.openclaw-/workspace) defaultWorkspace, // Common custom workspace names path2.join(HOME, "openclaw-workspace"), path2.join(HOME, ".openclaw-workspace"), // Legacy/custom names path2.join(HOME, "molty"), path2.join(HOME, "clawd"), path2.join(HOME, "moltbot") ].filter(Boolean); const foundWorkspace = candidates.find((candidate) => { if (!candidate || !fs2.existsSync(candidate)) { return false; } const hasMemory = fs2.existsSync(path2.join(candidate, "memory")); const hasState = fs2.existsSync(path2.join(candidate, "state")); const hasConfig = fs2.existsSync(path2.join(candidate, ".openclaw")); return hasMemory || hasState || hasConfig; }); return foundWorkspace || defaultWorkspace; } function getWorkspaceFromGatewayConfig() { const openclawDir = getOpenClawDir2(); const configPaths = [ path2.join(openclawDir, "config.yaml"), path2.join(openclawDir, "config.json"), path2.join(openclawDir, "openclaw.json"), path2.join(openclawDir, "clawdbot.json"), // Fallback to standard XDG location path2.join(HOME, ".config", "openclaw", "config.yaml") ]; for (const configPath of configPaths) { try { if (fs2.existsSync(configPath)) { const content = fs2.readFileSync(configPath, "utf8"); const match = content.match(/workspace[:\s]+["']?([^"'\n]+)/i) || content.match(/workdir[:\s]+["']?([^"'\n]+)/i); if (match && match[1]) { const workspace = match[1].trim().replace(/^~/, HOME); if (fs2.existsSync(workspace)) { return workspace; } } } } catch (e) { } } return null; } function deepMerge(base, override) { const result = { ...base }; for (const key of Object.keys(override)) { if (override[key] && typeof override[key] === "object" && !Array.isArray(override[key]) && base[key] && typeof base[key] === "object") { result[key] = deepMerge(base[key], override[key]); } else if (override[key] !== null && override[key] !== void 0) { result[key] = override[key]; } } return result; } function loadConfigFile() { const basePath = path2.join(__dirname, "..", "config", "dashboard.json"); const localPath = path2.join(__dirname, "..", "config", "dashboard.local.json"); let config = {}; try { if (fs2.existsSync(basePath)) { const content = fs2.readFileSync(basePath, "utf8"); config = JSON.parse(content); } } catch (e) { console.warn(`[Config] Failed to load ${basePath}:`, e.message); } try { if (fs2.existsSync(localPath)) { const content = fs2.readFileSync(localPath, "utf8"); const localConfig = JSON.parse(content); config = deepMerge(config, localConfig); console.log(`[Config] Loaded local overrides from ${localPath}`); } } catch (e) { console.warn(`[Config] Failed to load ${localPath}:`, e.message); } return config; } function expandPath(p) { if (!p) return p; return p.replace(/^~/, HOME).replace(/\$HOME/g, HOME).replace(/\$\{HOME\}/g, HOME); } function loadConfig() { const fileConfig = loadConfigFile(); const workspace = process.env.OPENCLAW_WORKSPACE || expandPath(fileConfig.paths?.workspace) || detectWorkspace(); const config = { // Server settings server: { port: parseInt(process.env.PORT || fileConfig.server?.port || "3333", 10), host: process.env.HOST || fileConfig.server?.host || "localhost" }, // Paths - all relative to workspace unless absolute paths: { workspace, memory: expandPath(process.env.OPENCLAW_MEMORY_DIR || fileConfig.paths?.memory) || path2.join(workspace, "memory"), state: expandPath(process.env.OPENCLAW_STATE_DIR || fileConfig.paths?.state) || path2.join(workspace, "state"), cerebro: expandPath(process.env.OPENCLAW_CEREBRO_DIR || fileConfig.paths?.cerebro) || path2.join(workspace, "cerebro"), skills: expandPath(process.env.OPENCLAW_SKILLS_DIR || fileConfig.paths?.skills) || path2.join(workspace, "skills"), jobs: expandPath(process.env.OPENCLAW_JOBS_DIR || fileConfig.paths?.jobs) || path2.join(workspace, "jobs"), logs: expandPath(process.env.OPENCLAW_LOGS_DIR || fileConfig.paths?.logs) || path2.join(HOME, ".openclaw-command-center", "logs") }, // Auth settings auth: { mode: process.env.DASHBOARD_AUTH_MODE || fileConfig.auth?.mode || "none", token: process.env.DASHBOARD_TOKEN || fileConfig.auth?.token, allowedUsers: (process.env.DASHBOARD_ALLOWED_USERS || fileConfig.auth?.allowedUsers?.join(",") || "").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean), allowedIPs: (process.env.DASHBOARD_ALLOWED_IPS || fileConfig.auth?.allowedIPs?.join(",") || "127.0.0.1,::1").split(",").map((s) => s.trim()), publicPaths: fileConfig.auth?.publicPaths || ["/api/health", "/api/whoami", "/favicon.ico"] }, // Branding branding: { name: fileConfig.branding?.name || "OpenClaw Command Center", theme: fileConfig.branding?.theme || "default" }, // Integrations integrations: { linear: { enabled: !!(process.env.LINEAR_API_KEY || fileConfig.integrations?.linear?.apiKey), apiKey: process.env.LINEAR_API_KEY || fileConfig.integrations?.linear?.apiKey, teamId: process.env.LINEAR_TEAM_ID || fileConfig.integrations?.linear?.teamId } }, // Billing - for cost savings calculation billing: { claudePlanCost: parseFloat( process.env.CLAUDE_PLAN_COST || fileConfig.billing?.claudePlanCost || "200" ), claudePlanName: process.env.CLAUDE_PLAN_NAME || fileConfig.billing?.claudePlanName || "Claude Code Max" } }; return config; } var CONFIG2 = loadConfig(); console.log("[Config] Workspace:", CONFIG2.paths.workspace); console.log("[Config] Auth mode:", CONFIG2.auth.mode); module2.exports = { CONFIG: CONFIG2, loadConfig, detectWorkspace, expandPath, getOpenClawDir: getOpenClawDir2 }; } }); // src/jobs.js var require_jobs = __commonJS({ "src/jobs.js"(exports2, module2) { var path2 = require("path"); var { CONFIG: CONFIG2 } = require_config(); var JOBS_DIR = CONFIG2.paths.jobs; var JOBS_STATE_DIR = path2.join(CONFIG2.paths.state, "jobs"); var apiInstance = null; var forceApiUnavailable = false; async function getAPI() { if (forceApiUnavailable) return null; if (apiInstance) return apiInstance; try { const { createJobsAPI } = await import(path2.join(JOBS_DIR, "lib/api.js")); apiInstance = createJobsAPI({ definitionsDir: path2.join(JOBS_DIR, "definitions"), stateDir: JOBS_STATE_DIR }); return apiInstance; } catch (e) { console.error("Failed to load jobs API:", e.message); return null; } } function _resetForTesting(options = {}) { apiInstance = null; forceApiUnavailable = options.forceUnavailable || false; } function formatRelativeTime(isoString) { if (!isoString) return null; const date = new Date(isoString); const now = /* @__PURE__ */ new Date(); const diffMs = now - date; const diffMins = Math.round(diffMs / 6e4); if (diffMins < 0) { const futureMins = Math.abs(diffMins); if (futureMins < 60) return `in ${futureMins}m`; if (futureMins < 1440) return `in ${Math.round(futureMins / 60)}h`; return `in ${Math.round(futureMins / 1440)}d`; } if (diffMins < 1) return "just now"; if (diffMins < 60) return `${diffMins}m ago`; if (diffMins < 1440) return `${Math.round(diffMins / 60)}h ago`; return `${Math.round(diffMins / 1440)}d ago`; } async function handleJobsRequest2(req, res, pathname, query, method) { const api = await getAPI(); if (!api) { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Jobs API not available" })); return; } try { if (pathname === "/api/jobs/scheduler/status" && method === "GET") { const status = await api.getSchedulerStatus(); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(status, null, 2)); return; } if (pathname === "/api/jobs/stats" && method === "GET") { const stats = await api.getAggregateStats(); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(stats, null, 2)); return; } if (pathname === "/api/jobs/cache/clear" && method === "POST") { api.clearCache(); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ success: true, message: "Cache cleared" })); return; } if (pathname === "/api/jobs" && method === "GET") { const jobs = await api.listJobs(); const enhanced = jobs.map((job) => ({ ...job, lastRunRelative: formatRelativeTime(job.lastRun), nextRunRelative: formatRelativeTime(job.nextRun) })); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ jobs: enhanced, timestamp: Date.now() }, null, 2)); return; } const jobMatch = pathname.match(/^\/api\/jobs\/([^/]+)$/); if (jobMatch && method === "GET") { const jobId = decodeURIComponent(jobMatch[1]); const job = await api.getJob(jobId); if (!job) { res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Job not found" })); return; } job.lastRunRelative = formatRelativeTime(job.lastRun); job.nextRunRelative = formatRelativeTime(job.nextRun); if (job.recentRuns) { job.recentRuns = job.recentRuns.map((run) => ({ ...run, startedAtRelative: formatRelativeTime(run.startedAt), completedAtRelative: formatRelativeTime(run.completedAt) })); } res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(job, null, 2)); return; } const historyMatch = pathname.match(/^\/api\/jobs\/([^/]+)\/history$/); if (historyMatch && method === "GET") { const jobId = decodeURIComponent(historyMatch[1]); const limit = parseInt(query.get("limit") || "50", 10); const runs = await api.getJobHistory(jobId, limit); const enhanced = runs.map((run) => ({ ...run, startedAtRelative: formatRelativeTime(run.startedAt), completedAtRelative: formatRelativeTime(run.completedAt) })); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ runs: enhanced, timestamp: Date.now() }, null, 2)); return; } const runMatch = pathname.match(/^\/api\/jobs\/([^/]+)\/run$/); if (runMatch && method === "POST") { const jobId = decodeURIComponent(runMatch[1]); const result = await api.runJob(jobId); res.writeHead(result.success ? 200 : 400, { "Content-Type": "application/json" }); res.end(JSON.stringify(result, null, 2)); return; } const pauseMatch = pathname.match(/^\/api\/jobs\/([^/]+)\/pause$/); if (pauseMatch && method === "POST") { const jobId = decodeURIComponent(pauseMatch[1]); let body = ""; await new Promise((resolve) => { req.on("data", (chunk) => body += chunk); req.on("end", resolve); }); let reason = null; try { const parsed = JSON.parse(body || "{}"); reason = parsed.reason; } catch (_e) { } const result = await api.pauseJob(jobId, { by: req.authUser?.login || "dashboard", reason }); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(result, null, 2)); return; } const resumeMatch = pathname.match(/^\/api\/jobs\/([^/]+)\/resume$/); if (resumeMatch && method === "POST") { const jobId = decodeURIComponent(resumeMatch[1]); const result = await api.resumeJob(jobId); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(result, null, 2)); return; } const skipMatch = pathname.match(/^\/api\/jobs\/([^/]+)\/skip$/); if (skipMatch && method === "POST") { const jobId = decodeURIComponent(skipMatch[1]); const result = await api.skipJob(jobId); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(result, null, 2)); return; } const killMatch = pathname.match(/^\/api\/jobs\/([^/]+)\/kill$/); if (killMatch && method === "POST") { const jobId = decodeURIComponent(killMatch[1]); const result = await api.killJob(jobId); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(result, null, 2)); return; } res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Not found" })); } catch (e) { console.error("Jobs API error:", e); res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: e.message })); } } function isJobsRoute2(pathname) { return pathname.startsWith("/api/jobs"); } module2.exports = { handleJobsRequest: handleJobsRequest2, isJobsRoute: isJobsRoute2, _resetForTesting }; } }); // src/openclaw.js var require_openclaw = __commonJS({ "src/openclaw.js"(exports2, module2) { var { execFileSync, execFile } = require("child_process"); var { promisify } = require("util"); var execFileAsync = promisify(execFile); function getSafeEnv() { return { PATH: process.env.PATH, HOME: process.env.HOME, USER: process.env.USER, SHELL: process.env.SHELL, LANG: process.env.LANG, NO_COLOR: "1", TERM: "dumb", OPENCLAW_PROFILE: process.env.OPENCLAW_PROFILE || "", OPENCLAW_WORKSPACE: process.env.OPENCLAW_WORKSPACE || "", OPENCLAW_HOME: process.env.OPENCLAW_HOME || "" }; } function buildArgs(args2) { const profile = process.env.OPENCLAW_PROFILE || ""; const profileArgs = profile ? ["--profile", profile] : []; const cleanArgs = args2.replace(/\s*2>&1\s*/g, " ").replace(/\s*2>\/dev\/null\s*/g, " ").trim(); return [...profileArgs, ...cleanArgs.split(/\s+/).filter(Boolean)]; } function runOpenClaw2(args2) { try { const result = execFileSync("openclaw", buildArgs(args2), { encoding: "utf8", timeout: 3e3, env: getSafeEnv(), stdio: ["pipe", "pipe", "pipe"] }); return result; } catch (e) { return null; } } async function runOpenClawAsync2(args2) { try { const { stdout } = await execFileAsync("openclaw", buildArgs(args2), { encoding: "utf8", timeout: 2e4, env: getSafeEnv() }); return stdout; } catch (e) { console.error("[OpenClaw Async] Error:", e.message); return null; } } function extractJSON2(output) { if (!output) return null; const jsonStart = output.search(/[[{]/); if (jsonStart === -1) return null; return output.slice(jsonStart); } module2.exports = { runOpenClaw: runOpenClaw2, runOpenClawAsync: runOpenClawAsync2, extractJSON: extractJSON2, getSafeEnv }; } }); // src/vitals.js var require_vitals = __commonJS({ "src/vitals.js"(exports2, module2) { var { runCmd, formatBytes } = require_utils(); var cachedVitals = null; var lastVitalsUpdate = 0; var VITALS_CACHE_TTL = 3e4; var vitalsRefreshing = false; async function refreshVitalsAsync() { if (vitalsRefreshing) return; vitalsRefreshing = true; const vitals = { hostname: "", uptime: "", disk: { used: 0, free: 0, total: 0, percent: 0, kbPerTransfer: 0, iops: 0, throughputMBps: 0 }, cpu: { loadAvg: [0, 0, 0], cores: 0, usage: 0 }, memory: { used: 0, free: 0, total: 0, percent: 0, pressure: "normal" }, temperature: null }; const isLinux = process.platform === "linux"; const isMacOS = process.platform === "darwin"; try { const coresCmd = isLinux ? "nproc" : "sysctl -n hw.ncpu"; const memCmd = isLinux ? "cat /proc/meminfo | grep MemTotal | awk '{print $2}'" : "sysctl -n hw.memsize"; const topCmd = isLinux ? "top -bn1 | head -3 | grep -E '^%?Cpu|^ ?CPU' || echo ''" : 'top -l 1 -n 0 2>/dev/null | grep "CPU usage" || echo ""'; const mpstatCmd = isLinux ? "(command -v mpstat >/dev/null 2>&1 && mpstat 1 1 | tail -1 | sed 's/^Average: *//') || echo ''" : ""; const [hostname, uptimeRaw, coresRaw, memTotalRaw, memInfoRaw, dfRaw, topOutput, mpstatOutput] = await Promise.all([ runCmd("hostname", { fallback: "unknown" }), runCmd("uptime", { fallback: "" }), runCmd(coresCmd, { fallback: "1" }), runCmd(memCmd, { fallback: "0" }), isLinux ? runCmd("cat /proc/meminfo", { fallback: "" }) : runCmd("vm_stat", { fallback: "" }), runCmd("df -k ~ | tail -1", { fallback: "" }), runCmd(topCmd, { fallback: "" }), isLinux ? runCmd(mpstatCmd, { fallback: "" }) : Promise.resolve("") ]); vitals.hostname = hostname; const uptimeMatch = uptimeRaw.match(/up\s+([^,]+)/); if (uptimeMatch) vitals.uptime = uptimeMatch[1].trim(); const loadMatch = uptimeRaw.match(/load averages?:\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/); if (loadMatch) vitals.cpu.loadAvg = [ parseFloat(loadMatch[1]), parseFloat(loadMatch[2]), parseFloat(loadMatch[3]) ]; vitals.cpu.cores = parseInt(coresRaw, 10) || 1; vitals.cpu.usage = Math.min(100, Math.round(vitals.cpu.loadAvg[0] / vitals.cpu.cores * 100)); if (isLinux) { if (mpstatOutput) { const parts = mpstatOutput.trim().split(/\s+/); const user = parts.length > 1 ? parseFloat(parts[1]) : NaN; const sys = parts.length > 3 ? parseFloat(parts[3]) : NaN; const idle = parts.length ? parseFloat(parts[parts.length - 1]) : NaN; if (!Number.isNaN(user)) vitals.cpu.userPercent = user; if (!Number.isNaN(sys)) vitals.cpu.sysPercent = sys; if (!Number.isNaN(idle)) { vitals.cpu.idlePercent = idle; vitals.cpu.usage = Math.max(0, Math.min(100, Math.round(100 - idle))); } } if (topOutput && (vitals.cpu.idlePercent === null || vitals.cpu.idlePercent === void 0)) { const userMatch = topOutput.match(/([\d.]+)\s*us/); const sysMatch = topOutput.match(/([\d.]+)\s*sy/); const idleMatch = topOutput.match(/([\d.]+)\s*id/); vitals.cpu.userPercent = userMatch ? parseFloat(userMatch[1]) : null; vitals.cpu.sysPercent = sysMatch ? parseFloat(sysMatch[1]) : null; vitals.cpu.idlePercent = idleMatch ? parseFloat(idleMatch[1]) : null; if (vitals.cpu.userPercent !== null && vitals.cpu.sysPercent !== null) { vitals.cpu.usage = Math.round(vitals.cpu.userPercent + vitals.cpu.sysPercent); } } } else if (topOutput) { const userMatch = topOutput.match(/([\d.]+)%\s*user/); const sysMatch = topOutput.match(/([\d.]+)%\s*sys/); const idleMatch = topOutput.match(/([\d.]+)%\s*idle/); vitals.cpu.userPercent = userMatch ? parseFloat(userMatch[1]) : null; vitals.cpu.sysPercent = sysMatch ? parseFloat(sysMatch[1]) : null; vitals.cpu.idlePercent = idleMatch ? parseFloat(idleMatch[1]) : null; if (vitals.cpu.userPercent !== null && vitals.cpu.sysPercent !== null) { vitals.cpu.usage = Math.round(vitals.cpu.userPercent + vitals.cpu.sysPercent); } } const dfParts = dfRaw.split(/\s+/); if (dfParts.length >= 4) { vitals.disk.total = parseInt(dfParts[1], 10) * 1024; vitals.disk.used = parseInt(dfParts[2], 10) * 1024; vitals.disk.free = parseInt(dfParts[3], 10) * 1024; vitals.disk.percent = Math.round(parseInt(dfParts[2], 10) / parseInt(dfParts[1], 10) * 100); } if (isLinux) { const memTotalKB = parseInt(memTotalRaw, 10) || 0; const memAvailableMatch = memInfoRaw.match(/MemAvailable:\s+(\d+)/); const memFreeMatch = memInfoRaw.match(/MemFree:\s+(\d+)/); vitals.memory.total = memTotalKB * 1024; const memAvailable = parseInt(memAvailableMatch?.[1] || memFreeMatch?.[1] || 0, 10) * 1024; vitals.memory.used = vitals.memory.total - memAvailable; vitals.memory.free = memAvailable; vitals.memory.percent = vitals.memory.total > 0 ? Math.round(vitals.memory.used / vitals.memory.total * 100) : 0; } else { const pageSizeMatch = memInfoRaw.match(/page size of (\d+) bytes/); const pageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : 4096; const activePages = parseInt((memInfoRaw.match(/Pages active:\s+(\d+)/) || [])[1] || 0, 10); const wiredPages = parseInt( (memInfoRaw.match(/Pages wired down:\s+(\d+)/) || [])[1] || 0, 10 ); const compressedPages = parseInt( (memInfoRaw.match(/Pages occupied by compressor:\s+(\d+)/) || [])[1] || 0, 10 ); vitals.memory.total = parseInt(memTotalRaw, 10) || 0; vitals.memory.used = (activePages + wiredPages + compressedPages) * pageSize; vitals.memory.free = vitals.memory.total - vitals.memory.used; vitals.memory.percent = vitals.memory.total > 0 ? Math.round(vitals.memory.used / vitals.memory.total * 100) : 0; } vitals.memory.pressure = vitals.memory.percent > 90 ? "critical" : vitals.memory.percent > 75 ? "warning" : "normal"; const timeoutPrefix = isLinux ? "timeout 5" : "$(command -v gtimeout >/dev/null 2>&1 && echo gtimeout 5)"; const iostatArgs = isLinux ? "-d -o JSON 1 2" : "-d -c 2 2"; const iostatCmd = `${timeoutPrefix} iostat ${iostatArgs} 2>/dev/null || echo ''`; const [perfCores, effCores, chip, iostatRaw] = await Promise.all([ isMacOS ? runCmd("sysctl -n hw.perflevel0.logicalcpu 2>/dev/null || echo 0", { fallback: "0" }) : Promise.resolve("0"), isMacOS ? runCmd("sysctl -n hw.perflevel1.logicalcpu 2>/dev/null || echo 0", { fallback: "0" }) : Promise.resolve("0"), isMacOS ? runCmd( 'system_profiler SPHardwareDataType 2>/dev/null | grep "Chip:" | cut -d: -f2 || echo ""', { fallback: "" } ) : Promise.resolve(""), runCmd(iostatCmd, { fallback: "", timeout: 5e3 }) ]); if (isLinux) { const cpuBrand = await runCmd( "cat /proc/cpuinfo | grep 'model name' | head -1 | cut -d: -f2", { fallback: "" } ); if (cpuBrand) vitals.cpu.brand = cpuBrand.trim(); } vitals.cpu.pCores = parseInt(perfCores, 10) || null; vitals.cpu.eCores = parseInt(effCores, 10) || null; if (chip) vitals.cpu.chip = chip; if (isLinux) { try { const iostatJson = JSON.parse(iostatRaw); const samples = iostatJson.sysstat.hosts[0].statistics; const disks = samples[samples.length - 1].disk; const disk = disks.filter((d) => !d.disk_device.startsWith("loop")).sort((a, b) => b.tps - a.tps)[0]; if (disk) { const kbReadPerSec = disk["kB_read/s"] || 0; const kbWrtnPerSec = disk["kB_wrtn/s"] || 0; vitals.disk.iops = disk.tps || 0; vitals.disk.throughputMBps = (kbReadPerSec + kbWrtnPerSec) / 1024; vitals.disk.kbPerTransfer = disk.tps > 0 ? (kbReadPerSec + kbWrtnPerSec) / disk.tps : 0; } } catch { } } else { const iostatLines = iostatRaw.split("\n").filter((l) => l.trim()); const lastLine = iostatLines.length > 0 ? iostatLines[iostatLines.length - 1] : ""; const iostatParts = lastLine.split(/\s+/).filter(Boolean); if (iostatParts.length >= 3) { vitals.disk.kbPerTransfer = parseFloat(iostatParts[0]) || 0; vitals.disk.iops = parseFloat(iostatParts[1]) || 0; vitals.disk.throughputMBps = parseFloat(iostatParts[2]) || 0; } } vitals.temperature = null; vitals.temperatureNote = null; const isAppleSilicon = vitals.cpu.chip && /apple/i.test(vitals.cpu.chip); if (isAppleSilicon) { vitals.temperatureNote = "Apple Silicon (requires elevated access)"; try { const pmOutput = await runCmd( 'sudo -n powermetrics --samplers smc -i 1 -n 1 2>/dev/null | grep -i "die temp" | head -1', { fallback: "", timeout: 5e3 } ); const tempMatch = pmOutput.match(/([\d.]+)/); if (tempMatch) { vitals.temperature = parseFloat(tempMatch[1]); vitals.temperatureNote = null; } } catch (e) { } } else if (isMacOS) { const home = require("os").homedir(); try { const temp = await runCmd( `osx-cpu-temp 2>/dev/null || ${home}/bin/osx-cpu-temp 2>/dev/null`, { fallback: "" } ); if (temp && temp.includes("\xB0")) { const tempMatch = temp.match(/([\d.]+)/); if (tempMatch && parseFloat(tempMatch[1]) > 0) { vitals.temperature = parseFloat(tempMatch[1]); } } } catch (e) { } if (!vitals.temperature) { try { const ioregRaw = await runCmd( "ioreg -r -n AppleSmartBattery 2>/dev/null | grep Temperature", { fallback: "" } ); const tempMatch = ioregRaw.match(/"Temperature"\s*=\s*(\d+)/); if (tempMatch) { vitals.temperature = Math.round(parseInt(tempMatch[1], 10) / 100); } } catch (e) { } } } else if (isLinux) { try { const temp = await runCmd("cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null", { fallback: "" }); if (temp) { vitals.temperature = Math.round(parseInt(temp, 10) / 1e3); } } catch (e) { } } } catch (e) { console.error("[Vitals] Async refresh failed:", e.message); } vitals.memory.usedFormatted = formatBytes(vitals.memory.used); vitals.memory.totalFormatted = formatBytes(vitals.memory.total); vitals.memory.freeFormatted = formatBytes(vitals.memory.free); vitals.disk.usedFormatted = formatBytes(vitals.disk.used); vitals.disk.totalFormatted = formatBytes(vitals.disk.total); vitals.disk.freeFormatted = formatBytes(vitals.disk.free); cachedVitals = vitals; lastVitalsUpdate = Date.now(); vitalsRefreshing = false; console.log("[Vitals] Cache refreshed async"); } setTimeout(() => refreshVitalsAsync(), 500); setInterval(() => refreshVitalsAsync(), VITALS_CACHE_TTL); function getSystemVitals2() { const now = Date.now(); if (!cachedVitals || now - lastVitalsUpdate > VITALS_CACHE_TTL) { refreshVitalsAsync(); } if (cachedVitals) return cachedVitals; return { hostname: "loading...", uptime: "", disk: { used: 0, free: 0, total: 0, percent: 0, usedFormatted: "-", totalFormatted: "-", freeFormatted: "-" }, cpu: { loadAvg: [0, 0, 0], cores: 0, usage: 0 }, memory: { used: 0, free: 0, total: 0, percent: 0, pressure: "normal", usedFormatted: "-", totalFormatted: "-", freeFormatted: "-" }, temperature: null }; } var cachedDeps = null; async function checkOptionalDeps2() { const isLinux = process.platform === "linux"; const isMacOS = process.platform === "darwin"; const platform = isLinux ? "linux" : isMacOS ? "darwin" : null; const results = []; if (!platform) { cachedDeps = results; return results; } const fs2 = require("fs"); const path2 = require("path"); const depsFile = path2.join(__dirname, "..", "config", "system-deps.json"); let depsConfig; try { depsConfig = JSON.parse(fs2.readFileSync(depsFile, "utf8")); } catch { cachedDeps = results; return results; } const deps = depsConfig[platform] || []; const home = require("os").homedir(); let pkgManager = null; if (isLinux) { for (const pm of ["apt", "dnf", "yum", "pacman", "apk"]) { const has = await runCmd(`which ${pm}`, { fallback: "" }); if (has) { pkgManager = pm; break; } } } else if (isMacOS) { const hasBrew = await runCmd("which brew", { fallback: "" }); if (hasBrew) pkgManager = "brew"; } let isAppleSilicon = false; if (isMacOS) { const chip = await runCmd("sysctl -n machdep.cpu.brand_string", { fallback: "" }); isAppleSilicon = /apple/i.test(chip); } for (const dep of deps) { if (dep.condition === "intel" && isAppleSilicon) continue; let installed = false; const hasBinary = await runCmd(`which ${dep.binary} 2>/dev/null`, { fallback: "" }); if (hasBinary) { installed = true; } else if (isMacOS && dep.binary === "osx-cpu-temp") { const homebin = await runCmd(`test -x ${home}/bin/osx-cpu-temp && echo ok`, { fallback: "" }); if (homebin) installed = true; } const installCmd = dep.install[pkgManager] || null; results.push({ id: dep.id, name: dep.name, purpose: dep.purpose, affects: dep.affects, installed, installCmd, url: dep.url || null }); } cachedDeps = results; const missing = results.filter((d) => !d.installed); if (missing.length > 0) { console.log("[Startup] Optional dependencies for enhanced vitals:"); for (const dep of missing) { const action = dep.installCmd || dep.url || "see docs"; console.log(` \u{1F4A1} ${dep.name} \u2014 ${dep.purpose}: ${action}`); } } return results; } function getOptionalDeps2() { return cachedDeps; } module2.exports = { refreshVitalsAsync, getSystemVitals: getSystemVitals2, checkOptionalDeps: checkOptionalDeps2, getOptionalDeps: getOptionalDeps2, VITALS_CACHE_TTL }; } }); // src/auth.js var require_auth = __commonJS({ "src/auth.js"(exports2, module2) { var AUTH_HEADERS = { tailscale: { login: "tailscale-user-login", name: "tailscale-user-name", pic: "tailscale-user-profile-pic" }, cloudflare: { email: "cf-access-authenticated-user-email" } }; function checkAuth2(req, authConfig) { const mode = authConfig.mode; const remoteAddr = req.socket?.remoteAddress || ""; const isLocalhost = remoteAddr === "127.0.0.1" || remoteAddr === "::1" || remoteAddr === "::ffff:127.0.0.1"; if (isLocalhost) { return { authorized: true, user: { type: "localhost", login: "localhost" } }; } if (mode === "none") { return { authorized: true, user: null }; } if (mode === "token") { const authHeader = req.headers["authorization"] || ""; const token = authHeader.replace(/^Bearer\s+/i, ""); if (token && token === authConfig.token) { return { authorized: true, user: { type: "token" } }; } return { authorized: false, reason: "Invalid or missing token" }; } if (mode === "tailscale") { const login = (req.headers[AUTH_HEADERS.tailscale.login] || "").toLowerCase(); const name = req.headers[AUTH_HEADERS.tailscale.name] || ""; const pic = req.headers[AUTH_HEADERS.tailscale.pic] || ""; if (!login) { return { authorized: false, reason: "Not accessed via Tailscale Serve" }; } const isAllowed = authConfig.allowedUsers.some((allowed) => { if (allowed === "*") return true; if (allowed === login) return true; if (allowed.startsWith("*@")) { const domain = allowed.slice(2); return login.endsWith("@" + domain); } return false; }); if (isAllowed) { return { authorized: true, user: { type: "tailscale", login, name, pic } }; } return { authorized: false, reason: `User ${login} not in allowlist`, user: { login } }; } if (mode === "cloudflare") { const email = (req.headers[AUTH_HEADERS.cloudflare.email] || "").toLowerCase(); if (!email) { return { authorized: false, reason: "Not accessed via Cloudflare Access" }; } const isAllowed = authConfig.allowedUsers.some((allowed) => { if (allowed === "*") return true; if (allowed === email) return true; if (allowed.startsWith("*@")) { const domain = allowed.slice(2); return email.endsWith("@" + domain); } return false; }); if (isAllowed) { return { authorized: true, user: { type: "cloudflare", email } }; } return { authorized: false, reason: `User ${email} not in allowlist`, user: { email } }; } if (mode === "allowlist") { const clientIP = req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket?.remoteAddress || ""; const isAllowed = authConfig.allowedIPs.some((allowed) => { if (allowed === clientIP) return true; if (allowed.endsWith("/24")) { const prefix = allowed.slice(0, -3).split(".").slice(0, 3).join("."); return clientIP.startsWith(prefix + "."); } return false; }); if (isAllowed) { return { authorized: true, user: { type: "ip", ip: clientIP } }; } return { authorized: false, reason: `IP ${clientIP} not in allowlist` }; } return { authorized: false, reason: "Unknown auth mode" }; } function getUnauthorizedPage2(reason, user, authConfig) { const userInfo = user ? `` : ""; return ` Access Denied - Command Center
\u{1F510}

Access Denied

${reason}
${userInfo}

This dashboard requires authentication via ${authConfig.mode}.

${authConfig.mode === "tailscale" ? `

Make sure you're accessing via your Tailscale URL and your account is in the allowlist.

` : ""} ${authConfig.mode === "cloudflare" ? `

Make sure you're accessing via Cloudflare Access and your email is in the allowlist.

` : ""}
Auth mode: ${authConfig.mode}
`; } module2.exports = { AUTH_HEADERS, checkAuth: checkAuth2, getUnauthorizedPage: getUnauthorizedPage2 }; } }); // src/privacy.js var require_privacy = __commonJS({ "src/privacy.js"(exports2, module2) { var fs2 = require("fs"); var path2 = require("path"); function getPrivacyFilePath(dataDir) { return path2.join(dataDir, "privacy-settings.json"); } function loadPrivacySettings2(dataDir) { try { const privacyFile = getPrivacyFilePath(dataDir); if (fs2.existsSync(privacyFile)) { return JSON.parse(fs2.readFileSync(privacyFile, "utf8")); } } catch (e) { console.error("Failed to load privacy settings:", e.message); } return { version: 1, hiddenTopics: [], hiddenSessions: [], hiddenCrons: [], hideHostname: false, updatedAt: null }; } function savePrivacySettings2(dataDir, data) { try { if (!fs2.existsSync(dataDir)) { fs2.mkdirSync(dataDir, { recursive: true }); } data.updatedAt = (/* @__PURE__ */ new Date()).toISOString(); fs2.writeFileSync(getPrivacyFilePath(dataDir), JSON.stringify(data, null, 2)); return true; } catch (e) { console.error("Failed to save privacy settings:", e.message); return false; } } module2.exports = { loadPrivacySettings: loadPrivacySettings2, savePrivacySettings: savePrivacySettings2 }; } }); // src/operators.js var require_operators = __commonJS({ "src/operators.js"(exports2, module2) { var fs2 = require("fs"); var path2 = require("path"); function loadOperators2(dataDir) { const operatorsFile = path2.join(dataDir, "operators.json"); try { if (fs2.existsSync(operatorsFile)) { return JSON.parse(fs2.readFileSync(operatorsFile, "utf8")); } } catch (e) { console.error("Failed to load operators:", e.message); } return { version: 1, operators: [], roles: {} }; } function saveOperators2(dataDir, data) { try { if (!fs2.existsSync(dataDir)) { fs2.mkdirSync(dataDir, { recursive: true }); } const operatorsFile = path2.join(dataDir, "operators.json"); fs2.writeFileSync(operatorsFile, JSON.stringify(data, null, 2)); return true; } catch (e) { console.error("Failed to save operators:", e.message); return false; } } function getOperatorBySlackId2(dataDir, slackId) { const data = loadOperators2(dataDir); return data.operators.find((op) => op.id === slackId || op.metadata?.slackId === slackId); } var operatorsRefreshing = false; async function refreshOperatorsAsync(dataDir, getOpenClawDir2) { if (operatorsRefreshing) return; operatorsRefreshing = true; const toMs = (ts, fallback) => { if (typeof ts === "number" && Number.isFinite(ts)) return ts; if (typeof ts === "string") { const parsed = Date.parse(ts); if (Number.isFinite(parsed)) return parsed; } return fallback; }; try { const openclawDir = getOpenClawDir2(); const sessionsDir = path2.join(openclawDir, "agents", "main", "sessions"); if (!fs2.existsSync(sessionsDir)) { operatorsRefreshing = false; return; } const files = fs2.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl")); const operatorsMap = /* @__PURE__ */ new Map(); const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1e3; for (const file of files) { const filePath = path2.join(sessionsDir, file); try { const stat = fs2.statSync(filePath); if (stat.mtimeMs < sevenDaysAgo) continue; const fd = fs2.openSync(filePath, "r"); const buffer = Buffer.alloc(10240); const bytesRead = fs2.readSync(fd, buffer, 0, 10240, 0); fs2.closeSync(fd); const content = buffer.toString("utf8", 0, bytesRead); const lines = content.split("\n").slice(0, 20); for (const line of lines) { if (!line.trim()) continue; try { const entry = JSON.parse(line); if (entry.type !== "message" || !entry.message) continue; const msg = entry.message; if (msg.role !== "user") continue; let text = ""; if (typeof msg.content === "string") { text = msg.content; } else if (Array.isArray(msg.content)) { const textPart = msg.content.find((c) => c.type === "text"); if (textPart) text = textPart.text || ""; } if (!text) continue; const slackMatch = text.match(/\[Slack[^\]]*\]\s*([\w.-]+)\s*\(([A-Z0-9]+)\):/); if (slackMatch) { const username = slackMatch[1]; const userId = slackMatch[2]; if (!operatorsMap.has(userId)) { operatorsMap.set(userId, { id: userId, name: username, username, source: "slack", firstSeen: toMs(entry.timestamp, stat.mtimeMs), lastSeen: toMs(entry.timestamp, stat.mtimeMs), sessionCount: 1 }); } else { const op = operatorsMap.get(userId); op.lastSeen = Math.max(op.lastSeen, toMs(entry.timestamp, stat.mtimeMs)); op.sessionCount++; } break; } const telegramMatch = text.match(/\[Telegram[^\]]*\]\s*([\w.-]+):/); if (telegramMatch) { const username = telegramMatch[1]; const operatorId = `telegram:${username}`; if (!operatorsMap.has(operatorId)) { operatorsMap.set(operatorId, { id: operatorId, name: username, username, source: "telegram", firstSeen: toMs(entry.timestamp, stat.mtimeMs), lastSeen: toMs(entry.timestamp, stat.mtimeMs), sessionCount: 1 }); } else { const op = operatorsMap.get(operatorId); op.lastSeen = Math.max(op.lastSeen, toMs(entry.timestamp, stat.mtimeMs)); op.sessionCount++; } break; } const discordSenderMatch = text.match(/"sender":\s*"(\d+)"/); const discordLabelMatch = text.match(/"label":\s*"([^"]+)"/); const discordUsernameMatch = text.match(/"username":\s*"([^"]+)"/); if (discordSenderMatch) { const userId = discordSenderMatch[1]; const label = discordLabelMatch ? discordLabelMatch[1] : userId; const username = discordUsernameMatch ? discordUsernameMatch[1] : label; const opId = `discord:${userId}`; if (!operatorsMap.has(opId)) { operatorsMap.set(opId, { id: opId, discordId: userId, name: label, username, source: "discord", firstSeen: toMs(entry.timestamp, stat.mtimeMs), lastSeen: toMs(entry.timestamp, stat.mtimeMs), sessionCount: 1 }); } else { const op = operatorsMap.get(opId); op.lastSeen = Math.max(op.lastSeen, toMs(entry.timestamp, stat.mtimeMs)); op.sessionCount++; } break; } } catch (e) { } } } catch (e) { } } const existing = loadOperators2(dataDir); const existingMap = new Map(existing.operators.map((op) => [op.id, op])); for (const [id, autoOp] of operatorsMap) { if (existingMap.has(id)) { const manual = existingMap.get(id); manual.lastSeen = Math.max(manual.lastSeen || 0, autoOp.lastSeen); manual.sessionCount = (manual.sessionCount || 0) + autoOp.sessionCount; } else { existingMap.set(id, autoOp); } } const merged = { version: 1, operators: Array.from(existingMap.values()).sort( (a, b) => (b.lastSeen || 0) - (a.lastSeen || 0) ), roles: existing.roles || {}, lastRefreshed: Date.now() }; saveOperators2(dataDir, merged); console.log(`[Operators] Refreshed: ${merged.operators.length} operators detected`); } catch (e) { console.error("[Operators] Refresh failed:", e.message); } operatorsRefreshing = false; } function startOperatorsRefresh2(dataDir, getOpenClawDir2) { setTimeout(() => refreshOperatorsAsync(dataDir, getOpenClawDir2), 2e3); setInterval(() => refreshOperatorsAsync(dataDir, getOpenClawDir2), 5 * 60 * 1e3); } module2.exports = { loadOperators: loadOperators2, saveOperators: saveOperators2, getOperatorBySlackId: getOperatorBySlackId2, refreshOperatorsAsync, startOperatorsRefresh: startOperatorsRefresh2 }; } }); // src/topics.js var require_topics = __commonJS({ "src/topics.js"(exports2, module2) { var TOPIC_PATTERNS = { dashboard: ["dashboard", "command center", "ui", "interface", "status page"], scheduling: ["cron", "schedule", "timer", "reminder", "alarm", "periodic", "interval"], heartbeat: [ "heartbeat", "heartbeat_ok", "poll", "health check", "ping", "keepalive", "monitoring" ], memory: ["memory", "remember", "recall", "notes", "journal", "log", "context"], Slack: ["slack", "channel", "#cc-", "thread", "mention", "dm", "workspace"], email: ["email", "mail", "inbox", "gmail", "send email", "unread", "compose"], calendar: ["calendar", "event", "meeting", "appointment", "schedule", "gcal"], coding: [ "code", "script", "function", "debug", "error", "bug", "implement", "refactor", "programming" ], git: [ "git", "commit", "branch", "merge", "push", "pull", "repository", "pr", "pull request", "github" ], "file editing": ["file", "edit", "write", "read", "create", "delete", "modify", "save"], API: ["api", "endpoint", "request", "response", "webhook", "integration", "rest", "graphql"], research: ["search", "research", "lookup", "find", "investigate", "learn", "study"], browser: ["browser", "webpage", "website", "url", "click", "navigate", "screenshot", "web_fetch"], "Quip export": ["quip", "export", "document", "spreadsheet"], finance: ["finance", "investment", "stock", "money", "budget", "bank", "trading", "portfolio"], home: ["home", "automation", "lights", "thermostat", "smart home", "iot", "homekit"], health: ["health", "fitness", "workout", "exercise", "weight", "sleep", "nutrition"], travel: ["travel", "flight", "hotel", "trip", "vacation", "booking", "airport"], food: ["food", "recipe", "restaurant", "cooking", "meal", "order", "delivery"], subagent: ["subagent", "spawn", "sub-agent", "delegate", "worker", "parallel"], tools: ["tool", "exec", "shell", "command", "terminal", "bash", "run"] }; function detectTopics(text) { if (!text) return []; const lowerText = text.toLowerCase(); const scores = {}; for (const [topic, keywords] of Object.entries(TOPIC_PATTERNS)) { let score = 0; for (const keyword of keywords) { if (keyword.length <= 3) { const regex = new RegExp(`\\b${keyword}\\b`, "i"); if (regex.test(lowerText)) score++; } else if (lowerText.includes(keyword)) { score++; } } if (score > 0) { scores[topic] = score; } } if (Object.keys(scores).length === 0) return []; const bestScore = Math.max(...Object.values(scores)); const threshold = Math.max(2, bestScore * 0.5); return Object.entries(scores).filter(([_, score]) => score >= threshold || score >= 1 && bestScore <= 2).sort((a, b) => b[1] - a[1]).map(([topic, _]) => topic); } module2.exports = { TOPIC_PATTERNS, detectTopics }; } }); // src/sessions.js var require_sessions = __commonJS({ "src/sessions.js"(exports2, module2) { var fs2 = require("fs"); var path2 = require("path"); var { detectTopics } = require_topics(); var CHANNEL_MAP = { c0aax7y80np: "#cc-meta", c0ab9f8sdfe: "#cc-research", c0aan4rq7v5: "#cc-finance", c0abxulk1qq: "#cc-properties", c0ab5nz8mkl: "#cc-ai", c0aan38tzv5: "#cc-dev", c0ab7wwhqvc: "#cc-home", c0ab1pjhxef: "#cc-health", c0ab7txvcqd: "#cc-legal", c0aay2g3n3r: "#cc-social", c0aaxrw2wqp: "#cc-business", c0ab19f3lae: "#cc-random", c0ab0r74y33: "#cc-food", c0ab0qrq3r9: "#cc-travel", c0ab0sbqqlg: "#cc-family", c0ab0slqdba: "#cc-games", c0ab1ps7ef2: "#cc-music", c0absbnrsbe: "#cc-dashboard" }; function parseSessionLabel(key) { const parts = key.split(":"); if (parts.includes("slack")) { const channelIdx = parts.indexOf("channel"); if (channelIdx >= 0 && parts[channelIdx + 1]) { const channelId = parts[channelIdx + 1].toLowerCase(); const channelName = CHANNEL_MAP[channelId] || `#${channelId}`; if (parts.includes("thread")) { const threadTs = parts[parts.indexOf("thread") + 1]; const ts = parseFloat(threadTs); const date = new Date(ts * 1e3); const timeStr = date.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" }); return `${channelName} thread @ ${timeStr}`; } return channelName; } } if (key.includes("telegram")) { return "\u{1F4F1} Telegram"; } if (key === "agent:main:main") { return "\u{1F3E0} Main Session"; } return key.length > 40 ? key.slice(0, 37) + "..." : key; } function createSessionsModule2(deps) { const { getOpenClawDir: getOpenClawDir2, getOperatorBySlackId: getOperatorBySlackId2, runOpenClaw: runOpenClaw2, runOpenClawAsync: runOpenClawAsync2, extractJSON: extractJSON2 } = deps; let sessionsCache = { sessions: [], timestamp: 0, refreshing: false }; const SESSIONS_CACHE_TTL = 1e4; function getSessionOriginator(sessionId) { try { if (!sessionId) return null; const openclawDir = getOpenClawDir2(); const transcriptPath = path2.join( openclawDir, "agents", "main", "sessions", `${sessionId}.jsonl` ); if (!fs2.existsSync(transcriptPath)) return null; const content = fs2.readFileSync(transcriptPath, "utf8"); const lines = content.trim().split("\n"); for (let i = 0; i < Math.min(lines.length, 10); i++) { try { const entry = JSON.parse(lines[i]); if (entry.type !== "message" || !entry.message) continue; const msg = entry.message; if (msg.role !== "user") continue; let text = ""; if (typeof msg.content === "string") { text = msg.content; } else if (Array.isArray(msg.content)) { const textPart = msg.content.find((c) => c.type === "text"); if (textPart) text = textPart.text || ""; } if (!text) continue; const slackUserMatch = text.match(/\]\s*([\w.-]+)\s*\(([A-Z0-9]+)\):/); if (slackUserMatch) { const username = slackUserMatch[1]; const userId = slackUserMatch[2]; const operator = getOperatorBySlackId2(userId); return { userId, username, displayName: operator?.name || username, role: operator?.role || "user", avatar: operator?.avatar || null }; } } catch (e) { } } return null; } catch (e) { return null; } } function getSessionTopic(sessionId) { if (!sessionId) return null; try { const openclawDir = getOpenClawDir2(); const transcriptPath = path2.join( openclawDir, "agents", "main", "sessions", `${sessionId}.jsonl` ); if (!fs2.existsSync(transcriptPath)) return null; const fd = fs2.openSync(transcriptPath, "r"); const buffer = Buffer.alloc(5e4); const bytesRead = fs2.readSync(fd, buffer, 0, 5e4, 0); fs2.closeSync(fd); if (bytesRead === 0) return null; const content = buffer.toString("utf8", 0, bytesRead); const lines = content.split("\n").filter((l) => l.trim()); let textSamples = []; for (const line of lines.slice(0, 30)) { try { const entry = JSON.parse(line); if (entry.type === "message" && entry.message?.content) { const msgContent = entry.message.content; if (Array.isArray(msgContent)) { msgContent.forEach((c) => { if (c.type === "text" && c.text) { textSamples.push(c.text.slice(0, 500)); } }); } else if (typeof msgContent === "string") { textSamples.push(msgContent.slice(0, 500)); } } } catch (e) { } } if (textSamples.length === 0) return null; const topics = detectTopics(textSamples.join(" ")); return topics.length > 0 ? topics.slice(0, 2).join(", ") : null; } catch (e) { return null; } } function mapSession(s) { const minutesAgo = s.ageMs ? s.ageMs / 6e4 : Infinity; let channel = "other"; if (s.key.includes("slack")) channel = "slack"; else if (s.key.includes("telegram")) channel = "telegram"; else if (s.key.includes("discord")) channel = "discord"; else if (s.key.includes("signal")) channel = "signal"; else if (s.key.includes("whatsapp")) channel = "whatsapp"; let sessionType = "channel"; if (s.key.includes(":subagent:")) sessionType = "subagent"; else if (s.key.includes(":cron:")) sessionType = "cron"; else if (s.key === "agent:main:main") sessionType = "main"; const originator = getSessionOriginator(s.sessionId); const label = s.groupChannel || s.displayName || parseSessionLabel(s.key); const topic = getSessionTopic(s.sessionId); const totalTokens = s.totalTokens || 0; const sessionAgeMinutes = Math.max(1, Math.min(minutesAgo, 24 * 60)); const burnRate = Math.round(totalTokens / sessionAgeMinutes); return { sessionKey: s.key, sessionId: s.sessionId, label, groupChannel: s.groupChannel || null, displayName: s.displayName || null, kind: s.kind, channel, sessionType, active: minutesAgo < 15, recentlyActive: minutesAgo < 60, minutesAgo: Math.round(minutesAgo), tokens: s.totalTokens || 0, model: s.model, originator, topic, metrics: { burnRate, toolCalls: 0, minutesActive: Math.max(1, Math.min(Math.round(minutesAgo), 24 * 60)) } }; } async function refreshSessionsCache() { if (sessionsCache.refreshing) return; sessionsCache.refreshing = true; try { const output = await runOpenClawAsync2("sessions --json 2>/dev/null"); const jsonStr = extractJSON2(output); if (jsonStr) { const data = JSON.parse(jsonStr); const sessions2 = data.sessions || []; const mapped = sessions2.map((s) => mapSession(s)); sessionsCache = { sessions: mapped, timestamp: Date.now(), refreshing: false }; console.log(`[Sessions Cache] Refreshed: ${mapped.length} sessions`); } } catch (e) { console.error("[Sessions Cache] Refresh error:", e.message); } sessionsCache.refreshing = false; } function getSessionsCached() { const now = Date.now(); const isStale = now - sessionsCache.timestamp > SESSIONS_CACHE_TTL; if (isStale && !sessionsCache.refreshing) { refreshSessionsCache(); } return sessionsCache.sessions; } function getSessions(options = {}) { const limit = Object.prototype.hasOwnProperty.call(options, "limit") ? options.limit : 20; const returnCount = options.returnCount || false; if (limit === null) { const cached = getSessionsCached(); const totalCount = cached.length; return returnCount ? { sessions: cached, totalCount } : cached; } try { const output = runOpenClaw2("sessions --json 2>/dev/null"); const jsonStr = extractJSON2(output); if (jsonStr) { const data = JSON.parse(jsonStr); const totalCount = data.count || data.sessions?.length || 0; let sessions2 = data.sessions || []; if (limit != null) { sessions2 = sessions2.slice(0, limit); } const mapped = sessions2.map((s) => mapSession(s)); return returnCount ? { sessions: mapped, totalCount } : mapped; } } catch (e) { console.error("Failed to get sessions:", e.message); } return returnCount ? { sessions: [], totalCount: 0 } : []; } function readTranscript(sessionId) { const openclawDir = getOpenClawDir2(); const transcriptPath = path2.join( openclawDir, "agents", "main", "sessions", `${sessionId}.jsonl` ); try { if (!fs2.existsSync(transcriptPath)) return []; const content = fs2.readFileSync(transcriptPath, "utf8"); return content.trim().split("\n").map((line) => { try { return JSON.parse(line); } catch { return null; } }).filter(Boolean); } catch (e) { console.error("Failed to read transcript:", e.message); return []; } } function getSessionDetail(sessionKey) { try { const listOutput = runOpenClaw2("sessions --json 2>/dev/null"); let sessionInfo = null; const jsonStr = extractJSON2(listOutput); if (jsonStr) { const data = JSON.parse(jsonStr); sessionInfo = data.sessions?.find((s) => s.key === sessionKey); } if (!sessionInfo) { return { error: "Session not found" }; } const transcript = readTranscript(sessionInfo.sessionId); let messages = []; let tools = {}; let facts = []; let needsAttention = []; let totalInputTokens = 0; let totalOutputTokens = 0; let totalCacheRead = 0; let totalCacheWrite = 0; let totalCost = 0; let detectedModel = sessionInfo.model || null; transcript.forEach((entry) => { if (entry.type !== "message" || !entry.message) return; const msg = entry.message; if (!msg.role) return; if (msg.usage) { totalInputTokens += msg.usage.input || msg.usage.inputTokens || 0; totalOutputTokens += msg.usage.output || msg.usage.outputTokens || 0; totalCacheRead += msg.usage.cacheRead || msg.usage.cacheReadTokens || 0; totalCacheWrite += msg.usage.cacheWrite || msg.usage.cacheWriteTokens || 0; if (msg.usage.cost?.total) totalCost += msg.usage.cost.total; } if (msg.role === "assistant" && msg.model && !detectedModel) { detectedModel = msg.model; } let text = ""; if (typeof msg.content === "string") { text = msg.content; } else if (Array.isArray(msg.content)) { const textPart = msg.content.find((c) => c.type === "text"); if (textPart) text = textPart.text || ""; msg.content.filter((c) => c.type === "toolCall" || c.type === "tool_use").forEach((tc) => { const name = tc.name || tc.tool || "unknown"; tools[name] = (tools[name] || 0) + 1; }); } if (text && msg.role !== "toolResult") { messages.push({ role: msg.role, text, timestamp: entry.timestamp }); } if (msg.role === "user" && text) { const lowerText = text.toLowerCase(); if (text.includes("?")) { const questions = text.match(/[^.!?\n]*\?/g) || []; questions.slice(0, 2).forEach((q) => { if (q.length > 15 && q.length < 200) { needsAttention.push(`\u2753 ${q.trim()}`); } }); } if (lowerText.includes("todo") || lowerText.includes("remind") || lowerText.includes("need to")) { const match = text.match(/(?:todo|remind|need to)[^.!?\n]*/i); if (match) needsAttention.push(`\u{1F4CB} ${match[0].slice(0, 100)}`); } } if (msg.role === "assistant" && text) { const lowerText = text.toLowerCase(); ["\u2705", "done", "created", "updated", "fixed", "deployed"].forEach((keyword) => { if (lowerText.includes(keyword)) { const lines = text.split("\n").filter((l) => l.toLowerCase().includes(keyword)); lines.slice(0, 2).forEach((line) => { if (line.length > 5 && line.length < 150) { facts.push(line.trim().slice(0, 100)); } }); } }); } }); let summary = "No activity yet."; const userMessages = messages.filter((m) => m.role === "user"); const assistantMessages = messages.filter((m) => m.role === "assistant"); let topics = []; if (messages.length > 0) { summary = `${messages.length} messages (${userMessages.length} user, ${assistantMessages.length} assistant). `; const allText = messages.map((m) => m.text).join(" "); topics = detectTopics(allText); if (topics.length > 0) { summary += `Topics: ${topics.join(", ")}.`; } } const toolsArray = Object.entries(tools).map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count); const ageMs = sessionInfo.ageMs || 0; const lastActive = ageMs < 6e4 ? "Just now" : ageMs < 36e5 ? `${Math.round(ageMs / 6e4)} minutes ago` : ageMs < 864e5 ? `${Math.round(ageMs / 36e5)} hours ago` : `${Math.round(ageMs / 864e5)} days ago`; let channelDisplay = "Other"; if (sessionInfo.groupChannel) { channelDisplay = sessionInfo.groupChannel; } else if (sessionInfo.displayName) { channelDisplay = sessionInfo.displayName; } else if (sessionKey.includes("slack")) { const parts = sessionKey.split(":"); const channelIdx = parts.indexOf("channel"); if (channelIdx >= 0 && parts[channelIdx + 1]) { const channelId = parts[channelIdx + 1].toLowerCase(); channelDisplay = CHANNEL_MAP[channelId] || `#${channelId}`; } else { channelDisplay = "Slack"; } } else if (sessionKey.includes("telegram")) { channelDisplay = "Telegram"; } const finalTotalTokens = totalInputTokens + totalOutputTokens || sessionInfo.totalTokens || 0; const finalInputTokens = totalInputTokens || sessionInfo.inputTokens || 0; const finalOutputTokens = totalOutputTokens || sessionInfo.outputTokens || 0; const modelDisplay = (detectedModel || sessionInfo.model || "-").replace("anthropic/", "").replace("openai/", ""); return { key: sessionKey, kind: sessionInfo.kind, channel: channelDisplay, groupChannel: sessionInfo.groupChannel || channelDisplay, model: modelDisplay, tokens: finalTotalTokens, inputTokens: finalInputTokens, outputTokens: finalOutputTokens, cacheRead: totalCacheRead, cacheWrite: totalCacheWrite, estCost: totalCost > 0 ? `$${totalCost.toFixed(4)}` : null, lastActive, summary, topics, // Array of detected topics facts: [...new Set(facts)].slice(0, 8), needsAttention: [...new Set(needsAttention)].slice(0, 5), tools: toolsArray.slice(0, 10), messages: messages.slice(-15).reverse().map((m) => ({ role: m.role, text: m.text.slice(0, 500) })) }; } catch (e) { console.error("Failed to get session detail:", e.message); return { error: e.message }; } } return { getSessionOriginator, getSessionTopic, mapSession, refreshSessionsCache, getSessionsCached, getSessions, readTranscript, getSessionDetail, parseSessionLabel }; } module2.exports = { createSessionsModule: createSessionsModule2, CHANNEL_MAP }; } }); // src/cron.js var require_cron = __commonJS({ "src/cron.js"(exports2, module2) { var fs2 = require("fs"); var path2 = require("path"); function cronToHuman(expr) { if (!expr || expr === "\u2014") return null; const parts = expr.split(" "); if (parts.length < 5) return null; const [minute, hour, dayOfMonth, month, dayOfWeek] = parts; const dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; function formatTime(h, m) { const hNum = parseInt(h, 10); const mNum = parseInt(m, 10); if (isNaN(hNum)) return null; const ampm = hNum >= 12 ? "pm" : "am"; const h12 = hNum === 0 ? 12 : hNum > 12 ? hNum - 12 : hNum; return mNum === 0 ? `${h12}${ampm}` : `${h12}:${mNum.toString().padStart(2, "0")}${ampm}`; } if (minute === "*" && hour === "*" && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") { return "Every minute"; } if (minute.startsWith("*/")) { const interval = minute.slice(2); return `Every ${interval} minutes`; } if (hour.startsWith("*/")) { const interval = hour.slice(2); const minStr = minute === "0" ? "" : `:${minute.padStart(2, "0")}`; return `Every ${interval} hours${minStr ? " at " + minStr : ""}`; } if (minute !== "*" && hour === "*" && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") { return `Hourly at :${minute.padStart(2, "0")}`; } let timeStr = ""; if (minute !== "*" && hour !== "*" && !hour.startsWith("*/")) { timeStr = formatTime(hour, minute); } if (timeStr && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") { return `Daily at ${timeStr}`; } if ((dayOfWeek === "1-5" || dayOfWeek === "MON-FRI") && dayOfMonth === "*" && month === "*") { return timeStr ? `Weekdays at ${timeStr}` : "Weekdays"; } if ((dayOfWeek === "0,6" || dayOfWeek === "6,0") && dayOfMonth === "*" && month === "*") { return timeStr ? `Weekends at ${timeStr}` : "Weekends"; } if (dayOfMonth === "*" && month === "*" && dayOfWeek !== "*") { const days = dayOfWeek.split(",").map((d) => { const num = parseInt(d, 10); return dayNames[num] || d; }); const dayStr = days.length === 1 ? days[0] : days.join(", "); return timeStr ? `${dayStr} at ${timeStr}` : `Every ${dayStr}`; } if (dayOfMonth !== "*" && month === "*" && dayOfWeek === "*") { const day = parseInt(dayOfMonth, 10); const suffix = day === 1 || day === 21 || day === 31 ? "st" : day === 2 || day === 22 ? "nd" : day === 3 || day === 23 ? "rd" : "th"; return timeStr ? `${day}${suffix} of month at ${timeStr}` : `${day}${suffix} of every month`; } if (timeStr) { return `At ${timeStr}`; } return expr; } function getCronJobs2(getOpenClawDir2) { try { const cronPath = path2.join(getOpenClawDir2(), "cron", "jobs.json"); if (fs2.existsSync(cronPath)) { const data = JSON.parse(fs2.readFileSync(cronPath, "utf8")); return (data.jobs || []).map((j) => { let scheduleStr = "\u2014"; let scheduleHuman = null; if (j.schedule) { if (j.schedule.kind === "cron" && j.schedule.expr) { scheduleStr = j.schedule.expr; scheduleHuman = cronToHuman(j.schedule.expr); } else if (j.schedule.kind === "once") { scheduleStr = "once"; scheduleHuman = "One-time"; } } let nextRunStr = "\u2014"; if (j.state?.nextRunAtMs) { const next = new Date(j.state.nextRunAtMs); const now = /* @__PURE__ */ new Date(); const diffMs = next - now; const diffMins = Math.round(diffMs / 6e4); if (diffMins < 0) { nextRunStr = "overdue"; } else if (diffMins < 60) { nextRunStr = `${diffMins}m`; } else if (diffMins < 1440) { nextRunStr = `${Math.round(diffMins / 60)}h`; } else { nextRunStr = `${Math.round(diffMins / 1440)}d`; } } return { id: j.id, name: j.name || j.id.slice(0, 8), schedule: scheduleStr, scheduleHuman, nextRun: nextRunStr, enabled: j.enabled !== false, lastStatus: j.state?.lastStatus }; }); } } catch (e) { console.error("Failed to get cron:", e.message); } return []; } module2.exports = { cronToHuman, getCronJobs: getCronJobs2 }; } }); // src/cerebro.js var require_cerebro = __commonJS({ "src/cerebro.js"(exports2, module2) { var fs2 = require("fs"); var path2 = require("path"); var { formatTimeAgo } = require_utils(); function getCerebroTopics2(cerebroDir, options = {}) { const { offset = 0, limit = 20, status: filterStatus = "all" } = options; const topicsDir = path2.join(cerebroDir, "topics"); const orphansDir = path2.join(cerebroDir, "orphans"); const topics = []; const result = { initialized: false, cerebroPath: cerebroDir, topics: { active: 0, resolved: 0, parked: 0, total: 0 }, threads: 0, orphans: 0, recentTopics: [], lastUpdated: null }; try { if (!fs2.existsSync(cerebroDir)) { return result; } result.initialized = true; let latestModified = null; if (!fs2.existsSync(topicsDir)) { return result; } const topicNames = fs2.readdirSync(topicsDir).filter((name) => { const topicPath = path2.join(topicsDir, name); return fs2.statSync(topicPath).isDirectory() && !name.startsWith("_"); }); topicNames.forEach((name) => { const topicMdPath = path2.join(topicsDir, name, "topic.md"); const topicDirPath = path2.join(topicsDir, name); let stat; let content = ""; if (fs2.existsSync(topicMdPath)) { stat = fs2.statSync(topicMdPath); content = fs2.readFileSync(topicMdPath, "utf8"); } else { stat = fs2.statSync(topicDirPath); } try { const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); let title = name; let topicStatus = "active"; let category = "general"; let created = null; if (frontmatterMatch) { const frontmatter = frontmatterMatch[1]; const titleMatch = frontmatter.match(/title:\s*(.+)/); const statusMatch = frontmatter.match(/status:\s*(.+)/); const categoryMatch = frontmatter.match(/category:\s*(.+)/); const createdMatch = frontmatter.match(/created:\s*(.+)/); if (titleMatch) title = titleMatch[1].trim(); if (statusMatch) topicStatus = statusMatch[1].trim().toLowerCase(); if (categoryMatch) category = categoryMatch[1].trim(); if (createdMatch) created = createdMatch[1].trim(); } const threadsDir = path2.join(topicsDir, name, "threads"); let threadCount = 0; if (fs2.existsSync(threadsDir)) { threadCount = fs2.readdirSync(threadsDir).filter((f) => f.endsWith(".md") || f.endsWith(".json")).length; } result.threads += threadCount; if (topicStatus === "active") result.topics.active++; else if (topicStatus === "resolved") result.topics.resolved++; else if (topicStatus === "parked") result.topics.parked++; if (!latestModified || stat.mtime > latestModified) { latestModified = stat.mtime; } topics.push({ name, title, status: topicStatus, category, created, threads: threadCount, lastModified: stat.mtimeMs }); } catch (e) { console.error(`Failed to parse topic ${name}:`, e.message); } }); result.topics.total = topics.length; const statusPriority = { active: 0, resolved: 1, parked: 2 }; topics.sort((a, b) => { const statusDiff = (statusPriority[a.status] || 3) - (statusPriority[b.status] || 3); if (statusDiff !== 0) return statusDiff; return b.lastModified - a.lastModified; }); let filtered = topics; if (filterStatus !== "all") { filtered = topics.filter((t) => t.status === filterStatus); } const paginated = filtered.slice(offset, offset + limit); result.recentTopics = paginated.map((t) => ({ name: t.name, title: t.title, status: t.status, threads: t.threads, age: formatTimeAgo(new Date(t.lastModified)) })); if (fs2.existsSync(orphansDir)) { try { result.orphans = fs2.readdirSync(orphansDir).filter((f) => f.endsWith(".md")).length; } catch (e) { } } result.lastUpdated = latestModified ? latestModified.toISOString() : null; } catch (e) { console.error("Failed to get Cerebro topics:", e.message); } return result; } function updateTopicStatus2(cerebroDir, topicId, newStatus) { const topicDir = path2.join(cerebroDir, "topics", topicId); const topicFile = path2.join(topicDir, "topic.md"); if (!fs2.existsSync(topicDir)) { return { error: `Topic '${topicId}' not found`, code: 404 }; } if (!fs2.existsSync(topicFile)) { const content2 = `--- title: ${topicId} status: ${newStatus} category: general created: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} --- # ${topicId} ## Overview *Topic tracking file.* ## Notes `; fs2.writeFileSync(topicFile, content2, "utf8"); return { topic: { id: topicId, name: topicId, title: topicId, status: newStatus } }; } let content = fs2.readFileSync(topicFile, "utf8"); let title = topicId; const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); if (frontmatterMatch) { let frontmatter = frontmatterMatch[1]; const titleMatch = frontmatter.match(/title:\s*["']?([^"'\n]+)["']?/i); if (titleMatch) title = titleMatch[1]; if (frontmatter.includes("status:")) { frontmatter = frontmatter.replace( /status:\s*(active|resolved|parked)/i, `status: ${newStatus}` ); } else { frontmatter = frontmatter.trim() + ` status: ${newStatus}`; } content = content.replace(/^---\n[\s\S]*?\n---/, `--- ${frontmatter} ---`); } else { const headerMatch = content.match(/^#\s*(.+)/m); if (headerMatch) title = headerMatch[1]; const frontmatter = `--- title: ${title} status: ${newStatus} category: general created: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} --- `; content = frontmatter + content; } fs2.writeFileSync(topicFile, content, "utf8"); return { topic: { id: topicId, name: topicId, title, status: newStatus } }; } module2.exports = { getCerebroTopics: getCerebroTopics2, updateTopicStatus: updateTopicStatus2 }; } }); // src/tokens.js var require_tokens = __commonJS({ "src/tokens.js"(exports2, module2) { var fs2 = require("fs"); var path2 = require("path"); var { formatNumber, formatTokens } = require_utils(); var TOKEN_RATES = { input: 15, // $15/1M input tokens output: 75, // $75/1M output tokens cacheRead: 1.5, // $1.50/1M (90% discount from input) cacheWrite: 18.75 // $18.75/1M (25% premium on input) }; var tokenUsageCache = { data: null, timestamp: 0, refreshing: false }; var TOKEN_USAGE_CACHE_TTL = 3e4; var refreshInterval = null; function emptyUsageBucket() { return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, requests: 0 }; } async function refreshTokenUsageAsync2(getOpenClawDir2) { if (tokenUsageCache.refreshing) return; tokenUsageCache.refreshing = true; try { const sessionsDir = path2.join(getOpenClawDir2(), "agents", "main", "sessions"); const files = await fs2.promises.readdir(sessionsDir); const jsonlFiles = files.filter((f) => f.endsWith(".jsonl")); const now = Date.now(); const oneDayAgo = now - 24 * 60 * 60 * 1e3; const threeDaysAgo = now - 3 * 24 * 60 * 60 * 1e3; const sevenDaysAgo = now - 7 * 24 * 60 * 60 * 1e3; const usage24h = emptyUsageBucket(); const usage3d = emptyUsageBucket(); const usage7d = emptyUsageBucket(); const batchSize = 50; for (let i = 0; i < jsonlFiles.length; i += batchSize) { const batch = jsonlFiles.slice(i, i + batchSize); await Promise.all( batch.map(async (file) => { const filePath = path2.join(sessionsDir, file); try { const stat = await fs2.promises.stat(filePath); if (stat.mtimeMs < sevenDaysAgo) return; const content = await fs2.promises.readFile(filePath, "utf8"); const lines = content.trim().split("\n"); for (const line of lines) { if (!line) continue; try { const entry = JSON.parse(line); const entryTime = entry.timestamp ? new Date(entry.timestamp).getTime() : 0; if (entryTime < sevenDaysAgo) continue; if (entry.message?.usage) { const u = entry.message.usage; const input = u.input || 0; const output = u.output || 0; const cacheRead = u.cacheRead || 0; const cacheWrite = u.cacheWrite || 0; const cost = u.cost?.total || 0; if (entryTime >= oneDayAgo) { usage24h.input += input; usage24h.output += output; usage24h.cacheRead += cacheRead; usage24h.cacheWrite += cacheWrite; usage24h.cost += cost; usage24h.requests++; } if (entryTime >= threeDaysAgo) { usage3d.input += input; usage3d.output += output; usage3d.cacheRead += cacheRead; usage3d.cacheWrite += cacheWrite; usage3d.cost += cost; usage3d.requests++; } usage7d.input += input; usage7d.output += output; usage7d.cacheRead += cacheRead; usage7d.cacheWrite += cacheWrite; usage7d.cost += cost; usage7d.requests++; } } catch (e) { } } } catch (e) { } }) ); await new Promise((resolve) => setImmediate(resolve)); } const finalizeBucket = (bucket) => ({ ...bucket, tokensNoCache: bucket.input + bucket.output, tokensWithCache: bucket.input + bucket.output + bucket.cacheRead + bucket.cacheWrite }); const result = { // Primary (24h) for backward compatibility ...finalizeBucket(usage24h), // All three windows windows: { "24h": finalizeBucket(usage24h), "3d": finalizeBucket(usage3d), "7d": finalizeBucket(usage7d) } }; tokenUsageCache = { data: result, timestamp: Date.now(), refreshing: false }; console.log( `[Token Usage] Cached: 24h=${usage24h.requests} 3d=${usage3d.requests} 7d=${usage7d.requests} requests` ); } catch (e) { console.error("[Token Usage] Refresh error:", e.message); tokenUsageCache.refreshing = false; } } function getDailyTokenUsage2(getOpenClawDir2) { const now = Date.now(); const isStale = now - tokenUsageCache.timestamp > TOKEN_USAGE_CACHE_TTL; if (isStale && !tokenUsageCache.refreshing && getOpenClawDir2) { refreshTokenUsageAsync2(getOpenClawDir2); } const emptyResult = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, requests: 0, tokensNoCache: 0, tokensWithCache: 0, windows: { "24h": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, requests: 0, tokensNoCache: 0, tokensWithCache: 0 }, "3d": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, requests: 0, tokensNoCache: 0, tokensWithCache: 0 }, "7d": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, requests: 0, tokensNoCache: 0, tokensWithCache: 0 } } }; return tokenUsageCache.data || emptyResult; } function calculateCostForBucket(bucket, rates = TOKEN_RATES) { const inputCost = bucket.input / 1e6 * rates.input; const outputCost = bucket.output / 1e6 * rates.output; const cacheReadCost = bucket.cacheRead / 1e6 * rates.cacheRead; const cacheWriteCost = bucket.cacheWrite / 1e6 * rates.cacheWrite; return { inputCost, outputCost, cacheReadCost, cacheWriteCost, totalCost: inputCost + outputCost + cacheReadCost + cacheWriteCost }; } function getCostBreakdown2(config, getSessions, getOpenClawDir2) { const usage = getDailyTokenUsage2(getOpenClawDir2); if (!usage) { return { error: "Failed to get usage data" }; } const costs = calculateCostForBucket(usage); const planCost = config.billing?.claudePlanCost || 200; const planName = config.billing?.claudePlanName || "Claude Code Max"; const windowConfigs = { "24h": { days: 1, label: "24h" }, "3d": { days: 3, label: "3dma" }, "7d": { days: 7, label: "7dma" } }; const windows = {}; for (const [key, windowConfig] of Object.entries(windowConfigs)) { const bucket = usage.windows?.[key] || usage; const bucketCosts = calculateCostForBucket(bucket); const dailyAvg = bucketCosts.totalCost / windowConfig.days; const monthlyProjected = dailyAvg * 30; const monthlySavings = monthlyProjected - planCost; windows[key] = { label: windowConfig.label, days: windowConfig.days, totalCost: bucketCosts.totalCost, dailyAvg, monthlyProjected, monthlySavings, savingsPercent: monthlySavings > 0 ? Math.round(monthlySavings / monthlyProjected * 100) : 0, requests: bucket.requests, tokens: { input: bucket.input, output: bucket.output, cacheRead: bucket.cacheRead, cacheWrite: bucket.cacheWrite } }; } return { // Raw token counts (24h for backward compatibility) inputTokens: usage.input, outputTokens: usage.output, cacheRead: usage.cacheRead, cacheWrite: usage.cacheWrite, requests: usage.requests, // Pricing rates rates: { input: TOKEN_RATES.input.toFixed(2), output: TOKEN_RATES.output.toFixed(2), cacheRead: TOKEN_RATES.cacheRead.toFixed(2), cacheWrite: TOKEN_RATES.cacheWrite.toFixed(2) }, // Cost calculation breakdown (24h) calculation: { inputCost: costs.inputCost, outputCost: costs.outputCost, cacheReadCost: costs.cacheReadCost, cacheWriteCost: costs.cacheWriteCost }, // Totals (24h for backward compatibility) totalCost: costs.totalCost, planCost, planName, // Period period: "24 hours", // Multi-window data for moving averages windows, // Top sessions by tokens topSessions: getTopSessionsByTokens(5, getSessions) }; } function getTopSessionsByTokens(limit = 5, getSessions) { try { const sessions2 = getSessions({ limit: null }); return sessions2.filter((s) => s.tokens > 0).sort((a, b) => b.tokens - a.tokens).slice(0, limit).map((s) => ({ label: s.label, tokens: s.tokens, channel: s.channel, active: s.active })); } catch (e) { console.error("[TopSessions] Error:", e.message); return []; } } function getTokenStats2(sessions2, capacity, config = {}) { let activeMainCount = capacity?.main?.active ?? 0; let activeSubagentCount = capacity?.subagent?.active ?? 0; let activeCount = activeMainCount + activeSubagentCount; let mainLimit = capacity?.main?.max ?? 12; let subagentLimit = capacity?.subagent?.max ?? 24; if (!capacity && sessions2 && sessions2.length > 0) { activeCount = 0; activeMainCount = 0; activeSubagentCount = 0; sessions2.forEach((s) => { if (s.active) { activeCount++; if (s.key && s.key.includes(":subagent:")) { activeSubagentCount++; } else { activeMainCount++; } } }); } const usage = getDailyTokenUsage2(); const totalInput = usage?.input || 0; const totalOutput = usage?.output || 0; const total = totalInput + totalOutput; const costs = calculateCostForBucket(usage); const estCost = costs.totalCost; const planCost = config?.billing?.claudePlanCost ?? 200; const planName = config?.billing?.claudePlanName ?? "Claude Code Max"; const monthlyApiCost = estCost * 30; const monthlySavings = monthlyApiCost - planCost; const savingsPositive = monthlySavings > 0; const sessionCount = sessions2?.length || 1; const avgTokensPerSession = Math.round(total / sessionCount); const avgCostPerSession = estCost / sessionCount; const windowConfigs = { "24h": { days: 1, label: "24h" }, "3dma": { days: 3, label: "3dma" }, "7dma": { days: 7, label: "7dma" } }; const savingsWindows = {}; for (const [key, windowConfig] of Object.entries(windowConfigs)) { const bucketKey = key.replace("dma", "d").replace("24h", "24h"); const bucket = usage.windows?.[bucketKey === "24h" ? "24h" : bucketKey] || usage; const bucketCosts = calculateCostForBucket(bucket); const dailyAvg = bucketCosts.totalCost / windowConfig.days; const monthlyProjected = dailyAvg * 30; const windowSavings = monthlyProjected - planCost; const windowSavingsPositive = windowSavings > 0; savingsWindows[key] = { label: windowConfig.label, estCost: `$${formatNumber(dailyAvg)}`, estMonthlyCost: `$${Math.round(monthlyProjected).toLocaleString()}`, estSavings: windowSavingsPositive ? `$${formatNumber(windowSavings)}/mo` : null, savingsPercent: windowSavingsPositive ? Math.round(windowSavings / monthlyProjected * 100) : 0, requests: bucket.requests }; } return { total: formatTokens(total), input: formatTokens(totalInput), output: formatTokens(totalOutput), cacheRead: formatTokens(usage?.cacheRead || 0), cacheWrite: formatTokens(usage?.cacheWrite || 0), requests: usage?.requests || 0, activeCount, activeMainCount, activeSubagentCount, mainLimit, subagentLimit, estCost: `$${formatNumber(estCost)}`, planCost: `$${planCost.toFixed(0)}`, planName, // 24h savings (backward compatible) estSavings: savingsPositive ? `$${formatNumber(monthlySavings)}/mo` : null, savingsPercent: savingsPositive ? Math.round(monthlySavings / monthlyApiCost * 100) : 0, estMonthlyCost: `$${Math.round(monthlyApiCost).toLocaleString()}`, // Multi-window savings (24h, 3da, 7da) savingsWindows, // Per-session averages avgTokensPerSession: formatTokens(avgTokensPerSession), avgCostPerSession: `$${avgCostPerSession.toFixed(2)}`, sessionCount }; } function startTokenUsageRefresh2(getOpenClawDir2) { refreshTokenUsageAsync2(getOpenClawDir2); if (refreshInterval) { clearInterval(refreshInterval); } refreshInterval = setInterval(() => { refreshTokenUsageAsync2(getOpenClawDir2); }, TOKEN_USAGE_CACHE_TTL); return refreshInterval; } module2.exports = { TOKEN_RATES, emptyUsageBucket, refreshTokenUsageAsync: refreshTokenUsageAsync2, getDailyTokenUsage: getDailyTokenUsage2, calculateCostForBucket, getCostBreakdown: getCostBreakdown2, getTopSessionsByTokens, getTokenStats: getTokenStats2, startTokenUsageRefresh: startTokenUsageRefresh2 }; } }); // src/llm-usage.js var require_llm_usage = __commonJS({ "src/llm-usage.js"(exports2, module2) { var fs2 = require("fs"); var path2 = require("path"); var { execFile } = require("child_process"); var { getSafeEnv } = require_openclaw(); var llmUsageCache = { data: null, timestamp: 0, refreshing: false }; var LLM_CACHE_TTL_MS = 6e4; function refreshLlmUsageAsync() { if (llmUsageCache.refreshing) return; llmUsageCache.refreshing = true; const profile = process.env.OPENCLAW_PROFILE || ""; const args2 = profile ? ["--profile", profile, "status", "--usage", "--json"] : ["status", "--usage", "--json"]; execFile( "openclaw", args2, { encoding: "utf8", timeout: 2e4, env: getSafeEnv() }, (err, stdout) => { llmUsageCache.refreshing = false; if (err) { console.error("[LLM Usage] Async refresh failed:", err.message); return; } try { const jsonStart = stdout.indexOf("{"); const jsonStr = jsonStart >= 0 ? stdout.slice(jsonStart) : stdout; const parsed = JSON.parse(jsonStr); if (parsed.usage) { const result = transformLiveUsageData(parsed.usage); llmUsageCache.data = result; llmUsageCache.timestamp = Date.now(); console.log("[LLM Usage] Cache refreshed"); } } catch (e) { console.error("[LLM Usage] Parse error:", e.message); } } ); } function transformLiveUsageData(usage) { const anthropic = usage.providers?.find((p) => p.provider === "anthropic"); const codexProvider = usage.providers?.find((p) => p.provider === "openai-codex"); if (anthropic?.error) { return { timestamp: (/* @__PURE__ */ new Date()).toISOString(), source: "error", error: anthropic.error, errorType: anthropic.error.includes("403") ? "auth" : "unknown", claude: { session: { usedPct: null, remainingPct: null, resetsIn: null, error: anthropic.error }, weekly: { usedPct: null, remainingPct: null, resets: null, error: anthropic.error }, sonnet: { usedPct: null, remainingPct: null, resets: null, error: anthropic.error }, lastSynced: null }, codex: { sessionsToday: 0, tasksToday: 0, usage5hPct: 0, usageDayPct: 0 }, routing: { total: 0, claudeTasks: 0, codexTasks: 0, claudePct: 0, codexPct: 0, codexFloor: 20 } }; } const session5h = anthropic?.windows?.find((w) => w.label === "5h"); const weekAll = anthropic?.windows?.find((w) => w.label === "Week"); const sonnetWeek = anthropic?.windows?.find((w) => w.label === "Sonnet"); const codex5h = codexProvider?.windows?.find((w) => w.label === "5h"); const codexDay = codexProvider?.windows?.find((w) => w.label === "Day"); const formatReset = (resetAt) => { if (!resetAt) return "?"; const diff = resetAt - Date.now(); if (diff < 0) return "now"; if (diff < 36e5) return Math.round(diff / 6e4) + "m"; if (diff < 864e5) return Math.round(diff / 36e5) + "h"; return Math.round(diff / 864e5) + "d"; }; return { timestamp: (/* @__PURE__ */ new Date()).toISOString(), source: "live", claude: { session: { usedPct: Math.round(session5h?.usedPercent || 0), remainingPct: Math.round(100 - (session5h?.usedPercent || 0)), resetsIn: formatReset(session5h?.resetAt) }, weekly: { usedPct: Math.round(weekAll?.usedPercent || 0), remainingPct: Math.round(100 - (weekAll?.usedPercent || 0)), resets: formatReset(weekAll?.resetAt) }, sonnet: { usedPct: Math.round(sonnetWeek?.usedPercent || 0), remainingPct: Math.round(100 - (sonnetWeek?.usedPercent || 0)), resets: formatReset(sonnetWeek?.resetAt) }, lastSynced: (/* @__PURE__ */ new Date()).toISOString() }, codex: { sessionsToday: 0, tasksToday: 0, usage5hPct: Math.round(codex5h?.usedPercent || 0), usageDayPct: Math.round(codexDay?.usedPercent || 0) }, routing: { total: 0, claudeTasks: 0, codexTasks: 0, claudePct: 0, codexPct: 0, codexFloor: 20 } }; } function getLlmUsage2(statePath) { const now = Date.now(); if (!llmUsageCache.data || now - llmUsageCache.timestamp > LLM_CACHE_TTL_MS) { refreshLlmUsageAsync(); } if (llmUsageCache.data && llmUsageCache.data.source !== "error") { return llmUsageCache.data; } const stateFile = path2.join(statePath, "llm-routing.json"); try { if (fs2.existsSync(stateFile)) { const data = JSON.parse(fs2.readFileSync(stateFile, "utf8")); const sessionValid = data.claude?.session?.resets_in && data.claude.session.resets_in !== "unknown"; const weeklyValid = data.claude?.weekly_all_models?.resets && data.claude.weekly_all_models.resets !== "unknown"; if (sessionValid || weeklyValid) { return { timestamp: (/* @__PURE__ */ new Date()).toISOString(), source: "file", claude: { session: { usedPct: Math.round((data.claude?.session?.used_pct || 0) * 100), remainingPct: Math.round((data.claude?.session?.remaining_pct || 1) * 100), resetsIn: data.claude?.session?.resets_in || "?" }, weekly: { usedPct: Math.round((data.claude?.weekly_all_models?.used_pct || 0) * 100), remainingPct: Math.round((data.claude?.weekly_all_models?.remaining_pct || 1) * 100), resets: data.claude?.weekly_all_models?.resets || "?" }, sonnet: { usedPct: Math.round((data.claude?.weekly_sonnet?.used_pct || 0) * 100), remainingPct: Math.round((data.claude?.weekly_sonnet?.remaining_pct || 1) * 100), resets: data.claude?.weekly_sonnet?.resets || "?" }, lastSynced: data.claude?.last_synced || null }, codex: { sessionsToday: data.codex?.sessions_today || 0, tasksToday: data.codex?.tasks_today || 0, usage5hPct: data.codex?.usage_5h_pct || 0, usageDayPct: data.codex?.usage_day_pct || 0 }, routing: { total: data.routing?.total_tasks || 0, claudeTasks: data.routing?.claude_tasks || 0, codexTasks: data.routing?.codex_tasks || 0, claudePct: data.routing?.total_tasks > 0 ? Math.round(data.routing.claude_tasks / data.routing.total_tasks * 100) : 0, codexPct: data.routing?.total_tasks > 0 ? Math.round(data.routing.codex_tasks / data.routing.total_tasks * 100) : 0, codexFloor: Math.round((data.routing?.codex_floor_pct || 0.2) * 100) } }; } } } catch (e) { console.error("[LLM Usage] File fallback failed:", e.message); } return { timestamp: (/* @__PURE__ */ new Date()).toISOString(), source: "error", error: "API key lacks user:profile OAuth scope", errorType: "auth", claude: { session: { usedPct: null, remainingPct: null, resetsIn: null, error: "Auth required" }, weekly: { usedPct: null, remainingPct: null, resets: null, error: "Auth required" }, sonnet: { usedPct: null, remainingPct: null, resets: null, error: "Auth required" }, lastSynced: null }, codex: { sessionsToday: 0, tasksToday: 0, usage5hPct: 0, usageDayPct: 0 }, routing: { total: 0, claudeTasks: 0, codexTasks: 0, claudePct: 0, codexPct: 0, codexFloor: 20 } }; } function getRoutingStats2(skillsPath, statePath, hours = 24) { const safeHours = parseInt(hours, 10) || 24; try { const { execFileSync } = require("child_process"); const skillDir = path2.join(skillsPath, "llm_routing"); const output = execFileSync( "python", ["-m", "llm_routing", "stats", "--hours", String(safeHours), "--json"], { encoding: "utf8", timeout: 1e4, cwd: skillDir, env: getSafeEnv() } ); return JSON.parse(output); } catch (e) { try { const logFile = path2.join(statePath, "routing-log.jsonl"); if (!fs2.existsSync(logFile)) { return { total_requests: 0, by_model: {}, by_task_type: {} }; } const cutoff = Date.now() - hours * 3600 * 1e3; const lines = fs2.readFileSync(logFile, "utf8").trim().split("\n").filter(Boolean); const stats = { total_requests: 0, by_model: {}, by_task_type: {}, escalations: 0, avg_latency_ms: 0, success_rate: 0 }; let latencies = []; let successes = 0; for (const line of lines) { try { const entry = JSON.parse(line); const ts = new Date(entry.timestamp).getTime(); if (ts < cutoff) continue; stats.total_requests++; const model = entry.selected_model || "unknown"; stats.by_model[model] = (stats.by_model[model] || 0) + 1; const tt = entry.task_type || "unknown"; stats.by_task_type[tt] = (stats.by_task_type[tt] || 0) + 1; if (entry.escalation_reason) stats.escalations++; if (entry.latency_ms) latencies.push(entry.latency_ms); if (entry.success === true) successes++; } catch { } } if (latencies.length > 0) { stats.avg_latency_ms = Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length); } if (stats.total_requests > 0) { stats.success_rate = Math.round(successes / stats.total_requests * 100); } return stats; } catch (e2) { console.error("Failed to read routing stats:", e2.message); return { error: e2.message }; } } } function startLlmUsageRefresh2() { setTimeout(() => refreshLlmUsageAsync(), 1e3); setInterval(() => refreshLlmUsageAsync(), LLM_CACHE_TTL_MS); } module2.exports = { refreshLlmUsageAsync, transformLiveUsageData, getLlmUsage: getLlmUsage2, getRoutingStats: getRoutingStats2, startLlmUsageRefresh: startLlmUsageRefresh2 }; } }); // src/actions.js var require_actions = __commonJS({ "src/actions.js"(exports2, module2) { var ALLOWED_ACTIONS = /* @__PURE__ */ new Set([ "gateway-status", "gateway-restart", "sessions-list", "cron-list", "health-check", "clear-stale-sessions" ]); function executeAction2(action, deps) { const { runOpenClaw: runOpenClaw2, extractJSON: extractJSON2, PORT: PORT2 } = deps; const results = { success: false, action, output: "", error: null }; if (!ALLOWED_ACTIONS.has(action)) { results.error = `Unknown action: ${action}`; return results; } try { switch (action) { case "gateway-status": results.output = runOpenClaw2("gateway status 2>&1") || "Unknown"; results.success = true; break; case "gateway-restart": results.output = "To restart gateway, run: openclaw gateway restart"; results.success = true; results.note = "Dashboard cannot restart gateway for safety"; break; case "sessions-list": results.output = runOpenClaw2("sessions 2>&1") || "No sessions"; results.success = true; break; case "cron-list": results.output = runOpenClaw2("cron list 2>&1") || "No cron jobs"; results.success = true; break; case "health-check": { const gateway = runOpenClaw2("gateway status 2>&1"); const sessions2 = runOpenClaw2("sessions --json 2>&1"); let sessionCount = 0; try { const data = JSON.parse(sessions2); sessionCount = data.sessions?.length || 0; } catch (e) { } results.output = [ `Gateway: ${gateway?.includes("running") ? "OK Running" : "NOT Running"}`, `Sessions: ${sessionCount}`, `Dashboard: OK Running on port ${PORT2}` ].join("\n"); results.success = true; break; } case "clear-stale-sessions": { const staleOutput = runOpenClaw2("sessions --json 2>&1"); let staleCount = 0; try { const staleJson = extractJSON2(staleOutput); if (staleJson) { const data = JSON.parse(staleJson); staleCount = (data.sessions || []).filter((s) => s.ageMs > 24 * 60 * 60 * 1e3).length; } } catch (e) { } results.output = `Found ${staleCount} stale sessions (>24h old). To clean: openclaw sessions prune`; results.success = true; break; } } } catch (e) { results.error = e.message; } return results; } module2.exports = { executeAction: executeAction2, ALLOWED_ACTIONS }; } }); // src/data.js var require_data = __commonJS({ "src/data.js"(exports2, module2) { var fs2 = require("fs"); var path2 = require("path"); function migrateDataDir2(dataDir, legacyDataDir) { try { if (!fs2.existsSync(legacyDataDir)) return; if (!fs2.existsSync(dataDir)) { fs2.mkdirSync(dataDir, { recursive: true }); } const legacyFiles = fs2.readdirSync(legacyDataDir); if (legacyFiles.length === 0) return; let migrated = 0; for (const file of legacyFiles) { const srcPath = path2.join(legacyDataDir, file); const destPath = path2.join(dataDir, file); if (fs2.existsSync(destPath)) continue; const stat = fs2.statSync(srcPath); if (stat.isFile()) { fs2.copyFileSync(srcPath, destPath); migrated++; console.log(`[Migration] Copied ${file} to profile-aware data dir`); } } if (migrated > 0) { console.log(`[Migration] Migrated ${migrated} file(s) to ${dataDir}`); console.log(`[Migration] Legacy data preserved at ${legacyDataDir}`); } } catch (e) { console.error("[Migration] Failed to migrate data:", e.message); } } module2.exports = { migrateDataDir: migrateDataDir2 }; } }); // src/state.js var require_state = __commonJS({ "src/state.js"(exports2, module2) { var fs2 = require("fs"); var os = require("os"); var path2 = require("path"); var { execFileSync } = require("child_process"); var { formatBytes, formatTimeAgo } = require_utils(); function createStateModule2(deps) { const { CONFIG: CONFIG2, getOpenClawDir: getOpenClawDir2, getSessions, getSystemVitals: getSystemVitals2, getCronJobs: getCronJobs2, loadOperators: loadOperators2, getLlmUsage: getLlmUsage2, getDailyTokenUsage: getDailyTokenUsage2, getTokenStats: getTokenStats2, getCerebroTopics: getCerebroTopics2, runOpenClaw: runOpenClaw2, extractJSON: extractJSON2, readTranscript } = deps; const PATHS2 = CONFIG2.paths; let cachedState = null; let lastStateUpdate = 0; const STATE_CACHE_TTL = 3e4; let stateRefreshInterval = null; function getSystemStatus() { const hostname = os.hostname(); let uptime = "\u2014"; try { const uptimeRaw = execFileSync("uptime", [], { encoding: "utf8" }); const match = uptimeRaw.match(/up\s+([^,]+)/); if (match) uptime = match[1].trim(); } catch (e) { } let gateway = "Unknown"; try { const status = runOpenClaw2("gateway status 2>/dev/null"); if (status && status.includes("running")) { gateway = "Running"; } else if (status && status.includes("stopped")) { gateway = "Stopped"; } } catch (e) { } return { hostname, gateway, model: "claude-opus-4-5", uptime }; } function getRecentActivity() { const activities = []; const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0]; const memoryFile = path2.join(PATHS2.memory, `${today}.md`); try { if (fs2.existsSync(memoryFile)) { const content = fs2.readFileSync(memoryFile, "utf8"); const lines = content.split("\n").filter((l) => l.startsWith("- ")); lines.slice(-5).forEach((line) => { const text = line.replace(/^- /, "").slice(0, 80); activities.push({ icon: text.includes("\u2705") ? "\u2705" : text.includes("\u274C") ? "\u274C" : "\u{1F4DD}", text: text.replace(/[\u2705\u274C\uD83D\uDCDD\uD83D\uDD27]/g, "").trim(), time: today }); }); } } catch (e) { console.error("Failed to read activity:", e.message); } return activities.reverse(); } function getCapacity() { const result = { main: { active: 0, max: 12 }, subagent: { active: 0, max: 24 } }; const openclawDir = getOpenClawDir2(); try { const configPath = path2.join(openclawDir, "openclaw.json"); if (fs2.existsSync(configPath)) { const config = JSON.parse(fs2.readFileSync(configPath, "utf8")); if (config?.agents?.defaults?.maxConcurrent) { result.main.max = config.agents.defaults.maxConcurrent; } if (config?.agents?.defaults?.subagents?.maxConcurrent) { result.subagent.max = config.agents.defaults.subagents.maxConcurrent; } } } catch (e) { } try { const output = runOpenClaw2("sessions --json 2>/dev/null"); const jsonStr = extractJSON2(output); if (jsonStr) { const data = JSON.parse(jsonStr); const sessions2 = data.sessions || []; const fiveMinMs = 5 * 60 * 1e3; for (const s of sessions2) { if (s.ageMs > fiveMinMs) continue; const key = s.key || ""; if (key.includes(":subagent:") || key.includes(":cron:")) { result.subagent.active++; } else { result.main.active++; } } return result; } } catch (e) { console.error("Failed to get capacity from sessions, falling back to filesystem:", e.message); } try { const sessionsDir = path2.join(openclawDir, "agents", "main", "sessions"); if (fs2.existsSync(sessionsDir)) { const fiveMinAgo = Date.now() - 5 * 60 * 1e3; const files = fs2.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl")); let mainActive = 0; let subActive = 0; for (const file of files) { try { const filePath = path2.join(sessionsDir, file); const stat = fs2.statSync(filePath); if (stat.mtimeMs < fiveMinAgo) continue; let isSubagent = false; try { const fd = fs2.openSync(filePath, "r"); const buffer = Buffer.alloc(512); fs2.readSync(fd, buffer, 0, 512, 0); fs2.closeSync(fd); const firstLine = buffer.toString("utf8").split("\n")[0]; const parsed = JSON.parse(firstLine); const key = parsed.key || parsed.id || ""; isSubagent = key.includes(":subagent:") || key.includes(":cron:"); } catch (parseErr) { isSubagent = file.includes("subagent"); } if (isSubagent) { subActive++; } else { mainActive++; } } catch (e) { } } result.main.active = mainActive; result.subagent.active = subActive; } } catch (e) { console.error("Failed to count active sessions from filesystem:", e.message); } return result; } function getMemoryStats() { const memoryDir = PATHS2.memory; const memoryFile = path2.join(PATHS2.workspace, "MEMORY.md"); const stats = { totalFiles: 0, totalSize: 0, totalSizeFormatted: "0 B", memoryMdSize: 0, memoryMdSizeFormatted: "0 B", memoryMdLines: 0, recentFiles: [], oldestFile: null, newestFile: null }; try { const collectMemoryFiles = (dir, baseDir) => { const entries = fs2.readdirSync(dir, { withFileTypes: true }); const files = []; for (const entry of entries) { const entryPath = path2.join(dir, entry.name); if (entry.isDirectory()) { files.push(...collectMemoryFiles(entryPath, baseDir)); } else if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".json"))) { const stat = fs2.statSync(entryPath); const relativePath = path2.relative(baseDir, entryPath); files.push({ name: relativePath, size: stat.size, sizeFormatted: formatBytes(stat.size), modified: stat.mtime }); } } return files; }; if (fs2.existsSync(memoryFile)) { const memStat = fs2.statSync(memoryFile); stats.memoryMdSize = memStat.size; stats.memoryMdSizeFormatted = formatBytes(memStat.size); const content = fs2.readFileSync(memoryFile, "utf8"); stats.memoryMdLines = content.split("\n").length; stats.totalSize += memStat.size; stats.totalFiles++; } if (fs2.existsSync(memoryDir)) { const files = collectMemoryFiles(memoryDir, memoryDir).sort( (a, b) => b.modified - a.modified ); stats.totalFiles += files.length; files.forEach((f) => stats.totalSize += f.size); stats.recentFiles = files.slice(0, 5).map((f) => ({ name: f.name, sizeFormatted: f.sizeFormatted, age: formatTimeAgo(f.modified) })); if (files.length > 0) { stats.newestFile = files[0].name; stats.oldestFile = files[files.length - 1].name; } } stats.totalSizeFormatted = formatBytes(stats.totalSize); } catch (e) { console.error("Failed to get memory stats:", e.message); } return stats; } function getData() { const allSessions = getSessions({ limit: null }); const pageSize = 20; const displaySessions = allSessions.slice(0, pageSize); const tokenStats = getTokenStats2(allSessions); const capacity = getCapacity(); const memory = getMemoryStats(); const statusCounts = { all: allSessions.length, live: allSessions.filter((s) => s.active).length, recent: allSessions.filter((s) => !s.active && s.recentlyActive).length, idle: allSessions.filter((s) => !s.active && !s.recentlyActive).length }; const totalPages = Math.ceil(allSessions.length / pageSize); return { sessions: displaySessions, tokenStats, capacity, memory, pagination: { page: 1, pageSize, total: allSessions.length, totalPages, hasPrev: false, hasNext: totalPages > 1 }, statusCounts }; } function getFullState() { const now = Date.now(); if (cachedState && now - lastStateUpdate < STATE_CACHE_TTL) { return cachedState; } let sessions2 = []; let tokenStats = {}; let statusCounts = { all: 0, live: 0, recent: 0, idle: 0 }; let vitals = {}; let capacity = {}; let operators = { operators: [], roles: {} }; let llmUsage = {}; let cron = []; let memory = {}; let cerebro = {}; let subagents = []; let allSessions = []; let totalSessionCount = 0; try { allSessions = getSessions({ limit: null }); totalSessionCount = allSessions.length; sessions2 = allSessions.slice(0, 20); } catch (e) { console.error("[State] sessions:", e.message); } try { vitals = getSystemVitals2(); } catch (e) { console.error("[State] vitals:", e.message); } try { capacity = getCapacity(); } catch (e) { console.error("[State] capacity:", e.message); } try { tokenStats = getTokenStats2(allSessions, capacity, CONFIG2); } catch (e) { console.error("[State] tokenStats:", e.message); } try { const liveSessions = allSessions.filter((s) => s.active); const recentSessions = allSessions.filter((s) => !s.active && s.recentlyActive); const idleSessions = allSessions.filter((s) => !s.active && !s.recentlyActive); statusCounts = { all: totalSessionCount, live: liveSessions.length, recent: recentSessions.length, idle: idleSessions.length }; } catch (e) { console.error("[State] statusCounts:", e.message); } try { const operatorData = loadOperators2(); const operatorsWithStats = operatorData.operators.map((op) => { const userSessions = allSessions.filter( (s) => s.originator?.userId === op.id || s.originator?.userId === op.metadata?.slackId ); return { ...op, stats: { activeSessions: userSessions.filter((s) => s.active).length, totalSessions: userSessions.length, lastSeen: userSessions.length > 0 ? new Date( Date.now() - Math.min(...userSessions.map((s) => s.minutesAgo)) * 6e4 ).toISOString() : op.lastSeen } }; }); operators = { ...operatorData, operators: operatorsWithStats }; } catch (e) { console.error("[State] operators:", e.message); } try { llmUsage = getLlmUsage2(); } catch (e) { console.error("[State] llmUsage:", e.message); } try { cron = getCronJobs2(); } catch (e) { console.error("[State] cron:", e.message); } try { memory = getMemoryStats(); } catch (e) { console.error("[State] memory:", e.message); } try { cerebro = getCerebroTopics2(); } catch (e) { console.error("[State] cerebro:", e.message); } try { const retentionHours = parseInt(process.env.SUBAGENT_RETENTION_HOURS || "12", 10); const retentionMs = retentionHours * 60 * 60 * 1e3; subagents = allSessions.filter((s) => s.sessionKey && s.sessionKey.includes(":subagent:")).filter((s) => (s.minutesAgo || 0) * 6e4 < retentionMs).map((s) => { const match = s.sessionKey.match(/:subagent:([a-f0-9-]+)$/); const subagentId = match ? match[1] : s.sessionId; return { id: subagentId, shortId: subagentId.slice(0, 8), task: s.label || s.displayName || "Sub-agent task", tokens: s.tokens || 0, ageMs: (s.minutesAgo || 0) * 6e4, active: s.active, recentlyActive: s.recentlyActive }; }); } catch (e) { console.error("[State] subagents:", e.message); } cachedState = { vitals, sessions: sessions2, tokenStats, statusCounts, capacity, operators, llmUsage, cron, memory, cerebro, subagents, pagination: { page: 1, pageSize: 20, total: totalSessionCount, totalPages: Math.max(1, Math.ceil(totalSessionCount / 20)), hasPrev: false, hasNext: totalSessionCount > 20 }, timestamp: now }; lastStateUpdate = now; return cachedState; } function refreshState() { lastStateUpdate = 0; return getFullState(); } function startStateRefresh(broadcastSSE2, intervalMs = 3e4) { if (stateRefreshInterval) return; stateRefreshInterval = setInterval(() => { try { const newState = refreshState(); broadcastSSE2("update", newState); } catch (e) { console.error("[State] Refresh error:", e.message); } }, intervalMs); console.log(`[State] Background refresh started (${intervalMs}ms interval)`); } function stopStateRefresh() { if (stateRefreshInterval) { clearInterval(stateRefreshInterval); stateRefreshInterval = null; console.log("[State] Background refresh stopped"); } } function getSubagentStatus() { const subagents = []; try { const output = runOpenClaw2("sessions --json 2>/dev/null"); const jsonStr = extractJSON2(output); if (jsonStr) { const data = JSON.parse(jsonStr); const subagentSessions = (data.sessions || []).filter( (s) => s.key && s.key.includes(":subagent:") ); for (const s of subagentSessions) { const ageMs = s.ageMs || Infinity; const isActive = ageMs < 5 * 60 * 1e3; const isRecent = ageMs < 30 * 60 * 1e3; const match = s.key.match(/:subagent:([a-f0-9-]+)$/); const subagentId = match ? match[1] : s.sessionId; const shortId = subagentId.slice(0, 8); let taskSummary = "Unknown task"; let label = null; const transcript = readTranscript(s.sessionId); for (const entry of transcript.slice(0, 15)) { if (entry.type === "message" && entry.message?.role === "user") { const content = entry.message.content; let text = ""; if (typeof content === "string") { text = content; } else if (Array.isArray(content)) { const textPart = content.find((c) => c.type === "text"); if (textPart) text = textPart.text || ""; } if (!text) continue; const labelMatch = text.match(/Label:\s*([^\n]+)/i); if (labelMatch) { label = labelMatch[1].trim(); } let taskMatch = text.match(/You were created to handle:\s*\*\*([^*]+)\*\*/i); if (taskMatch) { taskSummary = taskMatch[1].trim(); break; } taskMatch = text.match(/\*\*([A-Z]{2,5}-\d+:\s*[^*]+)\*\*/); if (taskMatch) { taskSummary = taskMatch[1].trim(); break; } const firstLine = text.split("\n")[0].replace(/^\*\*|\*\*$/g, "").trim(); if (firstLine.length > 10 && firstLine.length < 100) { taskSummary = firstLine; break; } } } const messageCount = transcript.filter( (e) => e.type === "message" && e.message?.role ).length; subagents.push({ id: subagentId, shortId, sessionId: s.sessionId, label: label || shortId, task: taskSummary, model: s.model?.replace("anthropic/", "") || "unknown", status: isActive ? "active" : isRecent ? "idle" : "stale", ageMs, ageFormatted: ageMs < 6e4 ? "Just now" : ageMs < 36e5 ? `${Math.round(ageMs / 6e4)}m ago` : `${Math.round(ageMs / 36e5)}h ago`, messageCount, tokens: s.totalTokens || 0 }); } } } catch (e) { console.error("Failed to get subagent status:", e.message); } return subagents.sort((a, b) => a.ageMs - b.ageMs); } return { getSystemStatus, getRecentActivity, getCapacity, getMemoryStats, getFullState, refreshState, startStateRefresh, stopStateRefresh, getData, getSubagentStatus }; } module2.exports = { createStateModule: createStateModule2 }; } }); // src/index.js var http = require("http"); var fs = require("fs"); var path = require("path"); var args = process.argv.slice(2); var cliProfile = null; var cliPort = null; for (let i = 0; i < args.length; i++) { switch (args[i]) { case "--profile": case "-p": cliProfile = args[++i]; break; case "--port": cliPort = parseInt(args[++i], 10); break; case "--help": case "-h": console.log(` OpenClaw Command Center Usage: node lib/server.js [options] Options: --profile, -p OpenClaw profile (uses ~/.openclaw-) --port Server port (default: 3333) --help, -h Show this help Environment: OPENCLAW_PROFILE Same as --profile PORT Same as --port Examples: node lib/server.js --profile production node lib/server.js -p dev --port 3334 `); process.exit(0); } } if (cliProfile) { process.env.OPENCLAW_PROFILE = cliProfile; } if (cliPort) { process.env.PORT = cliPort.toString(); } var { getVersion } = require_utils(); var { CONFIG, getOpenClawDir } = require_config(); var { handleJobsRequest, isJobsRoute } = require_jobs(); var { runOpenClaw, runOpenClawAsync, extractJSON } = require_openclaw(); var { getSystemVitals, checkOptionalDeps, getOptionalDeps } = require_vitals(); var { checkAuth, getUnauthorizedPage } = require_auth(); var { loadPrivacySettings, savePrivacySettings } = require_privacy(); var { loadOperators, saveOperators, getOperatorBySlackId, startOperatorsRefresh } = require_operators(); var { createSessionsModule } = require_sessions(); var { getCronJobs } = require_cron(); var { getCerebroTopics, updateTopicStatus } = require_cerebro(); var { getDailyTokenUsage, getTokenStats, getCostBreakdown, startTokenUsageRefresh, refreshTokenUsageAsync } = require_tokens(); var { getLlmUsage, getRoutingStats, startLlmUsageRefresh } = require_llm_usage(); var { executeAction } = require_actions(); var { migrateDataDir } = require_data(); var { createStateModule } = require_state(); var PORT = CONFIG.server.port; var DASHBOARD_DIR = path.join(__dirname, "../public"); var PATHS = CONFIG.paths; var AUTH_CONFIG = { mode: CONFIG.auth.mode, token: CONFIG.auth.token, allowedUsers: CONFIG.auth.allowedUsers, allowedIPs: CONFIG.auth.allowedIPs, publicPaths: CONFIG.auth.publicPaths }; var DATA_DIR = path.join(getOpenClawDir(), "command-center", "data"); var LEGACY_DATA_DIR = path.join(DASHBOARD_DIR, "data"); var sseClients = /* @__PURE__ */ new Set(); function sendSSE(res, event, data) { try { res.write(`event: ${event} data: ${JSON.stringify(data)} `); } catch (e) { } } function broadcastSSE(event, data) { for (const client of sseClients) { sendSSE(client, event, data); } } var sessions = createSessionsModule({ getOpenClawDir, getOperatorBySlackId: (slackId) => getOperatorBySlackId(DATA_DIR, slackId), runOpenClaw, runOpenClawAsync, extractJSON }); var state = createStateModule({ CONFIG, getOpenClawDir, getSessions: (opts) => sessions.getSessions(opts), getSystemVitals, getCronJobs: () => getCronJobs(getOpenClawDir), loadOperators: () => loadOperators(DATA_DIR), getLlmUsage: () => getLlmUsage(PATHS.state), getDailyTokenUsage: () => getDailyTokenUsage(getOpenClawDir), getTokenStats, getCerebroTopics: (opts) => getCerebroTopics(PATHS.cerebro, opts), runOpenClaw, extractJSON, readTranscript: (sessionId) => sessions.readTranscript(sessionId) }); process.nextTick(() => migrateDataDir(DATA_DIR, LEGACY_DATA_DIR)); startOperatorsRefresh(DATA_DIR, getOpenClawDir); startLlmUsageRefresh(); startTokenUsageRefresh(getOpenClawDir); function serveStatic(req, res) { const requestUrl = new URL(req.url, `http://${req.headers.host || "localhost"}`); const pathname = requestUrl.pathname === "/" ? "/index.html" : requestUrl.pathname; if (pathname.includes("..")) { res.writeHead(400); res.end("Bad request"); return; } const normalizedPath = path.normalize(pathname).replace(/^[/\\]+/, ""); const filePath = path.join(DASHBOARD_DIR, normalizedPath); const resolvedDashboardDir = path.resolve(DASHBOARD_DIR); const resolvedFilePath = path.resolve(filePath); if (!resolvedFilePath.startsWith(resolvedDashboardDir + path.sep) && resolvedFilePath !== resolvedDashboardDir) { res.writeHead(403); res.end("Forbidden"); return; } const ext = path.extname(filePath); const contentTypes = { ".html": "text/html", ".css": "text/css", ".js": "text/javascript", ".json": "application/json", ".png": "image/png", ".svg": "image/svg+xml" }; fs.readFile(filePath, (err, content) => { if (err) { res.writeHead(404); res.end("Not found"); return; } const headers = { "Content-Type": contentTypes[ext] || "text/plain" }; if ([".html", ".css", ".js", ".json"].includes(ext)) { headers["Cache-Control"] = "no-store"; } res.writeHead(200, headers); res.end(content); }); } function handleApi(req, res) { const sessionsList = sessions.getSessions(); const capacity = state.getCapacity(); const tokenStats = getTokenStats(sessionsList, capacity, CONFIG); const data = { sessions: sessionsList, cron: getCronJobs(getOpenClawDir), system: state.getSystemStatus(), activity: state.getRecentActivity(), tokenStats, capacity, timestamp: (/* @__PURE__ */ new Date()).toISOString() }; res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(data, null, 2)); } var server = http.createServer((req, res) => { res.setHeader("Access-Control-Allow-Origin", "*"); const urlParts = req.url.split("?"); const pathname = urlParts[0]; const query = new URLSearchParams(urlParts[1] || ""); if (pathname === "/api/health") { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ status: "ok", port: PORT, timestamp: (/* @__PURE__ */ new Date()).toISOString() })); return; } const isPublicPath = AUTH_CONFIG.publicPaths.some( (p) => pathname === p || pathname.startsWith(p + "/") ); if (!isPublicPath && AUTH_CONFIG.mode !== "none") { const authResult = checkAuth(req, AUTH_CONFIG); if (!authResult.authorized) { console.log(`[AUTH] Denied: ${authResult.reason} (path: ${pathname})`); res.writeHead(403, { "Content-Type": "text/html" }); res.end(getUnauthorizedPage(authResult.reason, authResult.user, AUTH_CONFIG)); return; } req.authUser = authResult.user; if (authResult.user?.login || authResult.user?.email) { console.log( `[AUTH] Allowed: ${authResult.user.login || authResult.user.email} (path: ${pathname})` ); } else { console.log(`[AUTH] Allowed: ${req.socket?.remoteAddress} (path: ${pathname})`); } } if (pathname === "/api/status") { handleApi(req, res); } else if (pathname === "/api/session") { const sessionKey = query.get("key"); if (!sessionKey) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Missing session key" })); return; } const detail = sessions.getSessionDetail(sessionKey); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(detail, null, 2)); } else if (pathname === "/api/cerebro") { const offset = parseInt(query.get("offset") || "0", 10); const limit = parseInt(query.get("limit") || "20", 10); const statusFilter = query.get("status") || "all"; const data = getCerebroTopics(PATHS.cerebro, { offset, limit, status: statusFilter }); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(data, null, 2)); } else if (pathname.startsWith("/api/cerebro/topic/") && pathname.endsWith("/status") && req.method === "POST") { const topicId = decodeURIComponent( pathname.replace("/api/cerebro/topic/", "").replace("/status", "") ); let body = ""; req.on("data", (chunk) => { body += chunk; }); req.on("end", () => { try { const { status: newStatus } = JSON.parse(body); if (!newStatus || !["active", "resolved", "parked"].includes(newStatus)) { res.writeHead(400, { "Content-Type": "application/json" }); res.end( JSON.stringify({ error: "Invalid status. Must be: active, resolved, or parked" }) ); return; } const result = updateTopicStatus(PATHS.cerebro, topicId, newStatus); if (result.error) { res.writeHead(result.code || 500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: result.error })); return; } res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(result, null, 2)); } catch (e) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Invalid JSON body" })); } }); return; } else if (pathname === "/api/llm-quota") { const data = getLlmUsage(PATHS.state); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(data, null, 2)); } else if (pathname === "/api/cost-breakdown") { const data = getCostBreakdown(CONFIG, (opts) => sessions.getSessions(opts), getOpenClawDir); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(data, null, 2)); } else if (pathname === "/api/subagents") { const data = state.getSubagentStatus(); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ subagents: data }, null, 2)); } else if (pathname === "/api/action") { const action = query.get("action"); if (!action) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Missing action parameter" })); return; } const result = executeAction(action, { runOpenClaw, extractJSON, PORT }); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(result, null, 2)); } else if (pathname === "/api/events") { res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive", "X-Accel-Buffering": "no" }); sseClients.add(res); console.log(`[SSE] Client connected (total: ${sseClients.size})`); sendSSE(res, "connected", { message: "Connected to Command Center", timestamp: Date.now() }); const cachedState = state.getFullState(); if (cachedState) { sendSSE(res, "update", cachedState); } else { sendSSE(res, "update", { sessions: [], loading: true }); } req.on("close", () => { sseClients.delete(res); console.log(`[SSE] Client disconnected (total: ${sseClients.size})`); }); return; } else if (pathname === "/api/whoami") { res.writeHead(200, { "Content-Type": "application/json" }); res.end( JSON.stringify( { authMode: AUTH_CONFIG.mode, user: req.authUser || null }, null, 2 ) ); } else if (pathname === "/api/about") { res.writeHead(200, { "Content-Type": "application/json" }); res.end( JSON.stringify( { name: "OpenClaw Command Center", version: getVersion(), description: "A Starcraft-inspired dashboard for AI agent orchestration", license: "MIT", repository: "https://github.com/jontsai/openclaw-command-center", builtWith: ["OpenClaw", "Node.js", "Vanilla JS"], inspirations: ["Starcraft", "Inside Out", "iStatMenus", "DaisyDisk", "Gmail"] }, null, 2 ) ); } else if (pathname === "/api/state") { const fullState = state.getFullState(); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(fullState, null, 2)); } else if (pathname === "/api/vitals") { const vitals = getSystemVitals(); const optionalDeps = getOptionalDeps(); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ vitals, optionalDeps }, null, 2)); } else if (pathname === "/api/capacity") { const capacity = state.getCapacity(); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(capacity, null, 2)); } else if (pathname === "/api/sessions") { const page = parseInt(query.get("page")) || 1; const pageSize = parseInt(query.get("pageSize")) || 20; const statusFilter = query.get("status"); const allSessions = sessions.getSessions({ limit: null }); const statusCounts = { all: allSessions.length, live: allSessions.filter((s) => s.active).length, recent: allSessions.filter((s) => !s.active && s.recentlyActive).length, idle: allSessions.filter((s) => !s.active && !s.recentlyActive).length }; let filteredSessions = allSessions; if (statusFilter === "live") { filteredSessions = allSessions.filter((s) => s.active); } else if (statusFilter === "recent") { filteredSessions = allSessions.filter((s) => !s.active && s.recentlyActive); } else if (statusFilter === "idle") { filteredSessions = allSessions.filter((s) => !s.active && !s.recentlyActive); } const total = filteredSessions.length; const totalPages = Math.ceil(total / pageSize); const offset = (page - 1) * pageSize; const displaySessions = filteredSessions.slice(offset, offset + pageSize); const tokenStats = getTokenStats(allSessions, state.getCapacity(), CONFIG); const capacity = state.getCapacity(); res.writeHead(200, { "Content-Type": "application/json" }); res.end( JSON.stringify( { sessions: displaySessions, pagination: { page, pageSize, total, totalPages, hasPrev: page > 1, hasNext: page < totalPages }, statusCounts, tokenStats, capacity }, null, 2 ) ); } else if (pathname === "/api/cron") { const cron = getCronJobs(getOpenClawDir); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ cron }, null, 2)); } else if (pathname === "/api/operators") { const method = req.method; const data = loadOperators(DATA_DIR); if (method === "GET") { const allSessions = sessions.getSessions({ limit: null }); const operatorsWithStats = data.operators.map((op) => { const userSessions = allSessions.filter( (s) => s.originator?.userId === op.id || s.originator?.userId === op.metadata?.slackId ); return { ...op, stats: { activeSessions: userSessions.filter((s) => s.active).length, totalSessions: userSessions.length, lastSeen: userSessions.length > 0 ? new Date( Date.now() - Math.min(...userSessions.map((s) => s.minutesAgo)) * 6e4 ).toISOString() : op.lastSeen } }; }); res.writeHead(200, { "Content-Type": "application/json" }); res.end( JSON.stringify( { operators: operatorsWithStats, roles: data.roles, timestamp: Date.now() }, null, 2 ) ); } else if (method === "POST") { let body = ""; req.on("data", (chunk) => body += chunk); req.on("end", () => { try { const newOp = JSON.parse(body); const existingIdx = data.operators.findIndex((op) => op.id === newOp.id); if (existingIdx >= 0) { data.operators[existingIdx] = { ...data.operators[existingIdx], ...newOp }; } else { data.operators.push({ ...newOp, createdAt: (/* @__PURE__ */ new Date()).toISOString() }); } if (saveOperators(DATA_DIR, data)) { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ success: true, operator: newOp })); } else { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Failed to save" })); } } catch (e) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Invalid JSON" })); } }); return; } else { res.writeHead(405, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Method not allowed" })); } return; } else if (pathname === "/api/llm-usage") { const usage = getLlmUsage(PATHS.state); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(usage, null, 2)); } else if (pathname === "/api/routing-stats") { const hours = parseInt(query.get("hours") || "24", 10); const stats = getRoutingStats(PATHS.skills, PATHS.state, hours); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(stats, null, 2)); } else if (pathname === "/api/memory") { const memory = state.getMemoryStats(); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ memory }, null, 2)); } else if (pathname === "/api/privacy") { if (req.method === "GET") { const settings = loadPrivacySettings(DATA_DIR); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(settings, null, 2)); } else if (req.method === "POST" || req.method === "PUT") { let body = ""; req.on("data", (chunk) => body += chunk); req.on("end", () => { try { const updates = JSON.parse(body); const current = loadPrivacySettings(DATA_DIR); const merged = { version: current.version || 1, hiddenTopics: updates.hiddenTopics ?? current.hiddenTopics ?? [], hiddenSessions: updates.hiddenSessions ?? current.hiddenSessions ?? [], hiddenCrons: updates.hiddenCrons ?? current.hiddenCrons ?? [], hideHostname: updates.hideHostname ?? current.hideHostname ?? false }; if (savePrivacySettings(DATA_DIR, merged)) { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ success: true, settings: merged })); } else { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Failed to save privacy settings" })); } } catch (e) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Invalid JSON: " + e.message })); } }); return; } else { res.writeHead(405, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Method not allowed" })); } return; } else if (isJobsRoute(pathname)) { handleJobsRequest(req, res, pathname, query, req.method); } else { serveStatic(req, res); } }); server.listen(PORT, () => { const profile = process.env.OPENCLAW_PROFILE; console.log(`\u{1F99E} OpenClaw Command Center running at http://localhost:${PORT}`); if (profile) { console.log(` Profile: ${profile} (~/.openclaw-${profile})`); } console.log(` Press Ctrl+C to stop`); setTimeout(async () => { console.log("[Startup] Pre-warming caches in background..."); try { await Promise.all([sessions.refreshSessionsCache(), refreshTokenUsageAsync(getOpenClawDir)]); getSystemVitals(); console.log("[Startup] Caches warmed."); } catch (e) { console.log("[Startup] Cache warming error:", e.message); } checkOptionalDeps(); }, 100); const SESSIONS_CACHE_TTL = 1e4; setInterval(() => sessions.refreshSessionsCache(), SESSIONS_CACHE_TTL); }); var sseRefreshing = false; setInterval(() => { if (sseClients.size > 0 && !sseRefreshing) { sseRefreshing = true; try { const fullState = state.refreshState(); broadcastSSE("update", fullState); broadcastSSE("heartbeat", { clients: sseClients.size, timestamp: Date.now() }); } catch (e) { console.error("[SSE] Broadcast error:", e.message); } sseRefreshing = false; } }, 15e3);