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.
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeCode
4
+ # Permission modes
5
+ PERMISSION_MODES = %w[default acceptEdits bypassPermissions].freeze
6
+
7
+ # Content block types
8
+ class TextBlock
9
+ attr_reader :text
10
+
11
+ def initialize(text)
12
+ @text = text
13
+ end
14
+ end
15
+
16
+ class ToolUseBlock
17
+ attr_reader :id, :name, :input
18
+
19
+ def initialize(id:, name:, input:)
20
+ @id = id
21
+ @name = name
22
+ @input = input
23
+ end
24
+ end
25
+
26
+ class ToolResultBlock
27
+ attr_reader :tool_use_id, :content, :is_error
28
+
29
+ def initialize(tool_use_id:, content: nil, is_error: nil)
30
+ @tool_use_id = tool_use_id
31
+ @content = content
32
+ @is_error = is_error
33
+ end
34
+ end
35
+
36
+ # Base message class
37
+ class BaseMessage
38
+ end
39
+
40
+ # Message types
41
+ class UserMessage < BaseMessage
42
+ attr_reader :content
43
+
44
+ def initialize(content)
45
+ @content = content
46
+ end
47
+ end
48
+
49
+ class AssistantMessage < BaseMessage
50
+ attr_reader :content
51
+
52
+ def initialize(content)
53
+ @content = content
54
+ end
55
+ end
56
+
57
+ class SystemMessage < BaseMessage
58
+ attr_reader :subtype, :data
59
+
60
+ def initialize(subtype:, data:)
61
+ @subtype = subtype
62
+ @data = data
63
+ end
64
+ end
65
+
66
+ class ResultMessage < BaseMessage
67
+ attr_reader :subtype, :duration_ms, :duration_api_ms, :is_error, :num_turns,
68
+ :session_id, :total_cost_usd, :usage, :result
69
+
70
+ def initialize(subtype:, duration_ms:, duration_api_ms: nil, is_error: false, num_turns:,
71
+ session_id:, total_cost_usd: nil, usage: nil, result: nil)
72
+ @subtype = subtype
73
+ @duration_ms = duration_ms
74
+ @duration_api_ms = duration_api_ms
75
+ @is_error = is_error
76
+ @num_turns = num_turns
77
+ @session_id = session_id
78
+ @total_cost_usd = total_cost_usd
79
+ @usage = usage
80
+ @result = result
81
+ end
82
+ end
83
+
84
+ # MCP Server configurations
85
+ class McpStdioServerConfig
86
+ attr_reader :command, :args, :env, :type
87
+
88
+ def initialize(command:, args: [], env: {}, type: 'stdio')
89
+ @command = command
90
+ @args = args
91
+ @env = env
92
+ @type = type
93
+ end
94
+
95
+ def to_h
96
+ {
97
+ type: @type,
98
+ command: @command,
99
+ args: @args,
100
+ env: @env
101
+ }
102
+ end
103
+ end
104
+
105
+ class McpSSEServerConfig
106
+ attr_reader :url, :headers, :type
107
+
108
+ def initialize(url:, headers: {})
109
+ @url = url
110
+ @headers = headers
111
+ @type = 'sse'
112
+ end
113
+
114
+ def to_h
115
+ {
116
+ type: @type,
117
+ url: @url,
118
+ headers: @headers
119
+ }
120
+ end
121
+ end
122
+
123
+ class McpHttpServerConfig
124
+ attr_reader :url, :headers, :type
125
+
126
+ def initialize(url:, headers: {})
127
+ @url = url
128
+ @headers = headers
129
+ @type = 'http'
130
+ end
131
+
132
+ def to_h
133
+ {
134
+ type: @type,
135
+ url: @url,
136
+ headers: @headers
137
+ }
138
+ end
139
+ end
140
+
141
+ # JSONL message helpers for streaming input
142
+ module JSONLHelpers
143
+ # Create a user message in the format expected by Claude CLI
144
+ def self.create_user_message(text)
145
+ {
146
+ 'type' => 'user',
147
+ 'message' => {
148
+ 'role' => 'user',
149
+ 'content' => [
150
+ {
151
+ 'type' => 'text',
152
+ 'text' => text
153
+ }
154
+ ]
155
+ }
156
+ }
157
+ end
158
+
159
+ # Format multiple messages as JSONL
160
+ def self.format_messages_as_jsonl(messages)
161
+ messages.map { |msg| msg.to_json }.join("\n")
162
+ end
163
+
164
+ # Create a multi-turn conversation as JSONL messages
165
+ def self.create_conversation(*turns)
166
+ turns.compact.reject(&:empty?).map { |turn| create_user_message(turn) }
167
+ end
168
+ end
169
+
170
+ # Query options
171
+ class ClaudeCodeOptions
172
+ attr_reader :allowed_tools, :max_thinking_tokens, :system_prompt, :append_system_prompt,
173
+ :mcp_tools, :mcp_servers, :permission_mode, :continue_conversation, :resume,
174
+ :resume_conversation_id, :max_turns, :disallowed_tools, :model, :permission_prompt_tool_name, :cwd,
175
+ :input_format, :output_format
176
+
177
+ def initialize(
178
+ allowed_tools: [],
179
+ max_thinking_tokens: 8000,
180
+ system_prompt: nil,
181
+ append_system_prompt: nil,
182
+ mcp_tools: [],
183
+ mcp_servers: {},
184
+ permission_mode: nil,
185
+ continue_conversation: false,
186
+ resume: nil,
187
+ resume_conversation_id: nil,
188
+ max_turns: nil,
189
+ disallowed_tools: [],
190
+ model: nil,
191
+ permission_prompt_tool_name: nil,
192
+ cwd: nil,
193
+ input_format: nil,
194
+ output_format: nil
195
+ )
196
+ @allowed_tools = allowed_tools
197
+ @max_thinking_tokens = max_thinking_tokens
198
+ @system_prompt = system_prompt
199
+ @append_system_prompt = append_system_prompt
200
+ @mcp_tools = mcp_tools
201
+ @mcp_servers = mcp_servers
202
+ @permission_mode = permission_mode
203
+ @continue_conversation = continue_conversation
204
+ @resume = resume
205
+ @resume_conversation_id = resume_conversation_id
206
+ @max_turns = max_turns
207
+ @disallowed_tools = disallowed_tools
208
+ @model = model
209
+ @permission_prompt_tool_name = permission_prompt_tool_name
210
+ @cwd = cwd
211
+ @input_format = input_format
212
+ @output_format = output_format
213
+ end
214
+
215
+ def to_h
216
+ {
217
+ allowed_tools: @allowed_tools,
218
+ max_thinking_tokens: @max_thinking_tokens,
219
+ system_prompt: @system_prompt,
220
+ append_system_prompt: @append_system_prompt,
221
+ mcp_tools: @mcp_tools,
222
+ mcp_servers: @mcp_servers.transform_values { |config| config.respond_to?(:to_h) ? config.to_h : config },
223
+ permission_mode: @permission_mode,
224
+ continue_conversation: @continue_conversation,
225
+ resume: @resume,
226
+ resume_conversation_id: @resume_conversation_id,
227
+ max_turns: @max_turns,
228
+ disallowed_tools: @disallowed_tools,
229
+ model: @model,
230
+ permission_prompt_tool_name: @permission_prompt_tool_name,
231
+ cwd: @cwd&.to_s,
232
+ input_format: @input_format,
233
+ output_format: @output_format
234
+ }.compact
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeCode
4
+ VERSION = "0.0.14"
5
+ end
@@ -0,0 +1,265 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'open3'
5
+ require 'pathname'
6
+
7
+ require_relative 'claude_code/version'
8
+ require_relative 'claude_code/types'
9
+ require_relative 'claude_code/errors'
10
+ require_relative 'claude_code/client'
11
+
12
+ module ClaudeCode
13
+ # Main query method - supports both positional and keyword arguments
14
+ def self.query(prompt_or_args = nil, prompt: nil, options: nil, cli_path: nil, mcp_servers: {}, &block)
15
+ # Handle positional argument for backward compatibility
16
+ if prompt_or_args.is_a?(String)
17
+ prompt = prompt_or_args
18
+ elsif prompt_or_args.is_a?(Hash)
19
+ # Extract from hash if all args passed as first parameter
20
+ prompt = prompt_or_args[:prompt] || prompt
21
+ options = prompt_or_args[:options] || options
22
+ cli_path = prompt_or_args[:cli_path] || cli_path
23
+ mcp_servers = prompt_or_args[:mcp_servers] || mcp_servers
24
+ end
25
+ options ||= ClaudeCodeOptions.new
26
+
27
+ # Merge MCP servers if provided as a separate parameter
28
+ unless mcp_servers.empty?
29
+ options = ClaudeCodeOptions.new(
30
+ allowed_tools: options.allowed_tools,
31
+ max_thinking_tokens: options.max_thinking_tokens,
32
+ system_prompt: options.system_prompt,
33
+ append_system_prompt: options.append_system_prompt,
34
+ mcp_tools: options.mcp_tools,
35
+ mcp_servers: options.mcp_servers.merge(mcp_servers),
36
+ permission_mode: options.permission_mode,
37
+ continue_conversation: options.continue_conversation,
38
+ resume: options.resume,
39
+ max_turns: options.max_turns,
40
+ disallowed_tools: options.disallowed_tools,
41
+ model: options.model,
42
+ permission_prompt_tool_name: options.permission_prompt_tool_name,
43
+ cwd: options.cwd
44
+ )
45
+ end
46
+
47
+ ENV['CLAUDE_CODE_ENTRYPOINT'] = 'sdk-ruby'
48
+
49
+ client = Client.new
50
+ result = client.process_query(prompt: prompt, options: options, cli_path: cli_path, mcp_servers: mcp_servers)
51
+
52
+ if block_given?
53
+ result.each { |message| yield message }
54
+ else
55
+ result
56
+ end
57
+ end
58
+
59
+ # Convenience method for adding MCP servers
60
+ def self.add_mcp_server(name, config)
61
+ { name => config }
62
+ end
63
+
64
+ # Ultra-convenient method for quick MCP queries
65
+ def self.quick_mcp_query(prompt, server_name:, server_url:, tools:, **options)
66
+ cli_path = options.delete(:cli_path)
67
+
68
+ mcp_servers = add_mcp_server(server_name, server_url)
69
+
70
+ # Ensure tools are in the correct format
71
+ allowed_tools = Array(tools).map { |tool|
72
+ tool.start_with?("mcp__") ? tool : "mcp__#{server_name}__#{tool}"
73
+ }
74
+
75
+ opts = ClaudeCodeOptions.new(
76
+ allowed_tools: allowed_tools,
77
+ max_turns: options[:max_turns] || 1,
78
+ system_prompt: options[:system_prompt] || "You are helpful. Use the available MCP tools to answer questions.",
79
+ **options.slice(:model, :permission_mode, :cwd)
80
+ )
81
+
82
+ query(
83
+ prompt: prompt,
84
+ options: opts,
85
+ cli_path: cli_path,
86
+ mcp_servers: mcp_servers
87
+ )
88
+ end
89
+
90
+ # Continue the most recent conversation
91
+ def self.continue_conversation(prompt = nil, options: nil, cli_path: nil, mcp_servers: {})
92
+ options ||= ClaudeCodeOptions.new
93
+
94
+ # Set continue_conversation to true
95
+ continue_options = ClaudeCodeOptions.new(
96
+ allowed_tools: options.allowed_tools,
97
+ max_thinking_tokens: options.max_thinking_tokens,
98
+ system_prompt: options.system_prompt,
99
+ append_system_prompt: options.append_system_prompt,
100
+ mcp_tools: options.mcp_tools,
101
+ mcp_servers: options.mcp_servers,
102
+ permission_mode: options.permission_mode,
103
+ continue_conversation: true,
104
+ resume: options.resume,
105
+ max_turns: options.max_turns,
106
+ disallowed_tools: options.disallowed_tools,
107
+ model: options.model,
108
+ permission_prompt_tool_name: options.permission_prompt_tool_name,
109
+ cwd: options.cwd
110
+ )
111
+
112
+ query(
113
+ prompt: prompt || "",
114
+ options: continue_options,
115
+ cli_path: cli_path,
116
+ mcp_servers: mcp_servers
117
+ )
118
+ end
119
+
120
+ # Resume a specific conversation by session ID
121
+ def self.resume_conversation(session_id, prompt = nil, options: nil, cli_path: nil, mcp_servers: {})
122
+ options ||= ClaudeCodeOptions.new
123
+
124
+ # Set resume with the session ID
125
+ resume_options = ClaudeCodeOptions.new(
126
+ allowed_tools: options.allowed_tools,
127
+ max_thinking_tokens: options.max_thinking_tokens,
128
+ system_prompt: options.system_prompt,
129
+ append_system_prompt: options.append_system_prompt,
130
+ mcp_tools: options.mcp_tools,
131
+ mcp_servers: options.mcp_servers,
132
+ permission_mode: options.permission_mode,
133
+ continue_conversation: options.continue_conversation,
134
+ resume_conversation_id: session_id,
135
+ max_turns: options.max_turns,
136
+ disallowed_tools: options.disallowed_tools,
137
+ model: options.model,
138
+ permission_prompt_tool_name: options.permission_prompt_tool_name,
139
+ cwd: options.cwd
140
+ )
141
+
142
+ query(
143
+ prompt: prompt || "",
144
+ options: resume_options,
145
+ cli_path: cli_path,
146
+ mcp_servers: mcp_servers
147
+ )
148
+ end
149
+
150
+ # Query with streaming JSON input (multiple turns via JSONL)
151
+ def self.stream_json_query(messages, options: nil, cli_path: nil, mcp_servers: {}, &block)
152
+ options ||= ClaudeCodeOptions.new
153
+
154
+ # Set input_format to stream-json
155
+ stream_options = ClaudeCodeOptions.new(
156
+ allowed_tools: options.allowed_tools,
157
+ max_thinking_tokens: options.max_thinking_tokens,
158
+ system_prompt: options.system_prompt,
159
+ append_system_prompt: options.append_system_prompt,
160
+ mcp_tools: options.mcp_tools,
161
+ mcp_servers: options.mcp_servers,
162
+ permission_mode: options.permission_mode,
163
+ continue_conversation: options.continue_conversation,
164
+ resume_conversation_id: options.resume_conversation_id,
165
+ max_turns: options.max_turns,
166
+ disallowed_tools: options.disallowed_tools,
167
+ model: options.model,
168
+ permission_prompt_tool_name: options.permission_prompt_tool_name,
169
+ cwd: options.cwd,
170
+ input_format: 'stream-json'
171
+ )
172
+
173
+ # Use the client to process the query
174
+ client = Client.new
175
+ enumerator = client.process_query(messages: messages, options: stream_options, cli_path: cli_path, mcp_servers: mcp_servers)
176
+
177
+ if block_given?
178
+ enumerator.each { |message| yield message }
179
+ else
180
+ enumerator
181
+ end
182
+ end
183
+
184
+ # Helper method to parse CLI messages for streaming JSON input
185
+ def self.parse_cli_message(data)
186
+ case data['type']
187
+ when 'user'
188
+ UserMessage.new(data.dig('message', 'content'))
189
+ when 'assistant'
190
+ content_blocks = parse_content_blocks(data.dig('message', 'content') || [])
191
+ AssistantMessage.new(content_blocks)
192
+ when 'system'
193
+ SystemMessage.new(subtype: data['subtype'], data: data)
194
+ when 'result'
195
+ ResultMessage.new(
196
+ subtype: data['subtype'],
197
+ duration_ms: data['duration_ms'],
198
+ duration_api_ms: data['duration_api_ms'],
199
+ is_error: data['is_error'],
200
+ num_turns: data['num_turns'],
201
+ session_id: data['session_id'],
202
+ total_cost_usd: data['total_cost_usd'],
203
+ usage: data['usage'],
204
+ result: data['result']
205
+ )
206
+ end
207
+ end
208
+
209
+ # Helper method to parse content blocks
210
+ def self.parse_content_blocks(blocks)
211
+ blocks.map do |block|
212
+ case block['type']
213
+ when 'text'
214
+ TextBlock.new(block['text'])
215
+ when 'tool_use'
216
+ ToolUseBlock.new(id: block['id'], name: block['name'], input: block['input'])
217
+ when 'tool_result'
218
+ ToolResultBlock.new(
219
+ tool_use_id: block['tool_use_id'],
220
+ content: block['content'],
221
+ is_error: block['is_error']
222
+ )
223
+ end
224
+ end.compact
225
+ end
226
+
227
+ # Streaming helper that prints messages as they arrive
228
+ def self.stream_query(prompt:, options: nil, cli_path: nil, mcp_servers: {}, &block)
229
+ query(
230
+ prompt: prompt,
231
+ options: options,
232
+ cli_path: cli_path,
233
+ mcp_servers: mcp_servers
234
+ ).each_with_index do |message, index|
235
+ if block_given?
236
+ yield message, index
237
+ else
238
+ # Use auto_format_message for consistent formatting
239
+ auto_format_message(message)
240
+ end
241
+ end
242
+ end
243
+
244
+ # Auto-format message for pretty printing
245
+ def self.auto_format_message(message)
246
+ case message
247
+ when SystemMessage
248
+ puts "🔧 System: #{message.subtype}" if message.subtype != "init"
249
+ when AssistantMessage
250
+ message.content.each do |block|
251
+ case block
252
+ when TextBlock
253
+ puts "💬 #{block.text}"
254
+ when ToolUseBlock
255
+ puts "🔧 #{block.name}"
256
+ when ToolResultBlock
257
+ puts "📤 #{block.content}"
258
+ end
259
+ end
260
+ when ResultMessage
261
+ puts "✅ Cost: $#{format('%.6f', message.total_cost_usd || 0)}"
262
+ end
263
+ $stdout.flush
264
+ end
265
+ end