@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.
- package/LICENSE +21 -0
- package/README.md +317 -0
- package/dist/cli.js +914 -0
- package/dist/constants.js +21 -0
- package/dist/env.js +188 -0
- package/dist/hooks.js +391 -0
- package/dist/index.js +77 -0
- package/dist/inputs.js +35 -0
- package/dist/logger.js +494 -0
- package/dist/outputs.js +282 -0
- package/dist/runtime.js +222 -0
- package/dist/scaffold.js +466 -0
- package/dist/tool-helpers.js +366 -0
- package/dist/tool-inputs.js +21 -0
- package/package.json +68 -0
- package/types/cli.d.ts +281 -0
- package/types/constants.d.ts +9 -0
- package/types/env.d.ts +150 -0
- package/types/hooks.d.ts +851 -0
- package/types/index.d.ts +137 -0
- package/types/inputs.d.ts +601 -0
- package/types/logger.d.ts +471 -0
- package/types/outputs.d.ts +643 -0
- package/types/runtime.d.ts +75 -0
- package/types/scaffold.d.ts +46 -0
- package/types/tool-helpers.d.ts +336 -0
- package/types/tool-inputs.d.ts +228 -0
package/dist/outputs.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output types and builders for Claude Code hooks.
|
|
3
|
+
*
|
|
4
|
+
* Provides type-safe output builder functions for all 12 hook types. Each builder
|
|
5
|
+
* accepts options that match the wire format expected by Claude Code, with types
|
|
6
|
+
* derived from the Claude Agent SDK's `SyncHookJSONOutput` type.
|
|
7
|
+
* @see https://code.claude.com/docs/en/hooks
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Exit Code Constants
|
|
12
|
+
// ============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* Exit codes used by Claude Code hooks.
|
|
15
|
+
*
|
|
16
|
+
* | Exit Code | Name | When Used | Claude Code Behavior |
|
|
17
|
+
* |-----------|------|-----------|---------------------|
|
|
18
|
+
* | 0 | Success | Handler returns normally | Continue, parse stdout as JSON |
|
|
19
|
+
* | 1 | Error | Invalid input, non-blocking error | Non-blocking, stderr to user only |
|
|
20
|
+
* | 2 | Block | Handler throws OR `stopReason` set | Blocking, stderr shown to Claude |
|
|
21
|
+
*/
|
|
22
|
+
export const EXIT_CODES = {
|
|
23
|
+
/** Handler completed successfully. Claude Code parses stdout as JSON. */
|
|
24
|
+
SUCCESS: 0,
|
|
25
|
+
/** Non-blocking error occurred (e.g., invalid input). stderr shown to user only. */
|
|
26
|
+
ERROR: 1,
|
|
27
|
+
/** Handler threw exception OR blocking action requested. stderr shown to Claude. */
|
|
28
|
+
BLOCK: 2
|
|
29
|
+
};
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Output Builder Factories
|
|
32
|
+
// ============================================================================
|
|
33
|
+
/**
|
|
34
|
+
* Factory for hooks that have hookSpecificOutput with a hookEventName discriminator.
|
|
35
|
+
* @param hookType - The hook type name used as the _type discriminator
|
|
36
|
+
* @returns A builder function that creates the output object
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
function createHookSpecificOutputBuilder(hookType) {
|
|
40
|
+
return (options = {}) => {
|
|
41
|
+
const { hookSpecificOutput, ...rest } = options;
|
|
42
|
+
const stdout =
|
|
43
|
+
hookSpecificOutput !== undefined
|
|
44
|
+
? { ...rest, hookSpecificOutput: { hookEventName: hookType, ...hookSpecificOutput } }
|
|
45
|
+
: rest;
|
|
46
|
+
return { _type: hookType, stdout };
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Factory for hooks that only use CommonOptions (simple passthrough).
|
|
51
|
+
* @param hookType - The hook type name used as the _type discriminator
|
|
52
|
+
* @returns A builder function that creates the output object
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
function createSimpleOutputBuilder(hookType) {
|
|
56
|
+
return (options = {}) => ({
|
|
57
|
+
_type: hookType,
|
|
58
|
+
stdout: options
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Factory for hooks that use decision-based options (Stop, SubagentStop).
|
|
63
|
+
* @param hookType - The hook type name used as the _type discriminator
|
|
64
|
+
* @returns A builder function that creates the output object
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
function createDecisionOutputBuilder(hookType) {
|
|
68
|
+
return (options = {}) => ({
|
|
69
|
+
_type: hookType,
|
|
70
|
+
stdout: options
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Creates an output for PreToolUse hooks.
|
|
75
|
+
* @param options - Configuration options for the hook output
|
|
76
|
+
* @returns A PreToolUseOutput object ready for the runtime
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* // Allow tool execution
|
|
80
|
+
* preToolUseOutput({
|
|
81
|
+
* hookSpecificOutput: { permissionDecision: 'allow' }
|
|
82
|
+
* });
|
|
83
|
+
*
|
|
84
|
+
* // Deny with reason
|
|
85
|
+
* preToolUseOutput({
|
|
86
|
+
* hookSpecificOutput: {
|
|
87
|
+
* permissionDecision: 'deny',
|
|
88
|
+
* permissionDecisionReason: 'Dangerous command detected'
|
|
89
|
+
* }
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* // Allow with modified input
|
|
93
|
+
* preToolUseOutput({
|
|
94
|
+
* hookSpecificOutput: {
|
|
95
|
+
* permissionDecision: 'allow',
|
|
96
|
+
* updatedInput: { command: 'ls -la' }
|
|
97
|
+
* }
|
|
98
|
+
* });
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export const preToolUseOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('PreToolUse');
|
|
102
|
+
/**
|
|
103
|
+
* Creates an output for PostToolUse hooks.
|
|
104
|
+
* @param options - Configuration options for the hook output
|
|
105
|
+
* @returns A PostToolUseOutput object ready for the runtime
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* // Add context after a file read
|
|
109
|
+
* postToolUseOutput({
|
|
110
|
+
* hookSpecificOutput: {
|
|
111
|
+
* additionalContext: 'File contains sensitive data'
|
|
112
|
+
* }
|
|
113
|
+
* });
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export const postToolUseOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('PostToolUse');
|
|
117
|
+
/**
|
|
118
|
+
* Creates an output for PostToolUseFailure hooks.
|
|
119
|
+
* @param options - Configuration options for the hook output
|
|
120
|
+
* @returns A PostToolUseFailureOutput object ready for the runtime
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* postToolUseFailureOutput({
|
|
124
|
+
* hookSpecificOutput: {
|
|
125
|
+
* additionalContext: 'Try using a different approach'
|
|
126
|
+
* }
|
|
127
|
+
* });
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export const postToolUseFailureOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('PostToolUseFailure');
|
|
131
|
+
/**
|
|
132
|
+
* Creates an output for UserPromptSubmit hooks.
|
|
133
|
+
* @param options - Configuration options for the hook output
|
|
134
|
+
* @returns A UserPromptSubmitOutput object ready for the runtime
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* userPromptSubmitOutput({
|
|
138
|
+
* hookSpecificOutput: {
|
|
139
|
+
* additionalContext: 'This project uses TypeScript strict mode'
|
|
140
|
+
* }
|
|
141
|
+
* });
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export const userPromptSubmitOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('UserPromptSubmit');
|
|
145
|
+
/**
|
|
146
|
+
* Creates an output for SessionStart hooks.
|
|
147
|
+
* @param options - Configuration options for the hook output
|
|
148
|
+
* @returns A SessionStartOutput object ready for the runtime
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* sessionStartOutput({
|
|
152
|
+
* hookSpecificOutput: {
|
|
153
|
+
* additionalContext: JSON.stringify({ project: 'my-project' })
|
|
154
|
+
* }
|
|
155
|
+
* });
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export const sessionStartOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('SessionStart');
|
|
159
|
+
/**
|
|
160
|
+
* Creates an output for SessionEnd hooks.
|
|
161
|
+
* @param options - Configuration options for the hook output
|
|
162
|
+
* @returns A SessionEndOutput object ready for the runtime
|
|
163
|
+
* @example
|
|
164
|
+
* ```typescript
|
|
165
|
+
* sessionEndOutput({});
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
export const sessionEndOutput = /* @__PURE__ */ createSimpleOutputBuilder('SessionEnd');
|
|
169
|
+
/**
|
|
170
|
+
* Creates an output for Stop hooks.
|
|
171
|
+
* @param options - Configuration options for the hook output
|
|
172
|
+
* @returns A StopOutput object ready for the runtime
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* // Allow the stop
|
|
176
|
+
* stopOutput({ decision: 'approve' });
|
|
177
|
+
*
|
|
178
|
+
* // Block with reason
|
|
179
|
+
* stopOutput({
|
|
180
|
+
* decision: 'block',
|
|
181
|
+
* reason: 'There are uncommitted changes'
|
|
182
|
+
* });
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
export const stopOutput = /* @__PURE__ */ createDecisionOutputBuilder('Stop');
|
|
186
|
+
/**
|
|
187
|
+
* Creates an output for SubagentStart hooks.
|
|
188
|
+
* @param options - Configuration options for the hook output
|
|
189
|
+
* @returns A SubagentStartOutput object ready for the runtime
|
|
190
|
+
* @example
|
|
191
|
+
* ```typescript
|
|
192
|
+
* subagentStartOutput({
|
|
193
|
+
* hookSpecificOutput: {
|
|
194
|
+
* additionalContext: 'Focus on finding patterns'
|
|
195
|
+
* }
|
|
196
|
+
* });
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
export const subagentStartOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('SubagentStart');
|
|
200
|
+
/**
|
|
201
|
+
* Creates an output for SubagentStop hooks.
|
|
202
|
+
* @param options - Configuration options for the hook output
|
|
203
|
+
* @returns A SubagentStopOutput object ready for the runtime
|
|
204
|
+
* @example
|
|
205
|
+
* ```typescript
|
|
206
|
+
* // Block with reason
|
|
207
|
+
* subagentStopOutput({
|
|
208
|
+
* decision: 'block',
|
|
209
|
+
* reason: 'Task not complete'
|
|
210
|
+
* });
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
export const subagentStopOutput = /* @__PURE__ */ createDecisionOutputBuilder('SubagentStop');
|
|
214
|
+
/**
|
|
215
|
+
* Creates an output for Notification hooks.
|
|
216
|
+
* @param options - Configuration options for the hook output
|
|
217
|
+
* @returns A NotificationOutput object ready for the runtime
|
|
218
|
+
* @example
|
|
219
|
+
* ```typescript
|
|
220
|
+
* // Add context about the notification
|
|
221
|
+
* notificationOutput({
|
|
222
|
+
* hookSpecificOutput: {
|
|
223
|
+
* additionalContext: 'Notification forwarded to Slack #alerts channel'
|
|
224
|
+
* }
|
|
225
|
+
* });
|
|
226
|
+
*
|
|
227
|
+
* // Suppress the notification
|
|
228
|
+
* notificationOutput({ suppressOutput: true });
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
export const notificationOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('Notification');
|
|
232
|
+
/**
|
|
233
|
+
* Creates an output for PreCompact hooks.
|
|
234
|
+
* @param options - Configuration options for the hook output
|
|
235
|
+
* @returns A PreCompactOutput object ready for the runtime
|
|
236
|
+
* @example
|
|
237
|
+
* ```typescript
|
|
238
|
+
* preCompactOutput({
|
|
239
|
+
* systemMessage: 'Remember: strict mode is enabled'
|
|
240
|
+
* });
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
export const preCompactOutput = /* @__PURE__ */ createSimpleOutputBuilder('PreCompact');
|
|
244
|
+
/**
|
|
245
|
+
* Creates an output for PermissionRequest hooks.
|
|
246
|
+
* @param options - Configuration options for the hook output
|
|
247
|
+
* @returns A PermissionRequestOutput object ready for the runtime
|
|
248
|
+
* @example
|
|
249
|
+
* ```typescript
|
|
250
|
+
* // Auto-approve
|
|
251
|
+
* permissionRequestOutput({
|
|
252
|
+
* hookSpecificOutput: {
|
|
253
|
+
* decision: { behavior: 'allow' }
|
|
254
|
+
* }
|
|
255
|
+
* });
|
|
256
|
+
*
|
|
257
|
+
* // Auto-approve with modified input
|
|
258
|
+
* permissionRequestOutput({
|
|
259
|
+
* hookSpecificOutput: {
|
|
260
|
+
* decision: {
|
|
261
|
+
* behavior: 'allow',
|
|
262
|
+
* updatedInput: { file_path: '/safe/path' }
|
|
263
|
+
* }
|
|
264
|
+
* }
|
|
265
|
+
* });
|
|
266
|
+
*
|
|
267
|
+
* // Auto-deny
|
|
268
|
+
* permissionRequestOutput({
|
|
269
|
+
* hookSpecificOutput: {
|
|
270
|
+
* decision: {
|
|
271
|
+
* behavior: 'deny',
|
|
272
|
+
* message: 'Not allowed',
|
|
273
|
+
* interrupt: true
|
|
274
|
+
* }
|
|
275
|
+
* }
|
|
276
|
+
* });
|
|
277
|
+
*
|
|
278
|
+
* // Fall through to normal prompt
|
|
279
|
+
* permissionRequestOutput({});
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
export const permissionRequestOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('PermissionRequest');
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime module for Claude Code hooks.
|
|
3
|
+
*
|
|
4
|
+
* Handles stdin/stdout/exit code semantics for compiled hook execution.
|
|
5
|
+
* This module is the core orchestrator that:
|
|
6
|
+
* - Reads JSON from stdin (wire format with snake_case properties)
|
|
7
|
+
* - Invokes the hook handler
|
|
8
|
+
* - Writes output to stdout
|
|
9
|
+
* - Manages exit codes
|
|
10
|
+
* @module
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // In a compiled hook file
|
|
14
|
+
* import { execute } from '@goodfoot/claude-code-hooks/runtime';
|
|
15
|
+
* import myHook from './my-hook.js';
|
|
16
|
+
*
|
|
17
|
+
* execute(myHook);
|
|
18
|
+
* ```
|
|
19
|
+
* @see https://code.claude.com/docs/en/hooks
|
|
20
|
+
*/
|
|
21
|
+
import { persistEnvVar, persistEnvVars } from './env.js';
|
|
22
|
+
import { logger } from './logger.js';
|
|
23
|
+
import { EXIT_CODES } from './outputs.js';
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Stdin/Stdout Handling
|
|
26
|
+
// ============================================================================
|
|
27
|
+
/**
|
|
28
|
+
* Reads all data from stdin.
|
|
29
|
+
* @returns Promise resolving to the complete stdin content
|
|
30
|
+
*/
|
|
31
|
+
async function readStdin() {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const chunks = [];
|
|
34
|
+
// Set encoding first to ensure data events receive strings
|
|
35
|
+
process.stdin.setEncoding('utf-8');
|
|
36
|
+
process.stdin.on('data', (chunk) => {
|
|
37
|
+
chunks.push(chunk);
|
|
38
|
+
});
|
|
39
|
+
process.stdin.on('end', () => {
|
|
40
|
+
resolve(chunks.join(''));
|
|
41
|
+
});
|
|
42
|
+
process.stdin.on('error', (error) => {
|
|
43
|
+
reject(error);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parses stdin JSON input.
|
|
49
|
+
* @param stdinContent - Raw stdin content
|
|
50
|
+
* @returns Parsed input (wire format with snake_case properties)
|
|
51
|
+
* @throws Error if JSON is malformed
|
|
52
|
+
*/
|
|
53
|
+
function parseStdinInput(stdinContent) {
|
|
54
|
+
// Parse JSON - input uses wire format (snake_case) directly
|
|
55
|
+
const rawInput = JSON.parse(stdinContent);
|
|
56
|
+
return rawInput;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Writes hook output to stdout.
|
|
60
|
+
*
|
|
61
|
+
* Output uses camelCase keys per Claude Code hook specification.
|
|
62
|
+
* @param output - The hook output to write
|
|
63
|
+
* @see https://code.claude.com/docs/en/hooks#hook-output-structure
|
|
64
|
+
*/
|
|
65
|
+
function writeStdout(output) {
|
|
66
|
+
// Output uses camelCase - no transformation needed
|
|
67
|
+
process.stdout.write(JSON.stringify(output));
|
|
68
|
+
}
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// Error Handling
|
|
71
|
+
// ============================================================================
|
|
72
|
+
/**
|
|
73
|
+
* Creates an error output for malformed stdin JSON.
|
|
74
|
+
* @param error - The parse error
|
|
75
|
+
* @returns HookOutput with empty stdout
|
|
76
|
+
*/
|
|
77
|
+
function createMalformedInputOutput(error) {
|
|
78
|
+
logger.error(`Invalid JSON input: ${error instanceof Error ? error.message : String(error)}`);
|
|
79
|
+
return { stdout: {} };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Writes handler error stacktrace to stderr and exits with code 2.
|
|
83
|
+
*
|
|
84
|
+
* When a hook handler throws an exception:
|
|
85
|
+
* - Stacktrace (with sourcemaps if available) is output to stderr
|
|
86
|
+
* - Process exits with code 2 (BLOCK)
|
|
87
|
+
* - No JSON is output to stdout
|
|
88
|
+
* @param error - The error thrown by the handler
|
|
89
|
+
*/
|
|
90
|
+
function handleHandlerError(error) {
|
|
91
|
+
// Write stack trace to stderr (sourcemaps are applied automatically by Node.js)
|
|
92
|
+
if (error instanceof Error) {
|
|
93
|
+
process.stderr.write(`${error.stack ?? error.message}\n`);
|
|
94
|
+
} else {
|
|
95
|
+
process.stderr.write(`${String(error)}\n`);
|
|
96
|
+
}
|
|
97
|
+
// Log to file if configured
|
|
98
|
+
logger.error(`Hook handler error: ${error instanceof Error ? error.message : String(error)}`);
|
|
99
|
+
// Clear logger context and close
|
|
100
|
+
logger.clearContext();
|
|
101
|
+
logger.close();
|
|
102
|
+
// Exit with code 2 (BLOCK) - no JSON output
|
|
103
|
+
process.exit(EXIT_CODES.BLOCK);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Converts a SpecificHookOutput to HookOutput for wire format.
|
|
107
|
+
*
|
|
108
|
+
* SpecificHookOutput types have: { _type, exitCode, stdout, stderr? }
|
|
109
|
+
* HookOutput has: { exitCode, stdout, stderr? }
|
|
110
|
+
*
|
|
111
|
+
* Since output builders now produce wire-format directly, this function
|
|
112
|
+
* simply strips the `_type` discriminator field.
|
|
113
|
+
* @param specificOutput - The specific output from a hook handler
|
|
114
|
+
* @returns HookOutput ready for serialization
|
|
115
|
+
* @see https://code.claude.com/docs/en/hooks#hook-output-structure
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* const specificOutput = preToolUseOutput({ hookSpecificOutput: { permissionDecision: 'allow' } });
|
|
119
|
+
* const hookOutput = convertToHookOutput(specificOutput);
|
|
120
|
+
* // hookOutput: { exitCode: 0, stdout: { hookSpecificOutput: { ... } } }
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export function convertToHookOutput(specificOutput) {
|
|
124
|
+
return { stdout: specificOutput.stdout };
|
|
125
|
+
}
|
|
126
|
+
// ============================================================================
|
|
127
|
+
// Execute Function
|
|
128
|
+
// ============================================================================
|
|
129
|
+
/**
|
|
130
|
+
* Executes a hook handler with full runtime orchestration.
|
|
131
|
+
*
|
|
132
|
+
* This is the main entry point that compiled hooks use. When a compiled hook
|
|
133
|
+
* runs as a CLI:
|
|
134
|
+
*
|
|
135
|
+
* 1. Reads all stdin
|
|
136
|
+
* 2. Parses JSON (wire format with snake_case properties)
|
|
137
|
+
* 3. Sets up logger context (hookType, input)
|
|
138
|
+
* 4. Calls handler with input and context (logger)
|
|
139
|
+
* 5. Handles any errors, logs them
|
|
140
|
+
* 6. Writes JSON to stdout
|
|
141
|
+
* 7. Closes logger
|
|
142
|
+
* 8. Exits with appropriate code
|
|
143
|
+
* @param hookFn - The hook function to execute (from hook factory)
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* // In compiled hook file
|
|
147
|
+
* import { execute } from '@goodfoot/claude-code-hooks/runtime';
|
|
148
|
+
* import { preToolUseHook, preToolUseOutput } from '@goodfoot/claude-code-hooks';
|
|
149
|
+
*
|
|
150
|
+
* const myHook = preToolUseHook({ matcher: 'Bash' }, async (input, { logger }) => {
|
|
151
|
+
* logger.info('Processing Bash command');
|
|
152
|
+
* return preToolUseOutput({ allow: true });
|
|
153
|
+
* });
|
|
154
|
+
*
|
|
155
|
+
* execute(myHook);
|
|
156
|
+
* ```
|
|
157
|
+
* @see https://code.claude.com/docs/en/hooks
|
|
158
|
+
*/
|
|
159
|
+
export async function execute(hookFn) {
|
|
160
|
+
let output;
|
|
161
|
+
try {
|
|
162
|
+
// Check for log file configuration conflicts
|
|
163
|
+
// CLAUDE_CODE_HOOKS_CLI_LOG_FILE is injected by the CLI --log parameter
|
|
164
|
+
// CLAUDE_CODE_HOOKS_LOG_FILE is the user's environment variable
|
|
165
|
+
const cliLogFile = process.env['CLAUDE_CODE_HOOKS_CLI_LOG_FILE'];
|
|
166
|
+
const envLogFile = process.env['CLAUDE_CODE_HOOKS_LOG_FILE'];
|
|
167
|
+
if (cliLogFile !== undefined && envLogFile !== undefined && cliLogFile !== envLogFile) {
|
|
168
|
+
// Write error to stderr and exit with error code
|
|
169
|
+
process.stderr.write(
|
|
170
|
+
`Log file configuration conflict: CLI --log="${cliLogFile}" vs CLAUDE_CODE_HOOKS_LOG_FILE="${envLogFile}". ` +
|
|
171
|
+
'Use only one method to configure hook logging.\n'
|
|
172
|
+
);
|
|
173
|
+
process.exit(EXIT_CODES.ERROR);
|
|
174
|
+
}
|
|
175
|
+
// If CLI log file is set, configure the logger
|
|
176
|
+
if (cliLogFile !== undefined) {
|
|
177
|
+
logger.setLogFile(cliLogFile);
|
|
178
|
+
}
|
|
179
|
+
// Read and parse stdin
|
|
180
|
+
let stdinContent;
|
|
181
|
+
try {
|
|
182
|
+
stdinContent = await readStdin();
|
|
183
|
+
} catch (error) {
|
|
184
|
+
logger.logError(error, 'Failed to read stdin');
|
|
185
|
+
output = createMalformedInputOutput(error);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
// Parse and transform input
|
|
189
|
+
let input;
|
|
190
|
+
try {
|
|
191
|
+
input = parseStdinInput(stdinContent);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
logger.logError(error, 'Failed to parse stdin JSON');
|
|
194
|
+
output = createMalformedInputOutput(error);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// Set logger context
|
|
198
|
+
const hookEventName = hookFn.hookEventName;
|
|
199
|
+
logger.setContext(hookEventName, input);
|
|
200
|
+
// Build context - SessionStart hooks get extended context with persistEnvVar
|
|
201
|
+
const context = hookEventName === 'SessionStart' ? { logger, persistEnvVar, persistEnvVars } : { logger };
|
|
202
|
+
// Execute handler
|
|
203
|
+
try {
|
|
204
|
+
const specificOutput = await hookFn(input, context);
|
|
205
|
+
output = convertToHookOutput(specificOutput);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
// Handler threw - output stacktrace to stderr and exit with code 2
|
|
208
|
+
// This call never returns (process.exit)
|
|
209
|
+
handleHandlerError(error);
|
|
210
|
+
}
|
|
211
|
+
} finally {
|
|
212
|
+
// Write output if we have it
|
|
213
|
+
if (output !== undefined) {
|
|
214
|
+
writeStdout(output.stdout);
|
|
215
|
+
}
|
|
216
|
+
// Clear logger context
|
|
217
|
+
logger.clearContext();
|
|
218
|
+
logger.close();
|
|
219
|
+
// Exit with success (handler errors exit via handleHandlerError with code 2)
|
|
220
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
221
|
+
}
|
|
222
|
+
}
|