@goodfoot/claude-code-hooks 1.0.10 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/outputs.js CHANGED
@@ -20,12 +20,12 @@
20
20
  * | 2 | Block | Handler throws OR `stopReason` set | Blocking, stderr shown to Claude |
21
21
  */
22
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
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
29
  };
30
30
  // ============================================================================
31
31
  // Output Builder Factories
@@ -37,14 +37,13 @@ export const EXIT_CODES = {
37
37
  * @internal
38
38
  */
39
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
- };
40
+ return (options = {}) => {
41
+ const { hookSpecificOutput, ...rest } = options;
42
+ const stdout = hookSpecificOutput !== undefined
43
+ ? { ...rest, hookSpecificOutput: { hookEventName: hookType, ...hookSpecificOutput } }
44
+ : rest;
45
+ return { _type: hookType, stdout };
46
+ };
48
47
  }
49
48
  /**
50
49
  * Factory for hooks that only use CommonOptions (simple passthrough).
@@ -53,10 +52,10 @@ function createHookSpecificOutputBuilder(hookType) {
53
52
  * @internal
54
53
  */
55
54
  function createSimpleOutputBuilder(hookType) {
56
- return (options = {}) => ({
57
- _type: hookType,
58
- stdout: options
59
- });
55
+ return (options = {}) => ({
56
+ _type: hookType,
57
+ stdout: options,
58
+ });
60
59
  }
61
60
  /**
62
61
  * Factory for hooks that use decision-based options (Stop, SubagentStop).
@@ -65,10 +64,10 @@ function createSimpleOutputBuilder(hookType) {
65
64
  * @internal
66
65
  */
67
66
  function createDecisionOutputBuilder(hookType) {
68
- return (options = {}) => ({
69
- _type: hookType,
70
- stdout: options
71
- });
67
+ return (options = {}) => ({
68
+ _type: hookType,
69
+ stdout: options,
70
+ });
72
71
  }
73
72
  /**
74
73
  * Creates an output for PreToolUse hooks.
@@ -98,7 +97,7 @@ function createDecisionOutputBuilder(hookType) {
98
97
  * });
99
98
  * ```
100
99
  */
101
- export const preToolUseOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('PreToolUse');
100
+ export const preToolUseOutput = /* @__PURE__ */ createHookSpecificOutputBuilder("PreToolUse");
102
101
  /**
103
102
  * Creates an output for PostToolUse hooks.
104
103
  * @param options - Configuration options for the hook output
@@ -113,7 +112,7 @@ export const preToolUseOutput = /* @__PURE__ */ createHookSpecificOutputBuilder(
113
112
  * });
114
113
  * ```
115
114
  */
116
- export const postToolUseOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('PostToolUse');
115
+ export const postToolUseOutput = /* @__PURE__ */ createHookSpecificOutputBuilder("PostToolUse");
117
116
  /**
118
117
  * Creates an output for PostToolUseFailure hooks.
119
118
  * @param options - Configuration options for the hook output
@@ -127,7 +126,7 @@ export const postToolUseOutput = /* @__PURE__ */ createHookSpecificOutputBuilder
127
126
  * });
128
127
  * ```
129
128
  */
130
- export const postToolUseFailureOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('PostToolUseFailure');
129
+ export const postToolUseFailureOutput = /* @__PURE__ */ createHookSpecificOutputBuilder("PostToolUseFailure");
131
130
  /**
132
131
  * Creates an output for UserPromptSubmit hooks.
133
132
  * @param options - Configuration options for the hook output
@@ -141,7 +140,7 @@ export const postToolUseFailureOutput = /* @__PURE__ */ createHookSpecificOutput
141
140
  * });
