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

121 lines • 3.9 kB
/** * Hammerspoon Bridge - Interface to Hammerspoon automation tool * Compares performance with our native CGEvent Python bridge */ import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); export class HammerspoonBridge { static instance; constructor() { } static getInstance() { if (!HammerspoonBridge.instance) { HammerspoonBridge.instance = new HammerspoonBridge(); } return HammerspoonBridge.instance; } /** * Check if Hammerspoon is installed and running */ async isAvailable() { try { const { stdout } = await execAsync('hs -c "hs.ipc.cliStatus()"'); return stdout.includes('running'); } catch { return false; } } /** * Execute raw Lua code in Hammerspoon */ async executeLua(luaCode) { try { // Escape the Lua code for shell const escapedCode = luaCode.replace(/"/g, '\\"').replace(/\$/g, '\\$'); const { stdout, stderr } = await execAsync(`hs -c "${escapedCode}"`); if (stderr) { throw new Error(`Hammerspoon error: ${stderr}`); } return stdout.trim(); } catch (error) { throw new Error(`Failed to execute Hammerspoon command: ${error}`); } } /** * Type text using Hammerspoon's eventtap */ async typeText(text, delayMs = 0) { const escapedText = text.replace(/"/g, '\\"').replace(/\\/g, '\\\\'); if (delayMs > 0) { // Type with delay between characters const lua = ` local text = "${escapedText}" for i = 1, #text do hs.eventtap.keyStrokes(text:sub(i,i)) hs.timer.usleep(${delayMs * 1000}) end `; await this.executeLua(lua); } else { // Type all at once (fastest) await this.executeLua(`hs.eventtap.keyStrokes("${escapedText}")`); } } /** * Click at coordinates */ async click(x, y, button = 'left') { const hsButton = button === 'left' ? 'hs.eventtap.event.types.leftMouseDown' : 'hs.eventtap.event.types.rightMouseDown'; const hsButtonUp = button === 'left' ? 'hs.eventtap.event.types.leftMouseUp' : 'hs.eventtap.event.types.rightMouseUp'; const lua = ` local point = hs.geometry.point(${x}, ${y}) hs.eventtap.event.newMouseEvent(${hsButton}, point):post() hs.eventtap.event.newMouseEvent(${hsButtonUp}, point):post() `; await this.executeLua(lua); } /** * Move mouse to coordinates */ async moveMouse(x, y) { await this.executeLua(`hs.mouse.absolutePosition(hs.geometry.point(${x}, ${y}))`); } /** * Take a screenshot */ async screenshot(path) { const outputPath = path || `/tmp/hammerspoon-screenshot-${Date.now()}.png`; await this.executeLua(`hs.screen.mainScreen():snapshot():saveToFile("${outputPath}")`); return outputPath; } /** * Get all windows */ async getAllWindows() { const lua = ` local windows = {} for _, window in ipairs(hs.window.allWindows()) do table.insert(windows, { title = window:title(), app = window:application():name(), id = window:id(), frame = window:frame() }) end return hs.json.encode(windows) `; const result = await this.executeLua(lua); return JSON.parse(result); } /** * Press key combination */ async keyPress(modifiers, key) { const modList = modifiers.map(m => `"${m}"`).join(', '); await this.executeLua(`hs.eventtap.keyStroke({${modList}}, "${key}")`); } } //# sourceMappingURL=hammerspoon-bridge.js.map