a82d1a24b9
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>
127 lines
3.3 KiB
PowerShell
127 lines
3.3 KiB
PowerShell
# Save Observation Hook for Cursor (PowerShell)
|
|
# Captures MCP tool usage and shell command execution
|
|
# Maps to claude-mem's save-hook functionality
|
|
|
|
$ErrorActionPreference = "SilentlyContinue"
|
|
|
|
# Source common utilities
|
|
$commonPath = Join-Path $PSScriptRoot "common.ps1"
|
|
if (Test-Path $commonPath) {
|
|
. $commonPath
|
|
} else {
|
|
Write-Warning "common.ps1 not found, using fallback functions"
|
|
exit 0
|
|
}
|
|
|
|
# Read JSON input from stdin with error handling
|
|
$input = Read-JsonInput
|
|
|
|
# Extract common fields with safe fallbacks
|
|
$conversationId = Get-JsonField $input "conversation_id" ""
|
|
$generationId = Get-JsonField $input "generation_id" ""
|
|
$workspaceRoot = Get-JsonField $input "workspace_roots[0]" ""
|
|
|
|
# Fallback to current directory if no workspace root
|
|
if (Test-IsEmpty $workspaceRoot) {
|
|
$workspaceRoot = Get-Location
|
|
}
|
|
|
|
# Use conversation_id as session_id (stable across turns), fallback to generation_id
|
|
$sessionId = $conversationId
|
|
if (Test-IsEmpty $sessionId) {
|
|
$sessionId = $generationId
|
|
}
|
|
|
|
# Exit if no session_id available
|
|
if (Test-IsEmpty $sessionId) {
|
|
exit 0
|
|
}
|
|
|
|
# Get worker port from settings with validation
|
|
$workerPort = Get-WorkerPort
|
|
|
|
# Determine hook type and extract relevant data
|
|
$hookEvent = Get-JsonField $input "hook_event_name" ""
|
|
|
|
$payload = $null
|
|
|
|
if ($hookEvent -eq "afterMCPExecution") {
|
|
# MCP tool execution
|
|
$toolName = Get-JsonField $input "tool_name" ""
|
|
|
|
if (Test-IsEmpty $toolName) {
|
|
exit 0
|
|
}
|
|
|
|
# Extract tool_input and tool_response, defaulting to {} if invalid
|
|
$toolInput = @{}
|
|
$toolResponse = @{}
|
|
|
|
if ($input.PSObject.Properties.Name -contains "tool_input") {
|
|
$toolInput = $input.tool_input
|
|
if ($null -eq $toolInput) { $toolInput = @{} }
|
|
}
|
|
|
|
if ($input.PSObject.Properties.Name -contains "result_json") {
|
|
$toolResponse = $input.result_json
|
|
if ($null -eq $toolResponse) { $toolResponse = @{} }
|
|
}
|
|
|
|
# Prepare observation payload
|
|
$payload = @{
|
|
contentSessionId = $sessionId
|
|
tool_name = $toolName
|
|
tool_input = $toolInput
|
|
tool_response = $toolResponse
|
|
cwd = $workspaceRoot
|
|
}
|
|
|
|
} elseif ($hookEvent -eq "afterShellExecution") {
|
|
# Shell command execution
|
|
$command = Get-JsonField $input "command" ""
|
|
|
|
if (Test-IsEmpty $command) {
|
|
exit 0
|
|
}
|
|
|
|
$output = Get-JsonField $input "output" ""
|
|
|
|
# Treat shell commands as "Bash" tool usage
|
|
$toolInput = @{ command = $command }
|
|
$toolResponse = @{ output = $output }
|
|
|
|
$payload = @{
|
|
contentSessionId = $sessionId
|
|
tool_name = "Bash"
|
|
tool_input = $toolInput
|
|
tool_response = $toolResponse
|
|
cwd = $workspaceRoot
|
|
}
|
|
|
|
} else {
|
|
exit 0
|
|
}
|
|
|
|
# Exit if payload creation failed
|
|
if ($null -eq $payload) {
|
|
exit 0
|
|
}
|
|
|
|
# Ensure worker is running (with retries like claude-mem hooks)
|
|
if (-not (Test-WorkerReady -Port $workerPort)) {
|
|
# Worker not ready - exit gracefully (don't block Cursor)
|
|
exit 0
|
|
}
|
|
|
|
# Send observation to claude-mem worker (fire-and-forget)
|
|
$uri = "http://127.0.0.1:$workerPort/api/sessions/observations"
|
|
|
|
try {
|
|
$bodyJson = ConvertTo-JsonCompact $payload
|
|
Invoke-RestMethod -Uri $uri -Method Post -Body $bodyJson -ContentType "application/json" -TimeoutSec 5 -ErrorAction SilentlyContinue | Out-Null
|
|
} catch {
|
|
# Ignore errors - don't block Cursor
|
|
}
|
|
|
|
exit 0
|