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

287 lines • 10.4 kB
import WebSocket from 'ws'; export class FlutterDebugEngine { ws; debugPort; vmServiceUrl; isConnected = false; async detectFlutterWeb(page) { return await page.evaluate(() => { // Check for Flutter-specific globals and elements const hasFlutterGlobals = !!window.flutterConfiguration || !!window.$flutterDriver; const hasFlutterElements = document.querySelector('flt-scene-host') !== null || document.querySelector('flt-glass-pane') !== null || document.querySelector('flutter-view') !== null; // Check for Flutter-specific scripts const scripts = Array.from(document.querySelectorAll('script')); const hasFlutterScripts = scripts.some(script => script.src.includes('flutter.js') || script.src.includes('flutter_web')); return hasFlutterGlobals || hasFlutterElements || hasFlutterScripts; }); } async findDebugPort(page) { // Try to find Flutter DevTools port from console logs const logs = await page.evaluate(() => { return window.__consoleHistory || []; }); // Look for Observatory/DevTools URL in logs const portMatch = logs.find((log) => log.includes('Observatory listening on') || log.includes('DevTools listening on')); if (portMatch) { const match = portMatch.match(/:(\d+)/); if (match) { return parseInt(match[1]); } } // Try common Flutter debug ports const commonPorts = [9100, 9101, 9102, 9200, 9201]; for (const port of commonPorts) { if (await this.checkPort(port)) { return port; } } return null; } async checkPort(port) { try { const response = await fetch(`http://localhost:${port}/ws`); return response.ok; } catch { return false; } } async connect(port) { if (this.isConnected) { return true; } try { this.debugPort = port; this.vmServiceUrl = `ws://localhost:${port}/ws`; return new Promise((resolve) => { this.ws = new WebSocket(this.vmServiceUrl); this.ws.on('open', () => { this.isConnected = true; // console.log('Connected to Flutter DevTools'); resolve(true); }); this.ws.on('error', (error) => { console.error('Flutter DevTools connection error:', error); resolve(false); }); this.ws.on('message', (data) => { this.handleMessage(data.toString()); }); }); } catch (error) { console.error('Failed to connect to Flutter DevTools:', error); return false; } } handleMessage(message) { try { const data = JSON.parse(message); // Handle VM service protocol messages if (data.method === 'streamNotify') { this.handleStreamNotification(data.params); } } catch (error) { console.error('Error parsing Flutter message:', error); } } handleStreamNotification(params) { // Handle different stream types switch (params.streamId) { case 'Extension': // Flutter extension events break; case 'Timeline': // Performance timeline events break; case 'Debug': // Debug events break; } } async getWidgetTree() { if (!this.isConnected || !this.ws) { return null; } // Send request for widget tree const request = { id: Date.now().toString(), method: 'ext.flutter.inspector.getWidgetTree', params: {} }; return new Promise((resolve) => { const timeout = setTimeout(() => resolve(null), 5000); const handler = (data) => { try { const response = JSON.parse(data.toString()); if (response.id === request.id) { clearTimeout(timeout); this.ws.off('message', handler); resolve(this.parseWidgetTree(response.result)); } } catch (error) { console.error('Error parsing widget tree response:', error); } }; this.ws.on('message', handler); this.ws.send(JSON.stringify(request)); }); } parseWidgetTree(data) { return { id: data.id || 'root', type: data.description?.split('(')[0] || 'Unknown', properties: data.properties || {}, children: (data.children || []).map((child) => this.parseWidgetTree(child)), renderObject: data.renderObject ? { type: data.renderObject.description, size: data.renderObject.size, position: data.renderObject.position } : undefined }; } async getPerformanceInfo() { if (!this.isConnected || !this.ws) { return null; } const request = { id: Date.now().toString(), method: 'ext.flutter.getVMTimeline', params: { timeOriginMicros: Date.now() * 1000 - 5000000, // Last 5 seconds timeExtentMicros: 5000000 } }; return new Promise((resolve) => { const timeout = setTimeout(() => resolve(null), 3000); const handler = (data) => { try { const response = JSON.parse(data.toString()); if (response.id === request.id) { clearTimeout(timeout); this.ws.off('message', handler); resolve(this.parsePerformanceData(response.result)); } } catch (error) { console.error('Error parsing performance data:', error); } }; this.ws.on('message', handler); this.ws.send(JSON.stringify(request)); }); } parsePerformanceData(data) { const events = data.traceEvents || []; // Calculate FPS from frame events const frameEvents = events.filter((e) => e.name === 'Frame'); const fps = frameEvents.length > 0 ? Math.round(1000000 / (frameEvents[frameEvents.length - 1].ts - frameEvents[0].ts) * frameEvents.length) : 0; // Calculate average frame times const buildTimes = events .filter((e) => e.name === 'Build') .map((e) => e.dur || 0); const avgBuildTime = buildTimes.length > 0 ? buildTimes.reduce((a, b) => a + b, 0) / buildTimes.length / 1000 : 0; return { fps, frameTime: fps > 0 ? 1000 / fps : 0, widgetBuildTime: avgBuildTime, rasterTime: 0, // Would need raster thread events eventCount: events.length }; } async inspectWidget(widgetId) { if (!this.isConnected || !this.ws) { return null; } const request = { id: Date.now().toString(), method: 'ext.flutter.inspector.getProperties', params: { objectId: widgetId, depth: 2 } }; return new Promise((resolve) => { const timeout = setTimeout(() => resolve(null), 3000); const handler = (data) => { try { const response = JSON.parse(data.toString()); if (response.id === request.id) { clearTimeout(timeout); this.ws.off('message', handler); resolve(response.result); } } catch (error) { console.error('Error parsing widget properties:', error); } }; this.ws.on('message', handler); this.ws.send(JSON.stringify(request)); }); } async highlightWidget(widgetId) { if (!this.isConnected || !this.ws) { return; } const request = { id: Date.now().toString(), method: 'ext.flutter.inspector.highlight', params: { objectId: widgetId } }; this.ws.send(JSON.stringify(request)); } async getMemoryUsage() { if (!this.isConnected || !this.ws) { return null; } const request = { id: Date.now().toString(), method: 'getMemoryUsage', params: {} }; return new Promise((resolve) => { const timeout = setTimeout(() => resolve(null), 3000); const handler = (data) => { try { const response = JSON.parse(data.toString()); if (response.id === request.id) { clearTimeout(timeout); this.ws.off('message', handler); resolve({ used: response.result.heapUsage, capacity: response.result.heapCapacity, external: response.result.externalUsage }); } } catch (error) { console.error('Error parsing memory data:', error); } }; this.ws.on('message', handler); this.ws.send(JSON.stringify(request)); }); } disconnect() { if (this.ws) { this.ws.close(); this.ws = undefined; this.isConnected = false; } } } //# sourceMappingURL=flutter-debug-engine.js.map