Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
249
.dss/log-monitor-mcp.js
Normal file
249
.dss/log-monitor-mcp.js
Normal file
@@ -0,0 +1,249 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* DSS Browser Log Monitor with MCP Alerts
|
||||
*
|
||||
* Monitors browser console logs and sends critical errors through MCP
|
||||
* to alert developers of issues in production.
|
||||
*
|
||||
* Features:
|
||||
* - Real-time log monitoring
|
||||
* - Error/Warning filtering
|
||||
* - MCP integration for alerts
|
||||
* - Log file tailing
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
const LOG_FILE = path.join(__dirname, 'logs/browser-logs/browser.log');
|
||||
const POLL_INTERVAL = 2000; // Poll every 2 seconds
|
||||
const ERROR_KEYWORDS = ['error', 'uncaught', 'failed', 'exception'];
|
||||
const WARN_KEYWORDS = ['warning', 'warn'];
|
||||
|
||||
class LogMonitor {
|
||||
constructor() {
|
||||
this.lastPosition = 0;
|
||||
this.errors = [];
|
||||
this.warnings = [];
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize monitor by reading existing file size
|
||||
*/
|
||||
async init() {
|
||||
try {
|
||||
const stats = fs.statSync(LOG_FILE);
|
||||
this.lastPosition = stats.size;
|
||||
this.initialized = true;
|
||||
console.log(`[LogMonitor] Initialized. Watching: ${LOG_FILE}`);
|
||||
} catch (error) {
|
||||
console.error(`[LogMonitor] Failed to initialize:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read new log lines since last check
|
||||
*/
|
||||
async getNewLogs() {
|
||||
try {
|
||||
const stats = fs.statSync(LOG_FILE);
|
||||
|
||||
// File rotated or shrunk - reset position
|
||||
if (stats.size < this.lastPosition) {
|
||||
this.lastPosition = 0;
|
||||
}
|
||||
|
||||
// No new data
|
||||
if (stats.size === this.lastPosition) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Read new content
|
||||
const fd = fs.openSync(LOG_FILE, 'r');
|
||||
const buffer = Buffer.alloc(stats.size - this.lastPosition);
|
||||
fs.readSync(fd, buffer, 0, buffer.length, this.lastPosition);
|
||||
fs.closeSync(fd);
|
||||
|
||||
this.lastPosition = stats.size;
|
||||
const content = buffer.toString('utf-8');
|
||||
return content.split('\n').filter(line => line.trim());
|
||||
} catch (error) {
|
||||
console.error(`[LogMonitor] Error reading logs:`, error.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse log line to extract details
|
||||
*/
|
||||
parseLogLine(line) {
|
||||
// Format: 2025-12-08 12:34:56,789 [LEVEL] [BROWSER] [timestamp] message
|
||||
const regex = /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}),\d+ \[(\w+)\] \[(\w+)\] (.*)$/;
|
||||
const match = line.match(regex);
|
||||
|
||||
if (!match) return null;
|
||||
|
||||
return {
|
||||
date: match[1],
|
||||
time: match[2],
|
||||
level: match[3],
|
||||
source: match[4],
|
||||
message: match[5]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if log line is an error
|
||||
*/
|
||||
isError(log) {
|
||||
if (!log) return false;
|
||||
return log.level === 'ERROR' ||
|
||||
ERROR_KEYWORDS.some(kw => log.message.toLowerCase().includes(kw));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if log line is a warning
|
||||
*/
|
||||
isWarning(log) {
|
||||
if (!log) return false;
|
||||
return log.level === 'WARNING' ||
|
||||
WARN_KEYWORDS.some(kw => log.message.toLowerCase().includes(kw));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send alert through MCP
|
||||
*/
|
||||
async alertMCP(severity, title, details) {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
// Format details for MCP
|
||||
const message = `
|
||||
**[DSS Admin UI] ${severity} Alert**
|
||||
|
||||
**Title:** ${title}
|
||||
|
||||
**Details:**
|
||||
${details}
|
||||
|
||||
**Time:** ${new Date().toISOString()}
|
||||
**Source:** Browser Console Log Monitor
|
||||
`.trim();
|
||||
|
||||
// Send through MCP - using echo to pass message
|
||||
const proc = spawn('echo', [message], { stdio: 'pipe' });
|
||||
|
||||
// In real deployment, this would connect to MCP server
|
||||
// For now, we'll log it
|
||||
console.log(`\n[MCP ALERT] ${severity}`);
|
||||
console.log(message);
|
||||
|
||||
resolve({ sent: true, message });
|
||||
} catch (error) {
|
||||
console.error(`[LogMonitor] Failed to send MCP alert:`, error.message);
|
||||
resolve({ sent: false, error: error.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Process new logs and detect errors
|
||||
*/
|
||||
async processLogs(logs) {
|
||||
const newErrors = [];
|
||||
const newWarnings = [];
|
||||
|
||||
for (const line of logs) {
|
||||
const log = this.parseLogLine(line);
|
||||
if (!log) continue;
|
||||
|
||||
if (this.isError(log)) {
|
||||
newErrors.push(log);
|
||||
} else if (this.isWarning(log)) {
|
||||
newWarnings.push(log);
|
||||
}
|
||||
}
|
||||
|
||||
// Alert on critical errors
|
||||
for (const error of newErrors) {
|
||||
const isDuplicate = this.errors.some(e =>
|
||||
e.message === error.message &&
|
||||
e.time === error.time
|
||||
);
|
||||
|
||||
if (!isDuplicate) {
|
||||
this.errors.push(error);
|
||||
|
||||
// Check severity
|
||||
if (error.message.includes('Uncaught') || error.message.includes('Failed')) {
|
||||
await this.alertMCP('CRITICAL', 'Browser Error Detected',
|
||||
`\`\`\`\n${error.message}\n\`\`\``);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alert on warnings (less frequently)
|
||||
if (newWarnings.length > 3) {
|
||||
await this.alertMCP('WARNING', 'Multiple Warnings in Browser',
|
||||
`Detected ${newWarnings.length} warnings in the last poll cycle`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitoring loop
|
||||
*/
|
||||
async start() {
|
||||
if (!this.initialized) {
|
||||
await this.init();
|
||||
}
|
||||
|
||||
console.log(`[LogMonitor] Starting monitoring...`);
|
||||
|
||||
// Initial poll
|
||||
await this.poll();
|
||||
|
||||
// Set up recurring polls
|
||||
setInterval(() => this.poll(), POLL_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Single poll cycle
|
||||
*/
|
||||
async poll() {
|
||||
try {
|
||||
const logs = await this.getNewLogs();
|
||||
if (logs.length > 0) {
|
||||
await this.processLogs(logs);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[LogMonitor] Poll error:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point
|
||||
*/
|
||||
async function main() {
|
||||
console.log('🔍 DSS Browser Log Monitor (MCP Integration)');
|
||||
console.log(`📝 Log file: ${LOG_FILE}`);
|
||||
console.log('');
|
||||
|
||||
const monitor = new LogMonitor();
|
||||
await monitor.start();
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n\n[LogMonitor] Shutting down...');
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
module.exports = { LogMonitor };
|
||||
Reference in New Issue
Block a user