claude_agent 0.7.14 → 0.7.16

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/rules/conventions.md +66 -16
  3. data/CHANGELOG.md +20 -0
  4. data/CLAUDE.md +24 -4
  5. data/README.md +52 -1529
  6. data/SPEC.md +56 -29
  7. data/docs/architecture.md +339 -0
  8. data/docs/client.md +526 -0
  9. data/docs/configuration.md +571 -0
  10. data/docs/conversations.md +461 -0
  11. data/docs/errors.md +127 -0
  12. data/docs/events.md +225 -0
  13. data/docs/getting-started.md +310 -0
  14. data/docs/hooks.md +380 -0
  15. data/docs/logging.md +96 -0
  16. data/docs/mcp.md +308 -0
  17. data/docs/messages.md +871 -0
  18. data/docs/permissions.md +611 -0
  19. data/docs/queries.md +227 -0
  20. data/docs/sessions.md +335 -0
  21. data/lib/claude_agent/abort_controller.rb +24 -0
  22. data/lib/claude_agent/client/commands.rb +32 -0
  23. data/lib/claude_agent/client.rb +10 -4
  24. data/lib/claude_agent/configuration.rb +129 -0
  25. data/lib/claude_agent/control_protocol/commands.rb +28 -0
  26. data/lib/claude_agent/conversation.rb +37 -4
  27. data/lib/claude_agent/errors.rb +21 -4
  28. data/lib/claude_agent/event_handler.rb +14 -0
  29. data/lib/claude_agent/fork_session.rb +117 -0
  30. data/lib/claude_agent/hook_registry.rb +110 -0
  31. data/lib/claude_agent/hooks.rb +4 -0
  32. data/lib/claude_agent/mcp/server.rb +22 -0
  33. data/lib/claude_agent/mcp/tool.rb +24 -3
  34. data/lib/claude_agent/message.rb +93 -0
  35. data/lib/claude_agent/messages/streaming.rb +37 -0
  36. data/lib/claude_agent/options.rb +10 -0
  37. data/lib/claude_agent/permission_policy.rb +174 -0
  38. data/lib/claude_agent/permission_request.rb +17 -0
  39. data/lib/claude_agent/session.rb +100 -11
  40. data/lib/claude_agent/session_paths.rb +5 -2
  41. data/lib/claude_agent/turn_result.rb +20 -2
  42. data/lib/claude_agent/types/sessions.rb +8 -0
  43. data/lib/claude_agent/version.rb +1 -1
  44. data/lib/claude_agent.rb +187 -0
  45. data/sig/claude_agent.rbs +38 -1
  46. metadata +20 -1
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ClaudeAgent
2
2
 
3
- Ruby gem for building AI-powered applications with the [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview). This library essentially wraps the Claude Code CLI, providing both simple one-shot queries and interactive bidirectional sessions.
3
+ Ruby SDK for building AI-powered applications with the [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview). Wraps the Claude Code CLI with idiomatic Ruby interfaces for one-shot queries, multi-turn conversations, permissions, hooks, and MCP tools.
4
4
 
5
5
  ## Requirements
6
6
 
