// Hook Manager View // Renders the Claude Code hooks management interface async function renderHookManager() { const container = document.getElementById('mainContent'); if (!container) return; // Hide stats grid and search for Hook view const statsGrid = document.getElementById('statsGrid'); const searchInput = document.getElementById('searchInput'); if (statsGrid) statsGrid.style.display = 'none'; if (searchInput) searchInput.parentElement.style.display = 'none'; // Load hook config if not already loaded if (!hookConfig.global.hooks && !hookConfig.project.hooks) { await loadHookConfig(); } const globalHooks = hookConfig.global?.hooks || {}; const projectHooks = hookConfig.project?.hooks || {}; // Count hooks const globalHookCount = countHooks(globalHooks); const projectHookCount = countHooks(projectHooks); container.innerHTML = `

Project Hooks

.claude/settings.json
${projectHookCount} hooks configured
${projectHookCount === 0 ? `

No hooks configured for this project

Create a hook to automate actions on tool usage

` : `
${renderHooksByEvent(projectHooks, 'project')}
`}

Global Hooks

~/.claude/settings.json
${globalHookCount} hooks configured
${globalHookCount === 0 ? `

No global hooks configured

Global hooks apply to all Claude Code sessions

` : `
${renderHooksByEvent(globalHooks, 'global')}
`}

Quick Install Templates

One-click hook installation
${renderQuickInstallCard('ccw-notify', 'CCW Dashboard Notify', 'Notify CCW dashboard when files are written', 'PostToolUse', 'Write')} ${renderQuickInstallCard('log-tool', 'Tool Usage Logger', 'Log all tool executions to a file', 'PostToolUse', 'All')} ${renderQuickInstallCard('lint-check', 'Auto Lint Check', 'Run ESLint on JavaScript/TypeScript files after write', 'PostToolUse', 'Write')} ${renderQuickInstallCard('git-add', 'Auto Git Stage', 'Automatically stage written files to git', 'PostToolUse', 'Write')}

Environment Variables Reference

$CLAUDE_FILE_PATHS Space-separated file paths affected
$CLAUDE_TOOL_NAME Name of the tool being executed
$CLAUDE_TOOL_INPUT JSON input passed to the tool
$CLAUDE_SESSION_ID Current Claude session ID
$CLAUDE_PROJECT_DIR Current project directory path
$CLAUDE_WORKING_DIR Current working directory
`; // Attach event listeners attachHookEventListeners(); // Initialize Lucide icons if (typeof lucide !== 'undefined') lucide.createIcons(); } function countHooks(hooks) { let count = 0; for (const event of Object.keys(hooks)) { const hookList = hooks[event]; count += Array.isArray(hookList) ? hookList.length : 1; } return count; } function renderHooksByEvent(hooks, scope) { const events = Object.keys(hooks); if (events.length === 0) return ''; return events.map(event => { const hookList = Array.isArray(hooks[event]) ? hooks[event] : [hooks[event]]; return hookList.map((hook, index) => { const matcher = hook.matcher || 'All tools'; const command = hook.command || 'N/A'; const args = hook.args || []; return `
${getHookEventIconLucide(event)}

${event}

${getHookEventDescription(event)}

matcher ${escapeHtml(matcher)}
command ${escapeHtml(command)}
${args.length > 0 ? `
args ${escapeHtml(args.slice(0, 3).join(' '))}${args.length > 3 ? '...' : ''}
` : ''}
`; }).join(''); }).join(''); } function renderQuickInstallCard(templateId, title, description, event, matcher) { const isInstalled = isHookTemplateInstalled(templateId); return `
${isInstalled ? '' : ''}

${escapeHtml(title)}

${escapeHtml(description)}

${event} Matches: ${matcher}
${isInstalled ? ` ` : ` `}
`; } function isHookTemplateInstalled(templateId) { const template = HOOK_TEMPLATES[templateId]; if (!template) return false; // Check project hooks const projectHooks = hookConfig.project?.hooks?.[template.event]; if (projectHooks) { const hookList = Array.isArray(projectHooks) ? projectHooks : [projectHooks]; if (hookList.some(h => h.command === template.command)) return true; } // Check global hooks const globalHooks = hookConfig.global?.hooks?.[template.event]; if (globalHooks) { const hookList = Array.isArray(globalHooks) ? globalHooks : [globalHooks]; if (hookList.some(h => h.command === template.command)) return true; } return false; } async function installHookTemplate(templateId, scope) { const template = HOOK_TEMPLATES[templateId]; if (!template) { showRefreshToast('Template not found', 'error'); return; } const hookData = { command: template.command, args: template.args }; if (template.matcher) { hookData.matcher = template.matcher; } await saveHook(scope, template.event, hookData); } async function uninstallHookTemplate(templateId) { const template = HOOK_TEMPLATES[templateId]; if (!template) return; // Find and remove from project hooks const projectHooks = hookConfig.project?.hooks?.[template.event]; if (projectHooks) { const hookList = Array.isArray(projectHooks) ? projectHooks : [projectHooks]; const index = hookList.findIndex(h => h.command === template.command); if (index !== -1) { await removeHook('project', template.event, index); return; } } // Find and remove from global hooks const globalHooks = hookConfig.global?.hooks?.[template.event]; if (globalHooks) { const hookList = Array.isArray(globalHooks) ? globalHooks : [globalHooks]; const index = hookList.findIndex(h => h.command === template.command); if (index !== -1) { await removeHook('global', template.event, index); return; } } } function attachHookEventListeners() { // Edit buttons document.querySelectorAll('.hook-card button[data-action="edit"]').forEach(btn => { btn.addEventListener('click', (e) => { const button = e.currentTarget; const scope = button.dataset.scope; const event = button.dataset.event; const index = parseInt(button.dataset.index); const hooks = scope === 'global' ? hookConfig.global.hooks : hookConfig.project.hooks; const hookList = Array.isArray(hooks[event]) ? hooks[event] : [hooks[event]]; const hook = hookList[index]; if (hook) { openHookCreateModal({ scope: scope, event: event, index: index, matcher: hook.matcher || '', command: hook.command, args: hook.args || [] }); } }); }); // Delete buttons document.querySelectorAll('.hook-card button[data-action="delete"]').forEach(btn => { btn.addEventListener('click', async (e) => { const button = e.currentTarget; const scope = button.dataset.scope; const event = button.dataset.event; const index = parseInt(button.dataset.index); if (confirm(`Remove this ${event} hook?`)) { await removeHook(scope, event, index); } }); }); // Install project buttons document.querySelectorAll('button[data-action="install-project"]').forEach(btn => { btn.addEventListener('click', async (e) => { const templateId = e.currentTarget.dataset.template; await installHookTemplate(templateId, 'project'); }); }); // Install global buttons document.querySelectorAll('button[data-action="install-global"]').forEach(btn => { btn.addEventListener('click', async (e) => { const templateId = e.currentTarget.dataset.template; await installHookTemplate(templateId, 'global'); }); }); // Uninstall buttons document.querySelectorAll('button[data-action="uninstall"]').forEach(btn => { btn.addEventListener('click', async (e) => { const templateId = e.currentTarget.dataset.template; await uninstallHookTemplate(templateId); }); }); }