@mauribadnights/clooks 0.5.1 → 0.5.2
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/README.md +2 -0
- package/dist/cli.js +0 -0
- package/dist/handlers.d.ts +2 -0
- package/dist/handlers.js +43 -0
- package/docs/.nojekyll +0 -0
- package/docs/_sidebar.md +32 -0
- package/docs/getting-started/installation.md +52 -0
- package/docs/getting-started/migration.md +68 -0
- package/docs/getting-started/quickstart.md +76 -0
- package/docs/guides/async-handlers.md +42 -0
- package/docs/guides/dependencies.md +153 -0
- package/docs/guides/filtering.md +153 -0
- package/docs/guides/handlers.md +236 -0
- package/docs/guides/llm-handlers.md +145 -0
- package/docs/guides/manifest.md +237 -0
- package/docs/guides/short-circuit.md +31 -0
- package/docs/guides/system-service.md +62 -0
- package/docs/index.html +43 -0
- package/docs/index.md +96 -0
- package/docs/operations/architecture.md +105 -0
- package/docs/operations/monitoring.md +94 -0
- package/docs/operations/security.md +76 -0
- package/docs/operations/troubleshooting.md +123 -0
- package/docs/plugins/cc-plugin-import.md +55 -0
- package/docs/plugins/creating-plugins.md +144 -0
- package/docs/plugins/using-plugins.md +63 -0
- package/docs/reference/cli.md +213 -0
- package/docs/reference/config-files.md +129 -0
- package/docs/reference/hook-events.md +128 -0
- package/docs/reference/http-api.md +122 -0
- package/docs/reference/types.md +410 -0
- package/package.json +1 -1
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Handlers
|
|
2
|
+
|
|
3
|
+
clooks supports three handler types: **script**, **inline**, and **llm**. Each type trades off between flexibility and performance. This guide covers how each type works, when to use it, and how to structure the output.
|
|
4
|
+
|
|
5
|
+
## Script Handlers
|
|
6
|
+
|
|
7
|
+
Script handlers run shell commands via `sh -c`. They are the most portable option -- any language that reads stdin and writes stdout works.
|
|
8
|
+
|
|
9
|
+
### How They Work
|
|
10
|
+
|
|
11
|
+
1. clooks spawns a child process with the handler's `command`.
|
|
12
|
+
2. The full `HookInput` JSON is piped to the process's stdin.
|
|
13
|
+
3. The process writes its response to stdout.
|
|
14
|
+
4. If stdout is valid JSON, it is used directly. If not, the raw text is wrapped as `{"additionalContext": "..."}`.
|
|
15
|
+
|
|
16
|
+
### Required Fields
|
|
17
|
+
|
|
18
|
+
| Field | Type | Description |
|
|
19
|
+
|-------|------|-------------|
|
|
20
|
+
| `command` | string | Shell command to execute via `sh -c` |
|
|
21
|
+
|
|
22
|
+
### Default Timeout
|
|
23
|
+
|
|
24
|
+
5000ms. Override with the `timeout` field.
|
|
25
|
+
|
|
26
|
+
### Example Configuration
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
handlers:
|
|
30
|
+
PreToolUse:
|
|
31
|
+
- id: bash-guard
|
|
32
|
+
type: script
|
|
33
|
+
command: "node ~/.clooks/hooks/bash-guard.js"
|
|
34
|
+
filter: "Bash"
|
|
35
|
+
timeout: 3000
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Example Script (Node.js)
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
#!/usr/bin/env node
|
|
42
|
+
|
|
43
|
+
// Read HookInput from stdin
|
|
44
|
+
let data = '';
|
|
45
|
+
process.stdin.on('data', chunk => { data += chunk; });
|
|
46
|
+
process.stdin.on('end', () => {
|
|
47
|
+
const input = JSON.parse(data);
|
|
48
|
+
|
|
49
|
+
// Check if the Bash command looks dangerous
|
|
50
|
+
const args = input.tool_input || {};
|
|
51
|
+
const command = args.command || '';
|
|
52
|
+
|
|
53
|
+
if (command.includes('rm -rf /')) {
|
|
54
|
+
// Block the tool call
|
|
55
|
+
console.log(JSON.stringify({
|
|
56
|
+
decision: 'block',
|
|
57
|
+
reason: 'Dangerous rm -rf command detected'
|
|
58
|
+
}));
|
|
59
|
+
} else {
|
|
60
|
+
// Add context for Claude
|
|
61
|
+
console.log(JSON.stringify({
|
|
62
|
+
additionalContext: `Bash command reviewed: ${command.slice(0, 80)}`
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
> **Note:** Non-zero exit codes are treated as handler failures. Stderr output is captured and included in the error message.
|
|
69
|
+
|
|
70
|
+
## Inline Handlers
|
|
71
|
+
|
|
72
|
+
Inline handlers import an ES module in-process. There is no subprocess overhead, making them the fastest handler type.
|
|
73
|
+
|
|
74
|
+
### How They Work
|
|
75
|
+
|
|
76
|
+
1. clooks dynamically imports the module specified by `module`.
|
|
77
|
+
2. The module's default export is called with the `HookInput` object.
|
|
78
|
+
3. The return value becomes the handler output.
|
|
79
|
+
|
|
80
|
+
### Required Fields
|
|
81
|
+
|
|
82
|
+
| Field | Type | Description |
|
|
83
|
+
|-------|------|-------------|
|
|
84
|
+
| `module` | string | Path to a `.js` or `.ts` file with a default export function |
|
|
85
|
+
|
|
86
|
+
### Default Timeout
|
|
87
|
+
|
|
88
|
+
5000ms. Override with the `timeout` field.
|
|
89
|
+
|
|
90
|
+
### Example Configuration
|
|
91
|
+
|
|
92
|
+
```yaml
|
|
93
|
+
handlers:
|
|
94
|
+
PreToolUse:
|
|
95
|
+
- id: context-injector
|
|
96
|
+
type: inline
|
|
97
|
+
module: ~/.clooks/hooks/context.js
|
|
98
|
+
filter: "Write|Edit"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Example Module
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// ~/.clooks/hooks/context.ts
|
|
105
|
+
import type { HookInput } from '@mauribadnights/clooks';
|
|
106
|
+
|
|
107
|
+
export default async function(input: HookInput) {
|
|
108
|
+
const toolName = input.tool_name ?? 'unknown';
|
|
109
|
+
const cwd = input.cwd;
|
|
110
|
+
|
|
111
|
+
// Return value becomes handler output
|
|
112
|
+
return {
|
|
113
|
+
additionalContext: `Tool ${toolName} executing in ${cwd}`
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> **Note:** The module must have a default export that is a function. If the export is missing or not a function, the handler fails with an error message identifying the module.
|
|
119
|
+
|
|
120
|
+
## LLM Handlers
|
|
121
|
+
|
|
122
|
+
LLM handlers call the Anthropic Messages API with prompt templates. They require no scripts -- the prompt is defined directly in the manifest.
|
|
123
|
+
|
|
124
|
+
### How They Work
|
|
125
|
+
|
|
126
|
+
1. The handler's `prompt` template is rendered by replacing `$VARIABLES` with actual values.
|
|
127
|
+
2. The rendered prompt is sent to the Anthropic API using the specified `model`.
|
|
128
|
+
3. The response text is returned as `{"additionalContext": "..."}`.
|
|
129
|
+
|
|
130
|
+
### Required Fields
|
|
131
|
+
|
|
132
|
+
| Field | Type | Description |
|
|
133
|
+
|-------|------|-------------|
|
|
134
|
+
| `model` | string | `claude-haiku-4-5`, `claude-sonnet-4-6`, or `claude-opus-4-6` |
|
|
135
|
+
| `prompt` | string | Prompt template with `$VARIABLE` interpolation |
|
|
136
|
+
|
|
137
|
+
### Optional Fields
|
|
138
|
+
|
|
139
|
+
| Field | Type | Default | Description |
|
|
140
|
+
|-------|------|---------|-------------|
|
|
141
|
+
| `maxTokens` | number | 1024 | Maximum tokens in the API response |
|
|
142
|
+
| `temperature` | number | 1.0 | Sampling temperature |
|
|
143
|
+
| `batchGroup` | string | — | Group ID for batching multiple handlers into one API call |
|
|
144
|
+
|
|
145
|
+
### Default Timeout
|
|
146
|
+
|
|
147
|
+
30000ms. Override with the `timeout` field.
|
|
148
|
+
|
|
149
|
+
### Prompt Variables
|
|
150
|
+
|
|
151
|
+
| Variable | Source |
|
|
152
|
+
|----------|--------|
|
|
153
|
+
| `$TRANSCRIPT` | Session transcript (requires `transcript` in prefetch) |
|
|
154
|
+
| `$GIT_STATUS` | `git status --porcelain` (requires `git_status` in prefetch) |
|
|
155
|
+
| `$GIT_DIFF` | `git diff --stat` (requires `git_diff` in prefetch) |
|
|
156
|
+
| `$ARGUMENTS` | JSON-serialized `tool_input` (PreToolUse/PostToolUse) |
|
|
157
|
+
| `$TOOL_NAME` | Tool name (PreToolUse/PostToolUse) |
|
|
158
|
+
| `$PROMPT` | User prompt text (UserPromptSubmit) |
|
|
159
|
+
| `$CWD` | Current working directory |
|
|
160
|
+
|
|
161
|
+
### Example Configuration
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
prefetch:
|
|
165
|
+
- git_status
|
|
166
|
+
|
|
167
|
+
handlers:
|
|
168
|
+
PreToolUse:
|
|
169
|
+
- id: code-reviewer
|
|
170
|
+
type: llm
|
|
171
|
+
model: claude-haiku-4-5
|
|
172
|
+
prompt: |
|
|
173
|
+
Review this tool call for potential issues.
|
|
174
|
+
Tool: $TOOL_NAME
|
|
175
|
+
Arguments: $ARGUMENTS
|
|
176
|
+
Git status: $GIT_STATUS
|
|
177
|
+
|
|
178
|
+
If there is a problem, explain it briefly. Otherwise say "Looks good."
|
|
179
|
+
filter: "Write|Edit"
|
|
180
|
+
maxTokens: 256
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
See [LLM Handlers](llm-handlers.md) for batching, cost tracking, and advanced usage.
|
|
184
|
+
|
|
185
|
+
## Handler Output Format
|
|
186
|
+
|
|
187
|
+
Handlers communicate back to Claude Code through their output. There are two primary output shapes.
|
|
188
|
+
|
|
189
|
+
### Adding Context
|
|
190
|
+
|
|
191
|
+
Return an `additionalContext` string to inject information into Claude's context window:
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"additionalContext": "Information to inject into Claude's context"
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
This is the most common output. Claude sees this text as additional context when deciding its next action.
|
|
200
|
+
|
|
201
|
+
### Blocking a Tool (PreToolUse only)
|
|
202
|
+
|
|
203
|
+
PreToolUse handlers can block a tool call by returning a `decision` of `"block"`:
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
{
|
|
207
|
+
"decision": "block",
|
|
208
|
+
"reason": "This operation is not allowed because..."
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
When a handler blocks a tool, Claude receives the reason and must find an alternative approach. Multiple handlers can run for the same event -- if any handler blocks, the tool is blocked.
|
|
213
|
+
|
|
214
|
+
### No Output
|
|
215
|
+
|
|
216
|
+
Returning nothing (empty stdout for scripts, `undefined` for inline) is valid. The handler is recorded as successful with no output.
|
|
217
|
+
|
|
218
|
+
## Auto-Disable
|
|
219
|
+
|
|
220
|
+
Handlers that fail repeatedly are automatically disabled to prevent cascading problems:
|
|
221
|
+
|
|
222
|
+
- After **3 consecutive failures**, the handler is marked as disabled.
|
|
223
|
+
- Disabled handlers are skipped on subsequent invocations (logged as auto-disabled).
|
|
224
|
+
- State is tracked per handler ID in memory.
|
|
225
|
+
|
|
226
|
+
To re-enable a disabled handler:
|
|
227
|
+
|
|
228
|
+
- **Edit the manifest** -- any manifest reload resets state for changed handlers.
|
|
229
|
+
- **Use `sessionIsolation: true`** -- state resets automatically on every `SessionStart` event.
|
|
230
|
+
- **Restart the daemon** -- `clooks restart` clears all in-memory state.
|
|
231
|
+
|
|
232
|
+
Check current handler status with `clooks stats`, which shows error counts and disabled state per handler.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
[Home](../index.md) | [Prev: Manifest](manifest.md) | [Next: LLM Handlers](llm-handlers.md)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# LLM Handlers
|
|
2
|
+
|
|
3
|
+
LLM handlers call the Anthropic Messages API directly from the manifest, with prompt templates, automatic batching, and cost tracking. This guide covers advanced usage beyond the basics in [Handlers](handlers.md).
|
|
4
|
+
|
|
5
|
+
## Basics
|
|
6
|
+
|
|
7
|
+
LLM handlers require an Anthropic API key. Provide it in one of two ways:
|
|
8
|
+
|
|
9
|
+
1. **Environment variable:** `ANTHROPIC_API_KEY=sk-ant-...`
|
|
10
|
+
2. **Manifest setting:** `settings.anthropicApiKey: sk-ant-...`
|
|
11
|
+
|
|
12
|
+
The Anthropic SDK is lazy-loaded on the first LLM handler invocation. If the SDK is not installed, the handler fails with an actionable error message.
|
|
13
|
+
|
|
14
|
+
### Supported Models
|
|
15
|
+
|
|
16
|
+
| Model | Best For |
|
|
17
|
+
|-------|----------|
|
|
18
|
+
| `claude-haiku-4-5` | Fast, cheap checks (guards, simple reviews) |
|
|
19
|
+
| `claude-sonnet-4-6` | Balanced analysis (code review, context synthesis) |
|
|
20
|
+
| `claude-opus-4-6` | Deep reasoning (security audits, architecture review) |
|
|
21
|
+
|
|
22
|
+
## Prompt Templates
|
|
23
|
+
|
|
24
|
+
Prompts support `$VARIABLE` interpolation. Variables are replaced with actual values before the API call.
|
|
25
|
+
|
|
26
|
+
| Variable | Requires Prefetch | Source |
|
|
27
|
+
|----------|-------------------|--------|
|
|
28
|
+
| `$TRANSCRIPT` | Yes (`transcript`) | Session transcript, last 50KB |
|
|
29
|
+
| `$GIT_STATUS` | Yes (`git_status`) | `git status --porcelain` output |
|
|
30
|
+
| `$GIT_DIFF` | Yes (`git_diff`) | `git diff --stat` output, max 20KB |
|
|
31
|
+
| `$ARGUMENTS` | No | JSON-serialized `tool_input` (PreToolUse/PostToolUse) |
|
|
32
|
+
| `$TOOL_NAME` | No | Tool name string (PreToolUse/PostToolUse) |
|
|
33
|
+
| `$PROMPT` | No | User prompt text (UserPromptSubmit) |
|
|
34
|
+
| `$CWD` | No | Current working directory |
|
|
35
|
+
|
|
36
|
+
Variables that require prefetch will resolve to an empty string if the corresponding prefetch key is not listed in the manifest's `prefetch` array.
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
prefetch:
|
|
40
|
+
- transcript
|
|
41
|
+
- git_status
|
|
42
|
+
|
|
43
|
+
handlers:
|
|
44
|
+
PreToolUse:
|
|
45
|
+
- id: reviewer
|
|
46
|
+
type: llm
|
|
47
|
+
model: claude-haiku-4-5
|
|
48
|
+
prompt: |
|
|
49
|
+
You are reviewing a tool call in a Claude Code session.
|
|
50
|
+
|
|
51
|
+
Tool: $TOOL_NAME
|
|
52
|
+
Arguments: $ARGUMENTS
|
|
53
|
+
Working directory: $CWD
|
|
54
|
+
Git status: $GIT_STATUS
|
|
55
|
+
|
|
56
|
+
Flag any issues. Be concise.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Batching
|
|
60
|
+
|
|
61
|
+
Handlers with the same `batchGroup` value that fire on the same event are combined into a single API call. This reduces latency and cost when multiple LLM handlers need to analyze the same context.
|
|
62
|
+
|
|
63
|
+
### How It Works
|
|
64
|
+
|
|
65
|
+
1. Handlers sharing a `batchGroup` are collected.
|
|
66
|
+
2. Their prompts are rendered individually, then combined into a structured multi-task prompt.
|
|
67
|
+
3. A single API call is made. The model is instructed to return a JSON object keyed by handler ID.
|
|
68
|
+
4. The response is parsed and each handler receives its portion of the result.
|
|
69
|
+
5. Token usage and cost are split evenly across group members.
|
|
70
|
+
|
|
71
|
+
### Scoping
|
|
72
|
+
|
|
73
|
+
Batch groups are scoped by `session_id`. Two different Claude Code sessions firing the same batch group at the same time will produce separate API calls. There is no cross-session contamination.
|
|
74
|
+
|
|
75
|
+
### Edge Cases
|
|
76
|
+
|
|
77
|
+
- **Single-handler groups:** If a batch group contains only one handler, it falls through to individual execution (no batching overhead).
|
|
78
|
+
- **Mixed models:** If handlers in a group specify different models, the first handler's model is used and a warning is logged.
|
|
79
|
+
- **JSON parse failure:** If the batched response is not valid JSON, all handlers in the group receive the raw text as `additionalContext`.
|
|
80
|
+
|
|
81
|
+
### Example
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
handlers:
|
|
85
|
+
PreToolUse:
|
|
86
|
+
- id: security-check
|
|
87
|
+
type: llm
|
|
88
|
+
model: claude-haiku-4-5
|
|
89
|
+
prompt: |
|
|
90
|
+
Check this tool call for security issues:
|
|
91
|
+
Tool: $TOOL_NAME
|
|
92
|
+
Arguments: $ARGUMENTS
|
|
93
|
+
batchGroup: pre-tool-review
|
|
94
|
+
filter: "Write|Bash"
|
|
95
|
+
|
|
96
|
+
- id: style-check
|
|
97
|
+
type: llm
|
|
98
|
+
model: claude-haiku-4-5
|
|
99
|
+
prompt: |
|
|
100
|
+
Check if this tool call follows project conventions:
|
|
101
|
+
Tool: $TOOL_NAME
|
|
102
|
+
Arguments: $ARGUMENTS
|
|
103
|
+
batchGroup: pre-tool-review
|
|
104
|
+
filter: "Write|Bash"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Both handlers fire on the same `PreToolUse` event with the same `batchGroup`. clooks combines them into one API call, halving latency and cost compared to two separate calls.
|
|
108
|
+
|
|
109
|
+
## Cost Tracking
|
|
110
|
+
|
|
111
|
+
All LLM handler invocations are logged to `~/.clooks/costs.jsonl`. Each entry records the timestamp, event, handler ID, model, token usage, cost in USD, and whether the call was batched.
|
|
112
|
+
|
|
113
|
+
View cost data with:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
clooks costs
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Pricing
|
|
120
|
+
|
|
121
|
+
Pricing per million tokens (as of March 2026):
|
|
122
|
+
|
|
123
|
+
| Model | Input | Output |
|
|
124
|
+
|-------|-------|--------|
|
|
125
|
+
| `claude-haiku-4-5` | $0.80 | $4.00 |
|
|
126
|
+
| `claude-sonnet-4-6` | $3.00 | $15.00 |
|
|
127
|
+
| `claude-opus-4-6` | $15.00 | $75.00 |
|
|
128
|
+
|
|
129
|
+
For batched calls, the total token cost is split evenly across all handlers in the group.
|
|
130
|
+
|
|
131
|
+
## Best Practices
|
|
132
|
+
|
|
133
|
+
**Use Haiku for simple checks.** Guards, keyword detection, and light reviews run well on Haiku at a fraction of the cost. Reserve Sonnet and Opus for tasks that require deeper reasoning.
|
|
134
|
+
|
|
135
|
+
**Use batchGroup to combine related analyses.** If two handlers analyze the same tool call from different angles, batching them saves an API round-trip and reduces total tokens (the shared context is sent once).
|
|
136
|
+
|
|
137
|
+
**Set maxTokens conservatively.** Most handler responses are short. Setting `maxTokens: 256` or `maxTokens: 512` prevents runaway token usage on verbose responses.
|
|
138
|
+
|
|
139
|
+
**Use filter to avoid unnecessary API calls.** An LLM handler without a filter fires on every matching event. Adding `filter: "Write|Edit"` ensures the API is only called when relevant tools are invoked.
|
|
140
|
+
|
|
141
|
+
**Prefer prefetch over inline context.** If your prompt needs git status or the transcript, add the key to `prefetch` rather than running shell commands in a script handler. Prefetched data is fetched once and shared across all handlers.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
[Home](../index.md) | [Prev: Handlers](handlers.md) | [Next: Filtering](filtering.md)
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Manifest Reference
|
|
2
|
+
|
|
3
|
+
The manifest file (`~/.clooks/manifest.yaml`) is the single configuration source for the clooks daemon. It defines which handlers run, when they fire, and how the daemon behaves.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
A manifest has three top-level sections:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
prefetch:
|
|
11
|
+
- transcript
|
|
12
|
+
- git_status
|
|
13
|
+
- git_diff
|
|
14
|
+
|
|
15
|
+
handlers:
|
|
16
|
+
EventName:
|
|
17
|
+
- id: handler-id
|
|
18
|
+
type: script|inline|llm
|
|
19
|
+
# ...type-specific and shared fields
|
|
20
|
+
|
|
21
|
+
settings:
|
|
22
|
+
port: 7890
|
|
23
|
+
logLevel: info
|
|
24
|
+
authToken: token
|
|
25
|
+
anthropicApiKey: sk-...
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
All sections are optional. A minimal manifest needs only a `handlers` block with at least one event and one handler.
|
|
29
|
+
|
|
30
|
+
## Settings
|
|
31
|
+
|
|
32
|
+
Global daemon configuration lives under `settings`:
|
|
33
|
+
|
|
34
|
+
| Field | Type | Default | Description |
|
|
35
|
+
|-------|------|---------|-------------|
|
|
36
|
+
| `port` | number | 7890 | HTTP server port (1-65535) |
|
|
37
|
+
| `logLevel` | string | `"info"` | Log verbosity: `debug`, `info`, `warn`, `error` |
|
|
38
|
+
| `authToken` | string | — | Bearer token for HTTP authentication. Auto-generated by `clooks init` |
|
|
39
|
+
| `anthropicApiKey` | string | — | Anthropic API key. Alternative to setting the `ANTHROPIC_API_KEY` env var |
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
settings:
|
|
43
|
+
port: 7890
|
|
44
|
+
logLevel: info
|
|
45
|
+
authToken: clooks_a1b2c3d4e5f6
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Prefetch
|
|
49
|
+
|
|
50
|
+
The `prefetch` array declares context that should be fetched once per hook invocation and shared across all handlers. This avoids redundant work when multiple handlers need the same data.
|
|
51
|
+
|
|
52
|
+
| Key | Description | Size Limit |
|
|
53
|
+
|-----|-------------|------------|
|
|
54
|
+
| `transcript` | Last portion of the session transcript file | 50KB (tail) |
|
|
55
|
+
| `git_status` | Output of `git status --porcelain` in the session's cwd | — |
|
|
56
|
+
| `git_diff` | Output of `git diff --no-ext-diff --stat` in the session's cwd | 20KB (truncated) |
|
|
57
|
+
|
|
58
|
+
Prefetched data is available to LLM handler prompts as template variables: `$TRANSCRIPT`, `$GIT_STATUS`, `$GIT_DIFF`. Each key is fetched independently — a failure in one does not block the others.
|
|
59
|
+
|
|
60
|
+
```yaml
|
|
61
|
+
prefetch:
|
|
62
|
+
- transcript
|
|
63
|
+
- git_status
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Handler Events
|
|
67
|
+
|
|
68
|
+
Handlers are grouped by the Claude Code hook event they respond to. clooks supports all 9 events:
|
|
69
|
+
|
|
70
|
+
| Event | When It Fires |
|
|
71
|
+
|-------|---------------|
|
|
72
|
+
| `SessionStart` | A new Claude Code session begins |
|
|
73
|
+
| `UserPromptSubmit` | The user submits a prompt |
|
|
74
|
+
| `PreToolUse` | Before Claude executes a tool |
|
|
75
|
+
| `PostToolUse` | After a tool finishes executing |
|
|
76
|
+
| `Stop` | Claude finishes its response |
|
|
77
|
+
| `SubagentStart` | A subagent session starts |
|
|
78
|
+
| `SubagentStop` | A subagent session ends |
|
|
79
|
+
| `Notification` | Claude sends a notification |
|
|
80
|
+
| `ConfigChange` | Claude Code configuration changes |
|
|
81
|
+
|
|
82
|
+
See [Hook Events Reference](../reference/hook-events.md) for full payload documentation per event.
|
|
83
|
+
|
|
84
|
+
Each event key contains an array of handler definitions:
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
handlers:
|
|
88
|
+
PreToolUse:
|
|
89
|
+
- id: first-handler
|
|
90
|
+
type: script
|
|
91
|
+
command: "node ~/hooks/check.js"
|
|
92
|
+
- id: second-handler
|
|
93
|
+
type: llm
|
|
94
|
+
model: claude-haiku-4-5
|
|
95
|
+
prompt: "Review this tool call: $TOOL_NAME"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Shared Handler Fields
|
|
99
|
+
|
|
100
|
+
Every handler, regardless of type, supports these fields:
|
|
101
|
+
|
|
102
|
+
| Field | Type | Default | Description |
|
|
103
|
+
|-------|------|---------|-------------|
|
|
104
|
+
| `id` | string | required | Unique identifier across the entire manifest |
|
|
105
|
+
| `type` | string | required | `script`, `inline`, or `llm` |
|
|
106
|
+
| `enabled` | boolean | `true` | Toggle handler without removing it from the manifest |
|
|
107
|
+
| `filter` | string | — | Keyword filter applied to hook input. See [Filtering](filtering.md) |
|
|
108
|
+
| `timeout` | number | 5000 / 30000 | Milliseconds before handler is killed. Default is 5000ms for script/inline, 30000ms for LLM |
|
|
109
|
+
| `async` | boolean | `false` | Fire-and-forget execution. See [Async Handlers](async-handlers.md) |
|
|
110
|
+
| `depends` | string[] | — | Handler IDs this handler must wait for. See [Dependencies](dependencies.md) |
|
|
111
|
+
| `sessionIsolation` | boolean | `false` | Reset handler state (error counts, auto-disable) on every `SessionStart` |
|
|
112
|
+
| `agent` | string | — | Comma-separated agent names. Only fires in matching sessions. See [Filtering](filtering.md) |
|
|
113
|
+
| `project` | string | — | Glob pattern matched against session cwd. See [Filtering](filtering.md) |
|
|
114
|
+
|
|
115
|
+
Type-specific fields are covered in [Handlers](handlers.md).
|
|
116
|
+
|
|
117
|
+
## Hot Reload
|
|
118
|
+
|
|
119
|
+
The daemon watches `manifest.yaml` for filesystem changes. When the file is saved:
|
|
120
|
+
|
|
121
|
+
1. The new manifest is parsed and validated.
|
|
122
|
+
2. Handler lists are diffed against the previous version.
|
|
123
|
+
3. State for removed handlers is cleaned up.
|
|
124
|
+
4. Handlers with `sessionIsolation: true` that changed are reset.
|
|
125
|
+
5. The new handler set becomes active immediately.
|
|
126
|
+
|
|
127
|
+
No daemon restart is needed. Invalid manifests are rejected and the previous configuration continues running. Changes are logged at the `info` level.
|
|
128
|
+
|
|
129
|
+
## Validation
|
|
130
|
+
|
|
131
|
+
Run `clooks doctor` to validate your manifest structure. The validator enforces:
|
|
132
|
+
|
|
133
|
+
- The `handlers` key must be an object.
|
|
134
|
+
- Each event key must be a valid hook event name (`SessionStart`, `PreToolUse`, etc.).
|
|
135
|
+
- Each event must contain an array of handler objects.
|
|
136
|
+
- Every handler must have a string `id` and a valid `type`.
|
|
137
|
+
- Handler IDs must be unique across the entire manifest (not just within an event).
|
|
138
|
+
- Script handlers must have a `command` field.
|
|
139
|
+
- Inline handlers must have a `module` field.
|
|
140
|
+
- LLM handlers must have both `model` and `prompt` fields. Model must be one of: `claude-haiku-4-5`, `claude-sonnet-4-6`, `claude-opus-4-6`.
|
|
141
|
+
- `prefetch` must be an array containing only valid keys: `transcript`, `git_status`, `git_diff`.
|
|
142
|
+
- `settings.port` must be a number between 1 and 65535.
|
|
143
|
+
- `settings.logLevel` must be one of: `debug`, `info`, `warn`, `error`.
|
|
144
|
+
|
|
145
|
+
Invalid manifests produce specific error messages identifying the problem. For example:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
Error: Unknown hook event: "PreTool". Valid events: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, SubagentStart, SubagentStop, Notification, ConfigChange
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
Error: Duplicate handler id: "my-guard"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Complete Example
|
|
156
|
+
|
|
157
|
+
```yaml
|
|
158
|
+
prefetch:
|
|
159
|
+
- transcript
|
|
160
|
+
- git_status
|
|
161
|
+
- git_diff
|
|
162
|
+
|
|
163
|
+
handlers:
|
|
164
|
+
SessionStart:
|
|
165
|
+
- id: check-update
|
|
166
|
+
type: script
|
|
167
|
+
command: "node ~/.clooks/hooks/check-update.js"
|
|
168
|
+
timeout: 6000
|
|
169
|
+
|
|
170
|
+
UserPromptSubmit:
|
|
171
|
+
- id: prompt-logger
|
|
172
|
+
type: inline
|
|
173
|
+
module: ~/.clooks/hooks/log-prompt.js
|
|
174
|
+
async: true
|
|
175
|
+
|
|
176
|
+
PreToolUse:
|
|
177
|
+
- id: bash-guard
|
|
178
|
+
type: script
|
|
179
|
+
command: "node ~/.clooks/hooks/bash-guard.js"
|
|
180
|
+
filter: "Bash"
|
|
181
|
+
timeout: 3000
|
|
182
|
+
|
|
183
|
+
- id: write-review
|
|
184
|
+
type: llm
|
|
185
|
+
model: claude-haiku-4-5
|
|
186
|
+
prompt: |
|
|
187
|
+
Review this file write for potential issues:
|
|
188
|
+
Tool: $TOOL_NAME
|
|
189
|
+
Arguments: $ARGUMENTS
|
|
190
|
+
Git status: $GIT_STATUS
|
|
191
|
+
filter: "Write"
|
|
192
|
+
batchGroup: code-review
|
|
193
|
+
|
|
194
|
+
- id: style-check
|
|
195
|
+
type: llm
|
|
196
|
+
model: claude-haiku-4-5
|
|
197
|
+
prompt: |
|
|
198
|
+
Check if this write follows project style conventions:
|
|
199
|
+
$ARGUMENTS
|
|
200
|
+
filter: "Write"
|
|
201
|
+
batchGroup: code-review
|
|
202
|
+
depends: []
|
|
203
|
+
|
|
204
|
+
- id: deep-analysis
|
|
205
|
+
type: llm
|
|
206
|
+
model: claude-sonnet-4-6
|
|
207
|
+
prompt: |
|
|
208
|
+
Perform deep security and correctness review:
|
|
209
|
+
$ARGUMENTS
|
|
210
|
+
Context from prior checks available in input.
|
|
211
|
+
depends: [write-review, style-check]
|
|
212
|
+
filter: "Write"
|
|
213
|
+
|
|
214
|
+
PostToolUse:
|
|
215
|
+
- id: metrics-collector
|
|
216
|
+
type: inline
|
|
217
|
+
module: ~/.clooks/hooks/metrics.js
|
|
218
|
+
async: true
|
|
219
|
+
|
|
220
|
+
Stop:
|
|
221
|
+
- id: session-summary
|
|
222
|
+
type: llm
|
|
223
|
+
model: claude-haiku-4-5
|
|
224
|
+
prompt: |
|
|
225
|
+
Summarize what was accomplished in this session:
|
|
226
|
+
$TRANSCRIPT
|
|
227
|
+
maxTokens: 512
|
|
228
|
+
|
|
229
|
+
settings:
|
|
230
|
+
port: 7890
|
|
231
|
+
logLevel: info
|
|
232
|
+
authToken: clooks_a1b2c3d4e5f6
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
[Home](../index.md) | [Prev: Migration](../getting-started/migration.md) | [Next: Handlers](handlers.md)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Short-Circuit
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
When a PreToolUse handler blocks a tool call (returns `decision: "block"`), clooks automatically skips PostToolUse handlers for that same tool. This prevents wasted execution on tools that were already denied.
|
|
6
|
+
|
|
7
|
+
## How It Works
|
|
8
|
+
|
|
9
|
+
1. PreToolUse handler returns `{ decision: "block", reason: "..." }`
|
|
10
|
+
2. Server records denial in an in-memory DenyCache, keyed by `session_id:tool_name`
|
|
11
|
+
3. When PostToolUse fires for the same session + tool, clooks checks the DenyCache
|
|
12
|
+
4. If denied: PostToolUse handlers are skipped entirely
|
|
13
|
+
5. Cache entries expire after 30 seconds (prevents memory leaks)
|
|
14
|
+
|
|
15
|
+
## Configuration
|
|
16
|
+
|
|
17
|
+
This is automatic — no configuration needed. It works for all PreToolUse handlers that return a `block` decision.
|
|
18
|
+
|
|
19
|
+
The server also checks for `hookSpecificOutput.permissionDecision: "deny"` as an alternative denial signal.
|
|
20
|
+
|
|
21
|
+
## Cache Lifecycle
|
|
22
|
+
|
|
23
|
+
- Entries auto-expire after 30 seconds
|
|
24
|
+
- Periodic cleanup runs every 60 seconds
|
|
25
|
+
- Cache is in-memory only — cleared on daemon restart
|
|
26
|
+
|
|
27
|
+
> **Note:** The 30-second TTL is deliberately short. It only needs to survive long enough for the matching PostToolUse event to arrive, which typically happens within milliseconds of the PreToolUse denial.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
[Home](../index.md) | [Prev: Async Handlers](async-handlers.md) | [Next: System Service](system-service.md)
|