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
JavaScript
/**
* ๐ฌ 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