Skip to content

Commit 1d8d20d

Browse files
committed
updating to work with the latest version of the cli
1 parent 3db1955 commit 1d8d20d

10 files changed

Lines changed: 654 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.2.0] - 2025-09-07
11+
12+
### Added
13+
- Support for dynamic hooks configuration via `settings` parameter
14+
- `--settings` CLI flag support for runtime hooks configuration
15+
- Support for Hash, String, and Pathname settings input formats
16+
- Comprehensive RSpec tests for settings functionality
17+
- Dynamic hooks configuration documentation in README
18+
19+
### Changed
20+
- Options class now accepts optional `settings` parameter
21+
- Enhanced to_cli_args method to handle settings parameter
22+
23+
### Documentation
24+
- Added dynamic hooks configuration section to README
25+
- Added webhook integration examples
26+
- Added security considerations for hook commands
27+
1028
## [0.1.0] - 2025-01-10
1129

1230
### Added
@@ -32,5 +50,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3250
- RuboCop style enforcement
3351
- YARD documentation support
3452

35-
[Unreleased]: https://github.com/TanookiLabs/claude-code-sdk-ruby/compare/v0.1.0...HEAD
53+
[Unreleased]: https://github.com/TanookiLabs/claude-code-sdk-ruby/compare/v0.2.0...HEAD
54+
[0.2.0]: https://github.com/TanookiLabs/claude-code-sdk-ruby/compare/v0.1.0...v0.2.0
3655
[0.1.0]: https://github.com/TanookiLabs/claude-code-sdk-ruby/releases/tag/v0.1.0

README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,99 @@ end
216216
| `tree` | Array | Paths to include in file tree context |
217217
| `tree_symlinks` | Boolean | Include symlinks in file tree |
218218
| `tree_verbose` | Boolean | Verbose file tree output |
219+
| `settings` | Hash/String/Pathname | Dynamic hooks configuration - Hash (converted to JSON), JSON string, file path, or Pathname |
220+
221+
### Dynamic Hooks Configuration
222+
223+
The SDK supports dynamic hooks configuration via the `settings` parameter, enabling per-session hook management without modifying global configuration files.
224+
225+
#### Supported Input Formats
226+
227+
The `settings` parameter accepts three formats:
228+
229+
1. **Ruby Hash** - Directly pass a hash that will be converted to JSON:
230+
```ruby
231+
settings = {
232+
"hooks" => {
233+
"PostToolUse" => [
234+
{
235+
"matcher" => "Bash",
236+
"hooks" => [
237+
{
238+
"type" => "command",
239+
"command" => "echo 'Command executed' >> /tmp/claude.log"
240+
}
241+
]
242+
}
243+
]
244+
}
245+
}
246+
options = ClaudeCodeSDK::Options.new(settings: settings)
247+
```
248+
249+
2. **JSON String** - A JSON string directly:
250+
```ruby
251+
json_settings = '{"hooks": {"PostToolUse": [{"matcher": "Bash", "hooks": [{"type": "command", "command": "echo done"}]}]}}'
252+
options = ClaudeCodeSDK::Options.new(settings: json_settings)
253+
```
254+
255+
3. **File Path** - Path to a JSON settings file:
256+
```ruby
257+
options = ClaudeCodeSDK::Options.new(settings: "/path/to/settings.json")
258+
```
259+
260+
4. **Pathname Object** - For better path handling:
261+
```ruby
262+
require 'pathname'
263+
path = Pathname.new("/path/to/settings.json")
264+
options = ClaudeCodeSDK::Options.new(settings: path)
265+
```
266+
267+
#### Available Hook Events
268+
269+
- `PreToolUse` - Triggered before tool execution
270+
- `PostToolUse` - Triggered after tool completion
271+
- `UserPromptSubmit` - Triggered on user input
272+
- `Stop` - Triggered when main agent completes
273+
- `SubagentStop` - Triggered when subagent completes
274+
- `PreCompact` - Triggered before context compaction
275+
- `SessionStart` - Triggered at session start
276+
- `SessionEnd` - Triggered at session end
277+
278+
#### Webhook Integration Example
279+
280+
```ruby
281+
# Configure hooks to send events to a webhook
282+
webhook_url = "https://api.example.com/claude-hooks"
283+
auth_token = "secret-token"
284+
285+
hooks_config = {
286+
"hooks" => {
287+
"PostToolUse" => [
288+
{
289+
"matcher" => "Bash",
290+
"hooks" => [
291+
{
292+
"type" => "command",
293+
"command" => "curl -X POST #{webhook_url} -H 'Authorization: Bearer #{auth_token}' -d \"$HOOK_INPUT\""
294+
}
295+
]
296+
}
297+
]
298+
}
299+
}
300+
301+
options = ClaudeCodeSDK::Options.new(settings: hooks_config)
302+
ClaudeCodeSDK.query("Run ls command", options)
303+
```
304+
305+
#### Security Considerations
306+
307+
⚠️ **Warning**: Hook commands execute with full system privileges. Always:
308+
- Sanitize any user input used in hook commands
309+
- Use absolute paths in commands
310+
- Validate webhook URLs and authentication
311+
- Avoid exposing sensitive data in hook commands
219312

