claude_hooks 1.2.1 → 1.4.0

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -0
  3. data/README.md +21 -15
  4. data/docs/API/COMMON.md +4 -3
  5. data/docs/API/DIRECTORY_ADDED.md +34 -0
  6. data/docs/API/NOTIFICATION.md +5 -4
  7. data/docs/API/PERMISSION_DENIED.md +4 -0
  8. data/docs/API/PERMISSION_REQUEST.md +3 -3
  9. data/docs/API/POST_MODEL_SWITCH.md +53 -0
  10. data/docs/API/POST_TOOL_USE.md +7 -0
  11. data/docs/API/POST_TOOL_USE_FAILURE.md +1 -0
  12. data/docs/API/PRE_MODEL_SWITCH.md +67 -0
  13. data/docs/API/PRE_TOOL_USE.md +1 -0
  14. data/docs/API/SESSION_START.md +7 -1
  15. data/docs/API/SETUP.md +3 -3
  16. data/docs/API/STOP.md +6 -0
  17. data/docs/API/SUBAGENT_STOP.md +10 -0
  18. data/docs/API/TASK_CREATED.md +9 -7
  19. data/docs/API/USER_PROMPT_SUBMIT.md +14 -0
  20. data/docs/API/WORKTREE_REMOVE.md +9 -4
  21. data/docs/external/claude-hooks-reference.md +1188 -345
  22. data/docs/mitts/prd.md +8 -0
  23. data/docs/mitts/setup.md +5 -3
  24. data/lib/claude_hooks/base.rb +12 -0
  25. data/lib/claude_hooks/directory_added.rb +24 -0
  26. data/lib/claude_hooks/output/base.rb +6 -0
  27. data/lib/claude_hooks/output/directory_added.rb +21 -0
  28. data/lib/claude_hooks/output/notification.rb +10 -1
  29. data/lib/claude_hooks/output/post_model_switch.rb +50 -0
  30. data/lib/claude_hooks/output/post_tool_batch.rb +10 -0
  31. data/lib/claude_hooks/output/post_tool_use.rb +23 -5
  32. data/lib/claude_hooks/output/pre_model_switch.rb +121 -0
  33. data/lib/claude_hooks/output/pre_tool_use.rb +8 -0
  34. data/lib/claude_hooks/output/task_created.rb +34 -1
  35. data/lib/claude_hooks/output/user_prompt_expansion.rb +10 -0
  36. data/lib/claude_hooks/output/user_prompt_submit.rb +19 -6
  37. data/lib/claude_hooks/output/worktree_remove.rb +4 -2
  38. data/lib/claude_hooks/permission_denied.rb +7 -0
  39. data/lib/claude_hooks/permission_request.rb +10 -1
  40. data/lib/claude_hooks/post_model_switch.rb +70 -0
  41. data/lib/claude_hooks/post_tool_use.rb +14 -0
  42. data/lib/claude_hooks/post_tool_use_failure.rb +7 -0
  43. data/lib/claude_hooks/pre_model_switch.rb +90 -0
  44. data/lib/claude_hooks/pre_tool_use.rb +7 -0
  45. data/lib/claude_hooks/session_start.rb +18 -0
  46. data/lib/claude_hooks/setup.rb +3 -0
  47. data/lib/claude_hooks/task_created.rb +17 -0
  48. data/lib/claude_hooks/user_prompt_submit.rb +26 -5
  49. data/lib/claude_hooks/version.rb +1 -1
  50. data/lib/claude_hooks.rb +6 -0
  51. metadata +11 -2
@@ -30,6 +30,13 @@ module ClaudeHooks
30
30
  @input_data['tool_use_id'] || @input_data['toolUseId']
31
31
  end
32
32
 
33
+ # For MCP tools: { 'name' => ..., 'source' => ... } describing where the server's
34
+ # definition came from (e.g. 'plugin', 'sdk', 'user', 'project'). Base trust
35
+ # decisions on 'source', not on the name. nil for non-MCP tools. Requires Claude Code v2.1.274+.
36
+ def mcp_server
37
+ @input_data['mcp_server'] || @input_data['mcpServer']
38
+ end
39
+
33
40
  # === OUTPUT DATA HELPERS ===
34
41
 
35
42
  def block_tool!(reason = '')
@@ -59,5 +66,12 @@ module ClaudeHooks
59
66
  @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
60
67
  @output_data['hookSpecificOutput']['updatedMCPToolOutput'] = value
61
68
  end
69
+
70
+ # Annotate this call's result for the auto-mode classifier (not for Claude).
71
+ # Requires Claude Code v2.1.236 or later.
72
+ def classifier_context!(note)
73
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
74
+ @output_data['hookSpecificOutput']['classifierContext'] = note
75
+ end
62
76
  end
