Public/Get-ClaudeHookConfig.ps1
|
function Get-ClaudeHookConfig { <# .SYNOPSIS Lists hook entries from Claude Code settings files. .DESCRIPTION Returns a flat list of hook entries from the specified settings files. Each row includes Scope, Path, Event, Matcher, Type, Command, Shell, and Timeout. Use -Scope All (default) to read User, Project, and Local files in priority order. Rows are tagged with their source scope - this function lists, it does not resolve effective hooks (precedence merging is not applied). .PARAMETER Event Filter results to a specific event name. .PARAMETER Matcher Filter results to a specific matcher string. .PARAMETER Scope Which settings file(s) to read. All (default), User, Project, Local, or Plugin. Plugin requires -Path. .PARAMETER Path Override file path. Required when -Scope is Plugin. .EXAMPLE Get-ClaudeHookConfig | Format-Table Lists all hook entries from User, Project, and Local settings files in a table. .EXAMPLE Get-ClaudeHookConfig -Event PreToolUse -Matcher Bash Returns only the hooks registered for the PreToolUse event with the Bash matcher. .OUTPUTS System.Management.Automation.PSCustomObject .LINK about_ClaudeHooks #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSAvoidAssignmentToAutomaticVariable', 'Event', Scope = 'Function', Justification = 'Parameter is immediately re-assigned.' )] [OutputType([PSCustomObject])] [CmdletBinding()] param( [string]$Event, [string]$Matcher, [ValidateSet('All', 'User', 'Project', 'Local', 'Plugin')] [string]$Scope = 'All', [string]$Path ) # $eventName is an automatic variable in event handlers, so we use # $eventName for the parameter and $eventNameName for the internal variable # to avoid conflicts. $eventName = $PSBoundParameters['Event'] $targets = if ($Scope -eq 'All') { @( @{ Scope = 'User'; Path = Resolve-ClaudeSettingsPath -Scope User } @{ Scope = 'Project'; Path = Resolve-ClaudeSettingsPath -Scope Project } @{ Scope = 'Local'; Path = Resolve-ClaudeSettingsPath -Scope Local } ) } elseif ($Scope -eq 'Plugin') { @(@{ Scope = 'Plugin'; Path = $Path }) } else { @(@{ Scope = $Scope; Path = $(if ($Path) { $Path } else { Resolve-ClaudeSettingsPath -Scope $Scope }) }) } foreach ($target in $targets) { $filePath = $target.Path if (-not (Test-Path $filePath)) { continue } $raw = Get-Content $filePath -Raw -Encoding UTF8 -ErrorAction SilentlyContinue if ([string]::IsNullOrWhiteSpace($raw)) { continue } try { $settings = $raw | ConvertFrom-Json -Depth 64 -AsHashtable } catch { continue } if (-not $settings['hooks']) { continue } foreach ($evtName in $settings['hooks'].Keys) { if ($eventName -and $evtName -ne $eventName) { continue } foreach ($matcherEntry in @($settings['hooks'][$evtName])) { $mStr = $matcherEntry['matcher'] if ($PSBoundParameters.ContainsKey('Matcher') -and $mStr -ne $Matcher) { continue } foreach ($h in @($matcherEntry['hooks'])) { [pscustomobject]@{ Scope = $target.Scope Path = $filePath Event = $evtName Matcher = $mStr Type = $h['type'] Command = $h['command'] Shell = $h['shell'] Timeout = $h['timeout'] } } } } } } |