clawdforge/clients/ruby
Kayos 6b8bccfb8d clients/ruby: apply audit findings (b1d6e3f -> new)
- S1: Client#inspect redacts @token (default ruby inspect walks ivars);
  to_s aliased and pretty_print overridden so PP doesn't bypass it.
- S2: AppToken#inspect/#to_s/#pretty_print redact :token member; nil
  token still rendered as token=nil for list-row clarity.
- S3: validate token name vs [a-z0-9_-]+ in revoke_token and create_token;
  drops URI.encode_www_form_component path-encoding dependency.
- C1: upload_timeout_secs parameter on upload_file (default 60), decoupled
  from default_timeout/http_timeout_margin so big uploads aren't capped
  by the run-subprocess timeout.
- Q6: clearer multipart filename escape via gsub block form.
- C7: dropped unused @uri ivar.
- A3: YARD note clarifying http_client: bypasses base_url host/port
  routing.

Test gaps closed: Client/AppToken inspect+pp redaction, AppToken nil-token
inspect, revoke_token name validation (path traversal, uppercase, empty,
valid), create_token name validation, upload_timeout_secs independence
from default_timeout (incl. default==60), Array-form ip_cidrs round-trip,
non-JSON 5xx error body kept as String, empty 200 body raises Error.

35 runs / 104 assertions / 0 failures.

Audit: memory/clawdforge-audits/ruby-b1d6e3f.md
2026-04-28 23:07:49 -07:00
..
examples clients/ruby: initial Ruby SDK for clawdforge 2026-04-28 22:34:01 -07:00
lib clients/ruby: apply audit findings (b1d6e3f -> new) 2026-04-28 23:07:49 -07:00
test clients/ruby: apply audit findings (b1d6e3f -> new) 2026-04-28 23:07:49 -07:00
.gitignore clients/ruby: initial Ruby SDK for clawdforge 2026-04-28 22:34:01 -07:00
clawdforge.gemspec clients/ruby: initial Ruby SDK for clawdforge 2026-04-28 22:34:01 -07:00
Gemfile clients/ruby: initial Ruby SDK for clawdforge 2026-04-28 22:34:01 -07:00
Rakefile clients/ruby: initial Ruby SDK for clawdforge 2026-04-28 22:34:01 -07:00
README.md clients/ruby: initial Ruby SDK for clawdforge 2026-04-28 22:34:01 -07:00

clawdforge — Ruby SDK

A small, dependency-free Ruby client for the clawdforge HTTP service.

clawdforge is a LAN-only HTTP service that wraps claude -p subprocess calls behind a bearer-token-gated REST API. This SDK is a thin idiomatic wrapper around that API.

  • Stdlib only at runtimenet/http + json. No httparty, no faraday.
  • Ruby 3.0+ — keyword args, frozen string literals.
  • Five public methodshealthz, run, upload_file, create_token, list_tokens, revoke_token. That's it.

Install

From a path checkout (typical inside the clawdforge monorepo):

# Gemfile
gem "clawdforge", path: "clients/ruby"

Or from a built gem:

cd clients/ruby
gem build clawdforge.gemspec
gem install ./clawdforge-0.1.0.gem

Quickstart

require "clawdforge"

forge = Clawdforge::Client.new(
  base_url: "http://localhost:8800",
  token:    ENV.fetch("CLAWDFORGE_TOKEN"),
)

# 1) liveness
health = forge.healthz
puts health["claude_version"]

# 2) run a prompt
result = forge.run(
  prompt:       'Reply with JSON: {"hello": "world"}',
  model:        "sonnet",        # optional, default "sonnet"
  system:       "Be terse.",     # optional
  timeout_secs: 60,              # optional, default 120, server range 5..600
)

puts result.duration_ms
puts result.stop_reason

# `result.result` is parsed JSON (Hash) when the model returned valid JSON,
# otherwise the raw String.
puts result.result["hello"]

# 3) upload a file, then attach it to a /run call
ft = forge.upload_file("./recipe.png", ttl_secs: 3600)
forge.run(prompt: "extract recipe data", files: [ft.file_token])

Public API

Clawdforge::Client.new(base_url:, token:, **opts)

keyword type default meaning
base_url: String e.g. "http://localhost:8800". Required.
token: String bearer token. Required.
default_model: String "sonnet" model used when run doesn't supply one.
default_timeout: Integer 120 timeout_secs used when run doesn't supply.
http_timeout_margin: Integer 30 added to subprocess timeout for HTTP timeout.
http_client: Net::HTTP nil inject a pre-built Net::HTTP (mostly for tests).

#healthz

Returns a Hash: {"ok" => Bool, "claude_present" => Bool, "claude_version" => String | nil}.

#run(prompt:, model: nil, system: nil, files: nil, timeout_secs: nil) -> RunResult

  • prompt (String, required, non-empty)
  • model (String, optional)
  • system (String, optional)
  • files (Array, optional) — file tokens from upload_file
  • timeout_secs (Integer, optional, server range 5..600)

RunResult is a Struct with reader methods:

  • ok (Bool)
  • result (Hash | String) — parsed JSON if the model returned valid JSON, raw String otherwise
  • duration_ms (Integer)
  • stop_reason (String | nil)

#upload_file(path, ttl_secs: 3600, filename: nil, content_type: "application/octet-stream") -> FileToken

Streams the file in 1 MiB chunks via IO.copy_stream. FileToken carries file_token, ttl_secs, size.

Admin methods (admin-bootstrap-token gated)

  • #create_token(name, ip_cidrs: nil) -> AppToken
  • #list_tokens -> Array<AppToken>
  • #revoke_token(name) -> true (raises APIError(404) if not found)

AppToken's token field holds the plaintext bearer only at create time; on list responses it is nil (the server stores only a sha256 hash).

Errors

Clawdforge::Error
├── Clawdforge::TransportError          # connection refused, DNS, TCP timeout, TLS, …
└── Clawdforge::APIError                # 4xx / 5xx, exposes #status and #body
    └── Clawdforge::AuthError           # 401 / 403

Catch Clawdforge::Error to handle the whole family. On APIError, inspect .status (Integer) and .body (Hash if JSON, String otherwise).

begin
  forge.run(prompt: "hi", timeout_secs: 5)
rescue Clawdforge::AuthError => e
  warn "bad token: #{e.message}"
rescue Clawdforge::APIError => e
  warn "server said #{e.status}: #{e.body.inspect}"
rescue Clawdforge::TransportError => e
  warn "couldn't reach forge: #{e.message}"
end

No retry logic is built in. clawdforge runs are not idempotent (they spawn claude -p), so retry policy belongs with the caller.

Development

bundle install
bundle exec rake test    # runs Minitest with WebMock
bundle exec rake build   # builds clawdforge-X.Y.Z.gem

License

MIT.