63
77
  end
@@ -24,6 +24,13 @@ module ClaudeHooks
24
24
  @input_data['tool_use_id'] || @input_data['toolUseId']
25
25
  end
26
26
 
27
+ # For MCP tools: { 'name' => ..., 'source' => ... } describing where the server's
28
+ # definition came from (e.g. 'plugin', 'sdk', 'user', 'project'). Base trust
29
+ # decisions on 'source', not on the name. nil for non-MCP tools. Requires Claude Code v2.1.274+.
30
+ def mcp_server
31
+ @input_data['mcp_server'] || @input_data['mcpServer']
32
+ end
33
+
27
34
  def error
28
35
  @input_data['error']
29
36
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class PreModelSwitch < Base
7
+ def self.hook_type
8
+ 'PreModelSwitch'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[from_model to_model requested_model source]
13
+ end
14
+
15
+ # === INPUT DATA ACCESS ===
16
+
17
+ def from_model
18
+ @input_data['from_model'] || @input_data['fromModel']
19
+ end
20
+
21
+ def to_model
22
+ @input_data['to_model'] || @input_data['toModel']
23
+ end
24
+
25
+ def requested_model
26
+ @input_data['requested_model'] || @input_data['requestedModel']
27
+ end
28
+
29
+ def source
30
+ @input_data['source']
31
+ end
32
+
33
+ def context_tokens
34
+ @input_data['context_tokens'] || @input_data['contextTokens']
35
+ end
36
+
37
+ def prompt_cache_warm
38
+ @input_data.key?('prompt_cache_warm') ? @input_data['prompt_cache_warm'] : @input_data['promptCacheWarm']
39
+ end
40
+
41
+ def cache_ttl
42
+ @input_data['cache_ttl'] || @input_data['cacheTtl']
43
+ end
44
+
45
+ def estimated_cache_write_usd
46
+ @input_data['estimated_cache_write_usd'] || @input_data['estimatedCacheWriteUsd']
47
+ end
48
+
49
+ def pricing
50
+ @input_data['pricing']
51
+ end
52
+
53
+ # === OUTPUT DATA HELPERS ===
54
+ #
55
+ # PreModelSwitch mirrors PreToolUse's permissionDecision API (allow/deny/ask).
56
+ # It does not accept 'defer', 'updatedInput', or 'additionalContext'.
57
+
58
+ def approve!(reason = '')
59
+ @output_data['hookSpecificOutput'] = {
60
+ 'hookEventName' => hook_event_name,
61
+ 'permissionDecision' => 'allow',
62
+ 'permissionDecisionReason' => reason
63
+ }
64
+ end
65
+ alias_method :allow!, :approve!
66
+
67
+ def deny!(reason = '')
68
+ @output_data['hookSpecificOutput'] = {
69
+ 'hookEventName' => hook_event_name,
70
+ 'permissionDecision' => 'deny',
71
+ 'permissionDecisionReason' => reason
72
+ }
73
+ end
74
+ alias_method :block_switch!, :deny!
75
+
76
+ def ask!(reason = '')
77
+ @output_data['hookSpecificOutput'] = {
78
+ 'hookEventName' => hook_event_name,
79
+ 'permissionDecision' => 'ask',
80
+ 'permissionDecisionReason' => reason
81
+ }
82
+ end
83
+
84
+ # Top-level decision:block also cancels the switch (equivalent to exit 2).
85
+ def block!(reason = '')
86
+ @output_data['decision'] = 'block'
87
+ @output_data['reason'] = reason
88
+ end
89
+ end
90
+ end
@@ -26,6 +26,13 @@ module ClaudeHooks
26
26
  @input_data['tool_use_id'] || @input_data['toolUseId']
27
27
  end
28
28
 
29
+ # For MCP tools: { 'name' => ..., 'source' => ... } describing where the server's
30
+ # definition came from (e.g. 'plugin', 'sdk', 'user', 'project'). Base trust
31
+ # decisions on 'source', not on the name. nil for non-MCP tools. Requires Claude Code v2.1.274+.
32
+ def mcp_server
33
+ @input_data['mcp_server'] || @input_data['mcpServer']
34
+ end
35
+
29
36
  # === OUTPUT DATA HELPERS ===
30
37
 
31
38
  def approve_tool!(reason = '')
@@ -26,6 +26,24 @@ module ClaudeHooks
26
26
  @input_data['session_title'] || @input_data['sessionTitle']
27
27
  end
28
28
 
