@premierstudio/ai-hooks 1.1.3 → 1.1.5
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/dist/adapters/all.js +2 -2056
- package/dist/adapters/all.js.map +1 -1
- package/dist/adapters/index.js +1 -120
- package/dist/adapters/index.js.map +1 -1
- package/dist/chunk-DSRP646D.js +100 -0
- package/dist/chunk-DSRP646D.js.map +1 -0
- package/dist/chunk-FHFRGMW6.js +122 -0
- package/dist/chunk-FHFRGMW6.js.map +1 -0
- package/dist/chunk-HCEOWJK3.js +261 -0
- package/dist/chunk-HCEOWJK3.js.map +1 -0
- package/dist/chunk-N7ASBTX5.js +63 -0
- package/dist/chunk-N7ASBTX5.js.map +1 -0
- package/dist/chunk-ZOWUSGNF.js +1945 -0
- package/dist/chunk-ZOWUSGNF.js.map +1 -0
- package/dist/cli/index.js +31 -2338
- package/dist/cli/index.js.map +1 -1
- package/dist/hooks/index.js +2 -153
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.js +4 -528
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/adapters/all.js
CHANGED
|
@@ -1,2058 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import { resolve, dirname } from 'path';
|
|
4
|
-
|
|
5
|
-
// src/adapters/registry.ts
|
|
6
|
-
var AdapterRegistry = class {
|
|
7
|
-
adapters = /* @__PURE__ */ new Map();
|
|
8
|
-
factories = /* @__PURE__ */ new Map();
|
|
9
|
-
/**
|
|
10
|
-
* Register an adapter instance.
|
|
11
|
-
*/
|
|
12
|
-
register(adapter10) {
|
|
13
|
-
this.adapters.set(adapter10.id, adapter10);
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Register an adapter factory for lazy instantiation.
|
|
17
|
-
*/
|
|
18
|
-
registerFactory(id, factory) {
|
|
19
|
-
this.factories.set(id, factory);
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Get a registered adapter by ID.
|
|
23
|
-
*/
|
|
24
|
-
get(id) {
|
|
25
|
-
const existing = this.adapters.get(id);
|
|
26
|
-
if (existing) return existing;
|
|
27
|
-
const factory = this.factories.get(id);
|
|
28
|
-
if (factory) {
|
|
29
|
-
const adapter10 = factory();
|
|
30
|
-
this.adapters.set(id, adapter10);
|
|
31
|
-
return adapter10;
|
|
32
|
-
}
|
|
33
|
-
return void 0;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Get all registered adapter IDs.
|
|
37
|
-
*/
|
|
38
|
-
list() {
|
|
39
|
-
return [.../* @__PURE__ */ new Set([...this.adapters.keys(), ...this.factories.keys()])];
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Detect which tools are available in the current environment.
|
|
43
|
-
* Returns adapters that successfully detect their tool.
|
|
44
|
-
*/
|
|
45
|
-
async detectAll() {
|
|
46
|
-
const detected = [];
|
|
47
|
-
for (const id of this.list()) {
|
|
48
|
-
const adapter10 = this.get(id);
|
|
49
|
-
if (adapter10) {
|
|
50
|
-
try {
|
|
51
|
-
const found = await adapter10.detect();
|
|
52
|
-
if (found) {
|
|
53
|
-
detected.push(adapter10);
|
|
54
|
-
}
|
|
55
|
-
} catch {
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return detected;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Clear the registry. Useful for testing.
|
|
63
|
-
*/
|
|
64
|
-
clear() {
|
|
65
|
-
this.adapters.clear();
|
|
66
|
-
this.factories.clear();
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
var registry = new AdapterRegistry();
|
|
70
|
-
var BaseAdapter = class {
|
|
71
|
-
/**
|
|
72
|
-
* Default install: write generated configs to disk.
|
|
73
|
-
*/
|
|
74
|
-
async install(configs) {
|
|
75
|
-
for (const config of configs) {
|
|
76
|
-
const fullPath = resolve(process.cwd(), config.path);
|
|
77
|
-
await mkdir(dirname(fullPath), { recursive: true });
|
|
78
|
-
await writeFile(fullPath, config.content, "utf-8");
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Default uninstall: remove generated config files.
|
|
83
|
-
*/
|
|
84
|
-
async uninstall() {
|
|
85
|
-
}
|
|
86
|
-
// ── Utility Methods ───────────────────────────────────────
|
|
87
|
-
async fileExists(path) {
|
|
88
|
-
return existsSync(resolve(process.cwd(), path));
|
|
89
|
-
}
|
|
90
|
-
async readJsonFile(path) {
|
|
91
|
-
const fullPath = resolve(process.cwd(), path);
|
|
92
|
-
if (!existsSync(fullPath)) return null;
|
|
93
|
-
const content = await readFile(fullPath, "utf-8");
|
|
94
|
-
return JSON.parse(content);
|
|
95
|
-
}
|
|
96
|
-
async writeJsonFile(path, data) {
|
|
97
|
-
const fullPath = resolve(process.cwd(), path);
|
|
98
|
-
await mkdir(dirname(fullPath), { recursive: true });
|
|
99
|
-
await writeFile(fullPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
100
|
-
}
|
|
101
|
-
async removeFile(path) {
|
|
102
|
-
const fullPath = resolve(process.cwd(), path);
|
|
103
|
-
if (existsSync(fullPath)) {
|
|
104
|
-
await rm(fullPath);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Check if a CLI command exists on PATH.
|
|
109
|
-
*/
|
|
110
|
-
async commandExists(command) {
|
|
111
|
-
const { exec } = await import('child_process');
|
|
112
|
-
return new Promise((resolve11) => {
|
|
113
|
-
exec(`which ${command}`, (error) => {
|
|
114
|
-
resolve11(!error);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
var EVENT_MAP = {
|
|
120
|
-
"session:start": [],
|
|
121
|
-
"session:end": [],
|
|
122
|
-
"prompt:submit": [],
|
|
123
|
-
"prompt:response": [],
|
|
124
|
-
"tool:before": ["tool:pre-execute"],
|
|
125
|
-
"tool:after": ["tool:post-execute"],
|
|
126
|
-
"file:write": ["tool:pre-execute"],
|
|
127
|
-
"file:edit": ["tool:pre-execute"],
|
|
128
|
-
"file:delete": ["tool:pre-execute"],
|
|
129
|
-
"shell:before": ["tool:pre-execute"],
|
|
130
|
-
"shell:after": ["tool:post-execute"],
|
|
131
|
-
"mcp:before": ["tool:pre-execute"],
|
|
132
|
-
"mcp:after": ["tool:post-execute"]
|
|
133
|
-
};
|
|
134
|
-
var REVERSE_MAP = {
|
|
135
|
-
"tool:pre-execute": [
|
|
136
|
-
"tool:before",
|
|
137
|
-
"file:write",
|
|
138
|
-
"file:edit",
|
|
139
|
-
"file:delete",
|
|
140
|
-
"shell:before",
|
|
141
|
-
"mcp:before"
|
|
142
|
-
],
|
|
143
|
-
"tool:post-execute": ["tool:after", "shell:after", "mcp:after"]
|
|
144
|
-
};
|
|
145
|
-
var AmpAdapter = class extends BaseAdapter {
|
|
146
|
-
id = "amp";
|
|
147
|
-
name = "Amp";
|
|
148
|
-
version = "1.0";
|
|
149
|
-
capabilities = {
|
|
150
|
-
beforeHooks: false,
|
|
151
|
-
afterHooks: true,
|
|
152
|
-
mcp: true,
|
|
153
|
-
configFile: true,
|
|
154
|
-
supportedEvents: [
|
|
155
|
-
"tool:before",
|
|
156
|
-
"tool:after",
|
|
157
|
-
"file:write",
|
|
158
|
-
"file:edit",
|
|
159
|
-
"file:delete",
|
|
160
|
-
"shell:before",
|
|
161
|
-
"shell:after",
|
|
162
|
-
"mcp:before",
|
|
163
|
-
"mcp:after"
|
|
164
|
-
],
|
|
165
|
-
blockableEvents: []
|
|
166
|
-
};
|
|
167
|
-
async detect() {
|
|
168
|
-
const hasCommand = await this.commandExists("amp");
|
|
169
|
-
const hasDir = existsSync(resolve(process.cwd(), ".amp"));
|
|
170
|
-
return hasCommand || hasDir;
|
|
171
|
-
}
|
|
172
|
-
async generate(hooks) {
|
|
173
|
-
const configs = [];
|
|
174
|
-
const mcpConfig = {
|
|
175
|
-
mcpServers: {
|
|
176
|
-
"ai-hooks": {
|
|
177
|
-
command: "npx",
|
|
178
|
-
args: ["@premierstudio/mcp-server"],
|
|
179
|
-
env: {
|
|
180
|
-
AI_HOOKS_CONFIG: resolve(process.cwd(), "ai-hooks.config.ts")
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
configs.push({
|
|
186
|
-
path: ".amp/mcp.json",
|
|
187
|
-
content: JSON.stringify(mcpConfig, null, 2) + "\n",
|
|
188
|
-
format: "json"
|
|
189
|
-
});
|
|
190
|
-
const neededEvents = /* @__PURE__ */ new Set();
|
|
191
|
-
for (const hook of hooks) {
|
|
192
|
-
for (const event of hook.events) {
|
|
193
|
-
const nativeEvents = this.mapEvent(event);
|
|
194
|
-
for (const ne of nativeEvents) {
|
|
195
|
-
neededEvents.add(ne);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
const hooksList = hooks.map((h) => ({
|
|
200
|
-
id: h.id,
|
|
201
|
-
name: h.name,
|
|
202
|
-
events: h.events,
|
|
203
|
-
phase: h.phase,
|
|
204
|
-
nativeEvents: h.events.flatMap((e) => this.mapEvent(e))
|
|
205
|
-
}));
|
|
206
|
-
configs.push({
|
|
207
|
-
path: ".amp/ai-hooks-manifest.json",
|
|
208
|
-
content: JSON.stringify(
|
|
209
|
-
{
|
|
210
|
-
adapter: "amp",
|
|
211
|
-
version: "1.0",
|
|
212
|
-
hooks: hooksList,
|
|
213
|
-
nativeEvents: [...neededEvents],
|
|
214
|
-
mcpServer: "@premierstudio/mcp-server"
|
|
215
|
-
},
|
|
216
|
-
null,
|
|
217
|
-
2
|
|
218
|
-
) + "\n",
|
|
219
|
-
format: "json"
|
|
220
|
-
});
|
|
221
|
-
return configs;
|
|
222
|
-
}
|
|
223
|
-
mapEvent(event) {
|
|
224
|
-
return EVENT_MAP[event] ?? [];
|
|
225
|
-
}
|
|
226
|
-
mapNativeEvent(nativeEvent) {
|
|
227
|
-
return REVERSE_MAP[nativeEvent] ?? [];
|
|
228
|
-
}
|
|
229
|
-
async uninstall() {
|
|
230
|
-
await this.removeFile(".amp/mcp.json");
|
|
231
|
-
await this.removeFile(".amp/ai-hooks-manifest.json");
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
var adapter = new AmpAdapter();
|
|
235
|
-
registry.register(adapter);
|
|
236
|
-
var EVENT_MAP2 = {
|
|
237
|
-
"session:start": ["SessionStart"],
|
|
238
|
-
"session:end": [],
|
|
239
|
-
"prompt:submit": ["UserPromptSubmit"],
|
|
240
|
-
"prompt:response": ["PostToolUse"],
|
|
241
|
-
// approximate - Claude doesn't expose response directly
|
|
242
|
-
"tool:before": ["PreToolUse"],
|
|
243
|
-
"tool:after": ["PostToolUse"],
|
|
244
|
-
"file:read": ["PreToolUse"],
|
|
245
|
-
// Read tool
|
|
246
|
-
"file:write": ["PreToolUse"],
|
|
247
|
-
// Write tool
|
|
248
|
-
"file:edit": ["PreToolUse"],
|
|
249
|
-
// Edit tool
|
|
250
|
-
"file:delete": ["PreToolUse"],
|
|
251
|
-
"shell:before": ["PreToolUse"],
|
|
252
|
-
// Bash tool
|
|
253
|
-
"shell:after": ["PostToolUse"],
|
|
254
|
-
// Bash tool
|
|
255
|
-
"mcp:before": ["PreToolUse"],
|
|
256
|
-
"mcp:after": ["PostToolUse"],
|
|
257
|
-
notification: ["Notification"]
|
|
258
|
-
};
|
|
259
|
-
var REVERSE_MAP2 = {
|
|
260
|
-
SessionStart: ["session:start"],
|
|
261
|
-
UserPromptSubmit: ["prompt:submit"],
|
|
262
|
-
PreToolUse: [
|
|
263
|
-
"tool:before",
|
|
264
|
-
"file:write",
|
|
265
|
-
"file:edit",
|
|
266
|
-
"file:delete",
|
|
267
|
-
"shell:before",
|
|
268
|
-
"mcp:before"
|
|
269
|
-
],
|
|
270
|
-
PostToolUse: ["tool:after", "shell:after", "mcp:after"],
|
|
271
|
-
Notification: ["notification"]
|
|
272
|
-
};
|
|
273
|
-
var ClaudeCodeAdapter = class extends BaseAdapter {
|
|
274
|
-
id = "claude-code";
|
|
275
|
-
name = "Claude Code";
|
|
276
|
-
version = "1.0";
|
|
277
|
-
capabilities = {
|
|
278
|
-
beforeHooks: true,
|
|
279
|
-
afterHooks: true,
|
|
280
|
-
mcp: true,
|
|
281
|
-
configFile: true,
|
|
282
|
-
supportedEvents: [
|
|
283
|
-
"session:start",
|
|
284
|
-
"prompt:submit",
|
|
285
|
-
"tool:before",
|
|
286
|
-
"tool:after",
|
|
287
|
-
"file:read",
|
|
288
|
-
"file:write",
|
|
289
|
-
"file:edit",
|
|
290
|
-
"file:delete",
|
|
291
|
-
"shell:before",
|
|
292
|
-
"shell:after",
|
|
293
|
-
"mcp:before",
|
|
294
|
-
"mcp:after",
|
|
295
|
-
"notification"
|
|
296
|
-
],
|
|
297
|
-
blockableEvents: [
|
|
298
|
-
"prompt:submit",
|
|
299
|
-
"tool:before",
|
|
300
|
-
"file:write",
|
|
301
|
-
"file:edit",
|
|
302
|
-
"file:delete",
|
|
303
|
-
"shell:before",
|
|
304
|
-
"mcp:before"
|
|
305
|
-
]
|
|
306
|
-
};
|
|
307
|
-
async detect() {
|
|
308
|
-
const hasCommand = await this.commandExists("claude");
|
|
309
|
-
const hasDir = existsSync(resolve(process.cwd(), ".claude"));
|
|
310
|
-
return hasCommand || hasDir;
|
|
311
|
-
}
|
|
312
|
-
async generate(hooks) {
|
|
313
|
-
const configs = [];
|
|
314
|
-
configs.push(this.generateRunner());
|
|
315
|
-
configs.push(await this.generateSettings(hooks));
|
|
316
|
-
return configs;
|
|
317
|
-
}
|
|
318
|
-
mapEvent(event) {
|
|
319
|
-
return EVENT_MAP2[event] ?? [];
|
|
320
|
-
}
|
|
321
|
-
mapNativeEvent(nativeEvent) {
|
|
322
|
-
return REVERSE_MAP2[nativeEvent] ?? [];
|
|
323
|
-
}
|
|
324
|
-
async uninstall() {
|
|
325
|
-
await this.removeFile(".claude/hooks/ai-hooks-runner.js");
|
|
326
|
-
}
|
|
327
|
-
// -- Private Methods ---------------------------------------------
|
|
328
|
-
/**
|
|
329
|
-
* Generate the hook runner script that Claude Code hooks call.
|
|
330
|
-
* This script loads the ai-hooks config and runs the appropriate chain.
|
|
331
|
-
*/
|
|
332
|
-
generateRunner() {
|
|
333
|
-
const script = `#!/usr/bin/env node
|
|
334
|
-
/**
|
|
335
|
-
* ai-hooks runner for Claude Code.
|
|
336
|
-
* Generated by: ai-hooks generate
|
|
337
|
-
*
|
|
338
|
-
* This script is called by Claude Code hooks. It loads your
|
|
339
|
-
* ai-hooks.config.ts and runs the matching hook chain.
|
|
340
|
-
*
|
|
341
|
-
* DO NOT EDIT - regenerate with: ai-hooks generate
|
|
342
|
-
*/
|
|
343
|
-
|
|
344
|
-
import { loadConfig } from "@premierstudio/ai-hooks";
|
|
345
|
-
import { HookEngine } from "@premierstudio/ai-hooks";
|
|
346
|
-
|
|
347
|
-
const hookEvent = process.env.CLAUDE_HOOK_EVENT;
|
|
348
|
-
const toolName = process.env.CLAUDE_TOOL_NAME;
|
|
349
|
-
const inputJson = process.env.CLAUDE_TOOL_INPUT;
|
|
350
|
-
|
|
351
|
-
async function run() {
|
|
352
|
-
const config = await loadConfig();
|
|
353
|
-
const engine = new HookEngine(config);
|
|
354
|
-
|
|
355
|
-
// Build the event from Claude Code's environment variables
|
|
356
|
-
const event = buildEvent(hookEvent, toolName, inputJson);
|
|
357
|
-
if (!event) {
|
|
358
|
-
process.exit(0);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const toolInfo = { name: "claude-code", version: "1.0" };
|
|
362
|
-
const results = await engine.emit(event, toolInfo);
|
|
363
|
-
|
|
364
|
-
// Check for blocks
|
|
365
|
-
const blocked = results.find((r) => r.blocked);
|
|
366
|
-
if (blocked) {
|
|
367
|
-
// Claude Code reads stdout JSON for hook results
|
|
368
|
-
const output = JSON.stringify({
|
|
369
|
-
decision: "block",
|
|
370
|
-
reason: blocked.reason ?? "Blocked by ai-hooks",
|
|
371
|
-
});
|
|
372
|
-
process.stdout.write(output);
|
|
373
|
-
process.exit(0);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
process.exit(0);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function buildEvent(hookEvent, toolName, inputJson) {
|
|
380
|
-
const timestamp = Date.now();
|
|
381
|
-
const metadata = {};
|
|
382
|
-
|
|
383
|
-
try {
|
|
384
|
-
const input = inputJson ? JSON.parse(inputJson) : {};
|
|
385
|
-
|
|
386
|
-
switch (hookEvent) {
|
|
387
|
-
case "PreToolUse":
|
|
388
|
-
return resolvePreToolUse(toolName, input, timestamp, metadata);
|
|
389
|
-
case "PostToolUse":
|
|
390
|
-
return resolvePostToolUse(toolName, input, timestamp, metadata);
|
|
391
|
-
case "SessionStart":
|
|
392
|
-
return {
|
|
393
|
-
type: "session:start",
|
|
394
|
-
tool: "claude-code",
|
|
395
|
-
version: "1.0",
|
|
396
|
-
workingDirectory: process.cwd(),
|
|
397
|
-
timestamp,
|
|
398
|
-
metadata,
|
|
399
|
-
};
|
|
400
|
-
case "UserPromptSubmit":
|
|
401
|
-
return {
|
|
402
|
-
type: "prompt:submit",
|
|
403
|
-
prompt: input.prompt ?? "",
|
|
404
|
-
timestamp,
|
|
405
|
-
metadata,
|
|
406
|
-
};
|
|
407
|
-
default:
|
|
408
|
-
return null;
|
|
409
|
-
}
|
|
410
|
-
} catch {
|
|
411
|
-
return null;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function resolvePreToolUse(toolName, input, timestamp, metadata) {
|
|
416
|
-
switch (toolName) {
|
|
417
|
-
case "Write":
|
|
418
|
-
return {
|
|
419
|
-
type: "file:write",
|
|
420
|
-
path: input.file_path ?? "",
|
|
421
|
-
content: input.content ?? "",
|
|
422
|
-
timestamp,
|
|
423
|
-
metadata,
|
|
424
|
-
};
|
|
425
|
-
case "Edit":
|
|
426
|
-
return {
|
|
427
|
-
type: "file:edit",
|
|
428
|
-
path: input.file_path ?? "",
|
|
429
|
-
oldContent: input.old_string ?? "",
|
|
430
|
-
newContent: input.new_string ?? "",
|
|
431
|
-
timestamp,
|
|
432
|
-
metadata,
|
|
433
|
-
};
|
|
434
|
-
case "Bash":
|
|
435
|
-
return {
|
|
436
|
-
type: "shell:before",
|
|
437
|
-
command: input.command ?? "",
|
|
438
|
-
cwd: process.cwd(),
|
|
439
|
-
timestamp,
|
|
440
|
-
metadata,
|
|
441
|
-
};
|
|
442
|
-
default:
|
|
443
|
-
return {
|
|
444
|
-
type: "tool:before",
|
|
445
|
-
toolName: toolName ?? "unknown",
|
|
446
|
-
input: input ?? {},
|
|
447
|
-
timestamp,
|
|
448
|
-
metadata,
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
function resolvePostToolUse(toolName, input, timestamp, metadata) {
|
|
454
|
-
switch (toolName) {
|
|
455
|
-
case "Bash":
|
|
456
|
-
return {
|
|
457
|
-
type: "shell:after",
|
|
458
|
-
command: input.command ?? "",
|
|
459
|
-
cwd: process.cwd(),
|
|
460
|
-
exitCode: input.exitCode ?? 0,
|
|
461
|
-
stdout: input.stdout ?? "",
|
|
462
|
-
stderr: input.stderr ?? "",
|
|
463
|
-
duration: 0,
|
|
464
|
-
timestamp,
|
|
465
|
-
metadata,
|
|
466
|
-
};
|
|
467
|
-
default:
|
|
468
|
-
return {
|
|
469
|
-
type: "tool:after",
|
|
470
|
-
toolName: toolName ?? "unknown",
|
|
471
|
-
input: input ?? {},
|
|
472
|
-
output: {},
|
|
473
|
-
duration: 0,
|
|
474
|
-
timestamp,
|
|
475
|
-
metadata,
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
run().catch((err) => {
|
|
481
|
-
console.error("[ai-hooks] Error:", err.message);
|
|
482
|
-
process.exit(1);
|
|
483
|
-
});
|
|
484
|
-
`;
|
|
485
|
-
return {
|
|
486
|
-
path: ".claude/hooks/ai-hooks-runner.js",
|
|
487
|
-
content: script,
|
|
488
|
-
format: "js",
|
|
489
|
-
gitignore: false
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
/**
|
|
493
|
-
* Generate the Claude Code settings.json hook entries.
|
|
494
|
-
*/
|
|
495
|
-
async generateSettings(hooks) {
|
|
496
|
-
const settingsPath = ".claude/settings.json";
|
|
497
|
-
let existing = {};
|
|
498
|
-
const fullPath = resolve(process.cwd(), settingsPath);
|
|
499
|
-
if (existsSync(fullPath)) {
|
|
500
|
-
const raw = await readFile(fullPath, "utf-8");
|
|
501
|
-
existing = JSON.parse(raw);
|
|
502
|
-
}
|
|
503
|
-
const neededEvents = /* @__PURE__ */ new Set();
|
|
504
|
-
for (const hook of hooks) {
|
|
505
|
-
for (const event of hook.events) {
|
|
506
|
-
const nativeEvents = this.mapEvent(event);
|
|
507
|
-
for (const ne of nativeEvents) {
|
|
508
|
-
neededEvents.add(ne);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
const hooksConfig = {};
|
|
513
|
-
for (const event of neededEvents) {
|
|
514
|
-
const hookEntry = {
|
|
515
|
-
type: "command",
|
|
516
|
-
command: `node .claude/hooks/ai-hooks-runner.js`,
|
|
517
|
-
timeout: 10,
|
|
518
|
-
description: `ai-hooks: ${event}`
|
|
519
|
-
};
|
|
520
|
-
if (!hooksConfig[event]) {
|
|
521
|
-
hooksConfig[event] = [];
|
|
522
|
-
}
|
|
523
|
-
hooksConfig[event].push({
|
|
524
|
-
hooks: [hookEntry]
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
const existingHooks = existing.hooks ?? {};
|
|
528
|
-
const mergedHooks = { ...existingHooks };
|
|
529
|
-
for (const [event, entries] of Object.entries(hooksConfig)) {
|
|
530
|
-
if (!mergedHooks[event]) {
|
|
531
|
-
mergedHooks[event] = [];
|
|
532
|
-
}
|
|
533
|
-
mergedHooks[event] = mergedHooks[event].filter((entry) => !entry.hooks?.some((h) => h.description?.startsWith("ai-hooks:")));
|
|
534
|
-
mergedHooks[event].push(...entries);
|
|
535
|
-
}
|
|
536
|
-
const mergedSettings = {
|
|
537
|
-
...existing,
|
|
538
|
-
hooks: mergedHooks
|
|
539
|
-
};
|
|
540
|
-
return {
|
|
541
|
-
path: settingsPath,
|
|
542
|
-
content: JSON.stringify(mergedSettings, null, 2) + "\n",
|
|
543
|
-
format: "json",
|
|
544
|
-
gitignore: false
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
};
|
|
548
|
-
var adapter2 = new ClaudeCodeAdapter();
|
|
549
|
-
registry.register(adapter2);
|
|
550
|
-
var EVENT_MAP3 = {
|
|
551
|
-
"session:start": ["TaskStart"],
|
|
552
|
-
"session:end": ["TaskCancel"],
|
|
553
|
-
"prompt:submit": ["UserPromptSubmit"],
|
|
554
|
-
"prompt:response": [],
|
|
555
|
-
"tool:before": ["PreToolUse"],
|
|
556
|
-
"tool:after": ["PostToolUse"],
|
|
557
|
-
"file:read": ["PreToolUse"],
|
|
558
|
-
"file:write": ["PreToolUse"],
|
|
559
|
-
"file:edit": ["PreToolUse"],
|
|
560
|
-
"file:delete": ["PreToolUse"],
|
|
561
|
-
"shell:before": ["PreToolUse"],
|
|
562
|
-
"shell:after": ["PostToolUse"],
|
|
563
|
-
"mcp:before": ["PreToolUse"],
|
|
564
|
-
"mcp:after": ["PostToolUse"],
|
|
565
|
-
notification: []
|
|
566
|
-
};
|
|
567
|
-
var REVERSE_MAP3 = {
|
|
568
|
-
TaskStart: ["session:start"],
|
|
569
|
-
TaskCancel: ["session:end"],
|
|
570
|
-
TaskResume: ["session:start"],
|
|
571
|
-
UserPromptSubmit: ["prompt:submit"],
|
|
572
|
-
PreToolUse: [
|
|
573
|
-
"tool:before",
|
|
574
|
-
"file:read",
|
|
575
|
-
"file:write",
|
|
576
|
-
"file:edit",
|
|
577
|
-
"file:delete",
|
|
578
|
-
"shell:before",
|
|
579
|
-
"mcp:before"
|
|
580
|
-
],
|
|
581
|
-
PostToolUse: ["tool:after", "shell:after", "mcp:after"],
|
|
582
|
-
PreCompact: []
|
|
583
|
-
};
|
|
584
|
-
var ClineAdapter = class extends BaseAdapter {
|
|
585
|
-
id = "cline";
|
|
586
|
-
name = "Cline";
|
|
587
|
-
version = "1.0";
|
|
588
|
-
capabilities = {
|
|
589
|
-
beforeHooks: true,
|
|
590
|
-
afterHooks: true,
|
|
591
|
-
mcp: true,
|
|
592
|
-
configFile: true,
|
|
593
|
-
supportedEvents: [
|
|
594
|
-
"session:start",
|
|
595
|
-
"session:end",
|
|
596
|
-
"prompt:submit",
|
|
597
|
-
"tool:before",
|
|
598
|
-
"tool:after",
|
|
599
|
-
"file:read",
|
|
600
|
-
"file:write",
|
|
601
|
-
"file:edit",
|
|
602
|
-
"file:delete",
|
|
603
|
-
"shell:before",
|
|
604
|
-
"shell:after",
|
|
605
|
-
"mcp:before",
|
|
606
|
-
"mcp:after"
|
|
607
|
-
],
|
|
608
|
-
blockableEvents: [
|
|
609
|
-
"tool:before",
|
|
610
|
-
"file:read",
|
|
611
|
-
"file:write",
|
|
612
|
-
"file:edit",
|
|
613
|
-
"file:delete",
|
|
614
|
-
"shell:before",
|
|
615
|
-
"mcp:before"
|
|
616
|
-
]
|
|
617
|
-
};
|
|
618
|
-
async detect() {
|
|
619
|
-
const hasCommand = await this.commandExists("cline");
|
|
620
|
-
const hasDir = existsSync(resolve(process.cwd(), ".clinerules"));
|
|
621
|
-
return hasCommand || hasDir;
|
|
622
|
-
}
|
|
623
|
-
async generate(hooks) {
|
|
624
|
-
const configs = [];
|
|
625
|
-
const neededEvents = /* @__PURE__ */ new Set();
|
|
626
|
-
for (const hook of hooks) {
|
|
627
|
-
for (const event of hook.events) {
|
|
628
|
-
const nativeEvents = this.mapEvent(event);
|
|
629
|
-
for (const ne of nativeEvents) {
|
|
630
|
-
neededEvents.add(ne);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
for (const event of neededEvents) {
|
|
635
|
-
configs.push({
|
|
636
|
-
path: `.clinerules/hooks/${event}`,
|
|
637
|
-
content: this.generateHookScript(event),
|
|
638
|
-
format: "js"
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
return configs;
|
|
642
|
-
}
|
|
643
|
-
mapEvent(event) {
|
|
644
|
-
return EVENT_MAP3[event] ?? [];
|
|
645
|
-
}
|
|
646
|
-
mapNativeEvent(nativeEvent) {
|
|
647
|
-
return REVERSE_MAP3[nativeEvent] ?? [];
|
|
648
|
-
}
|
|
649
|
-
async uninstall() {
|
|
650
|
-
const hookNames = [
|
|
651
|
-
"PreToolUse",
|
|
652
|
-
"PostToolUse",
|
|
653
|
-
"UserPromptSubmit",
|
|
654
|
-
"TaskStart",
|
|
655
|
-
"TaskResume",
|
|
656
|
-
"TaskCancel",
|
|
657
|
-
"PreCompact"
|
|
658
|
-
];
|
|
659
|
-
for (const name of hookNames) {
|
|
660
|
-
await this.removeFile(`.clinerules/hooks/${name}`);
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
generateHookScript(eventName) {
|
|
664
|
-
return `#!/usr/bin/env node
|
|
665
|
-
/**
|
|
666
|
-
* ai-hooks runner for Cline (${eventName}).
|
|
667
|
-
* Generated by: ai-hooks generate
|
|
668
|
-
*
|
|
669
|
-
* Cline passes hook event data as JSON via STDIN with:
|
|
670
|
-
* hookName, taskId, workspaceRoots, pendingToolInfo, etc.
|
|
671
|
-
*
|
|
672
|
-
* Output JSON with { cancel: true, errorMessage: "..." } to block.
|
|
673
|
-
* Output JSON with { cancel: false } to allow.
|
|
674
|
-
*
|
|
675
|
-
* DO NOT EDIT - regenerate with: ai-hooks generate
|
|
676
|
-
*/
|
|
677
|
-
import { loadConfig, HookEngine } from "@premierstudio/ai-hooks";
|
|
678
|
-
|
|
679
|
-
async function readStdin() {
|
|
680
|
-
const chunks = [];
|
|
681
|
-
for await (const chunk of process.stdin) {
|
|
682
|
-
chunks.push(chunk);
|
|
683
|
-
}
|
|
684
|
-
return Buffer.concat(chunks).toString("utf-8");
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
async function run() {
|
|
688
|
-
const raw = await readStdin();
|
|
689
|
-
const input = JSON.parse(raw || "{}");
|
|
690
|
-
const hookName = input.hookName ?? "${eventName}";
|
|
691
|
-
const pendingToolInfo = input.pendingToolInfo ?? {};
|
|
692
|
-
const toolName = pendingToolInfo.toolName ?? "";
|
|
693
|
-
|
|
694
|
-
const config = await loadConfig();
|
|
695
|
-
const engine = new HookEngine(config);
|
|
696
|
-
const toolInfo = { name: "cline", version: "1.0" };
|
|
697
|
-
const timestamp = Date.now();
|
|
698
|
-
const metadata = { taskId: input.taskId ?? "" };
|
|
699
|
-
|
|
700
|
-
let event;
|
|
701
|
-
switch (hookName) {
|
|
702
|
-
case "TaskStart":
|
|
703
|
-
case "TaskResume":
|
|
704
|
-
event = {
|
|
705
|
-
type: "session:start",
|
|
706
|
-
tool: "cline",
|
|
707
|
-
version: "1.0",
|
|
708
|
-
workingDirectory: (input.workspaceRoots ?? [])[0] ?? process.cwd(),
|
|
709
|
-
timestamp,
|
|
710
|
-
metadata,
|
|
711
|
-
};
|
|
712
|
-
break;
|
|
713
|
-
case "TaskCancel":
|
|
714
|
-
event = { type: "session:end", tool: "cline", duration: 0, timestamp, metadata };
|
|
715
|
-
break;
|
|
716
|
-
case "UserPromptSubmit":
|
|
717
|
-
event = { type: "prompt:submit", prompt: input.userMessage ?? "", timestamp, metadata };
|
|
718
|
-
break;
|
|
719
|
-
case "PreToolUse":
|
|
720
|
-
event = resolvePreToolEvent(toolName, pendingToolInfo, timestamp, metadata);
|
|
721
|
-
break;
|
|
722
|
-
case "PostToolUse":
|
|
723
|
-
event = resolvePostToolEvent(toolName, pendingToolInfo, timestamp, metadata);
|
|
724
|
-
break;
|
|
725
|
-
default:
|
|
726
|
-
process.stdout.write(JSON.stringify({ cancel: false }));
|
|
727
|
-
process.exit(0);
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
const results = await engine.emit(event, toolInfo);
|
|
731
|
-
const blocked = results.find((r) => r.blocked);
|
|
732
|
-
|
|
733
|
-
if (blocked) {
|
|
734
|
-
process.stdout.write(JSON.stringify({
|
|
735
|
-
cancel: true,
|
|
736
|
-
errorMessage: blocked.reason ?? "Blocked by ai-hooks",
|
|
737
|
-
}));
|
|
738
|
-
process.exit(0);
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
process.stdout.write(JSON.stringify({ cancel: false }));
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
function resolvePreToolEvent(toolName, info, timestamp, metadata) {
|
|
745
|
-
switch (toolName) {
|
|
746
|
-
case "write_to_file":
|
|
747
|
-
return { type: "file:write", path: info.path ?? "", content: info.content ?? "", timestamp, metadata };
|
|
748
|
-
case "replace_in_file":
|
|
749
|
-
return { type: "file:edit", path: info.path ?? "", oldContent: info.diff ?? "", newContent: "", timestamp, metadata };
|
|
750
|
-
case "read_file":
|
|
751
|
-
return { type: "file:read", path: info.path ?? "", timestamp, metadata };
|
|
752
|
-
case "execute_command":
|
|
753
|
-
return { type: "shell:before", command: info.command ?? "", cwd: process.cwd(), timestamp, metadata };
|
|
754
|
-
case "use_mcp_tool":
|
|
755
|
-
return { type: "mcp:before", server: info.mcpServer ?? "", tool: info.mcpTool ?? "", input: {}, timestamp, metadata };
|
|
756
|
-
default:
|
|
757
|
-
return { type: "tool:before", toolName: toolName || "unknown", input: info, timestamp, metadata };
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
function resolvePostToolEvent(toolName, info, timestamp, metadata) {
|
|
762
|
-
switch (toolName) {
|
|
763
|
-
case "execute_command":
|
|
764
|
-
return {
|
|
765
|
-
type: "shell:after",
|
|
766
|
-
command: info.command ?? "",
|
|
767
|
-
cwd: process.cwd(),
|
|
768
|
-
exitCode: 0,
|
|
769
|
-
stdout: "",
|
|
770
|
-
stderr: "",
|
|
771
|
-
duration: 0,
|
|
772
|
-
timestamp,
|
|
773
|
-
metadata,
|
|
774
|
-
};
|
|
775
|
-
default:
|
|
776
|
-
return { type: "tool:after", toolName: toolName || "unknown", input: info, output: {}, duration: 0, timestamp, metadata };
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
run().catch((err) => {
|
|
781
|
-
console.error("[ai-hooks] Error:", err.message);
|
|
782
|
-
process.stdout.write(JSON.stringify({ cancel: false }));
|
|
783
|
-
process.exit(0);
|
|
784
|
-
});
|
|
785
|
-
`;
|
|
786
|
-
}
|
|
787
|
-
};
|
|
788
|
-
var adapter3 = new ClineAdapter();
|
|
789
|
-
registry.register(adapter3);
|
|
790
|
-
var EVENT_MAP4 = {
|
|
791
|
-
"session:start": ["session_start"],
|
|
792
|
-
"session:end": ["session_end"],
|
|
793
|
-
"prompt:submit": ["user_message"],
|
|
794
|
-
"prompt:response": ["assistant_message"],
|
|
795
|
-
"tool:before": ["before_tool_call"],
|
|
796
|
-
"tool:after": ["after_tool_call"],
|
|
797
|
-
"file:write": ["before_file_write"],
|
|
798
|
-
"file:edit": ["before_file_edit"],
|
|
799
|
-
"file:delete": ["before_file_delete"],
|
|
800
|
-
"shell:before": ["before_shell"],
|
|
801
|
-
"shell:after": ["after_shell"],
|
|
802
|
-
"mcp:before": ["before_mcp_call"],
|
|
803
|
-
"mcp:after": ["after_mcp_call"]
|
|
804
|
-
};
|
|
805
|
-
var REVERSE_MAP4 = {
|
|
806
|
-
session_start: ["session:start"],
|
|
807
|
-
session_end: ["session:end"],
|
|
808
|
-
user_message: ["prompt:submit"],
|
|
809
|
-
assistant_message: ["prompt:response"],
|
|
810
|
-
before_tool_call: ["tool:before"],
|
|
811
|
-
after_tool_call: ["tool:after"],
|
|
812
|
-
before_file_write: ["file:write"],
|
|
813
|
-
before_file_edit: ["file:edit"],
|
|
814
|
-
before_file_delete: ["file:delete"],
|
|
815
|
-
before_shell: ["shell:before"],
|
|
816
|
-
after_shell: ["shell:after"],
|
|
817
|
-
before_mcp_call: ["mcp:before"],
|
|
818
|
-
after_mcp_call: ["mcp:after"]
|
|
819
|
-
};
|
|
820
|
-
var CodexAdapter = class extends BaseAdapter {
|
|
821
|
-
id = "codex";
|
|
822
|
-
name = "Codex CLI";
|
|
823
|
-
version = "1.0";
|
|
824
|
-
capabilities = {
|
|
825
|
-
beforeHooks: true,
|
|
826
|
-
afterHooks: true,
|
|
827
|
-
mcp: true,
|
|
828
|
-
configFile: true,
|
|
829
|
-
supportedEvents: [
|
|
830
|
-
"session:start",
|
|
831
|
-
"session:end",
|
|
832
|
-
"prompt:submit",
|
|
833
|
-
"prompt:response",
|
|
834
|
-
"tool:before",
|
|
835
|
-
"tool:after",
|
|
836
|
-
"file:write",
|
|
837
|
-
"file:edit",
|
|
838
|
-
"file:delete",
|
|
839
|
-
"shell:before",
|
|
840
|
-
"shell:after",
|
|
841
|
-
"mcp:before",
|
|
842
|
-
"mcp:after"
|
|
843
|
-
],
|
|
844
|
-
blockableEvents: [
|
|
845
|
-
"tool:before",
|
|
846
|
-
"file:write",
|
|
847
|
-
"file:edit",
|
|
848
|
-
"file:delete",
|
|
849
|
-
"shell:before",
|
|
850
|
-
"mcp:before"
|
|
851
|
-
]
|
|
852
|
-
};
|
|
853
|
-
async detect() {
|
|
854
|
-
const hasCommand = await this.commandExists("codex");
|
|
855
|
-
const hasConfig = existsSync(resolve(process.cwd(), "codex.json")) || existsSync(resolve(process.cwd(), ".codex"));
|
|
856
|
-
return hasCommand || hasConfig;
|
|
857
|
-
}
|
|
858
|
-
async generate(hooks) {
|
|
859
|
-
const hookEntries = {};
|
|
860
|
-
for (const hook of hooks) {
|
|
861
|
-
for (const event of hook.events) {
|
|
862
|
-
const nativeEvents = this.mapEvent(event);
|
|
863
|
-
for (const ne of nativeEvents) {
|
|
864
|
-
hookEntries[ne] = {
|
|
865
|
-
command: "node .codex/hooks/ai-hooks-runner.js",
|
|
866
|
-
timeout: 10
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
return [
|
|
872
|
-
{
|
|
873
|
-
path: ".codex/hooks/ai-hooks-runner.js",
|
|
874
|
-
content: this.generateRunner(),
|
|
875
|
-
format: "js"
|
|
876
|
-
},
|
|
877
|
-
{
|
|
878
|
-
path: "codex.json",
|
|
879
|
-
content: JSON.stringify({ hooks: hookEntries }, null, 2) + "\n",
|
|
880
|
-
format: "json"
|
|
881
|
-
}
|
|
882
|
-
];
|
|
883
|
-
}
|
|
884
|
-
mapEvent(event) {
|
|
885
|
-
return EVENT_MAP4[event] ?? [];
|
|
886
|
-
}
|
|
887
|
-
mapNativeEvent(nativeEvent) {
|
|
888
|
-
return REVERSE_MAP4[nativeEvent] ?? [];
|
|
889
|
-
}
|
|
890
|
-
async uninstall() {
|
|
891
|
-
await this.removeFile(".codex/hooks/ai-hooks-runner.js");
|
|
892
|
-
}
|
|
893
|
-
generateRunner() {
|
|
894
|
-
return `#!/usr/bin/env node
|
|
895
|
-
/**
|
|
896
|
-
* ai-hooks runner for Codex CLI.
|
|
897
|
-
* Generated by: ai-hooks generate
|
|
898
|
-
*/
|
|
899
|
-
import { loadConfig, HookEngine } from "@premierstudio/ai-hooks";
|
|
900
|
-
|
|
901
|
-
const hookEvent = process.env.CODEX_HOOK_EVENT;
|
|
902
|
-
const toolInput = process.env.CODEX_TOOL_INPUT;
|
|
903
|
-
|
|
904
|
-
async function run() {
|
|
905
|
-
const config = await loadConfig();
|
|
906
|
-
const engine = new HookEngine(config);
|
|
907
|
-
const toolInfo = { name: "codex", version: "1.0" };
|
|
908
|
-
|
|
909
|
-
const input = toolInput ? JSON.parse(toolInput) : {};
|
|
910
|
-
const timestamp = Date.now();
|
|
911
|
-
const metadata = {};
|
|
912
|
-
|
|
913
|
-
let event;
|
|
914
|
-
switch (hookEvent) {
|
|
915
|
-
case "before_shell":
|
|
916
|
-
event = { type: "shell:before", command: input.command ?? "", cwd: process.cwd(), timestamp, metadata };
|
|
917
|
-
break;
|
|
918
|
-
case "before_file_write":
|
|
919
|
-
event = { type: "file:write", path: input.path ?? "", content: input.content ?? "", timestamp, metadata };
|
|
920
|
-
break;
|
|
921
|
-
case "before_file_edit":
|
|
922
|
-
event = { type: "file:edit", path: input.path ?? "", oldContent: input.old ?? "", newContent: input.new ?? "", timestamp, metadata };
|
|
923
|
-
break;
|
|
924
|
-
default:
|
|
925
|
-
event = { type: "tool:before", toolName: hookEvent ?? "unknown", input, timestamp, metadata };
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
const results = await engine.emit(event, toolInfo);
|
|
929
|
-
const blocked = results.find((r) => r.blocked);
|
|
930
|
-
|
|
931
|
-
if (blocked) {
|
|
932
|
-
console.log(JSON.stringify({ blocked: true, reason: blocked.reason }));
|
|
933
|
-
process.exit(1);
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
run().catch(() => process.exit(1));
|
|
938
|
-
`;
|
|
939
|
-
}
|
|
940
|
-
};
|
|
941
|
-
var adapter4 = new CodexAdapter();
|
|
942
|
-
registry.register(adapter4);
|
|
943
|
-
var EVENT_MAP5 = {
|
|
944
|
-
"session:start": [],
|
|
945
|
-
"session:end": ["stop"],
|
|
946
|
-
"prompt:submit": ["beforeSubmitPrompt"],
|
|
947
|
-
"prompt:response": ["stop"],
|
|
948
|
-
"tool:before": ["beforeMCPExecution"],
|
|
949
|
-
"tool:after": [],
|
|
950
|
-
"file:read": ["beforeReadFile"],
|
|
951
|
-
"file:write": ["afterFileEdit"],
|
|
952
|
-
"file:edit": ["afterFileEdit"],
|
|
953
|
-
"file:delete": [],
|
|
954
|
-
"shell:before": ["beforeShellExecution"],
|
|
955
|
-
"shell:after": [],
|
|
956
|
-
"mcp:before": ["beforeMCPExecution"],
|
|
957
|
-
"mcp:after": []
|
|
958
|
-
};
|
|
959
|
-
var REVERSE_MAP5 = {
|
|
960
|
-
beforeSubmitPrompt: ["prompt:submit"],
|
|
961
|
-
beforeShellExecution: ["shell:before"],
|
|
962
|
-
beforeMCPExecution: ["tool:before", "mcp:before"],
|
|
963
|
-
beforeReadFile: ["file:read"],
|
|
964
|
-
afterFileEdit: ["file:write", "file:edit"],
|
|
965
|
-
stop: ["session:end", "prompt:response"]
|
|
966
|
-
};
|
|
967
|
-
var CursorAdapter = class extends BaseAdapter {
|
|
968
|
-
id = "cursor";
|
|
969
|
-
name = "Cursor";
|
|
970
|
-
version = "1.0";
|
|
971
|
-
capabilities = {
|
|
972
|
-
beforeHooks: true,
|
|
973
|
-
afterHooks: true,
|
|
974
|
-
mcp: true,
|
|
975
|
-
configFile: true,
|
|
976
|
-
supportedEvents: [
|
|
977
|
-
"session:end",
|
|
978
|
-
"prompt:submit",
|
|
979
|
-
"prompt:response",
|
|
980
|
-
"tool:before",
|
|
981
|
-
"file:read",
|
|
982
|
-
"file:write",
|
|
983
|
-
"file:edit",
|
|
984
|
-
"shell:before",
|
|
985
|
-
"mcp:before"
|
|
986
|
-
],
|
|
987
|
-
blockableEvents: ["shell:before", "mcp:before", "tool:before"]
|
|
988
|
-
};
|
|
989
|
-
async detect() {
|
|
990
|
-
const hasCommand = await this.commandExists("cursor");
|
|
991
|
-
const hasDir = existsSync(resolve(process.cwd(), ".cursor"));
|
|
992
|
-
return hasCommand || hasDir;
|
|
993
|
-
}
|
|
994
|
-
async generate(hooks) {
|
|
995
|
-
const configs = [];
|
|
996
|
-
const neededEvents = /* @__PURE__ */ new Set();
|
|
997
|
-
for (const hook of hooks) {
|
|
998
|
-
for (const event of hook.events) {
|
|
999
|
-
const nativeEvents = this.mapEvent(event);
|
|
1000
|
-
for (const ne of nativeEvents) {
|
|
1001
|
-
neededEvents.add(ne);
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
configs.push({
|
|
1006
|
-
path: ".cursor/hooks/ai-hooks-runner.js",
|
|
1007
|
-
content: this.generateRunner(),
|
|
1008
|
-
format: "js"
|
|
1009
|
-
});
|
|
1010
|
-
const hooksConfig = {};
|
|
1011
|
-
for (const event of neededEvents) {
|
|
1012
|
-
if (!hooksConfig[event]) {
|
|
1013
|
-
hooksConfig[event] = [];
|
|
1014
|
-
}
|
|
1015
|
-
hooksConfig[event].push({
|
|
1016
|
-
command: `node hooks/ai-hooks-runner.js ${event}`
|
|
1017
|
-
});
|
|
1018
|
-
}
|
|
1019
|
-
configs.push({
|
|
1020
|
-
path: ".cursor/hooks.json",
|
|
1021
|
-
content: JSON.stringify({ version: 1, hooks: hooksConfig }, null, 2) + "\n",
|
|
1022
|
-
format: "json"
|
|
1023
|
-
});
|
|
1024
|
-
return configs;
|
|
1025
|
-
}
|
|
1026
|
-
mapEvent(event) {
|
|
1027
|
-
return EVENT_MAP5[event] ?? [];
|
|
1028
|
-
}
|
|
1029
|
-
mapNativeEvent(nativeEvent) {
|
|
1030
|
-
return REVERSE_MAP5[nativeEvent] ?? [];
|
|
1031
|
-
}
|
|
1032
|
-
async uninstall() {
|
|
1033
|
-
await this.removeFile(".cursor/hooks/ai-hooks-runner.js");
|
|
1034
|
-
await this.removeFile(".cursor/hooks.json");
|
|
1035
|
-
}
|
|
1036
|
-
generateRunner() {
|
|
1037
|
-
return `#!/usr/bin/env node
|
|
1038
|
-
/**
|
|
1039
|
-
* ai-hooks runner for Cursor.
|
|
1040
|
-
* Generated by: ai-hooks generate
|
|
1041
|
-
*
|
|
1042
|
-
* Cursor passes the hook event name as a CLI argument.
|
|
1043
|
-
* Blocking hooks (beforeShellExecution, beforeMCPExecution)
|
|
1044
|
-
* return JSON: { "permission": "deny", "agentMessage": "reason" }
|
|
1045
|
-
*
|
|
1046
|
-
* DO NOT EDIT - regenerate with: ai-hooks generate
|
|
1047
|
-
*/
|
|
1048
|
-
import { loadConfig, HookEngine } from "@premierstudio/ai-hooks";
|
|
1049
|
-
|
|
1050
|
-
async function readStdin() {
|
|
1051
|
-
const chunks = [];
|
|
1052
|
-
for await (const chunk of process.stdin) {
|
|
1053
|
-
chunks.push(chunk);
|
|
1054
|
-
}
|
|
1055
|
-
return Buffer.concat(chunks).toString("utf-8");
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
async function run() {
|
|
1059
|
-
const hookEventName = process.argv[2] ?? "";
|
|
1060
|
-
let input = {};
|
|
1061
|
-
try {
|
|
1062
|
-
const raw = await readStdin();
|
|
1063
|
-
input = JSON.parse(raw || "{}");
|
|
1064
|
-
} catch {
|
|
1065
|
-
input = {};
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
const config = await loadConfig();
|
|
1069
|
-
const engine = new HookEngine(config);
|
|
1070
|
-
const toolInfo = { name: "cursor", version: "1.0" };
|
|
1071
|
-
const timestamp = Date.now();
|
|
1072
|
-
const metadata = {};
|
|
1073
|
-
|
|
1074
|
-
let event;
|
|
1075
|
-
switch (hookEventName) {
|
|
1076
|
-
case "beforeSubmitPrompt":
|
|
1077
|
-
event = { type: "prompt:submit", prompt: input.prompt ?? "", timestamp, metadata };
|
|
1078
|
-
break;
|
|
1079
|
-
case "beforeShellExecution":
|
|
1080
|
-
event = { type: "shell:before", command: input.command ?? "", cwd: process.cwd(), timestamp, metadata };
|
|
1081
|
-
break;
|
|
1082
|
-
case "beforeMCPExecution":
|
|
1083
|
-
event = { type: "mcp:before", server: input.server ?? "", method: input.method ?? "", params: input.params ?? {}, timestamp, metadata };
|
|
1084
|
-
break;
|
|
1085
|
-
case "beforeReadFile":
|
|
1086
|
-
event = { type: "file:read", path: input.path ?? "", timestamp, metadata };
|
|
1087
|
-
break;
|
|
1088
|
-
case "afterFileEdit":
|
|
1089
|
-
event = { type: "file:edit", path: input.path ?? "", oldContent: "", newContent: "", timestamp, metadata };
|
|
1090
|
-
break;
|
|
1091
|
-
case "stop":
|
|
1092
|
-
event = { type: "session:end", tool: "cursor", duration: 0, timestamp, metadata };
|
|
1093
|
-
break;
|
|
1094
|
-
default:
|
|
1095
|
-
process.exit(0);
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
const results = await engine.emit(event, toolInfo);
|
|
1099
|
-
const blocked = results.find((r) => r.blocked);
|
|
1100
|
-
|
|
1101
|
-
if (blocked) {
|
|
1102
|
-
const response = JSON.stringify({
|
|
1103
|
-
permission: "deny",
|
|
1104
|
-
agentMessage: blocked.reason ?? "Blocked by ai-hooks",
|
|
1105
|
-
userMessage: blocked.reason ?? "Blocked by ai-hooks",
|
|
1106
|
-
});
|
|
1107
|
-
process.stdout.write(response);
|
|
1108
|
-
process.exit(0);
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
if (hookEventName === "beforeShellExecution" || hookEventName === "beforeMCPExecution") {
|
|
1112
|
-
process.stdout.write(JSON.stringify({ permission: "allow" }));
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
run().catch((err) => {
|
|
1117
|
-
console.error("[ai-hooks] Error:", err.message);
|
|
1118
|
-
process.exit(1);
|
|
1119
|
-
});
|
|
1120
|
-
`;
|
|
1121
|
-
}
|
|
1122
|
-
};
|
|
1123
|
-
var adapter5 = new CursorAdapter();
|
|
1124
|
-
registry.register(adapter5);
|
|
1125
|
-
var EVENT_MAP6 = {
|
|
1126
|
-
"session:start": ["SessionStart"],
|
|
1127
|
-
"session:end": ["SessionEnd"],
|
|
1128
|
-
"prompt:submit": ["UserPromptSubmit"],
|
|
1129
|
-
"prompt:response": ["Stop"],
|
|
1130
|
-
"tool:before": ["PreToolUse"],
|
|
1131
|
-
"tool:after": ["PostToolUse"],
|
|
1132
|
-
"file:read": ["PreToolUse"],
|
|
1133
|
-
"file:write": ["PreToolUse"],
|
|
1134
|
-
"file:edit": ["PreToolUse"],
|
|
1135
|
-
"file:delete": ["PreToolUse"],
|
|
1136
|
-
"shell:before": ["PreToolUse"],
|
|
1137
|
-
"shell:after": ["PostToolUse"],
|
|
1138
|
-
"mcp:before": ["PreToolUse"],
|
|
1139
|
-
"mcp:after": ["PostToolUse"],
|
|
1140
|
-
notification: ["Notification"]
|
|
1141
|
-
};
|
|
1142
|
-
var REVERSE_MAP6 = {
|
|
1143
|
-
SessionStart: ["session:start"],
|
|
1144
|
-
SessionEnd: ["session:end"],
|
|
1145
|
-
UserPromptSubmit: ["prompt:submit"],
|
|
1146
|
-
Stop: ["prompt:response"],
|
|
1147
|
-
PreToolUse: [
|
|
1148
|
-
"tool:before",
|
|
1149
|
-
"file:read",
|
|
1150
|
-
"file:write",
|
|
1151
|
-
"file:edit",
|
|
1152
|
-
"file:delete",
|
|
1153
|
-
"shell:before",
|
|
1154
|
-
"mcp:before"
|
|
1155
|
-
],
|
|
1156
|
-
PostToolUse: ["tool:after", "shell:after", "mcp:after"],
|
|
1157
|
-
Notification: ["notification"]
|
|
1158
|
-
};
|
|
1159
|
-
var DroidAdapter = class extends BaseAdapter {
|
|
1160
|
-
id = "droid";
|
|
1161
|
-
name = "Factory Droid";
|
|
1162
|
-
version = "1.0";
|
|
1163
|
-
capabilities = {
|
|
1164
|
-
beforeHooks: true,
|
|
1165
|
-
afterHooks: true,
|
|
1166
|
-
mcp: true,
|
|
1167
|
-
configFile: true,
|
|
1168
|
-
supportedEvents: [
|
|
1169
|
-
"session:start",
|
|
1170
|
-
"session:end",
|
|
1171
|
-
"prompt:submit",
|
|
1172
|
-
"prompt:response",
|
|
1173
|
-
"tool:before",
|
|
1174
|
-
"tool:after",
|
|
1175
|
-
"file:read",
|
|
1176
|
-
"file:write",
|
|
1177
|
-
"file:edit",
|
|
1178
|
-
"file:delete",
|
|
1179
|
-
"shell:before",
|
|
1180
|
-
"shell:after",
|
|
1181
|
-
"mcp:before",
|
|
1182
|
-
"mcp:after",
|
|
1183
|
-
"notification"
|
|
1184
|
-
],
|
|
1185
|
-
blockableEvents: [
|
|
1186
|
-
"tool:before",
|
|
1187
|
-
"file:read",
|
|
1188
|
-
"file:write",
|
|
1189
|
-
"file:edit",
|
|
1190
|
-
"file:delete",
|
|
1191
|
-
"shell:before",
|
|
1192
|
-
"mcp:before"
|
|
1193
|
-
]
|
|
1194
|
-
};
|
|
1195
|
-
async detect() {
|
|
1196
|
-
const hasCommand = await this.commandExists("droid");
|
|
1197
|
-
const hasDir = existsSync(resolve(process.cwd(), ".factory"));
|
|
1198
|
-
return hasCommand || hasDir;
|
|
1199
|
-
}
|
|
1200
|
-
async generate(hooks) {
|
|
1201
|
-
const configs = [];
|
|
1202
|
-
const neededEvents = /* @__PURE__ */ new Set();
|
|
1203
|
-
for (const hook of hooks) {
|
|
1204
|
-
for (const event of hook.events) {
|
|
1205
|
-
const nativeEvents = this.mapEvent(event);
|
|
1206
|
-
for (const ne of nativeEvents) {
|
|
1207
|
-
neededEvents.add(ne);
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
configs.push({
|
|
1212
|
-
path: ".factory/hooks/ai-hooks-runner.js",
|
|
1213
|
-
content: this.generateRunner(),
|
|
1214
|
-
format: "js"
|
|
1215
|
-
});
|
|
1216
|
-
const hooksConfig = {};
|
|
1217
|
-
const runnerAbsPath = resolve(process.cwd(), ".factory/hooks/ai-hooks-runner.js");
|
|
1218
|
-
for (const event of neededEvents) {
|
|
1219
|
-
const hookEntry = {
|
|
1220
|
-
hooks: [
|
|
1221
|
-
{
|
|
1222
|
-
type: "command",
|
|
1223
|
-
command: `node ${runnerAbsPath}`,
|
|
1224
|
-
timeout: 30
|
|
1225
|
-
}
|
|
1226
|
-
]
|
|
1227
|
-
};
|
|
1228
|
-
if (event === "PreToolUse" || event === "PostToolUse") {
|
|
1229
|
-
hookEntry.matcher = "*";
|
|
1230
|
-
}
|
|
1231
|
-
if (!hooksConfig[event]) {
|
|
1232
|
-
hooksConfig[event] = [];
|
|
1233
|
-
}
|
|
1234
|
-
hooksConfig[event].push(hookEntry);
|
|
1235
|
-
}
|
|
1236
|
-
const settingsConfig = await this.mergeSettings(hooksConfig);
|
|
1237
|
-
configs.push({
|
|
1238
|
-
path: ".factory/settings.json",
|
|
1239
|
-
content: JSON.stringify(settingsConfig, null, 2) + "\n",
|
|
1240
|
-
format: "json"
|
|
1241
|
-
});
|
|
1242
|
-
return configs;
|
|
1243
|
-
}
|
|
1244
|
-
mapEvent(event) {
|
|
1245
|
-
return EVENT_MAP6[event] ?? [];
|
|
1246
|
-
}
|
|
1247
|
-
mapNativeEvent(nativeEvent) {
|
|
1248
|
-
return REVERSE_MAP6[nativeEvent] ?? [];
|
|
1249
|
-
}
|
|
1250
|
-
async uninstall() {
|
|
1251
|
-
await this.removeFile(".factory/hooks/ai-hooks-runner.js");
|
|
1252
|
-
}
|
|
1253
|
-
async mergeSettings(hooksConfig) {
|
|
1254
|
-
const settingsPath = resolve(process.cwd(), ".factory/settings.json");
|
|
1255
|
-
let existing = {};
|
|
1256
|
-
if (existsSync(settingsPath)) {
|
|
1257
|
-
const raw = await readFile(settingsPath, "utf-8");
|
|
1258
|
-
existing = JSON.parse(raw);
|
|
1259
|
-
}
|
|
1260
|
-
const existingHooks = existing.hooks ?? {};
|
|
1261
|
-
const mergedHooks = { ...existingHooks };
|
|
1262
|
-
for (const [event, entries] of Object.entries(hooksConfig)) {
|
|
1263
|
-
if (!mergedHooks[event]) {
|
|
1264
|
-
mergedHooks[event] = [];
|
|
1265
|
-
}
|
|
1266
|
-
mergedHooks[event] = mergedHooks[event].filter((entry) => !entry.hooks?.some((h) => h.command?.includes("ai-hooks-runner")));
|
|
1267
|
-
mergedHooks[event].push(...entries);
|
|
1268
|
-
}
|
|
1269
|
-
return {
|
|
1270
|
-
...existing,
|
|
1271
|
-
hooks: mergedHooks
|
|
1272
|
-
};
|
|
1273
|
-
}
|
|
1274
|
-
generateRunner() {
|
|
1275
|
-
return `#!/usr/bin/env node
|
|
1276
|
-
/**
|
|
1277
|
-
* ai-hooks runner for Factory Droid.
|
|
1278
|
-
* Generated by: ai-hooks generate
|
|
1279
|
-
*
|
|
1280
|
-
* Droid passes hook event data as JSON via STDIN with:
|
|
1281
|
-
* hook_event_name, session_id, cwd, tool_name, tool_input, tool_response
|
|
1282
|
-
*
|
|
1283
|
-
* Exit code 0 = success, exit code 2 = block (PreToolUse).
|
|
1284
|
-
* STDERR on exit code 2 is fed back to Droid as context.
|
|
1285
|
-
*
|
|
1286
|
-
* DO NOT EDIT - regenerate with: ai-hooks generate
|
|
1287
|
-
*/
|
|
1288
|
-
import { loadConfig, HookEngine } from "@premierstudio/ai-hooks";
|
|
1289
|
-
|
|
1290
|
-
async function readStdin() {
|
|
1291
|
-
const chunks = [];
|
|
1292
|
-
for await (const chunk of process.stdin) {
|
|
1293
|
-
chunks.push(chunk);
|
|
1294
|
-
}
|
|
1295
|
-
return Buffer.concat(chunks).toString("utf-8");
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
async function run() {
|
|
1299
|
-
const raw = await readStdin();
|
|
1300
|
-
const input = JSON.parse(raw || "{}");
|
|
1301
|
-
const hookEventName = input.hook_event_name ?? "";
|
|
1302
|
-
const toolName = input.tool_name ?? "";
|
|
1303
|
-
const toolInput = input.tool_input ?? {};
|
|
1304
|
-
const toolResponse = input.tool_response ?? {};
|
|
1305
|
-
|
|
1306
|
-
const config = await loadConfig();
|
|
1307
|
-
const engine = new HookEngine(config);
|
|
1308
|
-
const toolInfo = { name: "droid", version: "1.0" };
|
|
1309
|
-
const timestamp = Date.now();
|
|
1310
|
-
const metadata = { sessionId: input.session_id ?? "" };
|
|
1311
|
-
|
|
1312
|
-
let event;
|
|
1313
|
-
switch (hookEventName) {
|
|
1314
|
-
case "SessionStart":
|
|
1315
|
-
event = {
|
|
1316
|
-
type: "session:start",
|
|
1317
|
-
tool: "droid",
|
|
1318
|
-
version: "1.0",
|
|
1319
|
-
workingDirectory: input.cwd ?? process.cwd(),
|
|
1320
|
-
timestamp,
|
|
1321
|
-
metadata,
|
|
1322
|
-
};
|
|
1323
|
-
break;
|
|
1324
|
-
case "SessionEnd":
|
|
1325
|
-
event = { type: "session:end", tool: "droid", duration: 0, timestamp, metadata };
|
|
1326
|
-
break;
|
|
1327
|
-
case "UserPromptSubmit":
|
|
1328
|
-
event = { type: "prompt:submit", prompt: toolInput.prompt ?? "", timestamp, metadata };
|
|
1329
|
-
break;
|
|
1330
|
-
case "Notification":
|
|
1331
|
-
event = { type: "notification", level: "info", message: toolInput.message ?? "", timestamp, metadata };
|
|
1332
|
-
break;
|
|
1333
|
-
case "Stop":
|
|
1334
|
-
event = { type: "session:end", tool: "droid", duration: 0, timestamp, metadata };
|
|
1335
|
-
break;
|
|
1336
|
-
case "PreToolUse":
|
|
1337
|
-
event = resolvePreToolEvent(toolName, toolInput, timestamp, metadata);
|
|
1338
|
-
break;
|
|
1339
|
-
case "PostToolUse":
|
|
1340
|
-
event = resolvePostToolEvent(toolName, toolInput, toolResponse, timestamp, metadata);
|
|
1341
|
-
break;
|
|
1342
|
-
default:
|
|
1343
|
-
process.exit(0);
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
const results = await engine.emit(event, toolInfo);
|
|
1347
|
-
const blocked = results.find((r) => r.blocked);
|
|
1348
|
-
|
|
1349
|
-
if (blocked) {
|
|
1350
|
-
process.stderr.write(blocked.reason ?? "Blocked by ai-hooks");
|
|
1351
|
-
process.exit(2);
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
function resolvePreToolEvent(toolName, toolInput, timestamp, metadata) {
|
|
1356
|
-
switch (toolName) {
|
|
1357
|
-
case "Write":
|
|
1358
|
-
return { type: "file:write", path: toolInput.file_path ?? "", content: toolInput.content ?? "", timestamp, metadata };
|
|
1359
|
-
case "Edit":
|
|
1360
|
-
return { type: "file:edit", path: toolInput.file_path ?? "", oldContent: toolInput.old_string ?? "", newContent: toolInput.new_string ?? "", timestamp, metadata };
|
|
1361
|
-
case "Read":
|
|
1362
|
-
return { type: "file:read", path: toolInput.file_path ?? "", timestamp, metadata };
|
|
1363
|
-
case "Bash":
|
|
1364
|
-
return { type: "shell:before", command: toolInput.command ?? "", cwd: process.cwd(), timestamp, metadata };
|
|
1365
|
-
default:
|
|
1366
|
-
return { type: "tool:before", toolName: toolName || "unknown", input: toolInput, timestamp, metadata };
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
function resolvePostToolEvent(toolName, toolInput, toolResponse, timestamp, metadata) {
|
|
1371
|
-
switch (toolName) {
|
|
1372
|
-
case "Bash":
|
|
1373
|
-
return {
|
|
1374
|
-
type: "shell:after",
|
|
1375
|
-
command: toolInput.command ?? "",
|
|
1376
|
-
cwd: process.cwd(),
|
|
1377
|
-
exitCode: toolResponse.exitCode ?? 0,
|
|
1378
|
-
stdout: toolResponse.stdout ?? "",
|
|
1379
|
-
stderr: toolResponse.stderr ?? "",
|
|
1380
|
-
duration: 0,
|
|
1381
|
-
timestamp,
|
|
1382
|
-
metadata,
|
|
1383
|
-
};
|
|
1384
|
-
default:
|
|
1385
|
-
return { type: "tool:after", toolName: toolName || "unknown", input: toolInput, output: toolResponse, duration: 0, timestamp, metadata };
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
run().catch((err) => {
|
|
1390
|
-
console.error("[ai-hooks] Error:", err.message);
|
|
1391
|
-
process.exit(1);
|
|
1392
|
-
});
|
|
1393
|
-
`;
|
|
1394
|
-
}
|
|
1395
|
-
};
|
|
1396
|
-
var adapter6 = new DroidAdapter();
|
|
1397
|
-
registry.register(adapter6);
|
|
1398
|
-
var EVENT_MAP7 = {
|
|
1399
|
-
"session:start": ["SessionStart"],
|
|
1400
|
-
"session:end": ["SessionEnd"],
|
|
1401
|
-
"prompt:submit": ["BeforePrompt"],
|
|
1402
|
-
"prompt:response": ["AfterResponse"],
|
|
1403
|
-
"tool:before": ["BeforeTool"],
|
|
1404
|
-
"tool:after": ["AfterTool"],
|
|
1405
|
-
"file:write": ["BeforeTool"],
|
|
1406
|
-
// FileWrite tool
|
|
1407
|
-
"file:edit": ["BeforeTool"],
|
|
1408
|
-
// FileEdit tool
|
|
1409
|
-
"file:delete": ["BeforeTool"],
|
|
1410
|
-
// FileDelete tool
|
|
1411
|
-
"shell:before": ["BeforeShell"],
|
|
1412
|
-
"shell:after": ["AfterShell"],
|
|
1413
|
-
"mcp:before": ["BeforeTool"],
|
|
1414
|
-
"mcp:after": ["AfterTool"]
|
|
1415
|
-
};
|
|
1416
|
-
var REVERSE_MAP7 = {
|
|
1417
|
-
SessionStart: ["session:start"],
|
|
1418
|
-
SessionEnd: ["session:end"],
|
|
1419
|
-
BeforePrompt: ["prompt:submit"],
|
|
1420
|
-
AfterResponse: ["prompt:response"],
|
|
1421
|
-
BeforeTool: ["tool:before", "file:write", "file:edit", "file:delete", "mcp:before"],
|
|
1422
|
-
AfterTool: ["tool:after", "mcp:after"],
|
|
1423
|
-
BeforeShell: ["shell:before"],
|
|
1424
|
-
AfterShell: ["shell:after"]
|
|
1425
|
-
};
|
|
1426
|
-
var GeminiCliAdapter = class extends BaseAdapter {
|
|
1427
|
-
id = "gemini-cli";
|
|
1428
|
-
name = "Gemini CLI";
|
|
1429
|
-
version = "1.0";
|
|
1430
|
-
capabilities = {
|
|
1431
|
-
beforeHooks: true,
|
|
1432
|
-
afterHooks: true,
|
|
1433
|
-
mcp: true,
|
|
1434
|
-
configFile: true,
|
|
1435
|
-
supportedEvents: [
|
|
1436
|
-
"session:start",
|
|
1437
|
-
"session:end",
|
|
1438
|
-
"prompt:submit",
|
|
1439
|
-
"prompt:response",
|
|
1440
|
-
"tool:before",
|
|
1441
|
-
"tool:after",
|
|
1442
|
-
"file:write",
|
|
1443
|
-
"file:edit",
|
|
1444
|
-
"file:delete",
|
|
1445
|
-
"shell:before",
|
|
1446
|
-
"shell:after",
|
|
1447
|
-
"mcp:before",
|
|
1448
|
-
"mcp:after"
|
|
1449
|
-
],
|
|
1450
|
-
blockableEvents: [
|
|
1451
|
-
"prompt:submit",
|
|
1452
|
-
"tool:before",
|
|
1453
|
-
"file:write",
|
|
1454
|
-
"file:edit",
|
|
1455
|
-
"file:delete",
|
|
1456
|
-
"shell:before",
|
|
1457
|
-
"mcp:before"
|
|
1458
|
-
]
|
|
1459
|
-
};
|
|
1460
|
-
async detect() {
|
|
1461
|
-
const hasCommand = await this.commandExists("gemini");
|
|
1462
|
-
const hasConfig = existsSync(resolve(process.cwd(), ".gemini"));
|
|
1463
|
-
return hasCommand || hasConfig;
|
|
1464
|
-
}
|
|
1465
|
-
async generate(hooks) {
|
|
1466
|
-
const configs = [];
|
|
1467
|
-
const neededEvents = /* @__PURE__ */ new Set();
|
|
1468
|
-
for (const hook of hooks) {
|
|
1469
|
-
for (const event of hook.events) {
|
|
1470
|
-
const nativeEvents = this.mapEvent(event);
|
|
1471
|
-
for (const ne of nativeEvents) {
|
|
1472
|
-
neededEvents.add(ne);
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
for (const event of neededEvents) {
|
|
1477
|
-
configs.push({
|
|
1478
|
-
path: `.gemini/hooks/${event}.js`,
|
|
1479
|
-
content: this.generateEventScript(event),
|
|
1480
|
-
format: "js"
|
|
1481
|
-
});
|
|
1482
|
-
}
|
|
1483
|
-
configs.push({
|
|
1484
|
-
path: ".gemini/settings.json",
|
|
1485
|
-
content: JSON.stringify(
|
|
1486
|
-
{
|
|
1487
|
-
hooks: {
|
|
1488
|
-
enabled: true,
|
|
1489
|
-
directory: ".gemini/hooks"
|
|
1490
|
-
}
|
|
1491
|
-
},
|
|
1492
|
-
null,
|
|
1493
|
-
2
|
|
1494
|
-
) + "\n",
|
|
1495
|
-
format: "json"
|
|
1496
|
-
});
|
|
1497
|
-
return configs;
|
|
1498
|
-
}
|
|
1499
|
-
mapEvent(event) {
|
|
1500
|
-
return EVENT_MAP7[event] ?? [];
|
|
1501
|
-
}
|
|
1502
|
-
mapNativeEvent(nativeEvent) {
|
|
1503
|
-
return REVERSE_MAP7[nativeEvent] ?? [];
|
|
1504
|
-
}
|
|
1505
|
-
async uninstall() {
|
|
1506
|
-
for (const event of Object.keys(REVERSE_MAP7)) {
|
|
1507
|
-
await this.removeFile(`.gemini/hooks/${event}.js`);
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
generateEventScript(nativeEvent) {
|
|
1511
|
-
return `#!/usr/bin/env node
|
|
1512
|
-
/**
|
|
1513
|
-
* ai-hooks runner for Gemini CLI (${nativeEvent}).
|
|
1514
|
-
* Generated by: ai-hooks generate
|
|
1515
|
-
*/
|
|
1516
|
-
import { loadConfig, HookEngine } from "@premierstudio/ai-hooks";
|
|
1517
|
-
|
|
1518
|
-
const input = JSON.parse(process.env.GEMINI_HOOK_INPUT ?? "{}");
|
|
1519
|
-
|
|
1520
|
-
async function run() {
|
|
1521
|
-
const config = await loadConfig();
|
|
1522
|
-
const engine = new HookEngine(config);
|
|
1523
|
-
const toolInfo = { name: "gemini-cli", version: "1.0" };
|
|
1524
|
-
const timestamp = Date.now();
|
|
1525
|
-
const metadata = {};
|
|
1526
|
-
|
|
1527
|
-
let event;
|
|
1528
|
-
switch ("${nativeEvent}") {
|
|
1529
|
-
case "SessionStart":
|
|
1530
|
-
event = { type: "session:start", tool: "gemini-cli", version: "1.0", workingDirectory: process.cwd(), timestamp, metadata };
|
|
1531
|
-
break;
|
|
1532
|
-
case "BeforeShell":
|
|
1533
|
-
event = { type: "shell:before", command: input.command ?? "", cwd: process.cwd(), timestamp, metadata };
|
|
1534
|
-
break;
|
|
1535
|
-
case "BeforeTool":
|
|
1536
|
-
event = { type: "tool:before", toolName: input.toolName ?? "unknown", input, timestamp, metadata };
|
|
1537
|
-
break;
|
|
1538
|
-
case "BeforePrompt":
|
|
1539
|
-
event = { type: "prompt:submit", prompt: input.prompt ?? "", timestamp, metadata };
|
|
1540
|
-
break;
|
|
1541
|
-
default:
|
|
1542
|
-
event = { type: "tool:after", toolName: "${nativeEvent}", input, output: {}, duration: 0, timestamp, metadata };
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
const results = await engine.emit(event, toolInfo);
|
|
1546
|
-
const blocked = results.find((r) => r.blocked);
|
|
1547
|
-
|
|
1548
|
-
if (blocked) {
|
|
1549
|
-
console.log(JSON.stringify({ blocked: true, reason: blocked.reason }));
|
|
1550
|
-
process.exit(1);
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
run().catch(() => process.exit(1));
|
|
1555
|
-
`;
|
|
1556
|
-
}
|
|
1557
|
-
};
|
|
1558
|
-
var adapter7 = new GeminiCliAdapter();
|
|
1559
|
-
registry.register(adapter7);
|
|
1560
|
-
var EVENT_MAP8 = {
|
|
1561
|
-
"session:start": ["agentSpawn"],
|
|
1562
|
-
"session:end": ["stop"],
|
|
1563
|
-
"prompt:submit": ["userPromptSubmit"],
|
|
1564
|
-
"prompt:response": ["stop"],
|
|
1565
|
-
"tool:before": ["preToolUse"],
|
|
1566
|
-
"tool:after": ["postToolUse"],
|
|
1567
|
-
"file:read": ["preToolUse"],
|
|
1568
|
-
"file:write": ["preToolUse"],
|
|
1569
|
-
"file:edit": ["preToolUse"],
|
|
1570
|
-
"file:delete": ["preToolUse"],
|
|
1571
|
-
"shell:before": ["preToolUse"],
|
|
1572
|
-
"shell:after": ["postToolUse"],
|
|
1573
|
-
"mcp:before": ["preToolUse"],
|
|
1574
|
-
"mcp:after": ["postToolUse"]
|
|
1575
|
-
};
|
|
1576
|
-
var REVERSE_MAP8 = {
|
|
1577
|
-
agentSpawn: ["session:start"],
|
|
1578
|
-
userPromptSubmit: ["prompt:submit"],
|
|
1579
|
-
preToolUse: [
|
|
1580
|
-
"tool:before",
|
|
1581
|
-
"file:read",
|
|
1582
|
-
"file:write",
|
|
1583
|
-
"file:edit",
|
|
1584
|
-
"file:delete",
|
|
1585
|
-
"shell:before",
|
|
1586
|
-
"mcp:before"
|
|
1587
|
-
],
|
|
1588
|
-
postToolUse: ["tool:after", "shell:after", "mcp:after"],
|
|
1589
|
-
stop: ["session:end", "prompt:response"]
|
|
1590
|
-
};
|
|
1591
|
-
var KiroAdapter = class extends BaseAdapter {
|
|
1592
|
-
id = "kiro";
|
|
1593
|
-
name = "Kiro CLI";
|
|
1594
|
-
version = "1.0";
|
|
1595
|
-
capabilities = {
|
|
1596
|
-
beforeHooks: true,
|
|
1597
|
-
afterHooks: true,
|
|
1598
|
-
mcp: true,
|
|
1599
|
-
configFile: true,
|
|
1600
|
-
supportedEvents: [
|
|
1601
|
-
"session:start",
|
|
1602
|
-
"session:end",
|
|
1603
|
-
"prompt:submit",
|
|
1604
|
-
"prompt:response",
|
|
1605
|
-
"tool:before",
|
|
1606
|
-
"tool:after",
|
|
1607
|
-
"file:read",
|
|
1608
|
-
"file:write",
|
|
1609
|
-
"file:edit",
|
|
1610
|
-
"file:delete",
|
|
1611
|
-
"shell:before",
|
|
1612
|
-
"shell:after",
|
|
1613
|
-
"mcp:before",
|
|
1614
|
-
"mcp:after"
|
|
1615
|
-
],
|
|
1616
|
-
blockableEvents: [
|
|
1617
|
-
"tool:before",
|
|
1618
|
-
"file:read",
|
|
1619
|
-
"file:write",
|
|
1620
|
-
"file:edit",
|
|
1621
|
-
"file:delete",
|
|
1622
|
-
"shell:before",
|
|
1623
|
-
"mcp:before"
|
|
1624
|
-
]
|
|
1625
|
-
};
|
|
1626
|
-
async detect() {
|
|
1627
|
-
const hasCommand = await this.commandExists("kiro");
|
|
1628
|
-
const hasDir = existsSync(resolve(process.cwd(), ".kiro"));
|
|
1629
|
-
return hasCommand || hasDir;
|
|
1630
|
-
}
|
|
1631
|
-
async generate(hooks) {
|
|
1632
|
-
const configs = [];
|
|
1633
|
-
const neededEvents = /* @__PURE__ */ new Set();
|
|
1634
|
-
for (const hook of hooks) {
|
|
1635
|
-
for (const event of hook.events) {
|
|
1636
|
-
const nativeEvents = this.mapEvent(event);
|
|
1637
|
-
for (const ne of nativeEvents) {
|
|
1638
|
-
neededEvents.add(ne);
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
}
|
|
1642
|
-
configs.push({
|
|
1643
|
-
path: ".kiro/hooks/ai-hooks-runner.js",
|
|
1644
|
-
content: this.generateRunner(),
|
|
1645
|
-
format: "js"
|
|
1646
|
-
});
|
|
1647
|
-
const hooksConfig = {};
|
|
1648
|
-
for (const event of neededEvents) {
|
|
1649
|
-
const entry = {
|
|
1650
|
-
command: "node .kiro/hooks/ai-hooks-runner.js"
|
|
1651
|
-
};
|
|
1652
|
-
if (event === "preToolUse" || event === "postToolUse") {
|
|
1653
|
-
entry.matcher = "*";
|
|
1654
|
-
}
|
|
1655
|
-
if (!hooksConfig[event]) {
|
|
1656
|
-
hooksConfig[event] = [];
|
|
1657
|
-
}
|
|
1658
|
-
hooksConfig[event].push(entry);
|
|
1659
|
-
}
|
|
1660
|
-
configs.push({
|
|
1661
|
-
path: ".kiro/hooks/ai-hooks.json",
|
|
1662
|
-
content: JSON.stringify({ hooks: hooksConfig }, null, 2) + "\n",
|
|
1663
|
-
format: "json"
|
|
1664
|
-
});
|
|
1665
|
-
return configs;
|
|
1666
|
-
}
|
|
1667
|
-
mapEvent(event) {
|
|
1668
|
-
return EVENT_MAP8[event] ?? [];
|
|
1669
|
-
}
|
|
1670
|
-
mapNativeEvent(nativeEvent) {
|
|
1671
|
-
return REVERSE_MAP8[nativeEvent] ?? [];
|
|
1672
|
-
}
|
|
1673
|
-
async uninstall() {
|
|
1674
|
-
await this.removeFile(".kiro/hooks/ai-hooks-runner.js");
|
|
1675
|
-
await this.removeFile(".kiro/hooks/ai-hooks.json");
|
|
1676
|
-
}
|
|
1677
|
-
generateRunner() {
|
|
1678
|
-
return `#!/usr/bin/env node
|
|
1679
|
-
/**
|
|
1680
|
-
* ai-hooks runner for Kiro CLI.
|
|
1681
|
-
* Generated by: ai-hooks generate
|
|
1682
|
-
*
|
|
1683
|
-
* Kiro passes hook event data as JSON via STDIN.
|
|
1684
|
-
* Exit code 0 = success, exit code 2 = block (preToolUse only).
|
|
1685
|
-
*
|
|
1686
|
-
* DO NOT EDIT - regenerate with: ai-hooks generate
|
|
1687
|
-
*/
|
|
1688
|
-
import { loadConfig, HookEngine } from "@premierstudio/ai-hooks";
|
|
1689
|
-
|
|
1690
|
-
async function readStdin() {
|
|
1691
|
-
const chunks = [];
|
|
1692
|
-
for await (const chunk of process.stdin) {
|
|
1693
|
-
chunks.push(chunk);
|
|
1694
|
-
}
|
|
1695
|
-
return Buffer.concat(chunks).toString("utf-8");
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
async function run() {
|
|
1699
|
-
const raw = await readStdin();
|
|
1700
|
-
const input = JSON.parse(raw || "{}");
|
|
1701
|
-
const hookEventName = input.hook_event_name ?? "";
|
|
1702
|
-
const toolName = input.tool_name ?? "";
|
|
1703
|
-
const toolInput = input.tool_input ?? {};
|
|
1704
|
-
|
|
1705
|
-
const config = await loadConfig();
|
|
1706
|
-
const engine = new HookEngine(config);
|
|
1707
|
-
const toolInfo = { name: "kiro", version: "1.0" };
|
|
1708
|
-
const timestamp = Date.now();
|
|
1709
|
-
const metadata = {};
|
|
1710
|
-
|
|
1711
|
-
let event;
|
|
1712
|
-
switch (hookEventName) {
|
|
1713
|
-
case "agentSpawn":
|
|
1714
|
-
event = {
|
|
1715
|
-
type: "session:start",
|
|
1716
|
-
tool: "kiro",
|
|
1717
|
-
version: "1.0",
|
|
1718
|
-
workingDirectory: input.cwd ?? process.cwd(),
|
|
1719
|
-
timestamp,
|
|
1720
|
-
metadata,
|
|
1721
|
-
};
|
|
1722
|
-
break;
|
|
1723
|
-
case "userPromptSubmit":
|
|
1724
|
-
event = {
|
|
1725
|
-
type: "prompt:submit",
|
|
1726
|
-
prompt: toolInput.prompt ?? "",
|
|
1727
|
-
timestamp,
|
|
1728
|
-
metadata,
|
|
1729
|
-
};
|
|
1730
|
-
break;
|
|
1731
|
-
case "preToolUse":
|
|
1732
|
-
event = resolvePreToolEvent(toolName, toolInput, timestamp, metadata);
|
|
1733
|
-
break;
|
|
1734
|
-
case "postToolUse":
|
|
1735
|
-
event = resolvePostToolEvent(toolName, toolInput, input.tool_response ?? {}, timestamp, metadata);
|
|
1736
|
-
break;
|
|
1737
|
-
case "stop":
|
|
1738
|
-
event = {
|
|
1739
|
-
type: "session:end",
|
|
1740
|
-
tool: "kiro",
|
|
1741
|
-
duration: 0,
|
|
1742
|
-
timestamp,
|
|
1743
|
-
metadata,
|
|
1744
|
-
};
|
|
1745
|
-
break;
|
|
1746
|
-
default:
|
|
1747
|
-
process.exit(0);
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
const results = await engine.emit(event, toolInfo);
|
|
1751
|
-
const blocked = results.find((r) => r.blocked);
|
|
1752
|
-
|
|
1753
|
-
if (blocked) {
|
|
1754
|
-
process.stderr.write(blocked.reason ?? "Blocked by ai-hooks");
|
|
1755
|
-
process.exit(2);
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
function resolvePreToolEvent(toolName, toolInput, timestamp, metadata) {
|
|
1760
|
-
switch (toolName) {
|
|
1761
|
-
case "fs_write":
|
|
1762
|
-
case "write":
|
|
1763
|
-
return { type: "file:write", path: toolInput.path ?? "", content: toolInput.content ?? "", timestamp, metadata };
|
|
1764
|
-
case "fs_edit":
|
|
1765
|
-
case "edit":
|
|
1766
|
-
return { type: "file:edit", path: toolInput.path ?? "", oldContent: toolInput.old_string ?? "", newContent: toolInput.new_string ?? "", timestamp, metadata };
|
|
1767
|
-
case "fs_read":
|
|
1768
|
-
case "read":
|
|
1769
|
-
return { type: "file:read", path: toolInput.path ?? "", timestamp, metadata };
|
|
1770
|
-
case "execute_bash":
|
|
1771
|
-
case "shell":
|
|
1772
|
-
return { type: "shell:before", command: toolInput.command ?? "", cwd: process.cwd(), timestamp, metadata };
|
|
1773
|
-
default:
|
|
1774
|
-
return { type: "tool:before", toolName: toolName || "unknown", input: toolInput, timestamp, metadata };
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
|
|
1778
|
-
function resolvePostToolEvent(toolName, toolInput, toolResponse, timestamp, metadata) {
|
|
1779
|
-
switch (toolName) {
|
|
1780
|
-
case "execute_bash":
|
|
1781
|
-
case "shell":
|
|
1782
|
-
return {
|
|
1783
|
-
type: "shell:after",
|
|
1784
|
-
command: toolInput.command ?? "",
|
|
1785
|
-
cwd: process.cwd(),
|
|
1786
|
-
exitCode: toolResponse.exitCode ?? 0,
|
|
1787
|
-
stdout: toolResponse.stdout ?? "",
|
|
1788
|
-
stderr: toolResponse.stderr ?? "",
|
|
1789
|
-
duration: 0,
|
|
1790
|
-
timestamp,
|
|
1791
|
-
metadata,
|
|
1792
|
-
};
|
|
1793
|
-
default:
|
|
1794
|
-
return { type: "tool:after", toolName: toolName || "unknown", input: toolInput, output: toolResponse, duration: 0, timestamp, metadata };
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
run().catch((err) => {
|
|
1799
|
-
console.error("[ai-hooks] Error:", err.message);
|
|
1800
|
-
process.exit(1);
|
|
1801
|
-
});
|
|
1802
|
-
`;
|
|
1803
|
-
}
|
|
1804
|
-
};
|
|
1805
|
-
var adapter8 = new KiroAdapter();
|
|
1806
|
-
registry.register(adapter8);
|
|
1807
|
-
var EVENT_MAP9 = {
|
|
1808
|
-
"session:start": ["session.created"],
|
|
1809
|
-
"session:end": ["session.idle"],
|
|
1810
|
-
"prompt:submit": ["message.updated"],
|
|
1811
|
-
"prompt:response": ["message.part.updated"],
|
|
1812
|
-
"tool:before": ["tool.execute.before"],
|
|
1813
|
-
"tool:after": ["tool.execute.after"],
|
|
1814
|
-
"file:read": ["tool.execute.before"],
|
|
1815
|
-
"file:write": ["tool.execute.before", "file.edited"],
|
|
1816
|
-
"file:edit": ["tool.execute.before", "file.edited"],
|
|
1817
|
-
"file:delete": ["tool.execute.before"],
|
|
1818
|
-
"shell:before": ["tool.execute.before"],
|
|
1819
|
-
"shell:after": ["tool.execute.after"],
|
|
1820
|
-
"mcp:before": ["tool.execute.before"],
|
|
1821
|
-
"mcp:after": ["tool.execute.after"],
|
|
1822
|
-
notification: ["tui.toast.show"]
|
|
1823
|
-
};
|
|
1824
|
-
var REVERSE_MAP9 = {
|
|
1825
|
-
"session.created": ["session:start"],
|
|
1826
|
-
"session.idle": ["session:end"],
|
|
1827
|
-
"message.updated": ["prompt:submit"],
|
|
1828
|
-
"message.part.updated": ["prompt:response"],
|
|
1829
|
-
"tool.execute.before": [
|
|
1830
|
-
"tool:before",
|
|
1831
|
-
"file:read",
|
|
1832
|
-
"file:write",
|
|
1833
|
-
"file:edit",
|
|
1834
|
-
"file:delete",
|
|
1835
|
-
"shell:before",
|
|
1836
|
-
"mcp:before"
|
|
1837
|
-
],
|
|
1838
|
-
"tool.execute.after": ["tool:after", "shell:after", "mcp:after"],
|
|
1839
|
-
"file.edited": ["file:write", "file:edit"],
|
|
1840
|
-
"tui.toast.show": ["notification"]
|
|
1841
|
-
};
|
|
1842
|
-
var OpenCodeAdapter = class extends BaseAdapter {
|
|
1843
|
-
id = "opencode";
|
|
1844
|
-
name = "OpenCode";
|
|
1845
|
-
version = "1.0";
|
|
1846
|
-
capabilities = {
|
|
1847
|
-
beforeHooks: true,
|
|
1848
|
-
afterHooks: true,
|
|
1849
|
-
mcp: true,
|
|
1850
|
-
configFile: true,
|
|
1851
|
-
supportedEvents: [
|
|
1852
|
-
"session:start",
|
|
1853
|
-
"session:end",
|
|
1854
|
-
"prompt:submit",
|
|
1855
|
-
"prompt:response",
|
|
1856
|
-
"tool:before",
|
|
1857
|
-
"tool:after",
|
|
1858
|
-
"file:read",
|
|
1859
|
-
"file:write",
|
|
1860
|
-
"file:edit",
|
|
1861
|
-
"file:delete",
|
|
1862
|
-
"shell:before",
|
|
1863
|
-
"shell:after",
|
|
1864
|
-
"mcp:before",
|
|
1865
|
-
"mcp:after",
|
|
1866
|
-
"notification"
|
|
1867
|
-
],
|
|
1868
|
-
blockableEvents: [
|
|
1869
|
-
"tool:before",
|
|
1870
|
-
"file:read",
|
|
1871
|
-
"file:write",
|
|
1872
|
-
"file:edit",
|
|
1873
|
-
"file:delete",
|
|
1874
|
-
"shell:before",
|
|
1875
|
-
"mcp:before"
|
|
1876
|
-
]
|
|
1877
|
-
};
|
|
1878
|
-
async detect() {
|
|
1879
|
-
const hasCommand = await this.commandExists("opencode");
|
|
1880
|
-
const hasDir = existsSync(resolve(process.cwd(), ".opencode"));
|
|
1881
|
-
return hasCommand || hasDir;
|
|
1882
|
-
}
|
|
1883
|
-
async generate(hooks) {
|
|
1884
|
-
const configs = [];
|
|
1885
|
-
const neededEvents = /* @__PURE__ */ new Set();
|
|
1886
|
-
for (const hook of hooks) {
|
|
1887
|
-
for (const event of hook.events) {
|
|
1888
|
-
const nativeEvents = this.mapEvent(event);
|
|
1889
|
-
for (const ne of nativeEvents) {
|
|
1890
|
-
neededEvents.add(ne);
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
}
|
|
1894
|
-
configs.push({
|
|
1895
|
-
path: ".opencode/plugins/ai-hooks-plugin.js",
|
|
1896
|
-
content: this.generatePlugin(neededEvents),
|
|
1897
|
-
format: "js"
|
|
1898
|
-
});
|
|
1899
|
-
configs.push({
|
|
1900
|
-
path: ".opencode/plugins/package.json",
|
|
1901
|
-
content: JSON.stringify(
|
|
1902
|
-
{
|
|
1903
|
-
name: "ai-hooks-opencode-plugin",
|
|
1904
|
-
version: "1.0.0",
|
|
1905
|
-
type: "module",
|
|
1906
|
-
main: "ai-hooks-plugin.js",
|
|
1907
|
-
dependencies: {
|
|
1908
|
-
"@premierstudio/ai-hooks": "*"
|
|
1909
|
-
}
|
|
1910
|
-
},
|
|
1911
|
-
null,
|
|
1912
|
-
2
|
|
1913
|
-
) + "\n",
|
|
1914
|
-
format: "json"
|
|
1915
|
-
});
|
|
1916
|
-
return configs;
|
|
1917
|
-
}
|
|
1918
|
-
mapEvent(event) {
|
|
1919
|
-
return EVENT_MAP9[event] ?? [];
|
|
1920
|
-
}
|
|
1921
|
-
mapNativeEvent(nativeEvent) {
|
|
1922
|
-
return REVERSE_MAP9[nativeEvent] ?? [];
|
|
1923
|
-
}
|
|
1924
|
-
async uninstall() {
|
|
1925
|
-
await this.removeFile(".opencode/plugins/ai-hooks-plugin.js");
|
|
1926
|
-
await this.removeFile(".opencode/plugins/package.json");
|
|
1927
|
-
}
|
|
1928
|
-
generatePlugin(neededEvents) {
|
|
1929
|
-
const hookEntries = [...neededEvents].map((event) => {
|
|
1930
|
-
return ` "${event}": async (input, output) => {
|
|
1931
|
-
await handleHook("${event}", input, output);
|
|
1932
|
-
}`;
|
|
1933
|
-
}).join(",\n");
|
|
1934
|
-
return `/**
|
|
1935
|
-
* ai-hooks plugin for OpenCode.
|
|
1936
|
-
* Generated by: ai-hooks generate
|
|
1937
|
-
*
|
|
1938
|
-
* This plugin hooks into OpenCode's lifecycle events and delegates
|
|
1939
|
-
* to the ai-hooks engine for unified hook management.
|
|
1940
|
-
*
|
|
1941
|
-
* DO NOT EDIT - regenerate with: ai-hooks generate
|
|
1942
|
-
*/
|
|
1943
|
-
import { loadConfig, HookEngine } from "@premierstudio/ai-hooks";
|
|
1944
|
-
|
|
1945
|
-
let engine;
|
|
1946
|
-
|
|
1947
|
-
async function getEngine() {
|
|
1948
|
-
if (!engine) {
|
|
1949
|
-
const config = await loadConfig();
|
|
1950
|
-
engine = new HookEngine(config);
|
|
1951
|
-
}
|
|
1952
|
-
return engine;
|
|
1953
|
-
}
|
|
1954
|
-
|
|
1955
|
-
async function handleHook(hookName, input, output) {
|
|
1956
|
-
const eng = await getEngine();
|
|
1957
|
-
const toolInfo = { name: "opencode", version: "1.0" };
|
|
1958
|
-
const timestamp = Date.now();
|
|
1959
|
-
const metadata = {};
|
|
1960
|
-
|
|
1961
|
-
let event;
|
|
1962
|
-
switch (hookName) {
|
|
1963
|
-
case "session.created":
|
|
1964
|
-
event = { type: "session:start", tool: "opencode", version: "1.0", workingDirectory: process.cwd(), timestamp, metadata };
|
|
1965
|
-
break;
|
|
1966
|
-
case "session.idle":
|
|
1967
|
-
event = { type: "session:end", tool: "opencode", duration: 0, timestamp, metadata };
|
|
1968
|
-
break;
|
|
1969
|
-
case "tool.execute.before":
|
|
1970
|
-
event = resolveToolBefore(input, timestamp, metadata);
|
|
1971
|
-
break;
|
|
1972
|
-
case "tool.execute.after":
|
|
1973
|
-
event = resolveToolAfter(input, timestamp, metadata);
|
|
1974
|
-
break;
|
|
1975
|
-
case "file.edited":
|
|
1976
|
-
event = { type: "file:edit", path: input.path ?? "", oldContent: "", newContent: "", timestamp, metadata };
|
|
1977
|
-
break;
|
|
1978
|
-
case "message.updated":
|
|
1979
|
-
event = { type: "prompt:submit", prompt: input.content ?? "", timestamp, metadata };
|
|
1980
|
-
break;
|
|
1981
|
-
case "message.part.updated":
|
|
1982
|
-
event = { type: "prompt:response", response: input.content ?? "", model: "unknown", tokens: { input: 0, output: 0 }, timestamp, metadata };
|
|
1983
|
-
break;
|
|
1984
|
-
case "tui.toast.show":
|
|
1985
|
-
event = { type: "notification", level: "info", message: input.message ?? "", timestamp, metadata };
|
|
1986
|
-
break;
|
|
1987
|
-
default:
|
|
1988
|
-
return output;
|
|
1989
|
-
}
|
|
1990
|
-
|
|
1991
|
-
const results = await eng.emit(event, toolInfo);
|
|
1992
|
-
const blocked = results.find((r) => r.blocked);
|
|
1993
|
-
|
|
1994
|
-
if (blocked && hookName === "tool.execute.before") {
|
|
1995
|
-
return { ...output, blocked: true, reason: blocked.reason ?? "Blocked by ai-hooks" };
|
|
1996
|
-
}
|
|
1997
|
-
|
|
1998
|
-
return output;
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
function resolveToolBefore(input, timestamp, metadata) {
|
|
2002
|
-
const toolName = input.tool ?? input.name ?? "unknown";
|
|
2003
|
-
const toolInput = input.input ?? input.args ?? {};
|
|
2004
|
-
|
|
2005
|
-
switch (toolName) {
|
|
2006
|
-
case "file_write":
|
|
2007
|
-
case "write":
|
|
2008
|
-
return { type: "file:write", path: toolInput.path ?? "", content: toolInput.content ?? "", timestamp, metadata };
|
|
2009
|
-
case "file_edit":
|
|
2010
|
-
case "edit":
|
|
2011
|
-
return { type: "file:edit", path: toolInput.path ?? "", oldContent: toolInput.old ?? "", newContent: toolInput.new ?? "", timestamp, metadata };
|
|
2012
|
-
case "file_read":
|
|
2013
|
-
case "read":
|
|
2014
|
-
return { type: "file:read", path: toolInput.path ?? "", timestamp, metadata };
|
|
2015
|
-
case "bash":
|
|
2016
|
-
case "shell":
|
|
2017
|
-
return { type: "shell:before", command: toolInput.command ?? "", cwd: process.cwd(), timestamp, metadata };
|
|
2018
|
-
default:
|
|
2019
|
-
return { type: "tool:before", toolName, input: toolInput, timestamp, metadata };
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
2022
|
-
|
|
2023
|
-
function resolveToolAfter(input, timestamp, metadata) {
|
|
2024
|
-
const toolName = input.tool ?? input.name ?? "unknown";
|
|
2025
|
-
const toolInput = input.input ?? input.args ?? {};
|
|
2026
|
-
const toolOutput = input.output ?? input.result ?? {};
|
|
2027
|
-
|
|
2028
|
-
switch (toolName) {
|
|
2029
|
-
case "bash":
|
|
2030
|
-
case "shell":
|
|
2031
|
-
return {
|
|
2032
|
-
type: "shell:after",
|
|
2033
|
-
command: toolInput.command ?? "",
|
|
2034
|
-
cwd: process.cwd(),
|
|
2035
|
-
exitCode: toolOutput.exitCode ?? 0,
|
|
2036
|
-
stdout: toolOutput.stdout ?? "",
|
|
2037
|
-
stderr: toolOutput.stderr ?? "",
|
|
2038
|
-
duration: 0,
|
|
2039
|
-
timestamp,
|
|
2040
|
-
metadata,
|
|
2041
|
-
};
|
|
2042
|
-
default:
|
|
2043
|
-
return { type: "tool:after", toolName, input: toolInput, output: toolOutput, duration: 0, timestamp, metadata };
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
export const AiHooksPlugin = async ({ project, directory }) => {
|
|
2048
|
-
return {
|
|
2049
|
-
${hookEntries}
|
|
2050
|
-
};
|
|
2051
|
-
};
|
|
2052
|
-
`;
|
|
2053
|
-
}
|
|
2054
|
-
};
|
|
2055
|
-
var adapter9 = new OpenCodeAdapter();
|
|
2056
|
-
registry.register(adapter9);
|
|
1
|
+
import '../chunk-ZOWUSGNF.js';
|
|
2
|
+
import '../chunk-FHFRGMW6.js';
|
|
2057
3
|
//# sourceMappingURL=all.js.map
|
|
2058
4
|
//# sourceMappingURL=all.js.map
|