ruby_claude 0.0.0 → 0.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46dffd5cda4df76cdd6a657076af87f43c068929ce40b0973e2aa4dd26b4a7f1
4
- data.tar.gz: 5c55b6f8448491d93d3716bbfe9694ea7f160bef7bd1c5410a3021778f5b362a
3
+ metadata.gz: 7ce8e2c7378bd0ed7559c37995d1886fb37367b8e53e29f77a2bc65ffc10344d
4
+ data.tar.gz: a1e7bf9563ed416687cb0b8bac091850b3123b623ecf91fd25bbe31c240be9cc
5
5
  SHA512:
6
- metadata.gz: 34dcef3922757a6b7580f196f0528268b9827ec6bdf0fea979ef768a7d12ecc0723c3f75dcec094b25f8dd45d7a20c682aef654e535f878775d4470bcd33a615
7
- data.tar.gz: 0b2ecd60e8dbf74ef7d64b4b0eda9932b7b9dc5aad6a1c09aeb1dabaa159de5f0fdd1656b13cfc5944b7b681d269bc69668a967e2e8d825a92320244b86d14c5
6
+ metadata.gz: d158e722113e3b3ee48313f3fbb2cf0a9df9d7980b263045a1a89f5ff1c7786e396d4bc8d1195aef7edf256c720b18e5d0c1936253673b0a605c6e790f42b62d
7
+ data.tar.gz: ebac20bcc17499f0b5007c62dba352699d52bb779285b160401daa7a1a08399f67f0df0efcf37bfecbbd0146b99361024f9707b84da17a94b445deb24e409470
data/README.md CHANGED
@@ -1,59 +1,36 @@
1
1
  # Ruby Claude
2
2
 
3
- A small, dependency-light, idiomatic Ruby SDK for talking to Claude — by
4
- shelling out to the **Claude Code CLI** (`claude -p`) in headless mode and
5
- authenticating with your **Claude Pro/Max subscription** instead of an
6
- Anthropic API key.
7
-
8
- > **Unofficial.** This is a community gem. It is *not* affiliated with or
9
- > endorsed by Anthropic. It uses a documented, supported headless feature
10
- > (`claude -p`) and stays within your subscription's normal rate limits. It
11
- > does **not** extract or reuse OAuth tokens, and it makes **no** direct HTTP
12
- > calls to the Anthropic API.
13
-
14
- ## Why a subscription instead of an API key?
15
-
16
- `claude -p "<prompt>"` runs Claude Code non-interactively and prints the
17
- result, using whatever credentials the CLI is logged in with. If you logged in
18
- with a **subscription** (`claude` → `/login` → subscription option), those
19
- calls draw on your subscription — **no API billing**.
20
-
21
- The one catch: if `ANTHROPIC_API_KEY` is present in the environment, Claude
22
- Code may use it and bill the API. **Ruby Claude strips `ANTHROPIC_API_KEY`
23
- from the child process environment by default** (`use_subscription = true`) so
24
- the CLI falls back to your logged-in subscription credentials. Set
25
- `use_subscription = false` only if you *want* API-key billing.
3
+ A tiny, zero-dependency Ruby SDK for Claude that shells out to the **Claude Code
4
+ CLI** (`claude -p`) and authenticates with your **Claude Pro/Max subscription**
5
+ instead of an API key.
26
6
 
27
- ## Prerequisites
7
+ > **Unofficial** community gem — not affiliated with Anthropic. It uses the
8
+ > supported `claude -p` headless mode within your subscription's rate limits; no
9
+ > OAuth-token handling and no direct API calls.
28
10
 
29
- This gem drives the `claude` binary; it does not install or replace it.
11
+ ## Subscription, not API key
30
12
 
31
- 1. Install Node.js and the Claude Code CLI, and make sure `claude` is on your `PATH`:
32
- ```bash
33
- npm install -g @anthropic-ai/claude-code
34
- claude --version
35
- ```
36
- 2. Log in **once**, choosing the subscription option:
37
- ```bash
38
- claude # then run /login and pick "Claude account with subscription"
39
- ```
13
+ `claude -p` uses whatever the CLI is logged in with if that's a subscription,
14
+ calls draw on it with no API billing. Ruby Claude **strips `ANTHROPIC_API_KEY`
15
+ from the child environment by default** so the CLI can't silently fall back to
16
+ API billing; set `use_subscription = false` to opt back in.
40
17
 
41
- ## Installation
18
+ ## Prerequisites
42
19
 
