cc-hooks-ts 2.0.56 → 2.0.70

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 CHANGED
@@ -1,37 +1,68 @@
1
1
  # cc-hooks-ts
2
2
 
3
- Define Claude Code hooks with full type safety using TypeScript and Valibot validation.
3
+ Define Claude Code hooks with full type safety using TypeScript.
4
+
5
+ See [examples](./examples) for more usage examples.
6
+
7
+ <!-- TOC -->
8
+
9
+ - [cc-hooks-ts](#cc-hooks-ts)
10
+ - [Installation](#installation)
11
+ - [Basic Usage](#basic-usage)
12
+ - [Define a Hook](#define-a-hook)
13
+ - [Configure Claude Code](#configure-claude-code)
14
+ - [Tool Specific Hooks](#tool-specific-hooks)
15
+ - [Custom Tool Types Support](#custom-tool-types-support)
16
+ - [Advanced Usage](#advanced-usage)
17
+ - [Conditional Hook Execution](#conditional-hook-execution)
18
+ - [Advanced JSON Output](#advanced-json-output)
19
+ - [Documentation](#documentation)
20
+ - [Development](#development)
21
+ - [How to follow the upstream changes](#how-to-follow-the-upstream-changes)
22
+ - [License](#license)
23
+ - [Contributing](#contributing)
24
+
25
+ <!-- /TOC -->
4
26
 
5
27
  > [!NOTE]
6
- > Beginning with versions equal to 2.0.0 or 2.0.42 and above, we started releasing using the same version numbers as Claude Code.
7
- > This enables us to support newer type definitions while preserving maximum compatibility.
28
+ > Starting with versions 2.0.42, we will raise our version number to match Claude Code whenever Hook-related changes occur.
29
+ >
30
+ > This ensures we can adopt newer type definitions while maintaining compatibility.
8
31
 
9
32
  ## Installation
10
33
 
11
34
  ```bash
12
- npx nypm add cc-hooks-ts
35
+ # npm
36
+ npm i cc-hooks-ts
37
+
38
+ # yarn
39
+ yarn add cc-hooks-ts
40
+
41
+ # pnpm
42
+ pnpm add cc-hooks-ts
43
+
44
+ # Bun
45
+ bun add cc-hooks-ts
46
+
47
+ # Deno
48
+ deno add npm:cc-hooks-ts
13
49
  ```
14
50
 
15
51
  ## Basic Usage
16
52
 
17
53
  ### Define a Hook
18
54
 
19
- Example of running a simple SessionStart hook on Bun:
20
-
21
55
  ```typescript
22
56
  import { defineHook } from "cc-hooks-ts";
23
57
 
24
- const sessionHook = defineHook({
58
+ const hook = defineHook({
59
+ // Specify the event(s) that trigger this hook.
25
60
  trigger: {
26
- // Specify the hook event to listen for
27
- SessionStart: true,
28
- PreToolUse: {
29
- // PreToolUser and PostToolUse can be tool-specific. (affects type of context)
30
- Read: true
31
- }
61
+ SessionStart: true
32
62
  },
63
+ // Implement what you want to do.
33
64
  run: (context) => {
34
- // do something great
65
+ // Do something great here
35
66
  return context.success({
36
67
  messageForUser: "Welcome to your coding session!"
37
68
  });
@@ -41,11 +72,11 @@ const sessionHook = defineHook({
41
72
  // import.meta.main is available in Node.js 24.2+ and Bun and Deno
42
73
  if (import.meta.main) {
43
74
  const { runHook } = await import("cc-hooks-ts");
44
- await runHook(sessionHook);
75
+ await runHook(hook);
45
76
  }
46
77
  ```
47
78
 
48
- ### Call from Claude Code
79
+ ### Configure Claude Code
49
80
 
50
81
  Then, load defined hooks in your Claude Code settings at `~/.claude/settings.json`.
51
82
 
@@ -57,7 +88,7 @@ Then, load defined hooks in your Claude Code settings at `~/.claude/settings.jso
57
88
  "hooks": [
58
89
  {
59
90
  "type": "command",
60
- "command": "bun run --silent path/to/your/sessionHook.ts"
91
+ "command": "bun run -i --silent path/to/your/sessionHook.ts"
61
92
  }
62
93
  ]
63
94
  }
@@ -66,111 +97,122 @@ Then, load defined hooks in your Claude Code settings at `~/.claude/settings.jso
66
97
  }
67
98
  ```
68
99
 
69
- ## Custom Tool Type Support
70
-
71
- For better type inference in PreToolUse and PostToolUse hooks, you can extend the `ToolSchema` interface to define your own tool types:
100
+ ## Tool Specific Hooks
72
101
 
73
- ### Adding Custom Tool Definitions
102
+ In `PreToolUse`, `PostToolUse`, and `PostToolUseFailure` events, you can define hooks specific to tools by specifying tool names in the trigger configuration.
74
103
 
75
- Extend the `ToolSchema` interface to add custom tool definitions:
104
+ For example, you can create a hook that only runs before the `Read` tool is used:
76
105
 
77
106
  ```typescript
78
- // Use "npm:cc-hooks-ts" for Deno
79
- declare module "cc-hooks-ts" {
80
- interface ToolSchema {
81
- MyCustomTool: {
82
- input: {
83
- customParam: string;
84
- optionalParam?: number;
85
- };
86
- response: {
87
- result: string;
88
- };
89
- };
107
+ const preReadHook = defineHook({
108
+ trigger: { PreToolUse: { Read: true } },
109
+ run: (context) => {
110
+ // context.input.tool_input is typed as { file_path: string; limit?: number; offset?: number; }
111
+ const { file_path } = context.input.tool_input;
112
+
113
+ if (file_path.includes('.env')) {
114
+ return context.blockingError('Cannot read environment files');
115
+ }
116
+
117
+ return context.success();
90
118
  }
119
+ });
120
+
121
+ if (import.meta.main) {
122
+ const { runHook } = await import("cc-hooks-ts");
123
+ await runHook(preReadHook);
91
124
  }
92
125
  ```
93
126
 
94
- Now you can use your custom tool with full type safety:
127
+ Then configure it in Claude Code settings:
95
128
 
96
- ```typescript
97
- const customToolHook = defineHook({
98
- trigger: { PreToolUse: { MyCustomTool: true } },
99
- run: (context) => {
100
- // context.input.tool_input is typed as { customParam: string; optionalParam?: number; }
101
- const { customParam, optionalParam } = context.input.tool_input;
102
- return context.success();
129
+ ```json
130
+ {
131
+ "hooks": {
132
+ "PreToolUse": [
133
+ {
134
+ "matcher": "Read",
135
+ "hooks": [
136
+ {
137
+ "type": "command",
138
+ "command": "bun run -i --silent path/to/your/preReadHook.ts"
139
+ }
140
+ ]
141
+ }
142
+ ]
103
143
  }
104
- });
144
+ }
105
145
  ```
106
146
 
107
- ## API Reference
147
+ ### Custom Tool Types Support
108
148
 
109
- ### defineHook Function
149
+ You can add support for custom tools by extending the tool type definitions.
110
150
 
111
- Creates type-safe hook definitions with full TypeScript inference:
151
+ This is useful when you want to your MCP-defined tools to have type-safe hook inputs.
112
152
 
113
153
  ```typescript
114
- // Tool-specific PreToolUse hook
115
- const readHook = defineHook({
116
- trigger: { PreToolUse: { Read: true } },
154
+ import { defineHook } from "cc-hooks-ts";
155
+
156
+ // Example: type-safe hooks for DeepWiki MCP Server tools
157
+ declare module "cc-hooks-ts" {
158
+ interface ToolSchema {
159
+ mcp__deepwiki__ask_question: {
160
+ input: {
161
+ question: string;
162
+ repoName: string;
163
+ };
164
+ response: unknown;
165
+ };
166
+ }
167
+ }
168
+
169
+ const deepWikiHook = defineHook({
170
+ trigger: { PreToolUse: { mcp__deepwiki__ask_question: true } },
117
171
  run: (context) => {
118
- // context.input.tool_input is typed as { file_path: string }
119
- const { file_path } = context.input.tool_input;
172
+ // context.input.tool_input is typed as { question: string; repoName: string; }
173
+ const { question, repoName } = context.input.tool_input;
120
174
 
121
- if (file_path.includes('.env')) {
122
- return context.blockingError('Cannot read environment files');
175
+ if (question.length > 500) {
176
+ return context.blockingError('Question is too long');
123
177
  }
124
178
 
125
179
  return context.success();
126
180
  }
127
181
  });
182
+ ```
183
+
184
+ ## Advanced Usage
185
+
186
+ ### Conditional Hook Execution
128
187
 
129
- // Multiple event triggers
130
- const multiEventHook = defineHook({
188
+ You can conditionally execute hooks based on runtime logic using the `shouldRun` function.
189
+ If `shouldRun` returns `false`, the hook will be skipped.
190
+
191
+ ```ts
192
+ import { defineHook } from "cc-hooks-ts";
193
+
194
+ const hook = defineHook({
131
195
  trigger: {
132
- PreToolUse: { Read: true, WebFetch: true },
133
- PostToolUse: { Read: true }
196
+ Notification: true
134
197
  },
135
- // Optional: Define when the hook should run.
136
- shouldRun: () => process.env.NODE_ENV === 'development',
198
+ // Only run this hook on macOS
199
+ shouldRun: () => process.platform === "darwin",
137
200
  run: (context) => {
138
- // Handle different events and tools based on context.input
139
- return context.success();
201
+ // Some macOS-specific logic like sending a notification using AppleScript
202
+ return context.success()
140
203
  }
141
204
  });
142
205
  ```
143
206
 
144
- ### runHook Function
145
-
146
- Executes hooks with complete lifecycle management:
207
+ ### Advanced JSON Output
147
208
 
148
- - Reads input from stdin
149
- - Validates input using Valibot schemas
150
- - Creates typed context
151
- - Executes hook handler
152
- - Formats and outputs results
209
+ Use `context.json()` to return structured JSON output with advanced control over hook behavior.
153
210
 
154
- ```typescript
155
- await runHook(hook);
156
- ```
157
-
158
- ### Hook Context
211
+ 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).
159
212
 
160
- The context provides strongly typed input access and response helpers:
213
+ ## Documentation
161
214
 
162
- ```typescript
163
- run: (context) => {
164
- // Typed input based on trigger configuration
165
- const input = context.input;
166
-
167
- // Response helpers
168
- return context.success({ messageForUser: "Success!" });
169
- // or context.blockingError("Error occurred");
170
- // or context.nonBlockingError("Warning message");
171
- // or context.json({ event: "EventName", output: {...} });
172
- }
173
- ```
215
+ For more detailed information about Claude Code hooks, visit the [official documentation](https://docs.anthropic.com/en/docs/claude-code/hooks).
174
216
 
175
217
  ## Development
176
218
 
@@ -191,9 +233,28 @@ pnpm format
191
233
  pnpm typecheck
192
234
  ```
193
235
 
194
- ## Documentation
236
+ ### How to follow the upstream changes
195
237
 
196
- For more detailed information about Claude Code hooks, visit the [official documentation](https://docs.anthropic.com/en/docs/claude-code/hooks).
238
+ 1. Install the latest version of `@anthropic-ai/claude-agent-sdk` and run `pnpm run check`.
239
+ - If the command passes without errors, there are no type changes.
240
+
241
+ 2. Get diff of the types. This example gets the diff between Claude Code 2.0.69 and 2.0.70:
242
+
243
+ ```bash
244
+ npm diff --diff=@anthropic-ai/claude-agent-sdk@0.1.69 --diff=@anthropic-ai/claude-agent-sdk@0.1.70 '**/*.d.ts'
245
+
246
+ # You can use dandavison/delta for better diff visualization
247
+ 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
248
+ ```
249
+
250
+ 3. Reflect the changes.
251
+ - Edit `src/hooks/` for changed hook input / output types.
252
+ - No need for adding tests in most cases since we are testing the whole type definitions in these files:
253
+ - `src/hooks/input/schemas.test-d.ts`
254
+ - `src/hooks/output/index.test-d.ts`
255
+ - `src/hooks/event.test-d.ts`
256
+ - `src/hooks/permission.test-d.ts`
257
+ - Edit `src/index.ts` for changed tool input / output types.
197
258
 
198
259
  ## License
199
260
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as v from "valibot";
2
- import { AgentInput, BashInput, BashOutputInput, ExitPlanModeInput, FileEditInput, FileReadInput, FileWriteInput, GlobInput, GrepInput, KillShellInput, ListMcpResourcesInput, NotebookEditInput, ReadMcpResourceInput, TodoWriteInput, WebFetchInput, WebSearchInput } from "@anthropic-ai/claude-agent-sdk/sdk-tools";
2
+ import { AgentInput, BashInput, ExitPlanModeInput, FileEditInput, FileReadInput, FileWriteInput, GlobInput, GrepInput, KillShellInput, ListMcpResourcesInput, NotebookEditInput, ReadMcpResourceInput, TaskOutputInput, TodoWriteInput, WebFetchInput, WebSearchInput } from "@anthropic-ai/claude-agent-sdk/sdk-tools";
3
3
 
4
4
  //#region src/utils/types.d.ts
5
5
  type Awaitable<T> = Promise<T> | T;
@@ -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>;
@@ -747,7 +747,7 @@ interface ToolSchema {
747
747
  };
748
748
  };
749
749
  BashOutput: {
750
- input: BashOutputInput;
750
+ input: TaskOutputInput;
751
751
  response: unknown;
752
752
  };
753
753
  Edit: {
package/dist/index.mjs CHANGED
@@ -70,6 +70,7 @@ const permissionUpdateSchema = v.variant("type", [
70
70
  v.literal("bypassPermissions"),
71
71
  v.literal("default"),
72
72
  v.literal("dontAsk"),
73
+ v.literal("delegate"),
73
74
  v.literal("plan")
74
75
  ]),
75
76
  type: v.literal("setMode")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-hooks-ts",
3
- "version": "2.0.56",
3
+ "version": "2.0.70",
4
4
  "type": "module",
5
5
  "description": "Write claude code hooks with type safety",
6
6
  "sideEffects": false,
@@ -43,33 +43,33 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "@arethetypeswrong/core": "0.18.2",
46
- "@biomejs/biome": "2.3.8",
47
- "@types/node": "24.10.1",
46
+ "@types/node": "25.0.1",
48
47
  "@typescript/native-preview": "^7.0.0-dev.20251108.1",
49
48
  "@virtual-live-lab/eslint-config": "2.3.1",
50
49
  "@virtual-live-lab/tsconfig": "2.1.21",
51
- "eslint": "9.39.1",
50
+ "eslint": "9.39.2",
52
51
  "eslint-plugin-import-access": "3.1.0",
53
- "pkg-pr-new": "0.0.60",
54
- "publint": "0.3.15",
55
- "release-it": "19.0.6",
52
+ "oxfmt": "0.17.0",
53
+ "pkg-pr-new": "0.0.62",
54
+ "publint": "0.3.16",
55
+ "release-it": "19.1.0",
56
56
  "release-it-pnpm": "4.6.6",
57
- "tsdown": "0.16.8",
58
- "type-fest": "5.2.0",
57
+ "tsdown": "0.17.2",
58
+ "type-fest": "5.3.1",
59
59
  "typescript": "5.9.3",
60
- "typescript-eslint": "8.48.0",
60
+ "typescript-eslint": "8.49.0",
61
61
  "unplugin-unused": "0.5.6",
62
- "vitest": "4.0.14"
62
+ "vitest": "4.0.15"
63
63
  },
64
64
  "dependencies": {
65
- "@anthropic-ai/claude-agent-sdk": "0.1.56",
65
+ "@anthropic-ai/claude-agent-sdk": "0.1.70",
66
66
  "valibot": "^1.1.0"
67
67
  },
68
68
  "scripts": {
69
69
  "check": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run test && pnpm run build",
70
70
  "lint": "eslint --max-warnings 0",
71
- "format": "biome format --write",
72
- "format:check": "biome format --reporter=github",
71
+ "format": "oxfmt",
72
+ "format:check": "oxfmt --check",
73
73
  "typecheck": "tsgo --noEmit",
74
74
  "test": "vitest --run",
75
75
  "build": "tsdown",