claude_hooks 0.1.1 โ 0.1.2
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.
- checksums.yaml +4 -4
- data/README.md +137 -10
- data/example_dotclaude/hooks/entrypoints/user_prompt_submit.rb +1 -10
- data/example_dotclaude/hooks/handlers/user_prompt_submit/append_rules.rb +2 -23
- data/example_dotclaude/hooks/handlers/user_prompt_submit/log_user_prompt.rb +1 -14
- data/lib/claude_hooks/base.rb +3 -3
- data/lib/claude_hooks/cli.rb +119 -0
- data/lib/claude_hooks/configuration.rb +9 -2
- data/lib/claude_hooks/version.rb +2 -2
- data/lib/claude_hooks.rb +1 -0
- metadata +2 -3
- data/example_dotclaude/hooks/entrypoints/user_prompt_submit/append_rules.rb +0 -66
- data/example_dotclaude/hooks/entrypoints/user_prompt_submit/log_user_prompt.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 05073a7e1f50d4391f4afddff10e781bf70c1c1a5ec15b4d8c5cf7959827ca53
|
4
|
+
data.tar.gz: a477cb849efb154ac3452150030bde9a4219c82887afae7b3fded7d9d01b482c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca053e3783d088baebefa2ffed240a63f624e053dfbaa10e1513191b20428f152414d281a782e2a76ba24490b9d25033271c75c47f85e89785aec7570bcc92ff
|
7
|
+
data.tar.gz: 54baf322b8031460dfcf92998c86950dd84f25b67d6b3e78270f5951dce81d169b7d355a1e6498c31fd33442572943c93795bce1fbbcb359b32ceecb145af2d6
|
data/README.md
CHANGED
@@ -19,6 +19,7 @@ Here's how to create a simple hook:
|
|
19
19
|
1. **Create a simple hook script**
|
20
20
|
```ruby
|
21
21
|
#!/usr/bin/env ruby
|
22
|
+
require 'json'
|
22
23
|
require 'claude_hooks'
|
23
24
|
|
24
25
|
# Inherit from the right hook type class to get access to helper methods
|
@@ -72,23 +73,31 @@ That's it! Your hook will now add context to every user prompt. ๐
|
|
72
73
|
|
73
74
|
## ๐ฆ Installation
|
74
75
|
|
75
|
-
|
76
|
+
Install it globally (simpler):
|
77
|
+
|
78
|
+
```bash
|
79
|
+
$ gem install claude_hooks
|
80
|
+
```
|
81
|
+
|
82
|
+
**Note:** Claude Code itself will still use the system-installed gem, not the bundled version unless you use `bundle exec` to run it in your `.claude/settings.json`.
|
83
|
+
|
84
|
+
Or add it to your Gemfile (you can add a Gemfile in your `.claude` directory if needed):
|
85
|
+
|
76
86
|
|
77
87
|
```ruby
|
88
|
+
# .claude/Gemfile
|
89
|
+
source 'https://rubygems.org'
|
78
90
|
gem 'claude_hooks'
|
79
91
|
```
|
80
92
|
|
81
|
-
And then
|
93
|
+
And then run:
|
82
94
|
|
83
95
|
```bash
|
84
96
|
$ bundle install
|
85
97
|
```
|
86
98
|
|
87
|
-
|
88
|
-
|
89
|
-
```bash
|
90
|
-
$ gem install claude_hooks
|
91
|
-
```
|
99
|
+
> [!WARNING]
|
100
|
+
> If you use a Gemfile, you need to use `bundle exec` to run your hooks in your `.claude/settings.json`.
|
92
101
|
|
93
102
|
### ๐ง Configuration
|
94
103
|
|
@@ -225,6 +234,14 @@ end
|
|
225
234
|
- [๐จ Advices](#-advices)
|
226
235
|
- [โ ๏ธ Troubleshooting](#๏ธ-troubleshooting)
|
227
236
|
- [Make your entrypoint scripts executable](#make-your-entrypoint-scripts-executable)
|
237
|
+
- [๐งช CLI Debugging](#-cli-debugging)
|
238
|
+
- [Basic Usage](#basic-usage)
|
239
|
+
- [Customization with Blocks](#customization-with-blocks)
|
240
|
+
- [Testing Methods](#testing-methods)
|
241
|
+
- [1. Test with STDIN (default)](#1-test-with-stdin-default)
|
242
|
+
- [2. Test with default sample data instead of STDIN](#2-test-with-default-sample-data-instead-of-stdin)
|
243
|
+
- [3. Test with Sample Data + Customization](#3-test-with-sample-data--customization)
|
244
|
+
- [Example Hook with CLI Testing](#example-hook-with-cli-testing)
|
228
245
|
- [๐ Debugging](#-debugging)
|
229
246
|
- [Test an individual entrypoint](#test-an-individual-entrypoint)
|
230
247
|
|
@@ -579,6 +596,14 @@ log <<~TEXT
|
|
579
596
|
TEXT
|
580
597
|
```
|
581
598
|
|
599
|
+
You can also use the logger from an entrypoint script:
|
600
|
+
```ruby
|
601
|
+
require 'claude_hooks'
|
602
|
+
|
603
|
+
logger = ClaudeHooks::Logger.new("TEST-SESSION-01", 'entrypoint')
|
604
|
+
logger.log "Simple message"
|
605
|
+
```
|
606
|
+
|
582
607
|
#### Log File Location
|
583
608
|
Logs are written to session-specific files in the configured log directory:
|
584
609
|
- **Defaults to**: `~/.claude/logs/hooks/session-{session_id}.log`
|
@@ -737,7 +762,7 @@ exit 0
|
|
737
762
|
For the operation to stop for a UserPromptSubmit hook, you would return structured JSON data followed by `exit 1`:
|
738
763
|
|
739
764
|
```ruby
|
740
|
-
|
765
|
+
STDERR.puts JSON.generate({
|
741
766
|
continue: false,
|
742
767
|
stopReason: "JSON parsing error: #{e.message}",
|
743
768
|
suppressOutput: false
|
@@ -746,13 +771,13 @@ exit 1
|
|
746
771
|
```
|
747
772
|
|
748
773
|
> [!WARNING]
|
749
|
-
> Don't forget to use
|
774
|
+
> Don't forget to use `STDERR.puts` to output the JSON to STDERR.
|
750
775
|
|
751
776
|
|
752
777
|
## ๐จ Advices
|
753
778
|
|
754
779
|
1. **Logging**: Use `log()` method instead of `puts` to avoid interfering with JSON output
|
755
|
-
2. **Error Handling**: Hooks should handle their own errors and use the `log` method for debugging. For errors, don't forget to exit with the right exit code (1, 2) and output the JSON indicating the error to STDERR using
|
780
|
+
2. **Error Handling**: Hooks should handle their own errors and use the `log` method for debugging. For errors, don't forget to exit with the right exit code (1, 2) and output the JSON indicating the error to STDERR using `STDERR.puts`.
|
756
781
|
3. **Output Format**: Always return `output_data` or `nil` from your `call` method
|
757
782
|
4. **Path Management**: Use `path_for()` for all file operations relative to the Claude base directory
|
758
783
|
|
@@ -767,6 +792,108 @@ chmod +x ~/.claude/hooks/entrypoints/user_prompt_submit.rb
|
|
767
792
|
```
|
768
793
|
|
769
794
|
|
795
|
+
## ๐งช CLI Debugging
|
796
|
+
|
797
|
+
The `ClaudeHooks::CLI` module provides utilities to simplify testing hooks in isolation. Instead of writing repetitive JSON parsing and error handling code, you can use the CLI test runner.
|
798
|
+
|
799
|
+
### Basic Usage
|
800
|
+
|
801
|
+
Replace the traditional testing boilerplate:
|
802
|
+
|
803
|
+
```ruby
|
804
|
+
# Old way (15+ lines of repetitive code)
|
805
|
+
if __FILE__ == $0
|
806
|
+
begin
|
807
|
+
require 'json'
|
808
|
+
input_data = JSON.parse(STDIN.read)
|
809
|
+
hook = MyHook.new(input_data)
|
810
|
+
result = hook.call
|
811
|
+
puts JSON.generate(result)
|
812
|
+
rescue StandardError => e
|
813
|
+
STDERR.puts "Error: #{e.message}"
|
814
|
+
puts JSON.generate({
|
815
|
+
continue: false,
|
816
|
+
stopReason: "Error: #{e.message}",
|
817
|
+
suppressOutput: false
|
818
|
+
})
|
819
|
+
exit 1
|
820
|
+
end
|
821
|
+
end
|
822
|
+
```
|
823
|
+
|
824
|
+
With the simple CLI test runner:
|
825
|
+
|
826
|
+
```ruby
|
827
|
+
# New way (1 line!)
|
828
|
+
if __FILE__ == $0
|
829
|
+
ClaudeHooks::CLI.test_runner(MyHook)
|
830
|
+
end
|
831
|
+
```
|
832
|
+
|
833
|
+
### Customization with Blocks
|
834
|
+
|
835
|
+
You can customize the input data for testing using blocks:
|
836
|
+
|
837
|
+
```ruby
|
838
|
+
if __FILE__ == $0
|
839
|
+
ClaudeHooks::CLI.test_runner(MyHook) do |input_data|
|
840
|
+
input_data['debug_mode'] = true
|
841
|
+
input_data['custom_field'] = 'test_value'
|
842
|
+
input_data['user_name'] = 'TestUser'
|
843
|
+
end
|
844
|
+
end
|
845
|
+
```
|
846
|
+
|
847
|
+
### Testing Methods
|
848
|
+
|
849
|
+
#### 1. Test with STDIN (default)
|
850
|
+
```ruby
|
851
|
+
ClaudeHooks::CLI.test_runner(MyHook)
|
852
|
+
# Usage: echo '{"session_id":"test","prompt":"Hello"}' | ruby my_hook.rb
|
853
|
+
```
|
854
|
+
|
855
|
+
#### 2. Test with default sample data instead of STDIN
|
856
|
+
```ruby
|
857
|
+
ClaudeHooks::CLI.run_with_sample_data(MyHook, { 'prompt' => 'test prompt' })
|
858
|
+
# Provides default values, no STDIN needed
|
859
|
+
```
|
860
|
+
|
861
|
+
#### 3. Test with Sample Data + Customization
|
862
|
+
```ruby
|
863
|
+
ClaudeHooks::CLI.run_with_sample_data(MyHook) do |input_data|
|
864
|
+
input_data['prompt'] = 'Custom test prompt'
|
865
|
+
input_data['debug'] = true
|
866
|
+
end
|
867
|
+
```
|
868
|
+
|
869
|
+
### Example Hook with CLI Testing
|
870
|
+
|
871
|
+
```ruby
|
872
|
+
#!/usr/bin/env ruby
|
873
|
+
|
874
|
+
require 'claude_hooks'
|
875
|
+
|
876
|
+
class MyTestHook < ClaudeHooks::UserPromptSubmit
|
877
|
+
def call
|
878
|
+
log "Debug mode: #{input_data['debug_mode']}"
|
879
|
+
log "Processing: #{prompt}"
|
880
|
+
|
881
|
+
if input_data['debug_mode']
|
882
|
+
log "All input keys: #{input_data.keys.join(', ')}"
|
883
|
+
end
|
884
|
+
|
885
|
+
output_data
|
886
|
+
end
|
887
|
+
end
|
888
|
+
|
889
|
+
# Test runner with customization
|
890
|
+
if __FILE__ == $0
|
891
|
+
ClaudeHooks::CLI.test_runner(MyTestHook) do |input_data|
|
892
|
+
input_data['debug_mode'] = true
|
893
|
+
end
|
894
|
+
end
|
895
|
+
```
|
896
|
+
|
770
897
|
## ๐ Debugging
|
771
898
|
|
772
899
|
### Test an individual entrypoint
|
@@ -22,16 +22,7 @@ begin
|
|
22
22
|
# Output final merged result to Claude Code
|
23
23
|
puts JSON.generate(hook_output)
|
24
24
|
|
25
|
-
exit 0
|
26
|
-
rescue JSON::ParserError => e
|
27
|
-
STDERR.puts "Error parsing JSON: #{e.message}"
|
28
|
-
|
29
|
-
puts JSON.generate({
|
30
|
-
continue: false,
|
31
|
-
stopReason: "Hook JSON parsing error: #{e.message}",
|
32
|
-
suppressOutput: false
|
33
|
-
})
|
34
|
-
exit 1
|
25
|
+
exit 0 # Don't forget to exit with the right exit code (0, 1, 2)
|
35
26
|
rescue StandardError => e
|
36
27
|
STDERR.puts "Error in UserPromptSubmit hook: #{e.message} #{e.backtrace.join("\n")}"
|
37
28
|
|
@@ -39,28 +39,7 @@ end
|
|
39
39
|
|
40
40
|
# If this file is run directly (for testing), call the hook script
|
41
41
|
if __FILE__ == $0
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
input_data = JSON.parse(STDIN.read)
|
46
|
-
hook = AppendRules.new(input_data)
|
47
|
-
hook.call
|
48
|
-
puts hook.stringify_output
|
49
|
-
rescue JSON::ParserError => e
|
50
|
-
STDERR.puts "Error parsing JSON: #{e.message}"
|
51
|
-
puts JSON.generate({
|
52
|
-
continue: false,
|
53
|
-
stopReason: "JSON parsing error in AppendRules: #{e.message}",
|
54
|
-
suppressOutput: false
|
55
|
-
})
|
56
|
-
exit 0
|
57
|
-
rescue StandardError => e
|
58
|
-
STDERR.puts "Error in AppendRules hook: #{e.message}, #{e.backtrace.join("\n")}"
|
59
|
-
puts JSON.generate({
|
60
|
-
continue: false,
|
61
|
-
stopReason: "AppendRules execution error: #{e.message}",
|
62
|
-
suppressOutput: false
|
63
|
-
})
|
64
|
-
exit 0
|
42
|
+
ClaudeHooks::CLI.test_runner(AppendRules) do |input_data|
|
43
|
+
input_data['session_id'] = 'session-id-override-01'
|
65
44
|
end
|
66
45
|
end
|
@@ -33,18 +33,5 @@ end
|
|
33
33
|
|
34
34
|
# If this file is run directly (for testing), call the hook
|
35
35
|
if __FILE__ == $0
|
36
|
-
|
37
|
-
require 'json'
|
38
|
-
|
39
|
-
hook = LogUserPrompt.new(JSON.parse(STDIN.read))
|
40
|
-
hook.call
|
41
|
-
rescue StandardError => e
|
42
|
-
STDERR.puts "Error in LogUserPrompt hook: #{e.message}, #{e.backtrace.join("\n")}"
|
43
|
-
puts JSON.generate({
|
44
|
-
continue: false,
|
45
|
-
stopReason: "LogUserPrompt execution error: #{e.message}",
|
46
|
-
suppressOutput: false
|
47
|
-
})
|
48
|
-
exit 0
|
49
|
-
end
|
36
|
+
ClaudeHooks::CLI.test_runner(LogUserPrompt)
|
50
37
|
end
|
data/lib/claude_hooks/base.rb
CHANGED
@@ -26,7 +26,7 @@ module ClaudeHooks
|
|
26
26
|
|
27
27
|
attr_reader :config, :input_data, :output_data, :logger
|
28
28
|
def initialize(input_data = {})
|
29
|
-
@config = Configuration
|
29
|
+
@config = Configuration
|
30
30
|
@input_data = input_data
|
31
31
|
@output_data = {
|
32
32
|
'continue' => true,
|
@@ -108,11 +108,11 @@ module ClaudeHooks
|
|
108
108
|
# === CONFIG AND UTILITY METHODS ===
|
109
109
|
|
110
110
|
def base_dir
|
111
|
-
|
111
|
+
config.base_dir
|
112
112
|
end
|
113
113
|
|
114
114
|
def path_for(relative_path)
|
115
|
-
|
115
|
+
config.path_for(relative_path)
|
116
116
|
end
|
117
117
|
|
118
118
|
# Supports both single messages and blocks for multiline logging
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module ClaudeHooks
|
6
|
+
# CLI utility for testing hook handlers in isolation
|
7
|
+
# This module provides a standardized way to run hooks directly from the command line
|
8
|
+
# for testing and debugging purposes.
|
9
|
+
module CLI
|
10
|
+
class << self
|
11
|
+
# Run a hook class directly from command line
|
12
|
+
# Usage:
|
13
|
+
# ClaudeHooks::CLI.run_hook(YourHookClass)
|
14
|
+
# ClaudeHooks::CLI.run_hook(YourHookClass, custom_input_data)
|
15
|
+
#
|
16
|
+
# # With customization block:
|
17
|
+
# ClaudeHooks::CLI.run_hook(YourHookClass) do |input_data|
|
18
|
+
# input_data['debug_mode'] = true
|
19
|
+
# end
|
20
|
+
def run_hook(hook_class, input_data = nil, &block)
|
21
|
+
# If no input data provided, read from STDIN
|
22
|
+
input_data ||= read_stdin_input
|
23
|
+
|
24
|
+
# Apply customization block if provided
|
25
|
+
if block_given?
|
26
|
+
yield(input_data)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create and execute the hook
|
30
|
+
hook = hook_class.new(input_data)
|
31
|
+
result = hook.call
|
32
|
+
|
33
|
+
# Output the result as JSON (same format as production hooks)
|
34
|
+
puts JSON.generate(result) if result
|
35
|
+
|
36
|
+
result
|
37
|
+
rescue StandardError => e
|
38
|
+
handle_error(e, hook_class)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create a test runner block for a hook class
|
42
|
+
# This generates the common if __FILE__ == $0 block content
|
43
|
+
#
|
44
|
+
# Usage:
|
45
|
+
# ClaudeHooks::CLI.test_runner(YourHookClass)
|
46
|
+
#
|
47
|
+
# # With customization block:
|
48
|
+
# ClaudeHooks::CLI.test_runner(YourHookClass) do |input_data|
|
49
|
+
# input_data['custom_field'] = 'test_value'
|
50
|
+
# input_data['user_name'] = 'TestUser'
|
51
|
+
# end
|
52
|
+
def test_runner(hook_class, &block)
|
53
|
+
input_data = read_stdin_input
|
54
|
+
|
55
|
+
# Apply customization block if provided
|
56
|
+
if block_given?
|
57
|
+
yield(input_data)
|
58
|
+
end
|
59
|
+
|
60
|
+
run_hook(hook_class, input_data)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Run hook with sample data (useful for development)
|
64
|
+
# Usage:
|
65
|
+
# ClaudeHooks::CLI.run_with_sample_data(YourHookClass)
|
66
|
+
# ClaudeHooks::CLI.run_with_sample_data(YourHookClass, { 'prompt' => 'test prompt' })
|
67
|
+
#
|
68
|
+
# # With customization block:
|
69
|
+
# ClaudeHooks::CLI.run_with_sample_data(YourHookClass) do |input_data|
|
70
|
+
# input_data['prompt'] = 'Custom test prompt'
|
71
|
+
# input_data['debug'] = true
|
72
|
+
# end
|
73
|
+
def run_with_sample_data(hook_class, sample_data = {}, &block)
|
74
|
+
default_sample = {
|
75
|
+
'session_id' => 'test-session',
|
76
|
+
'transcript_path' => '/tmp/test_transcript.md',
|
77
|
+
'cwd' => Dir.pwd,
|
78
|
+
'hook_event_name' => hook_class.hook_type
|
79
|
+
}
|
80
|
+
|
81
|
+
# Merge with hook-specific sample data
|
82
|
+
merged_data = default_sample.merge(sample_data)
|
83
|
+
|
84
|
+
# Apply customization block if provided
|
85
|
+
if block_given?
|
86
|
+
yield(merged_data)
|
87
|
+
end
|
88
|
+
|
89
|
+
run_hook(hook_class, merged_data)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def read_stdin_input
|
95
|
+
stdin_content = STDIN.read.strip
|
96
|
+
return {} if stdin_content.empty?
|
97
|
+
|
98
|
+
JSON.parse(stdin_content)
|
99
|
+
rescue JSON::ParserError => e
|
100
|
+
raise "Invalid JSON input: #{e.message}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def handle_error(error, hook_class)
|
104
|
+
STDERR.puts "Error in #{hook_class.name} hook: #{error.message}"
|
105
|
+
STDERR.puts error.backtrace.join("\n") if error.backtrace
|
106
|
+
|
107
|
+
# Output error response in Claude Code format
|
108
|
+
error_response = {
|
109
|
+
continue: false,
|
110
|
+
stopReason: "#{hook_class.name} execution error: #{error.message}",
|
111
|
+
suppressOutput: false
|
112
|
+
}
|
113
|
+
|
114
|
+
puts JSON.generate(error_response)
|
115
|
+
exit 1
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -63,7 +63,8 @@ module ClaudeHooks
|
|
63
63
|
def method_missing(method_name, *args, &block)
|
64
64
|
# Convert method name to ENV key format (e.g., my_custom_setting -> MY_CUSTOM_SETTING)
|
65
65
|
env_key = method_name.to_s.upcase
|
66
|
-
|
66
|
+
# Convert snake_case method name to camelCase for config file lookup
|
67
|
+
config_key = snake_case_to_camel_case(method_name.to_s)
|
67
68
|
|
68
69
|
value = get_config_value(env_key, config_key)
|
69
70
|
return value unless value.nil?
|
@@ -74,13 +75,19 @@ module ClaudeHooks
|
|
74
75
|
def respond_to_missing?(method_name, include_private = false)
|
75
76
|
# Check if we have a config value for this method
|
76
77
|
env_key = method_name.to_s.upcase
|
77
|
-
config_key = method_name.to_s
|
78
|
+
config_key = snake_case_to_camel_case(method_name.to_s)
|
78
79
|
|
79
80
|
!get_config_value(env_key, config_key).nil? || super
|
80
81
|
end
|
81
82
|
|
82
83
|
private
|
83
84
|
|
85
|
+
def snake_case_to_camel_case(snake_str)
|
86
|
+
# Convert snake_case to camelCase (e.g., user_name -> userName)
|
87
|
+
parts = snake_str.split('_')
|
88
|
+
parts.first + parts[1..-1].map(&:capitalize).join
|
89
|
+
end
|
90
|
+
|
84
91
|
def config_file_path
|
85
92
|
@config_file_path ||= path_for('config/config.json')
|
86
93
|
end
|
data/lib/claude_hooks/version.rb
CHANGED
data/lib/claude_hooks.rb
CHANGED
@@ -4,6 +4,7 @@ require_relative "claude_hooks/version"
|
|
4
4
|
require_relative "claude_hooks/configuration"
|
5
5
|
require_relative "claude_hooks/logger"
|
6
6
|
require_relative "claude_hooks/base"
|
7
|
+
require_relative "claude_hooks/cli"
|
7
8
|
require_relative "claude_hooks/user_prompt_submit"
|
8
9
|
require_relative "claude_hooks/pre_tool_use"
|
9
10
|
require_relative "claude_hooks/post_tool_use"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: claude_hooks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gabriel Dehan
|
@@ -67,13 +67,12 @@ files:
|
|
67
67
|
- claude_hooks.gemspec
|
68
68
|
- example_dotclaude/commands/.gitkeep
|
69
69
|
- example_dotclaude/hooks/entrypoints/user_prompt_submit.rb
|
70
|
-
- example_dotclaude/hooks/entrypoints/user_prompt_submit/append_rules.rb
|
71
|
-
- example_dotclaude/hooks/entrypoints/user_prompt_submit/log_user_prompt.rb
|
72
70
|
- example_dotclaude/hooks/handlers/user_prompt_submit/append_rules.rb
|
73
71
|
- example_dotclaude/hooks/handlers/user_prompt_submit/log_user_prompt.rb
|
74
72
|
- example_dotclaude/settings.json
|
75
73
|
- lib/claude_hooks.rb
|
76
74
|
- lib/claude_hooks/base.rb
|
75
|
+
- lib/claude_hooks/cli.rb
|
77
76
|
- lib/claude_hooks/configuration.rb
|
78
77
|
- lib/claude_hooks/logger.rb
|
79
78
|
- lib/claude_hooks/notification.rb
|
@@ -1,66 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'claude_hooks'
|
4
|
-
|
5
|
-
# Hook script that appends rules to user prompt
|
6
|
-
class AppendRules < ClaudeHooks::UserPromptSubmit
|
7
|
-
|
8
|
-
def call
|
9
|
-
log "Executing AppendRules hook"
|
10
|
-
|
11
|
-
# Read the rule content
|
12
|
-
rule_content = read_rule_content
|
13
|
-
|
14
|
-
if rule_content
|
15
|
-
add_additional_context!(rule_content)
|
16
|
-
log "Successfully added rule content as additional context (#{rule_content.length} characters)"
|
17
|
-
else
|
18
|
-
log "No rule content found", level: :warn
|
19
|
-
end
|
20
|
-
|
21
|
-
output_data
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def read_rule_content
|
27
|
-
rule_file_path = path_for('rules/post-user-prompt.rule.md')
|
28
|
-
|
29
|
-
if File.exist?(rule_file_path)
|
30
|
-
content = File.read(rule_file_path).strip
|
31
|
-
return content unless content.empty?
|
32
|
-
end
|
33
|
-
|
34
|
-
log "Rule file not found or empty at: #{rule_file_path}", level: :warn
|
35
|
-
log "Base directory: #{base_dir}"
|
36
|
-
nil
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# If this file is run directly (for testing), call the hook script
|
41
|
-
if __FILE__ == $0
|
42
|
-
begin
|
43
|
-
require 'json'
|
44
|
-
|
45
|
-
input_data = JSON.parse(STDIN.read)
|
46
|
-
hook = AppendRules.new(input_data)
|
47
|
-
hook.call
|
48
|
-
puts hook.stringify_output
|
49
|
-
rescue JSON::ParserError => e
|
50
|
-
STDERR.puts "Error parsing JSON: #{e.message}"
|
51
|
-
puts JSON.generate({
|
52
|
-
continue: false,
|
53
|
-
stopReason: "JSON parsing error in AppendRules: #{e.message}",
|
54
|
-
suppressOutput: false
|
55
|
-
})
|
56
|
-
exit 0
|
57
|
-
rescue StandardError => e
|
58
|
-
STDERR.puts "Error in AppendRules hook: #{e.message}, #{e.backtrace.join("\n")}"
|
59
|
-
puts JSON.generate({
|
60
|
-
continue: false,
|
61
|
-
stopReason: "AppendRules execution error: #{e.message}",
|
62
|
-
suppressOutput: false
|
63
|
-
})
|
64
|
-
exit 0
|
65
|
-
end
|
66
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'fileutils'
|
4
|
-
require 'claude_hooks'
|
5
|
-
|
6
|
-
# Example hook module that logs user prompts to a file
|
7
|
-
class LogUserPrompt < ClaudeHooks::UserPromptSubmit
|
8
|
-
|
9
|
-
def call
|
10
|
-
log "Executing LogUserPrompt hook"
|
11
|
-
|
12
|
-
# Log the prompt to a file (just as an example)
|
13
|
-
log_file_path = path_for('logs/user_prompts.log')
|
14
|
-
ensure_log_directory_exists
|
15
|
-
|
16
|
-
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
17
|
-
|
18
|
-
log <<~TEXT
|
19
|
-
Prompt: #{current_prompt}
|
20
|
-
Logged user prompt to #{log_file_path}
|
21
|
-
TEXT
|
22
|
-
|
23
|
-
nil
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def ensure_log_directory_exists
|
29
|
-
log_dir = path_for('logs')
|
30
|
-
FileUtils.mkdir_p(log_dir) unless Dir.exist?(log_dir)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
# If this file is run directly (for testing), call the hook
|
35
|
-
if __FILE__ == $0
|
36
|
-
begin
|
37
|
-
require 'json'
|
38
|
-
|
39
|
-
hook = LogUserPrompt.new(JSON.parse(STDIN.read))
|
40
|
-
hook.call
|
41
|
-
rescue StandardError => e
|
42
|
-
STDERR.puts "Error in LogUserPrompt hook: #{e.message}, #{e.backtrace.join("\n")}"
|
43
|
-
puts JSON.generate({
|
44
|
-
continue: false,
|
45
|
-
stopReason: "LogUserPrompt execution error: #{e.message}",
|
46
|
-
suppressOutput: false
|
47
|
-
})
|
48
|
-
exit 0
|
49
|
-
end
|
50
|
-
end
|