29
+ # Optional cost/context fields present when source is "resume" or "fork".
30
+
31
+ def seconds_since_last_response
32
+ @input_data['seconds_since_last_response'] || @input_data['secondsSinceLastResponse']
33
+ end
34
+
35
+ def context_tokens
36
+ @input_data['context_tokens'] || @input_data['contextTokens']
37
+ end
38
+
39
+ def prompt_cache_likely_expired
40
+ @input_data.key?('prompt_cache_likely_expired') ? @input_data['prompt_cache_likely_expired'] : @input_data['promptCacheLikelyExpired']
41
+ end
42
+
43
+ def estimated_cache_write_usd
44
+ @input_data['estimated_cache_write_usd'] || @input_data['estimatedCacheWriteUsd']
45
+ end
46
+
29
47
  # === OUTPUT DATA HELPERS ===
30
48
 
31
49
  def add_additional_context!(context)
@@ -16,6 +16,9 @@ module ClaudeHooks
16
16
  @input_data['source']
17
17
  end
18
18
 
19
+ # DEPRECATED: Claude Code now discards all Setup JSON output, including
20
+ # hookSpecificOutput.additionalContext. Kept for backward compatibility, but
21
+ # this no longer adds context — use a SessionStart hook to inject context instead.
19
22
  def add_additional_context!(context)
20
23
  @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
21
24
  @output_data['hookSpecificOutput']['additionalContext'] = context
@@ -31,5 +31,22 @@ module ClaudeHooks
31
31
  def team_name
32
32
  @input_data['team_name'] || @input_data['teamName']
33
33
  end
34
+
35
+ # === OUTPUT DATA HELPERS ===
36
+ #
37
+ # Claude Code ignores `continue: false` for TaskCreated. Block task creation
38
+ # either by exiting 2 or with a top-level decision:block; `reason` is returned
39
+ # to Claude as the task-creation error.
40
+
41
+ def block!(reason = '')
42
+ @output_data['decision'] = 'block'
43
+ @output_data['reason'] = reason
44
+ end
45
+
46
+ def allow!
47
+ @output_data['decision'] = nil
48
+ @output_data['reason'] = nil
49
+ end
50
+ alias_method :unblock!, :allow!
34
51
  end
35
52
  end
@@ -20,18 +20,39 @@ module ClaudeHooks
20
20
  alias_method :user_prompt, :prompt
21
21
  alias_method :current_prompt, :prompt
22
22
 
23
+ # The session's custom title, when one is set. Requires Claude Code to send it
24
+ # (same meaning as SessionStart's session_title).
25
+ def session_title
26
+ @input_data['session_title'] || @input_data['sessionTitle']
27
+ end
28
+
23
29
  # === OUTPUT DATA HELPERS ===
24
30
 
25
31
  def add_additional_context!(context)
26
- @output_data['hookSpecificOutput'] = {
27
- 'hookEventName' => hook_event_name,
28
- 'additionalContext' => context
29
- }
32
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
33
+ @output_data['hookSpecificOutput']['additionalContext'] = context
30
34
  end
31
35
  alias_method :add_context!, :add_additional_context!
32
36
 
33
37
  def empty_additional_context!
34
- @output_data['hookSpecificOutput'] = nil
38
+ hso = @output_data['hookSpecificOutput']
39
+ return unless hso
40
+
41
+ hso.delete('additionalContext')
42
+ @output_data['hookSpecificOutput'] = nil if hso.keys == ['hookEventName']
43
+ end
44
+
45
+ def session_title!(title)
46
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
47
+ @output_data['hookSpecificOutput']['sessionTitle'] = title
48
+ end
49
+
50
+ # When the prompt is blocked (block_prompt! or exit 2), leave the prompt text out
51
+ # of the block message shown to the user and written to the transcript. Doesn't
52
+ # remove it from other local files such as prompt history.
53
+ def suppress_original_prompt!(value = true)
54
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
55
+ @output_data['hookSpecificOutput']['suppressOriginalPrompt'] = value
35
56
  end
36
57
 
37
58
  def block_prompt!(reason = '')
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeHooks
4
- VERSION = "1.2.1"
4
+ VERSION = "1.4.0"
5
5
  end
data/lib/claude_hooks.rb CHANGED
@@ -37,6 +37,9 @@ require_relative "claude_hooks/permission_denied"
37
37
  require_relative "claude_hooks/elicitation"
38
38
  require_relative "claude_hooks/elicitation_result"
39
39
  require_relative "claude_hooks/worktree_create"
40
+ require_relative "claude_hooks/directory_added"
41
+ require_relative "claude_hooks/pre_model_switch"
42
+ require_relative "claude_hooks/post_model_switch"
40
43
 
41
44
  # Output classes
