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
JavaScript
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