Skip to content

Commit 02f9815

Browse files
committed
wip
1 parent 105ff7c commit 02f9815

File tree

3 files changed

+179
-174
lines changed

3 files changed

+179
-174
lines changed

README.md

Lines changed: 130 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
A Laravel-inspired PHP SDK for building Claude Code hook responses with a clean, fluent API. This SDK makes it easy to create structured JSON responses for Claude Code hooks using an expressive, chainable interface.
88

9+
Claude Code hooks are user-defined shell commands that execute at specific points in Claude Code's lifecycle, providing deterministic control over its behavior. For more details, see the [Claude Code Hooks documentation](https://docs.anthropic.com/en/docs/claude-code/hooks).
10+
911
## Installation
1012

1113
You can install the package via composer:
@@ -16,239 +18,195 @@ composer require beyondcode/claude-hooks-sdk
1618

1719
## Usage
1820

19-
### Basic Examples
21+
### Creating a Claude Hook
2022

21-
#### PreToolUse Hooks
23+
Here's how to create a PHP script that Claude Code can use as a hook:
2224

23-
Block a tool call:
24-
```php
25-
use ClaudeHooks\Hook;
25+
#### Step 1: Create your PHP hook script
2626

27-
Hook::preToolUse()
28-
->block('Command uses deprecated grep instead of ripgrep')
29-
->send();
30-
```
27+
Create a new PHP file (e.g., `validate-code.php`) using the SDK:
3128

32-
Approve a tool call:
3329
```php
34-
Hook::preToolUse()
35-
->approve('Command validated successfully')
36-
->send();
37-
```
30+
<?php
3831

39-
#### PostToolUse Hooks
32+
require 'vendor/autoload.php';
4033

41-
Provide feedback after tool execution:
42-
```php
43-
Hook::postToolUse()
44-
->block('Code formatting violations detected')
45-
->send();
46-
```
34+
use BeyondCode\ClaudeHooks\ClaudeHook;
4735

48-
#### Stop/SubagentStop Hooks
36+
// Read the hook data from stdin.
37+
// This will automatically return the correct Hook instance (for example PreToolUse)
38+
$hook = ClaudeHook::create();
4939

50-
Prevent Claude from stopping:
51-
```php
52-
Hook::stop()
53-
->block('Tests are still running, please wait')
54-
->send();
40+
// Example: Validate bash commands
41+
if ($hook->toolName() === 'Bash') {
42+
$command = $hook->toolInput('command', '');
43+
44+
// Check for potentially dangerous commands
45+
if (str_contains($command, 'rm -rf')) {
46+
// Block the tool call with feedback
47+
$hook->response()->block('Dangerous command detected. Use caution with rm -rf commands.');
48+
}
49+
}
50+
51+
// Allow other tool calls to proceed
52+
$hook->success();
5553
```
5654

57-
### Advanced Examples
55+
#### Step 2: Register your hook in Claude Code
5856

59-
#### Suppress Output
57+
1. Run the `/hooks` command in Claude Code
58+
2. Select the `PreToolUse` hook event (runs before tool execution)
59+
3. Add a matcher (e.g., `Bash` to match shell commands)
60+
4. Add your hook command: `php /path/to/your/validate-code.php`
61+
5. Save to user or project settings
6062

61-
Hide stdout from transcript mode:
62-
```php
63-
Hook::preToolUse()
64-
->approve('Silently approved')
65-
->suppressOutput()
66-
->send();
67-
```
63+
Your hook is now active and will validate commands before Claude Code executes them!
6864

69-
#### Stop Processing
65+
### Hook Types and Methods
66+
67+
The SDK automatically creates the appropriate hook type based on the input:
7068

71-
Stop Claude from continuing with a reason:
7269
```php
73-
Hook::make()
74-
->stopProcessing('System maintenance in progress')
75-
->send();
76-
```
70+
use BeyondCode\ClaudeHooks\ClaudeHook;
71+
use BeyondCode\ClaudeHooks\Hooks\{PreToolUse, PostToolUse, Notification, Stop, SubagentStop};
7772

78-
#### Custom Fields
73+
$hook = ClaudeHook::create();
7974

80-
Add custom fields to the response:
81-
```php
82-
Hook::postToolUse()
83-
->with('customField', 'value')
84-
->merge(['foo' => 'bar', 'baz' => 123])
85-
->send();
86-
```
75+
if ($hook instanceof PreToolUse) {
76+
$toolName = $hook->toolName(); // e.g., "Bash", "Write", "Edit"
77+
$toolInput = $hook->toolInput(); // Full input array
78+
$filePath = $hook->toolInput('file_path'); // Specific input value
79+
}
8780

