sovr-mcp-proxy 6.0.1 → 7.0.0

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.
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @sovr/proxy-mcp — Claude Code Hooks Adapter
3
+ *
4
+ * Solves the bypass problem for Claude Code users specifically.
5
+ * Claude Code supports "Hooks" — scripts that run before/after tool use.
6
+ * PreToolUse hooks can BLOCK tool calls before they execute.
7
+ *
8
+ * This adapter generates the correct hooks configuration for Claude Code
9
+ * that routes ALL tool calls through SOVR for policy evaluation.
10
+ *
11
+ * Unlike the MCP proxy approach, hooks are ENFORCED by Claude Code itself —
12
+ * the LLM cannot bypass them because they run at the platform level.
13
+ *
14
+ * Integration:
15
+ * npx sovr-mcp-proxy init --claude-code
16
+ * → Writes hooks config to ~/.claude/settings.json
17
+ * → Installs sovr-hook.sh as the PreToolUse handler
18
+ *
19
+ * Hook flow:
20
+ * 1. LLM decides to call a tool (e.g., Bash with "rm -rf /")
21
+ * 2. Claude Code triggers PreToolUse hook BEFORE execution
22
+ * 3. sovr-hook.sh calls SOVR daemon/API for policy evaluation
23
+ * 4. Hook exits 0 (allow) or 2 (block) based on SOVR decision
24
+ * 5. Claude Code either proceeds or blocks the tool call
25
+ *
26
+ * Exit codes (Claude Code hook spec):
27
+ * 0 = Allow (proceed with tool call)
28
+ * 2 = Block (reject tool call, show reason to user)
29
+ * Other = Error (Claude Code decides based on its own policy)
30
+ *
31
+ * @see https://docs.anthropic.com/en/docs/claude-code/hooks
32
+ */
33
+ /** Claude Code hook event types */
34
+ type HookEvent = 'PreToolUse' | 'PostToolUse' | 'Notification' | 'Stop' | 'SubagentStop';
35
+ /** A single hook matcher */
36
+ interface HookMatcher {
37
+ /** Tool name pattern (regex supported) */
38
+ tool_name?: string;
39
+ /** Notification type pattern */
40
+ type?: string;
41
+ }
42
+ /** A single hook definition */
43
+ interface HookDef {
44
+ /** Matchers that determine when this hook fires */
45
+ matcher: HookMatcher;
46
+ /** Array of hook commands to execute */
47
+ hooks: Array<{
48
+ /** The command to run */
49
+ command: string;
50
+ /** Timeout in seconds */
51
+ timeout?: number;
52
+ }>;
53
+ }
54
+ /** Claude Code settings.json structure (partial) */
55
+ interface ClaudeCodeSettings {
56
+ hooks?: Record<HookEvent, HookDef[]>;
57
+ [key: string]: unknown;
58
+ }
59
+ /** Configuration for the hooks adapter */
60
+ interface HooksAdapterConfig {
61
+ /** SOVR API key */
62
+ apiKey?: string;
63
+ /** SOVR endpoint (default: daemon socket) */
64
+ endpoint?: string;
65
+ /** Fail mode: 'open' allows on error, 'closed' blocks on error */
66
+ failMode?: 'open' | 'closed';
67
+ /** Tools to intercept (regex patterns) */
68
+ toolPatterns?: string[];
69
+ /** Timeout for hook execution in seconds */
70
+ hookTimeout?: number;
71
+ /** Path to Claude Code settings (auto-detected if not provided) */
72
+ settingsPath?: string;
73
+ /** Whether to also add PostToolUse audit hook */
74
+ addAuditHook?: boolean;
75
+ }
76
+ /**
77
+ * Generate the sovr-hook.sh script content.
78
+ * This script is called by Claude Code for every PreToolUse event.
79
+ *
80
+ * It reads the hook input from stdin (JSON with tool_name, tool_input),
81
+ * calls SOVR for evaluation, and exits with the appropriate code.
82
+ */
83
+ declare function generateHookScript(config: HooksAdapterConfig): string;
84
+ /**
85
+ * Generate the PostToolUse audit hook script.
86
+ * This records what happened after a tool call completes.
87
+ */
88
+ declare function generateAuditHookScript(config: HooksAdapterConfig): string;
89
+ /**
90
+ * Generate the Claude Code hooks configuration object.
91
+ */
92
+ declare function generateHooksConfig(config: HooksAdapterConfig): Record<HookEvent, HookDef[]>;
93
+ /**
94
+ * Install SOVR hooks into Claude Code settings.
95
+ * Merges with existing settings, preserving non-SOVR hooks.
96
+ */
97
+ declare function installHooks(config: HooksAdapterConfig): {
98
+ settingsPath: string;
99
+ hookScriptPath: string;
100
+ auditScriptPath?: string;
101
+ installed: boolean;
102
+ backup?: string;
103
+ };
104
+ /**
105
+ * Remove SOVR hooks from Claude Code settings.
106
+ */
107
+ declare function uninstallHooks(config?: {
108
+ settingsPath?: string;
109
+ }): {
110
+ removed: boolean;
111
+ settingsPath: string;
112
+ };
113
+ /**
114
+ * Detect installed Claude Code and return its settings path.
115
+ */
116
+ declare function detectClaudeCode(): {
117
+ found: boolean;
118
+ settingsPath?: string;
119
+ version?: string;
120
+ };
121
+
122
+ export { type ClaudeCodeSettings, type HookDef, type HookEvent, type HookMatcher, type HooksAdapterConfig, detectClaudeCode, generateAuditHookScript, generateHookScript, generateHooksConfig, installHooks, uninstallHooks };
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @sovr/proxy-mcp — Claude Code Hooks Adapter
3
+ *
4
+ * Solves the bypass problem for Claude Code users specifically.
5
+ * Claude Code supports "Hooks" — scripts that run before/after tool use.
6
+ * PreToolUse hooks can BLOCK tool calls before they execute.
7
+ *
8
+ * This adapter generates the correct hooks configuration for Claude Code
9
+ * that routes ALL tool calls through SOVR for policy evaluation.
10
+ *
11
+ * Unlike the MCP proxy approach, hooks are ENFORCED by Claude Code itself —
12
+ * the LLM cannot bypass them because they run at the platform level.
13
+ *
14
+ * Integration:
15
+ * npx sovr-mcp-proxy init --claude-code
16
+ * → Writes hooks config to ~/.claude/settings.json
17
+ * → Installs sovr-hook.sh as the PreToolUse handler
18
+ *
19
+ * Hook flow:
20
+ * 1. LLM decides to call a tool (e.g., Bash with "rm -rf /")
21
+ * 2. Claude Code triggers PreToolUse hook BEFORE execution
22
+ * 3. sovr-hook.sh calls SOVR daemon/API for policy evaluation
23
+ * 4. Hook exits 0 (allow) or 2 (block) based on SOVR decision
24
+ * 5. Claude Code either proceeds or blocks the tool call
25
+ *
26
+ * Exit codes (Claude Code hook spec):
27
+ * 0 = Allow (proceed with tool call)
28
+ * 2 = Block (reject tool call, show reason to user)
29
+ * Other = Error (Claude Code decides based on its own policy)
30
+ *
31
+ * @see https://docs.anthropic.com/en/docs/claude-code/hooks
32
+ */
33
+ /** Claude Code hook event types */
34
+ type HookEvent = 'PreToolUse' | 'PostToolUse' | 'Notification' | 'Stop' | 'SubagentStop';
35
+ /** A single hook matcher */
36
+ interface HookMatcher {
37
+ /** Tool name pattern (regex supported) */
38
+ tool_name?: string;
39
+ /** Notification type pattern */
40
+ type?: string;
41
+ }
42
+ /** A single hook definition */
43
+ interface HookDef {
44
+ /** Matchers that determine when this hook fires */
45
+ matcher: HookMatcher;
46
+ /** Array of hook commands to execute */
47
+ hooks: Array<{
48
+ /** The command to run */
49
+ command: string;
50
+ /** Timeout in seconds */
51
+ timeout?: number;
52
+ }>;
53
+ }
54
+ /** Claude Code settings.json structure (partial) */
55
+ interface ClaudeCodeSettings {
56
+ hooks?: Record<HookEvent, HookDef[]>;
57
+ [key: string]: unknown;
58
+ }
59
+ /** Configuration for the hooks adapter */
60
+ interface HooksAdapterConfig {
61
+ /** SOVR API key */
62
+ apiKey?: string;
63
+ /** SOVR endpoint (default: daemon socket) */
64
+ endpoint?: string;
65
+ /** Fail mode: 'open' allows on error, 'closed' blocks on error */
66
+ failMode?: 'open' | 'closed';
67
+ /** Tools to intercept (regex patterns) */
68
+ toolPatterns?: string[];
69
+ /** Timeout for hook execution in seconds */
70
+ hookTimeout?: number;
71
+ /** Path to Claude Code settings (auto-detected if not provided) */
72
+ settingsPath?: string;
73
+ /** Whether to also add PostToolUse audit hook */
74
+ addAuditHook?: boolean;
75
+ }
76
+ /**
77
+ * Generate the sovr-hook.sh script content.
78
+ * This script is called by Claude Code for every PreToolUse event.
79
+ *
80
+ * It reads the hook input from stdin (JSON with tool_name, tool_input),
81
+ * calls SOVR for evaluation, and exits with the appropriate code.
82
+ */
83
+ declare function generateHookScript(config: HooksAdapterConfig): string;
84
+ /**
85
+ * Generate the PostToolUse audit hook script.
86
+ * This records what happened after a tool call completes.
87
+ */
88
+ declare function generateAuditHookScript(config: HooksAdapterConfig): string;
89
+ /**
90
+ * Generate the Claude Code hooks configuration object.
91
+ */
92
+ declare function generateHooksConfig(config: HooksAdapterConfig): Record<HookEvent, HookDef[]>;
93
+ /**
94
+ * Install SOVR hooks into Claude Code settings.
95
+ * Merges with existing settings, preserving non-SOVR hooks.
96
+ */
97
+ declare function installHooks(config: HooksAdapterConfig): {
98
+ settingsPath: string;
99
+ hookScriptPath: string;
100
+ auditScriptPath?: string;
101
+ installed: boolean;
102
+ backup?: string;
103
+ };
104
+ /**
105
+ * Remove SOVR hooks from Claude Code settings.
106
+ */
107
+ declare function uninstallHooks(config?: {
108
+ settingsPath?: string;
109
+ }): {
110
+ removed: boolean;
111
+ settingsPath: string;
112
+ };
113
+ /**
114
+ * Detect installed Claude Code and return its settings path.
115
+ */
116
+ declare function detectClaudeCode(): {
117
+ found: boolean;
118
+ settingsPath?: string;
119
+ version?: string;
120
+ };
121
+
122
+ export { type ClaudeCodeSettings, type HookDef, type HookEvent, type HookMatcher, type HooksAdapterConfig, detectClaudeCode, generateAuditHookScript, generateHookScript, generateHooksConfig, installHooks, uninstallHooks };
@@ -0,0 +1,321 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/hooksAdapter.ts
21
+ var hooksAdapter_exports = {};
22
+ __export(hooksAdapter_exports, {
23
+ detectClaudeCode: () => detectClaudeCode,
24
+ generateAuditHookScript: () => generateAuditHookScript,
25
+ generateHookScript: () => generateHookScript,
26
+ generateHooksConfig: () => generateHooksConfig,
27
+ installHooks: () => installHooks,
28
+ uninstallHooks: () => uninstallHooks
29
+ });
30
+ module.exports = __toCommonJS(hooksAdapter_exports);
31
+ var import_node_fs = require("fs");
32
+ var import_node_path = require("path");
33
+ var import_node_os = require("os");
34
+ var DEFAULT_TOOL_PATTERNS = [
35
+ "Bash",
36
+ "bash",
37
+ "shell",
38
+ "execute_command",
39
+ "Write",
40
+ // File write operations
41
+ "Edit",
42
+ // File edit operations
43
+ "MultiEdit"
44
+ // Multi-file edit operations
45
+ ];
46
+ var DEFAULT_HOOK_TIMEOUT = 10;
47
+ function generateHookScript(config) {
48
+ const failExit = config.failMode === "closed" ? 2 : 0;
49
+ const endpoint = config.endpoint || "http://localhost:45557";
50
+ const apiKeyRef = config.apiKey ? `"${config.apiKey}"` : '"${SOVR_API_KEY:-}"';
51
+ return `#!/bin/bash
52
+ # SOVR PreToolUse Hook for Claude Code
53
+ # Generated by: npx sovr-mcp-proxy init --claude-code
54
+ #
55
+ # This hook intercepts ALL tool calls and evaluates them against
56
+ # SOVR security policies BEFORE Claude Code executes them.
57
+ #
58
+ # Exit codes:
59
+ # 0 = ALLOW (proceed with tool call)
60
+ # 2 = BLOCK (reject tool call)
61
+ #
62
+ # Environment:
63
+ # SOVR_API_KEY \u2014 API key for SOVR authentication
64
+ # SOVR_ENDPOINT \u2014 SOVR daemon endpoint (default: ${endpoint})
65
+ # SOVR_FAIL_MODE \u2014 'open' (default) or 'closed'
66
+ # SOVR_HOOK_DEBUG \u2014 Set to '1' for debug logging
67
+
68
+ set -euo pipefail
69
+
70
+ # Configuration
71
+ SOVR_ENDPOINT="\${SOVR_ENDPOINT:-${endpoint}}"
72
+ SOVR_API_KEY=${apiKeyRef}
73
+ SOVR_FAIL_MODE="\${SOVR_FAIL_MODE:-${config.failMode || "open"}}"
74
+ SOVR_HOOK_DEBUG="\${SOVR_HOOK_DEBUG:-0}"
75
+ FAIL_EXIT=${failExit}
76
+
77
+ # Debug logging
78
+ debug() {
79
+ if [ "$SOVR_HOOK_DEBUG" = "1" ]; then
80
+ echo "[SOVR-HOOK $(date -u +%H:%M:%S)] $*" >&2
81
+ fi
82
+ }
83
+
84
+ # Read hook input from stdin
85
+ INPUT=$(cat)
86
+ debug "Input: $INPUT"
87
+
88
+ # Extract tool name and input from JSON
89
+ TOOL_NAME=$(echo "$INPUT" | grep -o '"tool_name":"[^"]*"' | head -1 | cut -d'"' -f4 2>/dev/null || echo "unknown")
90
+ TOOL_INPUT=$(echo "$INPUT" | grep -o '"tool_input":{[^}]*}' | head -1 2>/dev/null || echo "{}")
91
+
92
+ debug "Tool: $TOOL_NAME"
93
+
94
+ # Skip non-dangerous tools (read-only operations)
95
+ case "$TOOL_NAME" in
96
+ Read|View|LS|Glob|Grep|TodoRead|WebFetch|WebSearch)
97
+ debug "SKIP: Read-only tool $TOOL_NAME"
98
+ exit 0
99
+ ;;
100
+ esac
101
+
102
+ # Extract command for Bash tools
103
+ COMMAND=""
104
+ case "$TOOL_NAME" in
105
+ Bash|bash|shell|execute_command)
106
+ COMMAND=$(echo "$TOOL_INPUT" | grep -o '"command":"[^"]*"' | head -1 | cut -d'"' -f4 2>/dev/null || echo "")
107
+ ;;
108
+ Write|Edit|MultiEdit)
109
+ COMMAND=$(echo "$TOOL_INPUT" | grep -o '"file_path":"[^"]*"' | head -1 | cut -d'"' -f4 2>/dev/null || echo "")
110
+ ;;
111
+ esac
112
+
113
+ debug "Command: $COMMAND"
114
+
115
+ # Call SOVR daemon for evaluation
116
+ RESPONSE=$(curl -s -X POST "$SOVR_ENDPOINT/api/hook/evaluate" \\
117
+ -H "Content-Type: application/json" \\
118
+ -H "X-SOVR-API-Key: $SOVR_API_KEY" \\
119
+ -d "{
120
+ \\"tool_name\\": \\"$TOOL_NAME\\",
121
+ \\"command\\": $(echo "$COMMAND" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))' 2>/dev/null || echo '""'),
122
+ \\"channel\\": \\"hooks\\",
123
+ \\"context\\": {
124
+ \\"platform\\": \\"claude-code\\",
125
+ \\"hook_event\\": \\"PreToolUse\\"
126
+ }
127
+ }" \\
128
+ --connect-timeout 3 --max-time 5 2>/dev/null) || {
129
+ debug "SOVR daemon unreachable, fail-$SOVR_FAIL_MODE"
130
+ exit $FAIL_EXIT
131
+ }
132
+
133
+ debug "Response: $RESPONSE"
134
+
135
+ # Parse verdict
136
+ VERDICT=$(echo "$RESPONSE" | grep -o '"verdict":"[^"]*"' | head -1 | cut -d'"' -f4 2>/dev/null || echo "")
137
+ REASON=$(echo "$RESPONSE" | grep -o '"reason":"[^"]*"' | head -1 | cut -d'"' -f4 2>/dev/null || echo "Policy evaluation failed")
138
+
139
+ case "$VERDICT" in
140
+ allow)
141
+ debug "ALLOWED: $TOOL_NAME"
142
+ exit 0
143
+ ;;
144
+ deny)
145
+ debug "BLOCKED: $TOOL_NAME \u2014 $REASON"
146
+ # Output block reason as JSON for Claude Code to display
147
+ echo "{\\"error\\": \\"SOVR Policy Violation: $REASON\\"}"
148
+ exit 2
149
+ ;;
150
+ escalate)
151
+ debug "ESCALATED: $TOOL_NAME \u2014 requires approval"
152
+ echo "{\\"error\\": \\"SOVR: This action requires human approval. Reason: $REASON\\"}"
153
+ exit 2
154
+ ;;
155
+ *)
156
+ debug "UNKNOWN verdict: $VERDICT, fail-$SOVR_FAIL_MODE"
157
+ exit $FAIL_EXIT
158
+ ;;
159
+ esac
160
+ `;
161
+ }
162
+ function generateAuditHookScript(config) {
163
+ const endpoint = config.endpoint || "http://localhost:45557";
164
+ const apiKeyRef = config.apiKey ? `"${config.apiKey}"` : '"${SOVR_API_KEY:-}"';
165
+ return `#!/bin/bash
166
+ # SOVR PostToolUse Audit Hook for Claude Code
167
+ # Records tool execution results for audit trail
168
+ set -euo pipefail
169
+
170
+ SOVR_ENDPOINT="\${SOVR_ENDPOINT:-${endpoint}}"
171
+ SOVR_API_KEY=${apiKeyRef}
172
+
173
+ INPUT=$(cat)
174
+ TOOL_NAME=$(echo "$INPUT" | grep -o '"tool_name":"[^"]*"' | head -1 | cut -d'"' -f4 2>/dev/null || echo "unknown")
175
+
176
+ # Fire-and-forget audit log
177
+ curl -s -X POST "$SOVR_ENDPOINT/api/hook/audit" \\
178
+ -H "Content-Type: application/json" \\
179
+ -H "X-SOVR-API-Key: $SOVR_API_KEY" \\
180
+ -d "{
181
+ \\"tool_name\\": \\"$TOOL_NAME\\",
182
+ \\"event\\": \\"PostToolUse\\",
183
+ \\"timestamp\\": $(date +%s)000
184
+ }" \\
185
+ --connect-timeout 2 --max-time 3 2>/dev/null &
186
+
187
+ # Always allow (audit is non-blocking)
188
+ exit 0
189
+ `;
190
+ }
191
+ function generateHooksConfig(config) {
192
+ const hookScriptPath = getHookScriptPath();
193
+ const timeout = config.hookTimeout || DEFAULT_HOOK_TIMEOUT;
194
+ const hooks = {
195
+ PreToolUse: []
196
+ };
197
+ const patterns = config.toolPatterns || DEFAULT_TOOL_PATTERNS;
198
+ for (const pattern of patterns) {
199
+ hooks.PreToolUse.push({
200
+ matcher: { tool_name: pattern },
201
+ hooks: [{ command: hookScriptPath, timeout }]
202
+ });
203
+ }
204
+ if (config.addAuditHook) {
205
+ const auditScriptPath = getAuditHookScriptPath();
206
+ hooks.PostToolUse = patterns.map((pattern) => ({
207
+ matcher: { tool_name: pattern },
208
+ hooks: [{ command: auditScriptPath, timeout: 5 }]
209
+ }));
210
+ }
211
+ return hooks;
212
+ }
213
+ function installHooks(config) {
214
+ const settingsPath = config.settingsPath || getDefaultSettingsPath();
215
+ const hookScriptPath = getHookScriptPath();
216
+ const auditScriptPath = config.addAuditHook ? getAuditHookScriptPath() : void 0;
217
+ const settingsDir = (0, import_node_path.dirname)(settingsPath);
218
+ const hooksDir = (0, import_node_path.dirname)(hookScriptPath);
219
+ (0, import_node_fs.mkdirSync)(settingsDir, { recursive: true });
220
+ (0, import_node_fs.mkdirSync)(hooksDir, { recursive: true });
221
+ (0, import_node_fs.writeFileSync)(hookScriptPath, generateHookScript(config), "utf-8");
222
+ (0, import_node_fs.chmodSync)(hookScriptPath, 493);
223
+ if (auditScriptPath) {
224
+ (0, import_node_fs.writeFileSync)(auditScriptPath, generateAuditHookScript(config), "utf-8");
225
+ (0, import_node_fs.chmodSync)(auditScriptPath, 493);
226
+ }
227
+ let existingSettings = {};
228
+ let backup;
229
+ if ((0, import_node_fs.existsSync)(settingsPath)) {
230
+ try {
231
+ const raw = (0, import_node_fs.readFileSync)(settingsPath, "utf-8");
232
+ existingSettings = JSON.parse(raw);
233
+ backup = `${settingsPath}.sovr-backup.${Date.now()}`;
234
+ (0, import_node_fs.writeFileSync)(backup, raw, "utf-8");
235
+ } catch {
236
+ }
237
+ }
238
+ const sovrHooks = generateHooksConfig(config);
239
+ const mergedHooks = {};
240
+ if (existingSettings.hooks) {
241
+ for (const [event, defs] of Object.entries(existingSettings.hooks)) {
242
+ mergedHooks[event] = defs.filter(
243
+ (d) => !d.hooks.some((h) => h.command.includes("sovr"))
244
+ );
245
+ }
246
+ }
247
+ for (const [event, defs] of Object.entries(sovrHooks)) {
248
+ if (!mergedHooks[event]) mergedHooks[event] = [];
249
+ mergedHooks[event].push(...defs);
250
+ }
251
+ const updatedSettings = {
252
+ ...existingSettings,
253
+ hooks: mergedHooks
254
+ };
255
+ (0, import_node_fs.writeFileSync)(settingsPath, JSON.stringify(updatedSettings, null, 2), "utf-8");
256
+ return {
257
+ settingsPath,
258
+ hookScriptPath,
259
+ auditScriptPath,
260
+ installed: true,
261
+ backup
262
+ };
263
+ }
264
+ function uninstallHooks(config) {
265
+ const settingsPath = config?.settingsPath || getDefaultSettingsPath();
266
+ if (!(0, import_node_fs.existsSync)(settingsPath)) {
267
+ return { removed: false, settingsPath };
268
+ }
269
+ try {
270
+ const raw = (0, import_node_fs.readFileSync)(settingsPath, "utf-8");
271
+ const settings = JSON.parse(raw);
272
+ if (settings.hooks) {
273
+ for (const [event, defs] of Object.entries(settings.hooks)) {
274
+ settings.hooks[event] = defs.filter(
275
+ (d) => !d.hooks.some((h) => h.command.includes("sovr"))
276
+ );
277
+ }
278
+ }
279
+ (0, import_node_fs.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
280
+ return { removed: true, settingsPath };
281
+ } catch {
282
+ return { removed: false, settingsPath };
283
+ }
284
+ }
285
+ function getDefaultSettingsPath() {
286
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), ".claude", "settings.json");
287
+ }
288
+ function getHookScriptPath() {
289
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), ".sovr", "hooks", "sovr-pretool-hook.sh");
290
+ }
291
+ function getAuditHookScriptPath() {
292
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), ".sovr", "hooks", "sovr-posttool-audit.sh");
293
+ }
294
+ function detectClaudeCode() {
295
+ const defaultPath = getDefaultSettingsPath();
296
+ if ((0, import_node_fs.existsSync)((0, import_node_path.dirname)(defaultPath))) {
297
+ return {
298
+ found: true,
299
+ settingsPath: defaultPath
300
+ };
301
+ }
302
+ const altPaths = [
303
+ (0, import_node_path.join)((0, import_node_os.homedir)(), ".config", "claude", "settings.json"),
304
+ (0, import_node_path.join)((0, import_node_os.homedir)(), "Library", "Application Support", "Claude", "settings.json")
305
+ ];
306
+ for (const p of altPaths) {
307
+ if ((0, import_node_fs.existsSync)((0, import_node_path.dirname)(p))) {
308
+ return { found: true, settingsPath: p };
309
+ }
310
+ }
311
+ return { found: false };
312
+ }
313
+ // Annotate the CommonJS export names for ESM import in node:
314
+ 0 && (module.exports = {
315
+ detectClaudeCode,
316
+ generateAuditHookScript,
317
+ generateHookScript,
318
+ generateHooksConfig,
319
+ installHooks,
320
+ uninstallHooks
321
+ });