UNPKG

ai-debug-local-mcp

Version:

๐ŸŽฏ ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

365 lines โ€ข 14 kB
/** * ๐ŸŽฌ REVOLUTIONARY: Universal Screen Recording Engine v4.0 * * Records ANY application with AI-powered analysis: * - Terminal/Vim sessions for workflow optimization * - Database tools for query analysis * - Design tools for pattern recognition * - Development environments for tutorial creation * - Multi-application workflows for process automation */ import { spawn } from 'child_process'; import { promises as fs } from 'fs'; import * as path from 'path'; import * as os from 'os'; import { BackgroundWindowAutomation } from './background-window-automation.js'; export class UniversalScreenRecorder { activeSessions = new Map(); backgroundWindowAutomation = new BackgroundWindowAutomation(); /** * ๐ŸŽฌ REVOLUTIONARY: Record any application with intelligent targeting */ async startRecording(options) { const sessionId = this.generateSessionId(); const fullOptions = this.buildRecordingOptions(options); console.log(`๐ŸŽฌ Starting universal recording session: ${sessionId}`); // Create recording directory await fs.mkdir(path.dirname(fullOptions.outputPath), { recursive: true }); // Get application/window information let windowInfo = null; if (fullOptions.applicationName) { windowInfo = await this.getApplicationWindowInfo(fullOptions.applicationName, fullOptions.windowTitle); if (!windowInfo) { throw new Error(`Application "${fullOptions.applicationName}" not found`); } console.log(`๐ŸŽฏ Target application: ${fullOptions.applicationName} (PID: ${windowInfo.processId})`); } // Build FFmpeg command const ffmpegCommand = await this.buildFFmpegCommand(fullOptions, windowInfo); // Start recording process const recordingProcess = spawn('ffmpeg', ffmpegCommand, { stdio: ['pipe', 'pipe', 'pipe'] }); // Create session const session = { id: sessionId, startTime: new Date(), outputPath: fullOptions.outputPath, options: fullOptions, process: recordingProcess, status: 'recording', metadata: { applicationInfo: windowInfo, windowBounds: windowInfo?.bounds } }; this.activeSessions.set(sessionId, session); // Handle process events this.setupRecordingProcessHandlers(session); // Schedule automatic stop setTimeout(() => { this.stopRecording(sessionId); }, fullOptions.duration * 1000); // Start realtime analysis if enabled if (fullOptions.enableRealtimeAnalysis) { this.startRealtimeAnalysis(session); } console.log(`โœ… Recording started: ${fullOptions.duration}s to ${fullOptions.outputPath}`); return session; } /** * ๐Ÿ›‘ Stop recording and prepare for analysis */ async stopRecording(sessionId) { const session = this.activeSessions.get(sessionId); if (!session || session.status !== 'recording') { return null; } console.log(`๐Ÿ›‘ Stopping recording session: ${sessionId}`); // Gracefully stop FFmpeg if (session.process) { session.process.stdin?.write('q'); // Send quit command to FFmpeg session.process.kill('SIGTERM'); } session.status = 'completed'; // Get file metadata try { const stats = await fs.stat(session.outputPath); session.metadata.fileSize = stats.size; console.log(`๐Ÿ“ Recording saved: ${(stats.size / 1024 / 1024).toFixed(1)} MB`); } catch (error) { console.error('Failed to get file stats:', error); } return session; } /** * ๐Ÿ” Get information about target application/window */ async getApplicationWindowInfo(applicationName, windowTitle) { try { // Use our existing background window automation const contexts = this.backgroundWindowAutomation.listContexts(); let existingContext = contexts.find(c => c.applicationName.toLowerCase().includes(applicationName.toLowerCase())); if (!existingContext) { // Create a temporary context to get window info const contextName = `temp-recording-${Date.now()}`; existingContext = await this.backgroundWindowAutomation.createContext(contextName, applicationName, windowTitle); } return { processId: existingContext.processId, applicationName: existingContext.applicationName, bounds: existingContext.windowBounds }; } catch (error) { console.warn('Failed to get application info:', error); return null; } } /** * ๐Ÿ› ๏ธ Build FFmpeg command for different recording scenarios */ async buildFFmpegCommand(options, windowInfo) { const command = ['-y']; // Overwrite output file // Platform-specific input configuration if (process.platform === 'darwin') { // macOS: Use AVFoundation command.push('-f', 'avfoundation'); if (options.region) { // Record specific screen region command.push('-i', '1'); // Screen capture command.push('-filter:v', `crop=${options.region.width}:${options.region.height}:${options.region.x}:${options.region.y}`); } else if (windowInfo?.bounds) { // Record specific window region command.push('-i', '1'); // Screen capture const bounds = windowInfo.bounds; command.push('-filter:v', `crop=${bounds.width}:${bounds.height}:${bounds.x}:${bounds.y}`); } else { // Record full screen command.push('-i', '1'); } // Audio capture (optional) if (options.captureAudio) { command.push('-f', 'avfoundation', '-i', ':0'); // Audio input } } else if (process.platform === 'linux') { // Linux: Use X11grab command.push('-f', 'x11grab'); if (options.region) { command.push('-s', `${options.region.width}x${options.region.height}`); command.push('-i', `:0.0+${options.region.x},${options.region.y}`); } else { command.push('-i', ':0.0'); // Full screen } } else if (process.platform === 'win32') { // Windows: Use gdigrab command.push('-f', 'gdigrab'); command.push('-i', 'desktop'); if (options.region) { command.push('-offset_x', options.region.x.toString()); command.push('-offset_y', options.region.y.toString()); command.push('-video_size', `${options.region.width}x${options.region.height}`); } } // Frame rate command.push('-r', options.frameRate.toString()); // Quality settings const qualitySettings = this.getQualitySettings(options.quality); command.push(...qualitySettings); // Duration command.push('-t', options.duration.toString()); // Output file command.push(options.outputPath); console.log(`๐ŸŽฌ FFmpeg command: ffmpeg ${command.join(' ')}`); return command; } /** * โš™๏ธ Get quality-specific encoding settings */ getQualitySettings(quality) { switch (quality) { case 'ultra': return ['-c:v', 'libx264', '-preset', 'slow', '-crf', '18']; case 'high': return ['-c:v', 'libx264', '-preset', 'medium', '-crf', '23']; case 'medium': return ['-c:v', 'libx264', '-preset', 'fast', '-crf', '28']; case 'low': return ['-c:v', 'libx264', '-preset', 'veryfast', '-crf', '35']; default: return ['-c:v', 'libx264', '-preset', 'medium', '-crf', '23']; } } /** * ๐ŸŽฏ Build complete recording options with defaults */ buildRecordingOptions(options) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const defaultPath = path.join(os.tmpdir(), `ai-debug-recording-${timestamp}.mp4`); return { duration: 30, outputPath: defaultPath, format: 'mp4', quality: 'medium', followMouse: true, captureAudio: false, frameRate: 30, enableRealtimeAnalysis: false, extractFramesInterval: 5, generateTranscript: false, ...options }; } /** * ๐Ÿ”ง Setup recording process event handlers */ setupRecordingProcessHandlers(session) { if (!session.process) return; session.process.stdout?.on('data', (data) => { const output = data.toString(); // Parse FFmpeg progress information if (output.includes('frame=')) { const frameMatch = output.match(/frame=\s*(\d+)/); if (frameMatch) { session.metadata.frameCount = parseInt(frameMatch[1]); } } }); session.process.stderr?.on('data', (data) => { const error = data.toString(); if (error.includes('error') || error.includes('Error')) { console.error(`FFmpeg error: ${error}`); session.status = 'failed'; } }); session.process.on('exit', (code) => { console.log(`๐Ÿ Recording process exited with code: ${code}`); if (code === 0 && session.status === 'recording') { session.status = 'completed'; } else if (session.status === 'recording') { session.status = 'failed'; } }); } /** * ๐Ÿค– Start realtime AI analysis during recording */ async startRealtimeAnalysis(session) { console.log(`๐Ÿค– Starting realtime analysis for session: ${session.id}`); // Extract frames periodically during recording const extractInterval = setInterval(async () => { if (session.status !== 'recording') { clearInterval(extractInterval); return; } try { await this.extractCurrentFrame(session); } catch (error) { console.error('Frame extraction failed:', error); } }, session.options.extractFramesInterval * 1000); } /** * ๐Ÿ“ธ Extract current frame for realtime analysis */ async extractCurrentFrame(session) { const frameDir = path.join(path.dirname(session.outputPath), 'frames'); await fs.mkdir(frameDir, { recursive: true }); const frameNumber = Math.floor((Date.now() - session.startTime.getTime()) / 1000); const framePath = path.join(frameDir, `frame_${frameNumber.toString().padStart(3, '0')}.png`); // Use FFmpeg to extract current frame const extractCommand = [ '-ss', frameNumber.toString(), '-i', session.outputPath, '-frames:v', '1', '-y', framePath ]; const extractProcess = spawn('ffmpeg', extractCommand); return new Promise((resolve, reject) => { extractProcess.on('exit', (code) => { if (code === 0) { console.log(`๐Ÿ“ธ Frame extracted: ${framePath}`); resolve(); } else { reject(new Error(`Frame extraction failed with code: ${code}`)); } }); }); } /** * ๐Ÿ” Get active recording sessions */ getActiveSessions() { return Array.from(this.activeSessions.values()); } /** * ๐Ÿ“Š Get session by ID */ getSession(sessionId) { return this.activeSessions.get(sessionId); } /** * ๐Ÿงน Clean up completed sessions */ cleanupSession(sessionId) { const session = this.activeSessions.get(sessionId); if (session && session.status !== 'recording') { this.activeSessions.delete(sessionId); console.log(`๐Ÿงน Cleaned up session: ${sessionId}`); } } /** * ๐Ÿ†” Generate unique session ID */ generateSessionId() { return `rec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } } /** * ๐ŸŽฌ REVOLUTIONARY WORKFLOWS: Predefined recording profiles */ export class RecordingProfiles { static VIM_WORKFLOW = { applicationName: 'Terminal', duration: 60, quality: 'high', enableRealtimeAnalysis: true, extractFramesInterval: 2, // More frequent for terminal frameRate: 15 // Lower for text-based workflows }; static DATABASE_ANALYSIS = { duration: 120, quality: 'medium', enableRealtimeAnalysis: false, extractFramesInterval: 10, // Less frequent for queries frameRate: 10 }; static DESIGN_WORKFLOW = { duration: 300, quality: 'ultra', followMouse: true, frameRate: 30, enableRealtimeAnalysis: true, extractFramesInterval: 5 }; static MULTI_APP_DEVELOPMENT = { duration: 600, // 10 minutes quality: 'high', captureAudio: true, frameRate: 24, enableRealtimeAnalysis: true, extractFramesInterval: 15 }; } //# sourceMappingURL=universal-screen-recorder.js.map