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

274 lines • 8.89 kB
/** * Persistent Python Bridge - Eliminates subprocess overhead * Maintains a long-lived Python process for automation */ import { spawn } from 'child_process'; import { EventEmitter } from 'events'; import * as path from 'path'; export class PersistentPythonBridge extends EventEmitter { process = null; pythonPath; bridgePath; requestId = 0; pendingRequests = new Map(); responseBuffer = ''; isReady = false; startupPromise = null; constructor(pythonPath = 'python3') { super(); this.pythonPath = pythonPath; this.bridgePath = path.join(process.cwd(), 'native-cgevent-bridge-persistent.py'); } /** * Start the persistent Python process */ async start() { if (this.startupPromise) { return this.startupPromise; } this.startupPromise = this._start(); return this.startupPromise; } async _start() { return new Promise((resolve, reject) => { console.log('Starting persistent Python automation bridge...'); this.process = spawn(this.pythonPath, [this.bridgePath], { stdio: ['pipe', 'pipe', 'pipe'] }); this.process.stdout?.on('data', (data) => { this.handleOutput(data.toString()); }); this.process.stderr?.on('data', (data) => { const message = data.toString(); console.log('Python bridge:', message.trim()); if (message.includes('started')) { this.isReady = true; resolve(); } }); this.process.on('error', (error) => { console.error('Python bridge error:', error); this.handleProcessError(error); reject(error); }); this.process.on('exit', (code, signal) => { console.log(`Python bridge exited: code=${code}, signal=${signal}`); this.handleProcessExit(code, signal); }); // Test connection setTimeout(async () => { try { await this.ping(); console.log('Python bridge connection confirmed'); } catch (e) { console.error('Failed to ping Python bridge:', e); reject(e); } }, 1000); }); } /** * Handle output from Python process */ handleOutput(data) { this.responseBuffer += data; // Process complete lines const lines = this.responseBuffer.split('\n'); this.responseBuffer = lines.pop() || ''; for (const line of lines) { if (line.trim()) { try { const response = JSON.parse(line); this.handleResponse(response); } catch (e) { console.error('Failed to parse response:', line, e); } } } } /** * Handle a response from Python */ handleResponse(response) { const { id, success, result, error } = response; const pending = this.pendingRequests.get(id); if (pending) { clearTimeout(pending.timeout); this.pendingRequests.delete(id); if (success) { pending.resolve(result); } else { pending.reject(new Error(error || 'Unknown error')); } } } /** * Send a request to Python and wait for response */ async sendRequest(action, params = {}, timeoutMs = 5000) { if (!this.isReady) { await this.start(); } return new Promise((resolve, reject) => { const id = this.requestId++; const timeout = setTimeout(() => { this.pendingRequests.delete(id); reject(new Error(`Request timeout: ${action}`)); }, timeoutMs); this.pendingRequests.set(id, { resolve, reject, timeout }); const request = JSON.stringify({ id, action, params }) + '\n'; this.process?.stdin?.write(request); }); } /** * Handle process errors */ handleProcessError(error) { // Reject all pending requests for (const [id, pending] of this.pendingRequests) { clearTimeout(pending.timeout); pending.reject(error); } this.pendingRequests.clear(); this.isReady = false; } /** * Handle process exit */ handleProcessExit(code, signal) { const error = new Error(`Process exited: code=${code}, signal=${signal}`); this.handleProcessError(error); // Attempt restart if not intentional if (code !== 0 && this.startupPromise) { console.log('Attempting to restart Python bridge...'); this.startupPromise = null; setTimeout(() => this.start(), 1000); } } /** * Test connection */ async ping() { const result = await this.sendRequest('ping'); return result?.pong === true; } /** * Get all windows with metadata */ async getWindows(includeMinimized = true) { return this.sendRequest('get_windows', { include_minimized: includeMinimized }); } /** * Find a specific window */ async findWindow(options) { return this.sendRequest('find_window', { title: options.title, owner: options.owner, window_id: options.windowId }); } /** * Click at coordinates */ async click(x, y, button = 'left', clickCount = 1) { await this.sendRequest('click', { x, y, button, click_count: clickCount }); } /** * Move mouse to coordinates */ async moveMouse(x, y) { await this.sendRequest('move_mouse', { x, y }); } /** * Drag from start to end */ async drag(startX, startY, endX, endY) { await this.sendRequest('drag', { start_x: startX, start_y: startY, end_x: endX, end_y: endY }); } /** * Scroll at position */ async scroll(x, y, deltaX = 0, deltaY = 0) { await this.sendRequest('scroll', { x, y, delta_x: deltaX, delta_y: deltaY }); } /** * Type text */ async typeText(text) { await this.sendRequest('type_text', { text }, 10000); // Longer timeout for typing } /** * Get screen size */ async getScreenSize() { return this.sendRequest('get_screen_size'); } /** * Get active application */ async getActiveApp() { return this.sendRequest('get_active_app'); } /** * Shutdown the bridge */ async shutdown() { if (this.process) { await this.sendRequest('quit').catch(() => { }); this.process.kill(); this.process = null; this.isReady = false; this.startupPromise = null; } } } // Example usage and performance comparison if (require.main === module) { (async () => { const bridge = new PersistentPythonBridge(); console.log('Testing persistent Python bridge...\n'); // Start bridge const startTime = Date.now(); await bridge.start(); console.log(`Startup time: ${Date.now() - startTime}ms\n`); // Test window discovery const windowStart = Date.now(); const windows = await bridge.getWindows(); console.log(`Window discovery: ${Date.now() - windowStart}ms`); console.log(`Found ${windows.length} windows\n`); // Show some windows windows.slice(0, 5).forEach(w => { console.log(`- ${w.owner}: "${w.title}" (ID: ${w.id})`); }); // Test clicking speed (10 clicks) console.log('\nTesting click speed...'); const clickStart = Date.now(); for (let i = 0; i < 10; i++) { await bridge.click(500 + i * 20, 400); } const clickTime = Date.now() - clickStart; console.log(`10 clicks: ${clickTime}ms (${Math.round(clickTime / 10)}ms per click)`); // Compare to subprocess approach console.log('\nComparison:'); console.log('- Old subprocess: ~75ms per click'); console.log(`- New persistent: ~${Math.round(clickTime / 10)}ms per click`); console.log(`- Speedup: ${Math.round(75 / (clickTime / 10))}x faster!\n`); // Shutdown await bridge.shutdown(); console.log('Bridge shutdown complete'); })().catch(console.error); } //# sourceMappingURL=persistent-python-bridge.js.map