UNPKG

@stackmemoryai/stackmemory

Version:

Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a

216 lines (215 loc) 5.76 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import { exec } from "child_process"; import { promisify } from "util"; import { existsSync } from "fs"; import { join } from "path"; import { logger } from "../core/monitoring/logger.js"; const execAsync = promisify(exec); class DashboardLauncherSkill { config; serverProcess = null; webProcess = null; constructor(config) { this.config = { autoLaunch: true, webPort: 3e3, serverPort: 8080, openBrowser: true, mode: "web", ...config }; } /** * Check if the dashboard server is running */ async isServerRunning() { try { const response = await fetch( `http://localhost:${this.config.serverPort}/api/health` ); return response.ok; } catch { return false; } } /** * Check if the web dashboard is running */ async isWebRunning() { try { const response = await fetch(`http://localhost:${this.config.webPort}`); return response.ok; } catch { return false; } } /** * Launch the dashboard server */ async launchServer() { if (await this.isServerRunning()) { logger.info("Dashboard server already running"); return; } logger.info("Starting dashboard server..."); try { await execAsync("npm run build"); const { spawn } = await import("child_process"); this.serverProcess = spawn( "node", ["dist/features/web/server/index.js"], { detached: true, stdio: "ignore", env: { ...process.env, WS_PORT: String(this.config.serverPort) } } ); this.serverProcess.unref(); let attempts = 0; while (attempts < 10) { if (await this.isServerRunning()) { logger.info( `Dashboard server started on port ${this.config.serverPort}` ); return; } await new Promise((r) => setTimeout(r, 1e3)); attempts++; } throw new Error("Server failed to start"); } catch (error) { logger.error("Failed to launch dashboard server:", error); throw error; } } /** * Launch the web dashboard */ async launchWeb() { if (await this.isWebRunning()) { logger.info("Web dashboard already running"); if (this.config.openBrowser) { await this.openInBrowser(); } return; } logger.info("Starting web dashboard..."); try { const webPath = join(process.cwd(), "src/features/web/client"); if (!existsSync(webPath)) { logger.warn("Web dashboard not found. Run setup first."); return; } const { spawn } = await import("child_process"); this.webProcess = spawn("npm", ["run", "dev"], { cwd: webPath, detached: true, stdio: "ignore", env: { ...process.env, PORT: String(this.config.webPort), NEXT_PUBLIC_WS_URL: `http://localhost:${this.config.serverPort}` } }); this.webProcess.unref(); let attempts = 0; while (attempts < 15) { if (await this.isWebRunning()) { logger.info(`Web dashboard started on port ${this.config.webPort}`); if (this.config.openBrowser) { await this.openInBrowser(); } return; } await new Promise((r) => setTimeout(r, 2e3)); attempts++; } throw new Error("Web dashboard failed to start"); } catch (error) { logger.error("Failed to launch web dashboard:", error); throw error; } } /** * Launch the TUI dashboard */ async launchTUI() { logger.info("Launching TUI dashboard..."); try { const { spawn } = await import("child_process"); spawn("node", ["dist/features/tui/index.js"], { stdio: "inherit" }); } catch (error) { logger.error("Failed to launch TUI:", error); throw error; } } /** * Open dashboard in browser */ async openInBrowser() { const url = `http://localhost:${this.config.webPort}`; const platform = process.platform; try { let command; if (platform === "darwin") { command = `open ${url}`; } else if (platform === "win32") { command = `start ${url}`; } else { command = `xdg-open ${url}`; } await execAsync(command); logger.info(`Opened dashboard in browser: ${url}`); } catch (error) { logger.warn("Failed to open browser:", error); } } /** * Launch dashboard based on configuration */ async launch() { if (!this.config.autoLaunch) { logger.info("Dashboard auto-launch disabled"); return; } try { if (this.config.mode === "web" || this.config.mode === "both") { await this.launchServer(); await this.launchWeb(); } if (this.config.mode === "tui" || this.config.mode === "both") { await this.launchTUI(); } logger.info("Dashboard launched successfully"); } catch (error) { logger.error("Dashboard launch failed:", error); } } /** * Stop all dashboard processes */ async stop() { if (this.serverProcess) { this.serverProcess.kill(); this.serverProcess = null; } if (this.webProcess) { this.webProcess.kill(); this.webProcess = null; } logger.info("Dashboard processes stopped"); } } const dashboardLauncher = new DashboardLauncherSkill(); export { DashboardLauncherSkill, dashboardLauncher };