claude-wrapper 0.2.1

A type-safe Claude Code CLI wrapper for Rust
Documentation

claude-wrapper

A type-safe Claude Code CLI wrapper for Rust

Crates.io Documentation CI License

Overview

claude-wrapper provides a type-safe builder-pattern interface for invoking the claude CLI programmatically. It follows the same design philosophy as docker-wrapper and terraform-wrapper: each CLI subcommand is a builder struct that produces typed output.

Perfect for Rust applications that need to integrate with Claude Code CLI.

Installation

cargo add claude-wrapper

Quick Start

use claude_wrapper::{Claude, QueryCommand};

#[tokio::main]
async fn main() -> claude_wrapper::Result<()> {
    let claude = Claude::builder().build()?;
    let output = QueryCommand::new("explain this error")
        .model("sonnet")
        .execute(&claude)
        .await?;
    println!("{}", output.stdout);
    Ok(())
}

Two-Layer Builder Architecture

The Claude client holds shared configuration (binary path, environment, timeout). Command builders hold per-invocation options and call execute(&claude).

Claude Client

Configure once, reuse across commands:

let claude = Claude::builder()
    .env("AWS_REGION", "us-west-2")
    .timeout_secs(300)
    .build()?;

Options:

  • binary_path() - Path to claude binary (auto-detected by default)
  • working_dir() - Working directory for commands
  • env() - Set environment variables
  • timeout_secs() - Command timeout (default: 300)
  • global_args() - Additional global args

Command Builders

Each command is a separate builder. Available commands:

  • QueryCommand - Execute queries with full option coverage (28 options)
  • McpListCommand - List MCP servers
  • McpAddCommand - Add HTTP or stdio MCP server
  • McpAddJsonCommand - Add server from JSON config
  • McpRemoveCommand - Remove MCP server
  • McpAddFromDesktopCommand - Add desktop MCP server
  • McpResetProjectChoicesCommand - Reset project-specific choices
  • PluginListCommand - List plugins
  • PluginInstallCommand - Install plugin
  • PluginUninstallCommand - Uninstall plugin
  • PluginEnableCommand - Enable plugin
  • PluginDisableCommand - Disable plugin
  • PluginUpdateCommand - Update plugin
  • PluginValidateCommand - Validate plugin
  • AuthStatusCommand - Check authentication status
  • VersionCommand - Get CLI version
  • DoctorCommand - Run health check
  • AgentsListCommand - List available agents
  • RawCommand - Escape hatch for unsupported options

QueryCommand: The Workhorse

Full coverage of claude -p (print mode) options.

All QueryCommand Options

Method CLI Flag Type Description
model() --model &str Model alias or full ID
system_prompt() --system-prompt &str Replace default system prompt
append_system_prompt() --append-system-prompt &str Append to system prompt
output_format() --output-format OutputFormat text, json, stream-json
max_budget_usd() --max-budget-usd f64 Spending cap
permission_mode() --permission-mode PermissionMode default, acceptEdits, bypassPermissions, plan, auto
allowed_tools() --allowed-tools &[&str] Tool permission allow list
disallowed_tools() --disallowed-tools &[&str] Tool permission deny list
tools() --tools &[&str] Restrict available tools
mcp_config() --mcp-config &str MCP server config file
strict_mcp_config() --strict-mcp-config - Only use MCP from config
add_dir() --add-dir &str Additional accessible directories
effort() --effort Effort low, medium, high
max_turns() --max-turns u32 Conversation turn limit
json_schema() --json-schema &str Structured output validation
agent() --agent &str Agent for session
agents_json() --agents &str Custom agents JSON
continue_session() --continue - Resume most recent session
resume() --resume &str Resume by session ID
session_id() --session-id &str Use specific session ID
fork_session() --fork-session - Fork when resuming
fallback_model() --fallback-model &str Fallback model
no_session_persistence() --no-session-persistence - Don't save session
dangerously_skip_permissions() --dangerously-skip-permissions - Bypass permissions
file() --file &str File resources to download
input_format() --input-format InputFormat text or stream-json
include_partial_messages() --include-partial-messages - Partial chunks
settings() --settings &str Settings JSON file

Usage Examples

Simple query with model override:

