MAESTRO: fix(cursor): use bun runtime and fix hooks directory detection

Cherry-picked source changes from PR #721. Fixes two Cursor standalone
setup bugs:

1. findCursorHooksDir() now checks for hooks.json (unified CLI mode)
   in addition to legacy common.sh/common.ps1 scripts
2. installCursorHooks() now uses bun instead of node for hook commands
   since worker-service.cjs depends on bun:sqlite
3. Added findBunPath() to detect bun executable across platforms

Build artifacts skipped (pre-existing dompurify viewer dep issue).
Source-only cherry-pick, TypeScript compilation clean for modified file.

Co-Authored-By: polux0 <aleksaprosperitylabs@gmail.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2026-02-06 03:09:26 -05:00
parent 2f935a56b9
commit 8030c44af4
@@ -128,7 +128,7 @@ export async function updateCursorContextForProject(projectName: string, port: n
/** /**
* Find cursor-hooks directory * Find cursor-hooks directory
* Searches in order: marketplace install, source repo * Searches in order: marketplace install, source repo
* Checks for both bash (common.sh) and PowerShell (common.ps1) scripts * Checks for hooks.json (unified CLI mode) or legacy shell scripts
*/ */
export function findCursorHooksDir(): string | null { export function findCursorHooksDir(): string | null {
const possiblePaths = [ const possiblePaths = [
@@ -141,8 +141,10 @@ export function findCursorHooksDir(): string | null {
]; ];
for (const p of possiblePaths) { for (const p of possiblePaths) {
// Check for either bash or PowerShell common script // Check for hooks.json (unified CLI mode) or legacy shell scripts
if (existsSync(path.join(p, 'common.sh')) || existsSync(path.join(p, 'common.ps1'))) { if (existsSync(path.join(p, 'hooks.json')) ||
existsSync(path.join(p, 'common.sh')) ||
existsSync(path.join(p, 'common.ps1'))) {
return p; return p;
} }
} }
@@ -193,6 +195,37 @@ export function findWorkerServicePath(): string | null {
return null; return null;
} }
/**
* Find the Bun executable path
* Required because worker-service.cjs uses bun:sqlite which is Bun-specific
* Searches common installation locations across platforms
*/
export function findBunPath(): string {
const possiblePaths = [
// Standard user install location (most common)
path.join(homedir(), '.bun', 'bin', 'bun'),
// Global install locations
'/usr/local/bin/bun',
'/usr/bin/bun',
// Windows locations
...(process.platform === 'win32' ? [
path.join(homedir(), '.bun', 'bin', 'bun.exe'),
path.join(process.env.LOCALAPPDATA || '', 'bun', 'bun.exe'),
] : []),
];
for (const p of possiblePaths) {
if (p && existsSync(p)) {
return p;
}
}
// Fallback to 'bun' and hope it's in PATH
// This allows the installation to proceed even if we can't find bun
// The user will get a clear error when the hook runs if bun isn't available
return 'bun';
}
/** /**
* Get the target directory for Cursor hooks based on install target * Get the target directory for Cursor hooks based on install target
*/ */
@@ -312,15 +345,21 @@ export async function installCursorHooks(_sourceDir: string, target: CursorInsta
// Generate hooks.json with unified CLI commands // Generate hooks.json with unified CLI commands
const hooksJsonPath = path.join(targetDir, 'hooks.json'); const hooksJsonPath = path.join(targetDir, 'hooks.json');
// Find bun executable - required because worker-service.cjs uses bun:sqlite
const bunPath = findBunPath();
const escapedBunPath = bunPath.replace(/\\/g, '\\\\');
// Use the absolute path to worker-service.cjs // Use the absolute path to worker-service.cjs
// Escape backslashes for JSON on Windows // Escape backslashes for JSON on Windows
const escapedWorkerPath = workerServicePath.replace(/\\/g, '\\\\'); const escapedWorkerPath = workerServicePath.replace(/\\/g, '\\\\');
// Helper to create hook command using unified CLI // Helper to create hook command using unified CLI with bun runtime
const makeHookCommand = (command: string) => { const makeHookCommand = (command: string) => {
return `node "${escapedWorkerPath}" hook cursor ${command}`; return `"${escapedBunPath}" "${escapedWorkerPath}" hook cursor ${command}`;
}; };
console.log(` Using Bun runtime: ${bunPath}`);
const hooksJson: CursorHooksJson = { const hooksJson: CursorHooksJson = {
version: 1, version: 1,
hooks: { hooks: {
@@ -356,7 +395,7 @@ export async function installCursorHooks(_sourceDir: string, target: CursorInsta
Installation complete! Installation complete!
Hooks installed to: ${targetDir}/hooks.json Hooks installed to: ${targetDir}/hooks.json
Using unified CLI: node worker-service.cjs hook cursor <command> Using unified CLI: bun worker-service.cjs hook cursor <command>
Next steps: Next steps:
1. Start claude-mem worker: claude-mem start 1. Start claude-mem worker: claude-mem start
@@ -532,7 +571,7 @@ export function checkCursorHooksStatus(): number {
const firstCommand = hooksContent?.hooks?.beforeSubmitPrompt?.[0]?.command || ''; const firstCommand = hooksContent?.hooks?.beforeSubmitPrompt?.[0]?.command || '';
if (firstCommand.includes('worker-service.cjs') && firstCommand.includes('hook cursor')) { if (firstCommand.includes('worker-service.cjs') && firstCommand.includes('hook cursor')) {
console.log(` Mode: Unified CLI (node worker-service.cjs)`); console.log(` Mode: Unified CLI (bun worker-service.cjs)`);
} else { } else {
// Detect legacy shell scripts // Detect legacy shell scripts
const bashScripts = ['session-init.sh', 'context-inject.sh', 'save-observation.sh']; const bashScripts = ['session-init.sh', 'context-inject.sh', 'save-observation.sh'];