@mauribadnights/clooks 0.5.3 → 0.6.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/README.md +22 -8
- package/agents/clooks.md +14 -4
- package/dist/llm.d.ts +1 -1
- package/dist/llm.js +87 -6
- package/dist/manifest.js +29 -6
- package/dist/server.js +9 -5
- package/dist/types.d.ts +5 -1
- package/docs/getting-started/quickstart.md +1 -1
- package/docs/guides/handlers.md +19 -7
- package/docs/guides/llm-handlers.md +63 -7
- package/docs/guides/manifest.md +10 -1
- package/docs/index.md +1 -1
- package/docs/operations/architecture.md +6 -4
- package/docs/operations/monitoring.md +1 -1
- package/docs/plugins/creating-plugins.md +1 -1
- package/docs/reference/config-files.md +1 -1
- package/docs/reference/types.md +15 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,7 +101,7 @@ handlers:
|
|
|
101
101
|
timeout: 3000
|
|
102
102
|
enabled: true
|
|
103
103
|
|
|
104
|
-
# LLM handler -- calls Anthropic Messages API
|
|
104
|
+
# LLM handler -- calls Anthropic Messages API (default backend)
|
|
105
105
|
- id: code-review
|
|
106
106
|
type: llm
|
|
107
107
|
model: claude-haiku-4-5
|
|
@@ -119,6 +119,14 @@ handlers:
|
|
|
119
119
|
batchGroup: analysis
|
|
120
120
|
agent: "builder" # only fire in builder agent sessions
|
|
121
121
|
|
|
122
|
+
# LLM handler -- spawns Claude Code CLI with an agent
|
|
123
|
+
- id: agent-review
|
|
124
|
+
type: llm
|
|
125
|
+
backend: claude-code # spawn claude CLI instead of API call
|
|
126
|
+
llmAgent: security-reviewer # --agent flag
|
|
127
|
+
prompt: "Audit this change: $TOOL_NAME $ARGUMENTS"
|
|
128
|
+
filter: "Bash|Write"
|
|
129
|
+
|
|
122
130
|
UserPromptSubmit:
|
|
123
131
|
# Inline handler -- imports a JS module in-process (no subprocess)
|
|
124
132
|
- id: prompt-logger
|
|
@@ -152,7 +160,7 @@ settings:
|
|
|
152
160
|
|
|
153
161
|
**inline** -- imports a JS module and calls its default export. No subprocess overhead.
|
|
154
162
|
|
|
155
|
-
**llm** --
|
|
163
|
+
**llm** -- AI-powered analysis with `$VARIABLE` interpolation. Two backends: `api` (default, Anthropic API with batching and cost tracking) and `claude-code` (CLI spawn with agent support).
|
|
156
164
|
|
|
157
165
|
## Handler Fields Reference
|
|
158
166
|
|
|
@@ -162,15 +170,17 @@ settings:
|
|
|
162
170
|
| `type` | string | required | all | `script`, `inline`, or `llm` |
|
|
163
171
|
| `command` | string | required | script | Shell command to execute |
|
|
164
172
|
| `module` | string | required | inline | Path to JS module with default export |
|
|
165
|
-
| `model` | string | required | llm | `claude-haiku-4-5`, `claude-sonnet-4-6`, or `claude-opus-4-6` |
|
|
173
|
+
| `model` | string | required | llm | `claude-haiku-4-5`, `claude-sonnet-4-6`, or `claude-opus-4-6`. Required for `api` backend, optional for `claude-code`. |
|
|
166
174
|
| `prompt` | string | required | llm | Prompt template with `$VARIABLE` interpolation |
|
|
175
|
+
| `backend` | string | `api` | llm | `api` (Anthropic API) or `claude-code` (CLI spawn) |
|
|
176
|
+
| `llmAgent` | string | -- | llm | Agent name for `claude-code` backend (`--agent` flag) |
|
|
167
177
|
| `filter` | string | -- | all | Keyword filter (see Filtering) |
|
|
168
178
|
| `project` | string | -- | all | Glob pattern matched against cwd |
|
|
169
179
|
| `agent` | string | -- | all | Only fire when session agent matches |
|
|
170
180
|
| `async` | boolean | `false` | all | Fire-and-forget, don't block response |
|
|
171
181
|
| `depends` | string[] | -- | all | Handler IDs to wait for before executing |
|
|
172
182
|
| `sessionIsolation` | boolean | `false` | all | Reset handler state on SessionStart |
|
|
173
|
-
| `batchGroup` | string | -- | llm | Group ID for batching into one API call |
|
|
183
|
+
| `batchGroup` | string | -- | llm | Group ID for batching into one API call (`api` backend only) |
|
|
174
184
|
| `maxTokens` | number | `1024` | llm | Maximum output tokens |
|
|
175
185
|
| `temperature` | number | `1.0` | llm | Sampling temperature |
|
|
176
186
|
| `timeout` | number | `5000`/`30000` | all | Timeout in ms (5s default, 30s for llm) |
|
|
@@ -213,13 +223,17 @@ filter: "word1|!word2" # run if word1 present AND word2 absent
|
|
|
213
223
|
|
|
214
224
|
## LLM Handlers
|
|
215
225
|
|
|
216
|
-
|
|
226
|
+
LLM handlers support two backends: `api` (default, Anthropic Messages API) and `claude-code` (spawns `claude` CLI).
|
|
227
|
+
|
|
228
|
+
**API backend setup:**
|
|
217
229
|
|
|
218
230
|
```bash
|
|
219
|
-
npm install @anthropic-ai/sdk # peer dependency, required only for
|
|
231
|
+
npm install @anthropic-ai/sdk # peer dependency, required only for api backend
|
|
220
232
|
export ANTHROPIC_API_KEY=sk-... # or set in manifest: settings.anthropicApiKey
|
|
221
233
|
```
|
|
222
234
|
|
|
235
|
+
**Claude Code backend** requires no API key or SDK — just the `claude` CLI installed and authenticated. Supports the `llmAgent` field for running prompts with a specific agent (`--agent`).
|
|
236
|
+
|
|
223
237
|
**Prompt template variables:**
|
|
224
238
|
|
|
225
239
|
| Variable | Source | Description |
|
|
@@ -234,7 +248,7 @@ export ANTHROPIC_API_KEY=sk-... # or set in manifest: settings.anthropicApiKey
|
|
|
234
248
|
|
|
235
249
|
`$TRANSCRIPT`, `$GIT_STATUS`, and `$GIT_DIFF` require the corresponding key in `prefetch`. The others are always available from the hook input.
|
|
236
250
|
|
|
237
|
-
**Batching:** Handlers sharing a `batchGroup` on the same event are combined into a single API call. Three Haiku calls become one, saving ~2/3 of input token cost and eliminating two round-trips. Batch groups are scoped per session to prevent cross-session contamination.
|
|
251
|
+
**Batching (API backend only):** Handlers sharing a `batchGroup` on the same event are combined into a single API call. Three Haiku calls become one, saving ~2/3 of input token cost and eliminating two round-trips. Batch groups are scoped per session to prevent cross-session contamination. Claude Code backend handlers always execute individually.
|
|
238
252
|
|
|
239
253
|
## Async Handlers
|
|
240
254
|
|
|
@@ -408,7 +422,7 @@ src/
|
|
|
408
422
|
manifest.ts Manifest loading and validation
|
|
409
423
|
metrics.ts Metrics collection and aggregation
|
|
410
424
|
tui.ts Interactive terminal dashboard (ANSI-based)
|
|
411
|
-
llm.ts Anthropic API
|
|
425
|
+
llm.ts LLM execution (Anthropic API + Claude Code CLI) and batching
|
|
412
426
|
filter.ts Keyword filter engine
|
|
413
427
|
prefetch.ts Pre-fetch context (transcript, git status/diff)
|
|
414
428
|
plugin.ts Plugin install/remove/list
|
package/agents/clooks.md
CHANGED
|
@@ -33,7 +33,7 @@ You can run clooks CLI commands:
|
|
|
33
33
|
### Handler types
|
|
34
34
|
- **script** — spawns `sh -c "command"`, pipes hook JSON to stdin, reads stdout (~5-35ms)
|
|
35
35
|
- **inline** — imports a JS module, calls default export in-process (~0ms after first load)
|
|
36
|
-
- **llm** —
|
|
36
|
+
- **llm** — AI-powered analysis with prompt template and $VARIABLE interpolation. Two backends: `api` (Anthropic API, default) and `claude-code` (spawns `claude` CLI)
|
|
37
37
|
|
|
38
38
|
### Handler fields
|
|
39
39
|
Every handler has:
|
|
@@ -49,9 +49,11 @@ Every handler has:
|
|
|
49
49
|
- `enabled` — boolean
|
|
50
50
|
|
|
51
51
|
### LLM handler extra fields
|
|
52
|
-
- `model` — claude-haiku-4-5, claude-sonnet-4-6, or claude-opus-4-6
|
|
52
|
+
- `model` — claude-haiku-4-5, claude-sonnet-4-6, or claude-opus-4-6 (required for `api` backend, optional for `claude-code`)
|
|
53
53
|
- `prompt` — template with $TRANSCRIPT, $GIT_STATUS, $GIT_DIFF, $ARGUMENTS, $TOOL_NAME, $PROMPT, $CWD
|
|
54
|
-
- `
|
|
54
|
+
- `backend` — `api` (default, Anthropic API) or `claude-code` (spawns `claude -p`)
|
|
55
|
+
- `llmAgent` — agent name for `claude-code` backend (passes `--agent` to CLI)
|
|
56
|
+
- `batchGroup` — handlers with same group + same session = one API call (`api` backend only)
|
|
55
57
|
- `maxTokens`, `temperature`
|
|
56
58
|
|
|
57
59
|
### Plugin system
|
|
@@ -82,6 +84,13 @@ handlers:
|
|
|
82
84
|
batchGroup: analysis
|
|
83
85
|
async: true
|
|
84
86
|
|
|
87
|
+
- id: agent-review
|
|
88
|
+
type: llm
|
|
89
|
+
backend: claude-code
|
|
90
|
+
llmAgent: reviewer
|
|
91
|
+
prompt: "Review this prompt for clarity: $PROMPT"
|
|
92
|
+
async: true
|
|
93
|
+
|
|
85
94
|
Stop:
|
|
86
95
|
- id: session-logger
|
|
87
96
|
type: inline
|
|
@@ -134,7 +143,8 @@ SessionStart includes a command hook for `clooks ensure-running` that auto-start
|
|
|
134
143
|
- Mark non-blocking handlers as `async: true`
|
|
135
144
|
- Use `filter` to skip irrelevant invocations
|
|
136
145
|
- Use `project`/`agent` to scope handlers to relevant contexts
|
|
137
|
-
- Batch LLM handlers with `batchGroup`
|
|
146
|
+
- Batch `api` backend LLM handlers with `batchGroup`
|
|
147
|
+
- Use `claude-code` backend when agent capabilities are needed or to avoid API key management
|
|
138
148
|
- Use `prefetch` to avoid redundant file reads
|
|
139
149
|
|
|
140
150
|
### Writing new handlers
|
package/dist/llm.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare function resetClient(): void;
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function calculateCost(model: string, usage: TokenUsage): number;
|
|
8
8
|
/**
|
|
9
|
-
* Execute a single LLM handler
|
|
9
|
+
* Execute a single LLM handler, dispatching to the appropriate backend.
|
|
10
10
|
*/
|
|
11
11
|
export declare function executeLLMHandler(handler: LLMHandlerConfig, input: HookInput, context: PrefetchContext): Promise<HandlerResult>;
|
|
12
12
|
/**
|
package/dist/llm.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// clooks LLM handler execution — Anthropic Messages API with batching
|
|
2
|
+
// clooks LLM handler execution — Anthropic Messages API with batching, Claude Code CLI spawn
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.resetClient = resetClient;
|
|
5
5
|
exports.calculateCost = calculateCost;
|
|
6
6
|
exports.executeLLMHandler = executeLLMHandler;
|
|
7
7
|
exports.executeLLMHandlersBatched = executeLLMHandlersBatched;
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
8
9
|
const prefetch_js_1 = require("./prefetch.js");
|
|
9
10
|
const constants_js_1 = require("./constants.js");
|
|
10
11
|
/** Lazy-loaded Anthropic SDK client */
|
|
@@ -44,17 +45,27 @@ function calculateCost(model, usage) {
|
|
|
44
45
|
return inputCost + outputCost;
|
|
45
46
|
}
|
|
46
47
|
/**
|
|
47
|
-
* Execute a single LLM handler
|
|
48
|
+
* Execute a single LLM handler, dispatching to the appropriate backend.
|
|
48
49
|
*/
|
|
49
50
|
async function executeLLMHandler(handler, input, context) {
|
|
51
|
+
if (handler.backend === 'claude-code') {
|
|
52
|
+
return executeClaudeCodeHandler(handler, input, context);
|
|
53
|
+
}
|
|
54
|
+
return executeAPIHandler(handler, input, context);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Execute via Anthropic Messages API.
|
|
58
|
+
*/
|
|
59
|
+
async function executeAPIHandler(handler, input, context) {
|
|
50
60
|
const start = performance.now();
|
|
51
61
|
const timeout = handler.timeout ?? constants_js_1.DEFAULT_LLM_TIMEOUT;
|
|
52
62
|
const maxTokens = handler.maxTokens ?? constants_js_1.DEFAULT_LLM_MAX_TOKENS;
|
|
53
63
|
try {
|
|
54
64
|
const client = await getClient();
|
|
55
65
|
const prompt = (0, prefetch_js_1.renderPromptTemplate)(handler.prompt, input, context);
|
|
66
|
+
const model = handler.model; // Guaranteed by manifest validation for api backend
|
|
56
67
|
const apiCall = client.messages.create({
|
|
57
|
-
model
|
|
68
|
+
model,
|
|
58
69
|
max_tokens: maxTokens,
|
|
59
70
|
messages: [{ role: 'user', content: prompt }],
|
|
60
71
|
});
|
|
@@ -65,7 +76,7 @@ async function executeLLMHandler(handler, input, context) {
|
|
|
65
76
|
input_tokens: response.usage?.input_tokens ?? 0,
|
|
66
77
|
output_tokens: response.usage?.output_tokens ?? 0,
|
|
67
78
|
};
|
|
68
|
-
const cost_usd = calculateCost(
|
|
79
|
+
const cost_usd = calculateCost(model, usage);
|
|
69
80
|
return {
|
|
70
81
|
id: handler.id,
|
|
71
82
|
ok: true,
|
|
@@ -84,6 +95,72 @@ async function executeLLMHandler(handler, input, context) {
|
|
|
84
95
|
};
|
|
85
96
|
}
|
|
86
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Execute via Claude Code CLI spawn (`claude -p "prompt"`).
|
|
100
|
+
* Supports --agent and --model flags.
|
|
101
|
+
*/
|
|
102
|
+
function executeClaudeCodeHandler(handler, input, context) {
|
|
103
|
+
const start = performance.now();
|
|
104
|
+
const timeout = handler.timeout ?? constants_js_1.DEFAULT_LLM_TIMEOUT;
|
|
105
|
+
const prompt = (0, prefetch_js_1.renderPromptTemplate)(handler.prompt, input, context);
|
|
106
|
+
const args = ['-p', prompt, '--output-format', 'text'];
|
|
107
|
+
if (handler.llmAgent) {
|
|
108
|
+
args.push('--agent', handler.llmAgent);
|
|
109
|
+
}
|
|
110
|
+
if (handler.model) {
|
|
111
|
+
args.push('--model', handler.model);
|
|
112
|
+
}
|
|
113
|
+
if (handler.maxTokens) {
|
|
114
|
+
args.push('--max-tokens', String(handler.maxTokens));
|
|
115
|
+
}
|
|
116
|
+
return new Promise((resolve) => {
|
|
117
|
+
const child = (0, child_process_1.spawn)('claude', args, {
|
|
118
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
119
|
+
timeout,
|
|
120
|
+
env: { ...process.env },
|
|
121
|
+
});
|
|
122
|
+
let stdout = '';
|
|
123
|
+
let stderr = '';
|
|
124
|
+
child.stdout.on('data', (data) => {
|
|
125
|
+
stdout += data.toString();
|
|
126
|
+
});
|
|
127
|
+
child.stderr.on('data', (data) => {
|
|
128
|
+
stderr += data.toString();
|
|
129
|
+
});
|
|
130
|
+
child.stdin.end();
|
|
131
|
+
const timer = setTimeout(() => {
|
|
132
|
+
child.kill('SIGTERM');
|
|
133
|
+
}, timeout);
|
|
134
|
+
child.on('close', (code) => {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
const duration_ms = performance.now() - start;
|
|
137
|
+
if (code !== 0) {
|
|
138
|
+
resolve({
|
|
139
|
+
id: handler.id,
|
|
140
|
+
ok: false,
|
|
141
|
+
error: `claude exit code ${code}${stderr ? ': ' + stderr.trim() : ''}`,
|
|
142
|
+
duration_ms,
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
resolve({
|
|
147
|
+
id: handler.id,
|
|
148
|
+
ok: true,
|
|
149
|
+
output: { additionalContext: stdout.trim() },
|
|
150
|
+
duration_ms,
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
child.on('error', (err) => {
|
|
154
|
+
clearTimeout(timer);
|
|
155
|
+
resolve({
|
|
156
|
+
id: handler.id,
|
|
157
|
+
ok: false,
|
|
158
|
+
error: `Claude Code spawn error: ${err.message}`,
|
|
159
|
+
duration_ms: performance.now() - start,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
87
164
|
/**
|
|
88
165
|
* Execute a batched group of LLM handlers: combine prompts into a single
|
|
89
166
|
* multi-task API call, parse JSON response back into individual results.
|
|
@@ -91,7 +168,7 @@ async function executeLLMHandler(handler, input, context) {
|
|
|
91
168
|
async function executeBatchGroup(handlers, input, context) {
|
|
92
169
|
const start = performance.now();
|
|
93
170
|
// Use model from first handler; warn if others differ
|
|
94
|
-
const model = handlers[0].model;
|
|
171
|
+
const model = handlers[0].model; // Guaranteed by manifest validation — batching only applies to api backend
|
|
95
172
|
for (let i = 1; i < handlers.length; i++) {
|
|
96
173
|
if (handlers[i].model !== model) {
|
|
97
174
|
console.warn(`[clooks] Batch group "${handlers[0].batchGroup}": handler "${handlers[i].id}" ` +
|
|
@@ -192,10 +269,14 @@ function splitUsage(total, count) {
|
|
|
192
269
|
*/
|
|
193
270
|
async function executeLLMHandlersBatched(handlers, input, context, sessionId) {
|
|
194
271
|
// Group by batchGroup, scoped by sessionId to prevent cross-session batching
|
|
272
|
+
// claude-code handlers can't be batched — always run individually
|
|
195
273
|
const grouped = new Map();
|
|
196
274
|
const ungrouped = [];
|
|
197
275
|
for (const handler of handlers) {
|
|
198
|
-
if (handler.
|
|
276
|
+
if (handler.backend === 'claude-code') {
|
|
277
|
+
ungrouped.push(handler);
|
|
278
|
+
}
|
|
279
|
+
else if (handler.batchGroup) {
|
|
199
280
|
// Scope the batch key by sessionId so different sessions never batch together
|
|
200
281
|
const batchKey = sessionId
|
|
201
282
|
? `${handler.batchGroup}:${sessionId}`
|
package/dist/manifest.js
CHANGED
|
@@ -63,15 +63,38 @@ function validateManifest(manifest) {
|
|
|
63
63
|
}
|
|
64
64
|
if (handler.type === 'llm') {
|
|
65
65
|
const llm = handler;
|
|
66
|
-
if (!llm.model) {
|
|
67
|
-
throw new Error(`LLM handler "${handler.id}" must have a "model" field`);
|
|
68
|
-
}
|
|
69
66
|
if (!llm.prompt) {
|
|
70
67
|
throw new Error(`LLM handler "${handler.id}" must have a "prompt" field`);
|
|
71
68
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
// Validate backend
|
|
70
|
+
const validBackends = ['api', 'claude-code'];
|
|
71
|
+
if (llm.backend && !validBackends.includes(llm.backend)) {
|
|
72
|
+
throw new Error(`LLM handler "${handler.id}" backend must be one of: ${validBackends.join(', ')}`);
|
|
73
|
+
}
|
|
74
|
+
// llmAgent is only valid with claude-code backend
|
|
75
|
+
if (llm.llmAgent && llm.backend !== 'claude-code') {
|
|
76
|
+
throw new Error(`LLM handler "${handler.id}" llmAgent requires backend: claude-code`);
|
|
77
|
+
}
|
|
78
|
+
// model is required for api backend, optional for claude-code
|
|
79
|
+
if (llm.backend !== 'claude-code') {
|
|
80
|
+
if (!llm.model) {
|
|
81
|
+
throw new Error(`LLM handler "${handler.id}" must have a "model" field`);
|
|
82
|
+
}
|
|
83
|
+
const validModels = ['claude-haiku-4-5', 'claude-sonnet-4-6', 'claude-opus-4-6'];
|
|
84
|
+
if (!validModels.includes(llm.model)) {
|
|
85
|
+
throw new Error(`LLM handler "${handler.id}" model must be one of: ${validModels.join(', ')}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (llm.model) {
|
|
89
|
+
// claude-code backend with explicit model — still validate it
|
|
90
|
+
const validModels = ['claude-haiku-4-5', 'claude-sonnet-4-6', 'claude-opus-4-6'];
|
|
91
|
+
if (!validModels.includes(llm.model)) {
|
|
92
|
+
throw new Error(`LLM handler "${handler.id}" model must be one of: ${validModels.join(', ')}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// batchGroup is incompatible with claude-code backend
|
|
96
|
+
if (llm.batchGroup && llm.backend === 'claude-code') {
|
|
97
|
+
console.warn(`[clooks] Warning: LLM handler "${handler.id}" has batchGroup but uses claude-code backend — batching will be ignored`);
|
|
75
98
|
}
|
|
76
99
|
}
|
|
77
100
|
// Validate async field type
|
package/dist/server.js
CHANGED
|
@@ -91,11 +91,15 @@ function readBody(req) {
|
|
|
91
91
|
}
|
|
92
92
|
function sendJson(res, status, data) {
|
|
93
93
|
const body = JSON.stringify(data);
|
|
94
|
-
res.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
res.socket?.on('error', () => { }); // suppress EPIPE if client disconnected early
|
|
95
|
+
try {
|
|
96
|
+
res.writeHead(status, {
|
|
97
|
+
'Content-Type': 'application/json',
|
|
98
|
+
'Content-Length': Buffer.byteLength(body),
|
|
99
|
+
});
|
|
100
|
+
res.end(body);
|
|
101
|
+
}
|
|
102
|
+
catch (_) { }
|
|
99
103
|
}
|
|
100
104
|
/**
|
|
101
105
|
* Create the HTTP server for hook handling.
|
package/dist/types.d.ts
CHANGED
|
@@ -17,14 +17,18 @@ export interface HookInput {
|
|
|
17
17
|
}
|
|
18
18
|
/** Supported LLM models */
|
|
19
19
|
export type LLMModel = 'claude-haiku-4-5' | 'claude-sonnet-4-6' | 'claude-opus-4-6';
|
|
20
|
+
/** LLM execution backend */
|
|
21
|
+
export type LLMBackend = 'api' | 'claude-code';
|
|
20
22
|
/** Handler types — extended with 'llm' */
|
|
21
23
|
export type HandlerType = 'script' | 'inline' | 'llm';
|
|
22
24
|
/** LLM-specific handler config fields */
|
|
23
25
|
export interface LLMHandlerConfig {
|
|
24
26
|
id: string;
|
|
25
27
|
type: 'llm';
|
|
26
|
-
model
|
|
28
|
+
model?: LLMModel;
|
|
27
29
|
prompt: string;
|
|
30
|
+
backend?: LLMBackend;
|
|
31
|
+
llmAgent?: string;
|
|
28
32
|
batchGroup?: string;
|
|
29
33
|
maxTokens?: number;
|
|
30
34
|
temperature?: number;
|
|
@@ -68,7 +68,7 @@ This launches an interactive TUI showing execution counts, latency, and errors p
|
|
|
68
68
|
## What to try next
|
|
69
69
|
|
|
70
70
|
- Add a `filter` to scope handlers to specific tools
|
|
71
|
-
- Try an `llm` handler for AI-powered review
|
|
71
|
+
- Try an `llm` handler for AI-powered review (use `backend: claude-code` to skip API key setup)
|
|
72
72
|
- Run `clooks migrate` to convert existing command hooks
|
|
73
73
|
|
|
74
74
|
---
|
package/docs/guides/handlers.md
CHANGED
|
@@ -119,28 +119,31 @@ export default async function(input: HookInput) {
|
|
|
119
119
|
|
|
120
120
|
## LLM Handlers
|
|
121
121
|
|
|
122
|
-
LLM handlers
|
|
122
|
+
LLM handlers run AI-powered analysis with prompt templates. They support two backends: the Anthropic Messages API (`api`, default) and Claude Code CLI spawn (`claude-code`).
|
|
123
123
|
|
|
124
124
|
### How They Work
|
|
125
125
|
|
|
126
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
|
|
127
|
+
2. **API backend:** The rendered prompt is sent to the Anthropic API using the specified `model`.
|
|
128
|
+
3. **Claude Code backend:** The rendered prompt is passed to `claude -p`, optionally with `--agent` and `--model`.
|
|
129
|
+
4. The response text is returned as `{"additionalContext": "..."}`.
|
|
129
130
|
|
|
130
131
|
### Required Fields
|
|
131
132
|
|
|
132
133
|
| Field | Type | Description |
|
|
133
134
|
|-------|------|-------------|
|
|
134
|
-
| `model` | string | `claude-haiku-4-5`, `claude-sonnet-4-6`, or `claude-opus-4-6` |
|
|
135
135
|
| `prompt` | string | Prompt template with `$VARIABLE` interpolation |
|
|
136
|
+
| `model` | string | `claude-haiku-4-5`, `claude-sonnet-4-6`, or `claude-opus-4-6`. Required for `api` backend, optional for `claude-code`. |
|
|
136
137
|
|
|
137
138
|
### Optional Fields
|
|
138
139
|
|
|
139
140
|
| Field | Type | Default | Description |
|
|
140
141
|
|-------|------|---------|-------------|
|
|
141
|
-
| `
|
|
142
|
+
| `backend` | string | `api` | `api` (Anthropic API) or `claude-code` (CLI spawn) |
|
|
143
|
+
| `llmAgent` | string | — | Agent name for `claude-code` backend (`--agent` flag) |
|
|
144
|
+
| `maxTokens` | number | 1024 | Maximum tokens in the response |
|
|
142
145
|
| `temperature` | number | 1.0 | Sampling temperature |
|
|
143
|
-
| `batchGroup` | string | — | Group ID for batching
|
|
146
|
+
| `batchGroup` | string | — | Group ID for batching into one API call (`api` backend only) |
|
|
144
147
|
|
|
145
148
|
### Default Timeout
|
|
146
149
|
|
|
@@ -166,6 +169,7 @@ prefetch:
|
|
|
166
169
|
|
|
167
170
|
handlers:
|
|
168
171
|
PreToolUse:
|
|
172
|
+
# API backend (default) — fast, supports batching and cost tracking
|
|
169
173
|
- id: code-reviewer
|
|
170
174
|
type: llm
|
|
171
175
|
model: claude-haiku-4-5
|
|
@@ -178,9 +182,17 @@ handlers:
|
|
|
178
182
|
If there is a problem, explain it briefly. Otherwise say "Looks good."
|
|
179
183
|
filter: "Write|Edit"
|
|
180
184
|
maxTokens: 256
|
|
185
|
+
|
|
186
|
+
# Claude Code backend — supports agents, no API key needed
|
|
187
|
+
- id: agent-review
|
|
188
|
+
type: llm
|
|
189
|
+
backend: claude-code
|
|
190
|
+
llmAgent: security-reviewer
|
|
191
|
+
prompt: "Audit this tool call for security issues: $TOOL_NAME $ARGUMENTS"
|
|
192
|
+
filter: "Bash|Write"
|
|
181
193
|
```
|
|
182
194
|
|
|
183
|
-
See [LLM Handlers](llm-handlers.md) for batching, cost tracking, and advanced usage.
|
|
195
|
+
See [LLM Handlers](llm-handlers.md) for backends, batching, cost tracking, and advanced usage.
|
|
184
196
|
|
|
185
197
|
## Handler Output Format
|
|
186
198
|
|
|
@@ -1,16 +1,57 @@
|
|
|
1
1
|
# LLM Handlers
|
|
2
2
|
|
|
3
|
-
LLM handlers
|
|
3
|
+
LLM handlers run AI-powered analysis from the manifest, with prompt templates, automatic batching, and cost tracking. Two backends are available: the **Anthropic Messages API** (default) and **Claude Code CLI** spawn. This guide covers advanced usage beyond the basics in [Handlers](handlers.md).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Backends
|
|
6
6
|
|
|
7
|
-
LLM handlers
|
|
7
|
+
LLM handlers support two execution backends via the `backend` field:
|
|
8
|
+
|
|
9
|
+
| Backend | Value | Description |
|
|
10
|
+
|---------|-------|-------------|
|
|
11
|
+
| Anthropic API | `api` (default) | Direct API call. Supports batching, cost tracking, token usage. Requires API key and SDK. |
|
|
12
|
+
| Claude Code CLI | `claude-code` | Spawns `claude -p "prompt"`. Supports agents. No API key needed — uses your Claude Code subscription. |
|
|
13
|
+
|
|
14
|
+
### API Backend (default)
|
|
15
|
+
|
|
16
|
+
The `api` backend requires an Anthropic API key. Provide it in one of two ways:
|
|
8
17
|
|
|
9
18
|
1. **Environment variable:** `ANTHROPIC_API_KEY=sk-ant-...`
|
|
10
19
|
2. **Manifest setting:** `settings.anthropicApiKey: sk-ant-...`
|
|
11
20
|
|
|
12
21
|
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
22
|
|
|
23
|
+
### Claude Code Backend
|
|
24
|
+
|
|
25
|
+
The `claude-code` backend spawns a `claude` CLI process. It requires the Claude Code CLI to be installed and authenticated. No API key or SDK is needed.
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
- id: deep-review
|
|
29
|
+
type: llm
|
|
30
|
+
backend: claude-code
|
|
31
|
+
prompt: "Review this tool call: $TOOL_NAME $ARGUMENTS"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Key differences from the API backend:
|
|
35
|
+
|
|
36
|
+
- **No cost tracking** — usage is billed through your Claude Code subscription, not per-token.
|
|
37
|
+
- **No batching** — each handler spawns its own `claude` process. `batchGroup` is ignored.
|
|
38
|
+
- **Agent support** — use `llmAgent` to run the prompt with a specific agent.
|
|
39
|
+
- **Model is optional** — omit `model` to use Claude Code's default, or specify one to override.
|
|
40
|
+
|
|
41
|
+
#### Using Agents
|
|
42
|
+
|
|
43
|
+
The `llmAgent` field passes `--agent <name>` to the Claude Code CLI, running the prompt with a specific agent's instructions and tools:
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
- id: agent-review
|
|
47
|
+
type: llm
|
|
48
|
+
backend: claude-code
|
|
49
|
+
llmAgent: security-reviewer
|
|
50
|
+
prompt: "Audit this change for vulnerabilities: $ARGUMENTS"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
> **Note:** `llmAgent` is only valid with `backend: claude-code`. Using it with the `api` backend produces a validation error.
|
|
54
|
+
|
|
14
55
|
### Supported Models
|
|
15
56
|
|
|
16
57
|
| Model | Best For |
|
|
@@ -19,6 +60,8 @@ The Anthropic SDK is lazy-loaded on the first LLM handler invocation. If the SDK
|
|
|
19
60
|
| `claude-sonnet-4-6` | Balanced analysis (code review, context synthesis) |
|
|
20
61
|
| `claude-opus-4-6` | Deep reasoning (security audits, architecture review) |
|
|
21
62
|
|
|
63
|
+
`model` is required for the `api` backend. For `claude-code`, it is optional — when provided, it is passed as `--model` to the CLI.
|
|
64
|
+
|
|
22
65
|
## Prompt Templates
|
|
23
66
|
|
|
24
67
|
Prompts support `$VARIABLE` interpolation. Variables are replaced with actual values before the API call.
|
|
@@ -56,9 +99,9 @@ handlers:
|
|
|
56
99
|
Flag any issues. Be concise.
|
|
57
100
|
```
|
|
58
101
|
|
|
59
|
-
## Batching
|
|
102
|
+
## Batching (API Backend Only)
|
|
60
103
|
|
|
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.
|
|
104
|
+
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. Batching only applies to handlers using the `api` backend — `claude-code` handlers always execute individually.
|
|
62
105
|
|
|
63
106
|
### How It Works
|
|
64
107
|
|
|
@@ -128,15 +171,28 @@ Pricing per million tokens (as of March 2026):
|
|
|
128
171
|
|
|
129
172
|
For batched calls, the total token cost is split evenly across all handlers in the group.
|
|
130
173
|
|
|
174
|
+
## Choosing a Backend
|
|
175
|
+
|
|
176
|
+
| Consideration | `api` | `claude-code` |
|
|
177
|
+
|---------------|-------|---------------|
|
|
178
|
+
| Latency | Lower (direct HTTP) | Higher (process spawn) |
|
|
179
|
+
| Cost model | Per-token (tracked) | Subscription (not tracked) |
|
|
180
|
+
| Batching | Yes | No |
|
|
181
|
+
| Agent support | No | Yes (`llmAgent`) |
|
|
182
|
+
| Requires API key | Yes | No |
|
|
183
|
+
| Requires SDK | Yes | No |
|
|
184
|
+
|
|
185
|
+
**Use `api`** for high-frequency, latency-sensitive handlers (guards, quick checks). **Use `claude-code`** when you need agent capabilities, don't want to manage API keys, or want handlers to use your subscription.
|
|
186
|
+
|
|
131
187
|
## Best Practices
|
|
132
188
|
|
|
133
189
|
**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
190
|
|
|
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).
|
|
191
|
+
**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). Only applies to the `api` backend.
|
|
136
192
|
|
|
137
193
|
**Set maxTokens conservatively.** Most handler responses are short. Setting `maxTokens: 256` or `maxTokens: 512` prevents runaway token usage on verbose responses.
|
|
138
194
|
|
|
139
|
-
**Use filter to avoid unnecessary
|
|
195
|
+
**Use filter to avoid unnecessary calls.** An LLM handler without a filter fires on every matching event. Adding `filter: "Write|Edit"` ensures the handler is only invoked when relevant tools are used.
|
|
140
196
|
|
|
141
197
|
**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
198
|
|
package/docs/guides/manifest.md
CHANGED
|
@@ -137,7 +137,9 @@ Run `clooks doctor` to validate your manifest structure. The validator enforces:
|
|
|
137
137
|
- Handler IDs must be unique across the entire manifest (not just within an event).
|
|
138
138
|
- Script handlers must have a `command` field.
|
|
139
139
|
- Inline handlers must have a `module` field.
|
|
140
|
-
- LLM handlers must have
|
|
140
|
+
- LLM handlers must have a `prompt` field. The `model` field is required for the `api` backend (default) and optional for `claude-code`. Model must be one of: `claude-haiku-4-5`, `claude-sonnet-4-6`, `claude-opus-4-6`.
|
|
141
|
+
- LLM handler `backend` must be `api` or `claude-code` (if specified).
|
|
142
|
+
- `llmAgent` is only valid when `backend` is `claude-code`.
|
|
141
143
|
- `prefetch` must be an array containing only valid keys: `transcript`, `git_status`, `git_diff`.
|
|
142
144
|
- `settings.port` must be a number between 1 and 65535.
|
|
143
145
|
- `settings.logLevel` must be one of: `debug`, `info`, `warn`, `error`.
|
|
@@ -211,6 +213,13 @@ handlers:
|
|
|
211
213
|
depends: [write-review, style-check]
|
|
212
214
|
filter: "Write"
|
|
213
215
|
|
|
216
|
+
- id: agent-audit
|
|
217
|
+
type: llm
|
|
218
|
+
backend: claude-code
|
|
219
|
+
llmAgent: security-reviewer
|
|
220
|
+
prompt: "Audit this Bash command for security: $ARGUMENTS"
|
|
221
|
+
filter: "Bash"
|
|
222
|
+
|
|
214
223
|
PostToolUse:
|
|
215
224
|
- id: metrics-collector
|
|
216
225
|
type: inline
|
package/docs/index.md
CHANGED
|
@@ -53,7 +53,7 @@ docs/
|
|
|
53
53
|
|
|
54
54
|
- [Manifest](guides/manifest.md) -- manifest.yaml structure and fields
|
|
55
55
|
- [Handlers](guides/handlers.md) -- script, inline, and LLM handler types
|
|
56
|
-
- [LLM Handlers](guides/llm-handlers.md) -- prompt templates, batching, cost tracking
|
|
56
|
+
- [LLM Handlers](guides/llm-handlers.md) -- prompt templates, batching, cost tracking, Claude Code CLI backend
|
|
57
57
|
- [Filtering](guides/filtering.md) -- keyword-based handler filtering
|
|
58
58
|
- [Dependencies](guides/dependencies.md) -- topological execution waves
|
|
59
59
|
- [Async Handlers](guides/async-handlers.md) -- fire-and-forget execution
|
|
@@ -38,7 +38,8 @@ clooks daemon (localhost:7890)
|
|
|
38
38
|
+-- Execute each wave
|
|
39
39
|
| +-- Script handlers: spawn sh -c, pipe JSON stdin
|
|
40
40
|
| +-- Inline handlers: dynamic ES module import
|
|
41
|
-
| +-- LLM handlers: Anthropic API (batched by group)
|
|
41
|
+
| +-- LLM handlers (api): Anthropic API (batched by group)
|
|
42
|
+
| +-- LLM handlers (claude-code): spawn claude CLI (with optional --agent)
|
|
42
43
|
|
|
|
43
44
|
+-- Record metrics + costs
|
|
44
45
|
+-- Track deny decisions (short-circuit cache)
|
|
@@ -68,9 +69,9 @@ Dependency resolution produces "waves" — groups of handlers that can run in pa
|
|
|
68
69
|
- **Correct ordering** across waves
|
|
69
70
|
- **Data flow** — outputs from earlier waves are available to later waves
|
|
70
71
|
|
|
71
|
-
### LLM Batching
|
|
72
|
+
### LLM Batching (API Backend)
|
|
72
73
|
|
|
73
|
-
Multiple LLM handlers with the same `batchGroup` are combined into a single API call. One prompt with multiple tasks, one response parsed and distributed. This typically halves cost and latency for multi-handler analysis.
|
|
74
|
+
Multiple LLM handlers using the `api` backend with the same `batchGroup` are combined into a single API call. One prompt with multiple tasks, one response parsed and distributed. This typically halves cost and latency for multi-handler analysis. Handlers using the `claude-code` backend always execute individually.
|
|
74
75
|
|
|
75
76
|
### Hot Reload
|
|
76
77
|
|
|
@@ -90,7 +91,8 @@ When a PreToolUse handler blocks a tool, PostToolUse is skipped. The denial is c
|
|
|
90
91
|
- **Main process:** HTTP server, manifest loading, file watching, metric collection
|
|
91
92
|
- **Script handlers:** Spawned as child processes (`sh -c`), piped JSON on stdin/stdout
|
|
92
93
|
- **Inline handlers:** Loaded as ES modules in the main process (no subprocess)
|
|
93
|
-
- **LLM handlers:** Async HTTP calls to Anthropic API from the main process
|
|
94
|
+
- **LLM handlers (api):** Async HTTP calls to Anthropic API from the main process
|
|
95
|
+
- **LLM handlers (claude-code):** Spawned as `claude -p` child processes (with optional `--agent`)
|
|
94
96
|
|
|
95
97
|
## Resilience
|
|
96
98
|
|
|
@@ -37,7 +37,7 @@ clooks stats | less # Auto-detects piped output, switches to text
|
|
|
37
37
|
|
|
38
38
|
## Cost Tracking
|
|
39
39
|
|
|
40
|
-
LLM handler costs are logged to `~/.clooks/costs.jsonl`, rotated at 1MB.
|
|
40
|
+
LLM handler costs (API backend only) are logged to `~/.clooks/costs.jsonl`, rotated at 1MB. Handlers using the `claude-code` backend do not produce cost entries — usage is billed through the Claude Code subscription.
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
43
|
clooks costs
|
|
@@ -71,7 +71,7 @@ Use `$PLUGIN_DIR` in `command` and `module` paths. When the plugin is installed,
|
|
|
71
71
|
|
|
72
72
|
- `description` (string) — Shown in `clooks plugins` output
|
|
73
73
|
- `author` (string) — Plugin author
|
|
74
|
-
- `handlers` — Same format as user manifest handlers (all 3 types supported)
|
|
74
|
+
- `handlers` — Same format as user manifest handlers (all 3 types supported, including both `api` and `claude-code` LLM backends)
|
|
75
75
|
- `prefetch` — Keys to pre-fetch (merged with user manifest)
|
|
76
76
|
- `extras` — Freeform metadata:
|
|
77
77
|
- `skills` (string[]) — Skill names the plugin provides
|
|
@@ -64,7 +64,7 @@ All configuration, state, and data files used by clooks, with their locations, f
|
|
|
64
64
|
|
|
65
65
|
## Cost File Format
|
|
66
66
|
|
|
67
|
-
`costs.jsonl` tracks LLM-specific cost data, one entry per LLM handler invocation.
|
|
67
|
+
`costs.jsonl` tracks LLM-specific cost data, one entry per LLM handler invocation using the `api` backend. Handlers using the `claude-code` backend do not produce cost entries.
|
|
68
68
|
|
|
69
69
|
```json
|
|
70
70
|
{
|
package/docs/reference/types.md
CHANGED
|
@@ -90,11 +90,13 @@ interface InlineHandlerConfig extends BaseHandler {
|
|
|
90
90
|
|
|
91
91
|
interface LLMHandlerConfig extends BaseHandler {
|
|
92
92
|
type: 'llm';
|
|
93
|
-
model
|
|
93
|
+
model?: LLMModel; // Required for 'api' backend, optional for 'claude-code'
|
|
94
94
|
prompt: string;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
backend?: LLMBackend; // Default: 'api'
|
|
96
|
+
llmAgent?: string; // Agent name for 'claude-code' backend (--agent flag)
|
|
97
|
+
batchGroup?: string; // 'api' backend only
|
|
98
|
+
maxTokens?: number; // Default: 1024
|
|
99
|
+
temperature?: number; // Default: 1.0
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
type HandlerConfig = ScriptHandlerConfig | InlineHandlerConfig | LLMHandlerConfig;
|
|
@@ -106,6 +108,15 @@ type HandlerConfig = ScriptHandlerConfig | InlineHandlerConfig | LLMHandlerConfi
|
|
|
106
108
|
type LLMModel = 'claude-haiku-4-5' | 'claude-sonnet-4-6' | 'claude-opus-4-6';
|
|
107
109
|
```
|
|
108
110
|
|
|
111
|
+
### LLMBackend
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
type LLMBackend = 'api' | 'claude-code';
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
- `api` — Direct Anthropic Messages API call. Supports batching and cost tracking. Requires `ANTHROPIC_API_KEY` and the `@anthropic-ai/sdk` package.
|
|
118
|
+
- `claude-code` — Spawns `claude -p "prompt"`. Supports `llmAgent` for agent-based execution. No API key or SDK required.
|
|
119
|
+
|
|
109
120
|
### HandlerResult
|
|
110
121
|
|
|
111
122
|
```typescript
|
package/package.json
CHANGED