UNPKG

browser-connect-mcp

Version:

MCP server for browser DevTools and backend debugging - analyze console logs, network requests, and backend logs with AI assistance

388 lines • 13.9 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const child_process_1 = require("child_process"); const fs = __importStar(require("fs")); const chalk_1 = __importDefault(require("chalk")); const program = new commander_1.Command(); program .name('browser-connect-backend') .description('Helper tool for backend debugging with browser-connect-mcp') .version('1.0.0'); // Start command - launches backend with proper debugging setup program .command('start [command]') .description('Start your backend with debugging enabled') .option('-p, --port <port>', 'Server port', '3000') .option('-d, --debug-port <port>', 'Debug port', '9229') .option('--no-inspect', 'Disable Node.js inspector') .option('-w, --watch', 'Watch for file changes') .option('-l, --log-file <path>', 'Log output to file', 'debug.log') .action(async (command, options) => { console.log(chalk_1.default.blue('šŸš€ Starting backend with debugging enabled...\n')); // Auto-detect command if not provided if (!command) { command = detectStartCommand(); if (!command) { console.error(chalk_1.default.red('āŒ Could not detect start command. Please specify one.')); console.log(chalk_1.default.gray('Example: browser-connect-backend start "node server.js"')); process.exit(1); } console.log(chalk_1.default.gray(`Auto-detected command: ${command}`)); } // Parse and enhance the command let [cmd, ...args] = command.split(' '); // Add debugging flags for Node.js if (cmd === 'node' && options.inspect !== false) { if (!args.some(arg => arg.includes('--inspect'))) { args.unshift(`--inspect=${options.debugPort}`); } } // Add watch mode if requested if (options.watch && cmd === 'node') { cmd = 'nodemon'; if (!args.some(arg => arg.includes('--inspect'))) { args.unshift(`--inspect=${options.debugPort}`); } } // Handle npm/yarn commands if ((cmd === 'npm' || cmd === 'yarn') && args[0] === 'start') { // Set NODE_OPTIONS for debugging process.env.NODE_OPTIONS = `--inspect=${options.debugPort}`; } console.log(chalk_1.default.green(`āœ… Starting: ${cmd} ${args.join(' ')}`)); console.log(chalk_1.default.gray(`šŸ“ Server Port: ${options.port}`)); console.log(chalk_1.default.gray(`šŸ› Debug Port: ${options.debugPort}`)); if (options.logFile) { console.log(chalk_1.default.gray(`šŸ“ Logging to: ${options.logFile}`)); } console.log(''); // Create log file write stream const logStream = options.logFile ? fs.createWriteStream(options.logFile, { flags: 'a' }) : null; // Start the process const proc = (0, child_process_1.spawn)(cmd, args, { stdio: ['inherit', 'pipe', 'pipe'], env: { ...process.env, PORT: options.port, NODE_ENV: process.env.NODE_ENV || 'development' } }); // Handle output proc.stdout.on('data', (data) => { const text = data.toString(); process.stdout.write(text); if (logStream) { const timestamp = new Date().toISOString(); logStream.write(`[${timestamp}] [STDOUT] ${text}`); } }); proc.stderr.on('data', (data) => { const text = data.toString(); process.stderr.write(chalk_1.default.yellow(text)); if (logStream) { const timestamp = new Date().toISOString(); logStream.write(`[${timestamp}] [STDERR] ${text}`); } }); proc.on('error', (error) => { console.error(chalk_1.default.red(`\nāŒ Failed to start process: ${error.message}`)); }); proc.on('exit', (code) => { if (logStream) logStream.end(); console.log(chalk_1.default.gray(`\nProcess exited with code ${code}`)); }); // Handle graceful shutdown process.on('SIGINT', () => { console.log(chalk_1.default.yellow('\n\nšŸ›‘ Shutting down...')); proc.kill('SIGTERM'); setTimeout(() => proc.kill('SIGKILL'), 5000); }); // Show tips console.log(chalk_1.default.cyan('\nšŸ’” Tips:')); console.log(chalk_1.default.gray('- Your backend is running with debugging enabled')); console.log(chalk_1.default.gray('- Use Claude to "debug my backend" to analyze logs')); console.log(chalk_1.default.gray('- Chrome DevTools can connect to the debug port')); console.log(chalk_1.default.gray('- Press Ctrl+C to stop\n')); }); // Doctor command - diagnose debugging setup program .command('doctor') .description('Check your debugging setup') .action(async () => { console.log(chalk_1.default.blue('🩺 Checking backend debugging setup...\n')); const checks = []; // Check for Node.js try { const nodeVersion = await runCommand('node', ['--version']); checks.push({ name: 'Node.js', status: 'ok', message: `Found ${nodeVersion.trim()}` }); } catch { checks.push({ name: 'Node.js', status: 'error', message: 'Not found' }); } // Check for package.json if (fs.existsSync('package.json')) { const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8')); checks.push({ name: 'package.json', status: 'ok', message: `Found (${pkg.name || 'unnamed project'})` }); // Check for start script if (pkg.scripts?.start) { checks.push({ name: 'Start script', status: 'ok', message: `"${pkg.scripts.start}"` }); } else { checks.push({ name: 'Start script', status: 'warning', message: 'Not found (will look for server.js/app.js/index.js)' }); } } else { checks.push({ name: 'package.json', status: 'warning', message: 'Not found' }); } // Check for common entry points const entryPoints = ['server.js', 'app.js', 'index.js', 'src/index.js']; const foundEntry = entryPoints.find(f => fs.existsSync(f)); if (foundEntry) { checks.push({ name: 'Entry point', status: 'ok', message: `Found ${foundEntry}` }); } else { checks.push({ name: 'Entry point', status: 'warning', message: 'No standard entry point found' }); } // Check for log files const logFiles = findLogFiles(); if (logFiles.length > 0) { checks.push({ name: 'Log files', status: 'ok', message: `Found ${logFiles.length} log file(s): ${logFiles.join(', ')}` }); } else { checks.push({ name: 'Log files', status: 'info', message: 'No log files found (logs may go to stdout)' }); } // Check for Docker try { await runCommand('docker', ['--version']); checks.push({ name: 'Docker', status: 'ok', message: 'Available' }); } catch { checks.push({ name: 'Docker', status: 'info', message: 'Not available (optional)' }); } // Display results console.log(chalk_1.default.bold('Check Results:\n')); for (const check of checks) { const icon = check.status === 'ok' ? 'āœ…' : check.status === 'error' ? 'āŒ' : check.status === 'warning' ? 'āš ļø' : 'ā„¹ļø'; const color = check.status === 'ok' ? 'green' : check.status === 'error' ? 'red' : check.status === 'warning' ? 'yellow' : 'gray'; console.log(`${icon} ${chalk_1.default[color](check.name)}: ${check.message}`); } // Recommendations console.log(chalk_1.default.bold('\nšŸ“‹ Recommendations:\n')); if (!foundEntry && !fs.existsSync('package.json')) { console.log(chalk_1.default.yellow('• Create a package.json with: npm init -y')); console.log(chalk_1.default.yellow('• Add a start script to package.json')); } if (logFiles.length === 0) { console.log(chalk_1.default.gray('• Consider logging to a file for better debugging')); console.log(chalk_1.default.gray(' Example: node server.js > app.log 2>&1')); } console.log(chalk_1.default.green('\n✨ To start debugging:')); console.log(chalk_1.default.gray(' browser-connect-backend start')); }); // Logs command - find and display logs program .command('logs') .description('Find and display log files') .option('-f, --follow', 'Follow log output') .option('-n, --lines <number>', 'Number of lines to show', '50') .action(async (options) => { const logFiles = findLogFiles(); if (logFiles.length === 0) { console.log(chalk_1.default.yellow('No log files found in current directory')); console.log(chalk_1.default.gray('\nTip: Start your app with logging:')); console.log(chalk_1.default.gray(' browser-connect-backend start --log-file debug.log')); return; } console.log(chalk_1.default.blue(`šŸ“„ Found ${logFiles.length} log file(s):\n`)); logFiles.forEach((file, i) => { console.log(chalk_1.default.gray(` ${i + 1}. ${file}`)); }); // Use the first log file const logFile = logFiles[0]; console.log(chalk_1.default.green(`\nšŸ“– Showing ${logFile}:\n`)); if (options.follow) { // Follow mode const tail = (0, child_process_1.spawn)('tail', ['-f', '-n', options.lines, logFile]); tail.stdout.pipe(process.stdout); tail.stderr.pipe(process.stderr); process.on('SIGINT', () => { tail.kill(); process.exit(0); }); } else { // Show last N lines try { const output = await runCommand('tail', ['-n', options.lines, logFile]); console.log(output); } catch (error) { console.error(chalk_1.default.red('Failed to read log file')); } } }); // Helper functions function detectStartCommand() { // Check package.json if (fs.existsSync('package.json')) { const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8')); if (pkg.scripts?.start) { return 'npm start'; } if (pkg.scripts?.dev) { return 'npm run dev'; } } // Check for common entry points const entryPoints = [ 'server.js', 'app.js', 'index.js', 'src/index.js', 'src/server.js', 'src/app.js' ]; for (const entry of entryPoints) { if (fs.existsSync(entry)) { return `node ${entry}`; } } return null; } function findLogFiles() { const logPatterns = [ '*.log', 'logs/*.log', 'var/log/*.log', '.logs/*.log' ]; const files = []; // Check current directory const entries = fs.readdirSync('.', { withFileTypes: true }); for (const entry of entries) { if (entry.isFile() && entry.name.endsWith('.log')) { files.push(entry.name); } } // Check logs directory if (fs.existsSync('logs')) { const logEntries = fs.readdirSync('logs', { withFileTypes: true }); for (const entry of logEntries) { if (entry.isFile() && entry.name.endsWith('.log')) { files.push(`logs/${entry.name}`); } } } return files; } function runCommand(command, args) { return new Promise((resolve, reject) => { const proc = (0, child_process_1.spawn)(command, args); let output = ''; proc.stdout.on('data', (data) => { output += data.toString(); }); proc.on('close', (code) => { if (code === 0) { resolve(output); } else { reject(new Error(`Command failed with code ${code}`)); } }); }); } program.parse(); //# sourceMappingURL=backend-helper.js.map