142
141
  * ```
143
142
  */
144
- export const userPromptSubmitOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('UserPromptSubmit');
143
+ export const userPromptSubmitOutput = /* @__PURE__ */ createHookSpecificOutputBuilder("UserPromptSubmit");
145
144
  /**
146
145
  * Creates an output for SessionStart hooks.
147
146
  * @param options - Configuration options for the hook output
@@ -155,7 +154,7 @@ export const userPromptSubmitOutput = /* @__PURE__ */ createHookSpecificOutputBu
155
154
  * });
156
155
  * ```
157
156
  */
158
- export const sessionStartOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('SessionStart');
157
+ export const sessionStartOutput = /* @__PURE__ */ createHookSpecificOutputBuilder("SessionStart");
159
158
  /**
160
159
  * Creates an output for SessionEnd hooks.
161
160
  * @param options - Configuration options for the hook output
@@ -165,7 +164,7 @@ export const sessionStartOutput = /* @__PURE__ */ createHookSpecificOutputBuilde
165
164
  * sessionEndOutput({});
166
165
  * ```
167
166
  */
168
- export const sessionEndOutput = /* @__PURE__ */ createSimpleOutputBuilder('SessionEnd');
167
+ export const sessionEndOutput = /* @__PURE__ */ createSimpleOutputBuilder("SessionEnd");
169
168
  /**
170
169
  * Creates an output for Stop hooks.
171
170
  * @param options - Configuration options for the hook output
@@ -182,7 +181,7 @@ export const sessionEndOutput = /* @__PURE__ */ createSimpleOutputBuilder('Sessi
182
181
  * });
183
182
  * ```
184
183
  */
185
- export const stopOutput = /* @__PURE__ */ createDecisionOutputBuilder('Stop');
184
+ export const stopOutput = /* @__PURE__ */ createDecisionOutputBuilder("Stop");
186
185
  /**
187
186
  * Creates an output for SubagentStart hooks.
188
187
  * @param options - Configuration options for the hook output
@@ -196,7 +195,7 @@ export const stopOutput = /* @__PURE__ */ createDecisionOutputBuilder('Stop');
196
195
  * });
197
196
  * ```
198
197
  */
199
- export const subagentStartOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('SubagentStart');
198
+ export const subagentStartOutput = /* @__PURE__ */ createHookSpecificOutputBuilder("SubagentStart");
200
199
  /**
201
200
  * Creates an output for SubagentStop hooks.
202
201
  * @param options - Configuration options for the hook output
@@ -210,7 +209,7 @@ export const subagentStartOutput = /* @__PURE__ */ createHookSpecificOutputBuild
210
209
  * });
211
210
  * ```
212
211
  */
