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.

211 lines (210 loc) 7.27 kB
#!/usr/bin/env node import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import chalk from "chalk"; import Table from "cli-table3"; import { SessionManager } from "../../core/session/session-manager.js"; import Database from "better-sqlite3"; import { join } from "path"; import { existsSync } from "fs"; const dashboardCommand = { command: "dashboard", describe: "Display monitoring dashboard in terminal", builder: (yargs) => { return yargs.option("watch", { alias: "w", type: "boolean", description: "Auto-refresh dashboard", default: false }).option("interval", { alias: "i", type: "number", description: "Refresh interval in seconds", default: 5 }); }, handler: async (argv) => { const projectRoot = process.cwd(); const dbPath = join(projectRoot, ".stackmemory", "context.db"); if (!existsSync(dbPath)) { console.log( '\u274C StackMemory not initialized. Run "stackmemory init" first.' ); return; } const displayDashboard = async () => { console.clear(); console.log( chalk.cyan.bold( "\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\u2550\u2550\u2550\u2550\u2550" ) ); console.log( chalk.cyan.bold( " \u{1F680} StackMemory Monitoring Dashboard " ) ); console.log( chalk.cyan.bold( "\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\u2550\u2550\u2550\u2550\u2550" ) ); console.log(); const sessionManager = new SessionManager({ enableMonitoring: false }); await sessionManager.initialize(); const db = new Database(dbPath); const sessions = await sessionManager.listSessions({ state: "active", limit: 5 }); const sessionsTable = new Table({ head: [ chalk.white("Session ID"), chalk.white("Status"), chalk.white("Branch"), chalk.white("Duration"), chalk.white("Last Active") ], style: { head: [], border: [] } }); sessions.forEach((session) => { const duration = Math.round( (Date.now() - session.startedAt) / 1e3 / 60 ); const lastActive = Math.round( (Date.now() - session.lastActiveAt) / 1e3 / 60 ); const status = session.state === "active" ? chalk.green("\u25CF Active") : session.state === "completed" ? chalk.gray("\u25CF Completed") : chalk.yellow("\u25CF Idle"); sessionsTable.push([ session.sessionId.substring(0, 8), status, session.branch || "main", `${duration}m`, `${lastActive}m ago` ]); }); console.log(chalk.yellow.bold("\u{1F4CA} Active Sessions")); console.log(sessionsTable.toString()); console.log(); const frameStats = db.prepare( ` SELECT COUNT(*) as total, SUM(CASE WHEN state = 'active' THEN 1 ELSE 0 END) as active, COUNT(DISTINCT run_id) as sessions FROM frames ` ).get(); const statsTable = new Table({ head: [chalk.white("Metric"), chalk.white("Value")], style: { head: [], border: [] } }); statsTable.push( ["Total Frames", frameStats.total || 0], ["Active Frames", chalk.green(frameStats.active || 0)], ["Total Sessions", frameStats.sessions || 0] ); console.log(chalk.yellow.bold("\u{1F4C8} Frame Statistics")); console.log(statsTable.toString()); console.log(); const recentActivity = db.prepare( ` SELECT name, type, state, datetime(created_at, 'unixepoch') as created FROM frames ORDER BY created_at DESC LIMIT 5 ` ).all(); if (recentActivity.length > 0) { const activityTable = new Table({ head: [ chalk.white("Frame"), chalk.white("Type"), chalk.white("Status"), chalk.white("Created") ], style: { head: [], border: [] } }); recentActivity.forEach((frame) => { const status = frame.state === "active" ? chalk.green("Active") : chalk.gray("Closed"); activityTable.push([ frame.name.substring(0, 30), frame.type, status, frame.created ]); }); console.log(chalk.yellow.bold("\u{1F550} Recent Activity")); console.log(activityTable.toString()); console.log(); } const contextUsage = await estimateContextUsage(db); const usageBar = createProgressBar(contextUsage, 100); console.log(chalk.yellow.bold("\u{1F4BE} Context Usage")); console.log(`${usageBar} ${contextUsage}%`); console.log(); db.close(); if (argv.watch) { console.log( chalk.gray( `Auto-refreshing every ${argv.interval} seconds. Press Ctrl+C to exit.` ) ); } else { console.log(chalk.gray("Run with --watch to auto-refresh")); } }; try { await displayDashboard(); if (argv.watch) { const interval = setInterval(async () => { await displayDashboard(); }, argv.interval * 1e3); process.on("SIGINT", () => { clearInterval(interval); console.clear(); console.log(chalk.green("\u2705 Dashboard closed")); process.exit(0); }); } } catch (error) { console.error(chalk.red("\u274C Dashboard error:"), error.message); process.exit(1); } } }; function createProgressBar(value, max) { const percentage = Math.min(100, Math.round(value / max * 100)); const filled = Math.round(percentage / 5); const empty = 20 - filled; let color = chalk.green; if (percentage > 80) color = chalk.red; else if (percentage > 60) color = chalk.yellow; return color("\u2588".repeat(filled)) + chalk.gray("\u2591".repeat(empty)); } async function estimateContextUsage(db) { const result = db.prepare( ` SELECT COUNT(*) as frame_count, SUM(LENGTH(inputs)) as input_size, SUM(LENGTH(outputs)) as output_size FROM frames WHERE state = 'active' ` ).get(); const totalBytes = (result.input_size || 0) + (result.output_size || 0); const estimatedTokens = totalBytes / 4; const maxTokens = 128e3; return Math.round(estimatedTokens / maxTokens * 100); } export { dashboardCommand }; //# sourceMappingURL=dashboard.js.map