From d32db8800eab80be3ab1310400a19036fe795507 Mon Sep 17 00:00:00 2001 From: Sulkta Date: Sun, 28 Jun 2026 13:12:32 -0700 Subject: [PATCH] docs: public README + generic CI workflow --- .github/workflows/ci.yml | 29 +++++++++ README.md | 129 +++++++++++++++++++++++++++++++-------- 2 files changed, 131 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7c4c0eb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: ci + +on: + push: + branches: [main] + pull_request: + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: install rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: fmt + run: cargo fmt --all --check + + - name: clippy + run: cargo clippy --all-targets -- -D warnings + + - name: build + run: cargo build --all-targets + + - name: test + run: cargo test diff --git a/README.md b/README.md index 43de744..22aa3d6 100644 --- a/README.md +++ b/README.md @@ -4,22 +4,35 @@ Async Rust SDK for the Claude Agent CLI — a faithful port of the official Python [`claude-agent-sdk`](https://github.com/anthropics/claude-agent-sdk-python). This crate wraps the `claude` CLI as a subprocess and exposes its -newline-delimited JSON stream as ergonomic Rust types. The Python SDK's -two entry points map directly: +newline-delimited JSON stream as ergonomic, strongly-typed Rust values. The +Python SDK's two entry points map directly: - `query()` for fire-and-forget one-shot prompts. -- `ClaudeSDKClient` → `claude_agent_sdk::Client` for bidirectional +- `ClaudeSDKClient` → `claude_agent_sdk::Client` for bidirectional, multi-turn sessions. +It is an independent, community-maintained port and is not affiliated with or +endorsed by Anthropic. + ## Status **v0.0.1** — initial port. Core feature parity for the subprocess wire protocol; advanced features (control protocol, hooks, in-process MCP servers, -session_store, sandbox, plugins, agents) are deferred to v0.2. See -[v0.1 scope](#v01-scope-and-known-limitations) below. +session store, sandbox, plugins, agents) are deferred. See +[scope and known limitations](#scope-and-known-limitations) below. + +## Requirements + +- Rust 1.85 or newer (the crate uses the 2024 edition). +- The `claude` CLI available on `PATH` (or supplied explicitly via + `ClaudeAgentOptions::with_cli_path()`), and an authenticated Claude + session. **The SDK does not bundle the CLI binary** — it shells out to + whatever `claude` it finds. ## Install +The crate is distributed as a git dependency: + ```toml [dependencies] claude-agent-sdk = { git = "https://git.sulkta.com/Sulkta-OSS/claude-agent-sdk-rust" } @@ -27,9 +40,8 @@ tokio = { version = "1", features = ["full"] } tokio-stream = "0.1" ``` -Prerequisites: the `claude` CLI on `PATH` (or supply -`ClaudeAgentOptions::with_cli_path()`), and an authenticated Claude -session. The SDK does not bundle the CLI binary. +Pin to a tag or commit (`rev = "..."` / `tag = "..."`) for reproducible +builds. ## Quick start @@ -67,6 +79,9 @@ async fn main() -> claude_agent_sdk::Result<()> { } ``` +`query()` returns a stream that ends when the CLI emits its terminal `result` +message, automatically tearing down the subprocess. + ## Multi-turn sessions ```rust @@ -83,7 +98,7 @@ async fn main() -> claude_agent_sdk::Result<()> { // ...consume messages until a Result frame, then ask a follow-up... client.send("And of Germany?").await?; - // Drain until next Result, then disconnect. + // Drain until the next Result, then disconnect. while let Some(msg) = messages.next().await { if let Message::Result(_) = msg? { break; @@ -94,7 +109,49 @@ async fn main() -> claude_agent_sdk::Result<()> { } ``` -See the `examples/` directory for runnable variants. +`Client` keeps a single CLI subprocess alive across turns, so follow-up +prompts don't pay the cold-start cost again. + +## Configuration + +Both entry points are configured with `ClaudeAgentOptions`, a builder you +chain `.with_*()` methods on: + +```rust +use claude_agent_sdk::{ClaudeAgentOptions, PermissionMode}; + +let opts = ClaudeAgentOptions::new() + .with_model("claude-sonnet-4-5") + .with_permission_mode(PermissionMode::AcceptEdits) + .with_allowed_tool("Read") + .with_allowed_tool("Bash") + .with_cwd("/tmp/my-project") + .with_max_turns(5); +``` + +See the `ClaudeAgentOptions` API docs for the full list of supported fields. + +## Examples + +Runnable examples live in [`examples/`](examples/). With the `claude` CLI on +`PATH` and authenticated: + +```sh +cargo run --example basic # minimal one-shot query() +cargo run --example with_options # query() with a configured options builder +cargo run --example interactive # multi-turn Client session +``` + +## Building and testing + +```sh +cargo build +cargo test # unit + end-to-end tests. A fake CLI fixture stands in + # for the real `claude` binary, so the suite needs no + # network access or authenticated session to run. +cargo clippy --all-targets +cargo fmt --check +``` ## Mapping the Python SDK @@ -114,31 +171,49 @@ See the `examples/` directory for runnable variants. | `CLIJSONDecodeError` | `Error::JsonDecode { .. }` | | `MessageParseError` | `Error::MessageParse { .. }` | -## v0.1 scope and known limitations +## Scope and known limitations -The v0.1 port covers the core path. The following are deferred: +This release covers the core path of the Python SDK: -- **Control protocol** — `interrupt()`, `set_permission_mode()`, - `set_model()`, `get_mcp_status()`, etc. The Python SDK sends a JSON-RPC - `initialize` request before the first user message and handles control - requests/responses over the same stdio pair. The Rust `Client` currently - speaks only the bare user / assistant / result frames. Adding the - control protocol unlocks the rest of the API. +- Subprocess transport with newline-delimited JSON framing. +- All message and content-block types parsed faithfully. +- `query()` + `Client` for one-shot and bidirectional use. +- The `ClaudeAgentOptions` flag surface relevant to subprocess arguments. + +The following are deferred — see the upstream Python README for the current +shape of these features: + +- **Control protocol over JSON-RPC** — `interrupt()`, `set_permission_mode()`, + `set_model()`, `get_mcp_status()`, etc. The Rust `Client` currently speaks + only the bare user / assistant / result frames. Adding the control protocol + unlocks the rest of the API. - **`can_use_tool` permission callback** — requires the control protocol. -- **In-process MCP servers (`@tool` / `create_sdk_mcp_server`)** — the - Python decorator wraps a `mcp.server.Server` instance. The Rust shape - for this (likely a derive macro on a `Tool` trait + a runtime that - multiplexes over the control protocol) is not yet drafted. -- **`HookMatcher`** — wire format is supported on the CLI side but the Rust - callback surface is not designed. +- **In-process MCP servers (`@tool` / `create_sdk_mcp_server`)** — needs a + Rust trait/derive-macro shape that isn't drafted yet. +- **`HookMatcher`** — the wire format is supported on the CLI side, but the + Rust callback surface is not designed. - **`SessionStore`** mirroring adapter. - **`Sandbox` settings**, **plugins**, **agents** dataclass. -Each of these degrades gracefully — a v0.1 caller using a newer CLI sees -fewer features, not breakage. +Each of these degrades gracefully — a caller using a newer CLI sees fewer +features, not breakage. + +## Contributing + +Contributions are welcome. A good change: + +- keeps the wire protocol faithful to the upstream Python SDK (cite the + behavior you're matching where it isn't obvious); +- comes with a test — the end-to-end suite drives a fake CLI over the real + framing, so most behavior can be covered without a live `claude` binary; +- passes `cargo fmt --check`, `cargo clippy --all-targets`, and `cargo test`. + +Open an issue to discuss larger features (especially anything from the +deferred list above) before sending a big patch. ## License MIT. See [`LICENSE`](LICENSE). -This is an independent port; the upstream Python SDK is © Anthropic, PBC. +This is an independent port; the upstream Python SDK is © Anthropic, PBC, and +the bundled `LICENSE` preserves that notice alongside this port's copyright.