let output = QueryCommand::new("explain this error: file not found")
    .model("sonnet")
    .execute(&claude)
    .await?;
println!("{}", output.stdout);

JSON output with schema validation:

let output = QueryCommand::new("generate a user struct")
    .output_format(OutputFormat::Json)
    .json_schema(r#"{"type":"object","properties":{"id":{"type":"integer"}}}"#)
    .execute(&claude)
    .await?;

With permissions and tools:

let output = QueryCommand::new("implement the feature in TASK.md")
    .model("opus")
    .permission_mode(PermissionMode::Plan)
    .allowed_tools(["Bash", "Read", "Edit", "Write"])
    .max_budget_usd(1.0)
    .execute(&claude)
    .await?;

Session management:

// Start new session
let output = QueryCommand::new("analyze the codebase")
    .session_id("my-session")
    .execute(&claude)
    .await?;

// Resume later
let output = QueryCommand::new("what did we find?")
    .resume("my-session")
    .execute(&claude)
    .await?;

Streaming NDJSON Events

For real-time output, use stream_query():

use claude_wrapper::streaming::stream_query;

let output = stream_query(&claude, &cmd, |event| {
    if event.is_result() {
        println!("Result: {}", event.result_text().unwrap_or(""));
    }
    if event.is_error() {
        eprintln!("Error: {}", event.error().unwrap_or(""));
    }
}).await?;

MCP Server Commands

List Servers

let output = McpListCommand::new()
    .execute(&claude)
    .await?;
println!("{}", output.stdout);

Add HTTP Server

McpAddCommand::new("sentry", "https://mcp.sentry.dev/mcp")
    .transport("http")
    .execute(&claude)
    .await?;

Add Stdio Server

McpAddCommand::new("my-tool", "npx")
    .server_args(["my-mcp-server"])
    .env("API_KEY", "secret")
    .execute(&claude)
    .await?;

Add from JSON

McpAddJsonCommand::new("config.json")
    .execute(&claude)
    .await?;

Remove Server

McpRemoveCommand::new("old-server")
    .execute(&claude)
    .await?;

MCP Config Builder

Generate .mcp.json files programmatically:

use claude_wrapper::McpConfigBuilder;

McpConfigBuilder::new()
    .http_server("hub", "http://127.0.0.1:9090")
    .stdio_server("tool", "npx", ["my-server"])
    .write_to("/tmp/my-project/.mcp.json")?;

Also supports:

  • env() - Environment variables for servers
  • environment_file() - Load from env file

Plugin Management

// List plugins
PluginListCommand::new().execute(&claude).await?;

// Install plugin
PluginInstallCommand::new("my-plugin")
    .execute(&claude)
    .await?;

// Enable plugin
PluginEnableCommand::new("my-plugin")
    .execute(&claude)
    .await?;

// Update plugin
PluginUpdateCommand::new("my-plugin")
    .execute(&claude)
    .await?;

Other Commands

// Check auth
AuthStatusCommand::new().execute(&claude).await?;

// Get version
VersionCommand::new().execute(&claude).await?;

// Health check
DoctorCommand::new().execute(&claude).await?;

// List agents
AgentsListCommand::new().execute(&claude).await?;

Escape Hatch: RawCommand

For unsupported options, use RawCommand:

let output = RawCommand::new()
    .arg("custom-subcommand")
    .arg("--unsupported-flag")
    .arg("value")
    .execute(&claude)
    .await?;

Error Handling

All commands return Result<T>, where errors are typed with thiserror:

use claude_wrapper::Error;

match QueryCommand::new("test").execute(&claude).await {
    Ok(output) => println!("{}", output.stdout),
    Err(Error::ProcessError(e)) => eprintln!("Process failed: {}", e),
    Err(Error::InvalidUtf8) => eprintln!("Output not valid UTF-8"),
    Err(e) => eprintln!("Other error: {}", e),
}

Features

Optional Cargo features (all enabled by default):

  • json - JSON output parsing via serde_json
  • tempfile - Temporary file support for MCP config generation

Testing

Requires the claude CLI binary to be installed:

cargo test --lib --all-features
cargo test --test integration -- --ignored  # requires `claude` binary

License

MIT OR Apache-2.0