@goodfoot/agent-hooks 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -0
- package/dist/agents/antigravity/events.js +42 -0
- package/dist/agents/antigravity/hooks.js +59 -0
- package/dist/agents/antigravity/index.js +28 -0
- package/dist/agents/antigravity/outputs.js +81 -0
- package/dist/agents/antigravity/transport.js +84 -0
- package/dist/agents/antigravity/types.js +11 -0
- package/dist/agents/claude-code/env.js +27 -0
- package/dist/agents/claude-code/events.js +87 -0
- package/dist/agents/claude-code/hooks.js +1000 -0
- package/dist/agents/claude-code/index.js +57 -0
- package/dist/agents/claude-code/outputs.js +579 -0
- package/dist/agents/claude-code/tool-helpers.js +840 -0
- package/dist/agents/claude-code/transport.js +254 -0
- package/dist/agents/claude-code/types.js +11 -0
- package/dist/agents/codex/cli-support.js +318 -0
- package/dist/agents/codex/constants.js +29 -0
- package/dist/agents/codex/events.js +68 -0
- package/dist/agents/codex/hooks.js +93 -0
- package/dist/agents/codex/index.js +21 -0
- package/dist/agents/codex/outputs.js +189 -0
- package/dist/agents/codex/scaffold.js +273 -0
- package/dist/agents/codex/transport.js +139 -0
- package/dist/agents/codex/types.js +1 -0
- package/dist/cli.js +1357 -0
- package/dist/constants.js +39 -0
- package/dist/core/define-hook.js +61 -0
- package/dist/core/env.js +186 -0
- package/dist/core/logger.js +508 -0
- package/dist/core/stdin.js +38 -0
- package/dist/core/transport.js +231 -0
- package/dist/core/types.js +10 -0
- package/dist/index.js +17 -0
- package/dist/scaffold.js +512 -0
- package/package.json +61 -0
- package/types/agents/antigravity/events.d.ts +50 -0
- package/types/agents/antigravity/hooks.d.ts +76 -0
- package/types/agents/antigravity/index.d.ts +27 -0
- package/types/agents/antigravity/outputs.d.ts +107 -0
- package/types/agents/antigravity/transport.d.ts +47 -0
- package/types/agents/antigravity/types.d.ts +58 -0
- package/types/agents/claude-code/env.d.ts +27 -0
- package/types/agents/claude-code/events.d.ts +69 -0
- package/types/agents/claude-code/hooks.d.ts +1182 -0
- package/types/agents/claude-code/index.d.ts +23 -0
- package/types/agents/claude-code/outputs.d.ts +1160 -0
- package/types/agents/claude-code/tool-helpers.d.ts +807 -0
- package/types/agents/claude-code/transport.d.ts +119 -0
- package/types/agents/claude-code/types.d.ts +557 -0
- package/types/agents/codex/cli-support.d.ts +105 -0
- package/types/agents/codex/constants.d.ts +10 -0
- package/types/agents/codex/events.d.ts +63 -0
- package/types/agents/codex/hooks.d.ts +135 -0
- package/types/agents/codex/index.d.ts +19 -0
- package/types/agents/codex/outputs.d.ts +190 -0
- package/types/agents/codex/scaffold.d.ts +14 -0
- package/types/agents/codex/transport.d.ts +74 -0
- package/types/agents/codex/types.d.ts +131 -0
- package/types/cli.d.ts +315 -0
- package/types/constants.d.ts +9 -0
- package/types/core/define-hook.d.ts +39 -0
- package/types/core/env.d.ts +150 -0
- package/types/core/logger.d.ts +476 -0
- package/types/core/stdin.d.ts +21 -0
- package/types/core/transport.d.ts +131 -0
- package/types/core/types.d.ts +137 -0
- package/types/index.d.ts +18 -0
- package/types/scaffold.d.ts +46 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger system for agent 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 the host
|
|
7
|
+
* CLI).
|
|
8
|
+
* @module
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { logger } from '@goodfoot/agent-hooks';
|
|
12
|
+
*
|
|
13
|
+
* // Subscribe to log events
|
|
14
|
+
* const unsubscribe = logger.on('error', (event) => {
|
|
15
|
+
* console.error(`Error in ${event.hookType}: ${event.message}`);
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // Later, clean up
|
|
19
|
+
* unsubscribe();
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Available log levels.
|
|
24
|
+
*
|
|
25
|
+
* | Level | Severity | Use Case |
|
|
26
|
+
* |-------|----------|----------|
|
|
27
|
+
* | `debug` | Lowest | Detailed debugging information |
|
|
28
|
+
* | `info` | Low | General operational events |
|
|
29
|
+
* | `warn` | Medium | Warning conditions that may indicate issues |
|
|
30
|
+
* | `error` | High | Error conditions requiring attention |
|
|
31
|
+
*/
|
|
32
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
33
|
+
/**
|
|
34
|
+
* All log levels in order of severity (lowest to highest).
|
|
35
|
+
*/
|
|
36
|
+
export declare const LOG_LEVELS: readonly ["debug", "info", "warn", "error"];
|
|
37
|
+
/**
|
|
38
|
+
* A structured log event emitted by the logger.
|
|
39
|
+
*
|
|
40
|
+
* Log events contain contextual information about what happened during hook
|
|
41
|
+
* execution, making them suitable for debugging, monitoring, and analytics.
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* // Example log event
|
|
45
|
+
* const event: LogEvent = {
|
|
46
|
+
* timestamp: '2024-01-15T10:30:00.000Z',
|
|
47
|
+
* level: 'warn',
|
|
48
|
+
* hookType: 'PreToolUse',
|
|
49
|
+
* message: 'Blocking dangerous command',
|
|
50
|
+
* input: { tool_name: 'Bash', tool_input: { command: 'rm -rf /' } }
|
|
51
|
+
* };
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export interface LogEvent {
|
|
55
|
+
/**
|
|
56
|
+
* ISO 8601 timestamp of when the event occurred.
|
|
57
|
+
* @example '2024-01-15T10:30:00.000Z'
|
|
58
|
+
*/
|
|
59
|
+
timestamp: string;
|
|
60
|
+
/**
|
|
61
|
+
* Severity level of the log event.
|
|
62
|
+
*/
|
|
63
|
+
level: LogLevel;
|
|
64
|
+
/**
|
|
65
|
+
* Agent event name of the hook that generated this event.
|
|
66
|
+
* May be undefined for events outside hook context.
|
|
67
|
+
*/
|
|
68
|
+
hookType?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Human-readable description of what happened.
|
|
71
|
+
*/
|
|
72
|
+
message: string;
|
|
73
|
+
/**
|
|
74
|
+
* Hook input data at the time of logging.
|
|
75
|
+
* Useful for debugging and reproducing issues.
|
|
76
|
+
*/
|
|
77
|
+
input?: Record<string, unknown>;
|
|
78
|
+
/**
|
|
79
|
+
* Error information if this event represents an error.
|
|
80
|
+
* Contains structured error details for analysis.
|
|
81
|
+
*/
|
|
82
|
+
error?: LogEventError;
|
|
83
|
+
/**
|
|
84
|
+
* Additional context data provided by the caller.
|
|
85
|
+
* Can contain arbitrary metadata relevant to the event.
|
|
86
|
+
*/
|
|
87
|
+
context?: Record<string, unknown>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Structured error information within a log event.
|
|
91
|
+
*/
|
|
92
|
+
export interface LogEventError {
|
|
93
|
+
/**
|
|
94
|
+
* Error name (e.g., 'TypeError', 'ValidationError').
|
|
95
|
+
*/
|
|
96
|
+
name: string;
|
|
97
|
+
/**
|
|
98
|
+
* Error message describing what went wrong.
|
|
99
|
+
*/
|
|
100
|
+
message: string;
|
|
101
|
+
/**
|
|
102
|
+
* Stack trace if available.
|
|
103
|
+
*/
|
|
104
|
+
stack?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Error cause chain if the error was wrapped.
|
|
107
|
+
*/
|
|
108
|
+
cause?: LogEventError;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Handler function invoked when a log event is emitted.
|
|
112
|
+
*
|
|
113
|
+
* Handlers receive log events and can process them in any way - forwarding
|
|
114
|
+
* to external services, writing to custom formats, or aggregating metrics.
|
|
115
|
+
* @param event - The log event to handle
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* // Forward to external logging service
|
|
119
|
+
* const handler: LogEventHandler = (event) => {
|
|
120
|
+
* externalLogger.log({
|
|
121
|
+
* level: event.level,
|
|
122
|
+
* message: event.message,
|
|
123
|
+
* metadata: { hookType: event.hookType }
|
|
124
|
+
* });
|
|
125
|
+
* };
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export type LogEventHandler = (event: LogEvent) => void;
|
|
129
|
+
/**
|
|
130
|
+
* Function to unsubscribe a log event handler.
|
|
131
|
+
*
|
|
132
|
+
* Call this function to stop receiving log events. Always call unsubscribe
|
|
133
|
+
* when the handler is no longer needed to prevent memory leaks.
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const unsubscribe = logger.on('error', handleError);
|
|
137
|
+
* // ... later
|
|
138
|
+
* unsubscribe(); // Stop receiving events
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export type Unsubscribe = () => void;
|
|
142
|
+
/**
|
|
143
|
+
* Configuration options for the Logger.
|
|
144
|
+
*/
|
|
145
|
+
export interface LoggerConfig {
|
|
146
|
+
/**
|
|
147
|
+
* Path to the log file for file output.
|
|
148
|
+
* If not set, file logging is disabled.
|
|
149
|
+
*/
|
|
150
|
+
logFilePath?: string;
|
|
151
|
+
/**
|
|
152
|
+
* Name of the environment variable to read for the log file path.
|
|
153
|
+
* When set, the Logger reads `process.env[logEnvVar]` at construction time.
|
|
154
|
+
* Ignored if `logFilePath` is also provided.
|
|
155
|
+
* Defaults to `'AGENT_HOOKS_LOG_FILE'` for the exported singleton.
|
|
156
|
+
*/
|
|
157
|
+
logEnvVar?: string;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Logger for agent hooks with event subscription and file output.
|
|
161
|
+
*
|
|
162
|
+
* ## Key Behaviors
|
|
163
|
+
*
|
|
164
|
+
* | Configuration | Behavior |
|
|
165
|
+
* |--------------|----------|
|
|
166
|
+
* | No config (default) | **Silent** - no output anywhere |
|
|
167
|
+
* | `AGENT_HOOKS_LOG_FILE` env var | Append JSON lines to file |
|
|
168
|
+
* | `.on(level, handler)` registered | Events delivered to handlers only |
|
|
169
|
+
* | Multiple destinations | All destinations receive events |
|
|
170
|
+
*
|
|
171
|
+
* ## Important Notes
|
|
172
|
+
*
|
|
173
|
+
* - **Never outputs to stdout** (reserved for JSON hook response)
|
|
174
|
+
* - **Never outputs to stderr** (may interfere with host CLI error handling)
|
|
175
|
+
* - File output uses JSON Lines format for easy parsing
|
|
176
|
+
* - `.on(level, handler)` returns an unsubscribe function
|
|
177
|
+
* @example
|
|
178
|
+
* ```typescript
|
|
179
|
+
* import { logger } from '@goodfoot/agent-hooks';
|
|
180
|
+
*
|
|
181
|
+
* // Subscribe to events at specific level
|
|
182
|
+
* logger.on('warn', (event) => {
|
|
183
|
+
* sendAlert(event.message);
|
|
184
|
+
* });
|
|
185
|
+
*
|
|
186
|
+
* // Log within a hook handler
|
|
187
|
+
* export default preToolUseHook({ matcher: 'Bash' }, async (input, { logger }) => {
|
|
188
|
+
* logger.warn('About to validate Bash command');
|
|
189
|
+
* return preToolUseOutput({ allow: true });
|
|
190
|
+
* });
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
export declare class Logger {
|
|
194
|
+
/**
|
|
195
|
+
* Registered event handlers by log level.
|
|
196
|
+
*/
|
|
197
|
+
private handlers;
|
|
198
|
+
/**
|
|
199
|
+
* File descriptor for log file output.
|
|
200
|
+
* Lazily initialized on first write.
|
|
201
|
+
*/
|
|
202
|
+
private logFileFd;
|
|
203
|
+
/**
|
|
204
|
+
* Path to the log file, if configured.
|
|
205
|
+
*/
|
|
206
|
+
private logFilePath;
|
|
207
|
+
/**
|
|
208
|
+
* Whether file initialization has been attempted.
|
|
209
|
+
*/
|
|
210
|
+
private fileInitialized;
|
|
211
|
+
/**
|
|
212
|
+
* Current hook context for enriching log events.
|
|
213
|
+
*/
|
|
214
|
+
private currentHookType;
|
|
215
|
+
/**
|
|
216
|
+
* Current hook input for enriching log events.
|
|
217
|
+
*/
|
|
218
|
+
private currentInput;
|
|
219
|
+
/**
|
|
220
|
+
* Creates a new Logger instance.
|
|
221
|
+
*
|
|
222
|
+
* Typically you should use the exported `logger` singleton rather than
|
|
223
|
+
* creating new instances.
|
|
224
|
+
* @param config - Optional configuration
|
|
225
|
+
* @example
|
|
226
|
+
* ```typescript
|
|
227
|
+
* // Use singleton (recommended)
|
|
228
|
+
* import { logger } from '@goodfoot/agent-hooks';
|
|
229
|
+
*
|
|
230
|
+
* // Or create custom instance
|
|
231
|
+
* const customLogger = new Logger({ logFilePath: '/var/log/hooks.log' });
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
constructor(config?: LoggerConfig);
|
|
235
|
+
/**
|
|
236
|
+
* Logs a debug message.
|
|
237
|
+
*
|
|
238
|
+
* Use for detailed debugging information that is typically only useful
|
|
239
|
+
* during development or troubleshooting.
|
|
240
|
+
* @param message - The debug message
|
|
241
|
+
* @param context - Optional additional context
|
|
242
|
+
* @example
|
|
243
|
+
* ```typescript
|
|
244
|
+
* logger.debug('Processing tool input', { toolName: 'Bash', inputSize: 256 });
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
debug(message: string, context?: Record<string, unknown>): void;
|
|
248
|
+
/**
|
|
249
|
+
* Logs an info message.
|
|
250
|
+
*
|
|
251
|
+
* Use for general operational events like hook invocations, successful
|
|
252
|
+
* completions, or state changes.
|
|
253
|
+
* @param message - The info message
|
|
254
|
+
* @param context - Optional additional context
|
|
255
|
+
* @example
|
|
256
|
+
* ```typescript
|
|
257
|
+
* logger.info('Session started', { source: 'startup', sessionId: 'abc123' });
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
info(message: string, context?: Record<string, unknown>): void;
|
|
261
|
+
/**
|
|
262
|
+
* Logs a warning message.
|
|
263
|
+
*
|
|
264
|
+
* Use for conditions that may indicate issues but don't prevent
|
|
265
|
+
* operation, such as deprecated patterns or performance concerns.
|
|
266
|
+
* @param message - The warning message
|
|
267
|
+
* @param context - Optional additional context
|
|
268
|
+
* @example
|
|
269
|
+
* ```typescript
|
|
270
|
+
* logger.warn('Deprecated hook pattern detected', { pattern: 'legacyMatcher' });
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
warn(message: string, context?: Record<string, unknown>): void;
|
|
274
|
+
/**
|
|
275
|
+
* Logs an error message.
|
|
276
|
+
*
|
|
277
|
+
* Use for error conditions that require attention but were handled
|
|
278
|
+
* gracefully. For exceptions, prefer {@link logError}.
|
|
279
|
+
* @param message - The error message
|
|
280
|
+
* @param context - Optional additional context
|
|
281
|
+
* @example
|
|
282
|
+
* ```typescript
|
|
283
|
+
* logger.error('Failed to validate tool input', { toolName: 'Bash', reason: 'empty command' });
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
error(message: string, context?: Record<string, unknown>): void;
|
|
287
|
+
/**
|
|
288
|
+
* Logs a structured error with full error details.
|
|
289
|
+
*
|
|
290
|
+
* Use this method when logging caught exceptions to capture the full
|
|
291
|
+
* error context including name, message, stack trace, and cause chain.
|
|
292
|
+
* @param error - The error to log
|
|
293
|
+
* @param message - Human-readable description of what failed
|
|
294
|
+
* @param context - Optional additional context
|
|
295
|
+
* @example
|
|
296
|
+
* ```typescript
|
|
297
|
+
* try {
|
|
298
|
+
* await dangerousOperation();
|
|
299
|
+
* } catch (err) {
|
|
300
|
+
* logger.logError(err, 'Failed to execute dangerous operation', {
|
|
301
|
+
* operation: 'delete',
|
|
302
|
+
* target: '/important/file.txt'
|
|
303
|
+
* });
|
|
304
|
+
* }
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
logError(error: unknown, message: string, context?: Record<string, unknown>): void;
|
|
308
|
+
/**
|
|
309
|
+
* Subscribes a handler to log events at the specified level.
|
|
310
|
+
*
|
|
311
|
+
* The handler will be called for every log event at the specified level.
|
|
312
|
+
* Returns an unsubscribe function that should be called when the handler
|
|
313
|
+
* is no longer needed.
|
|
314
|
+
* @param level - The log level to subscribe to
|
|
315
|
+
* @param handler - The handler function to call for each event
|
|
316
|
+
* @returns A function to unsubscribe the handler
|
|
317
|
+
* @example
|
|
318
|
+
* ```typescript
|
|
319
|
+
* // Subscribe to error events
|
|
320
|
+
* const unsubscribe = logger.on('error', (event) => {
|
|
321
|
+
* console.error(`[${event.hookType}] ${event.message}`);
|
|
322
|
+
* if (event.error) {
|
|
323
|
+
* console.error(event.error.stack);
|
|
324
|
+
* }
|
|
325
|
+
* });
|
|
326
|
+
*
|
|
327
|
+
* // Later, clean up
|
|
328
|
+
* unsubscribe();
|
|
329
|
+
* ```
|
|
330
|
+
* @example
|
|
331
|
+
* ```typescript
|
|
332
|
+
* // Forward to external logging library
|
|
333
|
+
* import pino from 'pino';
|
|
334
|
+
* const pinoLogger = pino();
|
|
335
|
+
*
|
|
336
|
+
* logger.on('info', (event) => pinoLogger.info(event, event.message));
|
|
337
|
+
* logger.on('warn', (event) => pinoLogger.warn(event, event.message));
|
|
338
|
+
* logger.on('error', (event) => pinoLogger.error(event, event.message));
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
on(level: LogLevel, handler: LogEventHandler): Unsubscribe;
|
|
342
|
+
/**
|
|
343
|
+
* Sets the current hook context for enriching log events.
|
|
344
|
+
*
|
|
345
|
+
* This is called internally by the runtime before invoking hook handlers.
|
|
346
|
+
* You typically don't need to call this directly.
|
|
347
|
+
* @param hookType - The agent event name being executed
|
|
348
|
+
* @param input - The hook input data
|
|
349
|
+
* @internal
|
|
350
|
+
*/
|
|
351
|
+
setContext(hookType: string | undefined, input: Record<string, unknown> | undefined): void;
|
|
352
|
+
/**
|
|
353
|
+
* Clears the current hook context.
|
|
354
|
+
*
|
|
355
|
+
* Called internally by the runtime after hook execution completes.
|
|
356
|
+
* @internal
|
|
357
|
+
*/
|
|
358
|
+
clearContext(): void;
|
|
359
|
+
/**
|
|
360
|
+
* Configures the log file path at runtime.
|
|
361
|
+
*
|
|
362
|
+
* Call this to enable or change file logging. Setting to `null` disables
|
|
363
|
+
* file logging (but doesn't close existing file handle immediately).
|
|
364
|
+
* @param filePath - Path to the log file, or null to disable
|
|
365
|
+
* @example
|
|
366
|
+
* ```typescript
|
|
367
|
+
* // Enable file logging at runtime
|
|
368
|
+
* logger.setLogFile('/var/log/agent-hooks.log');
|
|
369
|
+
*
|
|
370
|
+
* // Disable file logging
|
|
371
|
+
* logger.setLogFile(null);
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
setLogFile(filePath: string | null): void;
|
|
375
|
+
/**
|
|
376
|
+
* Closes all resources held by the logger.
|
|
377
|
+
*
|
|
378
|
+
* Call this during graceful shutdown to ensure all log data is flushed.
|
|
379
|
+
* @example
|
|
380
|
+
* ```typescript
|
|
381
|
+
* process.on('exit', () => {
|
|
382
|
+
* logger.close();
|
|
383
|
+
* });
|
|
384
|
+
* ```
|
|
385
|
+
*/
|
|
386
|
+
close(): void;
|
|
387
|
+
/**
|
|
388
|
+
* Checks if there are any active handlers or destinations.
|
|
389
|
+
*
|
|
390
|
+
* Returns true if any handlers are registered or file logging is enabled.
|
|
391
|
+
* @returns Whether the logger has any active output destinations
|
|
392
|
+
*/
|
|
393
|
+
hasDestinations(): boolean;
|
|
394
|
+
/**
|
|
395
|
+
* Emits a log event.
|
|
396
|
+
* @param level - The severity level of the event
|
|
397
|
+
* @param message - The log message
|
|
398
|
+
* @param context - Optional additional context data
|
|
399
|
+
*/
|
|
400
|
+
private emit;
|
|
401
|
+
/**
|
|
402
|
+
* Delivers an event to all registered destinations.
|
|
403
|
+
* @param event - The log event to deliver
|
|
404
|
+
*/
|
|
405
|
+
private deliverEvent;
|
|
406
|
+
/**
|
|
407
|
+
* Writes an event to the log file.
|
|
408
|
+
* @param event - The log event to write
|
|
409
|
+
*/
|
|
410
|
+
private writeToFile;
|
|
411
|
+
/**
|
|
412
|
+
* Initializes the log file for writing.
|
|
413
|
+
*/
|
|
414
|
+
private initializeFile;
|
|
415
|
+
/**
|
|
416
|
+
* Extracts structured error information from an unknown error.
|
|
417
|
+
* @param error - The error to extract information from
|
|
418
|
+
* @returns Structured error information
|
|
419
|
+
*/
|
|
420
|
+
private extractErrorInfo;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Global logger instance for agent hooks.
|
|
424
|
+
*
|
|
425
|
+
* Use this singleton for all logging within hooks. The logger is configured
|
|
426
|
+
* via environment variables and supports event subscription for custom
|
|
427
|
+
* destinations.
|
|
428
|
+
*
|
|
429
|
+
* ## Configuration
|
|
430
|
+
*
|
|
431
|
+
* | Environment Variable | Description |
|
|
432
|
+
* |---------------------|-------------|
|
|
433
|
+
* | `AGENT_HOOKS_LOG_FILE` | Path to log file (JSON Lines format) |
|
|
434
|
+
*
|
|
435
|
+
* ## Usage in Hooks
|
|
436
|
+
*
|
|
437
|
+
* The logger is passed to hook handlers via context for convenience:
|
|
438
|
+
*
|
|
439
|
+
* ```typescript
|
|
440
|
+
* export default preToolUseHook({ matcher: 'Bash' }, async (input, { logger }) => {
|
|
441
|
+
* logger.warn('Validating Bash command');
|
|
442
|
+
* return preToolUseOutput({ allow: true });
|
|
443
|
+
* });
|
|
444
|
+
* ```
|
|
445
|
+
*
|
|
446
|
+
* ## External Integration
|
|
447
|
+
*
|
|
448
|
+
* Subscribe to events to forward logs to external systems:
|
|
449
|
+
*
|
|
450
|
+
* ```typescript
|
|
451
|
+
* import { logger } from '@goodfoot/agent-hooks';
|
|
452
|
+
* import pino from 'pino';
|
|
453
|
+
*
|
|
454
|
+
* const pinoLogger = pino({ level: 'debug' });
|
|
455
|
+
*
|
|
456
|
+
* logger.on('debug', (event) => pinoLogger.debug(event, event.message));
|
|
457
|
+
* logger.on('info', (event) => pinoLogger.info(event, event.message));
|
|
458
|
+
* logger.on('warn', (event) => pinoLogger.warn(event, event.message));
|
|
459
|
+
* logger.on('error', (event) => pinoLogger.error(event, event.message));
|
|
460
|
+
* ```
|
|
461
|
+
* @example
|
|
462
|
+
* ```typescript
|
|
463
|
+
* // Direct usage
|
|
464
|
+
* import { logger } from '@goodfoot/agent-hooks';
|
|
465
|
+
*
|
|
466
|
+
* logger.info('Starting operation');
|
|
467
|
+
* logger.warn('Resource limit approaching', { usage: 0.9 });
|
|
468
|
+
*
|
|
469
|
+
* try {
|
|
470
|
+
* await riskyOperation();
|
|
471
|
+
* } catch (err) {
|
|
472
|
+
* logger.logError(err, 'Risky operation failed');
|
|
473
|
+
* }
|
|
474
|
+
* ```
|
|
475
|
+
*/
|
|
476
|
+
export declare const logger: Logger;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stdin acquisition and JSON parsing for agent hooks.
|
|
3
|
+
*
|
|
4
|
+
* Both source runtimes (`claude-code-hooks`, `codex-hooks`) read stdin and
|
|
5
|
+
* parse JSON identically; this module is that shared logic. Wire rules for
|
|
6
|
+
* what a parse failure *means* belong to each agent's transport — core only
|
|
7
|
+
* classifies the outcome.
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Reads all data from stdin.
|
|
12
|
+
* @returns Promise resolving to the complete stdin content
|
|
13
|
+
*/
|
|
14
|
+
export declare function readStdin(): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Parses stdin JSON input.
|
|
17
|
+
* @param stdinContent - Raw stdin content
|
|
18
|
+
* @returns Parsed input in the host CLI's wire format
|
|
19
|
+
* @throws SyntaxError if JSON is malformed
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseStdinJson(stdinContent: string): unknown;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport abstraction and the shared driver.
|
|
3
|
+
*
|
|
4
|
+
* Core owns the try/catch skeleton — stdin read, JSON parse, handler
|
|
5
|
+
* invocation, outcome classification, buffered single-write emission, process
|
|
6
|
+
* exit — and has **no knowledge of exit codes or stdout policy**: every wire
|
|
7
|
+
* decision is delegated to the agent's {@link Transport.finalize}, a pure
|
|
8
|
+
* mapping from {@link HookOutcome} to `{ stdout?, stderr?, exitCode }`. Agent
|
|
9
|
+
* transports are data-heavy adapters; they must not re-implement driver logic.
|
|
10
|
+
* @module
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const transport: Transport<MyOutput> = {
|
|
14
|
+
* finalize: (outcome) => {
|
|
15
|
+
* if (outcome.kind === "response") {
|
|
16
|
+
* return { stdout: JSON.stringify(outcome.output ?? {}), exitCode: 0 };
|
|
17
|
+
* }
|
|
18
|
+
* return { stderr: "blocked", exitCode: 2 };
|
|
19
|
+
* },
|
|
20
|
+
* };
|
|
21
|
+
*
|
|
22
|
+
* await drive(transport, myHook);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import type { HookContext, HookErrorPhase, HookFunction } from "./types.js";
|
|
26
|
+
/**
|
|
27
|
+
* Error thrown by a hook handler to signal an intentional block.
|
|
28
|
+
*
|
|
29
|
+
* `drive()` classifies a caught {@link HookBlockError} **before** consulting
|
|
30
|
+
* the `unexpectedError` policy: it always routes to the `block` outcome,
|
|
31
|
+
* under every policy value, so an advisory event's in-flight block decision
|
|
32
|
+
* survives a crash mid-computation. Any other throw is subject to policy as
|
|
33
|
+
* usual. Agents may subclass or re-export this class; each agent's transport
|
|
34
|
+
* owns the wire translation of the `block` outcome.
|
|
35
|
+
*/
|
|
36
|
+
export declare class HookBlockError extends Error {
|
|
37
|
+
/**
|
|
38
|
+
* Optional structured fields carried alongside the block reason (e.g.
|
|
39
|
+
* extra wire fields the agent's translation may forward).
|
|
40
|
+
*/
|
|
41
|
+
readonly fields?: Record<string, unknown>;
|
|
42
|
+
/**
|
|
43
|
+
* @param message - The block reason; becomes the error `message`.
|
|
44
|
+
* @param fields - Optional additional structured fields.
|
|
45
|
+
*/
|
|
46
|
+
constructor(message: string, fields?: Record<string, unknown>);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The classified result of one hook invocation, prior to wire mapping.
|
|
50
|
+
*
|
|
51
|
+
* Discriminated by `kind`; core produces exactly one outcome per invocation
|
|
52
|
+
* and hands it to {@link Transport.finalize} exactly once.
|
|
53
|
+
* @template TOutput - The agent's output type for the event.
|
|
54
|
+
*/
|
|
55
|
+
export type HookOutcome<TOutput> =
|
|
56
|
+
/** The handler returned normally (`output: undefined` = empty response). */
|
|
57
|
+
{
|
|
58
|
+
kind: "response";
|
|
59
|
+
output: TOutput | undefined;
|
|
60
|
+
}
|
|
61
|
+
/** The handler threw a {@link HookBlockError}. */
|
|
62
|
+
| {
|
|
63
|
+
kind: "block";
|
|
64
|
+
error: HookBlockError;
|
|
65
|
+
}
|
|
66
|
+
/** The handler (or a pre-handler phase) failed unexpectedly. */
|
|
67
|
+
| {
|
|
68
|
+
kind: "handlerError";
|
|
69
|
+
error: unknown;
|
|
70
|
+
phase: HookErrorPhase;
|
|
71
|
+
}
|
|
72
|
+
/** The handler returned a plain-text-protocol payload, per the transport's predicate. */
|
|
73
|
+
| {
|
|
74
|
+
kind: "rawStdout";
|
|
75
|
+
stdout: string;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* The wire-level result produced by a transport's finalize mapping.
|
|
79
|
+
*/
|
|
80
|
+
export interface FinalizedResult {
|
|
81
|
+
/** Text to write to stdout, or undefined to write nothing. */
|
|
82
|
+
stdout?: string;
|
|
83
|
+
/** Text to write to stderr, or undefined to write nothing. */
|
|
84
|
+
stderr?: string;
|
|
85
|
+
/** Process exit code. */
|
|
86
|
+
exitCode: number;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Per-agent wire adapter.
|
|
90
|
+
*
|
|
91
|
+
* Implementations own all exit-code and stdout/stderr policy decisions:
|
|
92
|
+
* `finalize` is a pure mapping from the classified outcome to the streams and
|
|
93
|
+
* exit code the host CLI should observe. It must not throw and must not
|
|
94
|
+
* perform I/O; `drive()` owns writing and exiting.
|
|
95
|
+
*
|
|
96
|
+
* The optional `rawStdout` predicate lets the transport declare which
|
|
97
|
+
* returned outputs carry a plain-text protocol payload (e.g. worktree events
|
|
98
|
+
* whose stdout is read verbatim by the host): when it returns a string for a
|
|
99
|
+
* normally-returned output, `drive()` classifies the invocation as
|
|
100
|
+
* `rawStdout` instead of `response`. The returned output is never modified.
|
|
101
|
+
*
|
|
102
|
+
* @template TOutput - The agent's output type for the event.
|
|
103
|
+
*/
|
|
104
|
+
export interface Transport<TOutput> {
|
|
105
|
+
/**
|
|
106
|
+
* Purely maps a classified outcome to wire text and an exit code.
|
|
107
|
+
* Called exactly once per invocation.
|
|
108
|
+
*/
|
|
109
|
+
finalize(outcome: HookOutcome<TOutput>): FinalizedResult;
|
|
110
|
+
/**
|
|
111
|
+
* Optional plain-text-protocol detector. See {@link Transport}.
|
|
112
|
+
*/
|
|
113
|
+
rawStdout?(output: TOutput): string | undefined;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Runs one hook invocation end to end: reads stdin, parses JSON, invokes the
|
|
117
|
+
* handler, classifies the outcome, maps it once through the transport, writes
|
|
118
|
+
* the resulting streams buffered (at most one write per stream), and exits
|
|
119
|
+
* with the transport's exit code.
|
|
120
|
+
*
|
|
121
|
+
* Malformed/unreadable stdin is classified at its phase (`read`/`parse`) like
|
|
122
|
+
* any other unexpected failure and logged identically to both source
|
|
123
|
+
* runtimes; each agent's transport decides the wire shape for that
|
|
124
|
+
* classification.
|
|
125
|
+
*
|
|
126
|
+
* @param transport - The agent's wire adapter.
|
|
127
|
+
* @param hookFn - The hook function to execute (from a factory built on
|
|
128
|
+
* {@link defineHook}).
|
|
129
|
+
* @returns Never resolves normally: the process exits inside this function.
|
|
130
|
+
*/
|
|
131
|
+
export declare function drive<TInput, TOutput>(transport: Transport<TOutput>, hookFn: HookFunction<TInput, TOutput, HookContext>): Promise<void>;
|