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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8eb1adbb7a091ea436fa56940baf40407eb2d0f148760d7f1c470d4577794744
4
- data.tar.gz: cf79657245bd18e98ba407d49d49b4fe539fd7feb4be4daa46c4cee00e5ea1a7
3
+ metadata.gz: 05073a7e1f50d4391f4afddff10e781bf70c1c1a5ec15b4d8c5cf7959827ca53
4
+ data.tar.gz: a477cb849efb154ac3452150030bde9a4219c82887afae7b3fded7d9d01b482c
5
5
  SHA512:
6
- metadata.gz: c0303fbf858d8869ff0a326ce5031193f684cd6ca76d1fce9657b52439f1a789b06cbdcdf161663478b65e35455f5d9e756bd595eeb1e065d47be6e8936782e1
7
- data.tar.gz: 8a134f14432693883b43576f7c66b9e9e68acd4f0c97e10be1ad91893a2e8f1d25cc30445d41afd087a923272105e32bb0df110f4834d3930c275d4afc263181
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
- Add to your Gemfile (you can add a Gemfile in your `.claude` directory if needed):
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 execute:
93
+ And then run:
82
94
 
83
95
  ```bash
84
96
  $ bundle install
85
97
  ```
86
98
 
87
- Or install it globally:
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
- $stderr.puts JSON.generate({
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 `$stderr.puts` to output the JSON to STDERR.
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 `$stderr.puts`.
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
- 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
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
- 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
36
+ ClaudeHooks::CLI.test_runner(LogUserPrompt)
50
37
  end
@@ -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.config
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
- Configuration.base_dir
111
+ config.base_dir
112
112
  end
113
113
 
114
114
  def path_for(relative_path)
115
- Configuration.path_for(relative_path)
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
- config_key = method_name.to_s
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeHooks
4
- VERSION = "0.1.1"
5
- end
4
+ VERSION = "0.1.2"
5
+ end
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.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