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