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.

Examples:

One-shot

puts RubyClaude.query("Summarize lib/foo.rb in two sentences")

A configured client

client = RubyClaude::Client.new(model: "claude-sonnet-4-6", timeout: 180)
client.query("What does this project do?").text

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.0"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Attribute Details

#cost_usdFloat (readonly)

Returns total cost in USD (often 0.0 on a subscription).

Returns:

  • (Float)

    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"
      message = data["message"] || data
      text_from_content(message["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_msInteger (readonly)

Returns wall-clock duration in milliseconds.

Returns:

  • (Integer)

    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"
      message = data["message"] || data
      text_from_content(message["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

#errorBoolean (readonly)

Returns whether the CLI reported an error.

Returns:

  • (Boolean)

    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_statusInteger? (readonly)

Returns exit code, or nil if the process was signalled.

Returns:

  • (Integer, nil)

    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_turnsInteger (readonly)

Returns number of agentic turns.

Returns:

  • (Integer)

    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

#rawHash (readonly)

Returns the full parsed result object.

Returns:

  • (Hash)

    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"
      message = data["message"] || data
      text_from_content(message["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_idString? (readonly)

Returns the session id of this conversation.

Returns:

  • (String, nil)

    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"
      message = data["message"] || data
      text_from_content(message["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

#stderrString (readonly)

Returns captured stderr.

Returns:

  • (String)

    captured stderr



14
# File 'lib/ruby_claude/runner.rb', line 14

RunResult = Data.define(:stdout, :stderr, :exit_status)

#stdoutString? (readonly)

Returns captured stdout (nil when streaming).

Returns:

  • (String, nil)

    captured stdout (nil when streaming)



14
# File 'lib/ruby_claude/runner.rb', line 14

RunResult = Data.define(:stdout, :stderr, :exit_status)

#textString (readonly)

Returns the assistant’s final text result.

Returns:

  • (String)

    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"
      message = data["message"] || data
      text_from_content(message["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

#typeSymbol (readonly)

Returns the event type.

Returns:

  • (Symbol)

    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"
      message = data["message"] || data
      text_from_content(message["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

#usageHash (readonly)

Returns token usage counts, when present.

Returns:

  • (Hash)

    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

.configurationConfiguration

The global configuration used by query and as the default for new Client instances.

Returns:



32
33
34
# File 'lib/ruby_claude.rb', line 32

def configuration
  @configuration ||= Configuration.new
end

.configure {|config| ... } ⇒ Configuration

Configure the global defaults.

Yield Parameters:

Returns:



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_clientClient

The memoized default Client, rebuilt whenever configure is called.

Returns:



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.

Parameters:

Returns:



59
60
61
# File 'lib/ruby_claude.rb', line 59

def query(prompt, **options)
  default_client.query(prompt, **options)
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