Expand description
A type-safe Claude Code CLI wrapper for Rust.
claude-wrapper provides a builder-pattern interface for invoking the
claude CLI programmatically. Each subcommand is a typed builder that
produces typed output. The design follows the same shape as
docker-wrapper and
terraform-wrapper.
§Feature flags
| Feature | Default | Purpose |
|---|---|---|
async | yes | tokio-backed async API. Disabling drops tokio from the runtime dep tree. |
json | yes | JSON output parsing (QueryCommand::execute_json, streaming::StreamEvent, session::Session, streaming::stream_query). |
tempfile | yes | TempMcpConfig for one-shot MCP config files. |
sync | no | Blocking API: *_sync methods on exec, retry, every command builder, and Claude. |
Sync-only (tokio-free) build:
claude-wrapper = { version = "0.6", default-features = false, features = ["json", "sync"] }§Quick start (async)
use claude_wrapper::{Claude, ClaudeCommand, QueryCommand};
let claude = Claude::builder().build()?;
let output = QueryCommand::new("explain this error: file not found")
.model("sonnet")
.execute(&claude)
.await?;
println!("{}", output.stdout);§Quick start (sync)
Enable the sync feature and bring ClaudeCommandSyncExt into scope:
use claude_wrapper::{Claude, ClaudeCommandSyncExt, QueryCommand};
let claude = Claude::builder().build()?;
let output = QueryCommand::new("explain this error")
.execute_sync(&claude)?;
println!("{}", output.stdout);§Two-layer builder
The Claude client holds shared config (binary path, env, timeout,
default retry policy). Command builders hold per-invocation options
and call execute(&claude) (or execute_sync).
use claude_wrapper::{Claude, ClaudeCommand, Effort, PermissionMode, QueryCommand};
let claude = Claude::builder()
.env("AWS_REGION", "us-west-2")
.timeout_secs(300)
.build()?;
let output = QueryCommand::new("review src/main.rs")
.model("opus")
.system_prompt("You are a senior Rust developer")
.permission_mode(PermissionMode::Plan)
.effort(Effort::High)
.max_turns(5)
.no_session_persistence()
.execute(&claude)
.await?;§JSON output
use claude_wrapper::{Claude, QueryCommand};
let claude = Claude::builder().build()?;
let result = QueryCommand::new("what is 2+2?")
.execute_json(&claude)
.await?;
println!("answer: {}", result.result);
println!("cost: ${:.4}", result.cost_usd.unwrap_or(0.0));§Multi-turn conversations
Two shapes for multi-turn work, each suited to a different
process model. DuplexSession is the recommended choice for
long-running hosts; Session is the right fit for short-lived
processes.
DuplexSession | Session | |
|---|---|---|
| Process model | one child held open across turns | new subprocess per turn, --resume continuity |
| Mid-turn interrupt | yes (DuplexSession::interrupt) | no (only child.kill() via SIGKILL) |
| Mid-turn permission prompts | yes (PermissionHandler) | no |
| Broadcast event subscribers | yes (DuplexSession::subscribe) | no (per-turn stream_query) |
| Built-in cost / history tracking | no (TurnResult is per-turn) | yes (Session::total_cost_usd, Session::history, BudgetTracker) |
| Right for | long-running hosts (IDE backends, daemons, agent servers, chat UIs) | short-lived processes (CLIs, build scripts, batch jobs, lambdas) |
§DuplexSession (recommended for long-running hosts)
use claude_wrapper::Claude;
use claude_wrapper::duplex::{DuplexOptions, DuplexSession};
let claude = Claude::builder().build()?;
let session = DuplexSession::spawn(
&claude,
DuplexOptions::default().model("haiku"),
).await?;
let turn = session.send("what's 2 + 2?").await?;
println!("answer: {}", turn.result_text().unwrap_or(""));
session.close().await?;See the duplex module docs for the full API including
subscribe, interrupt, and respond_to_permission.
For host-side bookkeeping (history, cumulative cost, optional
BudgetTracker hard stop) on top of a DuplexSession, wrap
it in a Conversation. See the
conversation module docs.
§Session (for short-lived processes)
use std::sync::Arc;
use claude_wrapper::Claude;
use claude_wrapper::session::Session;
let claude = Arc::new(Claude::builder().build()?);
let mut session = Session::new(claude);
let _first = session.send("what's 2 + 2?").await?;
let _second = session.send("and squared?").await?;
println!("cost: ${:.4}", session.total_cost_usd());See the session module docs for the full API.
§Budget tracking
Attach a BudgetTracker to a session (or share one across several
sessions) to enforce a cumulative USD ceiling. Callbacks fire
exactly once when thresholds are crossed; pre-turn checks
short-circuit with Error::BudgetExceeded
once the ceiling is hit.
use std::sync::Arc;
use claude_wrapper::{BudgetTracker, Claude};
use claude_wrapper::session::Session;
let budget = BudgetTracker::builder()
.max_usd(5.00)
.warn_at_usd(4.00)
.on_warning(|t| eprintln!("warning: ${t:.2}"))
.on_exceeded(|t| eprintln!("budget hit: ${t:.2}"))
.build();
let claude = Arc::new(Claude::builder().build()?);
let mut session = Session::new(claude).with_budget(budget.clone());
session.send("hello").await?;
println!("spent: ${:.4}", budget.total_usd());§Tool permissions
Use ToolPattern for typed --allowed-tools / --disallowed-tools
entries. Typed constructors always produce valid patterns; loose
From<&str> keeps bare strings working for back-compat.
use claude_wrapper::{QueryCommand, ToolPattern};
let cmd = QueryCommand::new("review")
.allowed_tool(ToolPattern::tool("Read"))
.allowed_tool(ToolPattern::tool_with_args("Bash", "git log:*"))
.allowed_tool(ToolPattern::all("Write"))
.allowed_tool(ToolPattern::mcp("my-server", "*"))
.disallowed_tool(ToolPattern::tool_with_args("Bash", "rm*"));§Streaming
Process NDJSON events in real time with streaming::stream_query
(async) or streaming::stream_query_sync (blocking; non-Send
handler supported).
use claude_wrapper::{Claude, OutputFormat, QueryCommand};
use claude_wrapper::streaming::{StreamEvent, stream_query};
let claude = Claude::builder().build()?;
let cmd = QueryCommand::new("explain quicksort")
.output_format(OutputFormat::StreamJson);
stream_query(&claude, &cmd, |event: StreamEvent| {
if event.is_result() {
println!("result: {}", event.result_text().unwrap_or(""));
}
}).await?;§MCP config generation
Generate .mcp.json files for --mcp-config:
use claude_wrapper::{Claude, ClaudeCommand, McpConfigBuilder, QueryCommand};
McpConfigBuilder::new()
.http_server("hub", "http://127.0.0.1:9090")
.stdio_server("tool", "npx", ["my-server"])
.write_to("/tmp/my-project/.mcp.json")?;
let claude = Claude::builder().build()?;
QueryCommand::new("list tools")
.mcp_config("/tmp/my-project/.mcp.json")
.execute(&claude)
.await?;§Dangerous: bypass mode
--permission-mode bypassPermissions is isolated behind
dangerous::DangerousClient, which requires an env-var
acknowledgement (dangerous::ALLOW_ENV = "1") at process start.
See the dangerous module docs for details.
§Escape hatch
For subcommands not yet wrapped, use RawCommand:
use claude_wrapper::{Claude, ClaudeCommand, RawCommand};
let claude = Claude::builder().build()?;
let output = RawCommand::new("some-future-command")
.arg("--new-flag")
.arg("value")
.execute(&claude)
.await?;Re-exports§
pub use budget::BudgetBuilder;pub use budget::BudgetTracker;pub use command::ClaudeCommand;pub use command::ClaudeCommandSyncExt;pub use command::agents::AgentsCommand;pub use command::auth::AuthLoginCommand;pub use command::auth::AuthLogoutCommand;pub use command::auth::AuthStatusCommand;pub use command::auth::SetupTokenCommand;pub use command::auto_mode::AutoModeConfigCommand;pub use command::auto_mode::AutoModeCritiqueCommand;pub use command::auto_mode::AutoModeDefaultsCommand;pub use command::doctor::DoctorCommand;pub use command::install::InstallCommand;pub use command::marketplace::MarketplaceAddCommand;pub use command::marketplace::MarketplaceListCommand;pub use command::marketplace::MarketplaceRemoveCommand;pub use command::marketplace::MarketplaceUpdateCommand;pub use command::mcp::McpAddCommand;pub use command::mcp::McpAddFromDesktopCommand;pub use command::mcp::McpAddJsonCommand;pub use command::mcp::McpGetCommand;pub use command::mcp::McpListCommand;pub use command::mcp::McpRemoveCommand;pub use command::mcp::McpResetProjectChoicesCommand;pub use command::mcp::McpServeCommand;pub use command::plugin::PluginDisableCommand;pub use command::plugin::PluginEnableCommand;pub use command::plugin::PluginInstallCommand;pub use command::plugin::PluginListCommand;pub use command::plugin::PluginTagCommand;pub use command::plugin::PluginUninstallCommand;pub use command::plugin::PluginUpdateCommand;pub use command::plugin::PluginValidateCommand;pub use command::query::QueryCommand;pub use command::raw::RawCommand;pub use command::update::UpdateCommand;pub use command::version::VersionCommand;pub use conversation::Conversation;pub use duplex::DuplexOptions;pub use duplex::DuplexSession;pub use duplex::InboundEvent;pub use duplex::PermissionDecision;pub use duplex::PermissionHandler;pub use duplex::PermissionRequest;pub use duplex::TurnResult;pub use error::Error;pub use error::Result;pub use exec::CommandOutput;pub use mcp_config::TempMcpConfig;pub use mcp_config::McpConfigBuilder;pub use mcp_config::McpServerConfig;pub use retry::BackoffStrategy;pub use retry::RetryPolicy;pub use session::Session;pub use tool_pattern::PatternError;pub use tool_pattern::ToolPattern;pub use version::CliVersion;pub use version::VersionParseError;pub use types::*;
Modules§
- budget
- Cumulative USD budget tracking for Claude sessions.
- command
- conversation
- Host-side bookkeeping wrapper around
DuplexSession. - dangerous
- Opt-in dangerous operations. Currently: bypass permissions.
- duplex
- Long-lived duplex stream-json sessions.
- error
- exec
- mcp_
config - retry
- session
- Multi-turn session management for short-lived processes.
- streaming
- tool_
pattern - Tool permission patterns for
--allowed-tools/--disallowed-tools. - types
- version
Structs§
- Claude
- The Claude CLI client. Holds shared configuration applied to all commands.
- Claude
Builder - Builder for creating a
Claudeclient.