@premierstudio/ai-hooks 1.0.4

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/index.js ADDED
@@ -0,0 +1,530 @@
1
+ import { pathToFileURL } from 'url';
2
+ import { existsSync } from 'fs';
3
+ import { resolve, dirname } from 'path';
4
+ import { mkdir, writeFile, readFile, rm } from 'fs/promises';
5
+
6
+ // src/config/define.ts
7
+ function defineConfig(config) {
8
+ return config;
9
+ }
10
+ function hook(phase, events, handler) {
11
+ return new HookBuilderChain(phase, events, handler);
12
+ }
13
+ var HookBuilderChain = class {
14
+ constructor(phase, events, handler) {
15
+ this.phase = phase;
16
+ this.events = events;
17
+ this.handler = handler;
18
+ this._id = `hook-${events.join("-")}-${Date.now()}`;
19
+ this._name = `Hook for ${events.join(", ")}`;
20
+ }
21
+ _id;
22
+ _name;
23
+ _description;
24
+ _priority;
25
+ _filter;
26
+ _enabled;
27
+ id(id) {
28
+ this._id = id;
29
+ return this;
30
+ }
31
+ name(name) {
32
+ this._name = name;
33
+ return this;
34
+ }
35
+ description(desc) {
36
+ this._description = desc;
37
+ return this;
38
+ }
39
+ priority(p) {
40
+ this._priority = p;
41
+ return this;
42
+ }
43
+ filter(fn) {
44
+ this._filter = fn;
45
+ return this;
46
+ }
47
+ enabled(e) {
48
+ this._enabled = e;
49
+ return this;
50
+ }
51
+ build() {
52
+ return {
53
+ id: this._id,
54
+ name: this._name,
55
+ description: this._description,
56
+ events: this.events,
57
+ handler: this.handler,
58
+ phase: this.phase,
59
+ priority: this._priority,
60
+ filter: this._filter,
61
+ enabled: this._enabled
62
+ };
63
+ }
64
+ };
65
+ var CONFIG_FILENAMES = [
66
+ "ai-hooks.config.ts",
67
+ "ai-hooks.config.js",
68
+ "ai-hooks.config.mjs",
69
+ "ai-hooks.config.mts"
70
+ ];
71
+ function findConfigFile(cwd = process.cwd()) {
72
+ for (const name of CONFIG_FILENAMES) {
73
+ const fullPath = resolve(cwd, name);
74
+ if (existsSync(fullPath)) {
75
+ return fullPath;
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+ async function loadConfig(configPath, cwd) {
81
+ const resolvedPath = configPath ?? findConfigFile(cwd);
82
+ if (!resolvedPath) {
83
+ throw new ConfigNotFoundError(cwd ?? process.cwd());
84
+ }
85
+ if (!existsSync(resolvedPath)) {
86
+ throw new ConfigNotFoundError(resolvedPath);
87
+ }
88
+ const fileUrl = pathToFileURL(resolve(resolvedPath)).href;
89
+ const mod = await import(fileUrl);
90
+ const config = mod.default ?? mod;
91
+ if (!config.hooks || !Array.isArray(config.hooks)) {
92
+ throw new ConfigValidationError(
93
+ "Config must have a `hooks` array. Did you forget to use `defineConfig()`?"
94
+ );
95
+ }
96
+ if (config.extends && config.extends.length > 0) {
97
+ const mergedHooks = [...config.extends.flatMap((preset) => preset.hooks), ...config.hooks];
98
+ return { ...config, hooks: mergedHooks, extends: void 0 };
99
+ }
100
+ return config;
101
+ }
102
+ var ConfigNotFoundError = class extends Error {
103
+ constructor(searchPath) {
104
+ super(
105
+ `No ai-hooks config found. Searched in: ${searchPath}
106
+ Create an ai-hooks.config.ts file or run: ai-hooks init`
107
+ );
108
+ this.name = "ConfigNotFoundError";
109
+ }
110
+ };
111
+ var ConfigValidationError = class extends Error {
112
+ constructor(message) {
113
+ super(message);
114
+ this.name = "ConfigValidationError";
115
+ }
116
+ };
117
+
118
+ // src/types/hooks.ts
119
+ function isBeforeEvent(event) {
120
+ return event.type === "session:start" || event.type === "prompt:submit" || event.type === "tool:before" || event.type === "file:write" || event.type === "file:edit" || event.type === "file:delete" || event.type === "shell:before" || event.type === "mcp:before";
121
+ }
122
+
123
+ // src/runtime/chain.ts
124
+ async function executeChain(hooks, ctx, timeout) {
125
+ const sorted = [...hooks].toSorted((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
126
+ let index = 0;
127
+ const next = async () => {
128
+ if (index >= sorted.length) return;
129
+ const hook2 = sorted[index];
130
+ if (!hook2) return;
131
+ index++;
132
+ if (hook2.enabled === false) {
133
+ await next();
134
+ return;
135
+ }
136
+ if (hook2.filter && !hook2.filter(ctx.event)) {
137
+ await next();
138
+ return;
139
+ }
140
+ const blocked = ctx.results.some((r) => r.blocked);
141
+ if (blocked && hook2.phase === "before") {
142
+ return;
143
+ }
144
+ await Promise.race([
145
+ Promise.resolve(hook2.handler(ctx, next)),
146
+ new Promise(
147
+ (_, reject) => setTimeout(() => reject(new HookTimeoutError(hook2.id, timeout)), timeout)
148
+ )
149
+ ]);
150
+ };
151
+ try {
152
+ await next();
153
+ } catch (error) {
154
+ if (error instanceof HookTimeoutError) {
155
+ ctx.results.push({
156
+ blocked: false,
157
+ reason: `Hook "${error.hookId}" timed out after ${error.timeout}ms`
158
+ });
159
+ } else {
160
+ throw error;
161
+ }
162
+ }
163
+ return ctx.results;
164
+ }
165
+ var HookTimeoutError = class extends Error {
166
+ constructor(hookId, timeout) {
167
+ super(`Hook "${hookId}" timed out after ${timeout}ms`);
168
+ this.hookId = hookId;
169
+ this.timeout = timeout;
170
+ this.name = "HookTimeoutError";
171
+ }
172
+ };
173
+
174
+ // src/runtime/engine.ts
175
+ var DEFAULT_SETTINGS = {
176
+ cwd: process.cwd(),
177
+ logLevel: "warn",
178
+ hookTimeout: 5e3,
179
+ failMode: "open",
180
+ telemetry: false
181
+ };
182
+ var HookEngine = class {
183
+ hooks = /* @__PURE__ */ new Map();
184
+ settings;
185
+ constructor(config) {
186
+ this.settings = { ...DEFAULT_SETTINGS, ...config?.settings };
187
+ if (config) {
188
+ if (config.extends) {
189
+ for (const preset of config.extends) {
190
+ this.registerAll(preset.hooks);
191
+ }
192
+ }
193
+ this.registerAll(config.hooks);
194
+ }
195
+ }
196
+ /**
197
+ * Register a single hook definition.
198
+ */
199
+ register(hook2) {
200
+ for (const event of hook2.events) {
201
+ const existing = this.hooks.get(event) ?? [];
202
+ existing.push(hook2);
203
+ this.hooks.set(event, existing);
204
+ }
205
+ }
206
+ /**
207
+ * Register multiple hook definitions.
208
+ */
209
+ registerAll(hooks) {
210
+ for (const hook2 of hooks) {
211
+ this.register(hook2);
212
+ }
213
+ }
214
+ /**
215
+ * Unregister a hook by ID.
216
+ */
217
+ unregister(hookId) {
218
+ for (const [event, hooks] of this.hooks) {
219
+ const filtered = hooks.filter((h) => h.id !== hookId);
220
+ if (filtered.length === 0) {
221
+ this.hooks.delete(event);
222
+ } else {
223
+ this.hooks.set(event, filtered);
224
+ }
225
+ }
226
+ }
227
+ /**
228
+ * Emit an event and run the matching hook chain.
229
+ *
230
+ * For "before" events: returns results that may include blocks.
231
+ * For "after" events: returns observation results (no blocking).
232
+ */
233
+ async emit(event, toolInfo) {
234
+ const eventType = event.type;
235
+ const phase = isBeforeEvent(event) ? "before" : "after";
236
+ const allHooks = this.hooks.get(eventType) ?? [];
237
+ const phaseHooks = allHooks.filter((h) => h.phase === phase);
238
+ if (phaseHooks.length === 0) {
239
+ return [];
240
+ }
241
+ const ctx = {
242
+ event,
243
+ tool: toolInfo,
244
+ cwd: this.settings.cwd,
245
+ state: /* @__PURE__ */ new Map(),
246
+ results: [],
247
+ startedAt: Date.now()
248
+ };
249
+ try {
250
+ return await executeChain(phaseHooks, ctx, this.settings.hookTimeout);
251
+ } catch (error) {
252
+ if (this.settings.failMode === "open") {
253
+ this.log("error", `Hook chain error (fail-open): ${error}`);
254
+ return [];
255
+ }
256
+ return [
257
+ {
258
+ blocked: true,
259
+ reason: `Hook chain error (fail-closed): ${error}`
260
+ }
261
+ ];
262
+ }
263
+ }
264
+ /**
265
+ * Check if an event is blocked by running before hooks.
266
+ * Convenience wrapper around emit().
267
+ */
268
+ async isBlocked(event, toolInfo) {
269
+ if (!isBeforeEvent(event)) {
270
+ return { blocked: false };
271
+ }
272
+ const results = await this.emit(event, toolInfo);
273
+ const blockResult = results.find((r) => r.blocked);
274
+ return blockResult ? { blocked: true, reason: blockResult.reason } : { blocked: false };
275
+ }
276
+ /**
277
+ * Get all registered hooks, optionally filtered by event type.
278
+ */
279
+ getHooks(eventType) {
280
+ if (eventType) {
281
+ return this.hooks.get(eventType) ?? [];
282
+ }
283
+ const all = [];
284
+ const seen = /* @__PURE__ */ new Set();
285
+ for (const hooks of this.hooks.values()) {
286
+ for (const hook2 of hooks) {
287
+ if (!seen.has(hook2.id)) {
288
+ seen.add(hook2.id);
289
+ all.push(hook2);
290
+ }
291
+ }
292
+ }
293
+ return all;
294
+ }
295
+ /**
296
+ * Get current engine settings.
297
+ */
298
+ getSettings() {
299
+ return { ...this.settings };
300
+ }
301
+ log(level, message) {
302
+ const levels = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
303
+ const threshold = levels[this.settings.logLevel];
304
+ const messageLevel = levels[level];
305
+ if (messageLevel <= threshold) {
306
+ const prefix = `[ai-hooks:${level}]`;
307
+ if (level === "error") {
308
+ console.error(prefix, message);
309
+ } else if (level === "warn") {
310
+ console.warn(prefix, message);
311
+ } else {
312
+ console.log(prefix, message);
313
+ }
314
+ }
315
+ }
316
+ };
317
+
318
+ // src/adapters/registry.ts
319
+ var AdapterRegistry = class {
320
+ adapters = /* @__PURE__ */ new Map();
321
+ factories = /* @__PURE__ */ new Map();
322
+ /**
323
+ * Register an adapter instance.
324
+ */
325
+ register(adapter) {
326
+ this.adapters.set(adapter.id, adapter);
327
+ }
328
+ /**
329
+ * Register an adapter factory for lazy instantiation.
330
+ */
331
+ registerFactory(id, factory) {
332
+ this.factories.set(id, factory);
333
+ }
334
+ /**
335
+ * Get a registered adapter by ID.
336
+ */
337
+ get(id) {
338
+ const existing = this.adapters.get(id);
339
+ if (existing) return existing;
340
+ const factory = this.factories.get(id);
341
+ if (factory) {
342
+ const adapter = factory();
343
+ this.adapters.set(id, adapter);
344
+ return adapter;
345
+ }
346
+ return void 0;
347
+ }
348
+ /**
349
+ * Get all registered adapter IDs.
350
+ */
351
+ list() {
352
+ return [.../* @__PURE__ */ new Set([...this.adapters.keys(), ...this.factories.keys()])];
353
+ }
354
+ /**
355
+ * Detect which tools are available in the current environment.
356
+ * Returns adapters that successfully detect their tool.
357
+ */
358
+ async detectAll() {
359
+ const detected = [];
360
+ for (const id of this.list()) {
361
+ const adapter = this.get(id);
362
+ if (adapter) {
363
+ try {
364
+ const found = await adapter.detect();
365
+ if (found) {
366
+ detected.push(adapter);
367
+ }
368
+ } catch {
369
+ }
370
+ }
371
+ }
372
+ return detected;
373
+ }
374
+ /**
375
+ * Clear the registry. Useful for testing.
376
+ */
377
+ clear() {
378
+ this.adapters.clear();
379
+ this.factories.clear();
380
+ }
381
+ };
382
+ var registry = new AdapterRegistry();
383
+ var BaseAdapter = class {
384
+ /**
385
+ * Default install: write generated configs to disk.
386
+ */
387
+ async install(configs) {
388
+ for (const config of configs) {
389
+ const fullPath = resolve(process.cwd(), config.path);
390
+ await mkdir(dirname(fullPath), { recursive: true });
391
+ await writeFile(fullPath, config.content, "utf-8");
392
+ }
393
+ }
394
+ /**
395
+ * Default uninstall: remove generated config files.
396
+ */
397
+ async uninstall() {
398
+ }
399
+ // ── Utility Methods ───────────────────────────────────────
400
+ async fileExists(path) {
401
+ return existsSync(resolve(process.cwd(), path));
402
+ }
403
+ async readJsonFile(path) {
404
+ const fullPath = resolve(process.cwd(), path);
405
+ if (!existsSync(fullPath)) return null;
406
+ const content = await readFile(fullPath, "utf-8");
407
+ return JSON.parse(content);
408
+ }
409
+ async writeJsonFile(path, data) {
410
+ const fullPath = resolve(process.cwd(), path);
411
+ await mkdir(dirname(fullPath), { recursive: true });
412
+ await writeFile(fullPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
413
+ }
414
+ async removeFile(path) {
415
+ const fullPath = resolve(process.cwd(), path);
416
+ if (existsSync(fullPath)) {
417
+ await rm(fullPath);
418
+ }
419
+ }
420
+ /**
421
+ * Check if a CLI command exists on PATH.
422
+ */
423
+ async commandExists(command) {
424
+ const { exec } = await import('child_process');
425
+ return new Promise((resolve3) => {
426
+ exec(`which ${command}`, (error) => {
427
+ resolve3(!error);
428
+ });
429
+ });
430
+ }
431
+ };
432
+
433
+ // src/hooks/builtin.ts
434
+ var blockDangerousCommands = hook("before", ["shell:before"], async (ctx, next) => {
435
+ const command = ctx.event.command;
436
+ const dangerous = DANGEROUS_PATTERNS.find((p) => p.pattern.test(command));
437
+ if (dangerous) {
438
+ ctx.results.push({
439
+ blocked: true,
440
+ reason: `Blocked dangerous command: ${dangerous.description}`
441
+ });
442
+ return;
443
+ }
444
+ await next();
445
+ }).id("ai-hooks:block-dangerous-commands").name("Block Dangerous Commands").description("Prevents destructive shell commands like rm -rf /, drop database, etc.").priority(1).build();
446
+ var DANGEROUS_PATTERNS = [
447
+ { pattern: /rm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+)?\/\s*$/, description: "rm -rf /" },
448
+ { pattern: /rm\s+-[a-zA-Z]*f[a-zA-Z]*\s+~\/?\s*$/, description: "rm -rf ~" },
449
+ { pattern: /mkfs\./, description: "filesystem format" },
450
+ { pattern: /dd\s+.*of=\/dev\/[sh]d/, description: "disk overwrite" },
451
+ { pattern: /:\(\)\s*\{\s*:\|:&\s*\}\s*;:/, description: "fork bomb" },
452
+ { pattern: />\s*\/dev\/[sh]d/, description: "device overwrite" },
453
+ { pattern: /chmod\s+(-R\s+)?777\s+\//, description: "chmod 777 /" },
454
+ { pattern: /DROP\s+DATABASE/i, description: "DROP DATABASE" },
455
+ { pattern: /DROP\s+TABLE/i, description: "DROP TABLE" },
456
+ { pattern: /TRUNCATE\s+TABLE/i, description: "TRUNCATE TABLE" }
457
+ ];
458
+ var scanSecrets = hook("before", ["file:write", "file:edit"], async (ctx, next) => {
459
+ const content = ctx.event.type === "file:write" ? ctx.event.content : ctx.event.newContent;
460
+ const found = SECRET_PATTERNS.find((p) => p.pattern.test(content));
461
+ if (found) {
462
+ ctx.results.push({
463
+ blocked: true,
464
+ reason: `Potential secret detected: ${found.description}. Use environment variables instead.`
465
+ });
466
+ return;
467
+ }
468
+ await next();
469
+ }).id("ai-hooks:scan-secrets").name("Scan for Secrets").description("Prevents hardcoded API keys, tokens, and credentials in file writes.").priority(2).build();
470
+ var SECRET_PATTERNS = [
471
+ { pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"][a-zA-Z0-9]{20,}['"]/i, description: "API key" },
472
+ {
473
+ pattern: /(?:secret|token|password|passwd|pwd)\s*[:=]\s*['"][^'"]{8,}['"]/i,
474
+ description: "Secret/token/password"
475
+ },
476
+ { pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/, description: "Private key" },
477
+ { pattern: /ghp_[a-zA-Z0-9]{36}/, description: "GitHub personal access token" },
478
+ { pattern: /sk-[a-zA-Z0-9]{20,}/, description: "OpenAI/Stripe secret key" },
479
+ { pattern: /AKIA[0-9A-Z]{16}/, description: "AWS access key ID" },
480
+ { pattern: /xox[bpors]-[a-zA-Z0-9-]{10,}/, description: "Slack token" }
481
+ ];
482
+ var protectGitignored = hook("before", ["file:write"], async (ctx, next) => {
483
+ const path = ctx.event.path;
484
+ const sensitive = SENSITIVE_FILES.some((f) => path.endsWith(f));
485
+ if (sensitive) {
486
+ ctx.results.push({
487
+ blocked: true,
488
+ reason: `Cannot write to sensitive file: ${path}. This file should be managed manually.`
489
+ });
490
+ return;
491
+ }
492
+ await next();
493
+ }).id("ai-hooks:protect-sensitive-files").name("Protect Sensitive Files").description("Prevents AI tools from overwriting .env, credentials, and other sensitive files.").priority(3).build();
494
+ var SENSITIVE_FILES = [
495
+ ".env",
496
+ ".env.local",
497
+ ".env.production",
498
+ "credentials.json",
499
+ "service-account.json",
500
+ "id_rsa",
501
+ "id_ed25519",
502
+ ".npmrc",
503
+ ".pypirc"
504
+ ];
505
+ var auditShellCommands = hook("after", ["shell:after"], async (ctx, next) => {
506
+ const { command, exitCode, duration } = ctx.event;
507
+ ctx.results.push({
508
+ data: {
509
+ audit: {
510
+ type: "shell",
511
+ command,
512
+ exitCode,
513
+ duration,
514
+ timestamp: ctx.event.timestamp,
515
+ tool: ctx.tool.name
516
+ }
517
+ }
518
+ });
519
+ await next();
520
+ }).id("ai-hooks:audit-shell").name("Audit Shell Commands").description("Records all shell command executions for audit trail.").priority(999).build();
521
+ var builtinHooks = [
522
+ blockDangerousCommands,
523
+ scanSecrets,
524
+ protectGitignored,
525
+ auditShellCommands
526
+ ];
527
+
528
+ export { BaseAdapter, HookEngine, HookTimeoutError, auditShellCommands, blockDangerousCommands, builtinHooks, defineConfig, executeChain, findConfigFile, hook, isBeforeEvent, loadConfig, protectGitignored, registry, scanSecrets };
529
+ //# sourceMappingURL=index.js.map
530
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/define.ts","../src/config/loader.ts","../src/types/hooks.ts","../src/runtime/chain.ts","../src/runtime/engine.ts","../src/adapters/registry.ts","../src/adapters/base.ts","../src/hooks/builtin.ts"],"names":["hook","resolve","existsSync"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n AiHooksConfig,\n HookDefinition,\n HookEventType,\n HookContext,\n EventOf,\n} from \"../types/index.js\";\n\n/**\n * Define an ai-hooks configuration.\n * Use this as the default export of your `ai-hooks.config.ts`.\n *\n * @example\n * ```ts\n * import { defineConfig, hook } from \"@premierstudio/ai-hooks\";\n *\n * export default defineConfig({\n * hooks: [\n * hook(\"before\", [\"shell:before\"], async (ctx, next) => {\n * if (ctx.event.command.includes(\"rm -rf /\")) {\n * ctx.results.push({ blocked: true, reason: \"Dangerous command\" });\n * return;\n * }\n * await next();\n * }).id(\"block-dangerous\").name(\"Block Dangerous Commands\").build(),\n * ],\n * });\n * ```\n */\nexport function defineConfig(config: AiHooksConfig): AiHooksConfig {\n return config;\n}\n\n/**\n * Fluent builder for creating hook definitions.\n * The generic parameter provides type-safe access to event properties\n * inside the handler, while the output is a non-generic HookDefinition\n * for collection compatibility.\n *\n * @example\n * ```ts\n * hook(\"before\", [\"file:write\", \"file:edit\"], async (ctx, next) => {\n * // validate file changes\n * await next();\n * })\n * .id(\"validate-writes\")\n * .name(\"Validate File Writes\")\n * .priority(10)\n * .build()\n * ```\n */\nexport function hook<T extends HookEventType>(\n phase: \"before\",\n events: T[],\n handler: (ctx: HookContext<T>, next: () => Promise<void>) => Promise<void> | void,\n): HookBuilderChain<T>;\nexport function hook<T extends HookEventType>(\n phase: \"after\",\n events: T[],\n handler: (ctx: HookContext<T>, next: () => Promise<void>) => Promise<void> | void,\n): HookBuilderChain<T>;\nexport function hook<T extends HookEventType>(\n phase: \"before\" | \"after\",\n events: T[],\n handler: (ctx: HookContext<T>, next: () => Promise<void>) => Promise<void> | void,\n): HookBuilderChain<T> {\n return new HookBuilderChain(phase, events, handler);\n}\n\nclass HookBuilderChain<T extends HookEventType> {\n private _id: string;\n private _name: string;\n private _description?: string;\n private _priority?: number;\n private _filter?: (event: EventOf<T>) => boolean;\n private _enabled?: boolean;\n\n constructor(\n private phase: \"before\" | \"after\",\n private events: T[],\n private handler: (ctx: HookContext<T>, next: () => Promise<void>) => Promise<void> | void,\n ) {\n this._id = `hook-${events.join(\"-\")}-${Date.now()}`;\n this._name = `Hook for ${events.join(\", \")}`;\n }\n\n id(id: string): this {\n this._id = id;\n return this;\n }\n\n name(name: string): this {\n this._name = name;\n return this;\n }\n\n description(desc: string): this {\n this._description = desc;\n return this;\n }\n\n priority(p: number): this {\n this._priority = p;\n return this;\n }\n\n filter(fn: (event: EventOf<T>) => boolean): this {\n this._filter = fn;\n return this;\n }\n\n enabled(e: boolean): this {\n this._enabled = e;\n return this;\n }\n\n build(): HookDefinition {\n // The handler/filter are widened from their narrow generic types to\n // the base HookContext/HookEvent types. This is runtime-safe because\n // the handler only accesses properties of the specific event type\n // it was designed for, which are always present on the actual event.\n return {\n id: this._id,\n name: this._name,\n description: this._description,\n events: this.events,\n handler: this.handler as unknown as HookDefinition[\"handler\"],\n phase: this.phase,\n priority: this._priority,\n filter: this._filter as unknown as HookDefinition[\"filter\"],\n enabled: this._enabled,\n };\n }\n}\n","import { pathToFileURL } from \"node:url\";\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { AiHooksConfig } from \"../types/index.js\";\n\nconst CONFIG_FILENAMES = [\n \"ai-hooks.config.ts\",\n \"ai-hooks.config.js\",\n \"ai-hooks.config.mjs\",\n \"ai-hooks.config.mts\",\n];\n\n/**\n * Find the ai-hooks config file in the given directory.\n */\nexport function findConfigFile(cwd: string = process.cwd()): string | null {\n for (const name of CONFIG_FILENAMES) {\n const fullPath = resolve(cwd, name);\n if (existsSync(fullPath)) {\n return fullPath;\n }\n }\n return null;\n}\n\n/**\n * Load and resolve an ai-hooks config from a file path.\n *\n * Supports both .ts and .js files. For TypeScript files,\n * requires a runtime that supports TS (Node 22+ with --experimental-strip-types,\n * tsx, or bun).\n */\nexport async function loadConfig(configPath?: string, cwd?: string): Promise<AiHooksConfig> {\n const resolvedPath = configPath ?? findConfigFile(cwd);\n\n if (!resolvedPath) {\n throw new ConfigNotFoundError(cwd ?? process.cwd());\n }\n\n if (!existsSync(resolvedPath)) {\n throw new ConfigNotFoundError(resolvedPath);\n }\n\n const fileUrl = pathToFileURL(resolve(resolvedPath)).href;\n const mod = await import(fileUrl);\n\n const config: AiHooksConfig = mod.default ?? mod;\n\n if (!config.hooks || !Array.isArray(config.hooks)) {\n throw new ConfigValidationError(\n \"Config must have a `hooks` array. Did you forget to use `defineConfig()`?\",\n );\n }\n\n // Resolve extends (recursively merge presets)\n if (config.extends && config.extends.length > 0) {\n const mergedHooks = [...config.extends.flatMap((preset) => preset.hooks), ...config.hooks];\n return { ...config, hooks: mergedHooks, extends: undefined };\n }\n\n return config;\n}\n\nexport class ConfigNotFoundError extends Error {\n constructor(searchPath: string) {\n super(\n `No ai-hooks config found. Searched in: ${searchPath}\\n` +\n `Create an ai-hooks.config.ts file or run: ai-hooks init`,\n );\n this.name = \"ConfigNotFoundError\";\n }\n}\n\nexport class ConfigValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ConfigValidationError\";\n }\n}\n","import type { HookEvent, HookEventType, EventOf, BeforeEvent } from \"./events.js\";\n\n// Re-export for direct import paths\nexport type { HookEventType } from \"./events.js\";\n\n/**\n * Result of a \"before\" hook. Controls whether the event proceeds.\n */\nexport type HookResult = {\n /** If true, the event is blocked and won't proceed to the tool. */\n blocked?: boolean;\n /** Reason for blocking (shown to the user/tool). */\n reason?: string;\n /** Modified event data to pass forward (mutation). */\n mutated?: Partial<HookEvent>;\n /** Arbitrary data to attach to the hook context. */\n data?: Record<string, unknown>;\n};\n\n/**\n * The context object passed to every hook function.\n * Inspired by Express.js req/res pattern but adapted for AI tools.\n */\nexport type HookContext<T extends HookEventType = HookEventType> = {\n /** The event that triggered this hook. */\n event: EventOf<T>;\n /** The AI tool that emitted this event. */\n tool: {\n name: string;\n version: string;\n };\n /** Working directory where the tool is running. */\n cwd: string;\n /** Shared state bag for passing data between hooks in a chain. */\n state: Map<string, unknown>;\n /** Accumulated results from previous hooks in the chain. */\n results: HookResult[];\n /** Timestamp of when the hook chain started. */\n startedAt: number;\n};\n\n/**\n * A \"before\" hook function. Runs before the event is processed.\n * Can block, mutate, or pass through.\n */\nexport type BeforeHookFn<T extends HookEventType = HookEventType> = (\n ctx: HookContext<T>,\n next: () => Promise<void>,\n) => Promise<void> | void;\n\n/**\n * An \"after\" hook function. Runs after the event is processed.\n * Cannot block (event already happened), but can observe and react.\n */\nexport type AfterHookFn<T extends HookEventType = HookEventType> = (\n ctx: HookContext<T>,\n next: () => Promise<void>,\n) => Promise<void> | void;\n\n/**\n * A hook definition with metadata.\n *\n * The generic parameter provides type safety when creating hooks\n * for specific events. For collections/storage, use the non-generic\n * default which accepts any event type.\n */\nexport type HookDefinition = {\n /** Unique identifier for this hook. */\n id: string;\n /** Human-readable name. */\n name: string;\n /** Description of what this hook does. */\n description?: string;\n /** Which event types this hook listens to. */\n events: HookEventType[];\n /** The hook function. Takes the widest context; narrowing happens at creation time via hook(). */\n handler: (ctx: HookContext, next: () => Promise<void>) => Promise<void> | void;\n /** Priority (lower = runs first). Default: 100. */\n priority?: number;\n /** Whether this hook runs \"before\" or \"after\" the event. */\n phase: \"before\" | \"after\";\n /** Optional filter to narrow when this hook runs. */\n filter?: (event: HookEvent) => boolean;\n /** Whether this hook is enabled. Default: true. */\n enabled?: boolean;\n};\n\n/**\n * Type guard: is this a \"before\" event type?\n */\nexport function isBeforeEvent(event: HookEvent): event is BeforeEvent {\n return (\n event.type === \"session:start\" ||\n event.type === \"prompt:submit\" ||\n event.type === \"tool:before\" ||\n event.type === \"file:write\" ||\n event.type === \"file:edit\" ||\n event.type === \"file:delete\" ||\n event.type === \"shell:before\" ||\n event.type === \"mcp:before\"\n );\n}\n","import type { HookDefinition, HookContext, HookResult } from \"../types/index.js\";\n\n/**\n * Execute a chain of hooks in priority order with Express.js-style next() flow.\n *\n * Each hook calls `next()` to pass control to the next hook in the chain.\n * If a hook doesn't call `next()`, the chain stops (short-circuit).\n * Before hooks can block events by setting `ctx.results` with `blocked: true`.\n */\nexport async function executeChain(\n hooks: HookDefinition[],\n ctx: HookContext,\n timeout: number,\n): Promise<HookResult[]> {\n const sorted = [...hooks].toSorted((a, b) => (a.priority ?? 100) - (b.priority ?? 100));\n\n let index = 0;\n\n const next = async (): Promise<void> => {\n if (index >= sorted.length) return;\n\n const hook = sorted[index];\n /* v8 ignore next -- unreachable: guarded by index >= sorted.length above */\n if (!hook) return;\n index++;\n\n // Skip disabled hooks\n if (hook.enabled === false) {\n await next();\n return;\n }\n\n // Skip if filter doesn't match\n if (hook.filter && !hook.filter(ctx.event)) {\n await next();\n return;\n }\n\n // Check if a previous hook already blocked\n const blocked = ctx.results.some((r) => r.blocked);\n if (blocked && hook.phase === \"before\") {\n return; // Stop chain on block\n }\n\n await Promise.race([\n Promise.resolve(hook.handler(ctx, next)),\n new Promise<never>((_, reject) =>\n setTimeout(() => reject(new HookTimeoutError(hook.id, timeout)), timeout),\n ),\n ]);\n };\n\n try {\n await next();\n } catch (error) {\n if (error instanceof HookTimeoutError) {\n ctx.results.push({\n blocked: false,\n reason: `Hook \"${error.hookId}\" timed out after ${error.timeout}ms`,\n });\n } else {\n throw error;\n }\n }\n\n return ctx.results;\n}\n\nexport class HookTimeoutError extends Error {\n constructor(\n public readonly hookId: string,\n public readonly timeout: number,\n ) {\n super(`Hook \"${hookId}\" timed out after ${timeout}ms`);\n this.name = \"HookTimeoutError\";\n }\n}\n","import type {\n HookDefinition,\n HookContext,\n HookResult,\n HookEvent,\n HookEventType,\n AiHooksConfig,\n ConfigSettings,\n} from \"../types/index.js\";\nimport { isBeforeEvent } from \"../types/index.js\";\nimport { executeChain } from \"./chain.js\";\n\nconst DEFAULT_SETTINGS: Required<ConfigSettings> = {\n cwd: process.cwd(),\n logLevel: \"warn\",\n hookTimeout: 5000,\n failMode: \"open\",\n telemetry: false,\n};\n\n/**\n * The ai-hooks runtime engine.\n *\n * Manages hook registration, event dispatch, and chain execution.\n * This is the core orchestrator - adapters feed events into this engine,\n * and it runs the appropriate hook chains.\n */\nexport class HookEngine {\n private hooks: Map<HookEventType, HookDefinition[]> = new Map();\n private settings: Required<ConfigSettings>;\n\n constructor(config?: AiHooksConfig) {\n this.settings = { ...DEFAULT_SETTINGS, ...config?.settings };\n\n if (config) {\n // Apply presets first (in order)\n if (config.extends) {\n for (const preset of config.extends) {\n this.registerAll(preset.hooks);\n }\n }\n // Then apply local hooks (override presets)\n this.registerAll(config.hooks);\n }\n }\n\n /**\n * Register a single hook definition.\n */\n register(hook: HookDefinition): void {\n for (const event of hook.events) {\n const existing = this.hooks.get(event) ?? [];\n existing.push(hook);\n this.hooks.set(event, existing);\n }\n }\n\n /**\n * Register multiple hook definitions.\n */\n registerAll(hooks: HookDefinition[]): void {\n for (const hook of hooks) {\n this.register(hook);\n }\n }\n\n /**\n * Unregister a hook by ID.\n */\n unregister(hookId: string): void {\n for (const [event, hooks] of this.hooks) {\n const filtered = hooks.filter((h) => h.id !== hookId);\n if (filtered.length === 0) {\n this.hooks.delete(event);\n } else {\n this.hooks.set(event, filtered);\n }\n }\n }\n\n /**\n * Emit an event and run the matching hook chain.\n *\n * For \"before\" events: returns results that may include blocks.\n * For \"after\" events: returns observation results (no blocking).\n */\n async emit(event: HookEvent, toolInfo: { name: string; version: string }): Promise<HookResult[]> {\n const eventType = event.type as HookEventType;\n const phase = isBeforeEvent(event) ? \"before\" : \"after\";\n\n const allHooks = this.hooks.get(eventType) ?? [];\n const phaseHooks = allHooks.filter((h) => h.phase === phase);\n\n if (phaseHooks.length === 0) {\n return [];\n }\n\n const ctx: HookContext = {\n event,\n tool: toolInfo,\n cwd: this.settings.cwd,\n state: new Map(),\n results: [],\n startedAt: Date.now(),\n };\n\n try {\n return await executeChain(phaseHooks, ctx, this.settings.hookTimeout);\n } catch (error) {\n if (this.settings.failMode === \"open\") {\n this.log(\"error\", `Hook chain error (fail-open): ${error}`);\n return [];\n }\n // fail-closed: treat as blocked\n return [\n {\n blocked: true,\n reason: `Hook chain error (fail-closed): ${error}`,\n },\n ];\n }\n }\n\n /**\n * Check if an event is blocked by running before hooks.\n * Convenience wrapper around emit().\n */\n async isBlocked(\n event: HookEvent,\n toolInfo: { name: string; version: string },\n ): Promise<{ blocked: boolean; reason?: string }> {\n if (!isBeforeEvent(event)) {\n return { blocked: false };\n }\n\n const results = await this.emit(event, toolInfo);\n const blockResult = results.find((r) => r.blocked);\n\n return blockResult ? { blocked: true, reason: blockResult.reason } : { blocked: false };\n }\n\n /**\n * Get all registered hooks, optionally filtered by event type.\n */\n getHooks(eventType?: HookEventType): HookDefinition[] {\n if (eventType) {\n return this.hooks.get(eventType) ?? [];\n }\n const all: HookDefinition[] = [];\n const seen = new Set<string>();\n for (const hooks of this.hooks.values()) {\n for (const hook of hooks) {\n if (!seen.has(hook.id)) {\n seen.add(hook.id);\n all.push(hook);\n }\n }\n }\n return all;\n }\n\n /**\n * Get current engine settings.\n */\n getSettings(): Required<ConfigSettings> {\n return { ...this.settings };\n }\n\n private log(level: \"error\" | \"warn\" | \"info\" | \"debug\", message: string): void {\n const levels = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };\n const threshold = levels[this.settings.logLevel];\n const messageLevel = levels[level];\n\n if (messageLevel <= threshold) {\n const prefix = `[ai-hooks:${level}]`;\n if (level === \"error\") {\n console.error(prefix, message);\n } else if (level === \"warn\") {\n console.warn(prefix, message);\n } else {\n console.log(prefix, message);\n }\n }\n }\n}\n","import type { Adapter, AdapterFactory } from \"../types/index.js\";\n\n/**\n * Global adapter registry.\n * Adapters register themselves when imported, or can be manually added.\n */\nclass AdapterRegistry {\n private adapters: Map<string, Adapter> = new Map();\n private factories: Map<string, AdapterFactory> = new Map();\n\n /**\n * Register an adapter instance.\n */\n register(adapter: Adapter): void {\n this.adapters.set(adapter.id, adapter);\n }\n\n /**\n * Register an adapter factory for lazy instantiation.\n */\n registerFactory(id: string, factory: AdapterFactory): void {\n this.factories.set(id, factory);\n }\n\n /**\n * Get a registered adapter by ID.\n */\n get(id: string): Adapter | undefined {\n const existing = this.adapters.get(id);\n if (existing) return existing;\n\n // Try factory\n const factory = this.factories.get(id);\n if (factory) {\n const adapter = factory();\n this.adapters.set(id, adapter);\n return adapter;\n }\n\n return undefined;\n }\n\n /**\n * Get all registered adapter IDs.\n */\n list(): string[] {\n return [...new Set([...this.adapters.keys(), ...this.factories.keys()])];\n }\n\n /**\n * Detect which tools are available in the current environment.\n * Returns adapters that successfully detect their tool.\n */\n async detectAll(): Promise<Adapter[]> {\n const detected: Adapter[] = [];\n\n for (const id of this.list()) {\n const adapter = this.get(id);\n if (adapter) {\n try {\n const found = await adapter.detect();\n if (found) {\n detected.push(adapter);\n }\n } catch {\n // Detection failed, skip this adapter\n }\n }\n }\n\n return detected;\n }\n\n /**\n * Clear the registry. Useful for testing.\n */\n clear(): void {\n this.adapters.clear();\n this.factories.clear();\n }\n}\n\nexport const registry = new AdapterRegistry();\n","import { existsSync } from \"node:fs\";\nimport { readFile, writeFile, mkdir, rm } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport type {\n Adapter,\n AdapterCapabilities,\n GeneratedConfig,\n HookDefinition,\n HookEventType,\n} from \"../types/index.js\";\n\n/**\n * Base adapter class with shared utilities.\n * Tool-specific adapters extend this and implement the abstract methods.\n */\nexport abstract class BaseAdapter implements Adapter {\n abstract readonly id: string;\n abstract readonly name: string;\n abstract readonly version: string;\n abstract readonly capabilities: AdapterCapabilities;\n\n abstract detect(): Promise<boolean>;\n abstract generate(hooks: HookDefinition[]): Promise<GeneratedConfig[]>;\n abstract mapEvent(event: HookEventType): string[];\n abstract mapNativeEvent(nativeEvent: string): HookEventType[];\n\n /**\n * Default install: write generated configs to disk.\n */\n async install(configs: GeneratedConfig[]): Promise<void> {\n for (const config of configs) {\n const fullPath = resolve(process.cwd(), config.path);\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, config.content, \"utf-8\");\n }\n }\n\n /**\n * Default uninstall: remove generated config files.\n */\n async uninstall(): Promise<void> {\n // Subclasses should override with specific file paths\n }\n\n // ── Utility Methods ───────────────────────────────────────\n\n protected async fileExists(path: string): Promise<boolean> {\n return existsSync(resolve(process.cwd(), path));\n }\n\n protected async readJsonFile<T>(path: string): Promise<T | null> {\n const fullPath = resolve(process.cwd(), path);\n if (!existsSync(fullPath)) return null;\n const content = await readFile(fullPath, \"utf-8\");\n return JSON.parse(content) as T;\n }\n\n protected async writeJsonFile(path: string, data: unknown): Promise<void> {\n const fullPath = resolve(process.cwd(), path);\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, JSON.stringify(data, null, 2) + \"\\n\", \"utf-8\");\n }\n\n protected async removeFile(path: string): Promise<void> {\n const fullPath = resolve(process.cwd(), path);\n if (existsSync(fullPath)) {\n await rm(fullPath);\n }\n }\n\n /**\n * Check if a CLI command exists on PATH.\n */\n protected async commandExists(command: string): Promise<boolean> {\n const { exec } = await import(\"node:child_process\");\n return new Promise((resolve) => {\n exec(`which ${command}`, (error) => {\n resolve(!error);\n });\n });\n }\n}\n","import { hook } from \"../config/define.js\";\nimport type { HookDefinition } from \"../types/index.js\";\n\n/**\n * Built-in hook: Block dangerous shell commands.\n * Prevents rm -rf /, drop database, and other destructive patterns.\n */\nexport const blockDangerousCommands = hook(\"before\", [\"shell:before\"], async (ctx, next) => {\n const command = ctx.event.command;\n const dangerous = DANGEROUS_PATTERNS.find((p) => p.pattern.test(command));\n\n if (dangerous) {\n ctx.results.push({\n blocked: true,\n reason: `Blocked dangerous command: ${dangerous.description}`,\n });\n return;\n }\n\n await next();\n})\n .id(\"ai-hooks:block-dangerous-commands\")\n .name(\"Block Dangerous Commands\")\n .description(\"Prevents destructive shell commands like rm -rf /, drop database, etc.\")\n .priority(1)\n .build();\n\nconst DANGEROUS_PATTERNS = [\n { pattern: /rm\\s+(-[a-zA-Z]*f[a-zA-Z]*\\s+)?\\/\\s*$/, description: \"rm -rf /\" },\n { pattern: /rm\\s+-[a-zA-Z]*f[a-zA-Z]*\\s+~\\/?\\s*$/, description: \"rm -rf ~\" },\n { pattern: /mkfs\\./, description: \"filesystem format\" },\n { pattern: /dd\\s+.*of=\\/dev\\/[sh]d/, description: \"disk overwrite\" },\n { pattern: /:\\(\\)\\s*\\{\\s*:\\|:&\\s*\\}\\s*;:/, description: \"fork bomb\" },\n { pattern: />\\s*\\/dev\\/[sh]d/, description: \"device overwrite\" },\n { pattern: /chmod\\s+(-R\\s+)?777\\s+\\//, description: \"chmod 777 /\" },\n { pattern: /DROP\\s+DATABASE/i, description: \"DROP DATABASE\" },\n { pattern: /DROP\\s+TABLE/i, description: \"DROP TABLE\" },\n { pattern: /TRUNCATE\\s+TABLE/i, description: \"TRUNCATE TABLE\" },\n];\n\n/**\n * Built-in hook: Scan for secrets in file writes.\n * Detects API keys, tokens, and credentials being written to files.\n */\nexport const scanSecrets = hook(\"before\", [\"file:write\", \"file:edit\"], async (ctx, next) => {\n const content = ctx.event.type === \"file:write\" ? ctx.event.content : ctx.event.newContent;\n\n const found = SECRET_PATTERNS.find((p) => p.pattern.test(content));\n\n if (found) {\n ctx.results.push({\n blocked: true,\n reason: `Potential secret detected: ${found.description}. Use environment variables instead.`,\n });\n return;\n }\n\n await next();\n})\n .id(\"ai-hooks:scan-secrets\")\n .name(\"Scan for Secrets\")\n .description(\"Prevents hardcoded API keys, tokens, and credentials in file writes.\")\n .priority(2)\n .build();\n\nconst SECRET_PATTERNS = [\n { pattern: /(?:api[_-]?key|apikey)\\s*[:=]\\s*['\"][a-zA-Z0-9]{20,}['\"]/i, description: \"API key\" },\n {\n pattern: /(?:secret|token|password|passwd|pwd)\\s*[:=]\\s*['\"][^'\"]{8,}['\"]/i,\n description: \"Secret/token/password\",\n },\n { pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/, description: \"Private key\" },\n { pattern: /ghp_[a-zA-Z0-9]{36}/, description: \"GitHub personal access token\" },\n { pattern: /sk-[a-zA-Z0-9]{20,}/, description: \"OpenAI/Stripe secret key\" },\n { pattern: /AKIA[0-9A-Z]{16}/, description: \"AWS access key ID\" },\n { pattern: /xox[bpors]-[a-zA-Z0-9-]{10,}/, description: \"Slack token\" },\n];\n\n/**\n * Built-in hook: Protect gitignored files from being read.\n */\nexport const protectGitignored = hook(\"before\", [\"file:write\"], async (ctx, next) => {\n const path = ctx.event.path;\n\n // Block writes to common sensitive files\n const sensitive = SENSITIVE_FILES.some((f) => path.endsWith(f));\n if (sensitive) {\n ctx.results.push({\n blocked: true,\n reason: `Cannot write to sensitive file: ${path}. This file should be managed manually.`,\n });\n return;\n }\n\n await next();\n})\n .id(\"ai-hooks:protect-sensitive-files\")\n .name(\"Protect Sensitive Files\")\n .description(\"Prevents AI tools from overwriting .env, credentials, and other sensitive files.\")\n .priority(3)\n .build();\n\nconst SENSITIVE_FILES = [\n \".env\",\n \".env.local\",\n \".env.production\",\n \"credentials.json\",\n \"service-account.json\",\n \"id_rsa\",\n \"id_ed25519\",\n \".npmrc\",\n \".pypirc\",\n];\n\n/**\n * Built-in hook: Log all shell commands (after phase).\n */\nexport const auditShellCommands = hook(\"after\", [\"shell:after\"], async (ctx, next) => {\n const { command, exitCode, duration } = ctx.event;\n ctx.results.push({\n data: {\n audit: {\n type: \"shell\",\n command,\n exitCode,\n duration,\n timestamp: ctx.event.timestamp,\n tool: ctx.tool.name,\n },\n },\n });\n await next();\n})\n .id(\"ai-hooks:audit-shell\")\n .name(\"Audit Shell Commands\")\n .description(\"Records all shell command executions for audit trail.\")\n .priority(999)\n .build();\n\n/**\n * All built-in hooks as an array.\n * Use with `extends` in defineConfig to include defaults.\n */\nexport const builtinHooks: HookDefinition[] = [\n blockDangerousCommands,\n scanSecrets,\n protectGitignored,\n auditShellCommands,\n];\n"]}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@premierstudio/ai-hooks",
3
+ "version": "1.0.4",
4
+ "description": "Universal hooks framework for AI coding tools. What Express.js is to HTTP, ai-hooks is to AI.",
5
+ "keywords": [
6
+ "ai",
7
+ "ai-tools",
8
+ "claude-code",
9
+ "codex",
10
+ "developer-tools",
11
+ "gemini",
12
+ "hooks",
13
+ "middleware",
14
+ "orchestration",
15
+ "security"
16
+ ],
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/PremierStudio/ai-hooks"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "type": "module",
28
+ "main": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js"
34
+ },
35
+ "./hooks": {
36
+ "types": "./dist/hooks/index.d.ts",
37
+ "import": "./dist/hooks/index.js"
38
+ },
39
+ "./adapters": {
40
+ "types": "./dist/adapters/index.d.ts",
41
+ "import": "./dist/adapters/index.js"
42
+ }
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "scripts": {
48
+ "build": "tsup",
49
+ "dev": "tsup --watch",
50
+ "typecheck": "tsc --noEmit",
51
+ "test": "vitest run",
52
+ "test:watch": "vitest",
53
+ "clean": "rm -rf dist"
54
+ },
55
+ "engines": {
56
+ "node": ">=22.0.0"
57
+ }
58
+ }