88-
#### Error Responses
81+
if ($hook instanceof PostToolUse) {
82+
$toolResponse = $hook->toolResponse(); // Full response array
83+
$success = $hook->toolResponse('success', true); // With default value
84+
}
8985

90-
Send a blocking error (exit code 2):
91-
```php
92-
Hook::blockWithError('Invalid file path detected');
93-
```
86+
if ($hook instanceof Notification) {
87+
$message = $hook->message();
88+
$title = $hook->title();
89+
}
9490

95-
Send a non-blocking error:
96-
```php
97-
Hook::make()->fail('Warning: deprecated function used', 1);
91+
if ($hook instanceof Stop || $hook instanceof SubagentStop) {
92+
$isActive = $hook->stopHookActive();
93+
}
9894
```
9995

100-
### Example Hook Scripts
96+
### Response Methods
10197

102-
#### Code Formatter Hook
98+
All hooks provide a fluent response API:
10399

104100
```php
105-
#!/usr/bin/env php
106-
<?php
107-
require __DIR__ . '/vendor/autoload.php';
101+
// Continue processing (default behavior)
102+
$hook->response()->continue();
108103

109-
use ClaudeHooks\Hook;
110-
111-
$input = json_decode(file_get_contents('php://stdin'), true);
112-
$toolInput = $input['tool_input'] ?? [];
113-
$filePath = $toolInput['file_path'] ?? '';
114-
115-
if (!$filePath || !file_exists($filePath)) {
116-
exit(0);
117-
}
104+
// Stop Claude from continuing with a reason
105+
$hook->response()->stop('Reason for stopping');
118106

119-
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
107+
// For PreToolUse: approve or block tool calls
108+
$hook->response()->approve('Optional approval message')->continue();
109+
$hook->response()->block('Required reason for blocking')->continue();
120110

121-
// Run appropriate formatter
122-
$formatters = [
123-
'php' => 'php-cs-fixer fix %s',
124-
'js' => 'prettier --write %s',
125-
'ts' => 'prettier --write %s',
126-
'py' => 'black %s',
127-
];
111+
// Suppress output from transcript mode
112+
$hook->response()->suppressOutput()->continue();
113+
```
128114

129-
if (isset($formatters[$extension])) {
130-
$cmd = sprintf($formatters[$extension], escapeshellarg($filePath));
131-
exec($cmd, $output, $exitCode);
132-
133-
if ($exitCode !== 0) {
134-
Hook::postToolUse()
135-
->block("Formatting failed: " . implode("\n", $output))
136-
->send();
137-
}
138-
}
115+
### Example Hooks
139116

140-
Hook::success();
141-
```
117+
#### Code Formatter Hook
142118

143-
#### Command Validator Hook
119+
Automatically format PHP files after edits:
144120

145121
```php
146-
#!/usr/bin/env php
147122
<?php
148-
require __DIR__ . '/vendor/autoload.php';
149123

150-
use ClaudeHooks\Hook;
124+
require 'vendor/autoload.php';
151125

152-
$input = json_decode(file_get_contents('php://stdin'), true);
126+
use BeyondCode\ClaudeHooks\ClaudeHook;
127+
use BeyondCode\ClaudeHooks\Hooks\PostToolUse;
153128

154-
if ($input['tool_name'] !== 'Bash') {
155-
exit(0);
156-
}
129+
$hook = ClaudeHook::create();
157130

158-
$command = $input['tool_input']['command'] ?? '';
131+
$filePath = $hook->toolInput('file_path', '');
159132

