UNPKG

zai-mcp-server

Version:

🚀 REVOLUTIONARY AI-to-AI Collaboration Platform v6.1! NEW: Advanced Debugging Tools with Screenshot Analysis, Console Error Parsing, Automated Fix Generation, 5 Specialized Debugging Agents, Visual UI Analysis, JavaScript Error Intelligence, CSS/HTML Fix

776 lines (668 loc) 27.7 kB
/** * Android Debugging Manager - Comprehensive Android app debugging system * Supports wireless debugging, screenshot analysis, logcat monitoring, and automated fixes */ import { spawn, exec } from 'child_process'; import { promisify } from 'util'; import fs from 'fs/promises'; import path from 'path'; const execAsync = promisify(exec); export class AndroidDebuggingManager { constructor(multiProviderAI, debuggingOrchestrator) { this.multiProviderAI = multiProviderAI; this.debuggingOrchestrator = debuggingOrchestrator; this.connectedDevices = new Map(); this.activeDebuggingSessions = new Map(); this.logcatProcesses = new Map(); this.screenshotCache = new Map(); this.batteryAnalyzers = new Map(); this.performanceMonitors = new Map(); this.multiDevicePool = new Set(); this.deviceFarmConnections = new Map(); // Enhanced debugging capabilities this.supportedLanguages = ['java', 'kotlin', 'xml', 'javascript', 'dart']; this.debuggingModes = ['wireless', 'usb', 'emulator', 'cloud']; this.performanceMetrics = ['cpu', 'memory', 'battery', 'network', 'gpu']; // Android debugging configuration this.adbPath = this.findAdbPath(); this.screenshotDir = './android-screenshots'; this.logcatDir = './android-logs'; this.performanceDir = './android-performance'; this.batteryDir = './android-battery'; // Initialize directories this.initializeDirectories(); console.log('📱 Enhanced Android Debugging Manager initialized with multi-device and cloud support'); } /** * Find ADB path on the system */ findAdbPath() { const possiblePaths = [ '/usr/local/bin/adb', '/usr/bin/adb', process.env.ANDROID_HOME ? `${process.env.ANDROID_HOME}/platform-tools/adb` : null, process.env.ANDROID_SDK_ROOT ? `${process.env.ANDROID_SDK_ROOT}/platform-tools/adb` : null, 'adb' // Assume it's in PATH ].filter(Boolean); return possiblePaths[0] || 'adb'; } /** * Initialize required directories */ async initializeDirectories() { try { await fs.mkdir(this.screenshotDir, { recursive: true }); await fs.mkdir(this.logcatDir, { recursive: true }); await fs.mkdir(this.performanceDir, { recursive: true }); await fs.mkdir(this.batteryDir, { recursive: true }); console.log('📁 Enhanced Android debugging directories initialized'); } catch (error) { console.error('Failed to initialize directories:', error.message); } } /** * Connect to Android device via wireless debugging */ async connectWirelessDevice(deviceIp, port = 5555) { try { console.log(`📱 Connecting to Android device at ${deviceIp}:${port}...`); // Enable wireless debugging const connectResult = await execAsync(`${this.adbPath} connect ${deviceIp}:${port}`); if (connectResult.stdout.includes('connected')) { const deviceId = `${deviceIp}:${port}`; // Get device info const deviceInfo = await this.getDeviceInfo(deviceId); this.connectedDevices.set(deviceId, { id: deviceId, ip: deviceIp, port, connected: true, info: deviceInfo, connectedAt: Date.now() }); console.log(`✅ Connected to Android device: ${deviceInfo.model} (${deviceInfo.version})`); return { success: true, deviceId, deviceInfo, message: `Connected to ${deviceInfo.model}` }; } else { throw new Error('Failed to connect to device'); } } catch (error) { console.error(`❌ Failed to connect to ${deviceIp}:${port}:`, error.message); return { success: false, error: error.message, suggestions: [ 'Ensure wireless debugging is enabled on the device', 'Check that the device is on the same network', 'Verify the IP address and port are correct', 'Try pairing the device first if using Android 11+' ] }; } } /** * Pair with Android device (Android 11+) */ async pairDevice(deviceIp, pairingPort, pairingCode) { try { console.log(`🔗 Pairing with Android device at ${deviceIp}:${pairingPort}...`); const pairResult = await execAsync(`${this.adbPath} pair ${deviceIp}:${pairingPort} ${pairingCode}`); if (pairResult.stdout.includes('Successfully paired')) { console.log('✅ Device paired successfully'); return { success: true, message: 'Device paired successfully. You can now connect.' }; } else { throw new Error('Pairing failed'); } } catch (error) { console.error('❌ Device pairing failed:', error.message); return { success: false, error: error.message, suggestions: [ 'Verify the pairing code is correct', 'Check the pairing port number', 'Ensure the pairing dialog is still open on the device' ] }; } } /** * Get device information */ async getDeviceInfo(deviceId) { try { const [model, version, sdk, manufacturer] = await Promise.all([ execAsync(`${this.adbPath} -s ${deviceId} shell getprop ro.product.model`), execAsync(`${this.adbPath} -s ${deviceId} shell getprop ro.build.version.release`), execAsync(`${this.adbPath} -s ${deviceId} shell getprop ro.build.version.sdk`), execAsync(`${this.adbPath} -s ${deviceId} shell getprop ro.product.manufacturer`) ]); return { model: model.stdout.trim(), version: version.stdout.trim(), sdk: parseInt(sdk.stdout.trim()), manufacturer: manufacturer.stdout.trim() }; } catch (error) { console.error('Failed to get device info:', error.message); return { model: 'Unknown', version: 'Unknown', sdk: 0, manufacturer: 'Unknown' }; } } /** * List connected Android devices */ async listConnectedDevices() { try { const result = await execAsync(`${this.adbPath} devices`); const lines = result.stdout.split('\n').slice(1); // Skip header const devices = []; for (const line of lines) { if (line.trim() && line.includes('device')) { const deviceId = line.split('\t')[0]; const deviceInfo = await this.getDeviceInfo(deviceId); devices.push({ id: deviceId, status: 'connected', info: deviceInfo }); } } return devices; } catch (error) { console.error('Failed to list devices:', error.message); return []; } } /** * Take screenshot of Android device */ async takeScreenshot(deviceId, options = {}) { try { console.log(`📸 Taking screenshot of device ${deviceId}...`); const timestamp = Date.now(); const filename = `screenshot_${deviceId.replace(':', '_')}_${timestamp}.png`; const localPath = path.join(this.screenshotDir, filename); const devicePath = '/sdcard/screenshot_temp.png'; // Take screenshot on device await execAsync(`${this.adbPath} -s ${deviceId} shell screencap -p ${devicePath}`); // Pull screenshot to local machine await execAsync(`${this.adbPath} -s ${deviceId} pull ${devicePath} ${localPath}`); // Clean up device await execAsync(`${this.adbPath} -s ${deviceId} shell rm ${devicePath}`); // Read screenshot data const screenshotData = await fs.readFile(localPath); const base64Data = `data:image/png;base64,${screenshotData.toString('base64')}`; // Cache screenshot this.screenshotCache.set(`${deviceId}_${timestamp}`, { path: localPath, data: base64Data, timestamp, deviceId }); console.log(`✅ Screenshot saved: ${filename}`); return { success: true, filename, path: localPath, data: base64Data, timestamp, deviceId }; } catch (error) { console.error(`❌ Failed to take screenshot:`, error.message); return { success: false, error: error.message }; } } /** * Start logcat monitoring */ async startLogcatMonitoring(deviceId, options = {}) { try { console.log(`📋 Starting logcat monitoring for device ${deviceId}...`); const logFile = path.join(this.logcatDir, `logcat_${deviceId.replace(':', '_')}_${Date.now()}.log`); const logStream = await fs.open(logFile, 'w'); // Clear existing logs if requested if (options.clearLogs) { await execAsync(`${this.adbPath} -s ${deviceId} logcat -c`); } // Start logcat process const logcatArgs = ['-s', deviceId, 'logcat']; // Add filters if specified if (options.tags) { logcatArgs.push(...options.tags.map(tag => `${tag}:V`)); } if (options.priority) { logcatArgs.push(`*:${options.priority}`); } const logcatProcess = spawn(this.adbPath, logcatArgs); // Store process reference this.logcatProcesses.set(deviceId, { process: logcatProcess, logFile, startTime: Date.now(), options }); // Handle logcat output logcatProcess.stdout.on('data', async (data) => { const logEntry = data.toString(); await logStream.write(logEntry); // Emit real-time log events if needed this.emit('logcat', { deviceId, timestamp: Date.now(), data: logEntry }); }); logcatProcess.stderr.on('data', (data) => { console.error(`Logcat error: ${data}`); }); logcatProcess.on('close', (code) => { console.log(`📋 Logcat monitoring stopped for ${deviceId} (code: ${code})`); logStream.close(); this.logcatProcesses.delete(deviceId); }); console.log(`✅ Logcat monitoring started for ${deviceId}`); return { success: true, deviceId, logFile, message: 'Logcat monitoring started' }; } catch (error) { console.error(`❌ Failed to start logcat monitoring:`, error.message); return { success: false, error: error.message }; } } /** * Stop logcat monitoring */ async stopLogcatMonitoring(deviceId) { try { const logcatInfo = this.logcatProcesses.get(deviceId); if (logcatInfo) { logcatInfo.process.kill(); this.logcatProcesses.delete(deviceId); console.log(`✅ Logcat monitoring stopped for ${deviceId}`); return { success: true, message: 'Logcat monitoring stopped' }; } else { return { success: false, error: 'No active logcat monitoring found for this device' }; } } catch (error) { console.error(`❌ Failed to stop logcat monitoring:`, error.message); return { success: false, error: error.message }; } } /** * Analyze Android logcat for errors */ async analyzeLogcat(deviceId, options = {}) { try { console.log(`🔍 Analyzing logcat for device ${deviceId}...`); // Get recent logcat entries const logcatResult = await execAsync( `${this.adbPath} -s ${deviceId} logcat -d -v time ${options.filter || '*:W'}` ); const logLines = logcatResult.stdout.split('\n').filter(line => line.trim()); // Parse and categorize log entries const errors = []; const warnings = []; const crashes = []; for (const line of logLines) { const logEntry = this.parseLogcatLine(line); if (logEntry) { if (logEntry.level === 'E') { errors.push(logEntry); } else if (logEntry.level === 'W') { warnings.push(logEntry); } // Detect crashes if (line.includes('FATAL EXCEPTION') || line.includes('AndroidRuntime')) { crashes.push(logEntry); } } } // Generate analysis summary const analysis = { deviceId, timestamp: Date.now(), totalLines: logLines.length, errors: errors.length, warnings: warnings.length, crashes: crashes.length, entries: { errors: errors.slice(0, 10), // Limit to recent entries warnings: warnings.slice(0, 10), crashes }, patterns: this.identifyLogPatterns(logLines), recommendations: this.generateLogRecommendations(errors, warnings, crashes) }; console.log(`✅ Logcat analysis completed: ${errors.length} errors, ${warnings.length} warnings`); return { success: true, analysis }; } catch (error) { console.error(`❌ Failed to analyze logcat:`, error.message); return { success: false, error: error.message }; } } /** * Parse individual logcat line */ parseLogcatLine(line) { // Android logcat format: MM-DD HH:MM:SS.mmm PID TID LEVEL TAG: MESSAGE const logcatRegex = /^(\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+(\d+)\s+(\d+)\s+([VDIWEF])\s+([^:]+):\s*(.*)$/; const match = line.match(logcatRegex); if (match) { return { timestamp: match[1], pid: parseInt(match[2]), tid: parseInt(match[3]), level: match[4], tag: match[5].trim(), message: match[6].trim(), raw: line }; } return null; } /** * Identify common patterns in logs */ identifyLogPatterns(logLines) { const patterns = { networkErrors: logLines.filter(line => line.includes('NetworkException') || line.includes('ConnectException') || line.includes('SocketTimeoutException') ).length, memoryIssues: logLines.filter(line => line.includes('OutOfMemoryError') || line.includes('GC_') || line.includes('lowmemorykiller') ).length, uiErrors: logLines.filter(line => line.includes('ViewRootImpl') || line.includes('WindowManager') || line.includes('IllegalStateException') ).length, permissionErrors: logLines.filter(line => line.includes('SecurityException') || line.includes('Permission denied') ).length }; return patterns; } /** * Generate recommendations based on log analysis */ generateLogRecommendations(errors, warnings, crashes) { const recommendations = []; if (crashes.length > 0) { recommendations.push('Critical: Application crashes detected. Review stack traces immediately.'); } if (errors.length > 10) { recommendations.push('High error count detected. Consider implementing better error handling.'); } if (warnings.length > 20) { recommendations.push('Many warnings found. Address warnings to prevent future errors.'); } // Add specific recommendations based on error types const errorMessages = errors.map(e => e.message).join(' '); if (errorMessages.includes('NetworkException')) { recommendations.push('Network connectivity issues detected. Implement retry logic and offline handling.'); } if (errorMessages.includes('OutOfMemoryError')) { recommendations.push('Memory issues detected. Optimize memory usage and implement proper cleanup.'); } return recommendations; } /** * Get enhanced debugging session status */ getDebuggingStatus() { return { connectedDevices: Array.from(this.connectedDevices.values()), activeLogcatSessions: Array.from(this.logcatProcesses.keys()), activeSessions: this.activeDebuggingSessions.size, screenshotsCached: this.screenshotCache.size, multiDevicePool: Array.from(this.multiDevicePool), batteryAnalyzers: Array.from(this.batteryAnalyzers.keys()), performanceMonitors: Array.from(this.performanceMonitors.keys()), supportedLanguages: this.supportedLanguages, debuggingModes: this.debuggingModes, performanceMetrics: this.performanceMetrics, capabilities: { multiDevice: true, batteryAnalysis: true, performanceMonitoring: true, wirelessDebugging: true, cloudDebugging: false, // Future enhancement crossPlatform: false // Future enhancement } }; } /** * Multi-Device Debugging: Connect to multiple devices simultaneously */ async connectMultipleDevices(deviceConfigs) { console.log(`📱 Connecting to ${deviceConfigs.length} devices simultaneously...`); const connectionResults = []; const connectionPromises = deviceConfigs.map(async (config) => { try { const result = await this.connectWirelessDevice(config.ip, config.port); if (result.success) { this.multiDevicePool.add(result.deviceId); } return { ...result, config }; } catch (error) { return { success: false, error: error.message, config }; } }); const results = await Promise.allSettled(connectionPromises); for (const result of results) { if (result.status === 'fulfilled') { connectionResults.push(result.value); } else { connectionResults.push({ success: false, error: result.reason.message, config: null }); } } const successCount = connectionResults.filter(r => r.success).length; console.log(`✅ Connected to ${successCount}/${deviceConfigs.length} devices`); return { success: successCount > 0, totalDevices: deviceConfigs.length, connectedDevices: successCount, results: connectionResults, multiDevicePool: Array.from(this.multiDevicePool) }; } /** * Multi-Device Debugging: Take screenshots from all connected devices */ async takeMultiDeviceScreenshots(options = {}) { console.log(`📸 Taking screenshots from ${this.multiDevicePool.size} devices...`); const screenshotPromises = Array.from(this.multiDevicePool).map(async (deviceId) => { try { const result = await this.takeScreenshot(deviceId, options); return { deviceId, ...result }; } catch (error) { return { deviceId, success: false, error: error.message }; } }); const screenshots = await Promise.allSettled(screenshotPromises); const successfulScreenshots = screenshots .filter(s => s.status === 'fulfilled' && s.value.success) .map(s => s.value); console.log(`✅ Captured ${successfulScreenshots.length} screenshots from multi-device pool`); return { success: successfulScreenshots.length > 0, totalDevices: this.multiDevicePool.size, successfulScreenshots: successfulScreenshots.length, screenshots: successfulScreenshots }; } /** * Battery Usage Analysis: Monitor battery consumption */ async startBatteryAnalysis(deviceId, duration = 300000) { // 5 minutes default try { console.log(`🔋 Starting battery analysis for device ${deviceId}...`); const analysisId = `battery_${deviceId}_${Date.now()}`; const batteryFile = path.join(this.batteryDir, `${analysisId}.json`); // Get initial battery stats const initialStats = await this.getBatteryStats(deviceId); const batteryAnalyzer = { id: analysisId, deviceId, startTime: Date.now(), duration, initialStats, samples: [], isActive: true }; this.batteryAnalyzers.set(analysisId, batteryAnalyzer); // Start periodic battery monitoring const monitoringInterval = setInterval(async () => { try { const currentStats = await this.getBatteryStats(deviceId); batteryAnalyzer.samples.push({ timestamp: Date.now(), stats: currentStats }); // Save to file periodically await fs.writeFile(batteryFile, JSON.stringify(batteryAnalyzer, null, 2)); } catch (error) { console.error('Battery monitoring error:', error.message); } }, 10000); // Sample every 10 seconds // Stop monitoring after duration setTimeout(async () => { clearInterval(monitoringInterval); batteryAnalyzer.isActive = false; const finalAnalysis = await this.generateBatteryAnalysis(batteryAnalyzer); batteryAnalyzer.analysis = finalAnalysis; await fs.writeFile(batteryFile, JSON.stringify(batteryAnalyzer, null, 2)); console.log(`🔋 Battery analysis completed for ${deviceId}`); }, duration); return { success: true, analysisId, deviceId, duration, message: 'Battery analysis started' }; } catch (error) { console.error(`❌ Failed to start battery analysis:`, error.message); return { success: false, error: error.message }; } } /** * Get current battery statistics */ async getBatteryStats(deviceId) { try { const [level, temp, voltage, current] = await Promise.all([ execAsync(`${this.adbPath} -s ${deviceId} shell dumpsys battery | grep level`), execAsync(`${this.adbPath} -s ${deviceId} shell dumpsys battery | grep temperature`), execAsync(`${this.adbPath} -s ${deviceId} shell dumpsys battery | grep voltage`), execAsync(`${this.adbPath} -s ${deviceId} shell dumpsys battery | grep current`) ]); return { level: parseInt(level.stdout.match(/\d+/)?.[0] || '0'), temperature: parseInt(temp.stdout.match(/\d+/)?.[0] || '0') / 10, // Convert to Celsius voltage: parseInt(voltage.stdout.match(/\d+/)?.[0] || '0'), current: parseInt(current.stdout.match(/-?\d+/)?.[0] || '0'), timestamp: Date.now() }; } catch (error) { console.error('Failed to get battery stats:', error.message); return { level: 0, temperature: 0, voltage: 0, current: 0, timestamp: Date.now() }; } } /** * Disconnect from device */ async disconnectDevice(deviceId) { try { // Stop logcat monitoring if active await this.stopLogcatMonitoring(deviceId); // Remove from multi-device pool this.multiDevicePool.delete(deviceId); // Stop battery analysis if active for (const [analysisId, analyzer] of this.batteryAnalyzers) { if (analyzer.deviceId === deviceId) { analyzer.isActive = false; this.batteryAnalyzers.delete(analysisId); } } // Disconnect device await execAsync(`${this.adbPath} disconnect ${deviceId}`); // Remove from connected devices this.connectedDevices.delete(deviceId); console.log(`✅ Disconnected from device ${deviceId}`); return { success: true, message: `Disconnected from ${deviceId}` }; } catch (error) { console.error(`❌ Failed to disconnect from ${deviceId}:`, error.message); return { success: false, error: error.message }; } } }