UNPKG

@stackmemoryai/stackmemory

Version:

Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.

195 lines (194 loc) 7.23 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import express from "express"; import { createServer } from "http"; import { Server as SocketServer } from "socket.io"; import cors from "cors"; import { LinearTaskReader } from "../../tui/services/linear-task-reader.js"; import { SessionManager } from "../../../core/session/session-manager.js"; import { FrameManager } from "../../../core/context/index.js"; import Database from "better-sqlite3"; import { existsSync } from "fs"; import { join } from "path"; function getEnv(key, defaultValue) { const value = process.env[key]; if (value === void 0) { if (defaultValue !== void 0) return defaultValue; throw new Error(`Environment variable ${key} is required`); } return value; } function getOptionalEnv(key) { return process.env[key]; } const app = express(); const httpServer = createServer(app); const io = new SocketServer(httpServer, { cors: { origin: process.env["CLIENT_URL"] || "http://localhost:3000", methods: ["GET", "POST"] } }); app.use(cors()); app.use(express.json()); let taskReader; let sessionManager; let frameManager = null; let db = null; function initializeServices() { console.log("\u{1F680} Initializing StackMemory Web Server..."); taskReader = new LinearTaskReader(process.cwd()); console.log( `\u{1F4CB} TaskReader initialized with ${taskReader.getTasks().length} tasks` ); sessionManager = new SessionManager({ enableMonitoring: true }); console.log("\u{1F4CA} SessionManager initialized"); const dbPath = join(process.cwd(), ".stackmemory", "context.db"); if (existsSync(dbPath)) { try { db = new Database(dbPath); frameManager = new FrameManager(db, "web"); console.log("\u{1F4BE} Database and FrameManager initialized"); } catch (error) { console.error("\u274C Failed to initialize database:", error); } } } app.get("/api/health", (req, res) => { res.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() }); }); app.get("/api/tasks", (req, res) => { try { const tasks = taskReader.getTasks(); res.json({ tasks, total: tasks.length }); } catch (error) { res.status(500).json({ error: "Failed to fetch tasks" }); } }); app.get("/api/tasks/active", (req, res) => { try { const tasks = taskReader.getActiveTasks(); res.json({ tasks, total: tasks.length }); } catch (error) { res.status(500).json({ error: "Failed to fetch active tasks" }); } }); app.get("/api/tasks/by-state/:state", (req, res) => { try { const tasks = taskReader.getTasksByState(req.params.state); res.json({ tasks, total: tasks.length }); } catch (error) { res.status(500).json({ error: "Failed to fetch tasks by state" }); } }); app.get("/api/sessions", (req, res) => { try { const sessions = sessionManager?.getActiveSessions ? sessionManager.getActiveSessions() : []; res.json({ sessions, total: sessions.length }); } catch (error) { res.status(500).json({ error: "Failed to fetch sessions" }); } }); app.get("/api/frames", (req, res) => { try { if (!frameManager) { res.json({ frames: [], total: 0 }); return; } const frames = frameManager.getAllFrames(); res.json({ frames, total: frames.length }); } catch (error) { res.status(500).json({ error: "Failed to fetch frames" }); } }); app.get("/api/analytics", (req, res) => { try { const tasks = taskReader.getTasks(); const sessions = sessionManager?.getActiveSessions ? sessionManager.getActiveSessions() : []; const frames = frameManager?.getAllFrames() || []; const analytics = { summary: { totalTasks: tasks.length, activeTasks: tasks.filter((t) => t.state === "In Progress").length, completedTasks: tasks.filter((t) => t.state === "Done").length, totalSessions: sessions.length, totalFrames: frames.length }, tasksByState: tasks.reduce( (acc, task) => { acc[task.state] = (acc[task.state] || 0) + 1; return acc; }, {} ), tasksByPriority: tasks.reduce( (acc, task) => { const priority = task.priority || 4; acc[priority] = (acc[priority] || 0) + 1; return acc; }, {} ), recentActivity: { tasksUpdatedToday: tasks.filter((t) => { const updated = new Date(t.updatedAt); const today = /* @__PURE__ */ new Date(); return updated.toDateString() === today.toDateString(); }).length, sessionsToday: sessions.filter((s) => { const started = new Date(s.startTime); const today = /* @__PURE__ */ new Date(); return started.toDateString() === today.toDateString(); }).length } }; res.json(analytics); } catch (error) { res.status(500).json({ error: "Failed to fetch analytics" }); } }); io.on("connection", (socket) => { console.log("\u{1F464} Client connected:", socket.id); socket.emit("initial-data", { tasks: taskReader.getTasks(), sessions: sessionManager?.getActiveSessions ? sessionManager.getActiveSessions() : [], frames: frameManager?.getAllFrames() || [] }); socket.on("refresh-tasks", () => { socket.emit("tasks:update", taskReader.getTasks()); }); socket.on("refresh-sessions", () => { const sessions = sessionManager?.getActiveSessions ? sessionManager.getActiveSessions() : []; socket.emit("sessions:update", sessions); }); socket.on("refresh-frames", () => { socket.emit("frames:update", frameManager?.getAllFrames() || []); }); socket.on("disconnect", () => { console.log("\u{1F44B} Client disconnected:", socket.id); }); }); setInterval(() => { try { io.emit("tasks:update", taskReader.getTasks()); const sessions = sessionManager?.getActiveSessions ? sessionManager.getActiveSessions() : []; io.emit("sessions:update", sessions); io.emit("frames:update", frameManager?.getAllFrames() || []); } catch (error) { console.error("Error in periodic update:", error); } }, 5e3); const PORT = process.env["WS_PORT"] || 8080; initializeServices(); httpServer.listen(PORT, () => { console.log(` \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557 \u2551 StackMemory Web Dashboard Server \u2551 \u2551 Running on http://localhost:${PORT} \u2551 \u2551 WebSocket: ws://localhost:${PORT} \u2551 \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D `); }); //# sourceMappingURL=index.js.map