// ======================================== // Hook Quick Templates Component // ======================================== // Predefined hook templates for quick installation import { useMemo } from 'react'; import { useIntl } from 'react-intl'; import { Bell, Database, Wrench, Check, Zap, Download, Loader2, Shield, FileCode, FileSearch, GitBranch, Send, FileBarChart, } from 'lucide-react'; import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; import { cn } from '@/lib/utils'; import type { HookTriggerType } from './HookCard'; // ========== Types ========== /** * Template category type */ export type TemplateCategory = 'notification' | 'indexing' | 'automation'; /** * Hook template definition */ export interface HookTemplate { id: string; name: string; description: string; category: TemplateCategory; trigger: HookTriggerType; command: string; args?: string[]; matcher?: string; } /** * Component props */ export interface HookQuickTemplatesProps { /** Callback when install button is clicked */ onInstallTemplate: (templateId: string) => Promise; /** Array of installed template IDs */ installedTemplates: string[]; /** Optional loading state */ isLoading?: boolean; /** ID of the template currently being installed */ installingTemplateId?: string | null; } // ========== Hook Templates ========== /** * Predefined hook templates for quick installation */ export const HOOK_TEMPLATES: readonly HookTemplate[] = [ { id: 'session-start-notify', name: 'Session Start Notify', description: 'Notify dashboard when a new workflow session is created', category: 'notification', trigger: 'SessionStart', command: 'node', args: [ '-e', 'const cp=require("child_process");const payload=JSON.stringify({type:"SESSION_CREATED",timestamp:Date.now(),project:process.env.CLAUDE_PROJECT_DIR||process.cwd()});cp.spawnSync("curl",["-s","-X","POST","-H","Content-Type: application/json","-d",payload,"http://localhost:3456/api/hook"],{stdio:"inherit",shell:true})' ] }, { id: 'session-state-watch', name: 'Session State Watch', description: 'Watch for session metadata file changes (workflow-session.json)', category: 'notification', trigger: 'PostToolUse', matcher: 'Write|Edit', command: 'node', args: [ '-e', 'const p=JSON.parse(process.env.HOOK_INPUT||"{}");const file=(p.tool_input&&p.tool_input.file_path)||"";if(/workflow-session\\.json$|session-metadata\\.json$/.test(file)){const fs=require("fs");try{const content=fs.readFileSync(file,"utf8");const data=JSON.parse(content);const cp=require("child_process");const payload=JSON.stringify({type:"SESSION_STATE_CHANGED",file:file,sessionId:data.session_id||"",status:data.status||"unknown",project:process.env.CLAUDE_PROJECT_DIR||process.cwd(),timestamp:Date.now()});cp.spawnSync("curl",["-s","-X","POST","-H","Content-Type: application/json","-d",payload,"http://localhost:3456/api/hook"],{stdio:"inherit",shell:true})}catch(e){}}' ] }, // --- Notification --- { id: 'stop-notify', name: 'Stop Notify', description: 'Notify dashboard when Claude finishes responding', category: 'notification', trigger: 'Stop', command: 'node', args: [ '-e', 'const cp=require("child_process");const payload=JSON.stringify({type:"TASK_COMPLETED",timestamp:Date.now(),project:process.env.CLAUDE_PROJECT_DIR||process.cwd()});cp.spawnSync("curl",["-s","-X","POST","-H","Content-Type: application/json","-d",payload,"http://localhost:3456/api/hook"],{stdio:"inherit",shell:true})' ] }, // --- Automation --- { id: 'auto-format-on-write', name: 'Auto Format on Write', description: 'Auto-format files after Claude writes or edits them', category: 'automation', trigger: 'PostToolUse', matcher: 'Write|Edit', command: 'node', args: [ '-e', 'const p=JSON.parse(process.env.HOOK_INPUT||"{}");const file=(p.tool_input&&p.tool_input.file_path)||"";if(file){const cp=require("child_process");cp.spawnSync("npx",["prettier","--write",file],{stdio:"inherit",shell:true})}' ] }, { id: 'auto-lint-on-write', name: 'Auto Lint on Write', description: 'Auto-lint files after Claude writes or edits them', category: 'automation', trigger: 'PostToolUse', matcher: 'Write|Edit', command: 'node', args: [ '-e', 'const p=JSON.parse(process.env.HOOK_INPUT||"{}");const file=(p.tool_input&&p.tool_input.file_path)||"";if(file){const cp=require("child_process");cp.spawnSync("npx",["eslint","--fix",file],{stdio:"inherit",shell:true})}' ] }, { id: 'block-sensitive-files', name: 'Block Sensitive Files', description: 'Block modifications to sensitive files (.env, secrets, credentials)', category: 'automation', trigger: 'PreToolUse', matcher: 'Write|Edit', command: 'node', args: [ '-e', 'const p=JSON.parse(process.env.HOOK_INPUT||"{}");const file=(p.tool_input&&p.tool_input.file_path)||"";if(/\\.env|secret|credential|\\.key$/.test(file)){process.stderr.write("Blocked: modifying sensitive file "+file);process.exit(2)}' ] }, { id: 'git-auto-stage', name: 'Git Auto Stage', description: 'Auto stage all modified files when Claude finishes responding', category: 'automation', trigger: 'Stop', command: 'node', args: [ '-e', 'const cp=require("child_process");cp.spawnSync("git",["add","-u"],{stdio:"inherit",shell:true})' ] }, // --- Indexing --- { id: 'post-edit-index', name: 'Post Edit Index', description: 'Notify indexing service when files are modified', category: 'indexing', trigger: 'PostToolUse', matcher: 'Write|Edit', command: 'node', args: [ '-e', 'const p=JSON.parse(process.env.HOOK_INPUT||"{}");const file=(p.tool_input&&p.tool_input.file_path)||"";if(file){const cp=require("child_process");const payload=JSON.stringify({type:"FILE_MODIFIED",file:file,project:process.env.CLAUDE_PROJECT_DIR||process.cwd(),timestamp:Date.now()});cp.spawnSync("curl",["-s","-X","POST","-H","Content-Type: application/json","-d",payload,"http://localhost:3456/api/hook"],{stdio:"inherit",shell:true})}' ] }, { id: 'session-end-summary', name: 'Session End Summary', description: 'Send session summary to dashboard on session end', category: 'indexing', trigger: 'Stop', command: 'node', args: [ '-e', 'const p=JSON.parse(process.env.HOOK_INPUT||"{}");const cp=require("child_process");const payload=JSON.stringify({type:"SESSION_SUMMARY",transcript:p.transcript_path||"",project:process.env.CLAUDE_PROJECT_DIR||process.cwd(),timestamp:Date.now()});cp.spawnSync("curl",["-s","-X","POST","-H","Content-Type: application/json","-d",payload,"http://localhost:3456/api/hook"],{stdio:"inherit",shell:true})' ] } ] as const; // ========== Category Icons ========== const CATEGORY_ICONS: Record = { notification: { icon: Bell, color: 'text-blue-500', bg: 'bg-blue-500/10' }, indexing: { icon: Database, color: 'text-purple-500', bg: 'bg-purple-500/10' }, automation: { icon: Wrench, color: 'text-orange-500', bg: 'bg-orange-500/10' } }; // ========== Template Icons ========== const TEMPLATE_ICONS: Record = { 'session-start-notify': Send, 'session-state-watch': FileBarChart, 'stop-notify': Bell, 'auto-format-on-write': FileCode, 'auto-lint-on-write': FileSearch, 'block-sensitive-files': Shield, 'git-auto-stage': GitBranch, 'post-edit-index': Database, 'session-end-summary': FileBarChart, }; // ========== Category Names ========== function getCategoryName(category: TemplateCategory, formatMessage: ReturnType['formatMessage']): string { const names: Record = { notification: formatMessage({ id: 'cliHooks.templates.categories.notification' }), indexing: formatMessage({ id: 'cliHooks.templates.categories.indexing' }), automation: formatMessage({ id: 'cliHooks.templates.categories.automation' }) }; return names[category]; } // ========== Main Component ========== /** * HookQuickTemplates - Display predefined hook templates for quick installation */ export function HookQuickTemplates({ onInstallTemplate, installedTemplates, isLoading: _isLoading = false, installingTemplateId = null }: HookQuickTemplatesProps) { const { formatMessage } = useIntl(); // Group templates by category const templatesByCategory = useMemo(() => { return HOOK_TEMPLATES.reduce((acc, template) => { if (!acc[template.category]) { acc[template.category] = []; } acc[template.category].push(template); return acc; }, {} as Record); }, []); // Define category order const categoryOrder: TemplateCategory[] = ['notification', 'indexing', 'automation']; const handleInstall = async (templateId: string) => { await onInstallTemplate(templateId); }; return (
{/* Header */}

{formatMessage({ id: 'cliHooks.templates.title' })}

{formatMessage({ id: 'cliHooks.templates.description' })}

{/* Categories */} {categoryOrder.map((category) => { const templates = templatesByCategory[category]; if (!templates || templates.length === 0) return null; const { icon: CategoryIcon, color } = CATEGORY_ICONS[category]; return (
{/* Category Header */}

{getCategoryName(category, formatMessage)}

{templates.length}
{/* Template Card Grid */}
{templates.map((template) => { const isInstalled = installedTemplates.includes(template.id); const isInstalling = installingTemplateId === template.id; const TemplateIcon = TEMPLATE_ICONS[template.id] || Zap; const { color: catColor, bg: catBg } = CATEGORY_ICONS[template.category]; return ( {/* Card Header: Icon + Name + Install */}

{formatMessage({ id: `cliHooks.templates.templates.${template.id}.name` })}

{formatMessage({ id: `cliHooks.trigger.${template.trigger}` })} {template.matcher && ( {template.matcher} )}
{/* Icon Install Button */}
{/* Description */}

{formatMessage({ id: `cliHooks.templates.templates.${template.id}.description` })}

); })}
); })}
); } export default HookQuickTemplates;