@@ -9,1587 +9,110 @@ Ruby gem for building AI-powered applications with the [Claude Agent SDK](https:
9
9
 
10
10
  ## Installation
11
11
 
12
- Add to your Gemfile:
13
-
14
12
  ```ruby
15
13
  gem "claude_agent"
16
14
  ```
17
15
 
18
- Then run:
19
-
20
- ```bash
21
- bundle install
22
- ```
23
-
24
- Or install directly:
25
-
26
- ```bash
27
- gem install claude_agent
28
- ```
29
-
30
16
  ## Quick Start
31
17
 
32
- ### Conversation (Recommended)
33
-
34
- The simplest way to have multi-turn conversations:
35
-
36
- ```ruby
37
- require "claude_agent"
38
-
39
- ClaudeAgent::Conversation.open(
40
- permission_mode: "acceptEdits",
41
- on_stream: ->(text) { print text }
42
- ) do |c|
43
- c.say("Fix the bug in auth.rb")
44
- c.say("Now add tests for the fix")
45
- puts "\nTotal cost: $#{c.total_cost}"
46
- end
47
- ```
48
-
49
18
  ### One-Shot Query
50
19
 
51
- For single questions:
52
-
53
- ```ruby
54
- require "claude_agent"
55
-
56
- ClaudeAgent.query(prompt: "What is the capital of France?").each do |message|
57
- case message
58
- when ClaudeAgent::AssistantMessage
59
- puts message.text
60
- when ClaudeAgent::ResultMessage
61
- puts "Cost: $#{message.total_cost_usd}"
62
- end
63
- end
64
- ```
65
-
66
- ### One-Shot with TurnResult
67
-
68
- Get a structured result without writing `case` statements:
69
-
70
20
  ```ruby
71
21
  require "claude_agent"
72
22
 
73
- turn = ClaudeAgent.query_turn(prompt: "What is the capital of France?")
23
+ turn = ClaudeAgent.ask("What is the capital of France?")
74
24
  puts turn.text
75
25
  puts "Cost: $#{turn.cost}"
76
- puts "Model: #{turn.model}"
77
- ```
78
-
79
- ### Interactive Client
80
-
81
- For fine-grained control over the conversation:
82
-
83
- ```ruby
84
- require "claude_agent"
85
-
86
- ClaudeAgent::Client.open do |client|
87
- client.on_text { |text| print text }
88
-
89
- turn = client.send_and_receive("Remember the number 42")
90
- turn = client.send_and_receive("What number did I ask you to remember?")
91
-
92
- puts "\nAnswer: #{turn.text}"
93
- end
94
- ```
95
-
96
- ## Conversation API
97
-
98
- The `Conversation` class manages the full lifecycle: auto-connects on first message, tracks multi-turn history, accumulates usage, and builds a unified tool activity timeline.
99
-
100
- ### Basic Usage
101
-
102
- ```ruby
103
- conversation = ClaudeAgent.conversation(
104
- model: "claude-sonnet-4-5-20250514",
105
- permission_mode: "acceptEdits",
106
- on_text: ->(text) { print text }
107
- )
108
-
109
- turn = conversation.say("Help me refactor this module")
110
- puts turn.tool_uses.map(&:display_label) # ["Read lib/foo.rb", "Edit lib/foo.rb"]
111
-
112
- turn = conversation.say("Now update the tests")
113
- puts "Session cost: $#{conversation.total_cost}"
114
-
115
- conversation.close
116
26
  ```
117
27
 
118
- ### Block Form
119
-
120
- Automatically cleans up when the block exits:
121
-
122
- ```ruby
123
- ClaudeAgent::Conversation.open(
124
- max_turns: 10,
125
- on_tool_use: ->(tool) { puts " Using: #{tool.display_label}" }
126
- ) do |c|
127
- c.say("Implement the feature described in SPEC.md")
128
- c.say("Run the tests and fix any failures")
129
-
130
- puts "Tools used: #{c.tool_activity.size}"
131
- puts "Total cost: $#{c.total_cost}"
132
- end
133
- ```
134
-
135
- ### Resume a Previous Session
28
+ ### Multi-Turn Conversation
136
29
 
137
30
  ```ruby
138
- conversation = ClaudeAgent.resume_conversation("session-abc-123")
139
- turn = conversation.say("Continue where we left off")
140
- conversation.close
141
- ```
142
-
143
- ### Callbacks
144
-
145
- Register callbacks for real-time event handling. Any `on_*` keyword is accepted — see [Event Handlers](#event-handlers) for the full list.
146
-
147
- ```ruby
148
- conversation = ClaudeAgent.conversation(
149
- on_text: ->(text) { print text },
150
- on_stream: ->(text) { print text }, # Alias for on_text
151
- on_thinking: ->(thought) { puts "Thinking: #{thought}" },
152
- on_tool_use: ->(tool) { puts "Tool: #{tool.display_label}" },
153
- on_tool_result: ->(result) { puts "Result: #{result.content&.slice(0, 80)}" },
154
- on_result: ->(result) { puts "Done! Cost: $#{result.total_cost_usd}" },
155
- on_message: ->(msg) { log(msg) }, # Catch-all
156
- on_stream_event: ->(evt) { handle_stream(evt) }, # Type-based
157
- on_status: ->(status) { show_status(status) },
158
- on_tool_progress: ->(prog) { update_spinner(prog) }
159
- )
160
- ```
161
-
162
- ### Tool Activity Timeline
163
-
164
- Track all tool executions across turns with timing:
165
-
166
- ```ruby
167
- ClaudeAgent::Conversation.open(permission_mode: "acceptEdits") do |c|
168
- c.say("Refactor the auth module")
169
-
170
- c.tool_activity.each do |activity|
171
- puts "#{activity.display_label} (turn #{activity.turn_index})"
172
- puts " Duration: #{activity.duration&.round(2)}s" if activity.duration
173
- puts " Error!" if activity.error?
174
- end
175
- end
176
- ```
177
-
178
- ### Live Tool Tracking
179
-
180
- Track tool status in real time for live UIs. Unlike `tool_activity` (built after a turn), `LiveToolActivity` updates as tools run:
181
-
182
- ```ruby
183
- # Conversation level — opt in with track_tools: true
184
- ClaudeAgent::Conversation.open(
185
- permission_mode: "acceptEdits",
186
- track_tools: true
187
- ) do |c|
188
- c.tool_tracker.on_start { |entry| puts "▸ #{entry.display_label}" }
189
- c.tool_tracker.on_progress { |entry| puts " #{entry.elapsed&.round(1)}s..." }
190
- c.tool_tracker.on_complete { |entry| puts "✓ #{entry.display_label}" }
191
-
31
+ ClaudeAgent.chat do |c|
192
32
  c.say("Fix the bug in auth.rb")
193
- # Tracker resets between turns automatically
194
- end
195
-
196
- # Client level — attach directly
197
- tracker = ClaudeAgent::ToolActivityTracker.attach(client)
198
- tracker.on_start { |entry| show_spinner(entry) }
199
- tracker.on_complete { |entry| hide_spinner(entry) }
200
-
201
- # Standalone — attach to any EventHandler
202
- tracker = ClaudeAgent::ToolActivityTracker.attach(event_handler)
203
-
204
- # Catch-all callback (receives event symbol + entry)
205
- tracker.on_change { |event, entry| log(event, entry.id) }
206
-
207
- # Query running/completed tools at any point
208
- tracker.running # => [LiveToolActivity, ...]
209
- tracker.done # => [LiveToolActivity, ...]
210
- tracker.errored # => [LiveToolActivity, ...]
211
- tracker["toolu_01ABC"] # => LiveToolActivity (O(1) lookup by tool use ID)
212
- ```
213
-
214
- ### Conversation Accessors
215
-
216
- ```ruby
217
- conversation.turns # Array of TurnResult objects
218
- conversation.messages # All messages across all turns
219
- conversation.tool_activity # Array of ToolActivity objects
220
- conversation.tool_tracker # ToolActivityTracker (when track_tools: true)
221
- conversation.total_cost # Total cost in USD
222
- conversation.session_id # Session ID from most recent turn
223
- conversation.usage # CumulativeUsage stats
224
- conversation.open? # Whether conversation is connected
225
- conversation.closed? # Whether conversation has been closed
226
- ```
227
-
228
- ## Configuration
229
-
230
- Use `ClaudeAgent::Options` to customize behavior:
231
-
232
- ```ruby
233
- options = ClaudeAgent::Options.new(
234
- # Model selection
235
- model: "claude-sonnet-4-5-20250514",
236
- fallback_model: "claude-haiku-3-5-20241022",
237
-
238
- # Conversation limits
239
- max_turns: 10,
240
- max_budget_usd: 1.0,
241
-
242
- # Extended thinking
243
- thinking: { type: "enabled", budgetTokens: 10000 }, # or "adaptive" or "disabled"
244
- # max_thinking_tokens: 10000, # Shorthand (when thinking not set)
245
-
246
- # Response effort
247
- effort: "high", # "low", "medium", "high", "max"
248
-
249
- # System prompt
250
- system_prompt: "You are a helpful coding assistant.",
251
- append_system_prompt: "Always be concise.",
252
-
253
- # Tool configuration
254
- tools: ["Read", "Write", "Bash"],
255
- allowed_tools: ["Read"],
256
- disallowed_tools: ["Write"],
257
-
258
- # Permission modes: "default", "acceptEdits", "plan", "dontAsk", "bypassPermissions"
259
- permission_mode: "acceptEdits",
260
- permission_queue: true, # Enable queue-based permissions (see Permissions section)
261
-
262
- # Working directory for file operations
263
- cwd: "/path/to/project",
264
- add_dirs: ["/additional/path"],
265
-
266
- # Agent configuration
267
- agent: "my-agent", # Agent name for main thread
268
-
269
- # Session management
270
- resume: "session-id",
271
- session_id: "custom-uuid", # Custom conversation UUID
272
- continue_conversation: true,
273
- fork_session: true,
274
- persist_session: true, # Default: true
275
-
276
- # Structured output
277
- output_format: {
278
- type: "object",
279
- properties: { answer: { type: "string" } }
280
- },
281
-
282
- # Prompt suggestions
283
- prompt_suggestions: true,
284
-
285
- # Debug logging (CLI-level)
286
- debug: true,
287
- debug_file: "/path/to/debug.log"
288
- )
289
-
290
- ClaudeAgent.query(prompt: "Help me refactor this code", options: options)
291
- ```
292
-
293
- ### Tools Preset
294
-
295
- Use a preset tool configuration:
296
-
297
- ```ruby
298
- # Using ToolsPreset class
299
- options = ClaudeAgent::Options.new(
300
- tools: ClaudeAgent::ToolsPreset.new(preset: "claude_code")
301
- )
302
-
303
- # Or as a Hash
304
- options = ClaudeAgent::Options.new(
305
- tools: { type: "preset", preset: "claude_code" }
306
- )
307
- ```
308
-
309
- ### Sandbox Settings
310
-
311
- Configure sandboxed command execution:
312
-
313
- ```ruby
314
- sandbox = ClaudeAgent::SandboxSettings.new(
315
- enabled: true,
316
- auto_allow_bash_if_sandboxed: true,
317
- excluded_commands: ["docker", "kubectl"],
318
- network: ClaudeAgent::SandboxNetworkConfig.new(
319
- allowed_domains: ["api.example.com"],
320
- allow_local_binding: true,
321
- allow_managed_domains_only: false
322
- ),
323
- filesystem: ClaudeAgent::SandboxFilesystemConfig.new(
324
- allow_write: ["/tmp/*"],
325
- deny_write: ["/etc/*"],
326
- deny_read: ["/secrets/*"]
327
- ),
328
- ripgrep: ClaudeAgent::SandboxRipgrepConfig.new(
329
- command: "/usr/local/bin/rg"
330
- )
331
- )
332
-
333
- options = ClaudeAgent::Options.new(sandbox: sandbox)
334
- ```
335
-
336
- ### Custom Agents
337
-
338
- Define custom subagents:
339
-
340
- ```ruby
341
- agents = {
342
- "test-runner" => ClaudeAgent::AgentDefinition.new(
343
- description: "Runs tests and reports results",
344
- prompt: "You are a test runner. Execute tests and report failures clearly.",
345
- tools: ["Read", "Bash"],
346
- model: "haiku",
347
- max_turns: 5, # Max agentic turns before stopping
348
- skills: ["testing", "debug"] # Skills to preload into agent context
349
- )
350
- }
351
-
352
- options = ClaudeAgent::Options.new(agents: agents)
353
- ```
354
-
355
- ## TurnResult
356
-
357
- A `TurnResult` accumulates all messages from sending a prompt to receiving the final `ResultMessage`. It eliminates the need for `case` statements over raw message types.
358
-
359
- ### Getting a TurnResult
360
-
361
- ```ruby
362
- # Via Conversation
363
- turn = conversation.say("Fix the bug")
364
-
365
- # Via Client
366
- turn = client.send_and_receive("Fix the bug")
367
-
368
- # Via one-shot query
369
- turn = ClaudeAgent.query_turn(prompt: "Fix the bug")
370
- ```
371
-
372
- ### Accessors
373
-
374
- ```ruby
375
- # Text and thinking
376
- turn.text # All text content concatenated
377
- turn.thinking # All thinking content concatenated
378
-
379
- # Tool usage
380
- turn.tool_uses # Array of ToolUseBlock / ServerToolUseBlock
381
- turn.tool_results # Array of ToolResultBlock / ServerToolResultBlock
382
- turn.tool_executions # Array of { tool_use:, tool_result: } pairs
383
-
384
- # Result data
385
- turn.cost # Total cost in USD
386
- turn.duration_ms # Wall-clock duration
387
- turn.session_id # Session ID for resumption
388
- turn.model # Model name
389
- turn.stop_reason # Why the model stopped ("end_turn", "tool_use", etc.)
390
- turn.usage # Token usage hash
391
- turn.model_usage # Per-model usage breakdown
392
- turn.structured_output # Structured output (if requested)
393
- turn.num_turns # Number of turns in session
394
-
395
- # Status
396
- turn.success? # Whether turn completed successfully
397
- turn.error? # Whether turn ended with error
398
- turn.complete? # Whether a ResultMessage was received
399
- turn.errors # Array of error strings
400
- turn.permission_denials # Array of SDKPermissionDenial
401
-
402
- # Filtered message access
403
- turn.assistant_messages # All AssistantMessages
404
- turn.user_messages # All UserMessages / UserMessageReplays
405
- turn.stream_events # All StreamEvents
406
- turn.content_blocks # All content blocks across assistant messages
407
- ```
408
-
409
- ## Event Handlers
410
-
411
- Register typed callbacks instead of writing `case` statements. Works with `Client`, `Conversation`, or standalone.
412
-
413
- Three event layers fire for every message:
414
-
415
- 1. **Catch-all** — `:message` fires for every message
416
- 2. **Type-based** — `message.type` fires (e.g. `:assistant`, `:stream_event`, `:status`, `:tool_progress`)
417
- 3. **Decomposed** — convenience events for rich content (`:text`, `:thinking`, `:tool_use`, `:tool_result`)
418
-
419
- ### Via Client
420
-
421
- ```ruby
422
- ClaudeAgent::Client.open do |client|
423
- # Decomposed events (extracted content)
424
- client.on_text { |text| print text }
425
- client.on_tool_use { |tool| puts "\nUsing: #{tool.display_label}" }
426
- client.on_tool_result { |result, tool_use| puts "Done: #{tool_use&.name}" }
427
- client.on_result { |result| puts "\nCost: $#{result.total_cost_usd}" }
428
-
429
- # Type-based events (full message object)
430
- client.on_stream_event { |evt| handle_stream(evt) }
431
- client.on_status { |status| show_status(status) }
432
- client.on_tool_progress { |prog| update_spinner(prog) }
433
-
434
- client.send_and_receive("Fix the bug in auth.rb")
435
- end
436
- ```
437
-
438
- ### Via One-Shot Query
439
-
440
- ```ruby
441
- events = ClaudeAgent::EventHandler.new
442
- .on_text { |text| print text }
443
- .on_result { |r| puts "\nDone!" }
444
-
445
- ClaudeAgent.query_turn(prompt: "Explain this code", events: events)
446
- ```
447
-
448
- ### Standalone
449
-
450
- ```ruby
451
- handler = ClaudeAgent::EventHandler.new
452
- handler.on(:text) { |text| print text }
453
- handler.on(:thinking) { |thought| puts "Thinking: #{thought}" }
454
- handler.on(:tool_use) { |tool| puts "Tool: #{tool.display_label}" }
455
- handler.on(:tool_result) { |result, tool_use| puts "Result for #{tool_use&.name}" }
456
- handler.on(:result) { |result| puts "Cost: $#{result.total_cost_usd}" }
457
- handler.on(:message) { |msg| log(msg) } # Catch-all
458
-
459
- # Type-based events work with on() too
460
- handler.on(:stream_event) { |evt| handle_stream(evt) }
461
- handler.on(:status) { |status| show_status(status) }
462
-
463
- # Dispatch manually
464
- client.receive_response.each { |msg| handler.handle(msg) }
465
- handler.reset! # Clear turn state between turns
466
- ```
467
-
468
- ## Message Types
469
-
470
- The SDK provides strongly-typed message classes for all protocol messages.
471
-
472
- ### AssistantMessage
473
-
474
- Claude's responses:
475
-
476
- ```ruby
477
- message.text # Combined text content
478
- message.thinking # Extended thinking content (if enabled)
479
- message.model # Model that generated the response
480
- message.uuid # Message UUID
481
- message.session_id # Session identifier
482
- message.tool_uses # Array of ToolUseBlock if Claude wants to use tools
483
- message.has_tool_use? # Check if tools are being used
484
- ```
485
-
486
- ### ResultMessage
487
-
488
- Final message with usage statistics:
489
-
490
- ```ruby
491
- result.uuid # Message UUID
492
- result.session_id # Session identifier
493
- result.num_turns # Number of conversation turns
494
- result.duration_ms # Total duration in milliseconds
495
- result.total_cost_usd # API cost in USD
496
- result.usage # Token usage breakdown
497
- result.model_usage # Per-model usage breakdown
498
- result.is_error # Whether the session ended in error
499
- result.success? # Convenience method
500
- result.error? # Convenience method
501
- result.errors # Array of error messages (if any)
502
- result.permission_denials # Array of SDKPermissionDenial (if any)
503
- result.stop_reason # Why the model stopped generating (e.g. "end_turn", "tool_use")
504
- result.fast_mode_state # Fast mode status (if applicable)
505
- ```
506
-
507
- ### UserMessageReplay
508
-
509
- Replayed user messages when resuming a session with existing history:
510
-
511
- ```ruby
512
- replay.content # Message content
513
- replay.uuid # Message UUID
514
- replay.session_id # Session identifier
515
- replay.parent_tool_use_id # Parent tool use ID (if tool result)
516
- replay.replay? # true if this is a replayed message
517
- replay.synthetic? # true if this is a synthetic message
518
- ```
519
-
520
- ### SystemMessage
521
-
522
- Internal system events:
523
-
524
- ```ruby
525
- system_msg.subtype # e.g., "init"
526
- system_msg.data # Event-specific data
527
- ```
528
-
529
- ### StreamEvent
530
-
531
- Real-time streaming events:
532
-
533
- ```ruby
534
- event.uuid # Event UUID
535
- event.session_id # Session identifier
536
- event.event_type # Type of stream event
537
- event.event # Raw event data
538
- ```
539
-
540
- ### CompactBoundaryMessage
541
-
542
- Conversation compaction marker:
543
-
544
- ```ruby
545
- boundary.uuid # Message UUID
546
- boundary.session_id # Session identifier
547
- boundary.trigger # "manual" or "auto"
548
- boundary.pre_tokens # Token count before compaction
549
- ```
550
-
551
- ### StatusMessage
552
-
553
- Session status updates:
554
-
555
- ```ruby
556
- status.uuid # Message UUID
557
- status.session_id # Session identifier
558
- status.status # e.g., "compacting"
559
- ```
560
-
561
- ### ToolProgressMessage
562
-
563
- Long-running tool progress:
564
-
565
- ```ruby
566
- progress.tool_use_id # Tool use ID
567
- progress.tool_name # Tool name
568
- progress.elapsed_time_seconds # Time elapsed
569
- ```
570
-
571
- ### HookResponseMessage
572
-
573
- Hook execution output:
574
-
575
- ```ruby
576
- hook_response.hook_id # Hook identifier
577
- hook_response.hook_name # Hook name
578
- hook_response.hook_event # Hook event type
579
- hook_response.stdout # Hook stdout
580
- hook_response.stderr # Hook stderr
581
- hook_response.output # Combined output
582
- hook_response.exit_code # Exit code
583
- hook_response.outcome # "success", "error", or "cancelled"
584
- hook_response.success? # Convenience predicate
585
- hook_response.error? # Convenience predicate
586
- hook_response.cancelled? # Convenience predicate
587
- ```
588
-
589
- ### HookStartedMessage
590
-
591
- Hook execution start notification:
592
-
593
- ```ruby
594
- hook_started.hook_id # Hook identifier
595
- hook_started.hook_name # Hook name
596
- hook_started.hook_event # Hook event type
597
- ```
598
-
599
- ### HookProgressMessage
600
-
601
- Progress during hook execution:
602
-
603
- ```ruby
604
- hook_progress.hook_id # Hook identifier
605
- hook_progress.hook_name # Hook name
606
- hook_progress.hook_event # Hook event type
607
- hook_progress.stdout # Hook stdout so far
608
- hook_progress.stderr # Hook stderr so far
609
- hook_progress.output # Combined output
610
- ```
611
-
612
- ### ToolUseSummaryMessage
613
-
614
- Summary of tool use for collapsed display:
615
-
616
- ```ruby
617
- summary.summary # Human-readable summary text
618
- summary.preceding_tool_use_ids # Tool use IDs this summarizes
619
- ```
620
-
621
- ### FilesPersistedEvent
622
-
623
- Files persisted to storage during a session:
624
-
625
- ```ruby
626
- persisted.files # Array of successfully persisted file paths
627
- persisted.failed # Array of files that failed to persist
628
- persisted.processed_at # Timestamp of persistence
629
- ```
630
-
631
- ### AuthStatusMessage
632
-
633
- Authentication status during login:
634
-
635
- ```ruby
636
- auth.is_authenticating # Whether auth is in progress
637
- auth.output # Auth output messages
638
- auth.error # Error message (if any)
639
- ```
640
-
641
- ### TaskNotificationMessage
642
-
643
- Background task completion notifications:
644
-
645
- ```ruby
646
- notification.task_id # Background task ID
647
- notification.status # "completed", "failed", or "stopped"
648
- notification.output_file # Path to task output file
649
- notification.summary # Task summary
650
- notification.completed? # Convenience predicate
651
- notification.failed? # Convenience predicate
652
- notification.stopped? # Convenience predicate
653
- ```
654
-
655
- ### TaskStartedMessage
656
-
657
- Background task (subagent) start notification:
658
-
659
- ```ruby
660
- task_started.task_id # Task ID
661
- task_started.tool_use_id # Associated tool use ID (optional)
662
- task_started.description # Task description (optional)
663
- task_started.task_type # Task type (optional)
664
- ```
665
-
666
- ### TaskProgressMessage
667
-
668
- Background task progress reporting:
669
-
670
- ```ruby
671
- task_progress.task_id # Task ID
672
- task_progress.description # What the task is doing
673
- task_progress.usage # Token usage so far (optional)
674
- task_progress.last_tool_name # Most recent tool used (optional)
675
- ```
676
-
677
- ### RateLimitEvent
678
-
679
- Rate limit status and utilization:
680
-
681
- ```ruby
682
- rate_limit.rate_limit_info # Full rate limit info hash
683
- rate_limit.status # Rate limit status (e.g. "allowed_warning")
684
- ```
685
-
686
- ### PromptSuggestionMessage
687
-
688
- Suggested follow-up prompts (requires `prompt_suggestions: true`):
689
-
690
- ```ruby
691
- suggestion.suggestion # The suggested prompt text
692
- ```
693
-
694
- ### ElicitationCompleteMessage
695
-
696
- MCP elicitation completion:
697
-
698
- ```ruby
699
- elicitation.uuid # Message UUID
700
- elicitation.session_id # Session identifier
701
- elicitation.mcp_server_name # MCP server that requested elicitation
702
- elicitation.elicitation_id # Elicitation identifier
703
- ```
704
-
705
- ### LocalCommandOutputMessage
706
-
707
- Local command output:
708
-
709
- ```ruby
710
- output.uuid # Message UUID
711
- output.session_id # Session identifier
712
- output.content # Command output content
713
- ```
714
-
715
- ### GenericMessage
716
-
717
- Wraps unknown/future message types instead of raising errors:
718
-
719
- ```ruby
720
- msg.type # Message type as symbol (e.g. :fancy_new)
721
- msg[:data] # Dynamic field access via []
722
- msg.data # Dynamic field access via method_missing
723
- msg.to_h # Raw data hash
724
- ```
725
-
726
- ## Content Blocks
727
-
728
- Assistant messages contain content blocks:
729
-
730
- ```ruby
731
- message.content.each do |block|
732
- case block
733
- when ClaudeAgent::TextBlock
734
- puts block.text
735
- when ClaudeAgent::ThinkingBlock
736
- puts "Thinking: #{block.thinking}"
737
- when ClaudeAgent::ToolUseBlock
738
- puts "Tool: #{block.display_label}"
739
- puts " File: #{block.file_path}" if block.file_path
740
- puts " Summary: #{block.summary}"
741
- when ClaudeAgent::ToolResultBlock
742
- puts "Result for #{block.tool_use_id}: #{block.content}"
743
- when ClaudeAgent::ServerToolUseBlock
744
- puts "MCP Tool: #{block.display_label}" # "server_name/tool_name"
745
- when ClaudeAgent::ServerToolResultBlock
746
- puts "MCP Result from #{block.server_name}"
747
- when ClaudeAgent::ImageContentBlock
748
- puts "Image: #{block.media_type}, source: #{block.source_type}"
749
- when ClaudeAgent::GenericBlock
750
- puts "Unknown block: #{block.type}, data: #{block.to_h}"
751
- end
33
+ c.say("Now add tests for the fix")
34
+ puts "Total cost: $#{c.total_cost}"
752
35
  end
753
36
  ```
754
37
 
755
- ### ToolUseBlock Introspection
756
-
757
- Tool use blocks provide human-readable labels and summaries:
758
-
759
- ```ruby
760
- block.name # "Read", "Write", "Bash", etc.
761
- block.input # Tool input parameters (symbol-keyed Hash)
762
- block.file_path # File path for Read/Write/Edit/NotebookEdit (nil otherwise)
763
- block.display_label # One-line label: "Read lib/foo.rb", "Bash: git status", "Grep: pattern"
764
- block.summary # Detailed: "Write: /path.rb (3 lines)", "Edit: /path.rb replacing 5 line(s)"
765
- block.summary(max: 100) # Custom max length
766
- ```
767
-
768
- `ServerToolUseBlock` provides the same interface with server context in labels (e.g. `"calculator/add"`).
769
-
770
- ### GenericBlock
771
-
772
- Unknown content block types are wrapped instead of returning raw Hashes:
38
+ ### Streaming
773
39
 
774
40
  ```ruby
775
- block.type # Block type as symbol
776
- block[:field] # Dynamic field access via []
777
- block.field # Dynamic field access via method_missing
778
- block.to_h # Raw data hash
41
+ ClaudeAgent.ask("Explain Ruby blocks") { |msg| print msg.text_content }
779
42
  ```
780
43
 
781
- ## MCP Tools
782
-
783
- Create in-process MCP tools that Claude can use:
44
+ ### Global Configuration
784
45
 
785
46
  ```ruby
786
- # Define a tool
787
- calculator = ClaudeAgent::MCP::Tool.new(
788
- name: "add",
789
- description: "Add two numbers together",
790
- schema: { a: Float, b: Float }
791
- ) do |args|
792
- args[:a] + args[:b]
47
+ ClaudeAgent.configure do |c|
48
+ c.model = "claude-sonnet-4-5-20250514"
49
+ c.permission_mode = "acceptEdits"
50
+ c.max_turns = 10
793
51
  end
794
52
 
795
- # Create a server with tools
796
- server = ClaudeAgent::MCP::Server.new(
797
- name: "calculator",
798
- tools: [calculator]
799
- )
800
-
801
- # Use with options (SDK MCP servers)
802
- options = ClaudeAgent::Options.new(
803
- mcp_servers: {
804
- "calculator" => { type: "sdk", instance: server }
805
- }
806
- )
807
-
808
- ClaudeAgent.query(
809
- prompt: "What is 25 + 17?",
810
- options: options
811
- )
812
- ```
813
-
814
- > **Note:** MCP tool handlers receive **symbol-keyed** argument hashes (e.g. `args[:a]` not `args["a"]`).
815
-
816
- ### External MCP Servers
817
-
818
- Configure external MCP servers:
819
-
820
- ```ruby
821
- options = ClaudeAgent::Options.new(
822
- mcp_servers: {
823
- "filesystem" => {
824
- type: "stdio",
825
- command: "npx",
826
- args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
827
- }
828
- }
829
- )
830
- ```
831
-
832
- ### Tool Schema
833
-
834
- Define schemas using Ruby types or JSON Schema:
835
-
836
- ```ruby
837
- # Ruby types (converted automatically)
838
- schema: {
839
- name: String,
840
- age: Integer,
841
- score: Float,
842
- active: TrueClass, # boolean
843
- tags: Array,
844
- metadata: Hash
845
- }
846
-
847
- # Or use JSON Schema directly
848
- schema: {
849
- type: "object",
850
- properties: {
851
- name: { type: "string", description: "User's name" },
852
- age: { type: "integer", minimum: 0 }
853
- },
854
- required: ["name"]
855
- }
856
- ```
857
-
858
- ### Tool Annotations
859
-
860
- Annotate tools with hints about their behavior:
861
-
862
- ```ruby
863
- tool = ClaudeAgent::MCP::Tool.new(
864
- name: "search",
865
- description: "Search the web",
866
- schema: { query: String },
867
- annotations: {
868
- readOnlyHint: true, # Tool only reads data, no side effects
869
- destructiveHint: false, # Tool does not destroy/delete data
870
- idempotentHint: true, # Repeated calls with same args have same effect
871
- openWorldHint: true, # Tool interacts with external systems
872
- title: "Web Search" # Human-readable display name
873
- }
874
- ) { |args| "Results for #{args[:query]}" }
875
-
876
- # Or with the convenience method
877
- tool = ClaudeAgent::MCP.tool(
878
- "search", "Search the web", { query: String },
879
- annotations: { readOnlyHint: true, openWorldHint: true }
880
- ) { |args| "Results" }
53
+ # Or set individual fields
54
+ ClaudeAgent.model = "claude-sonnet-4-5-20250514"
881
55
  ```
882
56
 
883
- All annotation fields are optional hints — omit any that don't apply.
884
-
885
- ### Tool Return Values
886
-
887
- Tools can return various formats:
57
+ ### Permissions
888
58
 
889
59
  ```ruby
890
- # Simple string
891
- ClaudeAgent::MCP::Tool.new(name: "greet", description: "Greet") do |args|
892
- "Hello, #{args[:name]}!"
893
- end
894
-
895
- # Number (converted to string)
896
- ClaudeAgent::MCP::Tool.new(name: "add", description: "Add") do |args|
897
- args[:a] + args[:b]
898
- end
899
-
900
- # Custom MCP content
901
- ClaudeAgent::MCP::Tool.new(name: "fancy", description: "Fancy") do |args|
902
- { content: [{ type: "text", text: "Custom response" }] }
60
+ ClaudeAgent.permissions do |p|
61
+ p.allow "Read", "Grep", "Glob"
62
+ p.deny "Bash", message: "Shell access disabled"
63
+ p.deny_all
903
64
  end
904
65
  ```
905
66
 
906
- ## Hooks
907
-
908
- Intercept tool usage and other events with hooks:
67
+ ### Hooks
909
68
 
910
69
  ```ruby
911
- options = ClaudeAgent::Options.new(
912
- hooks: {
913
- "PreToolUse" => [
914
- ClaudeAgent::HookMatcher.new(
915
- matcher: "Bash|Write", # Match specific tools
916
- callbacks: [
917
- ->(input, context) {
918
- puts "Tool: #{input.tool_name}"
919
- puts "Input: #{input.tool_input}"
920
- puts "Tool Use ID: #{input.tool_use_id}"
921
- { continue_: true } # Allow the tool to proceed
922
- }
923
- ]
924
- )
925
- ],
926
- "PostToolUse" => [
927
- ClaudeAgent::HookMatcher.new(
928
- matcher: /^mcp__/, # Regex matching
929
- callbacks: [
930
- ->(input, context) {
931
- puts "Result: #{input.tool_response}"
932
- { continue_: true }
933
- }
934
- ]
935
- )
936
- ]
937
- }
938
- )
939
- ```
940
-
941
- > **Note:** Hook callbacks receive **symbol-keyed** input hashes (e.g. `input.tool_input[:file_path]`).
942
-
943
- ### Hook Events
944
-
945
- All available hook events:
946
-
947
- - `PreToolUse` - Before tool execution
948
- - `PostToolUse` - After successful tool execution
949
- - `PostToolUseFailure` - After tool execution failure
950
- - `Notification` - System notifications
951
- - `UserPromptSubmit` - When user submits a prompt
952
- - `SessionStart` - When session starts
953
- - `SessionEnd` - When session ends
954
- - `Stop` - When agent stops
955
- - `SubagentStart` - When subagent starts
956
- - `SubagentStop` - When subagent stops
957
- - `PreCompact` - Before conversation compaction
958
- - `PermissionRequest` - When permission is requested
959
- - `Setup` - Initial setup or maintenance (trigger: "init" or "maintenance")
960
- - `TeammateIdle` - When a teammate agent becomes idle
961
- - `TaskCompleted` - When a background task completes
962
- - `ConfigChange` - When configuration changes
963
- - `WorktreeCreate` - When a git worktree is created
964
- - `WorktreeRemove` - When a git worktree is removed
965
-
966
- ### Hook Input Types
967
-
968
- | Event | Input Type | Key Fields |
969
- |--------------------|---------------------------|---------------------------------------------------------|
970
- | PreToolUse | `PreToolUseInput` | tool_name, tool_input, tool_use_id |
971
- | PostToolUse | `PostToolUseInput` | tool_name, tool_input, tool_response, tool_use_id |
972
- | PostToolUseFailure | `PostToolUseFailureInput` | tool_name, tool_input, error, tool_use_id, is_interrupt |
973
- | Notification | `NotificationInput` | message, title, notification_type |
974
- | UserPromptSubmit | `UserPromptSubmitInput` | prompt |
975
- | SessionStart | `SessionStartInput` | source, agent_type, model |
976
- | SessionEnd | `SessionEndInput` | reason |
977
- | Stop | `StopInput` | stop_hook_active |
978
- | SubagentStart | `SubagentStartInput` | agent_id, agent_type |
979
- | SubagentStop | `SubagentStopInput` | stop_hook_active, agent_id, agent_transcript_path |
980
- | PreCompact | `PreCompactInput` | trigger, custom_instructions |
981
- | PermissionRequest | `PermissionRequestInput` | tool_name, tool_input, permission_suggestions |
982
- | Setup | `SetupInput` | trigger (init/maintenance) |
983
- | TeammateIdle | `TeammateIdleInput` | teammate_name, team_name |
984
- | TaskCompleted | `TaskCompletedInput` | task_id, task_subject, task_description, teammate_name |
985
- | ConfigChange | `ConfigChangeInput` | source, file_path |
986
- | WorktreeCreate | `WorktreeCreateInput` | name |
987
- | WorktreeRemove | `WorktreeRemoveInput` | worktree_path |
988
-
989
- All hook inputs inherit from `BaseHookInput` with: `hook_event_name`, `session_id`, `transcript_path`, `cwd`, `permission_mode`.
990
-
991
- ## Permissions
992
-
993
- Control tool permissions programmatically:
994
-
995
- ```ruby
996
- options = ClaudeAgent::Options.new(
997
- can_use_tool: ->(tool_name, tool_input, context) {
998
- # Context includes: permission_suggestions, blocked_path, decision_reason,
999
- # tool_use_id, agent_id, description
1000
-
1001
- # Allow all read operations
1002
- if tool_name == "Read"
1003
- ClaudeAgent::PermissionResultAllow.new
1004
- # Deny writes to sensitive paths
1005
- elsif tool_name == "Write" && tool_input[:file_path]&.include?(".env")
1006
- ClaudeAgent::PermissionResultDeny.new(
1007
- message: "Cannot modify .env files",
1008
- interrupt: true
1009
- )
1010
- else
1011
- ClaudeAgent::PermissionResultAllow.new
1012
- end
1013
- }
1014
- )
1015
- ```
1016
-
1017
- > **Note:** `can_use_tool` callbacks receive **symbol-keyed** `tool_input` hashes.
1018
-
1019
- ### Permission Results
1020
-
1021
- ```ruby
1022
- # Allow with optional modifications
1023
- ClaudeAgent::PermissionResultAllow.new(
1024
- updated_input: { file_path: "/safe/path" }, # Modify tool input
1025
- updated_permissions: [...] # Update permission rules
1026
- )
1027
-
1028
- # Deny
1029
- ClaudeAgent::PermissionResultDeny.new(
1030
- message: "Operation not allowed",
1031
- interrupt: true # Stop the agent
1032
- )
1033
- ```
1034
-
1035
- ### Permission Queue
1036
-
1037
- For UI-driven permission handling (e.g. TUI's, desktop apps, web UIs), use queue-based permissions instead of synchronous callbacks:
1038
-
1039
- ```ruby
1040
- # Enable via Options
1041
- options = ClaudeAgent::Options.new(permission_queue: true)
1042
-
1043
- # Or via Conversation (queue mode is the default)
1044
- conversation = ClaudeAgent.conversation(permission_mode: "default")
1045
- ```
1046
-
1047
- Resolve permissions from any thread:
1048
-
1049
- ```ruby
1050
- # Non-blocking poll
1051
- if request = client.pending_permission
1052
- puts "Tool: #{request.tool_name}"
1053
- puts "Input: #{request.input}"
1054
- request.allow! # or request.deny!(message: "Not allowed")
70
+ ClaudeAgent.hooks do |h|
71
+ h.before_tool_use(/Bash/) { |input, ctx| { continue_: true } }
72
+ h.after_tool_use { |input, ctx| { continue_: true } }
1055
73
  end
1056
-
1057
- # Check if any are waiting
1058
- client.pending_permissions?
1059
-
1060
- # Blocking wait with timeout
1061
- request = client.permission_queue.pop(timeout: 30)
1062
- request&.allow!
1063
-
1064
- # Drain all pending (e.g. during shutdown)
1065
- client.permission_queue.drain!(reason: "Session ended")
1066
74
  ```
1067
75
 
1068
- ### Hybrid Mode
1069
-
1070
- Combine synchronous callbacks with deferred queue resolution:
76
+ ### MCP Tools
1071
77
 
1072
78
  ```ruby
1073
- options = ClaudeAgent::Options.new(
1074
- can_use_tool: ->(tool_name, tool_input, context) {
1075
- if tool_name == "Read"
1076
- ClaudeAgent::PermissionResultAllow.new # Auto-allow reads
1077
- else
1078
- context.request.defer! # Defer everything else to the queue
1079
- end
1080
- }
1081
- )
1082
-
1083
- client = ClaudeAgent::Client.new(options: options)
1084
- client.connect
1085
-
1086
- # In another thread: resolve deferred permissions
1087
- Thread.new do
1088
- loop do
1089
- request = client.permission_queue.pop
1090
- break unless request
1091
- request.allow! # Or show UI dialog
79
+ server = ClaudeAgent::MCP::Server.new(name: "calc") do |s|
80
+ s.tool("add", "Add two numbers", { a: :number, b: :number }) do |args|
81
+ args[:a] + args[:b]
1092
82
  end
1093
83
  end
1094
- ```
1095
84
 
1096
- ### Permission Updates
1097
-
1098
- ```ruby
1099
- update = ClaudeAgent::PermissionUpdate.new(
1100
- type: "addRules", # addRules, replaceRules, removeRules, setMode, addDirectories, removeDirectories
1101
- rules: [
1102
- ClaudeAgent::PermissionRuleValue.new(tool_name: "Read", rule_content: "/**")
1103
- ],
1104
- behavior: "allow",
1105
- destination: "session" # userSettings, projectSettings, localSettings, session, cliArg
1106
- )
1107
- ```
1108
-
1109
- ## MCP Elicitation
1110
-
1111
- Handle MCP server elicitation requests (e.g. OAuth flows, form input):
1112
-
1113
- ```ruby
1114
- options = ClaudeAgent::Options.new(
1115
- on_elicitation: ->(request, signal:) {
1116
- # request contains: server_name, message, mode, url, elicitation_id, requested_schema
1117
- case request[:mode]
1118
- when "oauth"
1119
- # Handle OAuth flow
1120
- { action: "accept", content: { token: "..." } }
1121
- else
1122
- { action: "decline" }
1123
- end
1124
- }
1125
- )
1126
- ```
1127
-
1128
- Without `on_elicitation`, all elicitation requests are declined by default.
1129
-
1130
- ## Error Handling
1131
-
1132
- The SDK provides specific error types:
1133
-
1134
- ```ruby
1135
- begin
1136
- ClaudeAgent.query(prompt: "Hello")
1137
- rescue ClaudeAgent::CLINotFoundError
1138
- puts "Claude Code CLI not installed"
1139
- rescue ClaudeAgent::CLIVersionError => e
1140
- puts "CLI version too old: #{e.message}"
1141
- puts "Required: #{e.required_version}, Actual: #{e.actual_version}"
1142
- rescue ClaudeAgent::CLIConnectionError => e
1143
- puts "Connection failed: #{e.message}"
1144
- rescue ClaudeAgent::ProcessError => e
1145
- puts "Process error: #{e.message}, exit code: #{e.exit_code}"
1146
- rescue ClaudeAgent::TimeoutError => e
1147
- puts "Timeout: #{e.message}"
1148
- rescue ClaudeAgent::JSONDecodeError => e
1149
- puts "Invalid JSON response"
1150
- rescue ClaudeAgent::MessageParseError => e
1151
- puts "Could not parse message"
1152
- rescue ClaudeAgent::AbortError => e
1153
- puts "Operation aborted"
1154
- end
85
+ ClaudeAgent.register_mcp_server(server)
1155
86
  ```
1156
87
 
1157
- ## Cumulative Usage
88
+ ## Documentation
1158
89
 
1159
- The `Client` automatically tracks cumulative usage across turns:
1160
-
1161
- ```ruby
1162
- ClaudeAgent::Client.open do |client|
1163
- client.send_and_receive("Hello")
1164
- client.send_and_receive("Follow up")
1165
-
1166
- usage = client.cumulative_usage
1167
- puts "Tokens: #{usage.input_tokens} in / #{usage.output_tokens} out"
1168
- puts "Cache: #{usage.cache_read_input_tokens} read / #{usage.cache_creation_input_tokens} created"
1169
- puts "Cost: $#{usage.total_cost_usd}"
1170
- puts "Turns: #{usage.num_turns}"
1171
- puts "Duration: #{usage.duration_ms}ms"
1172
- end
1173
- ```
1174
-
1175
- Also available via `Conversation#usage`:
1176
-
1177
- ```ruby
1178
- ClaudeAgent::Conversation.open do |c|
1179
- c.say("Hello")
1180
- puts c.usage.to_h # => { input_tokens: 100, output_tokens: 50, ... }
1181
- end
1182
- ```
1183
-
1184
- ## Client API
1185
-
1186
- For fine-grained control:
1187
-
1188
- ```ruby
1189
- client = ClaudeAgent::Client.new(options: options)
1190
-
1191
- # Connect to CLI
1192
- client.connect
1193
-
1194
- # Send queries and receive TurnResults
1195
- turn = client.send_and_receive("First question")
1196
- puts turn.text
1197
-
1198
- turn = client.send_and_receive("Follow-up question")
1199
- puts turn.text
1200
-
1201
- # Or use lower-level send/receive
1202
- client.send_message("Question")
1203
- client.receive_response.each { |msg| process(msg) }
1204
-
1205
- # Or receive as TurnResult without sending
1206
- client.send_message("Question")
1207
- turn = client.receive_turn
1208
-
1209
- # Event handlers (persist across turns)
1210
- client.on_text { |text| print text }
1211
- client.on_tool_use { |tool| puts tool.display_label }
1212
- client.on_tool_result { |result, tool_use| puts "Done: #{tool_use&.name}" }
1213
- client.on_thinking { |thought| puts thought }
1214
- client.on_result { |result| puts "Cost: $#{result.total_cost_usd}" }
1215
- client.on_message { |msg| log(msg) }
1216
- # Type-based events for all message types
1217
- client.on_assistant { |msg| handle_assistant(msg) }
1218
- client.on_stream_event { |evt| handle_stream(evt) }
1219
- client.on_status { |status| show_status(status) }
1220
- client.on_tool_progress { |prog| update_spinner(prog) }
1221
-
1222
- # Control methods
1223
- client.interrupt # Cancel current operation
1224
- client.set_model("claude-opus-4-5-20251101") # Change model
1225
- client.set_permission_mode("acceptEdits") # Change permissions
1226
- client.set_max_thinking_tokens(5000) # Change thinking limit
1227
- client.stop_task("task-123") # Stop a running background task
1228
- client.apply_flag_settings({ "model" => "..." }) # Merge settings into flag layer
1229
-
1230
- # File checkpointing (requires enable_file_checkpointing: true)
1231
- result = client.rewind_files("user-message-uuid", dry_run: true)
1232
- puts "Can rewind: #{result.can_rewind}"
1233
- puts "Files changed: #{result.files_changed}"
1234
-
1235
- # Dynamic MCP server management
1236
- result = client.set_mcp_servers({
1237
- "my-server" => { type: "stdio", command: "node", args: ["server.js"] }
1238
- })
1239
- puts "Added: #{result.added}, Removed: #{result.removed}"
1240
-
1241
- # MCP server lifecycle
1242
- client.mcp_reconnect("my-server") # Reconnect a disconnected server
1243
- client.mcp_toggle("my-server", enabled: false) # Disable a server
1244
- client.mcp_toggle("my-server", enabled: true) # Re-enable a server
1245
- client.mcp_authenticate("my-remote-server") # OAuth authentication
1246
- client.mcp_clear_auth("my-remote-server") # Clear stored credentials
1247
-
1248
- # Permission queue access
1249
- client.pending_permission # Non-blocking poll for next request
1250
- client.pending_permissions? # Check if any requests waiting
1251
- client.permission_queue # Direct access to PermissionQueue
1252
-
1253
- # Cumulative usage tracking
1254
- client.cumulative_usage # CumulativeUsage with totals across all turns
1255
-
1256
- # Query capabilities
1257
- client.supported_commands.each { |cmd| puts "#{cmd.name}: #{cmd.description}" }
1258
- client.supported_models.each { |model| puts "#{model.value}: #{model.display_name}" }
1259
- client.supported_agents.each { |agent| puts "#{agent.name}: #{agent.description}" }
1260
- client.mcp_server_status.each { |s| puts "#{s.name}: #{s.status}" }
1261
- puts client.account_info.email
1262
-
1263
- # Disconnect
1264
- client.disconnect
1265
- ```
1266
-
1267
- ## Session Discovery
1268
-
1269
- Find and inspect past Claude Code sessions from disk without spawning a CLI subprocess.
1270
-
1271
- ### Session.find / Session.all
1272
-
1273
- ```ruby
1274
- # Find a specific session by ID
1275
- session = ClaudeAgent::Session.find("abc-123-def")
1276
- session = ClaudeAgent::Session.find("abc-123-def", dir: "/my/project")
1277
-
1278
- # List all sessions (most recent first)
1279
- sessions = ClaudeAgent::Session.all
1280
-
1281
- # Filter by directory, limit results
1282
- sessions = ClaudeAgent::Session.where(dir: "/path/to/project", limit: 10)
1283
-
1284
- sessions.each do |s|
1285
- puts "#{s.summary} (#{s.git_branch || 'no branch'})"
1286
- puts " Session: #{s.session_id}"
1287
- puts " Modified: #{Time.at(s.last_modified / 1000)}"
1288
- puts " Prompt: #{s.first_prompt}" if s.first_prompt
1289
- end
1290
- ```
1291
-
1292
- ### Reading Messages
1293
-
1294
- `Session#messages` returns a chainable, `Enumerable` relation:
1295
-
1296
- ```ruby
1297
- session = ClaudeAgent::Session.find("abc-123-def")
1298
-
1299
- # All messages
1300
- session.messages.each { |m| puts "#{m.type}: #{m.uuid}" }
1301
-
1302
- # Paginated
1303
- session.messages.where(limit: 10).map(&:uuid)
1304
- session.messages.where(offset: 5, limit: 10).to_a
1305
-
1306
- # Enumerable methods work
1307
- session.messages.first
1308
- session.messages.count
1309
- session.messages.select { |m| m.type == "assistant" }
1310
- ```
1311
-
1312
- ### Session Fields
1313
-
1314
- | Field | Type | Description |
1315
- |------------------|-----------------|--------------------------------------------------|
1316
- | `session_id` | `String` | UUID of the session |
1317
- | `summary` | `String` | Custom title, last summary, or first prompt |
1318
- | `last_modified` | `Integer` | Epoch milliseconds of last modification |
1319
- | `file_size` | `Integer` | Session file size in bytes |
1320
- | `custom_title` | `String\|nil` | User-set title, if any |
1321
- | `first_prompt` | `String\|nil` | First meaningful user prompt |
1322
- | `git_branch` | `String\|nil` | Git branch the session was on |
1323
- | `cwd` | `String\|nil` | Working directory of the session |
1324
-
1325
- ### Functional API
1326
-
1327
- The lower-level functional API is also available:
1328
-
1329
- ```ruby
1330
- # List sessions (returns SessionInfo objects)
1331
- infos = ClaudeAgent.list_sessions(dir: "/path/to/project", limit: 10)
1332
-
1333
- # Read messages directly
1334
- messages = ClaudeAgent.get_session_messages("abc-123-def", limit: 10, offset: 5)
1335
- ```
1336
-
1337
- ### Resume a Past Session
1338
-
1339
- Use with `Conversation.resume` to pick up where you left off:
1340
-
1341
- ```ruby
1342
- session = ClaudeAgent::Session.where(dir: Dir.pwd, limit: 5).first
1343
-
1344
- conversation = ClaudeAgent.resume_conversation(session.session_id)
1345
- turn = conversation.say("Continue where we left off")
1346
- conversation.close
1347
- ```
1348
-
1349
- ## V2 Session API (Unstable)
1350
-
1351
- > **Warning**: This API is unstable and may change without notice.
1352
-
1353
- The V2 Session API provides a simpler interface for multi-turn conversations, matching the TypeScript SDK's `SDKSession` interface.
1354
-
1355
- ### Create a Session
1356
-
1357
- ```ruby
1358
- # Create a new session
1359
- session = ClaudeAgent.unstable_v2_create_session(
1360
- model: "claude-sonnet-4-5-20250514",
1361
- permission_mode: "acceptEdits"
1362
- )
1363
-
1364
- # Send a message
1365
- session.send("Hello, Claude!")
1366
-
1367
- # Stream responses
1368
- session.stream.each do |msg|
1369
- case msg
1370
- when ClaudeAgent::AssistantMessage
1371
- puts msg.text
1372
- when ClaudeAgent::ResultMessage
1373
- puts "Done! Cost: $#{msg.total_cost_usd}"
1374
- end
1375
- end
1376
-
1377
- # Continue the conversation
1378
- session.send("Tell me more")
1379
- session.stream.each { |msg| puts msg.text if msg.is_a?(ClaudeAgent::AssistantMessage) }
1380
-
1381
- # Close when done
1382
- session.close
1383
- ```
1384
-
1385
- ### Resume a Session
1386
-
1387
- ```ruby
1388
- # Resume an existing session by ID
1389
- session = ClaudeAgent.unstable_v2_resume_session(
1390
- "session-abc123",
1391
- model: "claude-sonnet-4-5-20250514"
1392
- )
1393
-
1394
- session.send("What were we discussing?")
1395
- session.stream.each { |msg| puts msg.text if msg.is_a?(ClaudeAgent::AssistantMessage) }
1396
- session.close
1397
- ```
1398
-
1399
- ### One-Shot Prompt
1400
-
1401
- ```ruby
1402
- # Simple one-shot prompt (auto-closes session)
1403
- result = ClaudeAgent.unstable_v2_prompt(
1404
- "What is 2 + 2?",
1405
- model: "claude-sonnet-4-5-20250514"
1406
- )
1407
-
1408
- puts "Success: #{result.success?}"
1409
- puts "Cost: $#{result.total_cost_usd}"
1410
- ```
1411
-
1412
- ### SessionOptions
1413
-
1414
- The V2 API uses a simplified options type:
1415
-
1416
- ```ruby
1417
- options = ClaudeAgent::SessionOptions.new(
1418
- model: "claude-sonnet-4-5-20250514", # Required
1419
- permission_mode: "acceptEdits", # Optional
1420
- allowed_tools: ["Read", "Grep"], # Optional
1421
- disallowed_tools: ["Write"], # Optional
1422
- can_use_tool: ->(name, input, ctx) { ... }, # Optional
1423
- hooks: { "PreToolUse" => [...] }, # Optional
1424
- env: { "MY_VAR" => "value" }, # Optional
1425
- path_to_claude_code_executable: "/custom/path" # Optional
1426
- )
1427
-
1428
- session = ClaudeAgent.unstable_v2_create_session(options)
1429
- ```
1430
-
1431
- ## Types Reference
1432
-
1433
- ### Return Types
1434
-
1435
- | Type | Purpose |
1436
- |--------------------------|----------------------------------------------------------------------------------|
1437
- | `TurnResult` | Complete agent turn with text, tools, usage, and status accessors |
1438
- | `ToolActivity` | Tool use/result pair with turn index and timing (immutable, post-turn) |
1439
- | `LiveToolActivity` | Mutable real-time tool status (running/done/error) with elapsed time |
1440
- | `ToolActivityTracker` | Enumerable collection of `LiveToolActivity` with auto-wiring and `on_change` |
1441
- | `CumulativeUsage` | Running totals of tokens, cost, turns, and duration |
1442
- | `PermissionRequest` | Deferred permission promise resolvable from any thread |
1443
- | `PermissionQueue` | Thread-safe queue of pending permission requests |
1444
- | `EventHandler` | Typed event callback registry |
1445
- | `SlashCommand` | Available slash commands (name, description, argument_hint) |
1446
- | `ModelInfo` | Available models (value, display_name, description) |
1447
- | `AgentInfo` | Available agents (name, description, model) |
1448
- | `McpServerStatus` | MCP server status (name, status, server_info) |
1449
- | `AccountInfo` | Account information (email, organization, subscription_type) |
1450
- | `ModelUsage` | Per-model usage stats (input_tokens, output_tokens, cost_usd) |
1451
- | `McpSetServersResult` | Result of set_mcp_servers (added, removed, errors) |
1452
- | `RewindFilesResult` | Result of rewind_files (can_rewind, error, files_changed, insertions, deletions) |
1453
- | `Session` | Session finder with `.find`, `.all`, `#messages` (wraps SessionInfo) |
1454
- | `SessionMessageRelation` | Chainable, Enumerable query object for session messages |
1455
- | `SessionInfo` | Session metadata from `list_sessions` (session_id, summary, git_branch, cwd) |
1456
- | `SessionMessage` | Message from a session transcript (type, uuid, session_id, message) |
1457
- | `SDKPermissionDenial` | Permission denial info (tool_name, tool_use_id, tool_input) |
1458
-
1459
- ## Logging
1460
-
1461
- The SDK includes optional logging with zero overhead when disabled. All log output is silent by default.
1462
-
1463
- ### Quick Debug
1464
-
1465
- ```ruby
1466
- # Enable debug logging to stderr
1467
- ClaudeAgent.debug!
1468
-
1469
- # Or to a file
1470
- ClaudeAgent.debug!(output: File.open("claude_agent.log", "a"))
1471
- ```
1472
-
1473
- ### Custom Logger
1474
-
1475
- Set any `Logger`-compatible instance at the module level:
1476
-
1477
- ```ruby
1478
- ClaudeAgent.logger = Logger.new($stderr, level: :info)
1479
- ```
1480
-
1481
- ### Per-Query Logger
1482
-
1483
- Override the module-level logger for a specific query or client:
1484
-
1485
- ```ruby
1486
- my_logger = Logger.new("query.log", level: :debug)
1487
-
1488
- ClaudeAgent.query(prompt: "Hello", options: ClaudeAgent::Options.new(logger: my_logger))
1489
-
1490
- # Or with Client
1491
- client = ClaudeAgent::Client.new(options: ClaudeAgent::Options.new(logger: my_logger))
1492
- ```
1493
-
1494
- ### Log Output
1495
-
1496
- When enabled, the SDK logs events across transport, protocol, parsing, MCP, and query layers:
1497
-
1498
- ```
1499
- [ClaudeAgent] [12:00:00.123] INFO -- transport: Process spawned (pid=12345)
1500
- [ClaudeAgent] [12:00:00.456] DEBUG -- protocol: Sending control request: initialize (req_1_abc)
1501
- [ClaudeAgent] [12:00:01.789] INFO -- protocol: Permission decision for Bash: allow
1502
- [ClaudeAgent] [12:00:02.100] INFO -- query: Query complete (3.45s, cost=$0.012)
1503
- ```
1504
-
1505
- ### Log Levels
1506
-
1507
- | Level | What's Logged |
1508
- |-------|----------------------------------------------------------------------------------------------------------------|
1509
- | ERROR | Control request failures, unknown message types |
1510
- | WARN | Force kills, JSON parse errors during buffering, unknown MCP tools |
1511
- | INFO | Process spawn/close, protocol start/stop, permission decisions, tool calls, query start/completion with timing |
1512
- | DEBUG | Full commands, message types received, control request/response routing, reader thread lifecycle |
1513
-
1514
- ## Environment Variables
1515
-
1516
- The SDK sets these automatically:
1517
-
1518
- - `CLAUDE_CODE_ENTRYPOINT=sdk-rb`
1519
- - `CLAUDE_AGENT_SDK_VERSION=<version>`
1520
-
1521
- Enable debug logging via environment variable:
1522
-
1523
- ```bash
1524
- export CLAUDE_AGENT_DEBUG=1
1525
- ```
1526
-
1527
- Skip version checking (for development):
1528
-
1529
- ```bash
1530
- export CLAUDE_AGENT_SDK_SKIP_VERSION_CHECK=true
1531
- ```
90
+ | Guide | Description |
91
+ |--------------------------------------------|----------------------------------------------------------|
92
+ | [Getting Started](docs/getting-started.md) | Installation, first queries, multi-turn basics |
93
+ | [Configuration](docs/configuration.md) | Global config, Options, sandbox, agents, env vars |
94
+ | [Conversations](docs/conversations.md) | Multi-turn API, TurnResult, callbacks, tool tracking |
95
+ | [Queries](docs/queries.md) | One-shot interfaces: ask, query_turn, query |
96
+ | [Permissions](docs/permissions.md) | PermissionPolicy DSL, can_use_tool, queue mode |
97
+ | [Hooks](docs/hooks.md) | HookRegistry DSL, hook events, input types |
98
+ | [MCP Tools](docs/mcp.md) | In-process tools, servers, schemas, elicitation |
99
+ | [Events](docs/events.md) | EventHandler, typed callbacks, event layers |
100
+ | [Messages](docs/messages.md) | All 22 message types, 8 content blocks, pattern matching |
101
+ | [Sessions](docs/sessions.md) | Session discovery, mutations, forking, resume |
102
+ | [Client](docs/client.md) | Low-level bidirectional API (advanced) |
103
+ | [Errors](docs/errors.md) | Error hierarchy and handling patterns |
104
+ | [Logging](docs/logging.md) | Debug logging, custom loggers, log levels |
105
+ | [Architecture](docs/architecture.md) | Internal design, data flow, module map |
1532
106
 
1533
107
  ## Development
1534
108
 
1535
109
  ```bash
1536
- # Install dependencies
1537
- bin/setup
1538
-
1539
- # Run unit tests
1540
- bundle exec rake test
1541
-
1542
- # Run integration tests (requires Claude Code CLI v2.0.0+)
1543
- bundle exec rake test_integration
1544
-
1545
- # Run all tests
1546
- bundle exec rake test_all
1547
-
1548
- # Validate RBS type signatures
1549
- bundle exec rake rbs
1550
-
1551
- # Linting
1552
- bundle exec rubocop
1553
-
1554
- # Interactive console
1555
- bin/console
1556
-
1557
- # Binstubs for convenience
1558
- bin/test # Unit tests only
1559
- bin/test-integration # Integration tests
1560
- bin/test-all # All tests
1561
- bin/rbs-validate # Validate RBS signatures
1562
- bin/release 1.2.0 # Release a new version
1563
- ```
1564
-
1565
- ## Architecture
1566
-
1567
- ```
1568
- ClaudeAgent.conversation() / ClaudeAgent::Conversation
1569
-
1570
- │ Manages lifecycle, callbacks, turn history, tool activity
1571
-
1572
-
1573
- ClaudeAgent::Client
1574
-
1575
- │ Event handlers, cumulative usage, permission queue
1576
-
1577
-
1578
- ┌──────────────────────────┐
1579
- │ Control Protocol │ Request/response routing
1580
- │ - Hooks │ Permission callbacks
1581
- │ - MCP bridging │ Tool interception
1582
- └──────────┬───────────────┘
1583
-
1584
-
1585
- ┌──────────────────────────┐
1586
- │ Subprocess Transport │ JSON Lines protocol
1587
- │ - stdin/stdout │ Process management
1588
- │ - stderr handling │
1589
- └──────────┬───────────────┘
1590
-
1591
-
1592
- Claude Code CLI
110
+ bin/setup # Install dependencies
111
+ bundle exec rake # Tests + RBS + RuboCop
112
+ bundle exec rake test # Unit tests
113
+ bundle exec rake test_integration # Integration tests (requires CLI v2.0.0+)
114
+ bundle exec rubocop # Lint
115
+ bin/console # IRB with gem loaded
1593
116
  ```
1594
117
 
1595
118
  ## License