hooks.local.ts - Local hooks (updates .claude/settings.local.json)
hooks.user.ts - User hooks (updates ~/.claude/settings.json)
For example, create .claude/hooks/hooks.ts:
import{defineHooks}from"@timoaus/define-claude-code-hooks";constpreventEditingEnvFile=defineHook("PreToolUse",{matcher: "Write|Edit|MultiEdit",handler: async(input)=>{constfilePath=input.tool_input.file_path;if(filePath&&filePath.endsWith(".env")){return{decision: "block",reason:
"Direct editing of .env files is not allowed for security reasons",};}},});exportdefaultdefineHooks({PreToolUse: [preventEditingEnvFile],});
Extend your hooks with built-in logging utilities:
Creates a log file in your project root: hook-log.tool-use.json
Run the script to update your settings:
The CLI will automatically detect which hook files exist and update the corresponding settings files. Your hooks are now active! Claude Code will respect your rules and log tool usage.
The library includes several predefined hook utilities for common logging scenarios:
Hook Function
Options
logPreToolUseEvents Logs tool uses before execution
• Optional first param: matcher (regex pattern, defaults to '.*' for all tools) • maxEventsStored (default: 100) • logFileName (default: 'hook-log.tool-use.json') • includeToolInput (default: true)
logPostToolUseEvents Logs tool uses after execution
• Optional first param: matcher (regex pattern, defaults to '.*' for all tools) • maxEventsStored (default: 100) • logFileName (default: 'hook-log.tool-use.json') • includeToolInput (default: true) • includeToolResponse (default: true)
import{defineHooks}from"define-claude-code-hooks";exportdefaultdefineHooks({PreToolUse: [// Block grep commands and suggest ripgrep{matcher: "Bash",handler: async(input)=>{if(input.tool_input.command?.includes("grep")){return{decision: "block",reason: "Use ripgrep (rg) instead of grep for better performance",};}},},// Log all file writes{matcher: "Write|Edit|MultiEdit",handler: async(input)=>{console.error(`Writing to file: ${input.tool_input.file_path}`);},},],PostToolUse: [// Format TypeScript files after editing{matcher: "Write|Edit",handler: async(input)=>{if(input.tool_input.file_path?.endsWith(".ts")){const{ execSync }=require("child_process");execSync(`prettier --write "${input.tool_input.file_path}"`);}},},],Notification: [// Custom notification handlerasync(input)=>{console.log(`Claude says: ${input.message}`);},],});
2. Update your Claude Code settings:
# Automatically detect and update all hook files
npx define-claude-code-hooks
# Remove all managed hooks
npx define-claude-code-hooks --remove
# Use a custom global settings path (if not in ~/.claude/settings.json)
npx define-claude-code-hooks --global-settings-path /path/to/settings.json
The CLI automatically detects which hook files exist and updates the corresponding settings:
Note: If your global Claude settings.json is not in the default location (~/.claude/settings.json), use the --global-settings-path option to specify the correct path.
defineHooks(hooks: HookDefinition)
Define multiple hooks. Returns the hook definition object.
For PreToolUse and PostToolUse: pass an array of objects with matcher and handler
For other hooks: pass an array of handler functions
defineHook(type: HookType, definition)
Define a single hook (for advanced use cases).
For PreToolUse and PostToolUse: pass an object with matcher and handler
PreToolUse: Runs before tool execution, can block or approve
PostToolUse: Runs after tool execution
Notification: Handles Claude Code notifications
Stop: Runs when main agent stops
SubagentStop: Runs when subagent stops
Hooks can return structured responses:
interfaceHookOutput{// Common fieldscontinue?: boolean;// Whether Claude should continuestopReason?: string;// Message when continue is falsesuppressOutput?: boolean;// Hide output from transcript// PreToolUse specificdecision?: "approve"|"block";reason?: string;// Reason for decision}
The CLI scans for hook files (hooks.ts, hooks.local.ts, hooks.user.ts)
For each file found, it updates the corresponding settings.json with commands that use ts-node to execute TypeScript directly
Marks managed hooks so they can be safely removed later
This library is written in TypeScript and provides full type safety for all hook inputs and outputs.
Predefined Hook Utilities
The library includes several predefined hook utilities for common logging scenarios:
import{defineHooks,logPreToolUseEvents,logPostToolUseEvents,}from"@timoaus/define-claude-code-hooks";exportdefaultdefineHooks({// Log all tool usePreToolUse: logPreToolUseEvents(),// Logs all tools by defaultPostToolUse: logPostToolUseEvents(),// Logs all tools by default});// Or log specific tools onlyexportdefaultdefineHooks({PreToolUse: logPreToolUseEvents("Bash|Write|Edit",{maxEventsStored: 200,logFileName: "tool-use.json",}),PostToolUse: logPostToolUseEvents("Bash|Write|Edit",{maxEventsStored: 200,logFileName: "tool-use.json",}),});
Environment File Protection
import{defineHooks,blockEnvFiles,}from"@timoaus/define-claude-code-hooks";exportdefaultdefineHooks({PreToolUse: [blockEnvFiles,// Blocks access to .env files while allowing .env.example],});
The blockEnvFiles hook:
Blocks reading or writing to .env files and variants (.env.local, .env.production, etc.)
Allows access to example env files (.env.example, .env.sample, .env.template, .env.dist)
Works with Read, Write, Edit, and MultiEdit tools
Provides clear error messages when access is blocked