@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,471 @@
1
+ /**
2
+ * Logger system for Claude Code hooks.
3
+ *
4
+ * Provides structured logging with event subscription and optional file output.
5
+ * The logger is **silent by default** to avoid interfering with hook protocol
6
+ * (stdout is reserved for JSON responses, stderr may conflict with Claude Code).
7
+ * @module
8
+ * @example
9
+ * ```typescript
10
+ * import { logger } from '@goodfoot/claude-code-hooks';
11
+ *
12
+ * // Subscribe to log events
13
+ * const unsubscribe = logger.on('error', (event) => {
14
+ * console.error(`Error in ${event.hookType}: ${event.message}`);
15
+ * });
16
+ *
17
+ * // Later, clean up
18
+ * unsubscribe();
19
+ * ```
20
+ * @see https://code.claude.com/docs/en/hooks
21
+ */
22
+ import type { HookEventName, HookInput } from './inputs.js';
23
+ /**
24
+ * Available log levels.
25
+ *
26
+ * | Level | Severity | Use Case |
27
+ * |-------|----------|----------|
28
+ * | `debug` | Lowest | Detailed debugging information |
29
+ * | `info` | Low | General operational events |
30
+ * | `warn` | Medium | Warning conditions that may indicate issues |
31
+ * | `error` | High | Error conditions requiring attention |
32
+ */
33
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
34
+ /**
35
+ * All log levels in order of severity (lowest to highest).
36
+ */
37
+ export declare const LOG_LEVELS: readonly ['debug', 'info', 'warn', 'error'];
38
+ /**
39
+ * A structured log event emitted by the logger.
40
+ *
41
+ * Log events contain contextual information about what happened during hook
42
+ * execution, making them suitable for debugging, monitoring, and analytics.
43
+ * @example
44
+ * ```typescript
45
+ * // Example log event
46
+ * const event: LogEvent = {
47
+ * timestamp: '2024-01-15T10:30:00.000Z',
48
+ * level: 'warn',
49
+ * hookType: 'PreToolUse',
50
+ * message: 'Blocking dangerous command',
51
+ * input: { tool_name: 'Bash', tool_input: { command: 'rm -rf /' } }
52
+ * };
53
+ * ```
54
+ */
55
+ export interface LogEvent {
56
+ /**
57
+ * ISO 8601 timestamp of when the event occurred.
58
+ * @example '2024-01-15T10:30:00.000Z'
59
+ */
60
+ timestamp: string;
61
+ /**
62
+ * Severity level of the log event.
63
+ */
64
+ level: LogLevel;
65
+ /**
66
+ * Type of hook that generated this event.
67
+ * May be undefined for events outside hook context.
68
+ */
69
+ hookType?: HookEventName;
70
+ /**
71
+ * Human-readable description of what happened.
72
+ */
73
+ message: string;
74
+ /**
75
+ * Hook input data at the time of logging.
76
+ * Useful for debugging and reproducing issues.
77
+ */
78
+ input?: Partial<HookInput>;
79
+ /**
80
+ * Error information if this event represents an error.
81
+ * Contains structured error details for analysis.
82
+ */
83
+ error?: LogEventError;
84
+ /**
85
+ * Additional context data provided by the caller.
86
+ * Can contain arbitrary metadata relevant to the event.
87
+ */
88
+ context?: Record<string, unknown>;
89
+ }
90
+ /**
91
+ * Structured error information within a log event.
92
+ */
93
+ export interface LogEventError {
94
+ /**
95
+ * Error name (e.g., 'TypeError', 'ValidationError').
96
+ */
97
+ name: string;
98
+ /**
99
+ * Error message describing what went wrong.
100
+ */
101
+ message: string;
102
+ /**
103
+ * Stack trace if available.
104
+ */
105
+ stack?: string;
106
+ /**
107
+ * Error cause chain if the error was wrapped.
108
+ */
109
+ cause?: LogEventError;
110
+ }
111
+ /**
112
+ * Handler function invoked when a log event is emitted.
113
+ *
114
+ * Handlers receive log events and can process them in any way - forwarding
115
+ * to external services, writing to custom formats, or aggregating metrics.
116
+ * @param event - The log event to handle
117
+ * @example
118
+ * ```typescript
119
+ * // Forward to external logging service
120
+ * const handler: LogEventHandler = (event) => {
121
+ * externalLogger.log({
122
+ * level: event.level,
123
+ * message: event.message,
124
+ * metadata: { hookType: event.hookType }
125
+ * });
126
+ * };
127
+ * ```
128
+ */
129
+ export type LogEventHandler = (event: LogEvent) => void;
130
+ /**
131
+ * Function to unsubscribe a log event handler.
132
+ *
133
+ * Call this function to stop receiving log events. Always call unsubscribe
134
+ * when the handler is no longer needed to prevent memory leaks.
135
+ * @example
136
+ * ```typescript
137
+ * const unsubscribe = logger.on('error', handleError);
138
+ * // ... later
139
+ * unsubscribe(); // Stop receiving events
140
+ * ```
141
+ */
142
+ export type Unsubscribe = () => void;
143
+ /**
144
+ * Configuration options for the Logger.
145
+ */
146
+ export interface LoggerConfig {
147
+ /**
148
+ * Path to the log file for file output.
149
+ * If not set, file logging is disabled.
150
+ * Can also be set via `CLAUDE_CODE_HOOKS_LOG_FILE` environment variable.
151
+ */
152
+ logFilePath?: string;
153
+ }
154
+ /**
155
+ * Logger for Claude Code hooks with event subscription and file output.
156
+ *
157
+ * ## Key Behaviors
158
+ *
159
+ * | Configuration | Behavior |
160
+ * |--------------|----------|
161
+ * | No config (default) | **Silent** - no output anywhere |
162
+ * | `CLAUDE_CODE_HOOKS_LOG_FILE` env var | Append JSON lines to file |
163
+ * | `.on(level, handler)` registered | Events delivered to handlers only |
164
+ * | Multiple destinations | All destinations receive events |
165
+ *
166
+ * ## Important Notes
167
+ *
168
+ * - **Never outputs to stdout** (reserved for JSON hook response)
169
+ * - **Never outputs to stderr** (may interfere with Claude Code error handling)
170
+ * - File output uses JSON Lines format for easy parsing
171
+ * - `.on(level, handler)` returns an unsubscribe function
172
+ * @example
173
+ * ```typescript
174
+ * import { logger } from '@goodfoot/claude-code-hooks';
175
+ *
176
+ * // Subscribe to events at specific level
177
+ * logger.on('warn', (event) => {
178
+ * sendAlert(event.message);
179
+ * });
180
+ *
181
+ * // Log within a hook handler
182
+ * export default preToolUseHook({ matcher: 'Bash' }, async (input, { logger }) => {
183
+ * logger.warn('About to validate Bash command');
184
+ * return preToolUseOutput({ allow: true });
185
+ * });
186
+ * ```
187
+ */
188
+ export declare class Logger {
189
+ /**
190
+ * Registered event handlers by log level.
191
+ */
192
+ private handlers;
193
+ /**
194
+ * File descriptor for log file output.
195
+ * Lazily initialized on first write.
196
+ */
197
+ private logFileFd;
198
+ /**
199
+ * Path to the log file, if configured.
200
+ */
201
+ private logFilePath;
202
+ /**
203
+ * Whether file initialization has been attempted.
204
+ */
205
+ private fileInitialized;
206
+ /**
207
+ * Current hook context for enriching log events.
208
+ */
209
+ private currentHookType;
210
+ /**
211
+ * Current hook input for enriching log events.
212
+ */
213
+ private currentInput;
214
+ /**
215
+ * Creates a new Logger instance.
216
+ *
217
+ * Typically you should use the exported `logger` singleton rather than
218
+ * creating new instances.
219
+ * @param config - Optional configuration
220
+ * @example
221
+ * ```typescript
222
+ * // Use singleton (recommended)
223
+ * import { logger } from '@goodfoot/claude-code-hooks';
224
+ *
225
+ * // Or create custom instance
226
+ * const customLogger = new Logger({ logFilePath: '/var/log/hooks.log' });
227
+ * ```
228
+ */
229
+ constructor(config?: LoggerConfig);
230
+ /**
231
+ * Logs a debug message.
232
+ *
233
+ * Use for detailed debugging information that is typically only useful
234
+ * during development or troubleshooting.
235
+ * @param message - The debug message
236
+ * @param context - Optional additional context
237
+ * @example
238
+ * ```typescript
239
+ * logger.debug('Processing tool input', { toolName: 'Bash', inputSize: 256 });
240
+ * ```
241
+ */
242
+ debug(message: string, context?: Record<string, unknown>): void;
243
+ /**
244
+ * Logs an info message.
245
+ *
246
+ * Use for general operational events like hook invocations, successful
247
+ * completions, or state changes.
248
+ * @param message - The info message
249
+ * @param context - Optional additional context
250
+ * @example
251
+ * ```typescript
252
+ * logger.info('Session started', { source: 'startup', sessionId: 'abc123' });
253
+ * ```
254
+ */
255
+ info(message: string, context?: Record<string, unknown>): void;
256
+ /**
257
+ * Logs a warning message.
258
+ *
259
+ * Use for conditions that may indicate issues but don't prevent
260
+ * operation, such as deprecated patterns or performance concerns.
261
+ * @param message - The warning message
262
+ * @param context - Optional additional context
263
+ * @example
264
+ * ```typescript
265
+ * logger.warn('Deprecated hook pattern detected', { pattern: 'legacyMatcher' });
266
+ * ```
267
+ */
268
+ warn(message: string, context?: Record<string, unknown>): void;
269
+ /**
270
+ * Logs an error message.
271
+ *
272
+ * Use for error conditions that require attention but were handled
273
+ * gracefully. For exceptions, prefer {@link logError}.
274
+ * @param message - The error message
275
+ * @param context - Optional additional context
276
+ * @example
277
+ * ```typescript
278
+ * logger.error('Failed to validate tool input', { toolName: 'Bash', reason: 'empty command' });
279
+ * ```
280
+ */
281
+ error(message: string, context?: Record<string, unknown>): void;
282
+ /**
283
+ * Logs a structured error with full error details.
284
+ *
285
+ * Use this method when logging caught exceptions to capture the full
286
+ * error context including name, message, stack trace, and cause chain.
287
+ * @param error - The error to log
288
+ * @param message - Human-readable description of what failed
289
+ * @param context - Optional additional context
290
+ * @example
291
+ * ```typescript
292
+ * try {
293
+ * await dangerousOperation();
294
+ * } catch (err) {
295
+ * logger.logError(err, 'Failed to execute dangerous operation', {
296
+ * operation: 'delete',
297
+ * target: '/important/file.txt'
298
+ * });
299
+ * }
300
+ * ```
301
+ */
302
+ logError(error: unknown, message: string, context?: Record<string, unknown>): void;
303
+ /**
304
+ * Subscribes a handler to log events at the specified level.
305
+ *
306
+ * The handler will be called for every log event at the specified level.
307
+ * Returns an unsubscribe function that should be called when the handler
308
+ * is no longer needed.
309
+ * @param level - The log level to subscribe to
310
+ * @param handler - The handler function to call for each event
311
+ * @returns A function to unsubscribe the handler
312
+ * @example
313
+ * ```typescript
314
+ * // Subscribe to error events
315
+ * const unsubscribe = logger.on('error', (event) => {
316
+ * console.error(`[${event.hookType}] ${event.message}`);
317
+ * if (event.error) {
318
+ * console.error(event.error.stack);
319
+ * }
320
+ * });
321
+ *
322
+ * // Later, clean up
323
+ * unsubscribe();
324
+ * ```
325
+ * @example
326
+ * ```typescript
327
+ * // Forward to external logging library
328
+ * import pino from 'pino';
329
+ * const pinoLogger = pino();
330
+ *
331
+ * logger.on('info', (event) => pinoLogger.info(event, event.message));
332
+ * logger.on('warn', (event) => pinoLogger.warn(event, event.message));
333
+ * logger.on('error', (event) => pinoLogger.error(event, event.message));
334
+ * ```
335
+ */
336
+ on(level: LogLevel, handler: LogEventHandler): Unsubscribe;
337
+ /**
338
+ * Sets the current hook context for enriching log events.
339
+ *
340
+ * This is called internally by the runtime before invoking hook handlers.
341
+ * You typically don't need to call this directly.
342
+ * @param hookType - The type of hook being executed
343
+ * @param input - The hook input data
344
+ * @internal
345
+ */
346
+ setContext(hookType: HookEventName | undefined, input: Partial<HookInput> | undefined): void;
347
+ /**
348
+ * Clears the current hook context.
349
+ *
350
+ * Called internally by the runtime after hook execution completes.
351
+ * @internal
352
+ */
353
+ clearContext(): void;
354
+ /**
355
+ * Configures the log file path at runtime.
356
+ *
357
+ * Call this to enable or change file logging. Setting to `null` disables
358
+ * file logging (but doesn't close existing file handle immediately).
359
+ * @param filePath - Path to the log file, or null to disable
360
+ * @example
361
+ * ```typescript
362
+ * // Enable file logging at runtime
363
+ * logger.setLogFile('/var/log/claude-hooks.log');
364
+ *
365
+ * // Disable file logging
366
+ * logger.setLogFile(null);
367
+ * ```
368
+ */
369
+ setLogFile(filePath: string | null): void;
370
+ /**
371
+ * Closes all resources held by the logger.
372
+ *
373
+ * Call this during graceful shutdown to ensure all log data is flushed.
374
+ * @example
375
+ * ```typescript
376
+ * process.on('exit', () => {
377
+ * logger.close();
378
+ * });
379
+ * ```
380
+ */
381
+ close(): void;
382
+ /**
383
+ * Checks if there are any active handlers or destinations.
384
+ *
385
+ * Returns true if any handlers are registered or file logging is enabled.
386
+ * @returns Whether the logger has any active output destinations
387
+ */
388
+ hasDestinations(): boolean;
389
+ /**
390
+ * Emits a log event.
391
+ * @param level - The severity level of the event
392
+ * @param message - The log message
393
+ * @param context - Optional additional context data
394
+ */
395
+ private emit;
396
+ /**
397
+ * Delivers an event to all registered destinations.
398
+ * @param event - The log event to deliver
399
+ */
400
+ private deliverEvent;
401
+ /**
402
+ * Writes an event to the log file.
403
+ * @param event - The log event to write
404
+ */
405
+ private writeToFile;
406
+ /**
407
+ * Initializes the log file for writing.
408
+ */
409
+ private initializeFile;
410
+ /**
411
+ * Extracts structured error information from an unknown error.
412
+ * @param error - The error to extract information from
413
+ * @returns Structured error information
414
+ */
415
+ private extractErrorInfo;
416
+ }
417
+ /**
418
+ * Global logger instance for Claude Code hooks.
419
+ *
420
+ * Use this singleton for all logging within hooks. The logger is configured
421
+ * via environment variables and supports event subscription for custom
422
+ * destinations.
423
+ *
424
+ * ## Configuration
425
+ *
426
+ * | Environment Variable | Description |
427
+ * |---------------------|-------------|
428
+ * | `CLAUDE_CODE_HOOKS_LOG_FILE` | Path to log file (JSON Lines format) |
429
+ *
430
+ * ## Usage in Hooks
431
+ *
432
+ * The logger is passed to hook handlers via context for convenience:
433
+ *
434
+ * ```typescript
435
+ * export default preToolUseHook({ matcher: 'Bash' }, async (input, { logger }) => {
436
+ * logger.warn('Validating Bash command');
437
+ * return preToolUseOutput({ allow: true });
438
+ * });
439
+ * ```
440
+ *
441
+ * ## External Integration
442
+ *
443
+ * Subscribe to events to forward logs to external systems:
444
+ *
445
+ * ```typescript
446
+ * import { logger } from '@goodfoot/claude-code-hooks';
447
+ * import pino from 'pino';
448
+ *
449
+ * const pinoLogger = pino({ level: 'debug' });
450
+ *
451
+ * logger.on('debug', (event) => pinoLogger.debug(event, event.message));
452
+ * logger.on('info', (event) => pinoLogger.info(event, event.message));
453
+ * logger.on('warn', (event) => pinoLogger.warn(event, event.message));
454
+ * logger.on('error', (event) => pinoLogger.error(event, event.message));
455
+ * ```
456
+ * @example
457
+ * ```typescript
458
+ * // Direct usage
459
+ * import { logger } from '@goodfoot/claude-code-hooks';
460
+ *
461
+ * logger.info('Starting operation');
462
+ * logger.warn('Resource limit approaching', { usage: 0.9 });
463
+ *
464
+ * try {
465
+ * await riskyOperation();
466
+ * } catch (err) {
467
+ * logger.logError(err, 'Risky operation failed');
468
+ * }
469
+ * ```
470
+ */
471
+ export declare const logger: Logger;