cc-hooks-ts 2.0.65 → 2.0.76
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 +64 -0
- package/dist/index.d.mts +164 -12
- package/dist/index.mjs +53 -13
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -16,8 +16,10 @@ See [examples](./examples) for more usage examples.
|
|
|
16
16
|
- [Advanced Usage](#advanced-usage)
|
|
17
17
|
- [Conditional Hook Execution](#conditional-hook-execution)
|
|
18
18
|
- [Advanced JSON Output](#advanced-json-output)
|
|
19
|
+
- [Async JSON Output (Experimental)](#async-json-output-experimental)
|
|
19
20
|
- [Documentation](#documentation)
|
|
20
21
|
- [Development](#development)
|
|
22
|
+
- [How to follow the upstream changes](#how-to-follow-the-upstream-changes)
|
|
21
23
|
- [License](#license)
|
|
22
24
|
- [Contributing](#contributing)
|
|
23
25
|
|
|
@@ -209,6 +211,45 @@ Use `context.json()` to return structured JSON output with advanced control over
|
|
|
209
211
|
|
|
210
212
|
For detailed information about available JSON fields and their behavior, see the [official documentation](https://docs.anthropic.com/en/docs/claude-code/hooks#advanced:-json-output).
|
|
211
213
|
|
|
214
|
+
### Async JSON Output (Experimental)
|
|
215
|
+
|
|
216
|
+
> [!WARNING]
|
|
217
|
+
> This behavior is undocumented by Anthropic and may change.
|
|
218
|
+
|
|
219
|
+
> [!CAUTION]
|
|
220
|
+
> You must enable verbose output if you want to see async hook outputs like `systemMessage` or `hookSpecificOutput.additionalContext`.
|
|
221
|
+
>
|
|
222
|
+
> You can enable it in Claude Code by going to `/config` and setting "verbose" to true.
|
|
223
|
+
|
|
224
|
+
Claude Code also accepts async hook responses.
|
|
225
|
+
|
|
226
|
+
Use `context.defer()` when you need extra time to compute hook output.
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { defineHook } from "cc-hooks-ts";
|
|
230
|
+
|
|
231
|
+
const hook = defineHook({
|
|
232
|
+
trigger: { PostToolUse: { Read: true } },
|
|
233
|
+
run: (context) =>
|
|
234
|
+
context.defer(
|
|
235
|
+
async () => {
|
|
236
|
+
// Simulate long-running computation
|
|
237
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
event: "PostToolUse",
|
|
241
|
+
output: {
|
|
242
|
+
systemMessage: "Read tool used successfully after async processing!"
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
timeoutMs: 5000 // Optional timeout for the async operation.
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
212
253
|
## Documentation
|
|
213
254
|
|
|
214
255
|
For more detailed information about Claude Code hooks, visit the [official documentation](https://docs.anthropic.com/en/docs/claude-code/hooks).
|
|
@@ -232,6 +273,29 @@ pnpm format
|
|
|
232
273
|
pnpm typecheck
|
|
233
274
|
```
|
|
234
275
|
|
|
276
|
+
### How to follow the upstream changes
|
|
277
|
+
|
|
278
|
+
1. Install the latest version of `@anthropic-ai/claude-agent-sdk` and run `pnpm run check`.
|
|
279
|
+
- If the command passes without errors, there are no type changes.
|
|
280
|
+
|
|
281
|
+
2. Get diff of the types. This example gets the diff between Claude Code 2.0.69 and 2.0.70:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
npm diff --diff=@anthropic-ai/claude-agent-sdk@0.1.69 --diff=@anthropic-ai/claude-agent-sdk@0.1.70 '**/*.d.ts'
|
|
285
|
+
|
|
286
|
+
# Only for humans, You can use dandavison/delta for better diff visualization
|
|
287
|
+
npm diff --diff=@anthropic-ai/claude-agent-sdk@0.1.69 --diff=@anthropic-ai/claude-agent-sdk@0.1.70 '**/*.d.ts' | delta --side-by-side
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
3. Reflect the changes.
|
|
291
|
+
- Edit `src/hooks/` for changed hook input / output types.
|
|
292
|
+
- No need for adding tests in most cases since we are testing the whole type definitions in these files:
|
|
293
|
+
- `src/hooks/input/schemas.test-d.ts`
|
|
294
|
+
- `src/hooks/output/index.test-d.ts`
|
|
295
|
+
- `src/hooks/event.test-d.ts`
|
|
296
|
+
- `src/hooks/permission.test-d.ts`
|
|
297
|
+
- Edit `src/index.ts` for changed tool input / output types.
|
|
298
|
+
|
|
235
299
|
## License
|
|
236
300
|
|
|
237
301
|
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -157,7 +157,7 @@ declare const HookInputSchemas: {
|
|
|
157
157
|
readonly type: v.LiteralSchema<"removeRules", undefined>;
|
|
158
158
|
}, undefined>, v.ObjectSchema<{
|
|
159
159
|
readonly destination: v.UnionSchema<[v.LiteralSchema<"userSettings", undefined>, v.LiteralSchema<"projectSettings", undefined>, v.LiteralSchema<"localSettings", undefined>, v.LiteralSchema<"session", undefined>, v.LiteralSchema<"cliArg", undefined>], undefined>;
|
|
160
|
-
readonly mode: v.UnionSchema<[v.LiteralSchema<"acceptEdits", undefined>, v.LiteralSchema<"bypassPermissions", undefined>, v.LiteralSchema<"default", undefined>, v.LiteralSchema<"dontAsk", undefined>, v.LiteralSchema<"plan", undefined>], undefined>;
|
|
160
|
+
readonly mode: v.UnionSchema<[v.LiteralSchema<"acceptEdits", undefined>, v.LiteralSchema<"bypassPermissions", undefined>, v.LiteralSchema<"default", undefined>, v.LiteralSchema<"dontAsk", undefined>, v.LiteralSchema<"delegate", undefined>, v.LiteralSchema<"plan", undefined>], undefined>;
|
|
161
161
|
readonly type: v.LiteralSchema<"setMode", undefined>;
|
|
162
162
|
}, undefined>, v.ObjectSchema<{
|
|
163
163
|
readonly destination: v.UnionSchema<[v.LiteralSchema<"userSettings", undefined>, v.LiteralSchema<"projectSettings", undefined>, v.LiteralSchema<"localSettings", undefined>, v.LiteralSchema<"session", undefined>, v.LiteralSchema<"cliArg", undefined>], undefined>;
|
|
@@ -290,7 +290,7 @@ declare const permissionUpdateSchema: v.VariantSchema<"type", [v.ObjectSchema<{
|
|
|
290
290
|
readonly type: v.LiteralSchema<"removeRules", undefined>;
|
|
291
291
|
}, undefined>, v.ObjectSchema<{
|
|
292
292
|
readonly destination: v.UnionSchema<[v.LiteralSchema<"userSettings", undefined>, v.LiteralSchema<"projectSettings", undefined>, v.LiteralSchema<"localSettings", undefined>, v.LiteralSchema<"session", undefined>, v.LiteralSchema<"cliArg", undefined>], undefined>;
|
|
293
|
-
readonly mode: v.UnionSchema<[v.LiteralSchema<"acceptEdits", undefined>, v.LiteralSchema<"bypassPermissions", undefined>, v.LiteralSchema<"default", undefined>, v.LiteralSchema<"dontAsk", undefined>, v.LiteralSchema<"plan", undefined>], undefined>;
|
|
293
|
+
readonly mode: v.UnionSchema<[v.LiteralSchema<"acceptEdits", undefined>, v.LiteralSchema<"bypassPermissions", undefined>, v.LiteralSchema<"default", undefined>, v.LiteralSchema<"dontAsk", undefined>, v.LiteralSchema<"delegate", undefined>, v.LiteralSchema<"plan", undefined>], undefined>;
|
|
294
294
|
readonly type: v.LiteralSchema<"setMode", undefined>;
|
|
295
295
|
}, undefined>, v.ObjectSchema<{
|
|
296
296
|
readonly destination: v.UnionSchema<[v.LiteralSchema<"userSettings", undefined>, v.LiteralSchema<"projectSettings", undefined>, v.LiteralSchema<"localSettings", undefined>, v.LiteralSchema<"session", undefined>, v.LiteralSchema<"cliArg", undefined>], undefined>;
|
|
@@ -324,7 +324,28 @@ type HookOutput = {
|
|
|
324
324
|
/**
|
|
325
325
|
* @package
|
|
326
326
|
*/
|
|
327
|
-
type
|
|
327
|
+
type ExtractSyncHookOutput<TEvent$1 extends SupportedHookEvent> = HookOutput extends Record<SupportedHookEvent, unknown> ? HookOutput[TEvent$1] : never;
|
|
328
|
+
/**
|
|
329
|
+
* @package
|
|
330
|
+
*/
|
|
331
|
+
type ExtractAsyncHookOutput<TEvent$1 extends SupportedHookEvent> = _InternalExtractAsyncHookOutput<ExtractSyncHookOutput<TEvent$1>>;
|
|
332
|
+
/**
|
|
333
|
+
* Compute ExtractSyncHookOutput<TEvent> only once for better performance.
|
|
334
|
+
*
|
|
335
|
+
* Only `systemMessage` and `hookSpecificOutput.additionalContext` are read by Claude Code if we are using async hook.
|
|
336
|
+
* (This feature is not documented)
|
|
337
|
+
*
|
|
338
|
+
* @internal
|
|
339
|
+
*/
|
|
340
|
+
type _InternalExtractAsyncHookOutput<Output extends CommonHookOutputs> = Output extends {
|
|
341
|
+
hookSpecificOutput?: {
|
|
342
|
+
additionalContext?: infer TAdditionalContext;
|
|
343
|
+
};
|
|
344
|
+
} ? Pick<Output, "systemMessage"> & {
|
|
345
|
+
hookSpecificOutput?: {
|
|
346
|
+
additionalContext?: TAdditionalContext;
|
|
347
|
+
};
|
|
348
|
+
} : Pick<Output, "systemMessage">;
|
|
328
349
|
/**
|
|
329
350
|
* Common fields of hook outputs
|
|
330
351
|
*
|
|
@@ -507,28 +528,142 @@ interface HookContext<THookTrigger extends HookTrigger> {
|
|
|
507
528
|
/**
|
|
508
529
|
* Cause a blocking error.
|
|
509
530
|
*
|
|
510
|
-
*
|
|
531
|
+
* When called, Claude Code stops processing and reports the error to Claude.
|
|
532
|
+
* The hook exits with code 2, and the error message is fed back to Claude for automatic processing.
|
|
533
|
+
*
|
|
534
|
+
* @example
|
|
535
|
+
* // Block access to sensitive files
|
|
536
|
+
* const hook = defineHook({
|
|
537
|
+
* trigger: { PreToolUse: { Read: true } },
|
|
538
|
+
* run: (context) => {
|
|
539
|
+
* const { file_path } = context.input.tool_input;
|
|
540
|
+
*
|
|
541
|
+
* if (file_path.includes('.env') || file_path.includes('secrets')) {
|
|
542
|
+
* return context.blockingError('Access to sensitive files is not allowed');
|
|
543
|
+
* }
|
|
544
|
+
*
|
|
545
|
+
* return context.success();
|
|
546
|
+
* }
|
|
547
|
+
* });
|
|
511
548
|
*/
|
|
512
549
|
blockingError: (error: string) => HookResponseBlockingError;
|
|
550
|
+
/**
|
|
551
|
+
* Defer processing and produce JSON output after long-running computation.
|
|
552
|
+
*
|
|
553
|
+
* @experimental This behavior is undocumented by Anthropic and may change in future versions
|
|
554
|
+
*
|
|
555
|
+
* @example
|
|
556
|
+
* // Compute analysis with timeout protection
|
|
557
|
+
* const hook = defineHook({
|
|
558
|
+
* trigger: { PostToolUse: { Grep: true } },
|
|
559
|
+
* run: (context) => {
|
|
560
|
+
* return context.defer(
|
|
561
|
+
* async () => {
|
|
562
|
+
* const analysis = await analyzeGrepResults(context.input.tool_response);
|
|
563
|
+
*
|
|
564
|
+
* return {
|
|
565
|
+
* event: "PostToolUse",
|
|
566
|
+
* output: {
|
|
567
|
+
* systemMessage: `Found ${analysis.matchCount} matches`,
|
|
568
|
+
* hookSpecificOutput: {
|
|
569
|
+
* additionalContext: analysis.summary
|
|
570
|
+
* }
|
|
571
|
+
* }
|
|
572
|
+
* };
|
|
573
|
+
* },
|
|
574
|
+
* { timeoutMs: 5000 } // Fail fast if analysis takes too long
|
|
575
|
+
* );
|
|
576
|
+
* }
|
|
577
|
+
* });
|
|
578
|
+
*/
|
|
579
|
+
defer: (handler: () => Awaitable<AsyncHookResultJSON<THookTrigger>>, options?: {
|
|
580
|
+
/**
|
|
581
|
+
* Optional timeout in milliseconds.
|
|
582
|
+
*
|
|
583
|
+
* Claude Code has its own internal timeouts; this setting allows you to specify a shorter timeout
|
|
584
|
+
* and cc-hooks-ts will abort the operation as circuit breaker.
|
|
585
|
+
*
|
|
586
|
+
* @default
|
|
587
|
+
* Claude Code has its own internal timeouts.
|
|
588
|
+
*/
|
|
589
|
+
timeoutMs?: number | undefined;
|
|
590
|
+
}) => HookResponseAsyncJSON<THookTrigger>;
|
|
513
591
|
input: ExtractTriggeredHookInput<THookTrigger>;
|
|
514
592
|
/**
|
|
515
593
|
* Direct access to advanced JSON output.
|
|
516
594
|
*
|
|
595
|
+
* Provides fine-grained control over hook behavior through structured JSON output.
|
|
596
|
+
* This is the most powerful way to control Claude Code's behavior, including
|
|
597
|
+
* permission decisions, input modifications, and additional context.
|
|
598
|
+
*
|
|
517
599
|
* @see {@link https://docs.anthropic.com/en/docs/claude-code/hooks#advanced%3A-json-output}
|
|
600
|
+
*
|
|
601
|
+
* @example
|
|
602
|
+
* // PreToolUse: Deny access with reason
|
|
603
|
+
* const hook = defineHook({
|
|
604
|
+
* trigger: { PreToolUse: { Read: true } },
|
|
605
|
+
* run: (context) => {
|
|
606
|
+
* const { file_path } = context.input.tool_input;
|
|
607
|
+
*
|
|
608
|
+
* if (file_path.includes('.env')) {
|
|
609
|
+
* return context.json({
|
|
610
|
+
* event: "PreToolUse",
|
|
611
|
+
* output: {
|
|
612
|
+
* hookSpecificOutput: {
|
|
613
|
+
* permissionDecision: "deny",
|
|
614
|
+
* permissionDecisionReason: "Access to .env files is restricted for security."
|
|
615
|
+
* }
|
|
616
|
+
* }
|
|
617
|
+
* });
|
|
618
|
+
* }
|
|
619
|
+
*
|
|
620
|
+
* return context.success();
|
|
621
|
+
* }
|
|
622
|
+
* });
|
|
518
623
|
*/
|
|
519
|
-
json: (payload:
|
|
624
|
+
json: (payload: SyncHookResultJSON<THookTrigger>) => HookResponseSyncJSON<THookTrigger>;
|
|
520
625
|
/**
|
|
521
626
|
* Cause a non-blocking error.
|
|
522
627
|
*
|
|
523
|
-
*
|
|
628
|
+
* The message is shown to the user and execution continues.
|
|
629
|
+
*
|
|
630
|
+
* The hook exits with code 1, but Claude Code proceeds with normal operation.
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* // Optional notification without message
|
|
634
|
+
* const hook = defineHook({
|
|
635
|
+
* trigger: { Notification: true },
|
|
636
|
+
* run: (context) => {
|
|
637
|
+
* try {
|
|
638
|
+
* // Attempt to send notification
|
|
639
|
+
* sendNotification(context.input.message);
|
|
640
|
+
* return context.success();
|
|
641
|
+
* } catch (error) {
|
|
642
|
+
* // Fail silently - notification is optional
|
|
643
|
+
* return context.nonBlockingError();
|
|
644
|
+
* }
|
|
645
|
+
* }
|
|
646
|
+
* });
|
|
524
647
|
*/
|
|
525
648
|
nonBlockingError: (message?: string) => HookResponseNonBlockingError;
|
|
526
649
|
/**
|
|
527
650
|
* Indicate successful handling of the hook.
|
|
651
|
+
*
|
|
652
|
+
* The hook exits with code 0. Optionally provides a message for the user or additional context for Claude.
|
|
653
|
+
*
|
|
654
|
+
* @example
|
|
655
|
+
* // Basic success without payload
|
|
656
|
+
* const hook = defineHook({
|
|
657
|
+
* trigger: { PreToolUse: { Read: true } },
|
|
658
|
+
* run: (context) => {
|
|
659
|
+
* // Validation passed
|
|
660
|
+
* return context.success();
|
|
661
|
+
* }
|
|
662
|
+
* });
|
|
528
663
|
*/
|
|
529
664
|
success: (payload?: HookSuccessPayload) => HookResponseSuccess;
|
|
530
665
|
}
|
|
531
|
-
type HookResponse<THookTrigger extends HookTrigger> = HookResponseBlockingError |
|
|
666
|
+
type HookResponse<THookTrigger extends HookTrigger> = HookResponseBlockingError | HookResponseSyncJSON<THookTrigger> | HookResponseAsyncJSON<THookTrigger> | HookResponseNonBlockingError | HookResponseSuccess;
|
|
532
667
|
type HookResponseNonBlockingError = {
|
|
533
668
|
kind: "non-blocking-error";
|
|
534
669
|
payload?: string;
|
|
@@ -553,11 +688,28 @@ type HookSuccessPayload = {
|
|
|
553
688
|
*/
|
|
554
689
|
additionalClaudeContext?: string | undefined;
|
|
555
690
|
};
|
|
556
|
-
type
|
|
557
|
-
kind: "json";
|
|
558
|
-
payload:
|
|
691
|
+
type HookResponseSyncJSON<TTrigger extends HookTrigger> = {
|
|
692
|
+
kind: "json-sync";
|
|
693
|
+
payload: SyncHookResultJSON<TTrigger>;
|
|
559
694
|
};
|
|
560
|
-
type
|
|
695
|
+
type HookResponseAsyncJSON<TTrigger extends HookTrigger> = {
|
|
696
|
+
kind: "json-async";
|
|
697
|
+
timeoutMs?: number | undefined;
|
|
698
|
+
run: () => Awaitable<AsyncHookResultJSON<TTrigger>>;
|
|
699
|
+
};
|
|
700
|
+
type SyncHookResultJSON<TTrigger extends HookTrigger> = { [EventKey in keyof TTrigger]: EventKey extends SupportedHookEvent ? TTrigger[EventKey] extends true | Record<PropertyKey, true> ? {
|
|
701
|
+
/**
|
|
702
|
+
* The name of the event being triggered.
|
|
703
|
+
*
|
|
704
|
+
* Required for proper TypeScript inference.
|
|
705
|
+
*/
|
|
706
|
+
event: EventKey;
|
|
707
|
+
/**
|
|
708
|
+
* The output data for the event.
|
|
709
|
+
*/
|
|
710
|
+
output: ExtractSyncHookOutput<EventKey>;
|
|
711
|
+
} : never : never }[keyof TTrigger];
|
|
712
|
+
type AsyncHookResultJSON<TTrigger extends HookTrigger> = { [EventKey in keyof TTrigger]: EventKey extends SupportedHookEvent ? TTrigger[EventKey] extends true | Record<PropertyKey, true> ? {
|
|
561
713
|
/**
|
|
562
714
|
* The name of the event being triggered.
|
|
563
715
|
*
|
|
@@ -567,7 +719,7 @@ type HookResultJSON<TTrigger extends HookTrigger> = { [EventKey in keyof TTrigge
|
|
|
567
719
|
/**
|
|
568
720
|
* The output data for the event.
|
|
569
721
|
*/
|
|
570
|
-
output:
|
|
722
|
+
output: ExtractAsyncHookOutput<EventKey>;
|
|
571
723
|
} : never : never }[keyof TTrigger];
|
|
572
724
|
//#endregion
|
|
573
725
|
//#region src/define.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,11 @@ function defineHook(definition) {
|
|
|
6
6
|
}
|
|
7
7
|
function createContext(input) {
|
|
8
8
|
return {
|
|
9
|
+
defer: (handler, options) => ({
|
|
10
|
+
kind: "json-async",
|
|
11
|
+
run: handler,
|
|
12
|
+
timeoutMs: options?.timeoutMs
|
|
13
|
+
}),
|
|
9
14
|
blockingError: (error) => ({
|
|
10
15
|
kind: "blocking-error",
|
|
11
16
|
payload: error
|
|
@@ -23,7 +28,7 @@ function createContext(input) {
|
|
|
23
28
|
}
|
|
24
29
|
}),
|
|
25
30
|
json: (payload) => ({
|
|
26
|
-
kind: "json",
|
|
31
|
+
kind: "json-sync",
|
|
27
32
|
payload
|
|
28
33
|
})
|
|
29
34
|
};
|
|
@@ -70,6 +75,7 @@ const permissionUpdateSchema = v.variant("type", [
|
|
|
70
75
|
v.literal("bypassPermissions"),
|
|
71
76
|
v.literal("default"),
|
|
72
77
|
v.literal("dontAsk"),
|
|
78
|
+
v.literal("delegate"),
|
|
73
79
|
v.literal("plan")
|
|
74
80
|
]),
|
|
75
81
|
type: v.literal("setMode")
|
|
@@ -166,32 +172,66 @@ async function runHook(def) {
|
|
|
166
172
|
const parsed = v.parse(inputSchema, JSON.parse(rawInput));
|
|
167
173
|
eventName = parsed.hook_event_name;
|
|
168
174
|
const result = await run(createContext(parsed));
|
|
169
|
-
handleHookResult(eventName, result);
|
|
175
|
+
await handleHookResult(eventName, result);
|
|
170
176
|
} catch (error) {
|
|
171
|
-
handleHookResult(eventName, {
|
|
177
|
+
await handleHookResult(eventName, {
|
|
172
178
|
kind: "non-blocking-error",
|
|
173
179
|
payload: `Error in hook: ${error instanceof Error ? error.message : String(error)}`
|
|
174
180
|
});
|
|
175
181
|
}
|
|
176
182
|
}
|
|
177
|
-
function handleHookResult(eventName, hookResult) {
|
|
183
|
+
async function handleHookResult(eventName, hookResult) {
|
|
178
184
|
switch (hookResult.kind) {
|
|
179
|
-
case "success":
|
|
180
|
-
if (eventName === "UserPromptSubmit" || eventName === "SessionStart") {
|
|
181
|
-
if (isNonEmptyString(hookResult.payload.additionalClaudeContext)) console.log(hookResult.payload.additionalClaudeContext);
|
|
182
|
-
return process.exit(0);
|
|
183
|
-
}
|
|
184
|
-
if (isNonEmptyString(hookResult.payload.messageForUser)) console.log(hookResult.payload.messageForUser);
|
|
185
|
-
return process.exit(0);
|
|
186
185
|
case "blocking-error":
|
|
187
186
|
if (hookResult.payload) console.error(hookResult.payload);
|
|
188
187
|
return process.exit(2);
|
|
188
|
+
case "json-async": {
|
|
189
|
+
const userTimeout = hookResult.timeoutMs;
|
|
190
|
+
const startAsync = {
|
|
191
|
+
async: true,
|
|
192
|
+
asyncTimeout: userTimeout ?? void 0
|
|
193
|
+
};
|
|
194
|
+
console.log(JSON.stringify(startAsync));
|
|
195
|
+
const safeInvokeDeferredHook = async () => {
|
|
196
|
+
try {
|
|
197
|
+
return {
|
|
198
|
+
isError: false,
|
|
199
|
+
payload: await hookResult.run()
|
|
200
|
+
};
|
|
201
|
+
} catch (error) {
|
|
202
|
+
return {
|
|
203
|
+
isError: true,
|
|
204
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
let deferredResult;
|
|
209
|
+
if (userTimeout == null) deferredResult = await safeInvokeDeferredHook();
|
|
210
|
+
else deferredResult = await Promise.race([safeInvokeDeferredHook(), new Promise((resolve) => setTimeout(() => resolve({
|
|
211
|
+
isError: true,
|
|
212
|
+
reason: `Exceeded user specified timeout: ${userTimeout}ms`
|
|
213
|
+
}), userTimeout + 5e3))]);
|
|
214
|
+
if (deferredResult.isError) {
|
|
215
|
+
if (isNonEmptyString(deferredResult.reason)) console.error(`Async hook execution failed: ${deferredResult.reason}`);
|
|
216
|
+
return process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
console.log(JSON.stringify(deferredResult.payload.output));
|
|
219
|
+
return process.exit(0);
|
|
220
|
+
}
|
|
221
|
+
case "json-sync":
|
|
222
|
+
console.log(JSON.stringify(hookResult.payload.output));
|
|
223
|
+
return process.exit(0);
|
|
189
224
|
case "non-blocking-error":
|
|
190
225
|
if (isNonEmptyString(hookResult.payload)) console.error(hookResult.payload);
|
|
191
226
|
return process.exit(1);
|
|
192
|
-
case "
|
|
193
|
-
|
|
227
|
+
case "success":
|
|
228
|
+
if (eventName === "UserPromptSubmit" || eventName === "SessionStart") {
|
|
229
|
+
if (isNonEmptyString(hookResult.payload.additionalClaudeContext)) console.log(hookResult.payload.additionalClaudeContext);
|
|
230
|
+
return process.exit(0);
|
|
231
|
+
}
|
|
232
|
+
if (isNonEmptyString(hookResult.payload.messageForUser)) console.log(hookResult.payload.messageForUser);
|
|
194
233
|
return process.exit(0);
|
|
234
|
+
default: throw new Error(`Unknown hook result kind: ${JSON.stringify(hookResult)}`);
|
|
195
235
|
}
|
|
196
236
|
}
|
|
197
237
|
function extractInputSchemaFromTrigger(trigger) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-hooks-ts",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.76",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Write claude code hooks with type safety",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -43,26 +43,26 @@
|
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@arethetypeswrong/core": "0.18.2",
|
|
46
|
-
"@types/node": "
|
|
46
|
+
"@types/node": "25.0.3",
|
|
47
47
|
"@typescript/native-preview": "^7.0.0-dev.20251108.1",
|
|
48
48
|
"@virtual-live-lab/eslint-config": "2.3.1",
|
|
49
49
|
"@virtual-live-lab/tsconfig": "2.1.21",
|
|
50
|
-
"eslint": "9.39.
|
|
50
|
+
"eslint": "9.39.2",
|
|
51
51
|
"eslint-plugin-import-access": "3.1.0",
|
|
52
|
-
"oxfmt": "0.
|
|
52
|
+
"oxfmt": "0.19.0",
|
|
53
53
|
"pkg-pr-new": "0.0.62",
|
|
54
|
-
"publint": "0.3.
|
|
55
|
-
"release-it": "19.0
|
|
54
|
+
"publint": "0.3.16",
|
|
55
|
+
"release-it": "19.1.0",
|
|
56
56
|
"release-it-pnpm": "4.6.6",
|
|
57
|
-
"tsdown": "0.
|
|
57
|
+
"tsdown": "0.18.1",
|
|
58
58
|
"type-fest": "5.3.1",
|
|
59
59
|
"typescript": "5.9.3",
|
|
60
|
-
"typescript-eslint": "8.
|
|
60
|
+
"typescript-eslint": "8.50.0",
|
|
61
61
|
"unplugin-unused": "0.5.6",
|
|
62
|
-
"vitest": "4.0.
|
|
62
|
+
"vitest": "4.0.16"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@anthropic-ai/claude-agent-sdk": "0.1.
|
|
65
|
+
"@anthropic-ai/claude-agent-sdk": "0.1.76",
|
|
66
66
|
"valibot": "^1.1.0"
|
|
67
67
|
},
|
|
68
68
|
"scripts": {
|