213
- export const subagentStopOutput = /* @__PURE__ */ createDecisionOutputBuilder('SubagentStop');
212
+ export const subagentStopOutput = /* @__PURE__ */ createDecisionOutputBuilder("SubagentStop");
214
213
  /**
215
214
  * Creates an output for Notification hooks.
216
215
  * @param options - Configuration options for the hook output
@@ -228,7 +227,7 @@ export const subagentStopOutput = /* @__PURE__ */ createDecisionOutputBuilder('S
228
227
  * notificationOutput({ suppressOutput: true });
229
228
  * ```
230
229
  */
231
- export const notificationOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('Notification');
230
+ export const notificationOutput = /* @__PURE__ */ createHookSpecificOutputBuilder("Notification");
232
231
  /**
233
232
  * Creates an output for PreCompact hooks.
234
233
  * @param options - Configuration options for the hook output
@@ -240,7 +239,7 @@ export const notificationOutput = /* @__PURE__ */ createHookSpecificOutputBuilde
240
239
  * });
241
240
  * ```
242
241
  */
243
- export const preCompactOutput = /* @__PURE__ */ createSimpleOutputBuilder('PreCompact');
242
+ export const preCompactOutput = /* @__PURE__ */ createSimpleOutputBuilder("PreCompact");
244
243
  /**
245
244
  * Creates an output for PermissionRequest hooks.
246
245
  * @param options - Configuration options for the hook output
@@ -279,4 +278,22 @@ export const preCompactOutput = /* @__PURE__ */ createSimpleOutputBuilder('PreCo
279
278
  * permissionRequestOutput({});
280
279
  * ```
281
280
  */
282
- export const permissionRequestOutput = /* @__PURE__ */ createHookSpecificOutputBuilder('PermissionRequest');
281
+ export const permissionRequestOutput = /* @__PURE__ */ createHookSpecificOutputBuilder("PermissionRequest");
282
+ /**
283
+ * Creates an output for Setup hooks.
284
+ * @param options - Configuration options for the hook output
285
+ * @returns A SetupOutput object ready for the runtime
286
+ * @example
287
+ * ```typescript
288
+ * // Add context during setup
289
+ * setupOutput({
290
+ * hookSpecificOutput: {
291
+ * additionalContext: 'Project initialized with custom settings'
292
+ * }
293
+ * });
294
+ *
295
+ * // Simple passthrough
296
+ * setupOutput({});
297
+ * ```
298
+ */
299
+ export const setupOutput = /* @__PURE__ */ createHookSpecificOutputBuilder("Setup");
package/dist/runtime.js CHANGED
@@ -18,9 +18,9 @@
18
18
  * ```
19
19
  * @see https://code.claude.com/docs/en/hooks
20
20
  */
21
- import { persistEnvVar, persistEnvVars } from './env.js';
22
- import { logger } from './logger.js';
23
- import { EXIT_CODES } from './outputs.js';
21
+ import { persistEnvVar, persistEnvVars } from "./env.js";
22
+ import { logger } from "./logger.js";
23
+ import { EXIT_CODES } from "./outputs.js";
24
24
  // ============================================================================
25
25
  // Stdin/Stdout Handling
26
26
  // ============================================================================
@@ -29,20 +29,20 @@ import { EXIT_CODES } from './outputs.js';
29
29
  * @returns Promise resolving to the complete stdin content
30
30
  */
31
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);
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
+ });
38
45
  });
39
- process.stdin.on('end', () => {
40
- resolve(chunks.join(''));
41
- });
42
- process.stdin.on('error', (error) => {
43
- reject(error);
44
- });
45
- });
46
46
  }
47
47
  /**
48
48
  * Parses stdin JSON input.
@@ -51,9 +51,9 @@ async function readStdin() {
51
51
  * @throws Error if JSON is malformed
52
52
  */
53
53
  function parseStdinInput(stdinContent) {
54
- // Parse JSON - input uses wire format (snake_case) directly
55
- const rawInput = JSON.parse(stdinContent);
56
- return rawInput;
54
+ // Parse JSON - input uses wire format (snake_case) directly
55
+ const rawInput = JSON.parse(stdinContent);
56
+ return rawInput;
57
57
  }
58
58
  /**
59
59
  * Writes hook output to stdout.
@@ -63,8 +63,8 @@ function parseStdinInput(stdinContent) {
63
63
  * @see https://code.claude.com/docs/en/hooks#hook-output-structure
64
64
  */
65
65
  function writeStdout(output) {
66
- // Output uses camelCase - no transformation needed
67
- process.stdout.write(JSON.stringify(output));
66
+ // Output uses camelCase - no transformation needed
67
+ process.stdout.write(JSON.stringify(output));
68
68
  }
69
69
  // ============================================================================
70
70
  // Error Handling
