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