#!/usr/bin/env node /** * Cursor-to-Claude Code Hook Adapter * Transforms Cursor stdin JSON to Claude Code hook format, * then delegates to existing scripts/hooks/*.js */ const { execFileSync } = require('child_process'); const path = require('path'); const MAX_STDIN = 1024 * 1024; function readStdin() { return new Promise((resolve) => { let data = ''; process.stdin.setEncoding('utf8'); process.stdin.on('data', chunk => { if (data.length < MAX_STDIN) data += chunk.substring(0, MAX_STDIN - data.length); }); process.stdin.on('end', () => resolve(data)); }); } function getPluginRoot() { return path.resolve(__dirname, '..', '..'); } function transformToClaude(cursorInput, overrides = {}) { return { tool_input: { command: cursorInput.command || cursorInput.args?.command || '', file_path: cursorInput.path || cursorInput.file || cursorInput.args?.filePath || '', ...overrides.tool_input, }, tool_output: { output: cursorInput.output || cursorInput.result || '', ...overrides.tool_output, }, transcript_path: cursorInput.transcript_path || cursorInput.transcriptPath || cursorInput.session?.transcript_path || '', _cursor: { conversation_id: cursorInput.conversation_id, hook_event_name: cursorInput.hook_event_name, workspace_roots: cursorInput.workspace_roots, model: cursorInput.model, }, }; } function runExistingHook(scriptName, stdinData) { const scriptPath = path.join(getPluginRoot(), 'scripts', 'hooks', scriptName); try { execFileSync('node', [scriptPath], { input: typeof stdinData === 'string' ? stdinData : JSON.stringify(stdinData), stdio: ['pipe', 'pipe', 'pipe'], timeout: 15000, cwd: process.cwd(), }); } catch (e) { if (e.status === 2) process.exit(2); // Forward blocking exit code } } function hookEnabled(hookId, allowedProfiles = ['standard', 'strict']) { const rawProfile = String(process.env.ECC_HOOK_PROFILE || 'standard').toLowerCase(); const profile = ['minimal', 'standard', 'strict'].includes(rawProfile) ? rawProfile : 'standard'; const disabled = new Set( String(process.env.ECC_DISABLED_HOOKS || '') .split(',') .map(v => v.trim().toLowerCase()) .filter(Boolean) ); if (disabled.has(String(hookId || '').toLowerCase())) { return false; } return allowedProfiles.includes(profile); } module.exports = { readStdin, getPluginRoot, transformToClaude, runExistingHook, hookEnabled };