@goodfoot/claude-code-hooks 1.0.23 → 1.1.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/dist/outputs.js CHANGED
@@ -69,6 +69,23 @@ function createDecisionOutputBuilder(hookType) {
69
69
  stdout: options,
70
70
  });
71
71
  }
72
+ /**
73
+ * Factory for exit-code-based hooks (TeammateIdle, TaskCompleted).
74
+ *
75
+ * These hooks don't use JSON decision control (no CommonOptions).
76
+ * The only option is `stderr` — when present, it triggers exit code 2 (BLOCK).
77
+ * Stdout always receives `{}` (empty JSON object).
78
+ * @param hookType - The hook type name used as the _type discriminator
79
+ * @returns A builder function that creates the output object
80
+ * @internal
81
+ */
82
+ function createExitCodeOutputBuilder(hookType) {
83
+ return ({ stderr } = {}) => ({
84
+ _type: hookType,
85
+ stdout: {},
86
+ ...(stderr !== undefined ? { stderr } : {}),
87
+ });
88
+ }
72
89
  /**
73
90
  * Creates an output for PreToolUse hooks.
74
91
  * @param options - Configuration options for the hook output
@@ -303,17 +320,25 @@ export const setupOutput = /* @__PURE__ */ createHookSpecificOutputBuilder("Setu
303
320
  * @returns A TeammateIdleOutput object ready for the runtime
304
321
  * @example
305
322
  * ```typescript
323
+ * // Allow teammate to go idle
306
324
  * teammateIdleOutput({});
325
+ *
326
+ * // Block with feedback
327
+ * teammateIdleOutput({ stderr: 'Continue working: unfinished tasks remain.' });
307
328
  * ```
308
329
  */
309
- export const teammateIdleOutput = /* @__PURE__ */ createSimpleOutputBuilder("TeammateIdle");
330
+ export const teammateIdleOutput = /* @__PURE__ */ createExitCodeOutputBuilder("TeammateIdle");
310
331
  /**
311
332
  * Creates an output for TaskCompleted hooks.
312
333
  * @param options - Configuration options for the hook output
313
334
  * @returns A TaskCompletedOutput object ready for the runtime
314
335
  * @example
315
336
  * ```typescript
337
+ * // Allow task completion
316
338
  * taskCompletedOutput({});
339
+ *
340
+ * // Block with feedback
341
+ * taskCompletedOutput({ stderr: 'Cannot complete: tests are failing.' });
317
342
  * ```
318
343
  */
319
- export const taskCompletedOutput = /* @__PURE__ */ createSimpleOutputBuilder("TaskCompleted");
344
+ export const taskCompletedOutput = /* @__PURE__ */ createExitCodeOutputBuilder("TaskCompleted");
package/dist/runtime.js CHANGED
@@ -106,8 +106,8 @@ function handleHandlerError(error) {
106
106
  /**
107
107
  * Converts a SpecificHookOutput to HookOutput for wire format.
108
108
  *
109
- * SpecificHookOutput types have: { _type, exitCode, stdout, stderr? }
110
- * HookOutput has: { exitCode, stdout, stderr? }
109
+ * SpecificHookOutput types have: { _type, stdout, stderr? }
110
+ * HookOutput has: { stdout, stderr? }
111
111
  *
112
112
  * Since output builders now produce wire-format directly, this function
113
113
  * simply strips the `_type` discriminator field.
@@ -118,11 +118,12 @@ function handleHandlerError(error) {
118
118
  * ```typescript
119
119
  * const specificOutput = preToolUseOutput({ hookSpecificOutput: { permissionDecision: 'allow' } });
120
120
  * const hookOutput = convertToHookOutput(specificOutput);
121
- * // hookOutput: { exitCode: 0, stdout: { hookSpecificOutput: { ... } } }
121
+ * // hookOutput: { stdout: { hookSpecificOutput: { ... } } }
122
122
  * ```
123
123
  */
124
124
  export function convertToHookOutput(specificOutput) {
125
- return { stdout: specificOutput.stdout };
125
+ const { stdout, stderr } = specificOutput;
126
+ return stderr !== undefined ? { stdout, stderr } : { stdout };
126
127
  }
127
128
  // ============================================================================
128
129
  // Execute Function
@@ -216,9 +217,16 @@ export async function execute(hookFn) {
216
217
  if (output !== undefined) {
217
218
  writeStdout(output.stdout);
218
219
  }
219
- // Clear logger context
220
+ // Clean up logger (single cleanup path)
220
221
  logger.clearContext();
221
222
  logger.close();
223
+ // Exit-code BLOCK: unlike handler throw (no stdout), this path still writes
224
+ // structured JSON to stdout (as empty {}) alongside the stderr message.
225
+ // The caller controls stderr formatting (no appended newline).
226
+ if (output?.stderr !== undefined) {
227
+ process.stderr.write(output.stderr);
228
+ process.exit(EXIT_CODES.BLOCK);
229
+ }
222
230
  // Exit with success (handler errors exit via handleHandlerError with code 2)
223
231
  process.exit(EXIT_CODES.SUCCESS);
224
232
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodfoot/claude-code-hooks",
3
- "version": "1.0.23",
3
+ "version": "1.1.0",
4
4
  "description": "Type-safe Claude Code hooks library with camelCase types and output builders",
5
5
  "homepage": "https://github.com/goodfoot-io/marketplace/tree/main/packages/claude-code-hooks",
6
6
  "repository": {
package/types/index.d.ts CHANGED
@@ -13,7 +13,7 @@ export type { LogEvent, LogEventError, LogEventHandler, LoggerConfig, LogLevel,
13
13
  export { LOG_LEVELS, Logger, logger } from "./logger.js";
14
14
  export type {
15
15
  /** @deprecated Use CommonOptions instead */
16
- BaseOptions, CommonOptions, ExitCode, HookOutput, HookSpecificOutput, NotificationHookSpecificOutput, NotificationOptions, PermissionRequestAllowDecision, PermissionRequestDecision, PermissionRequestDenyDecision, PermissionRequestHookSpecificOutput, PermissionRequestOptions, PostToolUseFailureHookSpecificOutput, PostToolUseFailureOptions, PostToolUseHookSpecificOutput, PostToolUseOptions, PreCompactOptions, PreToolUseHookSpecificOutput, PreToolUseOptions, SessionEndOptions, SessionStartHookSpecificOutput, SessionStartOptions, SetupHookSpecificOutput, SetupOptions, StopOptions, SubagentStartHookSpecificOutput, SubagentStartOptions, SubagentStopOptions, SyncHookJSONOutput, TaskCompletedOptions, TeammateIdleOptions, UserPromptSubmitHookSpecificOutput, UserPromptSubmitOptions, } from "./outputs.js";
16
+ BaseOptions, CommonOptions, ExitCode, ExitCodeOptions, HookOutput, HookSpecificOutput, NotificationHookSpecificOutput, NotificationOptions, PermissionRequestAllowDecision, PermissionRequestDecision, PermissionRequestDenyDecision, PermissionRequestHookSpecificOutput, PermissionRequestOptions, PostToolUseFailureHookSpecificOutput, PostToolUseFailureOptions, PostToolUseHookSpecificOutput, PostToolUseOptions, PreCompactOptions, PreToolUseHookSpecificOutput, PreToolUseOptions, SessionEndOptions, SessionStartHookSpecificOutput, SessionStartOptions, SetupHookSpecificOutput, SetupOptions, StopOptions, SubagentStartHookSpecificOutput, SubagentStartOptions, SubagentStopOptions, SyncHookJSONOutput, TaskCompletedOptions, TeammateIdleOptions, UserPromptSubmitHookSpecificOutput, UserPromptSubmitOptions, } from "./outputs.js";
17
17
  export { EXIT_CODES, notificationOutput, permissionRequestOutput, postToolUseFailureOutput, postToolUseOutput, preCompactOutput, preToolUseOutput, sessionEndOutput, sessionStartOutput, setupOutput, stopOutput, subagentStartOutput, subagentStopOutput, taskCompletedOutput, teammateIdleOutput, userPromptSubmitOutput, } from "./outputs.js";
18
18
  export { execute, } from "./runtime.js";
19
19
  export type { ContentContext, PatternCheckResult, ToolUseInput } from "./tool-helpers.js";
@@ -132,6 +132,8 @@ export interface SyncHookJSONOutput {
132
132
  export interface HookOutput {
133
133
  /** JSON-serializable output to write to stdout. */
134
134
  stdout: SyncHookJSONOutput;
135
+ /** Optional message to write to stderr. When present, the runtime exits with code 2 (BLOCK). */
136
+ stderr?: string;
135
137
  }
136
138
  /**
137
139
  * Common options available to all output builders.
@@ -147,12 +149,24 @@ export interface CommonOptions {
147
149
  /** Reason for stopping/blocking (sets exit code to BLOCK). */
148
150
  stopReason?: string;
149
151
  }
152
+ /**
153
+ * Options for exit-code-based hooks (TeammateIdle, TaskCompleted).
154
+ *
155
+ * These hooks use exit codes only, not JSON decision control.
156
+ * When `stderr` is provided, the runtime writes it to stderr and exits with code 2 (BLOCK).
157
+ * When absent, the hook exits with code 0 (SUCCESS).
158
+ */
159
+ export interface ExitCodeOptions {
160
+ /** Message to write to stderr. When present, exits with code 2 (BLOCK). */
161
+ stderr?: string;
162
+ }
150
163
  /**
151
164
  * Base structure for all specific outputs.
152
165
  */
153
166
  interface BaseSpecificOutput<T extends string> {
154
167
  readonly _type: T;
155
168
  stdout: SyncHookJSONOutput;
169
+ stderr?: string;
156
170
  }
157
171
  /**
158
172
  *
@@ -606,39 +620,49 @@ export declare const setupOutput: (options?: CommonOptions & {
606
620
  };
607
621
  /**
608
622
  * Options for the TeammateIdle output builder.
609
- * TeammateIdle hooks only support common options.
623
+ * TeammateIdle hooks use exit codes only, not JSON decision control.
610
624
  */
611
- export type TeammateIdleOptions = CommonOptions;
625
+ export type TeammateIdleOptions = ExitCodeOptions;
612
626
  /**
613
627
  * Creates an output for TeammateIdle hooks.
614
628
  * @param options - Configuration options for the hook output
615
629
  * @returns A TeammateIdleOutput object ready for the runtime
616
630
  * @example
617
631
  * ```typescript
632
+ * // Allow teammate to go idle
618
633
  * teammateIdleOutput({});
634
+ *
635
+ * // Block with feedback
636
+ * teammateIdleOutput({ stderr: 'Continue working: unfinished tasks remain.' });
619
637
  * ```
620
638
  */
621
- export declare const teammateIdleOutput: (options?: CommonOptions) => {
639
+ export declare const teammateIdleOutput: ({ stderr }?: ExitCodeOptions) => {
622
640
  readonly _type: "TeammateIdle";
623
641
  stdout: SyncHookJSONOutput;
642
+ stderr?: string;
624
643
  };
625
644
  /**
626
645
  * Options for the TaskCompleted output builder.
627
- * TaskCompleted hooks only support common options.
646
+ * TaskCompleted hooks use exit codes only, not JSON decision control.
628
647
  */
629
- export type TaskCompletedOptions = CommonOptions;
648
+ export type TaskCompletedOptions = ExitCodeOptions;
630
649
  /**
631
650
  * Creates an output for TaskCompleted hooks.
632
651
  * @param options - Configuration options for the hook output
633
652
  * @returns A TaskCompletedOutput object ready for the runtime
634
653
  * @example
635
654
  * ```typescript
655
+ * // Allow task completion
636
656
  * taskCompletedOutput({});
657
+ *
658
+ * // Block with feedback
659
+ * taskCompletedOutput({ stderr: 'Cannot complete: tests are failing.' });
637
660
  * ```
638
661
  */
639
- export declare const taskCompletedOutput: (options?: CommonOptions) => {
662
+ export declare const taskCompletedOutput: ({ stderr }?: ExitCodeOptions) => {
640
663
  readonly _type: "TaskCompleted";
641
664
  stdout: SyncHookJSONOutput;
665
+ stderr?: string;
642
666
  };
643
667
  /**
644
668
  * @deprecated Use CommonOptions instead
@@ -24,8 +24,8 @@ import type { HookInput } from "./types.js";
24
24
  /**
25
25
  * Converts a SpecificHookOutput to HookOutput for wire format.
26
26
  *
27
- * SpecificHookOutput types have: { _type, exitCode, stdout, stderr? }
28
- * HookOutput has: { exitCode, stdout, stderr? }
27
+ * SpecificHookOutput types have: { _type, stdout, stderr? }
28
+ * HookOutput has: { stdout, stderr? }
29
29
  *
30
30
  * Since output builders now produce wire-format directly, this function
31
31
  * simply strips the `_type` discriminator field.
@@ -36,7 +36,7 @@ import type { HookInput } from "./types.js";
36
36
  * ```typescript
37
37
  * const specificOutput = preToolUseOutput({ hookSpecificOutput: { permissionDecision: 'allow' } });
38
38
  * const hookOutput = convertToHookOutput(specificOutput);
39
- * // hookOutput: { exitCode: 0, stdout: { hookSpecificOutput: { ... } } }
39
+ * // hookOutput: { stdout: { hookSpecificOutput: { ... } } }
40
40
  * ```
41
41
  */
42
42
  export declare function convertToHookOutput(specificOutput: SpecificHookOutput): HookOutput;