@mauribadnights/clooks 0.2.2 → 0.3.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 +72 -1
- package/dist/auth.d.ts +13 -0
- package/dist/auth.js +82 -0
- package/dist/builtin-hooks.d.ts +11 -0
- package/dist/builtin-hooks.js +67 -0
- package/dist/cli.js +131 -2
- 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 +56 -0
- package/dist/handlers.d.ts +6 -3
- package/dist/handlers.js +81 -46
- package/dist/index.d.ts +10 -5
- package/dist/index.js +23 -1
- package/dist/llm.d.ts +1 -1
- package/dist/llm.js +8 -4
- package/dist/manifest.d.ts +4 -0
- package/dist/manifest.js +24 -0
- package/dist/metrics.d.ts +14 -0
- package/dist/metrics.js +51 -0
- package/dist/migrate.d.ts +9 -0
- package/dist/migrate.js +64 -1
- 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 +5 -0
- package/dist/server.js +119 -5
- package/dist/shortcircuit.d.ts +20 -0
- package/dist/shortcircuit.js +49 -0
- package/dist/types.d.ts +31 -0
- package/hooks/check-update.js +37 -0
- package/package.json +2 -1
package/dist/handlers.d.ts
CHANGED
|
@@ -3,15 +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
9
|
* Reset handler states for handlers that have sessionIsolation: true.
|
|
8
10
|
* Called on SessionStart events.
|
|
9
11
|
*/
|
|
10
12
|
export declare function resetSessionIsolatedHandlers(handlers: HandlerConfig[]): void;
|
|
11
13
|
/**
|
|
12
|
-
* Execute all handlers for an event
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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.
|
|
15
18
|
*/
|
|
16
19
|
export declare function executeHandlers(_event: HookEvent, input: HookInput, handlers: HandlerConfig[], context?: PrefetchContext): Promise<HandlerResult[]>;
|
|
17
20
|
/**
|
package/dist/handlers.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.resetHandlerStates = resetHandlerStates;
|
|
5
5
|
exports.getHandlerStates = getHandlerStates;
|
|
6
|
+
exports.cleanupHandlerState = cleanupHandlerState;
|
|
6
7
|
exports.resetSessionIsolatedHandlers = resetSessionIsolatedHandlers;
|
|
7
8
|
exports.executeHandlers = executeHandlers;
|
|
8
9
|
exports.executeScriptHandler = executeScriptHandler;
|
|
@@ -13,6 +14,7 @@ const path_1 = require("path");
|
|
|
13
14
|
const constants_js_1 = require("./constants.js");
|
|
14
15
|
const filter_js_1 = require("./filter.js");
|
|
15
16
|
const llm_js_1 = require("./llm.js");
|
|
17
|
+
const deps_js_1 = require("./deps.js");
|
|
16
18
|
/** Runtime state per handler ID */
|
|
17
19
|
const handlerStates = new Map();
|
|
18
20
|
function getState(id) {
|
|
@@ -31,6 +33,10 @@ function resetHandlerStates() {
|
|
|
31
33
|
function getHandlerStates() {
|
|
32
34
|
return new Map(handlerStates);
|
|
33
35
|
}
|
|
36
|
+
/** Clean up state for a specific handler ID (used during manifest reload diffs). */
|
|
37
|
+
function cleanupHandlerState(handlerId) {
|
|
38
|
+
handlerStates.delete(handlerId);
|
|
39
|
+
}
|
|
34
40
|
/**
|
|
35
41
|
* Reset handler states for handlers that have sessionIsolation: true.
|
|
36
42
|
* Called on SessionStart events.
|
|
@@ -49,17 +55,16 @@ function resetSessionIsolatedHandlers(handlers) {
|
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
/**
|
|
52
|
-
* Execute all handlers for an event
|
|
53
|
-
*
|
|
54
|
-
*
|
|
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.
|
|
55
62
|
*/
|
|
56
63
|
async function executeHandlers(_event, input, handlers, context) {
|
|
57
|
-
//
|
|
58
|
-
const
|
|
59
|
-
const otherPromises = [];
|
|
64
|
+
// Pre-check: filter out disabled/auto-disabled/filtered handlers before dep resolution
|
|
65
|
+
const eligible = [];
|
|
60
66
|
const skippedResults = [];
|
|
61
67
|
for (const handler of handlers) {
|
|
62
|
-
// Skip disabled handlers (both manifest-disabled and auto-disabled)
|
|
63
68
|
if (handler.enabled === false) {
|
|
64
69
|
skippedResults.push({ id: handler.id, ok: true, output: undefined, duration_ms: 0 });
|
|
65
70
|
continue;
|
|
@@ -74,7 +79,6 @@ async function executeHandlers(_event, input, handlers, context) {
|
|
|
74
79
|
});
|
|
75
80
|
continue;
|
|
76
81
|
}
|
|
77
|
-
// Evaluate keyword filter before execution
|
|
78
82
|
if (handler.filter) {
|
|
79
83
|
const inputStr = JSON.stringify(input);
|
|
80
84
|
if (!(0, filter_js_1.evaluateFilter)(handler.filter, inputStr)) {
|
|
@@ -88,51 +92,82 @@ async function executeHandlers(_event, input, handlers, context) {
|
|
|
88
92
|
continue;
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
else {
|
|
96
|
-
// Execute script/inline handlers in parallel
|
|
97
|
-
otherPromises.push(executeOtherHandler(handler, input));
|
|
98
|
-
}
|
|
95
|
+
eligible.push(handler);
|
|
96
|
+
}
|
|
97
|
+
if (eligible.length === 0) {
|
|
98
|
+
return skippedResults;
|
|
99
99
|
}
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
100
|
+
// Resolve execution order into waves
|
|
101
|
+
let waves;
|
|
102
|
+
try {
|
|
103
|
+
waves = (0, deps_js_1.resolveExecutionOrder)(eligible);
|
|
104
|
+
}
|
|
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++;
|
|
109
115
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
+
}
|
|
119
130
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
+
}
|
|
126
150
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
+
}
|
|
132
164
|
}
|
|
165
|
+
// Store output for downstream handlers
|
|
166
|
+
handlerOutputs[result.id] = result.output;
|
|
133
167
|
}
|
|
168
|
+
allResults.push(...waveResults);
|
|
134
169
|
}
|
|
135
|
-
return
|
|
170
|
+
return allResults;
|
|
136
171
|
}
|
|
137
172
|
/**
|
|
138
173
|
* Execute a single script or inline handler with error handling.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +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, resetSessionIsolatedHandlers } 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';
|
|
8
12
|
export { startWatcher, stopWatcher } from './watcher.js';
|
|
9
|
-
export { generateAuthToken, validateAuth } from './auth.js';
|
|
13
|
+
export { generateAuthToken, validateAuth, rotateToken } from './auth.js';
|
|
14
|
+
export type { RotateTokenOptions } from './auth.js';
|
|
10
15
|
export { evaluateFilter } from './filter.js';
|
|
11
16
|
export { executeLLMHandler, executeLLMHandlersBatched, calculateCost, resetClient } from './llm.js';
|
|
12
17
|
export { prefetchContext, renderPromptTemplate } from './prefetch.js';
|
|
13
|
-
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';
|
|
14
|
-
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.validateAuth = exports.generateAuthToken = exports.stopWatcher = exports.startWatcher = exports.resetSessionIsolatedHandlers = 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");
|
|
@@ -22,12 +33,20 @@ Object.defineProperty(exports, "runDoctor", { enumerable: true, get: function ()
|
|
|
22
33
|
var handlers_js_1 = require("./handlers.js");
|
|
23
34
|
Object.defineProperty(exports, "executeHandlers", { enumerable: true, get: function () { return handlers_js_1.executeHandlers; } });
|
|
24
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; } });
|
|
25
43
|
var watcher_js_1 = require("./watcher.js");
|
|
26
44
|
Object.defineProperty(exports, "startWatcher", { enumerable: true, get: function () { return watcher_js_1.startWatcher; } });
|
|
27
45
|
Object.defineProperty(exports, "stopWatcher", { enumerable: true, get: function () { return watcher_js_1.stopWatcher; } });
|
|
28
46
|
var auth_js_1 = require("./auth.js");
|
|
29
47
|
Object.defineProperty(exports, "generateAuthToken", { enumerable: true, get: function () { return auth_js_1.generateAuthToken; } });
|
|
30
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; } });
|
|
31
50
|
var filter_js_1 = require("./filter.js");
|
|
32
51
|
Object.defineProperty(exports, "evaluateFilter", { enumerable: true, get: function () { return filter_js_1.evaluateFilter; } });
|
|
33
52
|
var llm_js_1 = require("./llm.js");
|
|
@@ -49,3 +68,6 @@ Object.defineProperty(exports, "COSTS_FILE", { enumerable: true, get: function (
|
|
|
49
68
|
Object.defineProperty(exports, "DEFAULT_LLM_TIMEOUT", { enumerable: true, get: function () { return constants_js_1.DEFAULT_LLM_TIMEOUT; } });
|
|
50
69
|
Object.defineProperty(exports, "DEFAULT_LLM_MAX_TOKENS", { enumerable: true, get: function () { return constants_js_1.DEFAULT_LLM_MAX_TOKENS; } });
|
|
51
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,6 +9,10 @@ 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
|
*/
|
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,6 +103,14 @@ 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
|
*/
|
|
@@ -113,8 +125,20 @@ function createDefaultManifest(authToken) {
|
|
|
113
125
|
if (authToken) {
|
|
114
126
|
settings.authToken = authToken;
|
|
115
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');
|
|
116
131
|
const example = {
|
|
117
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
|
+
],
|
|
118
142
|
PreToolUse: [
|
|
119
143
|
{
|
|
120
144
|
id: 'example-guard',
|
package/dist/metrics.d.ts
CHANGED
|
@@ -7,6 +7,16 @@ interface AggregatedStats {
|
|
|
7
7
|
minDuration: number;
|
|
8
8
|
maxDuration: number;
|
|
9
9
|
}
|
|
10
|
+
export interface HandlerStats {
|
|
11
|
+
handler: string;
|
|
12
|
+
event: string;
|
|
13
|
+
fires: number;
|
|
14
|
+
errors: number;
|
|
15
|
+
filtered: number;
|
|
16
|
+
avgDuration: number;
|
|
17
|
+
minDuration: number;
|
|
18
|
+
maxDuration: number;
|
|
19
|
+
}
|
|
10
20
|
export declare class MetricsCollector {
|
|
11
21
|
private static readonly MAX_ENTRIES;
|
|
12
22
|
private entries;
|
|
@@ -22,6 +32,10 @@ export declare class MetricsCollector {
|
|
|
22
32
|
getStats(): AggregatedStats[];
|
|
23
33
|
/** Get stats for a specific session. */
|
|
24
34
|
getSessionStats(sessionId: string): AggregatedStats[];
|
|
35
|
+
/** Get per-handler stats (not just per-event). */
|
|
36
|
+
getHandlerStats(): HandlerStats[];
|
|
37
|
+
/** Format per-handler stats as a CLI-friendly table. */
|
|
38
|
+
formatHandlerStatsTable(): string;
|
|
25
39
|
/** Flush is a no-op since we append on every record, but provided for API completeness. */
|
|
26
40
|
flush(): void;
|
|
27
41
|
/** Format stats as a CLI-friendly table. */
|
package/dist/metrics.js
CHANGED
|
@@ -95,6 +95,53 @@ class MetricsCollector {
|
|
|
95
95
|
}
|
|
96
96
|
return stats.sort((a, b) => b.fires - a.fires);
|
|
97
97
|
}
|
|
98
|
+
/** Get per-handler stats (not just per-event). */
|
|
99
|
+
getHandlerStats() {
|
|
100
|
+
const all = this.loadAll();
|
|
101
|
+
const byHandler = new Map();
|
|
102
|
+
for (const entry of all) {
|
|
103
|
+
const existing = byHandler.get(entry.handler) ?? [];
|
|
104
|
+
existing.push(entry);
|
|
105
|
+
byHandler.set(entry.handler, existing);
|
|
106
|
+
}
|
|
107
|
+
const stats = [];
|
|
108
|
+
for (const [handler, entries] of byHandler) {
|
|
109
|
+
const durations = entries.map((e) => e.duration_ms);
|
|
110
|
+
stats.push({
|
|
111
|
+
handler,
|
|
112
|
+
event: entries[0].event,
|
|
113
|
+
fires: entries.length,
|
|
114
|
+
errors: entries.filter((e) => !e.ok).length,
|
|
115
|
+
filtered: entries.filter((e) => e.filtered).length,
|
|
116
|
+
avgDuration: durations.reduce((a, b) => a + b, 0) / durations.length,
|
|
117
|
+
minDuration: Math.min(...durations),
|
|
118
|
+
maxDuration: Math.max(...durations),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return stats.sort((a, b) => {
|
|
122
|
+
if (b.fires !== a.fires)
|
|
123
|
+
return b.fires - a.fires;
|
|
124
|
+
return b.avgDuration - a.avgDuration;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/** Format per-handler stats as a CLI-friendly table. */
|
|
128
|
+
formatHandlerStatsTable() {
|
|
129
|
+
const stats = this.getHandlerStats();
|
|
130
|
+
if (stats.length === 0) {
|
|
131
|
+
return 'No per-handler metrics recorded yet.';
|
|
132
|
+
}
|
|
133
|
+
const header = padHandlerRow(['Handler', 'Event', 'Fires', 'Errors', 'Avg ms', 'Max ms']);
|
|
134
|
+
const separator = '-'.repeat(header.length);
|
|
135
|
+
const rows = stats.map((s) => padHandlerRow([
|
|
136
|
+
s.handler,
|
|
137
|
+
s.event,
|
|
138
|
+
String(s.fires),
|
|
139
|
+
String(s.errors),
|
|
140
|
+
s.avgDuration.toFixed(1),
|
|
141
|
+
s.maxDuration.toFixed(1),
|
|
142
|
+
]));
|
|
143
|
+
return [header, separator, ...rows].join('\n');
|
|
144
|
+
}
|
|
98
145
|
/** Flush is a no-op since we append on every record, but provided for API completeness. */
|
|
99
146
|
flush() {
|
|
100
147
|
// Already written on each record()
|
|
@@ -249,3 +296,7 @@ function padRow(cols) {
|
|
|
249
296
|
const widths = [20, 8, 8, 10, 10, 10];
|
|
250
297
|
return cols.map((col, i) => col.padEnd(widths[i])).join(' ');
|
|
251
298
|
}
|
|
299
|
+
function padHandlerRow(cols) {
|
|
300
|
+
const widths = [35, 20, 7, 7, 8, 8];
|
|
301
|
+
return cols.map((col, i) => col.padEnd(widths[i])).join(' ');
|
|
302
|
+
}
|
package/dist/migrate.d.ts
CHANGED
|
@@ -11,6 +11,15 @@ export interface MigratePathOptions {
|
|
|
11
11
|
* Find the Claude Code settings.json path.
|
|
12
12
|
*/
|
|
13
13
|
export declare function getSettingsPath(options?: MigratePathOptions): string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Derive a readable handler ID from a hook command.
|
|
16
|
+
*
|
|
17
|
+
* "node /path/to/gsd-check-update.js" -> "gsd-check-update"
|
|
18
|
+
* "node /path/to/hooks/post-action.js" -> "post-action"
|
|
19
|
+
* "python3 -m almicio.hooks.session_context" -> "almicio-session-context"
|
|
20
|
+
* "bash -c 'source ~/.zshrc; python3 /path/to/tts-summary.py'" -> "tts-summary"
|
|
21
|
+
*/
|
|
22
|
+
export declare function deriveHandlerId(command: string, event: string, index: number): string;
|
|
14
23
|
/**
|
|
15
24
|
* Migrate Claude Code settings.json command hooks to clooks HTTP hooks.
|
|
16
25
|
*
|
package/dist/migrate.js
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
// clooks migration utilities — convert shell hooks to HTTP hooks
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.getSettingsPath = getSettingsPath;
|
|
5
|
+
exports.deriveHandlerId = deriveHandlerId;
|
|
5
6
|
exports.migrate = migrate;
|
|
6
7
|
exports.restore = restore;
|
|
7
8
|
const fs_1 = require("fs");
|
|
8
9
|
const path_1 = require("path");
|
|
9
10
|
const os_1 = require("os");
|
|
10
11
|
const constants_js_1 = require("./constants.js");
|
|
12
|
+
const builtin_hooks_js_1 = require("./builtin-hooks.js");
|
|
11
13
|
const yaml_1 = require("yaml");
|
|
12
14
|
/**
|
|
13
15
|
* Find the Claude Code settings.json path.
|
|
@@ -25,6 +27,46 @@ function getSettingsPath(options) {
|
|
|
25
27
|
}
|
|
26
28
|
return null;
|
|
27
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Derive a readable handler ID from a hook command.
|
|
32
|
+
*
|
|
33
|
+
* "node /path/to/gsd-check-update.js" -> "gsd-check-update"
|
|
34
|
+
* "node /path/to/hooks/post-action.js" -> "post-action"
|
|
35
|
+
* "python3 -m almicio.hooks.session_context" -> "almicio-session-context"
|
|
36
|
+
* "bash -c 'source ~/.zshrc; python3 /path/to/tts-summary.py'" -> "tts-summary"
|
|
37
|
+
*/
|
|
38
|
+
function deriveHandlerId(command, event, index) {
|
|
39
|
+
let basename = null;
|
|
40
|
+
// Try python3 -m module.name pattern
|
|
41
|
+
const moduleMatch = command.match(/python3?\s+-m\s+([\w.]+)/);
|
|
42
|
+
if (moduleMatch) {
|
|
43
|
+
const modulePath = moduleMatch[1];
|
|
44
|
+
const lastSegment = modulePath.split('.').pop() ?? '';
|
|
45
|
+
if (lastSegment)
|
|
46
|
+
basename = lastSegment;
|
|
47
|
+
}
|
|
48
|
+
// Try to find the last .js or .py file in the command
|
|
49
|
+
if (!basename) {
|
|
50
|
+
// Match quoted or unquoted file paths ending in .js or .py
|
|
51
|
+
const fileMatches = [...command.matchAll(/(?:["'])?([^\s"']+\.(?:js|py))(?:["'])?/g)];
|
|
52
|
+
if (fileMatches.length > 0) {
|
|
53
|
+
const lastFile = fileMatches[fileMatches.length - 1][1];
|
|
54
|
+
// Extract basename without extension
|
|
55
|
+
const parts = lastFile.split('/');
|
|
56
|
+
const filename = parts[parts.length - 1];
|
|
57
|
+
basename = filename.replace(/\.(js|py)$/, '');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!basename) {
|
|
61
|
+
return `migrated-${event.toLowerCase()}-${index}`;
|
|
62
|
+
}
|
|
63
|
+
// Sanitize: replace non-alphanumeric chars with hyphens, lowercase
|
|
64
|
+
let id = basename.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
65
|
+
if (!id) {
|
|
66
|
+
return `migrated-${event.toLowerCase()}-${index}`;
|
|
67
|
+
}
|
|
68
|
+
return id;
|
|
69
|
+
}
|
|
28
70
|
/**
|
|
29
71
|
* Migrate Claude Code settings.json command hooks to clooks HTTP hooks.
|
|
30
72
|
*
|
|
@@ -60,6 +102,7 @@ function migrate(options) {
|
|
|
60
102
|
// Extract command hooks and build manifest
|
|
61
103
|
const manifestHandlers = {};
|
|
62
104
|
let handlerIndex = 0;
|
|
105
|
+
const usedIds = new Set();
|
|
63
106
|
// NOTE: In v0.1, matchers from the original rule groups are not preserved in the
|
|
64
107
|
// migrated HTTP hooks — all command hooks are consolidated into matcher-less rule groups.
|
|
65
108
|
// This is acceptable because clooks dispatches based on event type, not matchers.
|
|
@@ -82,8 +125,14 @@ function migrate(options) {
|
|
|
82
125
|
continue;
|
|
83
126
|
manifestHandlers[event] = commandHooks.map((hook) => {
|
|
84
127
|
handlerIndex++;
|
|
128
|
+
let id = deriveHandlerId(hook.command, event, handlerIndex);
|
|
129
|
+
// Ensure uniqueness: if ID already used, append index
|
|
130
|
+
if (usedIds.has(id)) {
|
|
131
|
+
id = `${id}-${handlerIndex}`;
|
|
132
|
+
}
|
|
133
|
+
usedIds.add(id);
|
|
85
134
|
return {
|
|
86
|
-
id
|
|
135
|
+
id,
|
|
87
136
|
type: 'script',
|
|
88
137
|
command: hook.command,
|
|
89
138
|
timeout: hook.timeout ? hook.timeout * 1000 : 5000, // Claude uses seconds, we use ms
|
|
@@ -91,6 +140,20 @@ function migrate(options) {
|
|
|
91
140
|
};
|
|
92
141
|
});
|
|
93
142
|
}
|
|
143
|
+
// Install built-in hook scripts
|
|
144
|
+
(0, builtin_hooks_js_1.installBuiltinHooks)();
|
|
145
|
+
// Add update checker to SessionStart handlers
|
|
146
|
+
const checkUpdatePath = (0, path_1.join)(constants_js_1.HOOKS_DIR, 'check-update.js');
|
|
147
|
+
if (!manifestHandlers['SessionStart']) {
|
|
148
|
+
manifestHandlers['SessionStart'] = [];
|
|
149
|
+
}
|
|
150
|
+
manifestHandlers['SessionStart'].unshift({
|
|
151
|
+
id: 'clooks-check-update',
|
|
152
|
+
type: 'script',
|
|
153
|
+
command: `node ${checkUpdatePath}`,
|
|
154
|
+
timeout: 6000,
|
|
155
|
+
enabled: true,
|
|
156
|
+
});
|
|
94
157
|
// Write manifest.yaml
|
|
95
158
|
const manifest = {
|
|
96
159
|
handlers: manifestHandlers,
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { PluginManifest, PluginRegistry, InstalledPlugin, Manifest } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Load the plugin registry (installed.json).
|
|
4
|
+
*/
|
|
5
|
+
export declare function loadRegistry(registryPath?: string): PluginRegistry;
|
|
6
|
+
/**
|
|
7
|
+
* Save the plugin registry.
|
|
8
|
+
*/
|
|
9
|
+
export declare function saveRegistry(registry: PluginRegistry, registryPath?: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* Validate a plugin manifest.
|
|
12
|
+
* Similar to validateManifest but checks plugin-specific fields (name, version required).
|
|
13
|
+
*/
|
|
14
|
+
export declare function validatePluginManifest(manifest: PluginManifest): void;
|
|
15
|
+
/**
|
|
16
|
+
* Load all installed plugins and return their manifests.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadPlugins(pluginsDir?: string, registryPath?: string): {
|
|
19
|
+
name: string;
|
|
20
|
+
manifest: PluginManifest;
|
|
21
|
+
}[];
|
|
22
|
+
/**
|
|
23
|
+
* Merge user manifest + plugin manifests into a composite manifest.
|
|
24
|
+
* Plugin handler IDs are namespaced as "pluginName/handlerId".
|
|
25
|
+
* Prefetch keys are unioned.
|
|
26
|
+
* Settings come from user manifest only.
|
|
27
|
+
*/
|
|
28
|
+
export declare function mergeManifests(userManifest: Manifest, plugins: {
|
|
29
|
+
name: string;
|
|
30
|
+
manifest: PluginManifest;
|
|
31
|
+
}[]): Manifest;
|
|
32
|
+
/**
|
|
33
|
+
* Install a plugin from a local directory path.
|
|
34
|
+
* 1. Read clooks-plugin.yaml from the path
|
|
35
|
+
* 2. Validate it
|
|
36
|
+
* 3. Copy the directory to plugins dir under {name}/
|
|
37
|
+
* 4. Register in installed.json
|
|
38
|
+
* 5. Resolve $PLUGIN_DIR in handler commands to the installed path
|
|
39
|
+
*/
|
|
40
|
+
export declare function installPlugin(sourcePath: string, pluginsDir?: string, registryPath?: string): InstalledPlugin;
|
|
41
|
+
/**
|
|
42
|
+
* Uninstall a plugin by name.
|
|
43
|
+
* 1. Remove from installed.json
|
|
44
|
+
* 2. Delete the plugin directory
|
|
45
|
+
*/
|
|
46
|
+
export declare function uninstallPlugin(name: string, pluginsDir?: string, registryPath?: string): void;
|
|
47
|
+
/**
|
|
48
|
+
* List installed plugins.
|
|
49
|
+
*/
|
|
50
|
+
export declare function listPlugins(registryPath?: string): InstalledPlugin[];
|