160-
// Validate dangerous commands
161-
$dangerous = ['rm -rf /', 'dd if=', ':(){:|:&};:'];
162-
foreach ($dangerous as $pattern) {
163-
if (strpos($command, $pattern) !== false) {
164-
Hook::preToolUse()
165-
->block("Dangerous command detected: $pattern")
166-
->send();
133+
if (str_ends_with($filePath, '.php')) {
134+
exec("php-cs-fixer fix $filePath", $output, $exitCode);
135+
136+
if ($exitCode !== 0) {
137+
$hook->response()
138+
->suppressOutput()
139+
->merge(['error' => 'Formatting failed'])
140+
->continue();
167141
}
168142
}
169-
170-
// Check for deprecated commands
171-
if (preg_match('/\bgrep\b(?!.*\|)/', $command)) {
172-
Hook::preToolUse()
173-
->block("Use 'rg' (ripgrep) instead of 'grep' for better performance")
174-
->send();
175-
}
176-
177-
// Approve if all checks pass
178-
Hook::preToolUse()
179-
->approve()
180-
->send();
181143
```
182144

183-
#### Stop Hook with Tests
145+
#### Security Validator Hook
146+
147+
Prevent modifications to sensitive files:
184148

185149
```php
186150
#!/usr/bin/env php
187151
<?php
188-
require __DIR__ . '/vendor/autoload.php';
189152

190-
use ClaudeHooks\Hook;
191-
192-
// Check if tests are running
193-
exec('pgrep -f "phpunit|pest"', $output, $exitCode);
194-
195-
if ($exitCode === 0) {
196-
Hook::stop()
197-
->block('Tests are still running. Please wait for completion.')
198-
->send();
199-
}
200-
201-
// Check for uncommitted changes
202-
exec('git diff --quiet', $output, $exitCode);
203-
204-
if ($exitCode !== 0) {
205-
Hook::stop()
206-
->block('You have uncommitted changes. Please commit or stash them first.')
207-
->send();
153+
require 'vendor/autoload.php';
154+
155+
use BeyondCode\ClaudeHooks\ClaudeHook;
156+
use BeyondCode\ClaudeHooks\Hooks\PreToolUse;
157+
158+
$hook = ClaudeHook::fromStdin(file_get_contents('php://stdin'));
159+
160+
if ($hook instanceof PreToolUse) {
161+
// Check file-modifying tools
162+
if (in_array($hook->toolName(), ['Write', 'Edit', 'MultiEdit'])) {
163+
$filePath = $hook->toolInput('file_path', '');
164+
165+
$sensitivePatterns = [
166+
'.env',
167+
'config/database.php',
168+
'storage/oauth-private.key',
169+
];
170+
171+
foreach ($sensitivePatterns as $pattern) {
172+
if (str_contains($filePath, $pattern)) {
173+
$hook->response()->block("Cannot modify sensitive file: $filePath");
174+
}
175+
}
176+
}
208177
}
209178

210-
// Allow stopping
211-
Hook::success();
179+
// Allow all other operations
180+
$hook->response()->continue();
212181
```
213182

214-
### API Reference
215-
216-
#### Static Factory Methods
217-
218-
- `Hook::preToolUse()` - Create a PreToolUse hook response
219-
- `Hook::postToolUse()` - Create a PostToolUse hook response
220-
- `Hook::stop()` - Create a Stop hook response
221-
- `Hook::subagentStop()` - Create a SubagentStop hook response
222-
- `Hook::make()` - Create a generic hook response
183+
#### Notification Handler Hook
223184

224-
#### Decision Methods
185+
Custom notification handling:
225186

226-
- `approve(string $reason = '')` - Approve tool execution (PreToolUse only)
227-
- `block(string $reason)` - Block tool execution or prevent stopping
228-
229-
#### Control Flow Methods
230-
231-
- `continueProcessing()` - Allow Claude to continue (default)
232-
- `stopProcessing(string $stopReason)` - Stop Claude with a reason
233-
- `suppressOutput(bool $suppress = true)` - Hide output from transcript
187+
```php
188+
<?php
234189

235-
#### Data Methods
190+
require 'vendor/autoload.php';
236191

237-
- `with(string $key, $value)` - Add a custom field
238-
- `merge(array $fields)` - Merge multiple fields
239-
- `toArray()` - Get output as array
240-
- `toJson(int $options)` - Get output as JSON string
192+
use BeyondCode\ClaudeHooks\ClaudeHook;
193+
use BeyondCode\ClaudeHooks\Hooks\Notification;
241194

242-
#### Output Methods
195+
$hook = ClaudeHook::create();
243196

244-
- `send(int $exitCode = 0)` - Send JSON response and exit
245-
- `error(string $message)` - Send blocking error (exit 2)
246-
- `fail(string $message, int $exitCode)` - Send non-blocking error
197+
// Send to custom notification system
198+
$notificationData = [
199+
'title' => $hook->title(),
200+
'message' => $hook->message(),
201+
'session' => $hook->sessionId(),
202+
'timestamp' => time()
203+
];
204+
205+
// Send notification to Slack, Discord, etc.
247206

248-
#### Static Helpers
207+
$hook->success();
208+
```
249209

250-
- `Hook::blockWithError(string $message)` - Quick blocking error
251-
- `Hook::success(string $message = '')` - Quick success response
252210

253211
## Testing
254212

composer.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
{
22
"name": "beyondcode/claude-hooks-sdk",
3-
"description": "A Laravel-inspired PHP SDK for building Claude Code hook responses with a fluent API.",
3+
"description": "A PHP SDK for building Claude Code hooks.",
44
"keywords": [
55
"beyondcode",
6+
"claude",
7+
"hooks",
68
"claude-hooks-sdk"
79
],
810
"homepage": "https://github.com/beyondcode/claude-hooks-sdk",
@@ -15,7 +17,7 @@
1517
}
1618
],
1719
"require": {
18-
"php": "^8.1"
20+
"php": "^8.3"
1921
},
2022
"require-dev": {
2123
"pestphp/pest": "^3.0",

0 commit comments

Comments
 (0)