@goodfoot/claude-code-hooks 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,851 @@
1
+ /**
2
+ * Hook factory functions for Claude Code hooks.
3
+ *
4
+ * Provides typed factory functions for all 12 hook types that handle:
5
+ * - Input type narrowing based on hook event type
6
+ * - Output type enforcement via return types
7
+ * - Error wrapping with automatic logging
8
+ * - Logger context injection
9
+ *
10
+ * Each factory accepts a HookConfig with optional matcher and timeout settings,
11
+ * and returns a function that the runtime invokes when the hook file executes.
12
+ * @module
13
+ * @example
14
+ * ```typescript
15
+ * import { preToolUseHook, preToolUseOutput } from '@goodfoot/claude-code-hooks';
16
+ *
17
+ * export default preToolUseHook({ matcher: 'Bash' }, async (input, { logger }) => {
18
+ * logger.info('Processing Bash command');
19
+ * return preToolUseOutput({ allow: true });
20
+ * });
21
+ * ```
22
+ * @see https://code.claude.com/docs/en/hooks
23
+ */
24
+ import type {
25
+ PreToolUseInput,
26
+ PostToolUseInput,
27
+ PostToolUseFailureInput,
28
+ NotificationInput,
29
+ UserPromptSubmitInput,
30
+ SessionStartInput,
31
+ SessionEndInput,
32
+ StopInput,
33
+ SubagentStartInput,
34
+ SubagentStopInput,
35
+ PreCompactInput,
36
+ PermissionRequestInput,
37
+ HookEventName
38
+ } from './inputs.js';
39
+ import type { Logger } from './logger.js';
40
+ import type {
41
+ SpecificHookOutput,
42
+ PreToolUseOutput,
43
+ PostToolUseOutput,
44
+ PostToolUseFailureOutput,
45
+ NotificationOutput,
46
+ UserPromptSubmitOutput,
47
+ SessionStartOutput,
48
+ SessionEndOutput,
49
+ StopOutput,
50
+ SubagentStartOutput,
51
+ SubagentStopOutput,
52
+ PreCompactOutput,
53
+ PermissionRequestOutput
54
+ } from './outputs.js';
55
+ import type { ToolInputMap, KnownToolName } from './tool-inputs.js';
56
+ /**
57
+ * Configuration options for hook factories.
58
+ *
59
+ * Controls how the hook matches events and handler timeout behavior.
60
+ *
61
+ * ## Matcher Semantics by Hook Type
62
+ *
63
+ * | Hook Type | Matcher Matches Against | Example Values |
64
+ * |-----------|------------------------|----------------|
65
+ * | PreToolUse | `tool_name` | `'Bash'`, `'Bash\|Read'`, `'.*'` |
66
+ * | PostToolUse | `tool_name` | `'Bash'`, `'Skill'`, `'Write\|Edit'` |
67
+ * | PostToolUseFailure | `tool_name` | `'Bash'` |
68
+ * | PermissionRequest | `tool_name` | `'Bash'`, `'Read'` |
69
+ * | SessionStart | `source` | `'startup'`, `'resume'`, `'clear'`, `'compact'` |
70
+ * | SessionEnd | `reason` | Exit reason string |
71
+ * | Stop | N/A (no matcher) | Fires on all stop events |
72
+ * | SubagentStart | `agent_type` | Subagent type |
73
+ * | SubagentStop | `agent_type` | Subagent type |
74
+ * | Notification | `notification_type` | Notification type |
75
+ * | UserPromptSubmit | N/A (no matcher) | Fires on all prompt submissions |
76
+ * | PreCompact | `trigger` | `'manual'`, `'auto'` |
77
+ * @example
78
+ * ```typescript
79
+ * // Match only Bash tool usage
80
+ * preToolUseHook({ matcher: 'Bash' }, handler);
81
+ *
82
+ * // Match multiple tools
83
+ * preToolUseHook({ matcher: 'Bash|Read|Write' }, handler);
84
+ *
85
+ * // Match all tools with regex
86
+ * preToolUseHook({ matcher: '.*' }, handler);
87
+ *
88
+ * // Set handler timeout
89
+ * preToolUseHook({ timeout: 5000 }, handler);
90
+ * ```
91
+ */
92
+ export interface HookConfig {
93
+ /**
94
+ * Regular expression pattern for matching hook events.
95
+ *
96
+ * What the pattern matches against depends on the hook type:
97
+ * - Tool hooks (PreToolUse, PostToolUse, etc.): matches `tool_name`
98
+ * - SessionStart: matches `source`
99
+ * - SessionEnd: matches `reason`
100
+ * - SubagentStart/Stop: matches `agent_type`
101
+ * - Notification: matches `notification_type`
102
+ * - PreCompact: matches `trigger`
103
+ * - Stop, UserPromptSubmit: no matcher (fires on all events)
104
+ *
105
+ * If not provided, the hook fires for all events of its type.
106
+ * @example
107
+ * ```typescript
108
+ * // Match Bash tool
109
+ * { matcher: 'Bash' }
110
+ *
111
+ * // Match multiple tools with alternation
112
+ * { matcher: 'Bash|Read|Write' }
113
+ *
114
+ * // Match all with regex
115
+ * { matcher: '.*' }
116
+ *
117
+ * // Match startup sessions
118
+ * { matcher: 'startup' }
119
+ * ```
120
+ */
121
+ matcher?: string;
122
+ /**
123
+ * Handler execution timeout in milliseconds.
124
+ *
125
+ * If the handler does not complete within this time, it will be
126
+ * terminated and an error will be logged. This prevents hooks from
127
+ * blocking Claude Code indefinitely.
128
+ *
129
+ * If not provided, uses the default timeout from the runtime.
130
+ * @example
131
+ * ```typescript
132
+ * // 5 second timeout
133
+ * { timeout: 5000 }
134
+ *
135
+ * // 30 second timeout for long operations
136
+ * { timeout: 30000 }
137
+ * ```
138
+ */
139
+ timeout?: number;
140
+ }
141
+ /**
142
+ * Configuration for hooks with a known single-tool matcher.
143
+ *
144
+ * When the matcher is a single known tool name, the handler receives
145
+ * automatically typed toolInput based on the tool type.
146
+ * @template T - The known tool name
147
+ * @example
148
+ * ```typescript
149
+ * // tool_input is automatically typed as WriteToolInput
150
+ * preToolUseHook({ matcher: 'Write' }, (input) => {
151
+ * console.log(input.tool_input.file_path); // Typed!
152
+ * console.log(input.tool_input.content); // Typed!
153
+ * });
154
+ * ```
155
+ */
156
+ export interface TypedHookConfig<T extends KnownToolName> {
157
+ /**
158
+ * The single tool name to match.
159
+ * When this is a known tool name, toolInput will be automatically typed.
160
+ */
161
+ matcher: T;
162
+ /**
163
+ * Handler execution timeout in milliseconds.
164
+ */
165
+ timeout?: number;
166
+ }
167
+ /**
168
+ * PreToolUseInput with typed tool_input for a specific tool.
169
+ * @template T - The known tool name
170
+ */
171
+ export type TypedPreToolUseInput<T extends KnownToolName> = Omit<PreToolUseInput, 'tool_name' | 'tool_input'> & {
172
+ tool_name: T;
173
+ tool_input: ToolInputMap[T];
174
+ };
175
+ /**
176
+ * PostToolUseInput with typed tool_input for a specific tool.
177
+ * @template T - The known tool name
178
+ */
179
+ export type TypedPostToolUseInput<T extends KnownToolName> = Omit<PostToolUseInput, 'tool_name' | 'tool_input'> & {
180
+ tool_name: T;
181
+ tool_input: ToolInputMap[T];
182
+ };
183
+ /**
184
+ * PostToolUseFailureInput with typed tool_input for a specific tool.
185
+ * @template T - The known tool name
186
+ */
187
+ export type TypedPostToolUseFailureInput<T extends KnownToolName> = Omit<
188
+ PostToolUseFailureInput,
189
+ 'tool_name' | 'tool_input'
190
+ > & {
191
+ tool_name: T;
192
+ tool_input: ToolInputMap[T];
193
+ };
194
+ /**
195
+ * PermissionRequestInput with typed tool_input for a specific tool.
196
+ * @template T - The known tool name
197
+ */
198
+ export type TypedPermissionRequestInput<T extends KnownToolName> = Omit<
199
+ PermissionRequestInput,
200
+ 'tool_name' | 'tool_input'
201
+ > & {
202
+ tool_name: T;
203
+ tool_input: ToolInputMap[T];
204
+ };
205
+ /**
206
+ * Context provided to hook handlers.
207
+ *
208
+ * Contains utilities and state available during hook execution.
209
+ * The context is injected by the runtime and should not be created manually.
210
+ * @example
211
+ * ```typescript
212
+ * export default preToolUseHook({}, async (input, { logger }) => {
213
+ * logger.info('Processing tool', { toolName: input.tool_name });
214
+ * return preToolUseOutput({ allow: true });
215
+ * });
216
+ * ```
217
+ */
218
+ export interface HookContext {
219
+ /**
220
+ * Logger instance for structured logging.
221
+ *
222
+ * The logger is pre-configured with the hook context (hookType, input)
223
+ * so log events are automatically enriched. Use this instead of
224
+ * console.log/error to ensure logs go to file, not stdout/stderr
225
+ * which would interfere with the hook protocol.
226
+ * @example
227
+ * ```typescript
228
+ * // Simple message
229
+ * logger.info('Processing request');
230
+ *
231
+ * // With context
232
+ * logger.warn('Rate limit approaching', { current: 95, max: 100 });
233
+ *
234
+ * // Error logging
235
+ * try {
236
+ * await riskyOperation();
237
+ * } catch (err) {
238
+ * logger.logError(err, 'Operation failed');
239
+ * }
240
+ * ```
241
+ */
242
+ logger: Logger;
243
+ }
244
+ /**
245
+ * Extended context for SessionStart hooks.
246
+ *
247
+ * SessionStart hooks have additional capabilities for persisting environment
248
+ * variables that will be available in all subsequent bash commands.
249
+ * @example
250
+ * ```typescript
251
+ * export default sessionStartHook({}, async (input, { logger, persistEnvVar }) => {
252
+ * // Set environment variables for the session
253
+ * persistEnvVar('NODE_ENV', 'development');
254
+ * persistEnvVar('DEBUG', 'true');
255
+ *
256
+ * return sessionStartOutput({});
257
+ * });
258
+ * ```
259
+ */
260
+ export interface SessionStartContext extends HookContext {
261
+ /**
262
+ * Persists an environment variable for use in subsequent bash commands.
263
+ *
264
+ * This function writes a shell export statement to the `CLAUDE_ENV_FILE`,
265
+ * which Claude Code sources before running bash commands. This allows
266
+ * SessionStart hooks to configure the environment for the entire session.
267
+ * @param name - The environment variable name
268
+ * @param value - The environment variable value (will be shell-escaped)
269
+ * @example
270
+ * ```typescript
271
+ * persistEnvVar('NODE_ENV', 'production');
272
+ * persistEnvVar('API_KEY', 'secret-key');
273
+ * ```
274
+ */
275
+ persistEnvVar: (name: string, value: string) => void;
276
+ /**
277
+ * Persists multiple environment variables at once.
278
+ *
279
+ * This is a convenience wrapper around `persistEnvVar` for setting
280
+ * multiple variables in a single call.
281
+ * @param vars - Object mapping variable names to values
282
+ * @example
283
+ * ```typescript
284
+ * persistEnvVars({
285
+ * NODE_ENV: 'production',
286
+ * API_KEY: 'secret',
287
+ * DEBUG: 'false'
288
+ * });
289
+ * ```
290
+ */
291
+ persistEnvVars: (vars: Record<string, string>) => void;
292
+ }
293
+ /**
294
+ * Handler function for a specific hook type.
295
+ *
296
+ * Receives the typed input and context, returns a specific output type.
297
+ * Can be async for operations that require awaiting.
298
+ * @template TInput - The input type for this hook
299
+ * @template TOutput - The specific output type for this hook
300
+ * @template TContext - The context type (defaults to HookContext)
301
+ */
302
+ export type HookHandler<TInput, TOutput extends SpecificHookOutput, TContext extends HookContext = HookContext> = (
303
+ input: TInput,
304
+ context: TContext
305
+ ) => TOutput | Promise<TOutput>;
306
+ /**
307
+ * The result of a hook factory - a function that wraps the handler.
308
+ *
309
+ * This is what gets exported from hook files and invoked by the runtime.
310
+ * The wrapper handles error catching and logging.
311
+ * @template TInput - The input type for this hook
312
+ * @template TOutput - The specific output type for this hook
313
+ * @template TContext - The context type (defaults to HookContext)
314
+ */
315
+ export interface HookFunction<TInput, TOutput extends SpecificHookOutput, TContext extends HookContext = HookContext> {
316
+ /**
317
+ * Execute the hook handler with the given input and context.
318
+ * @param input - The hook input data
319
+ * @param context - The hook execution context
320
+ * @returns The hook output (specific type, converted to HookOutput by runtime)
321
+ */
322
+ (input: TInput, context: TContext): Promise<TOutput>;
323
+ /**
324
+ * The hook event name this handler is for.
325
+ */
326
+ hookEventName: HookEventName;
327
+ /**
328
+ * The matcher pattern, if configured.
329
+ */
330
+ matcher?: string;
331
+ /**
332
+ * The timeout in milliseconds, if configured.
333
+ */
334
+ timeout?: number;
335
+ }
336
+ /**
337
+ * Creates a PreToolUse hook handler.
338
+ *
339
+ * PreToolUse hooks fire before any tool is executed, allowing you to:
340
+ * - Inspect and validate tool inputs
341
+ * - Allow, deny, or modify the tool execution
342
+ * - Add custom permission logic
343
+ *
344
+ * **Matcher**: Matches against `tool_name` (e.g., 'Bash', 'Read', 'Write')
345
+ *
346
+ * **Typed Overload**: When the matcher is a single known tool name (Write, Edit,
347
+ * MultiEdit, Read, Bash, Glob, Grep), the handler receives automatically typed
348
+ * `toolInput` based on the tool type.
349
+ * @param config - Hook configuration with optional matcher and timeout
350
+ * @param handler - The handler function to execute
351
+ * @returns A hook function that can be exported as the default export
352
+ * @example
353
+ * ```typescript
354
+ * import { preToolUseHook, preToolUseOutput } from '@goodfoot/claude-code-hooks';
355
+ *
356
+ * // Typed overload: tool_input is automatically typed as BashToolInput
357
+ * export default preToolUseHook({ matcher: 'Bash' }, async (input, { logger }) => {
358
+ * // input.tool_input.command is typed as string - no cast needed!
359
+ * if (input.tool_input.command.includes('rm -rf')) {
360
+ * logger.warn('Blocking destructive command', { command: input.tool_input.command });
361
+ * return preToolUseOutput({
362
+ * hookSpecificOutput: {
363
+ * permissionDecision: 'deny',
364
+ * permissionDecisionReason: 'Destructive commands are not allowed'
365
+ * }
366
+ * });
367
+ * }
368
+ *
369
+ * return preToolUseOutput({
370
+ * hookSpecificOutput: { permissionDecision: 'allow' }
371
+ * });
372
+ * });
373
+ * ```
374
+ * @example
375
+ * ```typescript
376
+ * // Typed overload: tool_input is automatically typed as WriteToolInput
377
+ * export default preToolUseHook({ matcher: 'Write' }, (input) => {
378
+ * const { file_path, content } = input.tool_input; // Full autocomplete!
379
+ * // ...
380
+ * });
381
+ * ```
382
+ * @see https://code.claude.com/docs/en/hooks#pretooluse
383
+ */
384
+ export declare function preToolUseHook<T extends KnownToolName>(
385
+ config: TypedHookConfig<T>,
386
+ handler: HookHandler<TypedPreToolUseInput<T>, PreToolUseOutput>
387
+ ): HookFunction<TypedPreToolUseInput<T>, PreToolUseOutput>;
388
+ export declare function preToolUseHook(
389
+ config: HookConfig,
390
+ handler: HookHandler<PreToolUseInput, PreToolUseOutput>
391
+ ): HookFunction<PreToolUseInput, PreToolUseOutput>;
392
+ /**
393
+ * Creates a PostToolUse hook handler.
394
+ *
395
+ * PostToolUse hooks fire after a tool executes successfully, allowing you to:
396
+ * - Inspect tool results
397
+ * - Add additional context to the conversation
398
+ * - Modify MCP tool output
399
+ *
400
+ * **Matcher**: Matches against `tool_name`
401
+ *
402
+ * **Typed Overload**: When the matcher is a single known tool name, the handler
403
+ * receives automatically typed `toolInput` based on the tool type.
404
+ * @param config - Hook configuration with optional matcher and timeout
405
+ * @param handler - The handler function to execute
406
+ * @returns A hook function that can be exported as the default export
407
+ * @example
408
+ * ```typescript
409
+ * import { postToolUseHook, postToolUseOutput } from '@goodfoot/claude-code-hooks';
410
+ *
411
+ * // Typed overload: tool_input is automatically typed as ReadToolInput
412
+ * export default postToolUseHook({ matcher: 'Read' }, async (input, { logger }) => {
413
+ * // input.tool_input.file_path is typed as string - no cast needed!
414
+ * logger.info('File read completed', { filePath: input.tool_input.file_path });
415
+ *
416
+ * return postToolUseOutput({
417
+ * hookSpecificOutput: {
418
+ * additionalContext: `File ${input.tool_input.file_path} was read successfully`
419
+ * }
420
+ * });
421
+ * });
422
+ * ```
423
+ * @see https://code.claude.com/docs/en/hooks#posttooluse
424
+ */
425
+ export declare function postToolUseHook<T extends KnownToolName>(
426
+ config: TypedHookConfig<T>,
427
+ handler: HookHandler<TypedPostToolUseInput<T>, PostToolUseOutput>
428
+ ): HookFunction<TypedPostToolUseInput<T>, PostToolUseOutput>;
429
+ export declare function postToolUseHook(
430
+ config: HookConfig,
431
+ handler: HookHandler<PostToolUseInput, PostToolUseOutput>
432
+ ): HookFunction<PostToolUseInput, PostToolUseOutput>;
433
+ /**
434
+ * Creates a PostToolUseFailure hook handler.
435
+ *
436
+ * PostToolUseFailure hooks fire after a tool execution fails, allowing you to:
437
+ * - Log or report tool failures
438
+ * - Add context about the failure
439
+ * - Take corrective action
440
+ *
441
+ * **Matcher**: Matches against `tool_name`
442
+ *
443
+ * **Typed Overload**: When the matcher is a single known tool name, the handler
444
+ * receives automatically typed `toolInput` based on the tool type.
445
+ * @param config - Hook configuration with optional matcher and timeout
446
+ * @param handler - The handler function to execute
447
+ * @returns A hook function that can be exported as the default export
448
+ * @example
449
+ * ```typescript
450
+ * import { postToolUseFailureHook, postToolUseFailureOutput } from '@goodfoot/claude-code-hooks';
451
+ *
452
+ * // Log tool failures and suggest alternatives
453
+ * export default postToolUseFailureHook({ matcher: 'Bash' }, async (input, { logger }) => {
454
+ * // input.tool_input.command is typed as string
455
+ * logger.error('Bash command failed', {
456
+ * command: input.tool_input.command,
457
+ * error: input.error
458
+ * });
459
+ *
460
+ * return postToolUseFailureOutput({
461
+ * hookSpecificOutput: {
462
+ * additionalContext: 'Please try an alternative approach'
463
+ * }
464
+ * });
465
+ * });
466
+ * ```
467
+ * @see https://code.claude.com/docs/en/hooks#posttoolusefailure
468
+ */
469
+ export declare function postToolUseFailureHook<T extends KnownToolName>(
470
+ config: TypedHookConfig<T>,
471
+ handler: HookHandler<TypedPostToolUseFailureInput<T>, PostToolUseFailureOutput>
472
+ ): HookFunction<TypedPostToolUseFailureInput<T>, PostToolUseFailureOutput>;
473
+ export declare function postToolUseFailureHook(
474
+ config: HookConfig,
475
+ handler: HookHandler<PostToolUseFailureInput, PostToolUseFailureOutput>
476
+ ): HookFunction<PostToolUseFailureInput, PostToolUseFailureOutput>;
477
+ /**
478
+ * Creates a Notification hook handler.
479
+ *
480
+ * Notification hooks fire when Claude Code sends a notification, allowing you to:
481
+ * - Forward notifications to external systems
482
+ * - Log important events
483
+ * - Trigger custom alerting
484
+ *
485
+ * **Matcher**: Matches against `notification_type`
486
+ * @param config - Hook configuration with optional matcher and timeout
487
+ * @param handler - The handler function to execute
488
+ * @returns A hook function that can be exported as the default export
489
+ * @example
490
+ * ```typescript
491
+ * import { notificationHook, notificationOutput } from '@goodfoot/claude-code-hooks';
492
+ *
493
+ * // Forward notifications to Slack
494
+ * export default notificationHook({}, async (input, { logger }) => {
495
+ * logger.info('Notification received', {
496
+ * type: input.notification_type,
497
+ * title: input.title
498
+ * });
499
+ *
500
+ * await sendSlackMessage(input.title ?? 'Notification', input.message);
501
+ *
502
+ * return notificationOutput({});
503
+ * });
504
+ * ```
505
+ * @see https://code.claude.com/docs/en/hooks#notification
506
+ */
507
+ export declare function notificationHook(
508
+ config: HookConfig,
509
+ handler: HookHandler<NotificationInput, NotificationOutput>
510
+ ): HookFunction<NotificationInput, NotificationOutput>;
511
+ /**
512
+ * Creates a UserPromptSubmit hook handler.
513
+ *
514
+ * UserPromptSubmit hooks fire when a user submits a prompt, allowing you to:
515
+ * - Add additional context or instructions
516
+ * - Log user interactions
517
+ * - Validate or transform prompts
518
+ *
519
+ * **Matcher**: No matcher support - fires on all prompt submissions
520
+ * @param config - Hook configuration with optional timeout (matcher is ignored)
521
+ * @param handler - The handler function to execute
522
+ * @returns A hook function that can be exported as the default export
523
+ * @example
524
+ * ```typescript
525
+ * import { userPromptSubmitHook, userPromptSubmitOutput } from '@goodfoot/claude-code-hooks';
526
+ *
527
+ * // Add project context to every prompt
528
+ * export default userPromptSubmitHook({}, async (input, { logger }) => {
529
+ * logger.debug('User prompt submitted', { promptLength: input.prompt.length });
530
+ *
531
+ * const projectContext = await getProjectContext();
532
+ *
533
+ * return userPromptSubmitOutput({
534
+ * additionalContext: projectContext
535
+ * });
536
+ * });
537
+ * ```
538
+ * @see https://code.claude.com/docs/en/hooks#userpromptsubmit
539
+ */
540
+ export declare function userPromptSubmitHook(
541
+ config: HookConfig,
542
+ handler: HookHandler<UserPromptSubmitInput, UserPromptSubmitOutput>
543
+ ): HookFunction<UserPromptSubmitInput, UserPromptSubmitOutput>;
544
+ /**
545
+ * Creates a SessionStart hook handler.
546
+ *
547
+ * SessionStart hooks fire when a Claude Code session starts or restarts,
548
+ * allowing you to:
549
+ * - Initialize session state
550
+ * - Inject context or instructions
551
+ * - Persist environment variables for subsequent bash commands
552
+ * - Set up logging or monitoring
553
+ *
554
+ * **Matcher**: Matches against `source` ('startup', 'resume', 'clear', 'compact')
555
+ *
556
+ * **Context**: SessionStart hooks receive an extended context with `persistEnvVar`
557
+ * and `persistEnvVars` functions for setting environment variables.
558
+ * @param config - Hook configuration with optional matcher and timeout
559
+ * @param handler - The handler function to execute
560
+ * @returns A hook function that can be exported as the default export
561
+ * @example
562
+ * ```typescript
563
+ * import { sessionStartHook, sessionStartOutput } from '@goodfoot/claude-code-hooks';
564
+ *
565
+ * // Persist environment variables for the session
566
+ * export default sessionStartHook({ matcher: 'startup' }, async (input, { logger, persistEnvVar }) => {
567
+ * logger.info('New session started', {
568
+ * sessionId: input.session_id,
569
+ * cwd: input.cwd
570
+ * });
571
+ *
572
+ * // Set environment variables for all subsequent bash commands
573
+ * persistEnvVar('NODE_ENV', 'development');
574
+ * persistEnvVar('DEBUG', 'true');
575
+ *
576
+ * return sessionStartOutput({});
577
+ * });
578
+ * ```
579
+ * @example
580
+ * ```typescript
581
+ * // Set multiple environment variables at once
582
+ * export default sessionStartHook({}, async (input, { persistEnvVars }) => {
583
+ * persistEnvVars({
584
+ * NODE_ENV: 'production',
585
+ * API_KEY: 'secret',
586
+ * DEBUG: 'false'
587
+ * });
588
+ *
589
+ * return sessionStartOutput({});
590
+ * });
591
+ * ```
592
+ * @see https://code.claude.com/docs/en/hooks#sessionstart
593
+ */
594
+ export declare function sessionStartHook(
595
+ config: HookConfig,
596
+ handler: HookHandler<SessionStartInput, SessionStartOutput, SessionStartContext>
597
+ ): HookFunction<SessionStartInput, SessionStartOutput, SessionStartContext>;
598
+ /**
599
+ * Creates a SessionEnd hook handler.
600
+ *
601
+ * SessionEnd hooks fire when a Claude Code session ends, allowing you to:
602
+ * - Clean up session resources
603
+ * - Log session metrics
604
+ * - Persist session state
605
+ *
606
+ * **Matcher**: Matches against `reason` (the exit reason string)
607
+ * @param config - Hook configuration with optional matcher and timeout
608
+ * @param handler - The handler function to execute
609
+ * @returns A hook function that can be exported as the default export
610
+ * @example
611
+ * ```typescript
612
+ * import { sessionEndHook, sessionEndOutput } from '@goodfoot/claude-code-hooks';
613
+ *
614
+ * // Log session end and clean up
615
+ * export default sessionEndHook({}, async (input, { logger }) => {
616
+ * logger.info('Session ended', {
617
+ * sessionId: input.session_id,
618
+ * reason: input.reason
619
+ * });
620
+ *
621
+ * await cleanupSessionResources(input.session_id);
622
+ *
623
+ * return sessionEndOutput({});
624
+ * });
625
+ * ```
626
+ * @see https://code.claude.com/docs/en/hooks#sessionend
627
+ */
628
+ export declare function sessionEndHook(
629
+ config: HookConfig,
630
+ handler: HookHandler<SessionEndInput, SessionEndOutput>
631
+ ): HookFunction<SessionEndInput, SessionEndOutput>;
632
+ /**
633
+ * Creates a Stop hook handler.
634
+ *
635
+ * Stop hooks fire when Claude Code is about to stop, allowing you to:
636
+ * - Block the stop and require additional action
637
+ * - Confirm the user wants to stop
638
+ * - Clean up resources before stopping
639
+ *
640
+ * **Matcher**: No matcher support - fires on all stop events
641
+ * @param config - Hook configuration with optional timeout (matcher is ignored)
642
+ * @param handler - The handler function to execute
643
+ * @returns A hook function that can be exported as the default export
644
+ * @example
645
+ * ```typescript
646
+ * import { stopHook, stopOutput } from '@goodfoot/claude-code-hooks';
647
+ *
648
+ * // Block stop if there are pending changes
649
+ * export default stopHook({}, async (input, { logger }) => {
650
+ * const pendingChanges = await checkPendingChanges();
651
+ *
652
+ * if (pendingChanges.length > 0) {
653
+ * logger.warn('Blocking stop due to pending changes', {
654
+ * count: pendingChanges.length
655
+ * });
656
+ *
657
+ * return stopOutput({
658
+ * decision: 'block',
659
+ * reason: `There are ${pendingChanges.length} uncommitted changes`,
660
+ * systemMessage: 'Please commit or discard changes before stopping'
661
+ * });
662
+ * }
663
+ *
664
+ * logger.info('Approving stop');
665
+ * return stopOutput({ decision: 'approve' });
666
+ * });
667
+ * ```
668
+ * @see https://code.claude.com/docs/en/hooks#stop
669
+ */
670
+ export declare function stopHook(
671
+ config: HookConfig,
672
+ handler: HookHandler<StopInput, StopOutput>
673
+ ): HookFunction<StopInput, StopOutput>;
674
+ /**
675
+ * Creates a SubagentStart hook handler.
676
+ *
677
+ * SubagentStart hooks fire when a subagent (Task tool) starts, allowing you to:
678
+ * - Inject context for the subagent
679
+ * - Log subagent invocations
680
+ * - Configure subagent behavior
681
+ *
682
+ * **Matcher**: Matches against `agent_type` (e.g., 'explore', 'codebase-analysis')
683
+ * @param config - Hook configuration with optional matcher and timeout
684
+ * @param handler - The handler function to execute
685
+ * @returns A hook function that can be exported as the default export
686
+ * @example
687
+ * ```typescript
688
+ * import { subagentStartHook, subagentStartOutput } from '@goodfoot/claude-code-hooks';
689
+ *
690
+ * // Add context for explore subagents
691
+ * export default subagentStartHook({ matcher: 'explore' }, async (input, { logger }) => {
692
+ * logger.info('Explore subagent starting', {
693
+ * agentId: input.agent_id,
694
+ * agentType: input.agent_type
695
+ * });
696
+ *
697
+ * return subagentStartOutput({
698
+ * additionalContext: 'Focus on finding patterns and conventions'
699
+ * });
700
+ * });
701
+ * ```
702
+ * @see https://code.claude.com/docs/en/hooks#subagentstart
703
+ */
704
+ export declare function subagentStartHook(
705
+ config: HookConfig,
706
+ handler: HookHandler<SubagentStartInput, SubagentStartOutput>
707
+ ): HookFunction<SubagentStartInput, SubagentStartOutput>;
708
+ /**
709
+ * Creates a SubagentStop hook handler.
710
+ *
711
+ * SubagentStop hooks fire when a subagent completes or stops, allowing you to:
712
+ * - Block the subagent from stopping
713
+ * - Process subagent results
714
+ * - Clean up subagent resources
715
+ * - Log subagent completion
716
+ *
717
+ * **Matcher**: Matches against `agent_type` (e.g., 'explore', 'codebase-analysis')
718
+ * @param config - Hook configuration with optional matcher and timeout
719
+ * @param handler - The handler function to execute
720
+ * @returns A hook function that can be exported as the default export
721
+ * @example
722
+ * ```typescript
723
+ * import { subagentStopHook, subagentStopOutput } from '@goodfoot/claude-code-hooks';
724
+ *
725
+ * // Block explore subagents if task incomplete
726
+ * export default subagentStopHook({ matcher: 'explore' }, async (input, { logger }) => {
727
+ * logger.info('Subagent stopping', {
728
+ * agentId: input.agent_id,
729
+ * agentType: input.agent_type
730
+ * });
731
+ *
732
+ * // Block if transcript shows incomplete work
733
+ * return subagentStopOutput({
734
+ * decision: 'block',
735
+ * reason: 'Please verify exploration is complete'
736
+ * });
737
+ * });
738
+ * ```
739
+ * @see https://code.claude.com/docs/en/hooks#subagentstop
740
+ */
741
+ export declare function subagentStopHook(
742
+ config: HookConfig,
743
+ handler: HookHandler<SubagentStopInput, SubagentStopOutput>
744
+ ): HookFunction<SubagentStopInput, SubagentStopOutput>;
745
+ /**
746
+ * Creates a PreCompact hook handler.
747
+ *
748
+ * PreCompact hooks fire before context compaction occurs, allowing you to:
749
+ * - Preserve important information before compaction
750
+ * - Log compaction events
751
+ * - Modify custom instructions for the compacted context
752
+ *
753
+ * **Matcher**: Matches against `trigger` ('manual', 'auto')
754
+ * @param config - Hook configuration with optional matcher and timeout
755
+ * @param handler - The handler function to execute
756
+ * @returns A hook function that can be exported as the default export
757
+ * @example
758
+ * ```typescript
759
+ * import { preCompactHook, preCompactOutput } from '@goodfoot/claude-code-hooks';
760
+ *
761
+ * // Log compaction events and preserve context
762
+ * export default preCompactHook({}, async (input, { logger }) => {
763
+ * logger.info('Context compaction triggered', {
764
+ * trigger: input.trigger,
765
+ * hasCustomInstructions: input.custom_instructions !== null
766
+ * });
767
+ *
768
+ * return preCompactOutput({
769
+ * systemMessage: 'Remember: strict mode is enabled'
770
+ * });
771
+ * });
772
+ * ```
773
+ * @example
774
+ * ```typescript
775
+ * // Only handle manual compaction
776
+ * export default preCompactHook({ matcher: 'manual' }, async (input, { logger }) => {
777
+ * logger.info('Manual compaction requested');
778
+ * return preCompactOutput({});
779
+ * });
780
+ * ```
781
+ * @see https://code.claude.com/docs/en/hooks#precompact
782
+ */
783
+ export declare function preCompactHook(
784
+ config: HookConfig,
785
+ handler: HookHandler<PreCompactInput, PreCompactOutput>
786
+ ): HookFunction<PreCompactInput, PreCompactOutput>;
787
+ /**
788
+ * Creates a PermissionRequest hook handler.
789
+ *
790
+ * PermissionRequest hooks fire when a permission prompt would be shown,
791
+ * allowing you to:
792
+ * - Auto-approve or deny tool executions
793
+ * - Implement custom permission logic
794
+ * - Modify tool inputs before approval
795
+ *
796
+ * **Matcher**: Matches against `tool_name`
797
+ *
798
+ * **Typed Overload**: When the matcher is a single known tool name, the handler
799
+ * receives automatically typed `toolInput` based on the tool type.
800
+ * @param config - Hook configuration with optional matcher and timeout
801
+ * @param handler - The handler function to execute
802
+ * @returns A hook function that can be exported as the default export
803
+ * @example
804
+ * ```typescript
805
+ * import { permissionRequestHook, permissionRequestOutput } from '@goodfoot/claude-code-hooks';
806
+ *
807
+ * // Typed overload: tool_input is automatically typed as ReadToolInput
808
+ * export default permissionRequestHook({ matcher: 'Read' }, async (input, { logger }) => {
809
+ * // input.tool_input.file_path is typed as string - no cast needed!
810
+ * if (input.tool_input.file_path.startsWith('/allowed/')) {
811
+ * logger.info('Auto-approving read in allowed directory', { filePath: input.tool_input.file_path });
812
+ * return permissionRequestOutput({
813
+ * hookSpecificOutput: { decision: { behavior: 'allow' } }
814
+ * });
815
+ * }
816
+ *
817
+ * // Fall through to normal permission prompt
818
+ * return permissionRequestOutput({});
819
+ * });
820
+ * ```
821
+ * @example
822
+ * ```typescript
823
+ * // Typed overload: tool_input is automatically typed as BashToolInput
824
+ * export default permissionRequestHook({ matcher: 'Bash' }, async (input, { logger }) => {
825
+ * // input.tool_input.command is typed as string - no cast needed!
826
+ * if (input.tool_input.command.includes('sudo')) {
827
+ * logger.warn('Denying sudo command', { command: input.tool_input.command });
828
+ * return permissionRequestOutput({
829
+ * hookSpecificOutput: {
830
+ * decision: {
831
+ * behavior: 'deny',
832
+ * message: 'sudo commands are not allowed',
833
+ * interrupt: true
834
+ * }
835
+ * }
836
+ * });
837
+ * }
838
+ *
839
+ * return permissionRequestOutput({});
840
+ * });
841
+ * ```
842
+ * @see https://code.claude.com/docs/en/hooks#permissionrequest
843
+ */
844
+ export declare function permissionRequestHook<T extends KnownToolName>(
845
+ config: TypedHookConfig<T>,
846
+ handler: HookHandler<TypedPermissionRequestInput<T>, PermissionRequestOutput>
847
+ ): HookFunction<TypedPermissionRequestInput<T>, PermissionRequestOutput>;
848
+ export declare function permissionRequestHook(
849
+ config: HookConfig,
850
+ handler: HookHandler<PermissionRequestInput, PermissionRequestOutput>
851
+ ): HookFunction<PermissionRequestInput, PermissionRequestOutput>;