Module: RubyClaude
- Defined in:
- lib/ruby_claude.rb,
lib/ruby_claude/event.rb,
lib/ruby_claude/client.rb,
lib/ruby_claude/errors.rb,
lib/ruby_claude/runner.rb,
lib/ruby_claude/command.rb,
lib/ruby_claude/session.rb,
lib/ruby_claude/version.rb,
lib/ruby_claude/response.rb,
lib/ruby_claude/configuration.rb
Overview
Ruby Claude — a subscription-authenticated Ruby SDK that talks to Claude by shelling out to the Claude Code CLI (+claude -p+) in headless mode.
It is an unofficial, community wrapper around a supported headless feature. By default it strips ANTHROPIC_API_KEY from the child environment so calls draw on the logged-in Pro/Max subscription rather than API billing.
Defined Under Namespace
Classes: AuthenticationError, BinaryNotFoundError, Client, Command, Configuration, Error, Event, ExecutionError, ParseError, Response, RunResult, Runner, Session, TimeoutError
Constant Summary collapse
- VERSION =
The released version of the gem, following Semantic Versioning.
"0.0.1"
Instance Attribute Summary collapse
-
#cost_usd ⇒ Float
readonly
Total cost in USD (often
0.0on a subscription). -
#duration_ms ⇒ Integer
readonly
Wall-clock duration in milliseconds.
-
#error ⇒ Boolean
readonly
Whether the CLI reported an error.
-
#exit_status ⇒ Integer?
readonly
Exit code, or nil if the process was signalled.
-
#num_turns ⇒ Integer
readonly
Number of agentic turns.
-
#raw ⇒ Hash
readonly
The full parsed result object.
-
#session_id ⇒ String?
readonly
The session id of this conversation.
-
#stderr ⇒ String
readonly
Captured stderr.
-
#stdout ⇒ String?
readonly
Captured stdout (nil when streaming).
-
#text ⇒ String
readonly
The assistant’s final text result.
-
#type ⇒ Symbol
readonly
The event type.
-
#usage ⇒ Hash
readonly
Token usage counts, when present.
Class Method Summary collapse
-
.configuration ⇒ Configuration
The global configuration used by RubyClaude.query and as the default for new Client instances.
-
.configure {|config| ... } ⇒ Configuration
Configure the global defaults.
-
.default_client ⇒ Client
The memoized default Client, rebuilt whenever RubyClaude.configure is called.
-
.query(prompt, **options) ⇒ Response
One-shot convenience that delegates to a memoized default Client.
-
.reset_configuration! ⇒ void
Reset all global state.
Instance Attribute Details
#cost_usd ⇒ Float (readonly)
Returns total cost in USD (often 0.0 on a subscription).
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/ruby_claude/event.rb', line 20 Event = Data.define(:type, :text, :session_id, :cost_usd, :duration_ms, :raw) do # Build an Event from one parsed NDJSON line. # # @param data [Hash, nil] the parsed line # @return [Event] def self.from_hash(data) data ||= {} new( type: (data["type"] || "unknown").to_sym, text: extract_text(data), session_id: data["session_id"], cost_usd: data["total_cost_usd"], duration_ms: data["duration_ms"], raw: data ) end # Pull human-readable text out of a parsed line, if any. # # @param data [Hash] # @return [String, nil] def self.extract_text(data) case data["type"] when "assistant", "user" = data["message"] || data text_from_content(["content"]) when "result" data["result"] end end # Join the text from a content array (or pass a bare string through). # # @param content [String, Array, nil] # @return [String, nil] def self.text_from_content(content) return content if content.is_a?(String) return nil unless content.is_a?(Array) texts = content .select { |block| block.is_a?(Hash) && block["type"] == "text" } .filter_map { |block| block["text"] } texts.empty? ? nil : texts.join end # @return [Boolean] whether this is the final result event def result? = type == :result # @return [Boolean] whether this is an assistant message event def assistant? = type == :assistant # @return [Boolean] whether this is a system event def system? = type == :system # @return [Boolean] whether this is a user message event def user? = type == :user end |
#duration_ms ⇒ Integer (readonly)
Returns wall-clock duration in milliseconds.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/ruby_claude/event.rb', line 20 Event = Data.define(:type, :text, :session_id, :cost_usd, :duration_ms, :raw) do # Build an Event from one parsed NDJSON line. # # @param data [Hash, nil] the parsed line # @return [Event] def self.from_hash(data) data ||= {} new( type: (data["type"] || "unknown").to_sym, text: extract_text(data), session_id: data["session_id"], cost_usd: data["total_cost_usd"], duration_ms: data["duration_ms"], raw: data ) end # Pull human-readable text out of a parsed line, if any. # # @param data [Hash] # @return [String, nil] def self.extract_text(data) case data["type"] when "assistant", "user" = data["message"] || data text_from_content(["content"]) when "result" data["result"] end end # Join the text from a content array (or pass a bare string through). # # @param content [String, Array, nil] # @return [String, nil] def self.text_from_content(content) return content if content.is_a?(String) return nil unless content.is_a?(Array) texts = content .select { |block| block.is_a?(Hash) && block["type"] == "text" } .filter_map { |block| block["text"] } texts.empty? ? nil : texts.join end # @return [Boolean] whether this is the final result event def result? = type == :result # @return [Boolean] whether this is an assistant message event def assistant? = type == :assistant # @return [Boolean] whether this is a system event def system? = type == :system # @return [Boolean] whether this is a user message event def user? = type == :user end |
#error ⇒ Boolean (readonly)
Returns whether the CLI reported an error.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/ruby_claude/response.rb', line 26 Response = Data.define(:text, :session_id, :cost_usd, :usage, :num_turns, :duration_ms, :error, :raw) do # Build a Response from a parsed CLI result hash. # # @param data [Hash, nil] the parsed result object # @return [Response] def self.from_result(data) data ||= {} new( text: data["result"] || "", session_id: data["session_id"], cost_usd: (data["total_cost_usd"] || data["cost_usd"] || 0.0).to_f, usage: data["usage"] || {}, num_turns: (data["num_turns"] || 0).to_i, duration_ms: (data["duration_ms"] || 0).to_i, error: data.fetch("is_error", false) ? true : false, raw: data ) end # @return [Boolean] whether the result represents an error def error? = !!error # @return [Boolean] whether the result was successful def success? = !error? # Returns the assistant text, so +puts response+ prints the answer. # # @return [String] def to_s = text end |
#exit_status ⇒ Integer? (readonly)
Returns exit code, or nil if the process was signalled.
14 |
# File 'lib/ruby_claude/runner.rb', line 14 RunResult = Data.define(:stdout, :stderr, :exit_status) |
#num_turns ⇒ Integer (readonly)
Returns number of agentic turns.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/ruby_claude/response.rb', line 26 Response = Data.define(:text, :session_id, :cost_usd, :usage, :num_turns, :duration_ms, :error, :raw) do # Build a Response from a parsed CLI result hash. # # @param data [Hash, nil] the parsed result object # @return [Response] def self.from_result(data) data ||= {} new( text: data["result"] || "", session_id: data["session_id"], cost_usd: (data["total_cost_usd"] || data["cost_usd"] || 0.0).to_f, usage: data["usage"] || {}, num_turns: (data["num_turns"] || 0).to_i, duration_ms: (data["duration_ms"] || 0).to_i, error: data.fetch("is_error", false) ? true : false, raw: data ) end # @return [Boolean] whether the result represents an error def error? = !!error # @return [Boolean] whether the result was successful def success? = !error? # Returns the assistant text, so +puts response+ prints the answer. # # @return [String] def to_s = text end |
#raw ⇒ Hash (readonly)
Returns the full parsed result object.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/ruby_claude/event.rb', line 20 Event = Data.define(:type, :text, :session_id, :cost_usd, :duration_ms, :raw) do # Build an Event from one parsed NDJSON line. # # @param data [Hash, nil] the parsed line # @return [Event] def self.from_hash(data) data ||= {} new( type: (data["type"] || "unknown").to_sym, text: extract_text(data), session_id: data["session_id"], cost_usd: data["total_cost_usd"], duration_ms: data["duration_ms"], raw: data ) end # Pull human-readable text out of a parsed line, if any. # # @param data [Hash] # @return [String, nil] def self.extract_text(data) case data["type"] when "assistant", "user" = data["message"] || data text_from_content(["content"]) when "result" data["result"] end end # Join the text from a content array (or pass a bare string through). # # @param content [String, Array, nil] # @return [String, nil] def self.text_from_content(content) return content if content.is_a?(String) return nil unless content.is_a?(Array) texts = content .select { |block| block.is_a?(Hash) && block["type"] == "text" } .filter_map { |block| block["text"] } texts.empty? ? nil : texts.join end # @return [Boolean] whether this is the final result event def result? = type == :result # @return [Boolean] whether this is an assistant message event def assistant? = type == :assistant # @return [Boolean] whether this is a system event def system? = type == :system # @return [Boolean] whether this is a user message event def user? = type == :user end |
#session_id ⇒ String? (readonly)
Returns the session id of this conversation.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/ruby_claude/event.rb', line 20 Event = Data.define(:type, :text, :session_id, :cost_usd, :duration_ms, :raw) do # Build an Event from one parsed NDJSON line. # # @param data [Hash, nil] the parsed line # @return [Event] def self.from_hash(data) data ||= {} new( type: (data["type"] || "unknown").to_sym, text: extract_text(data), session_id: data["session_id"], cost_usd: data["total_cost_usd"], duration_ms: data["duration_ms"], raw: data ) end # Pull human-readable text out of a parsed line, if any. # # @param data [Hash] # @return [String, nil] def self.extract_text(data) case data["type"] when "assistant", "user" = data["message"] || data text_from_content(["content"]) when "result" data["result"] end end # Join the text from a content array (or pass a bare string through). # # @param content [String, Array, nil] # @return [String, nil] def self.text_from_content(content) return content if content.is_a?(String) return nil unless content.is_a?(Array) texts = content .select { |block| block.is_a?(Hash) && block["type"] == "text" } .filter_map { |block| block["text"] } texts.empty? ? nil : texts.join end # @return [Boolean] whether this is the final result event def result? = type == :result # @return [Boolean] whether this is an assistant message event def assistant? = type == :assistant # @return [Boolean] whether this is a system event def system? = type == :system # @return [Boolean] whether this is a user message event def user? = type == :user end |
#stderr ⇒ String (readonly)
Returns captured stderr.
14 |
# File 'lib/ruby_claude/runner.rb', line 14 RunResult = Data.define(:stdout, :stderr, :exit_status) |
#stdout ⇒ String? (readonly)
Returns captured stdout (nil when streaming).
14 |
# File 'lib/ruby_claude/runner.rb', line 14 RunResult = Data.define(:stdout, :stderr, :exit_status) |
#text ⇒ String (readonly)
Returns the assistant’s final text result.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/ruby_claude/event.rb', line 20 Event = Data.define(:type, :text, :session_id, :cost_usd, :duration_ms, :raw) do # Build an Event from one parsed NDJSON line. # # @param data [Hash, nil] the parsed line # @return [Event] def self.from_hash(data) data ||= {} new( type: (data["type"] || "unknown").to_sym, text: extract_text(data), session_id: data["session_id"], cost_usd: data["total_cost_usd"], duration_ms: data["duration_ms"], raw: data ) end # Pull human-readable text out of a parsed line, if any. # # @param data [Hash] # @return [String, nil] def self.extract_text(data) case data["type"] when "assistant", "user" = data["message"] || data text_from_content(["content"]) when "result" data["result"] end end # Join the text from a content array (or pass a bare string through). # # @param content [String, Array, nil] # @return [String, nil] def self.text_from_content(content) return content if content.is_a?(String) return nil unless content.is_a?(Array) texts = content .select { |block| block.is_a?(Hash) && block["type"] == "text" } .filter_map { |block| block["text"] } texts.empty? ? nil : texts.join end # @return [Boolean] whether this is the final result event def result? = type == :result # @return [Boolean] whether this is an assistant message event def assistant? = type == :assistant # @return [Boolean] whether this is a system event def system? = type == :system # @return [Boolean] whether this is a user message event def user? = type == :user end |
#type ⇒ Symbol (readonly)
Returns the event type.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/ruby_claude/event.rb', line 20 Event = Data.define(:type, :text, :session_id, :cost_usd, :duration_ms, :raw) do # Build an Event from one parsed NDJSON line. # # @param data [Hash, nil] the parsed line # @return [Event] def self.from_hash(data) data ||= {} new( type: (data["type"] || "unknown").to_sym, text: extract_text(data), session_id: data["session_id"], cost_usd: data["total_cost_usd"], duration_ms: data["duration_ms"], raw: data ) end # Pull human-readable text out of a parsed line, if any. # # @param data [Hash] # @return [String, nil] def self.extract_text(data) case data["type"] when "assistant", "user" = data["message"] || data text_from_content(["content"]) when "result" data["result"] end end # Join the text from a content array (or pass a bare string through). # # @param content [String, Array, nil] # @return [String, nil] def self.text_from_content(content) return content if content.is_a?(String) return nil unless content.is_a?(Array) texts = content .select { |block| block.is_a?(Hash) && block["type"] == "text" } .filter_map { |block| block["text"] } texts.empty? ? nil : texts.join end # @return [Boolean] whether this is the final result event def result? = type == :result # @return [Boolean] whether this is an assistant message event def assistant? = type == :assistant # @return [Boolean] whether this is a system event def system? = type == :system # @return [Boolean] whether this is a user message event def user? = type == :user end |
#usage ⇒ Hash (readonly)
Returns token usage counts, when present.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/ruby_claude/response.rb', line 26 Response = Data.define(:text, :session_id, :cost_usd, :usage, :num_turns, :duration_ms, :error, :raw) do # Build a Response from a parsed CLI result hash. # # @param data [Hash, nil] the parsed result object # @return [Response] def self.from_result(data) data ||= {} new( text: data["result"] || "", session_id: data["session_id"], cost_usd: (data["total_cost_usd"] || data["cost_usd"] || 0.0).to_f, usage: data["usage"] || {}, num_turns: (data["num_turns"] || 0).to_i, duration_ms: (data["duration_ms"] || 0).to_i, error: data.fetch("is_error", false) ? true : false, raw: data ) end # @return [Boolean] whether the result represents an error def error? = !!error # @return [Boolean] whether the result was successful def success? = !error? # Returns the assistant text, so +puts response+ prints the answer. # # @return [String] def to_s = text end |
Class Method Details
.configuration ⇒ Configuration
32 33 34 |
# File 'lib/ruby_claude.rb', line 32 def configuration @configuration ||= Configuration.new end |
.configure {|config| ... } ⇒ Configuration
Configure the global defaults.
40 41 42 43 44 |
# File 'lib/ruby_claude.rb', line 40 def configure yield configuration if block_given? @default_client = nil # rebuild with the new configuration on next use configuration end |
.default_client ⇒ Client
66 67 68 |
# File 'lib/ruby_claude.rb', line 66 def default_client @default_client ||= Client.new end |
.query(prompt, **options) ⇒ Response
One-shot convenience that delegates to a memoized default Client.
59 60 61 |
# File 'lib/ruby_claude.rb', line 59 def query(prompt, **) default_client.query(prompt, **) end |
.reset_configuration! ⇒ void
This method returns an undefined value.
Reset all global state. Mainly useful in tests.
49 50 51 52 |
# File 'lib/ruby_claude.rb', line 49 def reset_configuration! @configuration = Configuration.new @default_client = nil end |