claude_hooks 0.1.2 → 0.2.1
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/CHANGELOG.md +38 -0
- data/README.md +87 -58
- data/example_dotclaude/hooks/entrypoints/user_prompt_submit.rb +2 -2
- data/example_dotclaude/hooks/handlers/user_prompt_submit/append_rules.rb +10 -8
- data/example_dotclaude/hooks/handlers/user_prompt_submit/log_user_prompt.rb +0 -12
- data/lib/claude_hooks/base.rb +18 -2
- data/lib/claude_hooks/configuration.rb +103 -19
- data/lib/claude_hooks/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '08b8b23bcfdeed5cf98e3477b6339f29488d6e547fd2fb10ef40947bbc3d45dc'
|
4
|
+
data.tar.gz: 20d0f599bac9dcde566829dabba1d1f3f13756181ed4ea7bbe4cfa1c0a95838a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7cd6ecea85c72ce39e77613f68c3dc9f689aca7611dd499482e20538bbb456e2c6b4c23bc791372173fd4b955e8d4211c3c8c29e0e97957812916f901327ce22
|
7
|
+
data.tar.gz: 93b6032c1c29c11081419d0415dae8ac5cd2494fa558af1637c081ce11d256403c1b26c49740c9aa8294c385aa311b12da2b3e5a6949e88a4a5f892b68abcf54
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,44 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [0.2.1] - 2025-08-21
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
- Fixed name of environment variable for the merge strategy
|
12
|
+
|
13
|
+
## [0.2.0] - 2025-08-21
|
14
|
+
|
15
|
+
### Added
|
16
|
+
- **Dual Configuration System**: Support for both home-level (`$HOME/.claude`) and project-level (`$CLAUDE_PROJECT_DIR/.claude`) configurations
|
17
|
+
- **Configuration Merging**: Intelligent merging of home and project configs with configurable precedence
|
18
|
+
- New environment variable `RUBY_CLAUDE_HOOKS_CONFIG_MERGE_STRATEGY` to control merge behavior ("project" or "home")
|
19
|
+
- New directory access methods: `home_claude_dir`, `project_claude_dir`
|
20
|
+
- New path utility methods: `home_path_for(path)`, `project_path_for(path)`
|
21
|
+
- Enhanced `path_for(path, base_dir=nil)` method with optional base directory parameter
|
22
|
+
- Comprehensive test suite for configuration functionality (`test/` directory)
|
23
|
+
- Configuration validation and edge case handling for missing `CLAUDE_PROJECT_DIR`
|
24
|
+
|
25
|
+
### Changed
|
26
|
+
- **Logs Location**: Logs now always go to `$HOME/.claude/{logDirectory}` regardless of active configuration
|
27
|
+
- Configuration loading now supports dual config file locations with intelligent merging
|
28
|
+
- Enhanced documentation with comprehensive dual configuration examples
|
29
|
+
- Updated API reference with new directory and path methods
|
30
|
+
|
31
|
+
### Deprecated
|
32
|
+
- `base_dir` method (still functional for backward compatibility)
|
33
|
+
- `RUBY_CLAUDE_HOOKS_BASE_DIR` environment variable (still supported as fallback)
|
34
|
+
|
35
|
+
### Fixed
|
36
|
+
- Graceful handling of undefined `CLAUDE_PROJECT_DIR` environment variable
|
37
|
+
- Proper path resolution when project directory is not available
|
38
|
+
- Backward compatibility maintained for all existing hook scripts
|
39
|
+
|
40
|
+
### Migration Notes
|
41
|
+
- Existing configurations continue to work without changes
|
42
|
+
- New projects can leverage dual configuration system
|
43
|
+
- `base_dir` and legacy `path_for` methods remain functional
|
44
|
+
- Environment variables maintain same precedence over config files
|
45
|
+
|
8
46
|
## [0.1.0] - 2025-08-17
|
9
47
|
|
10
48
|
### Added
|
data/README.md
CHANGED
@@ -4,6 +4,8 @@ A Ruby DSL (Domain Specific Language) for creating Claude Code hooks. This will
|
|
4
4
|
|
5
5
|
[**Why use this instead of writing bash, or simple ruby scripts?**](WHY.md)
|
6
6
|
|
7
|
+
> You might also be interested in my other project, a [Claude Code statusline](https://github.com/gabriel-dehan/claude_monitor_statusline) that shows your Claude usage in realtime, inside Claude Code ✨.
|
8
|
+
|
7
9
|
## 🚀 Quick Start
|
8
10
|
|
9
11
|
> [!TIP]
|
@@ -44,10 +46,11 @@ if __FILE__ == $0
|
|
44
46
|
end
|
45
47
|
```
|
46
48
|
|
47
|
-
3. ⚠️ **Make it executable
|
49
|
+
3. ⚠️ **Make it executable**
|
48
50
|
```bash
|
49
51
|
chmod +x add_context_after_prompt.rb
|
50
|
-
|
52
|
+
# Test it
|
53
|
+
echo '{"session_id":"test","prompt":"Hello!"}' | ./add_context_after_prompt.rb
|
51
54
|
```
|
52
55
|
|
53
56
|
4. **Register it in your `.claude/settings.json`**
|
@@ -87,6 +90,7 @@ Or add it to your Gemfile (you can add a Gemfile in your `.claude` directory if
|
|
87
90
|
```ruby
|
88
91
|
# .claude/Gemfile
|
89
92
|
source 'https://rubygems.org'
|
93
|
+
|
90
94
|
gem 'claude_hooks'
|
91
95
|
```
|
92
96
|
|
@@ -101,63 +105,92 @@ $ bundle install
|
|
101
105
|
|
102
106
|
### 🔧 Configuration
|
103
107
|
|
104
|
-
|
105
|
-
|
108
|
+
Claude Hooks supports both home-level (`$HOME/.claude`) and project-level (`$CLAUDE_PROJECT_DIR/.claude`) directories. Claude Hooks specific config files (`config/config.json`) found in either directory will be merged together.
|
106
109
|
|
107
|
-
|
110
|
+
| Directory | Description | Purpose |
|
111
|
+
|-----------|-------------|---------|
|
112
|
+
| `$HOME/.claude` | Home Claude directory | Global user settings and logs |
|
113
|
+
| `$CLAUDE_PROJECT_DIR/.claude` | Project Claude directory | Project-specific settings |
|
108
114
|
|
109
|
-
|
110
|
-
|
111
|
-
| `baseDir` | Base directory for all Claude files | `~/.claude` |
|
112
|
-
| `logDirectory` | Directory for logs (relative to baseDir) | `logs` |
|
115
|
+
> [!NOTE]
|
116
|
+
> Logs always go to `$HOME/.claude/{logDirectory}`
|
113
117
|
|
114
|
-
#### Environment Variables
|
118
|
+
#### Environment Variables
|
115
119
|
|
116
|
-
|
120
|
+
You can configure Claude Hooks through environment variables with the `RUBY_CLAUDE_HOOKS_` prefix:
|
117
121
|
|
118
122
|
```bash
|
119
|
-
|
120
|
-
export RUBY_CLAUDE_HOOKS_LOG_DIR="logs"
|
123
|
+
# Existing configuration options
|
124
|
+
export RUBY_CLAUDE_HOOKS_LOG_DIR="logs" # Default: logs (relative to HOME/.claude)
|
125
|
+
export RUBY_CLAUDE_HOOKS_CONFIG_MERGE_STRATEGY="project" # Config merge strategy: "project" or "home", default: "project"
|
126
|
+
export RUBY_CLAUDE_HOOKS_BASE_DIR="~/.claude" # DEPRECATED: fallback base directory
|
121
127
|
|
122
|
-
#
|
128
|
+
# Any variable prefixed with RUBY_CLAUDE_HOOKS_
|
129
|
+
# will also be available through the config object
|
123
130
|
export RUBY_CLAUDE_HOOKS_API_KEY="your-api-key"
|
124
131
|
export RUBY_CLAUDE_HOOKS_DEBUG_MODE="true"
|
125
132
|
export RUBY_CLAUDE_HOOKS_USER_NAME="Gabriel"
|
126
133
|
```
|
127
134
|
|
128
|
-
#### Configuration
|
135
|
+
#### Configuration Files
|
129
136
|
|
130
|
-
You can
|
131
|
-
The gem will read from it as fallback for any missing environment variables.
|
137
|
+
You can also use configuration files in any of the two locations:
|
132
138
|
|
139
|
+
**Home config** (`$HOME/.claude/config/config.json`):
|
133
140
|
```json
|
134
141
|
{
|
135
|
-
|
142
|
+
// Existing configuration option
|
136
143
|
"logDirectory": "logs",
|
137
|
-
|
138
|
-
"
|
144
|
+
// Custom configuration options
|
145
|
+
"apiKey": "your-global-api-key",
|
139
146
|
"userName": "Gabriel"
|
140
147
|
}
|
141
148
|
```
|
142
149
|
|
143
|
-
|
150
|
+
**Project config** (`$CLAUDE_PROJECT_DIR/.claude/config/config.json`):
|
151
|
+
```json
|
152
|
+
{
|
153
|
+
// Custom configuration option
|
154
|
+
"projectSpecificConfig": "someValue",
|
155
|
+
}
|
156
|
+
```
|
157
|
+
|
158
|
+
#### Configuration Merging
|
159
|
+
|
160
|
+
When both config files exist, they will be merged with configurable precedence:
|
161
|
+
|
162
|
+
- **Default (`project`)**: Project config values override home config values
|
163
|
+
- **Home precedence (`home`)**: Home config values override project config values
|
164
|
+
|
165
|
+
Set merge strategy: `export RUBY_CLAUDE_HOOKS_CONFIG_MERGE_STRATEGY="home" | "project"` (default: "project")
|
166
|
+
|
167
|
+
> [!WARNING]
|
168
|
+
> Environment Variables > Merged Config Files
|
169
|
+
|
170
|
+
#### Accessing Configuration Variables
|
144
171
|
|
145
172
|
You can access any configuration value in your handlers:
|
146
173
|
|
147
174
|
```ruby
|
148
175
|
class MyHandler < ClaudeHooks::UserPromptSubmit
|
149
176
|
def call
|
150
|
-
# Access
|
151
|
-
log "
|
177
|
+
# Access directory paths
|
178
|
+
log "Home Claude dir: #{home_claude_dir}"
|
179
|
+
log "Project Claude dir: #{project_claude_dir}" # nil if CLAUDE_PROJECT_DIR not set
|
180
|
+
log "Base dir (deprecated): #{base_dir}"
|
152
181
|
log "Logs dir: #{config.logs_directory}"
|
153
182
|
|
183
|
+
# Path utilities
|
184
|
+
log "Home config path: #{home_path_for('config')}"
|
185
|
+
log "Project hooks path: #{project_path_for('hooks')}" # nil if no project dir
|
186
|
+
|
154
187
|
# Access custom config via method calls
|
155
188
|
log "API Key: #{config.api_key}"
|
156
189
|
log "Debug mode: #{config.debug_mode}"
|
157
190
|
log "User: #{config.user_name}"
|
158
191
|
|
159
192
|
# Or use get_config_value for more control
|
160
|
-
user_name = config.get_config_value('USER_NAME', 'userName'
|
193
|
+
user_name = config.get_config_value('USER_NAME', 'userName')
|
161
194
|
log "Username: #{user_name}"
|
162
195
|
|
163
196
|
output_data
|
@@ -165,18 +198,16 @@ class MyHandler < ClaudeHooks::UserPromptSubmit
|
|
165
198
|
end
|
166
199
|
```
|
167
200
|
|
168
|
-
**Configuration Priority:** Environment variables always take precedence over config file values.
|
169
|
-
|
170
201
|
## 📖 Table of Contents
|
171
202
|
|
172
203
|
- [Ruby DSL for Claude Code hooks](#ruby-dsl-for-claude-code-hooks)
|
173
204
|
- [🚀 Quick Start](#-quick-start)
|
174
205
|
- [📦 Installation](#-installation)
|
175
206
|
- [🔧 Configuration](#-configuration)
|
176
|
-
- [
|
177
|
-
- [
|
178
|
-
- [Configuration
|
179
|
-
- [Accessing
|
207
|
+
- [Environment Variables](#environment-variables)
|
208
|
+
- [Configuration Files](#configuration-files)
|
209
|
+
- [Configuration Merging](#configuration-merging)
|
210
|
+
- [Accessing Configuration Variables](#accessing-configuration-variables)
|
180
211
|
- [📖 Table of Contents](#-table-of-contents)
|
181
212
|
- [🏗️ Architecture](#️-architecture)
|
182
213
|
- [Core Components](#core-components)
|
@@ -184,7 +215,7 @@ end
|
|
184
215
|
- [🪝 Hook Types](#-hook-types)
|
185
216
|
- [🚀 Claude Hook Flow](#-claude-hook-flow)
|
186
217
|
- [A very simplified view of how a hook works in Claude Code](#a-very-simplified-view-of-how-a-hook-works-in-claude-code)
|
187
|
-
- [🔄 Claude Hook
|
218
|
+
- [🔄 Proposal: a more robust Claude Hook execution flow](#-proposal-a-more-robust-claude-hook-execution-flow)
|
188
219
|
- [Basic Hook Handler Structure](#basic-hook-handler-structure)
|
189
220
|
- [Input Fields](#input-fields)
|
190
221
|
- [📚 API Reference](#-api-reference)
|
@@ -192,7 +223,9 @@ end
|
|
192
223
|
- [Input Methods](#input-methods)
|
193
224
|
- [Output Methods](#output-methods)
|
194
225
|
- [Class Output Methods](#class-output-methods)
|
226
|
+
- [Configuration and Utility Methods](#configuration-and-utility-methods)
|
195
227
|
- [Utility Methods](#utility-methods)
|
228
|
+
- [Configuration Methods](#configuration-methods)
|
196
229
|
- [UserPromptSubmit API](#userpromptsubmit-api)
|
197
230
|
- [Input Methods](#input-methods-1)
|
198
231
|
- [Output Methods](#output-methods-1)
|
@@ -218,9 +251,6 @@ end
|
|
218
251
|
- [SessionStart API](#sessionstart-api)
|
219
252
|
- [Input Methods](#input-methods-8)
|
220
253
|
- [Output Methods](#output-methods-8)
|
221
|
-
- [Configuration and Utility Methods](#configuration-and-utility-methods)
|
222
|
-
- [Configuration Methods](#configuration-methods)
|
223
|
-
- [Utility Methods](#utility-methods-2)
|
224
254
|
- [📝 Logging](#-logging)
|
225
255
|
- [Log File Location](#log-file-location)
|
226
256
|
- [Log Output Format](#log-output-format)
|
@@ -298,10 +328,10 @@ The framework supports the following hook types:
|
|
298
328
|
|
299
329
|
```mermaid
|
300
330
|
graph LR
|
301
|
-
|
331
|
+
A[Hook triggers] --> B[JSON from STDIN] --> C[Hook does its thing] --> D[JSON to STDOUT or STDERR] --> E[Yields back to Claude Code] --> A
|
302
332
|
```
|
303
333
|
|
304
|
-
### 🔄 Claude Hook
|
334
|
+
### 🔄 Proposal: a more robust Claude Hook execution flow
|
305
335
|
|
306
336
|
1. An entrypoint for a hook is set in `~/.claude/settings.json`
|
307
337
|
2. Claude Code calls the entrypoint script (e.g., `hooks/entrypoints/pre_tool_use.rb`)
|
@@ -318,8 +348,8 @@ graph TD
|
|
318
348
|
C --> D[📋 Entrypoint<br />Parses JSON from STDIN]
|
319
349
|
D --> E[📋 Entrypoint<br />Calls hook handlers]
|
320
350
|
|
321
|
-
E --> F[📝 AppendContextRules.call<br/><em>Returns output_data</em>]
|
322
|
-
E --> G[📝 PromptGuard.call<br/><em>Returns output_data</em>]
|
351
|
+
E --> F[📝 Handler<br />AppendContextRules.call<br/><em>Returns output_data</em>]
|
352
|
+
E --> G[📝 Handler<br />PromptGuard.call<br/><em>Returns output_data</em>]
|
323
353
|
|
324
354
|
F --> J[📋 Entrypoint<br />Calls _ClaudeHooks::UserPromptSubmit.merge_outputs_ to 🔀 merge outputs]
|
325
355
|
G --> J
|
@@ -422,11 +452,30 @@ Each hook type provides a **class method** `merge_outputs` that will try to inte
|
|
422
452
|
|--------|-------------|
|
423
453
|
| `merge_outputs(*outputs_data)` | Intelligently merge multiple outputs into a single output |
|
424
454
|
|
455
|
+
### Configuration and Utility Methods
|
456
|
+
|
457
|
+
Available in all hooks via the base `ClaudeHooks::Base` class:
|
458
|
+
|
425
459
|
#### Utility Methods
|
426
460
|
| Method | Description |
|
427
461
|
|--------|-------------|
|
428
462
|
| `log(message, level: :info)` | Log to session-specific file (levels: :info, :warn, :error) |
|
429
463
|
|
464
|
+
#### Configuration Methods
|
465
|
+
| Method | Description |
|
466
|
+
|--------|-------------|
|
467
|
+
| `home_claude_dir` | Get the home Claude directory (`$HOME/.claude`) |
|
468
|
+
| `project_claude_dir` | Get the project Claude directory (`$CLAUDE_PROJECT_DIR/.claude`, or `nil`) |
|
469
|
+
| `home_path_for(relative_path)` | Get absolute path relative to home Claude directory |
|
470
|
+
| `project_path_for(relative_path)` | Get absolute path relative to project Claude directory (or `nil`) |
|
471
|
+
| `base_dir` | Get the base Claude directory (**deprecated**) |
|
472
|
+
| `path_for(relative_path, base_dir=nil)` | Get absolute path relative to specified or default base dir (**deprecated**) |
|
473
|
+
| `config` | Access the merged configuration object |
|
474
|
+
| `config.get_config_value(env_key, config_file_key, default)` | Get any config value with fallback |
|
475
|
+
| `config.logs_directory` | Get logs directory path (always under home directory) |
|
476
|
+
| `config.your_custom_key` | Access any custom config via method_missing |
|
477
|
+
|
478
|
+
|
430
479
|
### UserPromptSubmit API
|
431
480
|
|
432
481
|
Available when inheriting from `ClaudeHooks::UserPromptSubmit`:
|
@@ -560,26 +609,6 @@ Available when inheriting from `ClaudeHooks::SessionStart`:
|
|
560
609
|
| `add_context!(context)` | Alias for `add_additional_context!` |
|
561
610
|
| `empty_additional_context!` | Clear additional context |
|
562
611
|
|
563
|
-
### Configuration and Utility Methods
|
564
|
-
|
565
|
-
Available in all hooks via the base `ClaudeHooks::Base` class:
|
566
|
-
|
567
|
-
#### Configuration Methods
|
568
|
-
| Method | Description |
|
569
|
-
|--------|-------------|
|
570
|
-
| `base_dir` | Get the base Claude directory |
|
571
|
-
| `path_for(relative_path)` | Get absolute path relative to base dir |
|
572
|
-
| `config` | Access the full configuration object |
|
573
|
-
| `config.get_config_value(env_key, config_key, default)` | Get any config value with fallback |
|
574
|
-
| `config.logs_directory` | Get logs directory path |
|
575
|
-
| `config.your_custom_key` | Access any custom config via method_missing |
|
576
|
-
|
577
|
-
#### Utility Methods
|
578
|
-
| Method | Description |
|
579
|
-
|--------|-------------|
|
580
|
-
| `log(message, level: :info)` | Log to session-specific file (levels: :info, :warn, :error) |
|
581
|
-
| `log(level: :info) { block }` | Multiline logging with block support |
|
582
|
-
|
583
612
|
### 📝 Logging
|
584
613
|
|
585
614
|
`ClaudeHooks::Base` provides a **session logger** that will write logs to session-specific files.
|
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'claude_hooks'
|
4
4
|
require 'json'
|
5
|
-
require_relative '../user_prompt_submit/append_rules'
|
6
|
-
require_relative '../user_prompt_submit/log_user_prompt'
|
5
|
+
require_relative '../handlers/user_prompt_submit/append_rules'
|
6
|
+
require_relative '../handlers/user_prompt_submit/log_user_prompt'
|
7
7
|
|
8
8
|
begin
|
9
9
|
# Read input from stdin
|
@@ -8,12 +8,12 @@ class AppendRules < ClaudeHooks::UserPromptSubmit
|
|
8
8
|
def call
|
9
9
|
log "Executing AppendRules hook"
|
10
10
|
|
11
|
-
# Read the
|
12
|
-
|
11
|
+
# Read the rules
|
12
|
+
rules = read_rules
|
13
13
|
|
14
|
-
if
|
15
|
-
add_additional_context!(
|
16
|
-
log "Successfully added
|
14
|
+
if rules
|
15
|
+
add_additional_context!(rules)
|
16
|
+
log "Successfully added rules as additional context (#{rules.length} characters)"
|
17
17
|
else
|
18
18
|
log "No rule content found", level: :warn
|
19
19
|
end
|
@@ -23,8 +23,9 @@ class AppendRules < ClaudeHooks::UserPromptSubmit
|
|
23
23
|
|
24
24
|
private
|
25
25
|
|
26
|
-
def
|
27
|
-
|
26
|
+
def read_rules
|
27
|
+
# If we were in the project directory, we would use project_path_for instead
|
28
|
+
rule_file_path = home_path_for('rules/post-user-prompt.rule.md')
|
28
29
|
|
29
30
|
if File.exist?(rule_file_path)
|
30
31
|
content = File.read(rule_file_path).strip
|
@@ -32,7 +33,8 @@ class AppendRules < ClaudeHooks::UserPromptSubmit
|
|
32
33
|
end
|
33
34
|
|
34
35
|
log "Rule file not found or empty at: #{rule_file_path}", level: :warn
|
35
|
-
|
36
|
+
# If we were in the project directory, we would use project_claude_dir instead
|
37
|
+
log "Base directory: #{home_claude_dir}"
|
36
38
|
nil
|
37
39
|
end
|
38
40
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require 'fileutils'
|
4
3
|
require 'claude_hooks'
|
5
4
|
|
6
5
|
# Example hook module that logs user prompts to a file
|
@@ -9,10 +8,6 @@ class LogUserPrompt < ClaudeHooks::UserPromptSubmit
|
|
9
8
|
def call
|
10
9
|
log "Executing LogUserPrompt hook"
|
11
10
|
|
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
11
|
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
17
12
|
|
18
13
|
log <<~TEXT
|
@@ -22,13 +17,6 @@ class LogUserPrompt < ClaudeHooks::UserPromptSubmit
|
|
22
17
|
|
23
18
|
nil
|
24
19
|
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
20
|
end
|
33
21
|
|
34
22
|
# If this file is run directly (for testing), call the hook
|
data/lib/claude_hooks/base.rb
CHANGED
@@ -111,8 +111,24 @@ module ClaudeHooks
|
|
111
111
|
config.base_dir
|
112
112
|
end
|
113
113
|
|
114
|
-
def
|
115
|
-
config.
|
114
|
+
def home_claude_dir
|
115
|
+
config.home_claude_dir
|
116
|
+
end
|
117
|
+
|
118
|
+
def project_claude_dir
|
119
|
+
config.project_claude_dir
|
120
|
+
end
|
121
|
+
|
122
|
+
def path_for(relative_path, base_directory = nil)
|
123
|
+
config.path_for(relative_path, base_directory)
|
124
|
+
end
|
125
|
+
|
126
|
+
def home_path_for(relative_path)
|
127
|
+
config.home_path_for(relative_path)
|
128
|
+
end
|
129
|
+
|
130
|
+
def project_path_for(relative_path)
|
131
|
+
config.project_path_for(relative_path)
|
116
132
|
end
|
117
133
|
|
118
134
|
# Supports both single messages and blocks for multiline logging
|
@@ -16,29 +16,75 @@ module ClaudeHooks
|
|
16
16
|
def reload!
|
17
17
|
@config = nil
|
18
18
|
@base_dir = nil
|
19
|
+
@home_claude_dir = nil
|
20
|
+
@project_claude_dir = nil
|
19
21
|
@config_file_path = nil
|
22
|
+
@home_config_file_path = nil
|
23
|
+
@project_config_file_path = nil
|
20
24
|
end
|
21
25
|
|
22
|
-
# Get the
|
26
|
+
# Get the home Claude directory (always ~/.claude)
|
27
|
+
def home_claude_dir
|
28
|
+
@home_claude_dir ||= File.expand_path('~/.claude')
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get the project Claude directory (from CLAUDE_PROJECT_DIR/.claude)
|
32
|
+
# Returns nil if CLAUDE_PROJECT_DIR environment variable is not set
|
33
|
+
def project_claude_dir
|
34
|
+
@project_claude_dir ||= begin
|
35
|
+
project_dir = ENV['CLAUDE_PROJECT_DIR']
|
36
|
+
if project_dir
|
37
|
+
File.expand_path(File.join(project_dir, '.claude'))
|
38
|
+
else
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get the base directory from ENV or default (backward compatibility)
|
45
|
+
# This method will determine which base directory to use based on context
|
23
46
|
def base_dir
|
24
47
|
@base_dir ||= begin
|
48
|
+
# Check for legacy environment variable first
|
25
49
|
env_base_dir = ENV["#{ENV_PREFIX}BASE_DIR"]
|
26
|
-
|
50
|
+
if env_base_dir
|
51
|
+
File.expand_path(env_base_dir)
|
52
|
+
else
|
53
|
+
# Default to home directory for backward compatibility
|
54
|
+
home_claude_dir
|
55
|
+
end
|
27
56
|
end
|
28
57
|
end
|
29
58
|
|
30
59
|
# Get the full path for a file/directory relative to base_dir
|
31
|
-
|
32
|
-
|
60
|
+
# Can optionally specify which base directory to use
|
61
|
+
def path_for(relative_path, base_directory = nil)
|
62
|
+
base_directory ||= base_dir
|
63
|
+
File.join(base_directory, relative_path)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Get the full path for a file/directory relative to home_claude_dir
|
67
|
+
def home_path_for(relative_path)
|
68
|
+
File.join(home_claude_dir, relative_path)
|
33
69
|
end
|
34
70
|
|
35
|
-
# Get the
|
71
|
+
# Get the full path for a file/directory relative to project_claude_dir
|
72
|
+
# Returns nil if CLAUDE_PROJECT_DIR environment variable is not set
|
73
|
+
def project_path_for(relative_path)
|
74
|
+
if project_claude_dir
|
75
|
+
File.join(project_claude_dir, relative_path)
|
76
|
+
else
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Get the log directory path (always relative to home_claude_dir)
|
36
82
|
def logs_directory
|
37
83
|
log_dir = get_config_value('LOG_DIR', 'logDirectory') || 'logs'
|
38
84
|
if log_dir.start_with?('/')
|
39
85
|
log_dir # Absolute path
|
40
86
|
else
|
41
|
-
|
87
|
+
File.join(home_claude_dir, log_dir) # Always relative to home_claude_dir
|
42
88
|
end
|
43
89
|
end
|
44
90
|
|
@@ -76,7 +122,7 @@ module ClaudeHooks
|
|
76
122
|
# Check if we have a config value for this method
|
77
123
|
env_key = method_name.to_s.upcase
|
78
124
|
config_key = snake_case_to_camel_case(method_name.to_s)
|
79
|
-
|
125
|
+
|
80
126
|
!get_config_value(env_key, config_key).nil? || super
|
81
127
|
end
|
82
128
|
|
@@ -92,25 +138,63 @@ module ClaudeHooks
|
|
92
138
|
@config_file_path ||= path_for('config/config.json')
|
93
139
|
end
|
94
140
|
|
141
|
+
def home_config_file_path
|
142
|
+
@home_config_file_path ||= File.join(home_claude_dir, 'config/config.json')
|
143
|
+
end
|
144
|
+
|
145
|
+
def project_config_file_path
|
146
|
+
@project_config_file_path ||= begin
|
147
|
+
if project_claude_dir
|
148
|
+
File.join(project_claude_dir, 'config/config.json')
|
149
|
+
else
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
95
155
|
def load_config
|
96
|
-
#
|
97
|
-
|
98
|
-
|
156
|
+
# Load and merge config files from both locations
|
157
|
+
merged_file_config = load_and_merge_config_files
|
158
|
+
|
99
159
|
# Merge with ENV variables
|
100
160
|
env_config = load_env_config
|
101
|
-
|
102
|
-
# ENV variables take precedence
|
103
|
-
|
161
|
+
|
162
|
+
# ENV variables take precedence over file configs
|
163
|
+
merged_file_config.merge(env_config)
|
164
|
+
end
|
165
|
+
|
166
|
+
def load_and_merge_config_files
|
167
|
+
home_config = load_config_file_from_path(home_config_file_path)
|
168
|
+
project_config = load_config_file_from_path(project_config_file_path) if project_config_file_path
|
169
|
+
|
170
|
+
# Determine merge strategy
|
171
|
+
merge_strategy = ENV['RUBY_CLAUDE_HOOKS_CONFIG_MERGE_STRATEGY'] || 'project'
|
172
|
+
|
173
|
+
if project_config && merge_strategy == 'project'
|
174
|
+
# Project config takes precedence
|
175
|
+
home_config.merge(project_config)
|
176
|
+
elsif project_config && merge_strategy == 'home'
|
177
|
+
# Home config takes precedence
|
178
|
+
project_config.merge(home_config)
|
179
|
+
else
|
180
|
+
# Only home config exists or no project config
|
181
|
+
home_config
|
182
|
+
end
|
104
183
|
end
|
105
184
|
|
106
185
|
def load_config_file
|
107
186
|
config_file = config_file_path
|
187
|
+
load_config_file_from_path(config_file)
|
188
|
+
end
|
108
189
|
|
109
|
-
|
190
|
+
def load_config_file_from_path(config_file_path)
|
191
|
+
return {} unless config_file_path
|
192
|
+
|
193
|
+
if File.exist?(config_file_path)
|
110
194
|
begin
|
111
|
-
JSON.parse(File.read(
|
195
|
+
JSON.parse(File.read(config_file_path))
|
112
196
|
rescue JSON::ParserError => e
|
113
|
-
warn "Warning: Error parsing config file #{
|
197
|
+
warn "Warning: Error parsing config file #{config_file_path}: #{e.message}"
|
114
198
|
{}
|
115
199
|
end
|
116
200
|
else
|
@@ -121,15 +205,15 @@ module ClaudeHooks
|
|
121
205
|
|
122
206
|
def load_env_config
|
123
207
|
env_config = {}
|
124
|
-
|
208
|
+
|
125
209
|
ENV.each do |key, value|
|
126
210
|
next unless key.start_with?(ENV_PREFIX)
|
127
|
-
|
211
|
+
|
128
212
|
# Remove prefix and convert to config key format
|
129
213
|
config_key = env_key_to_config_key(key.sub(ENV_PREFIX, ''))
|
130
214
|
env_config[config_key] = value
|
131
215
|
end
|
132
|
-
|
216
|
+
|
133
217
|
env_config
|
134
218
|
end
|
135
219
|
|
data/lib/claude_hooks/version.rb
CHANGED
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: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gabriel Dehan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-08-
|
11
|
+
date: 2025-08-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|