# Hook dispatcher: select the PowerShell or Bash implementation from local-env.json. param([string]$HookName) $utf8 = New-Object System.Text.UTF8Encoding($false) [Console]::InputEncoding = $utf8 [Console]::OutputEncoding = $utf8 function Write-EmptyHookResult { [Console]::Out.WriteLine('{}') } $inputPayload = [Console]::In.ReadToEnd() try { if ([string]::IsNullOrWhiteSpace($HookName) -or $HookName -notmatch '^[A-Za-z0-9][A-Za-z0-9._-]*$') { throw "Invalid hook name." } $cursorDir = Split-Path -Parent $PSScriptRoot $repoRoot = Split-Path -Parent $cursorDir $localEnvPath = Join-Path $cursorDir 'local-env.json' $shell = 'powershell' if (Test-Path -LiteralPath $localEnvPath -PathType Leaf) { try { $config = Get-Content -LiteralPath $localEnvPath -Raw -Encoding UTF8 | ConvertFrom-Json if ($config.shell -is [string] -and -not [string]::IsNullOrWhiteSpace($config.shell)) { $shell = $config.shell.Trim().ToLowerInvariant() } } catch { [Console]::Error.WriteLine("[run-hook] Ignoring invalid local-env.json: {0}", $_.Exception.Message) } } $processInfo = New-Object System.Diagnostics.ProcessStartInfo $processInfo.UseShellExecute = $false $processInfo.CreateNoWindow = $true $processInfo.RedirectStandardInput = $true $processInfo.RedirectStandardOutput = $true $processInfo.RedirectStandardError = $true $processInfo.WorkingDirectory = $repoRoot if ($processInfo.PSObject.Properties.Name -contains 'StandardInputEncoding') { $processInfo.StandardInputEncoding = $utf8 $processInfo.StandardOutputEncoding = $utf8 $processInfo.StandardErrorEncoding = $utf8 } switch ($shell) { { $_ -in @('powershell', 'powershell.exe', 'pwsh', 'pwsh.exe') } { $scriptPath = Join-Path $PSScriptRoot "$HookName.ps1" if (-not (Test-Path -LiteralPath $scriptPath -PathType Leaf)) { throw "PowerShell hook not found: $HookName" } $processInfo.FileName = if ($shell -in @('pwsh', 'pwsh.exe')) { 'pwsh' } else { 'powershell' } $processInfo.Arguments = "-NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$scriptPath`"" break } { $_ -in @('bash', 'bash.exe', 'zsh', 'sh') } { $scriptPath = (Join-Path $PSScriptRoot "$HookName.sh") -replace '\\', '/' if (-not (Test-Path -LiteralPath $scriptPath -PathType Leaf)) { throw "Shell hook not found: $HookName" } $processInfo.FileName = 'bash' $processInfo.Arguments = "`"$scriptPath`"" break } default { throw "Unsupported shell '$shell'." } } $process = New-Object System.Diagnostics.Process $process.StartInfo = $processInfo if (-not $process.Start()) { throw "Unable to start hook process." } $stdoutTask = $process.StandardOutput.ReadToEndAsync() $stderrTask = $process.StandardError.ReadToEndAsync() $process.StandardInput.Write($inputPayload) $process.StandardInput.Close() $process.WaitForExit() $stdout = $stdoutTask.Result $stderr = $stderrTask.Result if (-not [string]::IsNullOrWhiteSpace($stderr)) { [Console]::Error.WriteLine($stderr.TrimEnd()) } if ($process.ExitCode -ne 0) { throw "Hook exited with code $($process.ExitCode)." } $trimmed = $stdout.Trim() if ([string]::IsNullOrWhiteSpace($trimmed) -or -not $trimmed.StartsWith('{')) { throw "Hook returned an empty or non-object response." } $result = $trimmed | ConvertFrom-Json $normalized = $result | ConvertTo-Json -Compress -Depth 20 if ([string]::IsNullOrWhiteSpace($normalized) -or -not $normalized.StartsWith('{')) { throw "Hook response is not a JSON object." } [Console]::Out.WriteLine($normalized) } catch { [Console]::Error.WriteLine("[run-hook] {0}", $_.Exception.Message) Write-EmptyHookResult } exit 0