claude_code 0.0.14
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 +7 -0
- data/.rspec +3 -0
- data/.rspec_status +114 -0
- data/.rubocop.yml +51 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +37 -0
- data/LICENSE +21 -0
- data/README.md +288 -0
- data/Rakefile +32 -0
- data/USAGE_EXAMPLES.md +428 -0
- data/claude_code.gemspec +47 -0
- data/docs/README.md +254 -0
- data/docs/mcp_integration.md +404 -0
- data/docs/streaming.md +316 -0
- data/examples/authentication_examples.rb +133 -0
- data/examples/basic_usage.rb +73 -0
- data/examples/conversation_resuming.rb +96 -0
- data/examples/irb_helpers.rb +168 -0
- data/examples/jsonl_cli_equivalent.rb +108 -0
- data/examples/mcp_examples.rb +166 -0
- data/examples/model_examples.rb +111 -0
- data/examples/quick_start.rb +75 -0
- data/examples/rails_sidekiq_example.rb +336 -0
- data/examples/streaming_examples.rb +195 -0
- data/examples/streaming_json_input.rb +171 -0
- data/hello.txt +1 -0
- data/lib/claude_code/client.rb +437 -0
- data/lib/claude_code/errors.rb +48 -0
- data/lib/claude_code/types.rb +237 -0
- data/lib/claude_code/version.rb +5 -0
- data/lib/claude_code.rb +265 -0
- metadata +219 -0
data/USAGE_EXAMPLES.md
ADDED
@@ -0,0 +1,428 @@
|
|
1
|
+
# Ruby Claude Code SDK - Usage Examples
|
2
|
+
|
3
|
+
## Directory Structure
|
4
|
+
|
5
|
+
```
|
6
|
+
ruby/
|
7
|
+
├── lib/claude_code_sdk.rb # Main SDK entry point
|
8
|
+
├── docs/ # Comprehensive documentation
|
9
|
+
├── examples/ # Working code examples
|
10
|
+
├── spec/ # RSpec tests
|
11
|
+
└── README.md # Quick start guide
|
12
|
+
```
|
13
|
+
|
14
|
+
## Quick Start
|
15
|
+
|
16
|
+
### 1. From the ruby directory:
|
17
|
+
|
18
|
+
```bash
|
19
|
+
cd ruby
|
20
|
+
|
21
|
+
# Install dependencies
|
22
|
+
bundle install
|
23
|
+
|
24
|
+
# Try basic examples
|
25
|
+
ruby examples/basic_usage.rb
|
26
|
+
ruby examples/streaming_examples.rb
|
27
|
+
ruby examples/mcp_examples.rb
|
28
|
+
ruby examples/conversation_resuming.rb
|
29
|
+
ruby examples/authentication_examples.rb
|
30
|
+
ruby examples/streaming_json_input.rb
|
31
|
+
ruby examples/jsonl_cli_equivalent.rb
|
32
|
+
```
|
33
|
+
|
34
|
+
### 2. In IRB/console:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# Load from ruby directory
|
38
|
+
require_relative 'lib/claude_code_sdk'
|
39
|
+
|
40
|
+
# Or load with helpers
|
41
|
+
require_relative 'examples/irb_helpers'
|
42
|
+
|
43
|
+
# Then use:
|
44
|
+
quick_claude("What is Ruby?")
|
45
|
+
stream_claude("Explain blocks")
|
46
|
+
ninja_test("Tell me about yourself")
|
47
|
+
|
48
|
+
# Conversation helpers:
|
49
|
+
continue_chat("Follow up question")
|
50
|
+
resume_chat("session-id", "New prompt")
|
51
|
+
save_session("session-id")
|
52
|
+
resume_last("Continue with last session")
|
53
|
+
```
|
54
|
+
|
55
|
+
### 3. In a Ruby project:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
# Add to your project
|
59
|
+
require_relative 'path/to/ruby/lib/claude_code_sdk'
|
60
|
+
|
61
|
+
# Use normally
|
62
|
+
ClaudeCodeSDK.query(prompt: "Hello").each { |msg| puts msg }
|
63
|
+
```
|
64
|
+
|
65
|
+
## Rails + Sidekiq Streaming Example
|
66
|
+
|
67
|
+
Here's how to integrate the SDK with Rails and Sidekiq for real-time streaming:
|
68
|
+
|
69
|
+
### 1. Gemfile
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
# Gemfile
|
73
|
+
gem 'sidekiq'
|
74
|
+
gem 'redis'
|
75
|
+
|
76
|
+
# Add the Claude SDK (assuming it's in your project)
|
77
|
+
# gem 'claude_code_sdk', path: 'vendor/claude-code-sdk-ruby'
|
78
|
+
```
|
79
|
+
|
80
|
+
### 2. Sidekiq Job
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
# app/jobs/claude_streaming_job.rb
|
84
|
+
class ClaudeStreamingJob
|
85
|
+
include Sidekiq::Job
|
86
|
+
|
87
|
+
def perform(user_id, query_id, prompt, options = {})
|
88
|
+
require_relative '../vendor/claude-code-sdk-ruby/lib/claude_code_sdk'
|
89
|
+
|
90
|
+
channel = "claude_stream_#{user_id}_#{query_id}"
|
91
|
+
|
92
|
+
# Parse options
|
93
|
+
claude_options = ClaudeCodeSDK::ClaudeCodeOptions.new(
|
94
|
+
model: options['model'],
|
95
|
+
max_turns: options['max_turns'] || 1,
|
96
|
+
system_prompt: options['system_prompt'],
|
97
|
+
allowed_tools: options['allowed_tools'] || []
|
98
|
+
)
|
99
|
+
|
100
|
+
# Stream Claude responses
|
101
|
+
message_count = 0
|
102
|
+
ClaudeCodeSDK.query(
|
103
|
+
prompt: prompt,
|
104
|
+
options: claude_options,
|
105
|
+
cli_path: ENV['CLAUDE_CLI_PATH'] || '/usr/local/bin/claude'
|
106
|
+
).each do |message|
|
107
|
+
message_count += 1
|
108
|
+
|
109
|
+
# Broadcast each message via ActionCable
|
110
|
+
ActionCable.server.broadcast(channel, {
|
111
|
+
type: 'claude_message',
|
112
|
+
query_id: query_id,
|
113
|
+
message_index: message_count,
|
114
|
+
timestamp: Time.current.iso8601,
|
115
|
+
data: serialize_claude_message(message)
|
116
|
+
})
|
117
|
+
|
118
|
+
# Optional: Save to database
|
119
|
+
save_message_to_db(user_id, query_id, message_count, message)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Broadcast completion
|
123
|
+
ActionCable.server.broadcast(channel, {
|
124
|
+
type: 'complete',
|
125
|
+
query_id: query_id,
|
126
|
+
total_messages: message_count,
|
127
|
+
timestamp: Time.current.iso8601
|
128
|
+
})
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def serialize_claude_message(message)
|
134
|
+
case message
|
135
|
+
when ClaudeCodeSDK::SystemMessage
|
136
|
+
{
|
137
|
+
message_type: 'system',
|
138
|
+
subtype: message.subtype,
|
139
|
+
data: message.data
|
140
|
+
}
|
141
|
+
when ClaudeCodeSDK::AssistantMessage
|
142
|
+
{
|
143
|
+
message_type: 'assistant',
|
144
|
+
content: message.content.map { |block| serialize_content_block(block) }
|
145
|
+
}
|
146
|
+
when ClaudeCodeSDK::ResultMessage
|
147
|
+
{
|
148
|
+
message_type: 'result',
|
149
|
+
subtype: message.subtype,
|
150
|
+
duration_ms: message.duration_ms,
|
151
|
+
cost_usd: message.total_cost_usd,
|
152
|
+
session_id: message.session_id
|
153
|
+
}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def serialize_content_block(block)
|
158
|
+
case block
|
159
|
+
when ClaudeCodeSDK::TextBlock
|
160
|
+
{ type: 'text', text: block.text }
|
161
|
+
when ClaudeCodeSDK::ToolUseBlock
|
162
|
+
{ type: 'tool_use', id: block.id, name: block.name, input: block.input }
|
163
|
+
when ClaudeCodeSDK::ToolResultBlock
|
164
|
+
{ type: 'tool_result', tool_use_id: block.tool_use_id, content: block.content, is_error: block.is_error }
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def save_message_to_db(user_id, query_id, message_index, message)
|
169
|
+
# Example database save
|
170
|
+
# ClaudeMessage.create!(
|
171
|
+
# user_id: user_id,
|
172
|
+
# query_id: query_id,
|
173
|
+
# message_index: message_index,
|
174
|
+
# message_data: serialize_claude_message(message),
|
175
|
+
# created_at: Time.current
|
176
|
+
# )
|
177
|
+
end
|
178
|
+
end
|
179
|
+
```
|
180
|
+
|
181
|
+
### 3. ActionCable Channel
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
# app/channels/claude_stream_channel.rb
|
185
|
+
class ClaudeStreamChannel < ApplicationCable::Channel
|
186
|
+
def subscribed
|
187
|
+
# Authorize user access
|
188
|
+
if params[:user_id].to_i == current_user.id
|
189
|
+
stream_from "claude_stream_#{params[:user_id]}_#{params[:query_id]}"
|
190
|
+
else
|
191
|
+
reject
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def unsubscribed
|
196
|
+
# Cleanup when channel is unsubscribed
|
197
|
+
end
|
198
|
+
end
|
199
|
+
```
|
200
|
+
|
201
|
+
### 4. Controller
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
# app/controllers/claude_controller.rb
|
205
|
+
class ClaudeController < ApplicationController
|
206
|
+
before_action :authenticate_user!
|
207
|
+
|
208
|
+
def create_stream_query
|
209
|
+
query_id = SecureRandom.uuid
|
210
|
+
|
211
|
+
# Validate input
|
212
|
+
prompt = params.require(:prompt)
|
213
|
+
options = {
|
214
|
+
'model' => params[:model],
|
215
|
+
'max_turns' => params[:max_turns]&.to_i,
|
216
|
+
'system_prompt' => params[:system_prompt],
|
217
|
+
'allowed_tools' => params[:allowed_tools] || []
|
218
|
+
}
|
219
|
+
|
220
|
+
# Start background job
|
221
|
+
ClaudeStreamingJob.perform_async(
|
222
|
+
current_user.id,
|
223
|
+
query_id,
|
224
|
+
prompt,
|
225
|
+
options
|
226
|
+
)
|
227
|
+
|
228
|
+
render json: {
|
229
|
+
query_id: query_id,
|
230
|
+
channel: "claude_stream_#{current_user.id}_#{query_id}",
|
231
|
+
status: 'started'
|
232
|
+
}
|
233
|
+
end
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
237
|
+
### 5. Frontend JavaScript
|
238
|
+
|
239
|
+
```javascript
|
240
|
+
// app/javascript/claude_streaming.js
|
241
|
+
class ClaudeStreaming {
|
242
|
+
constructor(userId) {
|
243
|
+
this.userId = userId;
|
244
|
+
this.activeSubscriptions = new Map();
|
245
|
+
}
|
246
|
+
|
247
|
+
startQuery(prompt, options = {}) {
|
248
|
+
return fetch('/claude/stream_query', {
|
249
|
+
method: 'POST',
|
250
|
+
headers: {
|
251
|
+
'Content-Type': 'application/json',
|
252
|
+
'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
|
253
|
+
},
|
254
|
+
body: JSON.stringify({
|
255
|
+
prompt: prompt,
|
256
|
+
model: options.model,
|
257
|
+
max_turns: options.maxTurns,
|
258
|
+
system_prompt: options.systemPrompt,
|
259
|
+
allowed_tools: options.allowedTools
|
260
|
+
})
|
261
|
+
})
|
262
|
+
.then(response => response.json())
|
263
|
+
.then(data => {
|
264
|
+
this.subscribeToQuery(data.query_id, options.onMessage, options.onComplete);
|
265
|
+
return data;
|
266
|
+
});
|
267
|
+
}
|
268
|
+
|
269
|
+
subscribeToQuery(queryId, onMessage, onComplete) {
|
270
|
+
const subscription = App.cable.subscriptions.create(
|
271
|
+
{
|
272
|
+
channel: "ClaudeStreamChannel",
|
273
|
+
user_id: this.userId,
|
274
|
+
query_id: queryId
|
275
|
+
},
|
276
|
+
{
|
277
|
+
received: (data) => {
|
278
|
+
switch(data.type) {
|
279
|
+
case 'claude_message':
|
280
|
+
this.handleClaudeMessage(data, onMessage);
|
281
|
+
break;
|
282
|
+
case 'complete':
|
283
|
+
this.handleComplete(data, onComplete);
|
284
|
+
this.unsubscribeFromQuery(queryId);
|
285
|
+
break;
|
286
|
+
}
|
287
|
+
}
|
288
|
+
}
|
289
|
+
);
|
290
|
+
|
291
|
+
this.activeSubscriptions.set(queryId, subscription);
|
292
|
+
}
|
293
|
+
|
294
|
+
handleClaudeMessage(data, onMessage) {
|
295
|
+
const message = data.data;
|
296
|
+
|
297
|
+
if (message.message_type === 'assistant') {
|
298
|
+
message.content.forEach(block => {
|
299
|
+
if (block.type === 'text') {
|
300
|
+
onMessage({
|
301
|
+
type: 'text',
|
302
|
+
content: block.text,
|
303
|
+
timestamp: data.timestamp
|
304
|
+
});
|
305
|
+
} else if (block.type === 'tool_use') {
|
306
|
+
onMessage({
|
307
|
+
type: 'tool_use',
|
308
|
+
tool: block.name,
|
309
|
+
input: block.input,
|
310
|
+
timestamp: data.timestamp
|
311
|
+
});
|
312
|
+
}
|
313
|
+
});
|
314
|
+
} else if (message.message_type === 'result') {
|
315
|
+
onMessage({
|
316
|
+
type: 'result',
|
317
|
+
cost: message.cost_usd,
|
318
|
+
duration: message.duration_ms,
|
319
|
+
timestamp: data.timestamp
|
320
|
+
});
|
321
|
+
}
|
322
|
+
}
|
323
|
+
|
324
|
+
handleComplete(data, onComplete) {
|
325
|
+
if (onComplete) {
|
326
|
+
onComplete({
|
327
|
+
queryId: data.query_id,
|
328
|
+
totalMessages: data.total_messages,
|
329
|
+
timestamp: data.timestamp
|
330
|
+
});
|
331
|
+
}
|
332
|
+
}
|
333
|
+
|
334
|
+
unsubscribeFromQuery(queryId) {
|
335
|
+
const subscription = this.activeSubscriptions.get(queryId);
|
336
|
+
if (subscription) {
|
337
|
+
subscription.unsubscribe();
|
338
|
+
this.activeSubscriptions.delete(queryId);
|
339
|
+
}
|
340
|
+
}
|
341
|
+
}
|
342
|
+
|
343
|
+
// Usage
|
344
|
+
const claudeStreaming = new ClaudeStreaming(currentUserId);
|
345
|
+
|
346
|
+
claudeStreaming.startQuery("Explain Ruby on Rails", {
|
347
|
+
model: "sonnet",
|
348
|
+
maxTurns: 1,
|
349
|
+
onMessage: (message) => {
|
350
|
+
console.log('Received:', message);
|
351
|
+
if (message.type === 'text') {
|
352
|
+
appendToChat(message.content);
|
353
|
+
} else if (message.type === 'tool_use') {
|
354
|
+
showToolUsage(message.tool, message.input);
|
355
|
+
}
|
356
|
+
},
|
357
|
+
onComplete: (summary) => {
|
358
|
+
console.log('Query completed:', summary);
|
359
|
+
showCompletionMessage(summary);
|
360
|
+
}
|
361
|
+
});
|
362
|
+
```
|
363
|
+
|
364
|
+
### 6. Environment Configuration
|
365
|
+
|
366
|
+
```bash
|
367
|
+
# .env
|
368
|
+
CLAUDE_CLI_PATH=/usr/local/bin/claude
|
369
|
+
ANTHROPIC_API_KEY=your_api_key_here
|
370
|
+
REDIS_URL=redis://localhost:6379/0
|
371
|
+
```
|
372
|
+
|
373
|
+
### 7. Rails Routes
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
# config/routes.rb
|
377
|
+
Rails.application.routes.draw do
|
378
|
+
mount ActionCable.server => '/cable'
|
379
|
+
|
380
|
+
resources :claude, only: [] do
|
381
|
+
collection do
|
382
|
+
post 'stream_query'
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
```
|
387
|
+
|
388
|
+
## Usage in Rails Console
|
389
|
+
|
390
|
+
```ruby
|
391
|
+
# Start a streaming query
|
392
|
+
ClaudeStreamingJob.perform_async(
|
393
|
+
1, # user_id
|
394
|
+
SecureRandom.uuid, # query_id
|
395
|
+
"Explain Ruby metaprogramming", # prompt
|
396
|
+
{
|
397
|
+
'model' => 'sonnet',
|
398
|
+
'max_turns' => 1,
|
399
|
+
'system_prompt' => 'You are a Ruby expert'
|
400
|
+
}
|
401
|
+
)
|
402
|
+
|
403
|
+
# With MCP servers
|
404
|
+
ClaudeStreamingJob.perform_async(
|
405
|
+
1,
|
406
|
+
SecureRandom.uuid,
|
407
|
+
"Use the about tool",
|
408
|
+
{
|
409
|
+
'model' => 'sonnet',
|
410
|
+
'allowed_tools' => ['mcp__ninja__about'],
|
411
|
+
'mcp_servers' => {
|
412
|
+
'ninja' => 'https://mcp-creator-ninja-v1-4-0.mcp.soy/'
|
413
|
+
}
|
414
|
+
}
|
415
|
+
)
|
416
|
+
```
|
417
|
+
|
418
|
+
This setup provides:
|
419
|
+
- ✅ Real-time streaming via WebSockets
|
420
|
+
- ✅ Background processing with Sidekiq
|
421
|
+
- ✅ Error handling and retry logic
|
422
|
+
- ✅ Message persistence (optional)
|
423
|
+
- ✅ User authentication and authorization
|
424
|
+
- ✅ Frontend integration with ActionCable
|
425
|
+
- ✅ MCP server support
|
426
|
+
- ✅ Cost tracking and monitoring
|
427
|
+
|
428
|
+
The streaming happens in real-time, so users see Claude's responses as they're generated, providing an excellent user experience for AI-powered Rails applications.
|
data/claude_code.gemspec
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/claude_code/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'claude_code'
|
7
|
+
spec.version = ClaudeCode::VERSION
|
8
|
+
spec.authors = ['Anthropic']
|
9
|
+
spec.email = ['support@anthropic.com']
|
10
|
+
|
11
|
+
spec.summary = 'Ruby SDK for Claude Code with streaming support and ergonomic MCP integration'
|
12
|
+
spec.description = 'Official Ruby SDK for Claude Code. See the Claude Code SDK documentation for more information.'
|
13
|
+
spec.homepage = 'https://github.com/anthropics/claude-code-sdk-python'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.required_ruby_version = '>= 3.0.0'
|
16
|
+
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
19
|
+
spec.metadata['documentation_uri'] = 'https://docs.anthropic.com/en/docs/claude-code/sdk'
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/anthropics/claude-code-sdk-python/blob/main/ruby/CHANGELOG.md'
|
21
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
spec.files = Dir.chdir(__dir__) do
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
(File.expand_path(f) == __FILE__) ||
|
27
|
+
f.start_with?('bin/', 'test/', 'spec/', 'features/', '.git', '.github', 'appveyor', 'Gemfile')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
spec.bindir = 'exe'
|
31
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ['lib']
|
33
|
+
|
34
|
+
# Runtime dependencies
|
35
|
+
spec.add_dependency 'json', '~> 2.0'
|
36
|
+
|
37
|
+
# Development dependencies
|
38
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
39
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
40
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
41
|
+
spec.add_development_dependency 'rubocop', '~> 1.50'
|
42
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 2.20'
|
43
|
+
spec.add_development_dependency 'rubocop-performance', '~> 1.0'
|
44
|
+
spec.add_development_dependency 'rubocop-rake', '~> 0.6'
|
45
|
+
spec.add_development_dependency 'simplecov', '~> 0.22'
|
46
|
+
spec.add_development_dependency 'yard', '~> 0.9'
|
47
|
+
end
|