220313
## Real-World Examples
221314

examples/allspark_integration.rb

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#!/usr/bin/env ruby
2+
require 'bundler/setup'
3+
require 'claude_code_sdk'
4+
require 'json'
5+
6+
# Example: AllSpark Builder Integration with Dynamic Hooks
7+
puts "AllSpark Builder - Claude Code SDK Integration Example"
8+
puts "=" * 55
9+
10+
# Example hooks configuration that AllSpark Builder would generate
11+
session_id = "build-session-#{Time.now.to_i}"
12+
project_id = "project-123"
13+
webhook_url = "http://allspark_builder-web:3000/api/v2/code_agent_hooks/webhook"
14+
15+
# This is what CodeAgentHooksService.generate_settings_for_sdk would return
16+
allspark_hooks_json = {
17+
"hooks" => {
18+
"PostToolUse" => [
19+
{
20+
"matcher" => "Bash|LS|Glob|Grep",
21+
"hooks" => [{
22+
"type" => "command",
23+
"command" => <<~BASH.strip
24+
if [ -n "$HOOK_INPUT" ]; then
25+
echo "$HOOK_INPUT" | ruby -rjson -rtime -e '
26+
input = JSON.parse(STDIN.read)
27+
enhanced = {
28+
"original_hook_data" => input,
29+
"metadata" => {
30+
"hook_type" => "PostToolUse",
31+
"tool_type" => "Command",
32+
"session_id" => "#{session_id}",
33+
"project_id" => "#{project_id}",
34+
"timestamp" => Time.now.iso8601
35+
}
36+
}
37+
puts enhanced.to_json
38+
' | curl -s -X POST "#{webhook_url}" \
39+
-H "Content-Type: application/json" \
40+
-H "X-Hook-Auth: mock-jwt-token" \
41+
-d @- \
42+
--max-time 5 \
43+
> /dev/null 2>&1 || true
44+
fi
45+
BASH
46+
}]
47+
},
48+
{
49+
"matcher" => "Write|Edit|MultiEdit|Read",
50+
"hooks" => [{
51+
"type" => "command",
52+
"command" => <<~BASH.strip
53+
if [ -n "$HOOK_INPUT" ]; then
54+
echo "$HOOK_INPUT" | ruby -rjson -rtime -e '
55+
input = JSON.parse(STDIN.read)
56+
enhanced = {
57+
"original_hook_data" => input,
58+
"metadata" => {
59+
"hook_type" => "PostToolUse",
60+
"tool_type" => "FileChange",
61+
"session_id" => "#{session_id}",
62+
"project_id" => "#{project_id}",
63+
"timestamp" => Time.now.iso8601
64+
}
65+
}
66+
puts enhanced.to_json
67+
' | curl -s -X POST "#{webhook_url}" \
68+
-H "Content-Type: application/json" \
69+
-H "X-Hook-Auth: mock-jwt-token" \
70+
-d @- \
71+
--max-time 5 \
72+
> /dev/null 2>&1 || true
73+
fi
74+
BASH
75+
}]
76+
}
77+
],
78+
"PreToolUse" => [
79+
{
80+
"matcher" => "Bash|LS|Glob|Grep",
81+
"hooks" => [{
82+
"type" => "command",
83+
"command" => <<~BASH.strip
84+
if [ -n "$HOOK_INPUT" ]; then
85+
echo "$HOOK_INPUT" | ruby -rjson -rtime -e '
86+
input = JSON.parse(STDIN.read)
87+
enhanced = {
88+
"original_hook_data" => input,
89+
"metadata" => {
90+
"hook_type" => "PreToolUse",
91+
"tool_type" => "WorkingDirectory",
92+
"session_id" => "#{session_id}",
93+
"project_id" => "#{project_id}",
94+
"timestamp" => Time.now.iso8601
95+
}
96+
}
97+
puts enhanced.to_json
98+
' | curl -s -X POST "#{webhook_url}" \
99+
-H "Content-Type: application/json" \
100+
-H "X-Hook-Auth: mock-jwt-token" \
101+
-d @- \
102+
--max-time 5 \
103+
> /dev/null 2>&1 || true
104+
fi
105+
BASH
106+
}]
107+
}
108+
]
109+
}
110+
}.to_json
111+
112+
puts "\n1. AllSpark Hooks Configuration:"
113+
puts " Session ID: #{session_id}"
114+
puts " Project ID: #{project_id}"
115+
puts " Webhook URL: #{webhook_url}"
116+
puts " JSON Length: #{allspark_hooks_json.length} characters"
117+
118+
puts "\n2. Creating Claude Code SDK Options with AllSpark hooks:"
119+
120+
# This is how AllSpark Builder would use the SDK
121+
options = ClaudeCodeSDK::Options.new(
122+
system_prompt: "You are a helpful coding assistant for AllSpark Builder project #{project_id}",
123+
allowed_tools: ["Bash", "Read", "Write", "Edit", "LS", "Glob", "Grep"],
124+
permission_mode: ClaudeCodeSDK::PermissionMode::ACCEPT_EDITS,
125+
settings: allspark_hooks_json # JSON string from CodeAgentHooksService
126+
)
127+
128+
puts " ✅ Options created successfully"
129+
puts " ✅ Settings type: #{options.settings.class}"
130+
puts " ✅ Settings length: #{options.settings.length} characters"
131+
132+
puts "\n3. Generating CLI Arguments:"
133+
134+
cli_args = options.to_cli_args
135+
settings_index = cli_args.index("--settings")
136+
137+
puts " ✅ CLI args generated: #{cli_args.length} total arguments"
138+
puts " ✅ --settings flag found: #{settings_index ? "Yes (index #{settings_index})" : "No"}"
139+
140+
if settings_index
141+
settings_value = cli_args[settings_index + 1]
142+
puts " ✅ Settings value type: #{settings_value.class}"
143+
puts " ✅ Settings JSON valid: #{JSON.parse(settings_value) ? "Yes" : "No" rescue "No"}"
144+
puts " ✅ Settings preview: #{settings_value[0..100]}..."
145+
end
146+
147+
puts "\n4. Testing JSON Detection Logic:"
148+
149+
# Test the SDK's JSON detection
150+
test_cases = [
151+
['Hash object', {"hooks" => {}}],
152+
['JSON string', '{"hooks": {"PostToolUse": []}}'],
153+
['File path', '/path/to/settings.json'],
154+
['Pathname object', Pathname.new('/tmp/settings.json')]
155+
]
156+
157+
test_cases.each do |name, value|
158+
begin
159+
test_options = ClaudeCodeSDK::Options.new(settings: value)
160+
test_args = test_options.to_cli_args
161+
settings_idx = test_args.index("--settings")
162+
result = settings_idx ? test_args[settings_idx + 1] : "NOT FOUND"
163+
puts " ✅ #{name}: #{result.class} - #{result[0..50]}..."
164+
rescue => e
165+
puts " ❌ #{name}: ERROR - #{e.message}"
166+
end
167+
end
168+
169+
puts "\n5. Simulating ClaudeCodeRubyExecutorJob Integration:"
170+
171+
# This simulates what happens in the actual job
172+
puts " Building SDK options in job context..."
173+
174+
job_options_hash = {
175+
system_prompt: "AllSpark Builder Assistant",
176+
allowed_tools: ["Bash", "Read", "Write", "Edit"],
177+
settings: allspark_hooks_json # This comes from build_hook_settings(session)
178+
}
179+
180+
job_options = ClaudeCodeSDK::Options.new(**job_options_hash)
181+
job_cli_args = job_options.to_cli_args
182+
183+
puts " ✅ Job options created successfully"
184+
puts " ✅ Job CLI args: #{job_cli_args.length} arguments"
185+
puts " ✅ Hooks included: #{job_cli_args.include?('--settings') ? 'Yes' : 'No'}"
186+
187+
puts "\n" + "=" * 55
188+
puts "✅ AllSpark Builder integration test completed successfully!"
189+
puts "✅ The Claude Code SDK now supports dynamic hooks via JSON strings"
190+
puts "✅ AllSpark Builder can inject session-specific hooks into Claude Code"
191+
192+
# Show final CLI command that would be executed
193+
puts "\nFinal CLI command (abbreviated):"
194+
puts "claude-code #{job_cli_args[0..5].join(' ')} --settings '{...}' [prompt]"

0 commit comments

Comments
 (0)