feat(cursor): Add Windows PowerShell support for Cursor hooks
Complete Windows parity with bash scripts: - Create 7 PowerShell scripts mirroring bash functionality - Update installer to detect platform and install appropriate scripts - Generate platform-specific hooks.json with PowerShell invocation - Add enterprise support for Windows (ProgramData/Cursor) - Update findCursorHooksDir to check for both .sh and .ps1 - Add comprehensive Windows documentation to STANDALONE-SETUP.md Scripts added: common.ps1, session-init.ps1, context-inject.ps1, save-observation.ps1, save-file-edit.ps1, session-summary.ps1, user-message.ps1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
# Common utility functions for Cursor hooks (PowerShell)
|
||||
# Dot-source this file in hook scripts: . "$PSScriptRoot\common.ps1"
|
||||
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
# 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user