update
This commit is contained in:
470
2026.06.23_claude_code_hooks/audit.ps1
Normal file
470
2026.06.23_claude_code_hooks/audit.ps1
Normal file
@@ -0,0 +1,470 @@
|
||||
# This code is supported by the website: https://www.guanjihuan.com
|
||||
# The newest version of this code is on the web page: https://www.guanjihuan.com/archives/49203
|
||||
|
||||
[CmdletBinding()]
|
||||
param([Parameter(Mandatory, Position=0)][string]$EventType)
|
||||
|
||||
$Utf8NoBom = [System.Text.UTF8Encoding]::new($false)
|
||||
[Console]::InputEncoding = $Utf8NoBom
|
||||
[Console]::OutputEncoding = $Utf8NoBom
|
||||
|
||||
$LogDir = 'D:\claude\logs'
|
||||
$LogFile = Join-Path $LogDir "$(Get-Date -Format 'yyyy-MM-dd').json"
|
||||
$ErrorLog = Join-Path $LogDir 'audit-error.log'
|
||||
$MutexTimeoutMs = 5000
|
||||
|
||||
$StatusRunning = 'running'
|
||||
$StatusAwaitingUser = 'awaiting_user'
|
||||
$StatusCompleted = 'completed'
|
||||
$StatusStopped = 'stopped'
|
||||
$InProgressStatuses = @($StatusRunning, $StatusAwaitingUser)
|
||||
|
||||
New-Item -ItemType Directory -Path $LogDir -Force -ErrorAction SilentlyContinue | Out-Null
|
||||
|
||||
function Write-Err([string]$Msg, [string]$Stage) {
|
||||
try {
|
||||
$line = "$(Get-Date -Format 'o') [$Stage] $Msg"
|
||||
[System.IO.File]::AppendAllText($ErrorLog, $line + "`n", $Utf8NoBom)
|
||||
} catch { }
|
||||
}
|
||||
|
||||
function Read-StdinJson {
|
||||
try {
|
||||
$reader = [System.IO.StreamReader]::new([Console]::OpenStandardInput(), $Utf8NoBom)
|
||||
$stdin = $reader.ReadToEnd()
|
||||
if ($stdin -and -not [string]::IsNullOrWhiteSpace($stdin)) {
|
||||
return $stdin | ConvertFrom-Json -ErrorAction Stop
|
||||
}
|
||||
} catch {
|
||||
Write-Err "stdin_parse_failed: $_" 'parse'
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-PropertyValue($Obj, [string]$Name) {
|
||||
if ($null -eq $Obj) { return $null }
|
||||
$prop = $Obj.PSObject.Properties[$Name]
|
||||
if ($prop) { return $prop.Value }
|
||||
return $null
|
||||
}
|
||||
|
||||
function Test-ConcreteModel([string]$Model) {
|
||||
if ([string]::IsNullOrWhiteSpace($Model)) { return $false }
|
||||
return ($Model.Trim().ToLowerInvariant() -notin @('auto', 'default', 'unknown', 'null'))
|
||||
}
|
||||
|
||||
function Get-ModelName($Value) {
|
||||
if ($null -eq $Value) { return $null }
|
||||
if ($Value -is [string]) { return $Value.Trim() }
|
||||
|
||||
$fallback = $null
|
||||
foreach ($name in @('id', 'model', 'name', 'display_name')) {
|
||||
$model = Get-PropertyValue $Value $name
|
||||
if (-not $model) { continue }
|
||||
|
||||
$text = ([string]$model).Trim()
|
||||
if (-not $fallback) { $fallback = $text }
|
||||
if (Test-ConcreteModel $text) { return $text }
|
||||
}
|
||||
if ($fallback) { return $fallback }
|
||||
return ([string]$Value).Trim()
|
||||
}
|
||||
|
||||
function Get-ModelFromPayload($Payload) {
|
||||
if (-not $Payload) { return $null }
|
||||
|
||||
$model = Get-ModelName (Get-PropertyValue $Payload 'model')
|
||||
if (Test-ConcreteModel $model) { return $model }
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-ModelFromEnvironment {
|
||||
foreach ($name in @('ANTHROPIC_MODEL', 'CLAUDE_MODEL', 'ANTHROPIC_SMALL_FAST_MODEL')) {
|
||||
$model = [Environment]::GetEnvironmentVariable($name)
|
||||
if (Test-ConcreteModel $model) { return $model.Trim() }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-ModelFromPayloadOrTranscript($Payload) {
|
||||
$model = $null
|
||||
if ($Payload) {
|
||||
$transcriptPath = Get-PropertyValue $Payload 'transcript_path'
|
||||
if ($transcriptPath) {
|
||||
$model = Get-ModelFromTranscript ([string]$transcriptPath)
|
||||
if (Test-ConcreteModel $model) { return $model }
|
||||
}
|
||||
|
||||
$model = Get-ModelFromPayload $Payload
|
||||
if (Test-ConcreteModel $model) { return $model }
|
||||
}
|
||||
|
||||
return (Get-ModelFromEnvironment)
|
||||
}
|
||||
|
||||
function Update-PromptModel($Prompt, $Payload) {
|
||||
if (-not $Prompt) { return $false }
|
||||
if (Test-ConcreteModel ([string]$Prompt.model)) { return $false }
|
||||
|
||||
$model = Get-ModelFromPayloadOrTranscript $Payload
|
||||
if (Test-ConcreteModel $model) {
|
||||
$Prompt.model = $model
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function ConvertTo-ReadableJson {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(ValueFromPipeline)]$InputObject,
|
||||
[int]$Depth = 32
|
||||
)
|
||||
process {
|
||||
$json = $InputObject | ConvertTo-Json -Depth $Depth
|
||||
if ($json -match '\\u[0-9A-Fa-f]{4}') {
|
||||
$json = [regex]::Replace($json, '\\u([0-9A-Fa-f]{4})', {
|
||||
param($m) [char][Convert]::ToInt32($m.Groups[1].Value, 16)
|
||||
})
|
||||
}
|
||||
return $json
|
||||
}
|
||||
}
|
||||
|
||||
function Read-Log {
|
||||
if (-not (Test-Path -LiteralPath $LogFile)) { return @() }
|
||||
try {
|
||||
$raw = [System.IO.File]::ReadAllText($LogFile, $Utf8NoBom)
|
||||
if ([string]::IsNullOrWhiteSpace($raw)) { return @() }
|
||||
$data = $raw | ConvertFrom-Json -ErrorAction Stop
|
||||
if ($null -eq $data) { return @() }
|
||||
return @($data)
|
||||
} catch {
|
||||
Write-Err "log_read_failed: $_" 'read'
|
||||
return @()
|
||||
}
|
||||
}
|
||||
|
||||
function Write-Log([array]$Sessions) {
|
||||
$tmp = "$LogFile.tmp.$PID"
|
||||
try {
|
||||
$items = @($Sessions)
|
||||
if ($items.Count -eq 0) {
|
||||
$json = '[]'
|
||||
} else {
|
||||
$jsonItems = @($items | ForEach-Object { ConvertTo-ReadableJson -InputObject $_ -Depth 32 })
|
||||
$json = "[`n" + ($jsonItems -join ",`n") + "`n]"
|
||||
}
|
||||
[System.IO.File]::WriteAllText($tmp, $json, $Utf8NoBom)
|
||||
$null = ([System.IO.File]::ReadAllText($tmp, $Utf8NoBom) | ConvertFrom-Json -ErrorAction Stop)
|
||||
Move-Item -LiteralPath $tmp -Destination $LogFile -Force
|
||||
return $true
|
||||
} catch {
|
||||
Write-Err "log_write_failed: $_" 'write'
|
||||
if (Test-Path -LiteralPath $tmp) { Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue }
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Find-Session([array]$Sessions, [string]$Sid) {
|
||||
foreach ($s in $Sessions) {
|
||||
if ([string]$s.session_id -eq $Sid) { return $s }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Find-LastInProgress([array]$Sessions, [string]$Sid) {
|
||||
$session = Find-Session -Sessions $Sessions -Sid $Sid
|
||||
if (-not $session -or -not $session.prompts) { return $null }
|
||||
|
||||
$last = $null
|
||||
foreach ($p in @($session.prompts)) {
|
||||
if ([string]$p.status -in $InProgressStatuses) { $last = $p }
|
||||
}
|
||||
return $last
|
||||
}
|
||||
|
||||
function Complete-Prompt($Prompt, [string]$Now) {
|
||||
if (-not $Prompt -or [string]$Prompt.status -notin $InProgressStatuses) { return $false }
|
||||
$Prompt.status = $StatusCompleted
|
||||
$Prompt.end_time = $Now
|
||||
$Prompt.duration = Get-DurationHms -Start ([string]$Prompt.start_time) -End $Now
|
||||
return $true
|
||||
}
|
||||
|
||||
function Stop-Prompt($Prompt, [string]$Now) {
|
||||
if (-not $Prompt -or [string]$Prompt.status -notin $InProgressStatuses) { return $false }
|
||||
$Prompt.status = $StatusStopped
|
||||
$Prompt.end_time = $Now
|
||||
$Prompt.duration = Get-DurationHms -Start ([string]$Prompt.start_time) -End $Now
|
||||
return $true
|
||||
}
|
||||
|
||||
$script:_modelCachePath = $null
|
||||
$script:_modelCacheMtime = [datetime]::MinValue
|
||||
$script:_modelCacheValue = $null
|
||||
|
||||
function Get-ModelFromTranscript([string]$Path) {
|
||||
if ([string]::IsNullOrWhiteSpace($Path) -or -not (Test-Path -LiteralPath $Path)) { return $null }
|
||||
try {
|
||||
$mtime = (Get-Item -LiteralPath $Path).LastWriteTimeUtc
|
||||
if ($script:_modelCachePath -eq $Path -and $script:_modelCacheMtime -eq $mtime) {
|
||||
return $script:_modelCacheValue
|
||||
}
|
||||
|
||||
$raw = [System.IO.File]::ReadAllText($Path, $Utf8NoBom)
|
||||
if ([string]::IsNullOrEmpty($raw)) { return $null }
|
||||
|
||||
$lines = $raw -split "`r?`n"
|
||||
for ($i = $lines.Count - 1; $i -ge 0; $i--) {
|
||||
if ([string]::IsNullOrWhiteSpace($lines[$i])) { continue }
|
||||
$obj = $lines[$i] | ConvertFrom-Json -ErrorAction SilentlyContinue
|
||||
if ($obj -and [string]$obj.type -eq 'assistant' -and $obj.message -and $obj.message.model) {
|
||||
$script:_modelCachePath = $Path
|
||||
$script:_modelCacheMtime = $mtime
|
||||
$script:_modelCacheValue = [string]$obj.message.model
|
||||
return $script:_modelCacheValue
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Err "transcript_model_read_failed: $_" 'transcript'
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-DurationHms([string]$Start, [string]$End) {
|
||||
if ([string]::IsNullOrWhiteSpace($Start) -or [string]::IsNullOrWhiteSpace($End)) { return $null }
|
||||
try {
|
||||
$s = [datetime]::ParseExact($Start, 'yyyy-MM-dd HH:mm:ss', $null)
|
||||
$e = [datetime]::ParseExact($End, 'yyyy-MM-dd HH:mm:ss', $null)
|
||||
$ts = $e - $s
|
||||
if ($ts.TotalSeconds -lt 0) { return '00:00:00' }
|
||||
$h = [int][Math]::Floor($ts.TotalHours)
|
||||
return ('{0:D2}:{1:D2}:{2:D2}' -f $h, $ts.Minutes, $ts.Seconds)
|
||||
} catch {
|
||||
Write-Err "duration_parse_failed start='$Start' end='$End': $_" 'duration'
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Truncate([string]$Text, [int]$Length) {
|
||||
if ([string]::IsNullOrEmpty($Text) -or $Text.Length -le $Length) { return $Text }
|
||||
return $Text.Substring(0, $Length)
|
||||
}
|
||||
|
||||
function Get-ShortModel([string]$Model) {
|
||||
if ([string]::IsNullOrWhiteSpace($Model)) { return '' }
|
||||
$short = $Model -replace '^claude-', ''
|
||||
$parts = $short -split '[-/]'
|
||||
$short = ($parts | Select-Object -First 3) -join '-'
|
||||
if ($short.Length -gt 12) { $short = $short.Substring(0, 12) }
|
||||
return $short
|
||||
}
|
||||
|
||||
function Convert-PromptLists([array]$Sessions) {
|
||||
foreach ($s in $Sessions) {
|
||||
$prompts = [System.Collections.Generic.List[object]]::new()
|
||||
if ($s.prompts) {
|
||||
foreach ($p in @($s.prompts)) { $prompts.Add($p) }
|
||||
}
|
||||
if ($s.PSObject.Properties['prompts']) {
|
||||
$s.prompts = $prompts
|
||||
} else {
|
||||
$s | Add-Member -MemberType NoteProperty -Name prompts -Value $prompts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Write-StatusLine([string]$Line) {
|
||||
[Console]::Out.WriteLine($Line)
|
||||
}
|
||||
|
||||
$KnownEvents = @('SessionEnd', 'UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'PermissionRequest', 'Stop', 'StatusLine')
|
||||
if ($EventType -notin $KnownEvents) { exit 0 }
|
||||
|
||||
$Payload = Read-StdinJson
|
||||
|
||||
if ($EventType -eq 'StatusLine') {
|
||||
try {
|
||||
$sessions = @(Read-Log)
|
||||
$sid = if ($Payload) { [string](Get-PropertyValue $Payload 'session_id') } else { $null }
|
||||
$session = if ($sid) { Find-Session -Sessions $sessions -Sid $sid } else { $null }
|
||||
if (-not $session -and $sessions.Count -gt 0) {
|
||||
for ($i = $sessions.Count - 1; $i -ge 0; $i--) {
|
||||
if (Find-LastInProgress -Sessions $sessions -Sid ([string]$sessions[$i].session_id)) {
|
||||
$session = $sessions[$i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-not $session -and $sessions.Count -gt 0) {
|
||||
for ($i = $sessions.Count - 1; $i -ge 0; $i--) {
|
||||
if ($sessions[$i].prompts -and @($sessions[$i].prompts).Count -gt 0) {
|
||||
$session = $sessions[$i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-not $session) {
|
||||
Write-StatusLine '· idle'
|
||||
exit 0
|
||||
}
|
||||
|
||||
$prompt = Find-LastInProgress -Sessions $sessions -Sid ([string]$session.session_id)
|
||||
if (-not $prompt -and $session.prompts) { $prompt = @($session.prompts) | Select-Object -Last 1 }
|
||||
if (-not $prompt) {
|
||||
Write-StatusLine '· idle'
|
||||
exit 0
|
||||
}
|
||||
|
||||
$status = [string]$prompt.status
|
||||
$glyph = if ($status -eq $StatusRunning) { '*' } elseif ($status -eq $StatusAwaitingUser) { '?' } else { '·' }
|
||||
$displayModel = if (Test-ConcreteModel ([string]$prompt.model)) { [string]$prompt.model } else { Get-ModelFromEnvironment }
|
||||
$modelShort = Get-ShortModel $displayModel
|
||||
$content = Truncate (([string]$prompt.content) -replace '[\r\n]+', ' ') 30
|
||||
$line = "$glyph $status"
|
||||
if ($modelShort) { $line += " $modelShort" }
|
||||
if ($content) { $line += " $content" }
|
||||
Write-StatusLine $line
|
||||
} catch {
|
||||
Write-Err "status_failed: $_" 'status'
|
||||
Write-StatusLine '· ?'
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
|
||||
$mutexName = 'Global\audit-' + ([System.IO.Path]::GetFullPath($LogFile).ToLowerInvariant() -replace '[^a-z0-9]', '_')
|
||||
$mutex = $null
|
||||
$acquired = $false
|
||||
try {
|
||||
$mutex = [System.Threading.Mutex]::new($false, $mutexName)
|
||||
try {
|
||||
$acquired = $mutex.WaitOne($MutexTimeoutMs)
|
||||
} catch [System.Threading.AbandonedMutexException] {
|
||||
$acquired = $true
|
||||
}
|
||||
} catch {
|
||||
Write-Err "mutex_wait_failed: $_" 'mutex'
|
||||
}
|
||||
|
||||
if (-not $acquired) {
|
||||
Write-Err "mutex_timeout (${MutexTimeoutMs}ms) event=$EventType" 'mutex'
|
||||
if ($mutex) { try { $mutex.Dispose() } catch { } }
|
||||
exit 0
|
||||
}
|
||||
|
||||
try {
|
||||
$sessions = [System.Collections.Generic.List[object]]::new()
|
||||
foreach ($s in @(Read-Log)) { $sessions.Add($s) }
|
||||
Convert-PromptLists -Sessions @($sessions)
|
||||
|
||||
$sid = if ($Payload) { [string](Get-PropertyValue $Payload 'session_id') } else { $null }
|
||||
if ([string]::IsNullOrWhiteSpace($sid)) { $sid = [guid]::NewGuid().ToString() }
|
||||
|
||||
$currentSession = Find-Session -Sessions @($sessions) -Sid $sid
|
||||
if (-not $currentSession -and $EventType -eq 'UserPromptSubmit') {
|
||||
$project = if ($Payload) { [string](Get-PropertyValue $Payload 'cwd') } else { $null }
|
||||
$currentSession = [pscustomobject][ordered]@{
|
||||
project = $project
|
||||
session_id = $sid
|
||||
prompts = [System.Collections.Generic.List[object]]::new()
|
||||
}
|
||||
$sessions.Add($currentSession)
|
||||
}
|
||||
|
||||
$changed = $false
|
||||
$now = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
|
||||
switch ($EventType) {
|
||||
'UserPromptSubmit' {
|
||||
$content = ''
|
||||
if ($Payload) {
|
||||
foreach ($key in @('prompt', 'user_prompt', 'message')) {
|
||||
$value = Get-PropertyValue $Payload $key
|
||||
if ($null -ne $value) { $content = [string]$value; break }
|
||||
}
|
||||
}
|
||||
$content = Truncate $content 500
|
||||
|
||||
foreach ($prompt in @($currentSession.prompts)) {
|
||||
if (Stop-Prompt $prompt $now) { $changed = $true }
|
||||
}
|
||||
|
||||
$model = Get-ModelFromPayloadOrTranscript $Payload
|
||||
|
||||
$currentSession.prompts.Add([pscustomobject][ordered]@{
|
||||
content = $content
|
||||
model = $model
|
||||
start_time = $now
|
||||
end_time = ''
|
||||
duration = $null
|
||||
status = $StatusRunning
|
||||
})
|
||||
$changed = $true
|
||||
}
|
||||
|
||||
'PreToolUse' {
|
||||
$prompt = Find-LastInProgress -Sessions @($sessions) -Sid $sid
|
||||
if ($prompt) {
|
||||
$toolName = if ($Payload) { [string](Get-PropertyValue $Payload 'tool_name') } else { '' }
|
||||
if ($toolName -eq 'AskUserQuestion') {
|
||||
if ([string]$prompt.status -ne $StatusAwaitingUser) {
|
||||
$prompt.status = $StatusAwaitingUser
|
||||
$changed = $true
|
||||
}
|
||||
} elseif ([string]$prompt.status -eq $StatusAwaitingUser) {
|
||||
$prompt.status = $StatusRunning
|
||||
$changed = $true
|
||||
}
|
||||
if (Update-PromptModel $prompt $Payload) { $changed = $true }
|
||||
}
|
||||
}
|
||||
|
||||
'PermissionRequest' {
|
||||
$prompt = Find-LastInProgress -Sessions @($sessions) -Sid $sid
|
||||
if ($prompt) {
|
||||
if ([string]$prompt.status -ne $StatusAwaitingUser) {
|
||||
$prompt.status = $StatusAwaitingUser
|
||||
$changed = $true
|
||||
}
|
||||
if (Update-PromptModel $prompt $Payload) { $changed = $true }
|
||||
}
|
||||
}
|
||||
|
||||
'PostToolUse' {
|
||||
$prompt = Find-LastInProgress -Sessions @($sessions) -Sid $sid
|
||||
if ($prompt) {
|
||||
if ([string]$prompt.status -eq $StatusAwaitingUser) {
|
||||
$prompt.status = $StatusRunning
|
||||
$changed = $true
|
||||
}
|
||||
if (Update-PromptModel $prompt $Payload) { $changed = $true }
|
||||
}
|
||||
}
|
||||
|
||||
'Stop' {
|
||||
$prompt = Find-LastInProgress -Sessions @($sessions) -Sid $sid
|
||||
if ($prompt) {
|
||||
if (Update-PromptModel $prompt $Payload) { $changed = $true }
|
||||
if (Complete-Prompt $prompt $now) { $changed = $true }
|
||||
}
|
||||
}
|
||||
|
||||
'SessionEnd' {
|
||||
$prompt = Find-LastInProgress -Sessions @($sessions) -Sid $sid
|
||||
if ($prompt) {
|
||||
if (Update-PromptModel $prompt $Payload) { $changed = $true }
|
||||
if (Stop-Prompt $prompt $now) { $changed = $true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($changed) { [void](Write-Log -Sessions @($sessions)) }
|
||||
} catch {
|
||||
Write-Err "handler_failed: $_" 'handler'
|
||||
} finally {
|
||||
if ($acquired -and $mutex) { try { $mutex.ReleaseMutex() | Out-Null } catch { } }
|
||||
if ($mutex) { try { $mutex.Dispose() } catch { } }
|
||||
}
|
||||
|
||||
exit 0
|
||||
74
2026.06.23_claude_code_hooks/settings.json
Normal file
74
2026.06.23_claude_code_hooks/settings.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"hooks": {
|
||||
"SessionEnd": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"d:/claude/hooks/audit.ps1\" SessionEnd",
|
||||
"timeout": 15
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"d:/claude/hooks/audit.ps1\" UserPromptSubmit",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"d:/claude/hooks/audit.ps1\" PreToolUse",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"d:/claude/hooks/audit.ps1\" PostToolUse",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PermissionRequest": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"d:/claude/hooks/audit.ps1\" PermissionRequest",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"d:/claude/hooks/audit.ps1\" Stop",
|
||||
"timeout": 15
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user