claude_hooks 1.3.0 → 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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/README.md +19 -15
  4. data/docs/API/COMMON.md +4 -3
  5. data/docs/API/NOTIFICATION.md +5 -4
  6. data/docs/API/PERMISSION_DENIED.md +4 -0
  7. data/docs/API/PERMISSION_REQUEST.md +3 -3
  8. data/docs/API/POST_MODEL_SWITCH.md +53 -0
  9. data/docs/API/POST_TOOL_USE.md +7 -0
  10. data/docs/API/POST_TOOL_USE_FAILURE.md +1 -0
  11. data/docs/API/PRE_MODEL_SWITCH.md +67 -0
  12. data/docs/API/PRE_TOOL_USE.md +1 -0
  13. data/docs/API/SESSION_START.md +7 -1
  14. data/docs/API/SETUP.md +3 -3
  15. data/docs/API/STOP.md +6 -0
  16. data/docs/API/SUBAGENT_STOP.md +10 -0
  17. data/docs/API/TASK_CREATED.md +9 -7
  18. data/docs/API/USER_PROMPT_SUBMIT.md +14 -0
  19. data/docs/API/WORKTREE_REMOVE.md +9 -4
  20. data/docs/external/claude-hooks-reference.md +973 -294
  21. data/docs/mitts/prd.md +8 -0
  22. data/docs/mitts/setup.md +5 -3
  23. data/lib/claude_hooks/base.rb +12 -0
  24. data/lib/claude_hooks/output/base.rb +4 -0
  25. data/lib/claude_hooks/output/notification.rb +10 -1
  26. data/lib/claude_hooks/output/post_model_switch.rb +50 -0
  27. data/lib/claude_hooks/output/post_tool_batch.rb +10 -0
  28. data/lib/claude_hooks/output/post_tool_use.rb +23 -5
  29. data/lib/claude_hooks/output/pre_model_switch.rb +121 -0
  30. data/lib/claude_hooks/output/pre_tool_use.rb +8 -0
  31. data/lib/claude_hooks/output/task_created.rb +34 -1
  32. data/lib/claude_hooks/output/user_prompt_expansion.rb +10 -0
  33. data/lib/claude_hooks/output/user_prompt_submit.rb +19 -6
  34. data/lib/claude_hooks/output/worktree_remove.rb +4 -2
  35. data/lib/claude_hooks/permission_denied.rb +7 -0
  36. data/lib/claude_hooks/permission_request.rb +10 -1
  37. data/lib/claude_hooks/post_model_switch.rb +70 -0
  38. data/lib/claude_hooks/post_tool_use.rb +14 -0
  39. data/lib/claude_hooks/post_tool_use_failure.rb +7 -0
  40. data/lib/claude_hooks/pre_model_switch.rb +90 -0
  41. data/lib/claude_hooks/pre_tool_use.rb +7 -0
  42. data/lib/claude_hooks/session_start.rb +18 -0
  43. data/lib/claude_hooks/setup.rb +3 -0
  44. data/lib/claude_hooks/task_created.rb +17 -0
  45. data/lib/claude_hooks/user_prompt_submit.rb +26 -5
  46. data/lib/claude_hooks/version.rb +1 -1
  47. data/lib/claude_hooks.rb +4 -0
  48. metadata +8 -2
@@ -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.3.0"
4
+ VERSION = "1.4.0"
5
5
  end
data/lib/claude_hooks.rb CHANGED
@@ -38,6 +38,8 @@ require_relative "claude_hooks/elicitation"
38
38
  require_relative "claude_hooks/elicitation_result"
39
39
  require_relative "claude_hooks/worktree_create"
40
40
  require_relative "claude_hooks/directory_added"
41
+ require_relative "claude_hooks/pre_model_switch"
42
+ require_relative "claude_hooks/post_model_switch"
41
43
 
42
44
  # Output classes
43
45
  require_relative "claude_hooks/output/base"
@@ -72,6 +74,8 @@ require_relative "claude_hooks/output/elicitation"
72
74
  require_relative "claude_hooks/output/elicitation_result"
73
75
  require_relative "claude_hooks/output/worktree_create"
74
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"
75
79
 
76
80
  module ClaudeHooks
77
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.3.0
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-08-15 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
@@ -83,10 +83,12 @@ files:
83
83
  - docs/API/PERMISSION_DENIED.md
84
84
  - docs/API/PERMISSION_REQUEST.md
85
85
  - docs/API/POST_COMPACT.md
86
+ - docs/API/POST_MODEL_SWITCH.md
86
87
  - docs/API/POST_TOOL_BATCH.md
87
88
  - docs/API/POST_TOOL_USE.md
88
89
  - docs/API/POST_TOOL_USE_FAILURE.md
89
90
  - docs/API/PRE_COMPACT.md
91
+ - docs/API/PRE_MODEL_SWITCH.md
90
92
  - docs/API/PRE_TOOL_USE.md
91
93
  - docs/API/SESSION_END.md
92
94
  - docs/API/SESSION_START.md
@@ -146,10 +148,12 @@ files:
146
148
  - lib/claude_hooks/output/permission_denied.rb
147
149
  - lib/claude_hooks/output/permission_request.rb
148
150
  - lib/claude_hooks/output/post_compact.rb
151
+ - lib/claude_hooks/output/post_model_switch.rb
149
152
  - lib/claude_hooks/output/post_tool_batch.rb
150
153
  - lib/claude_hooks/output/post_tool_use.rb
151
154
  - lib/claude_hooks/output/post_tool_use_failure.rb
152
155
  - lib/claude_hooks/output/pre_compact.rb
156
+ - lib/claude_hooks/output/pre_model_switch.rb
153
157
  - lib/claude_hooks/output/pre_tool_use.rb
154
158
  - lib/claude_hooks/output/session_end.rb
155
159
  - lib/claude_hooks/output/session_start.rb
@@ -168,10 +172,12 @@ files:
168
172
  - lib/claude_hooks/permission_denied.rb
169
173
  - lib/claude_hooks/permission_request.rb
170
174
  - lib/claude_hooks/post_compact.rb
175
+ - lib/claude_hooks/post_model_switch.rb
171
176
  - lib/claude_hooks/post_tool_batch.rb
172
177
  - lib/claude_hooks/post_tool_use.rb
173
178
  - lib/claude_hooks/post_tool_use_failure.rb
174
179
  - lib/claude_hooks/pre_compact.rb
180
+ - lib/claude_hooks/pre_model_switch.rb
175
181
  - lib/claude_hooks/pre_tool_use.rb
176
182
  - lib/claude_hooks/session_end.rb
177
183
  - lib/claude_hooks/session_start.rb