42
45
  require_relative "claude_hooks/output/base"
@@ -70,6 +73,9 @@ require_relative "claude_hooks/output/permission_denied"
70
73
  require_relative "claude_hooks/output/elicitation"
71
74
  require_relative "claude_hooks/output/elicitation_result"
72
75
  require_relative "claude_hooks/output/worktree_create"
76
+ require_relative "claude_hooks/output/directory_added"
77
+ require_relative "claude_hooks/output/pre_model_switch"
78
+ require_relative "claude_hooks/output/post_model_switch"
73
79
 
74
80
  module ClaudeHooks
75
81
  class Error < StandardError; end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: claude_hooks
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriel Dehan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-07-14 00:00:00.000000000 Z
11
+ date: 2026-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -73,6 +73,7 @@ files:
73
73
  - docs/API/COMMON.md
74
74
  - docs/API/CONFIG_CHANGE.md
75
75
  - docs/API/CWD_CHANGED.md
76
+ - docs/API/DIRECTORY_ADDED.md
76
77
  - docs/API/ELICITATION.md
77
78
  - docs/API/ELICITATION_RESULT.md
78
79
  - docs/API/FILE_CHANGED.md
@@ -82,10 +83,12 @@ files:
82
83
  - docs/API/PERMISSION_DENIED.md
83
84
  - docs/API/PERMISSION_REQUEST.md
84
85
  - docs/API/POST_COMPACT.md
86
+ - docs/API/POST_MODEL_SWITCH.md
85
87
  - docs/API/POST_TOOL_BATCH.md
86
88
  - docs/API/POST_TOOL_USE.md
87
89
  - docs/API/POST_TOOL_USE_FAILURE.md
88
90
  - docs/API/PRE_COMPACT.md
91
+ - docs/API/PRE_MODEL_SWITCH.md
89
92
  - docs/API/PRE_TOOL_USE.md
90
93
  - docs/API/SESSION_END.md
91
94
  - docs/API/SESSION_START.md
@@ -124,6 +127,7 @@ files:
124
127
  - lib/claude_hooks/config_change.rb
125
128
  - lib/claude_hooks/configuration.rb
126
129
  - lib/claude_hooks/cwd_changed.rb
130
+ - lib/claude_hooks/directory_added.rb
127
131
  - lib/claude_hooks/elicitation.rb
128
132
  - lib/claude_hooks/elicitation_result.rb
129
133
  - lib/claude_hooks/file_changed.rb
@@ -134,6 +138,7 @@ files:
134
138
  - lib/claude_hooks/output/base.rb
135
139
  - lib/claude_hooks/output/config_change.rb
136
140
  - lib/claude_hooks/output/cwd_changed.rb
141
+ - lib/claude_hooks/output/directory_added.rb
137
142
  - lib/claude_hooks/output/elicitation.rb
138
143
  - lib/claude_hooks/output/elicitation_result.rb
139
144
  - lib/claude_hooks/output/file_changed.rb
@@ -143,10 +148,12 @@ files:
143
148
  - lib/claude_hooks/output/permission_denied.rb
144
149
  - lib/claude_hooks/output/permission_request.rb
145
150
  - lib/claude_hooks/output/post_compact.rb
151
+ - lib/claude_hooks/output/post_model_switch.rb
146
152
  - lib/claude_hooks/output/post_tool_batch.rb
147
153
  - lib/claude_hooks/output/post_tool_use.rb
148
154
  - lib/claude_hooks/output/post_tool_use_failure.rb
149
155
  - lib/claude_hooks/output/pre_compact.rb
156
+ - lib/claude_hooks/output/pre_model_switch.rb
150
157
  - lib/claude_hooks/output/pre_tool_use.rb
151
158
  - lib/claude_hooks/output/session_end.rb
152
159
  - lib/claude_hooks/output/session_start.rb
@@ -165,10 +172,12 @@ files:
165
172
  - lib/claude_hooks/permission_denied.rb
166
173
  - lib/claude_hooks/permission_request.rb
167
174
  - lib/claude_hooks/post_compact.rb
175
+ - lib/claude_hooks/post_model_switch.rb
168
176
  - lib/claude_hooks/post_tool_batch.rb
169
177
  - lib/claude_hooks/post_tool_use.rb
170
178
  - lib/claude_hooks/post_tool_use_failure.rb
171
179
  - lib/claude_hooks/pre_compact.rb
180
+ - lib/claude_hooks/pre_model_switch.rb
172
181
  - lib/claude_hooks/pre_tool_use.rb
173
182
  - lib/claude_hooks/session_end.rb
174
183
  - lib/claude_hooks/session_start.rb