43
- Add it to your `Gemfile`:
20
+ `claude` must be installed and logged in (this gem drives it, it doesn't replace it):
44
21
 
45
- ```ruby
46
- gem "ruby_claude"
22
+ ```bash
23
+ npm install -g @anthropic-ai/claude-code
24
+ claude # run /login once and choose the subscription option
47
25
  ```
48
26
 
49
- Or install directly:
27
+ ## Install
50
28
 
51
- ```bash
52
- gem install ruby_claude
29
+ ```ruby
30
+ gem "ruby_claude" # in your Gemfile
53
31
  ```
54
32
 
55
- Ruby **3.2+** is required (the value objects use `Data.define`). The gem has
56
- **zero runtime dependencies** — it only uses the standard library.
33
+ …or `gem install ruby_claude`. Requires Ruby 3.2+; zero runtime dependencies.
57
34
 
58
35
  ## Quickstart
59
36
 
@@ -63,167 +40,76 @@ require "ruby_claude"
63
40
  puts RubyClaude.query("Summarize lib/foo.rb in two sentences")
64
41
  ```
65
42
 
66
- That's it — if `claude` is installed and logged in, you get an answer back,
67
- billed against your subscription.
68
-
69
43
  ## Usage
70
44
 
71
- ### 1. One-shot convenience
72
-
73
- Delegates to a memoized, globally-configured default client.
74
-
75
- ```ruby
76
- puts RubyClaude.query("Summarize lib/foo.rb in two sentences")
77
- ```
78
-
79
- ### 2. A configured client
80
-
81
45
  ```ruby
82
- client = RubyClaude::Client.new(
83
- model: "claude-sonnet-4-6",
84
- cwd: "/path/to/project",
85
- append_system_prompt: "Always answer concisely.",
86
- allowed_tools: ["Read", "Grep"],
87
- timeout: 180
88
- )
46
+ # A configured client
47
+ client = RubyClaude::Client.new(model: "claude-sonnet-4-6",
48
+ allowed_tools: ["Read", "Grep"], timeout: 180)
89
49
 
90
50
  res = client.query("What does this project do?")
91
- res.text # => String, the final assistant result
92
- res.session_id # => String
93
- res.cost_usd # => Float (often 0.0 on a subscription)
94
- res.usage # => Hash (token counts, when present)
95
- res.num_turns # => Integer
96
- res.duration_ms # => Integer
97
- res.error? # => false
98
- res.raw # => parsed Hash of the CLI's final result JSON
99
- ```
100
-
101
- `Response#to_s` returns `text`, so `puts client.query("...")` prints the answer.
102
-
103
- ### 3. Streaming
51
+ res.text # final text (Response#to_s returns it too, so `puts res` works)
52
+ res.session_id # String
53
+ res.cost_usd # Float (often 0.0 on a subscription)
54
+ res.usage # Hash also: res.num_turns, res.duration_ms, res.error?, res.raw
104
55
 
105
- `#stream` yields typed events as they arrive and returns the final `Response`.
106
-
107
- ```ruby
56
+ # Streaming — yields typed events, returns the final Response
108
57
  client.stream("Write a haiku about Ruby") do |event|
109
- case event.type
110
- when :assistant then print event.text # assistant text for the turn
111
- when :result then puts "\n[done in #{event.duration_ms}ms]"
112
- end
58
+ print event.text if event.type == :assistant
113
59
  end
114
- ```
115
60
 
116
- Each `Event` exposes `type` (`:system`, `:assistant`, `:user`, `:result`),
117
- `text`, `session_id`, `cost_usd`, `duration_ms`, and `raw` (the full parsed
118
- line). Streaming uses `--output-format stream-json --verbose` under the hood.
61
+ # Multi-turn session resumes the underlying session_id automatically
62
+ chat = client.session
63
+ chat.query("My favorite number is 7.")
64
+ puts chat.query("What's my favorite number?") # => "...7..."
119
65
 
120
- ### 4. Multi-turn session
121
-
122
- A `Session` captures the underlying `session_id` from the first reply and
123
- transparently resumes it on later calls.
124
-
125
- ```ruby
126
- session = client.session
127
- session.query("My favorite number is 7.")
128
- puts session.query("What's my favorite number?") # => "...7..."
129
- session.id # => the session_id being resumed
66
+ # Global defaults for RubyClaude.query and new clients
67
+ RubyClaude.configure { |c| c.model = "claude-sonnet-4-6"; c.timeout = 300 }
130
68
  ```
131
69
 
132
- You can also resume a known session: `client.session(id: "…")`.
133
-
134
- ### 5. Global configuration
135
-
136
- ```ruby
137
- RubyClaude.configure do |c|
138
- c.model = "claude-sonnet-4-6"
139
- c.timeout = 300
140
- c.binary = "claude" # path/name of the CLI
141
- c.cwd = Dir.pwd
142
- c.use_subscription = true # strips ANTHROPIC_API_KEY from the child env
143
- end
144
- ```
70
+ A streaming `Event#type` is `:system`, `:assistant`, `:user`, or `:result`. Use
71
+ `#query` (alias `#ask`) — there is intentionally no `#send`.
145
72
 
146
- These become the defaults for `RubyClaude.query` and for new `Client`
147
- instances. Per-client options passed to `Client.new(**opts)` override them.
73
+ ## Configuration
148
74
 
149
- > **Note:** there is intentionally no `#send` method (it would shadow
150
- > `Object#send`). Use `#query`, or its alias `#ask`.
75
+ `Client.new(**opts)` overrides per instance; `RubyClaude.configure` sets globals.
151
76
 
152
- ## Configuration options
153
-
154
- | Option | Default | Maps to / effect |
155
- |------------------------|----------------------|---------------------------------------------------------------------------|
156
- | `binary` | `"claude"` | executable name/path |
157
- | `model` | `nil` (CLI default) | `--model` |
158
- | `cwd` | `Dir.pwd` | working directory for the subprocess |
159
- | `timeout` | `300` | seconds before the child is killed |
160
- | `use_subscription` | `true` | when true, delete `ANTHROPIC_API_KEY` from the child env |
161
- | `append_system_prompt` | `nil` | `--append-system-prompt` |
162
- | `allowed_tools` | `nil` | `--allowedTools` (array of tool/permission rules) |
163
- | `disallowed_tools` | `nil` | `--disallowedTools` |
164
- | `add_dirs` | `[]` | `--add-dir` (extra readable/writable directories) |
165
- | `permission_mode` | `nil` | `--permission-mode` (`default` / `acceptEdits` / `plan` / `bypassPermissions`) |
166
- | `max_turns` | `nil` | `--max-turns` |
167
-
168
- Tool and directory lists are passed as separate CLI tokens, so permission-rule
169
- patterns that contain spaces (e.g. `"Bash(git log *)"`) are preserved.
77
+ | Option | Default | Maps to |
78
+ |--------|---------|---------|
79
+ | `binary` | `"claude"` | executable name/path |
80
+ | `model` | `nil` | `--model` |
81
+ | `cwd` | `Dir.pwd` | subprocess working directory |
82
+ | `timeout` | `300` | seconds before the child is killed |
83
+ | `use_subscription` | `true` | strip `ANTHROPIC_API_KEY` from the child env |
84
+ | `append_system_prompt` | `nil` | `--append-system-prompt` |
85
+ | `allowed_tools` / `disallowed_tools` | `nil` | `--allowedTools` / `--disallowedTools` |
86
+ | `add_dirs` | `[]` | `--add-dir` |
87
+ | `permission_mode` | `nil` | `--permission-mode` |
88
+ | `max_turns` | `nil` | `--max-turns` |
170
89
 
171
90
  ## Errors
172
91
 
173
- All errors inherit from `RubyClaude::Error`:
174
-
175
- | Error | Raised when |
176
- |----------------------------------|-----------------------------------------------------------------------------|
177
- | `RubyClaude::BinaryNotFoundError`| `claude` is not on `PATH` / not executable (message explains how to install)|
178
- | `RubyClaude::AuthenticationError`| output/exit indicates you are not logged in (suggests `claude` + `/login`) |
179
- | `RubyClaude::TimeoutError` | the child exceeded `timeout`; the gem killed it |
180
- | `RubyClaude::ExecutionError` | non-zero exit, or a result with `is_error: true` (carries `#status`, `#stderr`) |
181
- | `RubyClaude::ParseError` | the CLI output couldn't be parsed as the expected JSON |
182
-
183
- ```ruby
184
- begin
185
- RubyClaude.query("hello")
186
- rescue RubyClaude::BinaryNotFoundError => e
187
- warn e.message # install + /login instructions
188
- rescue RubyClaude::AuthenticationError
189
- warn "Run `claude` and `/login` with your subscription."
190
- rescue RubyClaude::ExecutionError => e
191
- warn "claude failed (status #{e.status}): #{e.stderr}"
192
- end
193
- ```
92
+ All subclass `RubyClaude::Error`: `BinaryNotFoundError` (no `claude` on PATH),
93
+ `AuthenticationError` (not logged in), `TimeoutError`, `ExecutionError` (non-zero
94
+ exit or an `is_error` result; carries `#status`/`#stderr`), and `ParseError`.
194
95
 
195
96
  ## How it works
196
97
 
197
- Ruby Claude is a thin, well-factored wrapper around `claude -p`:
198
-
199
- - **`Command`** (pure, no I/O) turns your configuration + per-call options into
200
- the argv array (`["claude", "-p", "--output-format", "json", …]`) and the
201
- child-environment overrides (removing `ANTHROPIC_API_KEY` in subscription mode).
202
- - **`Runner`** owns all subprocess concerns: it spawns `claude` via `Open3`
203
- (always the array form — your prompt is **never** shell-interpolated), writes
204
- the prompt to **stdin** (avoiding `ARG_MAX` and escaping issues), enforces the
205
- timeout by killing the child, captures output, and — for streaming — reads
206
- stdout line-by-line as newline-delimited JSON.
207
- - **`Client`** composes the two and builds `Response` / `Event` objects.
208
- - **`Session`** remembers the `session_id` and passes `--resume <id>`.
209
-
210
- The runner is stateless and spawns one subprocess per call, so a `Client` is
211
- safe to reuse and to call concurrently from multiple threads.
98
+ `Command` builds the argv + child env (pure, no I/O); `Runner` spawns `claude`
99
+ via `Open3` (array form — no shell; prompt on stdin), enforces the timeout, and
100
+ parses output; `Client` builds `Response`/`Event`; `Session` resumes via
101
+ `--resume`. The runner is stateless per call, so a `Client` is safe to share
102
+ across threads.
212
103
 
213
104
  ## Development
214
105
 
215
106
  ```bash
216
- bundle install # install dev/test dependencies
217
- rake test # run the test suite (hermetic — never spawns claude)
218
- rake lint # rubocop
219
- rake # test + lint
220
- bin/console # IRB with the gem loaded
107
+ bundle exec rake # tests + lint (hermetic — never spawns the real claude)
108
+ bin/console # IRB with the gem loaded
221
109
  ```
222
110
 
223
- Tests inject a fake runner at the `Client`'s runner boundary, so the suite is
224
- fully hermetic: it never makes a network call and never invokes the real
225
- `claude` binary. (A handful of `Runner` tests spawn a throwaway local `ruby`
226
- process to exercise the subprocess plumbing.)
111
+ Contributing or tracking upstream SDK changes? See
112
+ [`doc/DEVELOPMENT.md`](doc/DEVELOPMENT.md).
227
113
 
228
114
  ## Building and publishing the gem
229
115
 
@@ -233,7 +119,7 @@ Before a release, bump it following [SemVer](https://semver.org).
233
119
  ### Build locally
234
120
 
235
121
  ```bash
236
- gem build ruby_claude.gemspec # => ruby_claude-<version>.gem
122
+ gem build ruby_claude.gemspec # => ruby_claude-<version>.gem
237
123
  gem install ./ruby_claude-<version>.gem # try the built gem locally
238
124
  ```
239
125
 
@@ -270,9 +156,6 @@ rake install # build and install locally
270
156
  gem push ruby_claude-<version>.gem
271
157
  ```
272
158
 
273
- The name `ruby_claude` is currently available on RubyGems. Releasing
274
- `0.0.0` is unusual — bump to e.g. `0.1.0` for your first real publish.
275
-
276
159
  Alternatively, do it all in one step with Bundler's release task, which builds
277
160
  the gem, creates and pushes a `v<version>` git tag, and pushes to RubyGems
278
161
  (requires a clean, committed tree):
@@ -2,5 +2,5 @@
2
2
 
3
3
  module RubyClaude
4
4
  # The released version of the gem, following Semantic Versioning.
5
- VERSION = "0.0.0"
5
+ VERSION = "0.0.1"
6
6
  end
data/ruby_claude.gemspec CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |spec|
6
6
  spec.name = "ruby_claude"
7
7
  spec.version = RubyClaude::VERSION
8
8
  spec.authors = ["Kaíque Kandy Koga"]
9
- spec.email = ["kaique.koga@javln.com"]
9
+ spec.email = ["kaiquekandykoga@gmail.com"]
10
10
 
11
11
  spec.summary = "Subscription-authenticated Ruby SDK for Claude via the Claude Code CLI."
12
12
  spec.description = <<~DESC
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_claude
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaíque Kandy Koga
@@ -74,7 +74,7 @@ description: |
74
74
  credentials. Unofficial; uses a supported headless feature within the
75
75
  subscription's rate limits.
76
76
  email:
77
- - kaique.koga@javln.com
77
+ - kaiquekandykoga@gmail.com
78
78
  executables: []
79
79
  extensions: []
80
80
  extra_rdoc_files: []