diff --git a/README.md b/README.md index 7e3a29c..8e783fb 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ The SDK automatically creates the appropriate hook type based on the input: ```php use BeyondCode\ClaudeHooks\ClaudeHook; -use BeyondCode\ClaudeHooks\Hooks\{PreToolUse, PostToolUse, Notification, Stop, SubagentStop}; +use BeyondCode\ClaudeHooks\Hooks\{PreToolUse, PostToolUse, Notification, UserPromptSubmit, Stop, SubagentStop}; $hook = ClaudeHook::create(); @@ -88,6 +88,10 @@ if ($hook instanceof Notification) { $title = $hook->title(); } +if ($hook instanceof UserPromptSubmit) { + $prompt = $hook->prompt(); +} + if ($hook instanceof Stop || $hook instanceof SubagentStop) { $isActive = $hook->stopHookActive(); } @@ -207,6 +211,47 @@ $notificationData = [ $hook->success(); ``` +#### User Prompt Submit Hook + +Validate and add context to user prompts: + +```php +prompt(); + $lowerPrompt = strtolower($prompt); + + // Add context via your script and output the "fancy" way + if (str_contains($lowerPrompt, 'secret-code')) { + $contextReminder = "\n\nšŸ” Your secret code is 'ABC123'"; + $modifiedPrompt = $prompt . $contextReminder; + + $hook->response()->merge(['prompt' => $modifiedPrompt])->continue(); + return; + } + + // Add context via your script and output directly to stdout + if (str_contains($lowerPrompt, 'laravel')) { + echo PHP_EOL . PHP_EOL . 'Remember, this is a Laravel project, so use laravel-boost mcp server and related tools.'; + return; + } +} + +// For all other hook types, allow them to proceed +$hook->response()->continue(); +``` + ## Testing diff --git a/src/ClaudeHook.php b/src/ClaudeHook.php index 0504301..744d250 100644 --- a/src/ClaudeHook.php +++ b/src/ClaudeHook.php @@ -8,6 +8,7 @@ use BeyondCode\ClaudeHooks\Hooks\PreToolUse; use BeyondCode\ClaudeHooks\Hooks\Stop; use BeyondCode\ClaudeHooks\Hooks\SubagentStop; +use BeyondCode\ClaudeHooks\Hooks\UserPromptSubmit; class ClaudeHook { @@ -15,6 +16,7 @@ class ClaudeHook 'PreToolUse' => PreToolUse::class, 'PostToolUse' => PostToolUse::class, 'Notification' => Notification::class, + 'UserPromptSubmit' => UserPromptSubmit::class, 'Stop' => Stop::class, 'SubagentStop' => SubagentStop::class, ]; diff --git a/src/Hooks/UserPromptSubmit.php b/src/Hooks/UserPromptSubmit.php new file mode 100644 index 0000000..cfe2422 --- /dev/null +++ b/src/Hooks/UserPromptSubmit.php @@ -0,0 +1,24 @@ +prompt = $data['prompt'] ?? ''; + } + + public function eventName(): string + { + return 'UserPromptSubmit'; + } + + public function prompt(): string + { + return $this->prompt; + } +} diff --git a/tests/ClaudeHookTest.php b/tests/ClaudeHookTest.php index 616f34e..9442a21 100644 --- a/tests/ClaudeHookTest.php +++ b/tests/ClaudeHookTest.php @@ -6,6 +6,7 @@ use BeyondCode\ClaudeHooks\Hooks\PreToolUse; use BeyondCode\ClaudeHooks\Hooks\Stop; use BeyondCode\ClaudeHooks\Hooks\SubagentStop; +use BeyondCode\ClaudeHooks\Hooks\UserPromptSubmit; it('creates PreToolUse hook from stdin', function () { $stdin = json_encode([ @@ -94,6 +95,24 @@ expect($hook->stopHookActive())->toBe(false); }); +it('creates UserPromptSubmit hook from stdin', function () { + $stdin = json_encode([ + 'session_id' => 'test-session', + 'transcript_path' => '/path/to/transcript.jsonl', + 'cwd' => '/path/to/project', + 'hook_event_name' => 'UserPromptSubmit', + 'prompt' => 'Write a function to calculate factorial', + ]); + + $hook = ClaudeHook::fromStdin($stdin); + + expect($hook)->toBeInstanceOf(UserPromptSubmit::class); + expect($hook->eventName())->toBe('UserPromptSubmit'); + expect($hook->prompt())->toBe('Write a function to calculate factorial'); + expect($hook->sessionId())->toBe('test-session'); + expect($hook->transcriptPath())->toBe('/path/to/transcript.jsonl'); +}); + it('throws exception for invalid JSON', function () { $stdin = 'invalid json'; diff --git a/tests/Hooks/UserPromptSubmitTest.php b/tests/Hooks/UserPromptSubmitTest.php new file mode 100644 index 0000000..3081b17 --- /dev/null +++ b/tests/Hooks/UserPromptSubmitTest.php @@ -0,0 +1,22 @@ +data = [ + 'session_id' => 'test-session', + 'transcript_path' => '/path/to/transcript.jsonl', + 'prompt' => 'Write a function to calculate factorial', + ]; +}); + +it('accesses prompt', function () { + $hook = new UserPromptSubmit($this->data); + expect($hook->prompt())->toBe('Write a function to calculate factorial'); +}); + +it('handles missing prompt gracefully', function () { + unset($this->data['prompt']); + $hook = new UserPromptSubmit($this->data); + expect($hook->prompt())->toBe(''); +});