@mauribadnights/clooks 0.2.0 → 0.3.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.
- package/README.md +133 -5
- package/dist/auth.d.ts +17 -0
- package/dist/auth.js +109 -0
- package/dist/builtin-hooks.d.ts +11 -0
- package/dist/builtin-hooks.js +67 -0
- package/dist/cli.js +136 -5
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +5 -1
- package/dist/deps.d.ts +13 -0
- package/dist/deps.js +83 -0
- package/dist/doctor.js +110 -0
- package/dist/handlers.d.ts +11 -3
- package/dist/handlers.js +99 -46
- package/dist/index.d.ts +11 -4
- package/dist/index.js +30 -1
- package/dist/llm.d.ts +1 -1
- package/dist/llm.js +8 -4
- package/dist/manifest.d.ts +5 -1
- package/dist/manifest.js +33 -5
- package/dist/metrics.d.ts +8 -1
- package/dist/metrics.js +32 -6
- package/dist/migrate.js +21 -2
- package/dist/plugin.d.ts +50 -0
- package/dist/plugin.js +279 -0
- package/dist/ratelimit.d.ts +12 -0
- package/dist/ratelimit.js +44 -0
- package/dist/server.d.ts +13 -2
- package/dist/server.js +168 -11
- package/dist/shortcircuit.d.ts +20 -0
- package/dist/shortcircuit.js +49 -0
- package/dist/types.d.ts +36 -0
- package/dist/watcher.d.ts +18 -0
- package/dist/watcher.js +120 -0
- package/hooks/check-update.js +37 -0
- package/package.json +2 -1
package/dist/deps.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// clooks dependency resolution — topological ordering of handler execution
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.resolveExecutionOrder = resolveExecutionOrder;
|
|
5
|
+
/**
|
|
6
|
+
* Build a directed acyclic graph from handler dependencies.
|
|
7
|
+
* Returns handlers grouped into "waves" — each wave contains handlers
|
|
8
|
+
* that can execute in parallel (all their deps are in previous waves).
|
|
9
|
+
*
|
|
10
|
+
* Wave 0: handlers with no deps
|
|
11
|
+
* Wave 1: handlers whose deps are all in wave 0
|
|
12
|
+
* etc.
|
|
13
|
+
*
|
|
14
|
+
* Uses Kahn's algorithm. Throws on cycles.
|
|
15
|
+
*/
|
|
16
|
+
function resolveExecutionOrder(handlers) {
|
|
17
|
+
if (handlers.length === 0)
|
|
18
|
+
return [];
|
|
19
|
+
// Build lookup and adjacency
|
|
20
|
+
const handlerMap = new Map();
|
|
21
|
+
const inDegree = new Map();
|
|
22
|
+
const dependents = new Map(); // depId → [handlers that depend on it]
|
|
23
|
+
for (const h of handlers) {
|
|
24
|
+
handlerMap.set(h.id, h);
|
|
25
|
+
inDegree.set(h.id, 0);
|
|
26
|
+
if (!dependents.has(h.id)) {
|
|
27
|
+
dependents.set(h.id, []);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Only consider deps that reference handlers in this set
|
|
31
|
+
const handlerIds = new Set(handlers.map(h => h.id));
|
|
32
|
+
for (const h of handlers) {
|
|
33
|
+
if (!h.depends || h.depends.length === 0)
|
|
34
|
+
continue;
|
|
35
|
+
for (const dep of h.depends) {
|
|
36
|
+
if (!handlerIds.has(dep)) {
|
|
37
|
+
// Dependency references a handler not in this event's set — skip silently
|
|
38
|
+
// (cross-event deps are not supported within a single executeHandlers call)
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
inDegree.set(h.id, (inDegree.get(h.id) ?? 0) + 1);
|
|
42
|
+
const existing = dependents.get(dep) ?? [];
|
|
43
|
+
existing.push(h.id);
|
|
44
|
+
dependents.set(dep, existing);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// BFS — Kahn's algorithm, collecting waves
|
|
48
|
+
const waves = [];
|
|
49
|
+
let queue = [];
|
|
50
|
+
// Wave 0: all handlers with in-degree 0
|
|
51
|
+
for (const [id, degree] of inDegree) {
|
|
52
|
+
if (degree === 0) {
|
|
53
|
+
queue.push(id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
let processedCount = 0;
|
|
57
|
+
while (queue.length > 0) {
|
|
58
|
+
const wave = [];
|
|
59
|
+
const nextQueue = [];
|
|
60
|
+
for (const id of queue) {
|
|
61
|
+
wave.push(handlerMap.get(id));
|
|
62
|
+
processedCount++;
|
|
63
|
+
// Decrement in-degree of dependents
|
|
64
|
+
for (const depId of dependents.get(id) ?? []) {
|
|
65
|
+
const newDegree = (inDegree.get(depId) ?? 1) - 1;
|
|
66
|
+
inDegree.set(depId, newDegree);
|
|
67
|
+
if (newDegree === 0) {
|
|
68
|
+
nextQueue.push(depId);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
waves.push(wave);
|
|
73
|
+
queue = nextQueue;
|
|
74
|
+
}
|
|
75
|
+
// Cycle detection: if not all handlers were processed, there's a cycle
|
|
76
|
+
if (processedCount < handlers.length) {
|
|
77
|
+
const cycleIds = handlers
|
|
78
|
+
.filter(h => (inDegree.get(h.id) ?? 0) > 0)
|
|
79
|
+
.map(h => h.id);
|
|
80
|
+
throw new Error(`Dependency cycle detected among handlers: ${cycleIds.join(', ')}`);
|
|
81
|
+
}
|
|
82
|
+
return waves;
|
|
83
|
+
}
|
package/dist/doctor.js
CHANGED
|
@@ -10,6 +10,8 @@ const os_1 = require("os");
|
|
|
10
10
|
const constants_js_1 = require("./constants.js");
|
|
11
11
|
const manifest_js_1 = require("./manifest.js");
|
|
12
12
|
const server_js_1 = require("./server.js");
|
|
13
|
+
const plugin_js_1 = require("./plugin.js");
|
|
14
|
+
const yaml_1 = require("yaml");
|
|
13
15
|
/**
|
|
14
16
|
* Run all diagnostic checks and return results.
|
|
15
17
|
*/
|
|
@@ -29,6 +31,10 @@ async function runDoctor() {
|
|
|
29
31
|
results.push(checkSettingsHooks());
|
|
30
32
|
// 7. No stale PID file
|
|
31
33
|
results.push(checkStalePid());
|
|
34
|
+
// 8. Auth token consistency (if configured)
|
|
35
|
+
results.push(checkAuthToken());
|
|
36
|
+
// 9. Plugin health checks
|
|
37
|
+
results.push(...checkPluginHealth());
|
|
32
38
|
return results;
|
|
33
39
|
}
|
|
34
40
|
function checkConfigDir() {
|
|
@@ -166,3 +172,107 @@ function checkStalePid() {
|
|
|
166
172
|
return { check: 'Stale PID', status: 'error', message: `Stale PID file: process ${pid} is dead. Remove ${constants_js_1.PID_FILE} or run "clooks start".` };
|
|
167
173
|
}
|
|
168
174
|
}
|
|
175
|
+
function checkAuthToken() {
|
|
176
|
+
try {
|
|
177
|
+
const manifest = (0, manifest_js_1.loadManifest)();
|
|
178
|
+
const authToken = manifest.settings?.authToken;
|
|
179
|
+
if (!authToken) {
|
|
180
|
+
return { check: 'Auth token', status: 'ok', message: 'No auth token configured (open access)' };
|
|
181
|
+
}
|
|
182
|
+
// Check that settings.json hooks include matching Authorization header
|
|
183
|
+
const candidates = [
|
|
184
|
+
(0, path_1.join)((0, os_1.homedir)(), '.claude', 'settings.local.json'),
|
|
185
|
+
(0, path_1.join)((0, os_1.homedir)(), '.claude', 'settings.json'),
|
|
186
|
+
];
|
|
187
|
+
for (const path of candidates) {
|
|
188
|
+
if (!(0, fs_1.existsSync)(path))
|
|
189
|
+
continue;
|
|
190
|
+
try {
|
|
191
|
+
const raw = (0, fs_1.readFileSync)(path, 'utf-8');
|
|
192
|
+
const settings = JSON.parse(raw);
|
|
193
|
+
if (!settings.hooks)
|
|
194
|
+
continue;
|
|
195
|
+
const expectedHeader = `Bearer ${authToken}`;
|
|
196
|
+
const httpHooks = [];
|
|
197
|
+
for (const ruleGroups of Object.values(settings.hooks)) {
|
|
198
|
+
for (const rule of ruleGroups) {
|
|
199
|
+
if (!Array.isArray(rule.hooks))
|
|
200
|
+
continue;
|
|
201
|
+
for (const hook of rule.hooks) {
|
|
202
|
+
if (hook.type === 'http' && hook.url?.includes(`localhost:${constants_js_1.DEFAULT_PORT}`)) {
|
|
203
|
+
httpHooks.push(hook);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (httpHooks.length === 0) {
|
|
209
|
+
return { check: 'Auth token', status: 'warn', message: 'Auth token set but no HTTP hooks found in settings.json' };
|
|
210
|
+
}
|
|
211
|
+
const missingAuth = httpHooks.filter(h => h.headers?.['Authorization'] !== expectedHeader);
|
|
212
|
+
if (missingAuth.length > 0) {
|
|
213
|
+
return { check: 'Auth token', status: 'error', message: `Auth token set but ${missingAuth.length} HTTP hook(s) missing matching Authorization header. Run "clooks migrate".` };
|
|
214
|
+
}
|
|
215
|
+
return { check: 'Auth token', status: 'ok', message: 'Auth token matches settings.json hook headers' };
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return { check: 'Auth token', status: 'warn', message: 'Auth token set but could not verify settings.json headers' };
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return { check: 'Auth token', status: 'ok', message: 'Could not load manifest for auth check' };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function checkPluginHealth() {
|
|
228
|
+
const results = [];
|
|
229
|
+
try {
|
|
230
|
+
const registry = (0, plugin_js_1.loadRegistry)();
|
|
231
|
+
if (registry.plugins.length === 0) {
|
|
232
|
+
results.push({ check: 'Plugins', status: 'ok', message: 'No plugins installed' });
|
|
233
|
+
return results;
|
|
234
|
+
}
|
|
235
|
+
for (const plugin of registry.plugins) {
|
|
236
|
+
// Check directory exists
|
|
237
|
+
if (!(0, fs_1.existsSync)(plugin.path)) {
|
|
238
|
+
results.push({
|
|
239
|
+
check: `Plugin "${plugin.name}"`,
|
|
240
|
+
status: 'error',
|
|
241
|
+
message: `Plugin directory missing: ${plugin.path}`,
|
|
242
|
+
});
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
// Check manifest exists and is valid
|
|
246
|
+
const manifestPath = (0, path_1.join)(plugin.path, constants_js_1.PLUGIN_MANIFEST_NAME);
|
|
247
|
+
if (!(0, fs_1.existsSync)(manifestPath)) {
|
|
248
|
+
results.push({
|
|
249
|
+
check: `Plugin "${plugin.name}"`,
|
|
250
|
+
status: 'error',
|
|
251
|
+
message: `Plugin manifest missing: ${manifestPath}`,
|
|
252
|
+
});
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
const raw = (0, fs_1.readFileSync)(manifestPath, 'utf-8');
|
|
257
|
+
const parsed = (0, yaml_1.parse)(raw);
|
|
258
|
+
(0, plugin_js_1.validatePluginManifest)(parsed);
|
|
259
|
+
results.push({
|
|
260
|
+
check: `Plugin "${plugin.name}"`,
|
|
261
|
+
status: 'ok',
|
|
262
|
+
message: `v${plugin.version} — manifest valid`,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
results.push({
|
|
267
|
+
check: `Plugin "${plugin.name}"`,
|
|
268
|
+
status: 'error',
|
|
269
|
+
message: `Invalid manifest: ${err instanceof Error ? err.message : String(err)}`,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
results.push({ check: 'Plugins', status: 'ok', message: 'Could not load plugin registry' });
|
|
276
|
+
}
|
|
277
|
+
return results;
|
|
278
|
+
}
|
package/dist/handlers.d.ts
CHANGED
|
@@ -3,10 +3,18 @@ import type { HandlerConfig, HandlerResult, HandlerState, HookEvent, HookInput,
|
|
|
3
3
|
export declare function resetHandlerStates(): void;
|
|
4
4
|
/** Get a copy of the handler states map */
|
|
5
5
|
export declare function getHandlerStates(): Map<string, HandlerState>;
|
|
6
|
+
/** Clean up state for a specific handler ID (used during manifest reload diffs). */
|
|
7
|
+
export declare function cleanupHandlerState(handlerId: string): void;
|
|
6
8
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
9
|
+
* Reset handler states for handlers that have sessionIsolation: true.
|
|
10
|
+
* Called on SessionStart events.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resetSessionIsolatedHandlers(handlers: HandlerConfig[]): void;
|
|
13
|
+
/**
|
|
14
|
+
* Execute all handlers for an event, respecting dependency order.
|
|
15
|
+
* Handlers are grouped into "waves" via topological sort.
|
|
16
|
+
* Within each wave, handlers run in parallel.
|
|
17
|
+
* Outputs from previous waves are available to dependent handlers via _handlerOutputs.
|
|
10
18
|
*/
|
|
11
19
|
export declare function executeHandlers(_event: HookEvent, input: HookInput, handlers: HandlerConfig[], context?: PrefetchContext): Promise<HandlerResult[]>;
|
|
12
20
|
/**
|
package/dist/handlers.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.resetHandlerStates = resetHandlerStates;
|
|
5
5
|
exports.getHandlerStates = getHandlerStates;
|
|
6
|
+
exports.cleanupHandlerState = cleanupHandlerState;
|
|
7
|
+
exports.resetSessionIsolatedHandlers = resetSessionIsolatedHandlers;
|
|
6
8
|
exports.executeHandlers = executeHandlers;
|
|
7
9
|
exports.executeScriptHandler = executeScriptHandler;
|
|
8
10
|
exports.executeInlineHandler = executeInlineHandler;
|
|
@@ -12,6 +14,7 @@ const path_1 = require("path");
|
|
|
12
14
|
const constants_js_1 = require("./constants.js");
|
|
13
15
|
const filter_js_1 = require("./filter.js");
|
|
14
16
|
const llm_js_1 = require("./llm.js");
|
|
17
|
+
const deps_js_1 = require("./deps.js");
|
|
15
18
|
/** Runtime state per handler ID */
|
|
16
19
|
const handlerStates = new Map();
|
|
17
20
|
function getState(id) {
|
|
@@ -30,18 +33,38 @@ function resetHandlerStates() {
|
|
|
30
33
|
function getHandlerStates() {
|
|
31
34
|
return new Map(handlerStates);
|
|
32
35
|
}
|
|
36
|
+
/** Clean up state for a specific handler ID (used during manifest reload diffs). */
|
|
37
|
+
function cleanupHandlerState(handlerId) {
|
|
38
|
+
handlerStates.delete(handlerId);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Reset handler states for handlers that have sessionIsolation: true.
|
|
42
|
+
* Called on SessionStart events.
|
|
43
|
+
*/
|
|
44
|
+
function resetSessionIsolatedHandlers(handlers) {
|
|
45
|
+
for (const handler of handlers) {
|
|
46
|
+
if (handler.sessionIsolation) {
|
|
47
|
+
const state = handlerStates.get(handler.id);
|
|
48
|
+
if (state) {
|
|
49
|
+
state.consecutiveFailures = 0;
|
|
50
|
+
state.disabled = false;
|
|
51
|
+
state.totalFires = 0;
|
|
52
|
+
state.totalErrors = 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
33
57
|
/**
|
|
34
|
-
* Execute all handlers for an event
|
|
35
|
-
*
|
|
36
|
-
*
|
|
58
|
+
* Execute all handlers for an event, respecting dependency order.
|
|
59
|
+
* Handlers are grouped into "waves" via topological sort.
|
|
60
|
+
* Within each wave, handlers run in parallel.
|
|
61
|
+
* Outputs from previous waves are available to dependent handlers via _handlerOutputs.
|
|
37
62
|
*/
|
|
38
63
|
async function executeHandlers(_event, input, handlers, context) {
|
|
39
|
-
//
|
|
40
|
-
const
|
|
41
|
-
const otherPromises = [];
|
|
64
|
+
// Pre-check: filter out disabled/auto-disabled/filtered handlers before dep resolution
|
|
65
|
+
const eligible = [];
|
|
42
66
|
const skippedResults = [];
|
|
43
67
|
for (const handler of handlers) {
|
|
44
|
-
// Skip disabled handlers (both manifest-disabled and auto-disabled)
|
|
45
68
|
if (handler.enabled === false) {
|
|
46
69
|
skippedResults.push({ id: handler.id, ok: true, output: undefined, duration_ms: 0 });
|
|
47
70
|
continue;
|
|
@@ -56,7 +79,6 @@ async function executeHandlers(_event, input, handlers, context) {
|
|
|
56
79
|
});
|
|
57
80
|
continue;
|
|
58
81
|
}
|
|
59
|
-
// Evaluate keyword filter before execution
|
|
60
82
|
if (handler.filter) {
|
|
61
83
|
const inputStr = JSON.stringify(input);
|
|
62
84
|
if (!(0, filter_js_1.evaluateFilter)(handler.filter, inputStr)) {
|
|
@@ -70,51 +92,82 @@ async function executeHandlers(_event, input, handlers, context) {
|
|
|
70
92
|
continue;
|
|
71
93
|
}
|
|
72
94
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
95
|
+
eligible.push(handler);
|
|
96
|
+
}
|
|
97
|
+
if (eligible.length === 0) {
|
|
98
|
+
return skippedResults;
|
|
99
|
+
}
|
|
100
|
+
// Resolve execution order into waves
|
|
101
|
+
let waves;
|
|
102
|
+
try {
|
|
103
|
+
waves = (0, deps_js_1.resolveExecutionOrder)(eligible);
|
|
81
104
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
105
|
+
catch {
|
|
106
|
+
// If dep resolution fails, fall back to flat parallel execution
|
|
107
|
+
waves = [eligible];
|
|
108
|
+
}
|
|
109
|
+
const allResults = [...skippedResults];
|
|
110
|
+
const handlerOutputs = {};
|
|
111
|
+
for (const wave of waves) {
|
|
112
|
+
// Mark totalFires for all handlers in this wave
|
|
113
|
+
for (const handler of wave) {
|
|
114
|
+
getState(handler.id).totalFires++;
|
|
91
115
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
116
|
+
// Build input with _handlerOutputs from previous waves
|
|
117
|
+
const waveInput = Object.keys(handlerOutputs).length > 0
|
|
118
|
+
? { ...input, _handlerOutputs: handlerOutputs }
|
|
119
|
+
: input;
|
|
120
|
+
// Separate LLM from script/inline within this wave
|
|
121
|
+
const llmHandlers = [];
|
|
122
|
+
const otherPromises = [];
|
|
123
|
+
for (const handler of wave) {
|
|
124
|
+
if (handler.type === 'llm') {
|
|
125
|
+
llmHandlers.push(handler);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
otherPromises.push(executeOtherHandler(handler, waveInput));
|
|
129
|
+
}
|
|
101
130
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
131
|
+
// Execute script/inline handlers in parallel
|
|
132
|
+
const otherResults = otherPromises.length > 0
|
|
133
|
+
? await Promise.all(otherPromises)
|
|
134
|
+
: [];
|
|
135
|
+
// Execute LLM handlers with batching (scoped to this wave)
|
|
136
|
+
let llmResults = [];
|
|
137
|
+
if (llmHandlers.length > 0) {
|
|
138
|
+
try {
|
|
139
|
+
llmResults = await (0, llm_js_1.executeLLMHandlersBatched)(llmHandlers, waveInput, context ?? {}, input.session_id);
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
143
|
+
llmResults = llmHandlers.map(h => ({
|
|
144
|
+
id: h.id,
|
|
145
|
+
ok: false,
|
|
146
|
+
error: `LLM execution failed: ${errorMsg}`,
|
|
147
|
+
duration_ms: 0,
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
108
150
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
151
|
+
const waveResults = [...otherResults, ...llmResults];
|
|
152
|
+
// Update failure tracking and collect outputs for dependents
|
|
153
|
+
for (const result of waveResults) {
|
|
154
|
+
const state = getState(result.id);
|
|
155
|
+
if (result.ok) {
|
|
156
|
+
state.consecutiveFailures = 0;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
state.consecutiveFailures++;
|
|
160
|
+
state.totalErrors++;
|
|
161
|
+
if (state.consecutiveFailures >= constants_js_1.MAX_CONSECUTIVE_FAILURES) {
|
|
162
|
+
state.disabled = true;
|
|
163
|
+
}
|
|
114
164
|
}
|
|
165
|
+
// Store output for downstream handlers
|
|
166
|
+
handlerOutputs[result.id] = result.output;
|
|
115
167
|
}
|
|
168
|
+
allResults.push(...waveResults);
|
|
116
169
|
}
|
|
117
|
-
return
|
|
170
|
+
return allResults;
|
|
118
171
|
}
|
|
119
172
|
/**
|
|
120
173
|
* Execute a single script or inline handler with error handling.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
export { createServer, startDaemon, stopDaemon, isDaemonRunning } from './server.js';
|
|
2
|
-
export { loadManifest, validateManifest, createDefaultManifest } from './manifest.js';
|
|
2
|
+
export { loadManifest, loadCompositeManifest, validateManifest, createDefaultManifest } from './manifest.js';
|
|
3
|
+
export { loadPlugins, mergeManifests, validatePluginManifest, loadRegistry, saveRegistry, installPlugin, uninstallPlugin, listPlugins } from './plugin.js';
|
|
3
4
|
export { MetricsCollector } from './metrics.js';
|
|
4
5
|
export { migrate, restore, getSettingsPath } from './migrate.js';
|
|
5
6
|
export type { MigratePathOptions } from './migrate.js';
|
|
6
7
|
export { runDoctor } from './doctor.js';
|
|
7
|
-
export { executeHandlers } from './handlers.js';
|
|
8
|
+
export { executeHandlers, resetSessionIsolatedHandlers, cleanupHandlerState } from './handlers.js';
|
|
9
|
+
export { resolveExecutionOrder } from './deps.js';
|
|
10
|
+
export { DenyCache } from './shortcircuit.js';
|
|
11
|
+
export { RateLimiter } from './ratelimit.js';
|
|
12
|
+
export { startWatcher, stopWatcher } from './watcher.js';
|
|
13
|
+
export { generateAuthToken, validateAuth, rotateToken } from './auth.js';
|
|
14
|
+
export type { RotateTokenOptions } from './auth.js';
|
|
8
15
|
export { evaluateFilter } from './filter.js';
|
|
9
16
|
export { executeLLMHandler, executeLLMHandlersBatched, calculateCost, resetClient } from './llm.js';
|
|
10
17
|
export { prefetchContext, renderPromptTemplate } from './prefetch.js';
|
|
11
|
-
export { DEFAULT_PORT, CONFIG_DIR, MANIFEST_PATH, PID_FILE, METRICS_FILE, LOG_FILE, COSTS_FILE, DEFAULT_LLM_TIMEOUT, DEFAULT_LLM_MAX_TOKENS, LLM_PRICING } from './constants.js';
|
|
12
|
-
export type { HookEvent, HookInput, HandlerType, HandlerConfig, ScriptHandlerConfig, InlineHandlerConfig, LLMHandlerConfig, LLMModel, Manifest, HandlerResult, MetricEntry, HandlerState, DiagnosticResult, PrefetchKey, PrefetchContext, TokenUsage, CostEntry, } from './types.js';
|
|
18
|
+
export { DEFAULT_PORT, CONFIG_DIR, MANIFEST_PATH, PID_FILE, METRICS_FILE, LOG_FILE, COSTS_FILE, DEFAULT_LLM_TIMEOUT, DEFAULT_LLM_MAX_TOKENS, LLM_PRICING, PLUGINS_DIR, PLUGIN_REGISTRY, PLUGIN_MANIFEST_NAME } from './constants.js';
|
|
19
|
+
export type { HookEvent, HookInput, HandlerType, HandlerConfig, ScriptHandlerConfig, InlineHandlerConfig, LLMHandlerConfig, LLMModel, Manifest, HandlerResult, MetricEntry, HandlerState, DiagnosticResult, PrefetchKey, PrefetchContext, TokenUsage, CostEntry, PluginManifest, InstalledPlugin, PluginRegistry, } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// clooks — public API exports
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.LLM_PRICING = exports.DEFAULT_LLM_MAX_TOKENS = exports.DEFAULT_LLM_TIMEOUT = exports.COSTS_FILE = exports.LOG_FILE = exports.METRICS_FILE = exports.PID_FILE = exports.MANIFEST_PATH = exports.CONFIG_DIR = exports.DEFAULT_PORT = exports.renderPromptTemplate = exports.prefetchContext = exports.resetClient = exports.calculateCost = exports.executeLLMHandlersBatched = exports.executeLLMHandler = exports.evaluateFilter = exports.executeHandlers = exports.runDoctor = exports.getSettingsPath = exports.restore = exports.migrate = exports.MetricsCollector = exports.createDefaultManifest = exports.validateManifest = exports.loadManifest = exports.isDaemonRunning = exports.stopDaemon = exports.startDaemon = exports.createServer = void 0;
|
|
4
|
+
exports.PLUGINS_DIR = exports.LLM_PRICING = exports.DEFAULT_LLM_MAX_TOKENS = exports.DEFAULT_LLM_TIMEOUT = exports.COSTS_FILE = exports.LOG_FILE = exports.METRICS_FILE = exports.PID_FILE = exports.MANIFEST_PATH = exports.CONFIG_DIR = exports.DEFAULT_PORT = exports.renderPromptTemplate = exports.prefetchContext = exports.resetClient = exports.calculateCost = exports.executeLLMHandlersBatched = exports.executeLLMHandler = exports.evaluateFilter = exports.rotateToken = exports.validateAuth = exports.generateAuthToken = exports.stopWatcher = exports.startWatcher = exports.RateLimiter = exports.DenyCache = exports.resolveExecutionOrder = exports.cleanupHandlerState = exports.resetSessionIsolatedHandlers = exports.executeHandlers = exports.runDoctor = exports.getSettingsPath = exports.restore = exports.migrate = exports.MetricsCollector = exports.listPlugins = exports.uninstallPlugin = exports.installPlugin = exports.saveRegistry = exports.loadRegistry = exports.validatePluginManifest = exports.mergeManifests = exports.loadPlugins = exports.createDefaultManifest = exports.validateManifest = exports.loadCompositeManifest = exports.loadManifest = exports.isDaemonRunning = exports.stopDaemon = exports.startDaemon = exports.createServer = void 0;
|
|
5
|
+
exports.PLUGIN_MANIFEST_NAME = exports.PLUGIN_REGISTRY = void 0;
|
|
5
6
|
var server_js_1 = require("./server.js");
|
|
6
7
|
Object.defineProperty(exports, "createServer", { enumerable: true, get: function () { return server_js_1.createServer; } });
|
|
7
8
|
Object.defineProperty(exports, "startDaemon", { enumerable: true, get: function () { return server_js_1.startDaemon; } });
|
|
@@ -9,8 +10,18 @@ Object.defineProperty(exports, "stopDaemon", { enumerable: true, get: function (
|
|
|
9
10
|
Object.defineProperty(exports, "isDaemonRunning", { enumerable: true, get: function () { return server_js_1.isDaemonRunning; } });
|
|
10
11
|
var manifest_js_1 = require("./manifest.js");
|
|
11
12
|
Object.defineProperty(exports, "loadManifest", { enumerable: true, get: function () { return manifest_js_1.loadManifest; } });
|
|
13
|
+
Object.defineProperty(exports, "loadCompositeManifest", { enumerable: true, get: function () { return manifest_js_1.loadCompositeManifest; } });
|
|
12
14
|
Object.defineProperty(exports, "validateManifest", { enumerable: true, get: function () { return manifest_js_1.validateManifest; } });
|
|
13
15
|
Object.defineProperty(exports, "createDefaultManifest", { enumerable: true, get: function () { return manifest_js_1.createDefaultManifest; } });
|
|
16
|
+
var plugin_js_1 = require("./plugin.js");
|
|
17
|
+
Object.defineProperty(exports, "loadPlugins", { enumerable: true, get: function () { return plugin_js_1.loadPlugins; } });
|
|
18
|
+
Object.defineProperty(exports, "mergeManifests", { enumerable: true, get: function () { return plugin_js_1.mergeManifests; } });
|
|
19
|
+
Object.defineProperty(exports, "validatePluginManifest", { enumerable: true, get: function () { return plugin_js_1.validatePluginManifest; } });
|
|
20
|
+
Object.defineProperty(exports, "loadRegistry", { enumerable: true, get: function () { return plugin_js_1.loadRegistry; } });
|
|
21
|
+
Object.defineProperty(exports, "saveRegistry", { enumerable: true, get: function () { return plugin_js_1.saveRegistry; } });
|
|
22
|
+
Object.defineProperty(exports, "installPlugin", { enumerable: true, get: function () { return plugin_js_1.installPlugin; } });
|
|
23
|
+
Object.defineProperty(exports, "uninstallPlugin", { enumerable: true, get: function () { return plugin_js_1.uninstallPlugin; } });
|
|
24
|
+
Object.defineProperty(exports, "listPlugins", { enumerable: true, get: function () { return plugin_js_1.listPlugins; } });
|
|
14
25
|
var metrics_js_1 = require("./metrics.js");
|
|
15
26
|
Object.defineProperty(exports, "MetricsCollector", { enumerable: true, get: function () { return metrics_js_1.MetricsCollector; } });
|
|
16
27
|
var migrate_js_1 = require("./migrate.js");
|
|
@@ -21,6 +32,21 @@ var doctor_js_1 = require("./doctor.js");
|
|
|
21
32
|
Object.defineProperty(exports, "runDoctor", { enumerable: true, get: function () { return doctor_js_1.runDoctor; } });
|
|
22
33
|
var handlers_js_1 = require("./handlers.js");
|
|
23
34
|
Object.defineProperty(exports, "executeHandlers", { enumerable: true, get: function () { return handlers_js_1.executeHandlers; } });
|
|
35
|
+
Object.defineProperty(exports, "resetSessionIsolatedHandlers", { enumerable: true, get: function () { return handlers_js_1.resetSessionIsolatedHandlers; } });
|
|
36
|
+
Object.defineProperty(exports, "cleanupHandlerState", { enumerable: true, get: function () { return handlers_js_1.cleanupHandlerState; } });
|
|
37
|
+
var deps_js_1 = require("./deps.js");
|
|
38
|
+
Object.defineProperty(exports, "resolveExecutionOrder", { enumerable: true, get: function () { return deps_js_1.resolveExecutionOrder; } });
|
|
39
|
+
var shortcircuit_js_1 = require("./shortcircuit.js");
|
|
40
|
+
Object.defineProperty(exports, "DenyCache", { enumerable: true, get: function () { return shortcircuit_js_1.DenyCache; } });
|
|
41
|
+
var ratelimit_js_1 = require("./ratelimit.js");
|
|
42
|
+
Object.defineProperty(exports, "RateLimiter", { enumerable: true, get: function () { return ratelimit_js_1.RateLimiter; } });
|
|
43
|
+
var watcher_js_1 = require("./watcher.js");
|
|
44
|
+
Object.defineProperty(exports, "startWatcher", { enumerable: true, get: function () { return watcher_js_1.startWatcher; } });
|
|
45
|
+
Object.defineProperty(exports, "stopWatcher", { enumerable: true, get: function () { return watcher_js_1.stopWatcher; } });
|
|
46
|
+
var auth_js_1 = require("./auth.js");
|
|
47
|
+
Object.defineProperty(exports, "generateAuthToken", { enumerable: true, get: function () { return auth_js_1.generateAuthToken; } });
|
|
48
|
+
Object.defineProperty(exports, "validateAuth", { enumerable: true, get: function () { return auth_js_1.validateAuth; } });
|
|
49
|
+
Object.defineProperty(exports, "rotateToken", { enumerable: true, get: function () { return auth_js_1.rotateToken; } });
|
|
24
50
|
var filter_js_1 = require("./filter.js");
|
|
25
51
|
Object.defineProperty(exports, "evaluateFilter", { enumerable: true, get: function () { return filter_js_1.evaluateFilter; } });
|
|
26
52
|
var llm_js_1 = require("./llm.js");
|
|
@@ -42,3 +68,6 @@ Object.defineProperty(exports, "COSTS_FILE", { enumerable: true, get: function (
|
|
|
42
68
|
Object.defineProperty(exports, "DEFAULT_LLM_TIMEOUT", { enumerable: true, get: function () { return constants_js_1.DEFAULT_LLM_TIMEOUT; } });
|
|
43
69
|
Object.defineProperty(exports, "DEFAULT_LLM_MAX_TOKENS", { enumerable: true, get: function () { return constants_js_1.DEFAULT_LLM_MAX_TOKENS; } });
|
|
44
70
|
Object.defineProperty(exports, "LLM_PRICING", { enumerable: true, get: function () { return constants_js_1.LLM_PRICING; } });
|
|
71
|
+
Object.defineProperty(exports, "PLUGINS_DIR", { enumerable: true, get: function () { return constants_js_1.PLUGINS_DIR; } });
|
|
72
|
+
Object.defineProperty(exports, "PLUGIN_REGISTRY", { enumerable: true, get: function () { return constants_js_1.PLUGIN_REGISTRY; } });
|
|
73
|
+
Object.defineProperty(exports, "PLUGIN_MANIFEST_NAME", { enumerable: true, get: function () { return constants_js_1.PLUGIN_MANIFEST_NAME; } });
|
package/dist/llm.d.ts
CHANGED
|
@@ -16,4 +16,4 @@ export declare function executeLLMHandler(handler: LLMHandlerConfig, input: Hook
|
|
|
16
16
|
* a single API call with a structured multi-task prompt. Handlers without a
|
|
17
17
|
* batchGroup are executed individually.
|
|
18
18
|
*/
|
|
19
|
-
export declare function executeLLMHandlersBatched(handlers: LLMHandlerConfig[], input: HookInput, context: PrefetchContext): Promise<HandlerResult[]>;
|
|
19
|
+
export declare function executeLLMHandlersBatched(handlers: LLMHandlerConfig[], input: HookInput, context: PrefetchContext, sessionId?: string): Promise<HandlerResult[]>;
|
package/dist/llm.js
CHANGED
|
@@ -190,15 +190,19 @@ function splitUsage(total, count) {
|
|
|
190
190
|
* a single API call with a structured multi-task prompt. Handlers without a
|
|
191
191
|
* batchGroup are executed individually.
|
|
192
192
|
*/
|
|
193
|
-
async function executeLLMHandlersBatched(handlers, input, context) {
|
|
194
|
-
// Group by batchGroup
|
|
193
|
+
async function executeLLMHandlersBatched(handlers, input, context, sessionId) {
|
|
194
|
+
// Group by batchGroup, scoped by sessionId to prevent cross-session batching
|
|
195
195
|
const grouped = new Map();
|
|
196
196
|
const ungrouped = [];
|
|
197
197
|
for (const handler of handlers) {
|
|
198
198
|
if (handler.batchGroup) {
|
|
199
|
-
|
|
199
|
+
// Scope the batch key by sessionId so different sessions never batch together
|
|
200
|
+
const batchKey = sessionId
|
|
201
|
+
? `${handler.batchGroup}:${sessionId}`
|
|
202
|
+
: handler.batchGroup;
|
|
203
|
+
const existing = grouped.get(batchKey) ?? [];
|
|
200
204
|
existing.push(handler);
|
|
201
|
-
grouped.set(
|
|
205
|
+
grouped.set(batchKey, existing);
|
|
202
206
|
}
|
|
203
207
|
else {
|
|
204
208
|
ungrouped.push(handler);
|
package/dist/manifest.d.ts
CHANGED
|
@@ -9,7 +9,11 @@ export declare function loadManifest(): Manifest;
|
|
|
9
9
|
* Throws on invalid structure.
|
|
10
10
|
*/
|
|
11
11
|
export declare function validateManifest(manifest: Manifest): void;
|
|
12
|
+
/**
|
|
13
|
+
* Load the composite manifest: user manifest + all installed plugins.
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadCompositeManifest(): Manifest;
|
|
12
16
|
/**
|
|
13
17
|
* Create a default commented example manifest.yaml in CONFIG_DIR.
|
|
14
18
|
*/
|
|
15
|
-
export declare function createDefaultManifest(): string;
|
|
19
|
+
export declare function createDefaultManifest(authToken?: string): string;
|
package/dist/manifest.js
CHANGED
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.loadManifest = loadManifest;
|
|
5
5
|
exports.validateManifest = validateManifest;
|
|
6
|
+
exports.loadCompositeManifest = loadCompositeManifest;
|
|
6
7
|
exports.createDefaultManifest = createDefaultManifest;
|
|
7
8
|
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = require("path");
|
|
8
10
|
const yaml_1 = require("yaml");
|
|
9
11
|
const constants_js_1 = require("./constants.js");
|
|
12
|
+
const plugin_js_1 = require("./plugin.js");
|
|
13
|
+
const builtin_hooks_js_1 = require("./builtin-hooks.js");
|
|
10
14
|
/**
|
|
11
15
|
* Load and validate the manifest from disk.
|
|
12
16
|
* Returns a Manifest with empty handlers if the file doesn't exist.
|
|
@@ -99,15 +103,42 @@ function validateManifest(manifest) {
|
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Load the composite manifest: user manifest + all installed plugins.
|
|
108
|
+
*/
|
|
109
|
+
function loadCompositeManifest() {
|
|
110
|
+
const userManifest = loadManifest();
|
|
111
|
+
const plugins = (0, plugin_js_1.loadPlugins)();
|
|
112
|
+
return (0, plugin_js_1.mergeManifests)(userManifest, plugins);
|
|
113
|
+
}
|
|
102
114
|
/**
|
|
103
115
|
* Create a default commented example manifest.yaml in CONFIG_DIR.
|
|
104
116
|
*/
|
|
105
|
-
function createDefaultManifest() {
|
|
117
|
+
function createDefaultManifest(authToken) {
|
|
106
118
|
if (!(0, fs_1.existsSync)(constants_js_1.CONFIG_DIR)) {
|
|
107
119
|
(0, fs_1.mkdirSync)(constants_js_1.CONFIG_DIR, { recursive: true });
|
|
108
120
|
}
|
|
121
|
+
const settings = {
|
|
122
|
+
port: 7890,
|
|
123
|
+
logLevel: 'info',
|
|
124
|
+
};
|
|
125
|
+
if (authToken) {
|
|
126
|
+
settings.authToken = authToken;
|
|
127
|
+
}
|
|
128
|
+
// Install built-in hook scripts to CONFIG_DIR/hooks/
|
|
129
|
+
(0, builtin_hooks_js_1.installBuiltinHooks)();
|
|
130
|
+
const checkUpdatePath = (0, path_1.join)(constants_js_1.HOOKS_DIR, 'check-update.js');
|
|
109
131
|
const example = {
|
|
110
132
|
handlers: {
|
|
133
|
+
SessionStart: [
|
|
134
|
+
{
|
|
135
|
+
id: 'clooks-check-update',
|
|
136
|
+
type: 'script',
|
|
137
|
+
command: `node ${checkUpdatePath}`,
|
|
138
|
+
timeout: 6000,
|
|
139
|
+
enabled: true,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
111
142
|
PreToolUse: [
|
|
112
143
|
{
|
|
113
144
|
id: 'example-guard',
|
|
@@ -118,10 +149,7 @@ function createDefaultManifest() {
|
|
|
118
149
|
},
|
|
119
150
|
],
|
|
120
151
|
},
|
|
121
|
-
settings
|
|
122
|
-
port: 7890,
|
|
123
|
-
logLevel: 'info',
|
|
124
|
-
},
|
|
152
|
+
settings,
|
|
125
153
|
};
|
|
126
154
|
const yamlStr = '# clooks manifest — define your hook handlers here\n' +
|
|
127
155
|
'# Docs: https://github.com/mauribadnights/clooks\n' +
|
package/dist/metrics.d.ts
CHANGED
|
@@ -8,8 +8,15 @@ interface AggregatedStats {
|
|
|
8
8
|
maxDuration: number;
|
|
9
9
|
}
|
|
10
10
|
export declare class MetricsCollector {
|
|
11
|
+
private static readonly MAX_ENTRIES;
|
|
11
12
|
private entries;
|
|
12
|
-
|
|
13
|
+
private ringIndex;
|
|
14
|
+
private totalRecorded;
|
|
15
|
+
private static readonly METRICS_MAX_BYTES;
|
|
16
|
+
private static readonly COSTS_MAX_BYTES;
|
|
17
|
+
/** Rotate a log file if it exceeds maxBytes. Keeps one backup (.1). */
|
|
18
|
+
private rotateIfNeeded;
|
|
19
|
+
/** Record a metric entry in memory (ring buffer) and append to disk. */
|
|
13
20
|
record(entry: MetricEntry): void;
|
|
14
21
|
/** Get aggregated stats per event type. */
|
|
15
22
|
getStats(): AggregatedStats[];
|