Files
claude-mem/cursor-hooks/common.ps1
T

193 lines
4.8 KiB
PowerShell

# Common utility functions for Cursor hooks (PowerShell)
# Dot-source this file in hook scripts: . "$PSScriptRoot\common.ps1"
# Note: ErrorActionPreference should be set in each script, not globally here
# Get worker port from settings with validation
function Get-WorkerPort {
$settingsPath = Join-Path $env:USERPROFILE ".claude-mem\settings.json"
$port = 37777
if (Test-Path $settingsPath) {
try {
$settings = Get-Content $settingsPath -Raw | ConvertFrom-Json
if ($settings.CLAUDE_MEM_WORKER_PORT) {
$parsedPort = [int]$settings.CLAUDE_MEM_WORKER_PORT
if ($parsedPort -ge 1 -and $parsedPort -le 65535) {
$port = $parsedPort
}
}
} catch {
# Ignore parse errors, use default
}
}
return $port
}
# Ensure worker is running with retries
function Test-WorkerReady {
param(
[int]$Port = 37777,
[int]$MaxRetries = 75
)
for ($i = 0; $i -lt $MaxRetries; $i++) {
try {
$response = Invoke-RestMethod -Uri "http://127.0.0.1:$Port/api/readiness" -Method Get -TimeoutSec 1 -ErrorAction Stop
return $true
} catch {
Start-Sleep -Milliseconds 200
}
}
return $false
}
# Get project name from workspace root
function Get-ProjectName {
param([string]$WorkspaceRoot)
if ([string]::IsNullOrEmpty($WorkspaceRoot)) {
return "unknown-project"
}
# Handle Windows drive root (e.g., "C:\")
if ($WorkspaceRoot -match '^([A-Za-z]):\\?$') {
return "drive-$($Matches[1].ToUpper())"
}
$projectName = Split-Path $WorkspaceRoot -Leaf
if ([string]::IsNullOrEmpty($projectName)) {
return "unknown-project"
}
return $projectName
}
# URL encode a string
function Get-UrlEncodedString {
param([string]$String)
if ([string]::IsNullOrEmpty($String)) {
return ""
}
return [System.Uri]::EscapeDataString($String)
}
# Check if string is empty or null
function Test-IsEmpty {
param([string]$String)
return [string]::IsNullOrEmpty($String) -or $String -eq "null" -or $String -eq "empty"
}
# Safely read JSON from stdin with error handling
function Read-JsonInput {
try {
$input = [Console]::In.ReadToEnd()
if ([string]::IsNullOrEmpty($input)) {
return @{}
}
return $input | ConvertFrom-Json -ErrorAction Stop
} catch {
return @{}
}
}
# Safely get JSON field with fallback
function Get-JsonField {
param(
[PSObject]$Json,
[string]$Field,
[string]$Fallback = ""
)
if ($null -eq $Json) {
return $Fallback
}
# Handle array access syntax (e.g., "workspace_roots[0]")
if ($Field -match '^(.+)\[(\d+)\]$') {
$arrayField = $Matches[1]
$index = [int]$Matches[2]
if ($Json.PSObject.Properties.Name -contains $arrayField) {
$array = $Json.$arrayField
if ($null -ne $array -and $array.Count -gt $index) {
$value = $array[$index]
if (-not (Test-IsEmpty $value)) {
return $value
}
}
}
return $Fallback
}
# Simple field access
if ($Json.PSObject.Properties.Name -contains $Field) {
$value = $Json.$Field
if (-not (Test-IsEmpty $value)) {
return $value
}
}
return $Fallback
}
# Convert object to JSON string (compact)
function ConvertTo-JsonCompact {
param([object]$Object)
return $Object | ConvertTo-Json -Compress -Depth 10
}
# Send HTTP POST request (fire-and-forget style)
function Send-HttpPostAsync {
param(
[string]$Uri,
[object]$Body
)
try {
$bodyJson = ConvertTo-JsonCompact $Body
Start-Job -ScriptBlock {
param($u, $b)
try {
Invoke-RestMethod -Uri $u -Method Post -Body $b -ContentType "application/json" -TimeoutSec 5 -ErrorAction SilentlyContinue | Out-Null
} catch {}
} -ArgumentList $Uri, $bodyJson | Out-Null
} catch {
# Ignore errors - fire and forget
}
}
# Send HTTP POST request (synchronous)
function Send-HttpPost {
param(
[string]$Uri,
[object]$Body
)
try {
$bodyJson = ConvertTo-JsonCompact $Body
Invoke-RestMethod -Uri $u -Method Post -Body $bodyJson -ContentType "application/json" -TimeoutSec 5 -ErrorAction SilentlyContinue | Out-Null
} catch {
# Ignore errors
}
}
# Get HTTP response
function Get-HttpResponse {
param(
[string]$Uri,
[int]$TimeoutSec = 5
)
try {
return Invoke-RestMethod -Uri $Uri -Method Get -TimeoutSec $TimeoutSec -ErrorAction Stop
} catch {
return $null
}
}