Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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();
}
Expand Down Expand Up @@ -207,6 +211,47 @@ $notificationData = [
$hook->success();
```

#### User Prompt Submit Hook

Validate and add context to user prompts:

```php
<?php

require 'vendor/autoload.php';

use BeyondCode\ClaudeHooks\ClaudeHook;
use BeyondCode\ClaudeHooks\Hooks\UserPromptSubmit;

$hook = ClaudeHook::create();

// Ask Claude Code - what is my secret-code?
// It should respond with ABC123 thanks to our injected prompt

if ($hook instanceof UserPromptSubmit) {
$prompt = $hook->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

Expand Down
2 changes: 2 additions & 0 deletions src/ClaudeHook.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
use BeyondCode\ClaudeHooks\Hooks\PreToolUse;
use BeyondCode\ClaudeHooks\Hooks\Stop;
use BeyondCode\ClaudeHooks\Hooks\SubagentStop;
use BeyondCode\ClaudeHooks\Hooks\UserPromptSubmit;

class ClaudeHook
{
protected static array $eventMap = [
'PreToolUse' => PreToolUse::class,
'PostToolUse' => PostToolUse::class,
'Notification' => Notification::class,
'UserPromptSubmit' => UserPromptSubmit::class,
'Stop' => Stop::class,
'SubagentStop' => SubagentStop::class,
];
Expand Down
24 changes: 24 additions & 0 deletions src/Hooks/UserPromptSubmit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace BeyondCode\ClaudeHooks\Hooks;

class UserPromptSubmit extends Hook
{
protected string $prompt;

public function __construct(array $data)
{
parent::__construct($data);
$this->prompt = $data['prompt'] ?? '';
}

public function eventName(): string
{
return 'UserPromptSubmit';
}

public function prompt(): string
{
return $this->prompt;
}
}
19 changes: 19 additions & 0 deletions tests/ClaudeHookTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down Expand Up @@ -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';

Expand Down
22 changes: 22 additions & 0 deletions tests/Hooks/UserPromptSubmitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

use BeyondCode\ClaudeHooks\Hooks\UserPromptSubmit;

beforeEach(function () {
$this->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('');
});