@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
JavaScript
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
};