@highflame/overwatch-v2 2.0.0-internal.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/bin/overwatch +12 -0
- package/dist/bin/overwatch +12 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +6295 -0
- package/dist/daemon.d.ts +10 -0
- package/dist/daemon.js +3578 -0
- package/dist/hooks/claudecode/hooks.json.template +20 -0
- package/dist/hooks/cursor/hooks.json.template +62 -0
- package/dist/hooks/universal-hook.ps1 +118 -0
- package/dist/hooks/universal-hook.sh +60 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +3429 -0
- package/dist/installer.d.ts +30 -0
- package/dist/module.d.ts +55 -0
- package/dist/scanner.d.ts +79 -0
- package/dist/version.d.ts +4 -0
- package/lib/platform-loader.js +234 -0
- package/package.json +75 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"UserPromptSubmit": [
|
|
3
|
+
{
|
|
4
|
+
"command": "~/.overwatch/universal-hook.sh claudecode UserPromptSubmit",
|
|
5
|
+
"timeout": 10000
|
|
6
|
+
}
|
|
7
|
+
],
|
|
8
|
+
"PreToolUse": [
|
|
9
|
+
{
|
|
10
|
+
"command": "~/.overwatch/universal-hook.sh claudecode PreToolUse",
|
|
11
|
+
"timeout": 10000
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"PostToolUse": [
|
|
15
|
+
{
|
|
16
|
+
"command": "~/.overwatch/universal-hook.sh claudecode PostToolUse",
|
|
17
|
+
"timeout": 5000
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"beforeSubmitPrompt": [
|
|
3
|
+
{
|
|
4
|
+
"command": "~/.overwatch/universal-hook.sh cursor beforeSubmitPrompt",
|
|
5
|
+
"timeout": 10000
|
|
6
|
+
}
|
|
7
|
+
],
|
|
8
|
+
"beforeShellExecution": [
|
|
9
|
+
{
|
|
10
|
+
"command": "~/.overwatch/universal-hook.sh cursor beforeShellExecution",
|
|
11
|
+
"timeout": 10000
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"beforeMCPExecution": [
|
|
15
|
+
{
|
|
16
|
+
"command": "~/.overwatch/universal-hook.sh cursor beforeMCPExecution",
|
|
17
|
+
"timeout": 10000
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"beforeReadFile": [
|
|
21
|
+
{
|
|
22
|
+
"command": "~/.overwatch/universal-hook.sh cursor beforeReadFile",
|
|
23
|
+
"timeout": 10000
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"afterShellExecution": [
|
|
27
|
+
{
|
|
28
|
+
"command": "~/.overwatch/universal-hook.sh cursor afterShellExecution",
|
|
29
|
+
"timeout": 5000
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"afterMCPExecution": [
|
|
33
|
+
{
|
|
34
|
+
"command": "~/.overwatch/universal-hook.sh cursor afterMCPExecution",
|
|
35
|
+
"timeout": 5000
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"afterFileEdit": [
|
|
39
|
+
{
|
|
40
|
+
"command": "~/.overwatch/universal-hook.sh cursor afterFileEdit",
|
|
41
|
+
"timeout": 5000
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"afterAgentResponse": [
|
|
45
|
+
{
|
|
46
|
+
"command": "~/.overwatch/universal-hook.sh cursor afterAgentResponse",
|
|
47
|
+
"timeout": 5000
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"afterAgentThought": [
|
|
51
|
+
{
|
|
52
|
+
"command": "~/.overwatch/universal-hook.sh cursor afterAgentThought",
|
|
53
|
+
"timeout": 5000
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"stop": [
|
|
57
|
+
{
|
|
58
|
+
"command": "~/.overwatch/universal-hook.sh cursor stop",
|
|
59
|
+
"timeout": 5000
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Overwatch Universal Hook Adapter (PowerShell)
|
|
2
|
+
# This script handles communication between AI tools and the Guardian daemon.
|
|
3
|
+
# Usage: pwsh -NoProfile -File universal-hook.ps1 <source> <event> [default_json]
|
|
4
|
+
#
|
|
5
|
+
# Input: JSON payload via stdin
|
|
6
|
+
# Output: JSON response to stdout
|
|
7
|
+
|
|
8
|
+
param(
|
|
9
|
+
[Parameter(Mandatory=$true, Position=0)]
|
|
10
|
+
[string]$IdeSource,
|
|
11
|
+
|
|
12
|
+
[Parameter(Mandatory=$true, Position=1)]
|
|
13
|
+
[string]$HookEvent,
|
|
14
|
+
|
|
15
|
+
[Parameter(Position=2)]
|
|
16
|
+
[string]$DefaultResponse = "{}"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
$ErrorActionPreference = "SilentlyContinue"
|
|
20
|
+
|
|
21
|
+
function Try-AutoStartDaemon {
|
|
22
|
+
$lockDir = Join-Path $env:USERPROFILE ".overwatch" "daemon.lock.d"
|
|
23
|
+
|
|
24
|
+
# Check if overwatch is on PATH
|
|
25
|
+
$overwatch = Get-Command "overwatch" -ErrorAction SilentlyContinue
|
|
26
|
+
if (-not $overwatch) { return }
|
|
27
|
+
|
|
28
|
+
# Clear stale lock first so we can start the daemon in this invocation
|
|
29
|
+
if (Test-Path $lockDir) {
|
|
30
|
+
$lockAge = (Get-Date) - (Get-Item $lockDir).LastWriteTime
|
|
31
|
+
if ($lockAge.TotalSeconds -gt 30) {
|
|
32
|
+
Remove-Item $lockDir -Force -ErrorAction SilentlyContinue
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (-not (Test-Path $lockDir)) {
|
|
37
|
+
# Create lock directory atomically
|
|
38
|
+
try {
|
|
39
|
+
[System.IO.Directory]::CreateDirectory($lockDir) | Out-Null
|
|
40
|
+
# Fire-and-forget: start daemon in background
|
|
41
|
+
Start-Process -FilePath "overwatch" -ArgumentList "start","--hook" `
|
|
42
|
+
-WindowStyle Hidden -PassThru | Out-Null
|
|
43
|
+
} catch {
|
|
44
|
+
# Another process grabbed the lock — ignore
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# 1. Read guardian port from discovery file, default to 17580
|
|
50
|
+
$portFile = Join-Path $env:USERPROFILE ".overwatch" "guardian_port"
|
|
51
|
+
$guardianPort = 17580
|
|
52
|
+
if (Test-Path $portFile) {
|
|
53
|
+
$portStr = (Get-Content $portFile -Raw -ErrorAction SilentlyContinue).Trim()
|
|
54
|
+
$parsed = 0
|
|
55
|
+
if ([int]::TryParse($portStr, [ref]$parsed)) {
|
|
56
|
+
$guardianPort = $parsed
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
$baseUrl = "http://127.0.0.1:$guardianPort"
|
|
61
|
+
$endpoint = "$baseUrl/hook/$IdeSource/$HookEvent"
|
|
62
|
+
|
|
63
|
+
# 2. Read payload from stdin with timeout
|
|
64
|
+
# Use OpenStandardInput() instead of [Console]::In — PowerShell's host can
|
|
65
|
+
# redirect Console.In when launched with -File, causing piped stdin to be lost.
|
|
66
|
+
# OpenStandardInput() bypasses the host and reads the raw OS stdin stream.
|
|
67
|
+
# Wrap in a Task with 5s timeout to prevent blocking the IDE if the pipe isn't closed.
|
|
68
|
+
$payload = ""
|
|
69
|
+
try {
|
|
70
|
+
$stream = [System.Console]::OpenStandardInput()
|
|
71
|
+
$reader = [System.IO.StreamReader]::new($stream, [System.Text.Encoding]::UTF8)
|
|
72
|
+
$task = $reader.ReadToEndAsync()
|
|
73
|
+
if ($task.Wait(5000)) {
|
|
74
|
+
$payload = $task.Result
|
|
75
|
+
}
|
|
76
|
+
$reader.Dispose()
|
|
77
|
+
} catch {
|
|
78
|
+
$payload = ""
|
|
79
|
+
}
|
|
80
|
+
if ([string]::IsNullOrWhiteSpace($payload)) {
|
|
81
|
+
$payload = "{}"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# 3. Cross-IDE dedup: skip if Cursor is firing a non-cursor hook
|
|
85
|
+
if ($IdeSource -ne "cursor" -and $payload -match '"cursor_version"') {
|
|
86
|
+
Write-Output $DefaultResponse
|
|
87
|
+
exit 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# 4. Health check & forward payload
|
|
91
|
+
try {
|
|
92
|
+
$healthResponse = Invoke-WebRequest -Uri "$baseUrl/health" `
|
|
93
|
+
-Method GET -TimeoutSec 1 -UseBasicParsing -ErrorAction Stop
|
|
94
|
+
$healthy = $healthResponse.StatusCode -eq 200
|
|
95
|
+
} catch {
|
|
96
|
+
$healthy = $false
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
$responseBody = $DefaultResponse
|
|
100
|
+
if ($healthy) {
|
|
101
|
+
try {
|
|
102
|
+
$response = Invoke-WebRequest -Uri $endpoint `
|
|
103
|
+
-Method POST `
|
|
104
|
+
-Body $payload `
|
|
105
|
+
-ContentType "application/json" `
|
|
106
|
+
-TimeoutSec 10 `
|
|
107
|
+
-UseBasicParsing `
|
|
108
|
+
-ErrorAction Stop
|
|
109
|
+
$responseBody = $response.Content
|
|
110
|
+
} catch {
|
|
111
|
+
# keep default
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
Try-AutoStartDaemon
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# 5. Pass-through: every supported agent reads stdout JSON.
|
|
118
|
+
Write-Output $responseBody
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Overwatch Universal Hook Adapter
|
|
3
|
+
# This script handles communication between AI tools and the Guardian daemon.
|
|
4
|
+
# Usage: ./universal-hook.sh <source> <event> <default_json>
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
IDE_SOURCE=$1
|
|
9
|
+
HOOK_EVENT=$2
|
|
10
|
+
DEFAULT_RESPONSE=${3:-"{}"}
|
|
11
|
+
|
|
12
|
+
# Auto-start daemon in background if not running (fire-and-forget)
|
|
13
|
+
try_auto_start() {
|
|
14
|
+
local LOCK_DIR="$HOME/.overwatch/daemon.lock.d"
|
|
15
|
+
if command -v overwatch >/dev/null 2>&1; then
|
|
16
|
+
# Use mkdir as an atomic lock — only one process can succeed
|
|
17
|
+
if mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
18
|
+
nohup overwatch start --hook >/dev/null 2>&1 &
|
|
19
|
+
else
|
|
20
|
+
# Lock exists — check if it's stale (older than 30s)
|
|
21
|
+
local LOCK_AGE=$(( $(date +%s) - $(stat -f %m "$LOCK_DIR" 2>/dev/null || stat -c %Y "$LOCK_DIR" 2>/dev/null || echo 0) ))
|
|
22
|
+
if [ "$LOCK_AGE" -gt 30 ]; then
|
|
23
|
+
rmdir "$LOCK_DIR" 2>/dev/null || true
|
|
24
|
+
fi
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Read guardian port from discovery file, default to static port 17580
|
|
30
|
+
GUARDIAN_PORT_FILE="$HOME/.overwatch/guardian_port"
|
|
31
|
+
GUARDIAN_PORT=$(cat "$GUARDIAN_PORT_FILE" 2>/dev/null || echo "17580")
|
|
32
|
+
ENDPOINT="http://127.0.0.1:${GUARDIAN_PORT}/hook/${IDE_SOURCE}/${HOOK_EVENT}"
|
|
33
|
+
|
|
34
|
+
# 2. Read payload from stdin (needed for dedup check and forwarding)
|
|
35
|
+
PAYLOAD=$(cat)
|
|
36
|
+
|
|
37
|
+
# 3. Cross-IDE dedup: skip if Cursor is firing a non-cursor hook.
|
|
38
|
+
# Cursor picks up hooks from other IDEs (e.g. ~/.claude/settings.json) and fires them,
|
|
39
|
+
# causing duplicate processing. Detect this via cursor_version in the payload.
|
|
40
|
+
if [ "$IDE_SOURCE" != "cursor" ] && echo "$PAYLOAD" | grep -q '"cursor_version"'; then
|
|
41
|
+
echo "$DEFAULT_RESPONSE"
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# 4. Health Check & Forward Payload
|
|
46
|
+
# We use a 1s timeout for the health check to avoid hanging if the daemon is dead.
|
|
47
|
+
if curl -s -f -m 1 "http://127.0.0.1:${GUARDIAN_PORT}/health" >/dev/null 2>&1; then
|
|
48
|
+
# Forward payload to daemon and capture response
|
|
49
|
+
# Use a 10s timeout for processing
|
|
50
|
+
RESPONSE=$(echo "$PAYLOAD" | curl -s -m 10 -X POST "$ENDPOINT" \
|
|
51
|
+
-H "Content-Type: application/json" \
|
|
52
|
+
-d @- 2>/dev/null || echo "$DEFAULT_RESPONSE")
|
|
53
|
+
else
|
|
54
|
+
# Daemon is unreachable — try to auto-start it
|
|
55
|
+
try_auto_start
|
|
56
|
+
RESPONSE="$DEFAULT_RESPONSE"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# 5. Pass-through: every supported agent reads stdout JSON.
|
|
60
|
+
echo "$RESPONSE"
|