@@ -75,8 +75,8 @@ function writeStdout(output) {
75
75
  * @returns HookOutput with empty stdout
76
76
  */
77
77
  function createMalformedInputOutput(error) {
78
- logger.error(`Invalid JSON input: ${error instanceof Error ? error.message : String(error)}`);
79
- return { stdout: {} };
78
+ logger.error(`Invalid JSON input: ${error instanceof Error ? error.message : String(error)}`);
79
+ return { stdout: {} };
80
80
  }
81
81
  /**
82
82
  * Writes handler error stacktrace to stderr and exits with code 2.
@@ -88,19 +88,20 @@ function createMalformedInputOutput(error) {
88
88
  * @param error - The error thrown by the handler
89
89
  */
90
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);
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
+ }
95
+ else {
96
+ process.stderr.write(`${String(error)}\n`);
97
+ }
98
+ // Log to file if configured
99
+ logger.error(`Hook handler error: ${error instanceof Error ? error.message : String(error)}`);
100
+ // Clear logger context and close
101
+ logger.clearContext();
102
+ logger.close();
103
+ // Exit with code 2 (BLOCK) - no JSON output
104
+ process.exit(EXIT_CODES.BLOCK);
104
105
  }
105
106
  /**
106
107
  * Converts a SpecificHookOutput to HookOutput for wire format.
@@ -121,7 +122,7 @@ function handleHandlerError(error) {
121
122
  * ```
122
123
  */
123
124
  export function convertToHookOutput(specificOutput) {
124
- return { stdout: specificOutput.stdout };
125
+ return { stdout: specificOutput.stdout };
125
126
  }
126
127
  // ============================================================================
127
128
  // Execute Function
@@ -157,66 +158,68 @@ export function convertToHookOutput(specificOutput) {
157
158
  * @see https://code.claude.com/docs/en/hooks
158
159
  */
159
160
  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;
161
+ let output;
190
162
  try {
191
- input = parseStdinInput(stdinContent);
192
- } catch (error) {
193
- logger.logError(error, 'Failed to parse stdin JSON');
194
- output = createMalformedInputOutput(error);
195
- return;
163
+ // Check for log file configuration conflicts
164
+ // CLAUDE_CODE_HOOKS_CLI_LOG_FILE is injected by the CLI --log parameter
165
+ // CLAUDE_CODE_HOOKS_LOG_FILE is the user's environment variable
166
+ const cliLogFile = process.env.CLAUDE_CODE_HOOKS_CLI_LOG_FILE;
167
+ const envLogFile = process.env.CLAUDE_CODE_HOOKS_LOG_FILE;
168
+ if (cliLogFile !== undefined && envLogFile !== undefined && cliLogFile !== envLogFile) {
169
+ // Write error to stderr and exit with error code
170
+ process.stderr.write(`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
+ process.exit(EXIT_CODES.ERROR);
173
+ }
174
+ // If CLI log file is set, configure the logger
175
+ if (cliLogFile !== undefined) {
176
+ logger.setLogFile(cliLogFile);
177
+ }
178
+ // Read and parse stdin
179
+ let stdinContent;
180
+ try {
181
+ stdinContent = await readStdin();
182
+ }
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
+ }
193
+ catch (error) {
194
+ logger.logError(error, "Failed to parse stdin JSON");
195
+ output = createMalformedInputOutput(error);
196
+ return;
197
+ }
198
+ // Set logger context
199
+ const hookEventName = hookFn.hookEventName;
200
+ logger.setContext(hookEventName, input);
201
+ // Build context - SessionStart hooks get extended context with persistEnvVar
202
+ const context = hookEventName === "SessionStart" ? { logger, persistEnvVar, persistEnvVars } : { logger };
203
+ // Execute handler
204
+ try {
205
+ const specificOutput = await hookFn(input, context);
206
+ output = convertToHookOutput(specificOutput);
207
+ }
208
+ catch (error) {
209
+ // Handler threw - output stacktrace to stderr and exit with code 2
210
+ // This call never returns (process.exit)
211
+ handleHandlerError(error);
212
+ }
196
213
  }
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);
214
+ finally {
215
+ // Write output if we have it
216
+ if (output !== undefined) {
217
+ writeStdout(output.stdout);
218
+ }
219
+ // Clear logger context
220
+ logger.clearContext();
221
+ logger.close();
222
+ // Exit with success (handler errors exit via handleHandlerError with code 2)
223
+ process.exit(EXIT_CODES.SUCCESS);
210
224
  }
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
225
  }