An Elixir SDK for programmatically interacting with Claude Code. This library provides a simple interface to query Claude and handle responses using the familiar Elixir streaming patterns.
graph TB
subgraph "Your Elixir Application"
A[ClaudeCodeSDK] --> B[Process Manager]
B --> C[Message Parser]
B --> D[Auth Checker]
end
subgraph "Claude Code CLI"
E[claude-code executable]
E --> F[API Communication]
end
subgraph "Claude API"
G[Claude Service]
end
A -->|spawn & control| E
E -->|HTTPS| G
G -->|Responses| E
E -->|JSON stream| B
C -->|Parsed Messages| A
style A fill:#4a9eff,stroke:#2d7dd2,stroke-width:2px,color:#000
style G fill:#ff6b6b,stroke:#ff4757,stroke-width:2px,color:#000
This SDK requires the Claude Code CLI to be installed:
npm install -g @anthropic-ai/claude-code
Add claude_code_sdk
to your list of dependencies in mix.exs
:
def deps do
[
{:claude_code_sdk, "~> 0.0.1"}
]
end
Then run:
mix deps.get
-
Authenticate the CLI (do this once):
claude login
-
Install dependencies:
mix deps.get
-
Run the showcase:
# Safe demo with mocks (no API costs) mix showcase # Live demo with real API calls (requires authentication) mix showcase --live
-
Try the live script runner:
# Run example scripts with live API calls mix run.live examples/basic_example.exs mix run.live examples/simple_analyzer.exs lib/claude_code_sdk.ex
- Core SDK Functions:
query/2
,continue/2
,resume/3
with stdin support - Live Script Runner:
mix run.live
for executing scripts with real API calls - Message Processing: Structured message types with proper parsing
- Options Configuration: Full CLI argument mapping with smart presets and correct CLI formats
- Subprocess Management: Robust erlexec integration with stdin support
- JSON Parsing: Custom parser without external dependencies
- Authentication: CLI delegation with status checking and diagnostics
- Error Handling: Improved error detection and timeout handling
- Stream Processing: Lazy evaluation with Elixir Streams
- Real-time Streaming: New async mode for true real-time message streaming
- Mocking System: Comprehensive testing without API calls (supports stdin workflows)
- Code Quality: Full dialyzer and credo compliance with refactored complex functions
- Developer Tools: ContentExtractor, AuthChecker, OptionBuilder, DebugMode
- Smart Configuration: Environment-aware defaults and preset configurations
- Advanced Error Handling: Retry logic, timeout handling, comprehensive error recovery
- Performance Optimization: Caching, parallel processing, memory optimization
- Integration Patterns: Phoenix LiveView, OTP applications, worker pools
- Security Features: Input validation, permission management, sandboxing
- Developer Tools: Debug mode, troubleshooting helpers, session management
- Advanced Examples: Code analysis pipelines, test generators, refactoring tools
- MCP Support: Model Context Protocol integration and tool management
# Simple query with smart content extraction
alias ClaudeCodeSDK.{ContentExtractor, OptionBuilder}
# Use preset development options
options = OptionBuilder.build_development_options()
ClaudeCodeSDK.query("Say exactly: Hello from Elixir!", options)
|> Enum.each(fn msg ->
case msg.type do
:assistant ->
content = ContentExtractor.extract_text(msg)
IO.puts("🤖 Claude: #{content}")
:result ->
if msg.subtype == :success do
IO.puts("✅ Success! Cost: $#{msg.data.total_cost_usd}")
end
end
end)
The SDK includes a comprehensive mocking system for testing without making actual API calls.
# Run tests with mocks (default)
mix test
# Run tests with live API calls
MIX_ENV=test mix test.live
# Run specific test with live API
MIX_ENV=test mix test.live test/specific_test.exs
# Enable mocking
Application.put_env(:claude_code_sdk, :use_mock, true)
# Start the mock server
{:ok, _} = ClaudeCodeSDK.Mock.start_link()
# Set a mock response
ClaudeCodeSDK.Mock.set_response("hello", [
%{
"type" => "assistant",
"message" => %{"content" => "Hello from mock!"}
}
])
# Query will return mock response
ClaudeCodeSDK.query("say hello") |> Enum.to_list()
Run the included demo to see mocking in action:
mix run demo_mock.exs
For detailed documentation about the mocking system, see MOCKING.md.
# Safe demo with mocks (no API costs)
mix showcase
# Live demo with real API calls (requires authentication)
mix showcase --live
mix run final_test.exs
- Complete test showing message parsing and interactionmix run example.exs
- Basic usage examplemix run demo_mock.exs
- Mock system demonstrationmix run test_full.exs
- Alternative test formatmix run test_mix.exs
- Basic erlexec functionality test
🌟 Start with mix showcase
for a complete overview of all features!
The SDK includes a powerful mix run.live
task for executing Elixir scripts with live Claude API calls:
# Run any .exs script with live API
mix run.live script.exs [args...]
# Examples
mix run.live examples/basic_example.exs
mix run.live examples/simple_analyzer.exs lib/claude_code_sdk.ex
mix run.live examples/file_reviewer.exs path/to/your/file.txt
- 🔴 Live API Integration: Makes real Claude API calls with proper stdin handling
⚠️ Cost Warnings: Clear warnings about API usage and costs- 📄 Argument Passing: Supports passing arguments to scripts
- 🛡️ Safe by Default: Requires explicit live mode activation
- 🎭 Mock Fallback: Scripts can still run in mock mode during development
Command | API Calls | Costs | Authentication Required |
---|---|---|---|
mix run script.exs |
None (mock mode) | $0.00 | No |
mix run.live script.exs |
Real API calls | Real costs | Yes (claude login ) |
The SDK includes several example scripts you can run immediately:
# Basic factorial function generation
mix run.live examples/basic_example.exs
# Code analysis with file input
mix run.live examples/simple_analyzer.exs lib/claude_code_sdk.ex
# Simple batch processing
mix run.live examples/simple_batch.exs
# File review and analysis
mix run.live examples/file_reviewer.exs README.md
Create scripts that automatically work in both mock and live modes:
#!/usr/bin/env elixir
# Check if we're in live mode
if Application.get_env(:claude_code_sdk, :use_mock, false) do
{:ok, _} = ClaudeCodeSDK.Mock.start_link()
IO.puts("🎭 Mock mode enabled")
else
IO.puts("🔴 Live mode enabled")
end
# Your script logic here...
response = ClaudeCodeSDK.query("Your prompt here")
|> extract_response()
IO.puts("Response: #{response}")
All examples and tests can run in two modes:
Mode | Command Format | API Calls | Costs | Authentication Required |
---|---|---|---|---|
Mock | mix showcase |
None (mocked) | $0.00 | No |
Live | mix showcase --live |
Real API calls | Real costs | Yes (claude login ) |
The showcase demonstrates all SDK functionality:
Feature Demonstrated | What It Shows |
---|---|
OptionBuilder | Smart configuration presets for development, production, chat, analysis |
AuthChecker | Environment validation and authentication diagnostics |
Basic SDK Usage | Core query functionality with mocked/real responses |
ContentExtractor | Easy text extraction from complex message formats |
DebugMode | Message analysis, benchmarking, troubleshooting tools |
Mock System | Complete testing infrastructure without API costs |
Advanced Configurations | Real-world scenarios for different use cases |
Performance Features | Benchmarking and timing analysis |
Command | Status | Notes |
---|---|---|
mix showcase |
✅ Working | Mock mode, fast, no costs |
mix showcase --live |
✅ Working | Live mode, real API calls, no hanging |
mix test |
✅ Working | Mock mode, 75 tests, 17 skipped |
mix test.live |
✅ Working | Live mode, properly warns about costs |
mix run example.exs |
✅ Working | Uses mock mode by default, auto-starts Mock |
mix run examples/simple_analyzer.exs |
✅ Working | Uses mock mode by default |
mix run.live examples/basic_example.exs |
✅ Working | Live mode, real API calls, stdin support |
mix run.live examples/simple_analyzer.exs |
✅ Working | Live mode, file analysis with arguments |
Runs a query against Claude Code and returns a stream of messages.
# Simple query
ClaudeCodeSDK.query("Write a hello world function")
|> Enum.to_list()
# With options
options = %ClaudeCodeSDK.Options{max_turns: 5, verbose: true}
ClaudeCodeSDK.query("Complex task", options)
|> Enum.to_list()
Continues the most recent conversation.
ClaudeCodeSDK.continue("Now add error handling")
|> Enum.to_list()
Resumes a specific conversation by session ID.
ClaudeCodeSDK.resume("session-id-here", "Add tests")
|> Enum.to_list()
Configure requests with ClaudeCodeSDK.Options
or use smart presets:
# Manual configuration
%ClaudeCodeSDK.Options{
max_turns: 10, # Maximum conversation turns
system_prompt: "Custom...", # Override system prompt
output_format: :stream_json,# Output format
verbose: true, # Enable verbose logging
cwd: "/path/to/project", # Working directory
async_streaming: true # Use real-time streaming (default: true)
}
# Smart presets with OptionBuilder
alias ClaudeCodeSDK.OptionBuilder
# Development: permissive settings, verbose logging
options = OptionBuilder.build_development_options()
# Production: restricted settings, minimal tools
options = OptionBuilder.build_production_options()
# Analysis: read-only tools for code analysis
options = OptionBuilder.build_analysis_options()
# Chat: simple conversations
options = OptionBuilder.build_chat_options()
# Auto-detect based on Mix.env()
options = OptionBuilder.for_environment()
# Custom combinations
options = OptionBuilder.merge(:development, %{max_turns: 5})
The SDK returns a stream of ClaudeCodeSDK.Message
structs with these types:
:system
- Session initialization (session_id, model, tools):user
- User messages:assistant
- Claude's responses:result
- Final result with cost/duration stats
Use the built-in ContentExtractor
for easy message processing:
alias ClaudeCodeSDK.ContentExtractor
# Extract all assistant responses
content = ClaudeCodeSDK.query("Your prompt")
|> Stream.filter(fn msg -> msg.type == :assistant end)
|> Stream.map(&ContentExtractor.extract_text/1)
|> Enum.join("\n")
# Check if message has text content
if ContentExtractor.has_text?(message) do
text = ContentExtractor.extract_text(message)
IO.puts("Response: #{text}")
end
This SDK uses your already-authenticated Claude CLI instance. No API keys needed - just run claude login
once and the SDK uses the stored session.
Use AuthChecker
to verify your setup before making queries:
alias ClaudeCodeSDK.AuthChecker
# Quick boolean check
if AuthChecker.authenticated?() do
# Proceed with queries
ClaudeCodeSDK.query("Hello!")
else
IO.puts("Please run: claude login")
end
# Full diagnostic check
diagnosis = AuthChecker.diagnose()
# Returns: %{
# cli_installed: true,
# authenticated: true,
# status: :ready,
# recommendations: []
# }
# Ensure ready or raise error
AuthChecker.ensure_ready!()
ClaudeCodeSDK.query("prompt")
|> Enum.each(fn msg ->
case msg do
%{type: :result, subtype: :success} ->
IO.puts("✅ Success!")
%{type: :result, subtype: error_type} when error_type in [:error_max_turns, :error_during_execution] ->
IO.puts("❌ Error: #{error_type}")
_ ->
# Process other message types
end
end)
The SDK works by:
- Spawning the Claude CLI as a subprocess using
erlexec
- Communicating via JSON messages over stdout/stderr
- Parsing responses into Elixir structs
- Returning lazy Streams for efficient processing
Key benefits:
- ✅ Uses existing CLI authentication
- ✅ Efficient streaming processing
- ✅ Real-time message streaming with async mode
- ✅ No external JSON dependencies
- ✅ Robust subprocess management with erlexec
The SDK offers two streaming modes:
-
Synchronous Mode (
Process
module) - Default whenasync_streaming: false
- Collects all output before parsing
- More reliable for batch processing
- Better error handling for malformed JSON
- Simpler debugging
-
Asynchronous Mode (
ProcessAsync
module) - Default whenasync_streaming: true
- Real-time message streaming as they arrive
- Lower latency for interactive applications
- True streaming experience
- Better for long-running queries
Configure via options:
# Use sync mode (more reliable)
ClaudeCodeSDK.query("prompt", %{async_streaming: false})
# Use async mode (real-time, default)
ClaudeCodeSDK.query("prompt", %{async_streaming: true})
Module not available error: Run with mix run
instead of plain elixir
:
# ❌ Won't work
elixir final_test.exs
# ✅ Works
mix run final_test.exs
Authentication errors: Make sure Claude CLI is authenticated:
claude login
Process errors: Ensure Claude CLI is installed:
npm install -g @anthropic-ai/claude-code
CLI argument format errors: Recent improvements have fixed common CLI format issues:
- Output format: Now correctly uses
stream-json
instead ofstream_json
- Permission modes: Now correctly uses
acceptEdits
instead ofaccept_edits
- These fixes ensure compatibility with the latest Claude CLI versions
Live mode not working: Make sure you're using mix run.live
for live API calls:
# ❌ Won't make live API calls
mix run examples/basic_example.exs
# ✅ Makes live API calls
mix run.live examples/basic_example.exs
Use DebugMode
for detailed troubleshooting:
alias ClaudeCodeSDK.DebugMode
# Run full diagnostics
DebugMode.run_diagnostics()
# Debug a specific query with timing
messages = DebugMode.debug_query("Hello")
# Benchmark performance
results = DebugMode.benchmark("Test query", nil, 3)
# Returns timing and cost statistics
# Analyze message statistics
stats = DebugMode.analyze_messages(messages)
The SDK includes four powerful modules to enhance your development experience:
Pre-configured option sets for common use cases:
build_development_options()
- Permissive settings for dev workbuild_production_options()
- Secure settings for productionbuild_analysis_options()
- Read-only tools for code analysisbuild_chat_options()
- Simple conversation settingsfor_environment()
- Auto-detects based on Mix.env()merge/2
- Combine presets with custom options
Prevents authentication errors with proactive checking:
authenticated?/0
- Quick boolean checkdiagnose/0
- Full diagnostic with recommendationsensure_ready!/0
- Raises if not ready for queries- Helpful error messages and setup instructions
Simplifies extracting text from complex message formats:
extract_text/1
- Get text from any message typehas_text?/1
- Check if message contains text content- Handles strings, arrays, tool responses gracefully
- No more manual message parsing
Comprehensive debugging and performance analysis:
debug_query/2
- Execute queries with detailed loggingrun_diagnostics/0
- Full environment health checkbenchmark/3
- Performance testing with statisticsanalyze_messages/1
- Extract insights from message streams
# Analyze code quality and security with smart configuration
alias ClaudeCodeSDK.{OptionBuilder, ContentExtractor}
# Use analysis-specific options (read-only tools)
options = OptionBuilder.build_analysis_options()
analysis_result = ClaudeCodeSDK.query("""
Review this code for security vulnerabilities and performance issues:
#{File.read!("lib/user_auth.ex")}
""", options)
|> Stream.filter(&(&1.type == :assistant))
|> Stream.map(&ContentExtractor.extract_text/1)
|> Enum.join("\n")
IO.puts("📊 Analysis Result:\n#{analysis_result}")
# Generate API documentation - FUTURE/PLANNED
ClaudeCodeSDK.query("Generate comprehensive docs for this module: #{file_content}")
|> Enum.filter(&(&1.type == :assistant))
|> Enum.map(&extract_content/1) # extract_content helper not yet implemented
# Create test suites automatically - FUTURE/PLANNED
options = %ClaudeCodeSDK.Options{max_turns: 5}
ClaudeCodeSDK.query("Generate ExUnit tests for this module", options)
# Multi-step refactoring with session management - FUTURE/PLANNED
session_id = start_refactoring_session("lib/legacy_code.ex") # Not yet implemented
ClaudeCodeSDK.resume(session_id, "Now optimize for performance")
ClaudeCodeSDK.resume(session_id, "Add proper error handling")
# Pair programming sessions - FUTURE/PLANNED
ClaudeCodeSDK.query("I'm working on a GenServer. Help me implement proper state management")
|> Stream.each(&IO.puts(extract_content(&1))) # extract_content helper not yet implemented
|> Stream.run()
# Generate boilerplate code - FUTURE/PLANNED
ClaudeCodeSDK.query("""
Create a Phoenix LiveView component for user authentication with:
- Login/logout functionality
- Session management
- Form validation
""")
The SDK supports different configurations for different environments:
- Test Environment: Mocks enabled by default (
config/test.exs
) - Development Environment: Real API calls (
config/dev.exs
) - Production Environment: Real API calls (
config/prod.exs
)
defmodule MyAppTest do
use ExUnit.Case
alias ClaudeCodeSDK.Mock
setup do
# Clear any existing mock responses
Mock.clear_responses()
:ok
end
test "my feature works correctly" do
# Set up mock response
Mock.set_response("analyze", [
%{
"type" => "assistant",
"message" => %{"content" => "Analysis complete: No issues found."}
}
])
# Your code that uses ClaudeCodeSDK
result = MyApp.analyze_code("def hello, do: :world")
# Assertions
assert result == "Analysis complete: No issues found."
end
end
For detailed documentation covering all features, advanced patterns, and integration examples, see:
The comprehensive manual includes:
- 🏗️ Architecture Deep Dive - Internal workings and design patterns ✅ IMPLEMENTED
- ⚙️ Advanced Configuration - MCP support, security, performance tuning (FUTURE/PLANNED)
- 🔧 Integration Patterns - Phoenix LiveView, OTP applications, task pipelines (FUTURE/PLANNED)
- 🛡️ Security & Best Practices - Input validation, permission management (FUTURE/PLANNED)
- 🐛 Troubleshooting Guide - Common issues and debugging techniques (FUTURE/PLANNED)
- 💡 Real-World Examples - Code analysis, test generation, refactoring tools (FUTURE/PLANNED)
MIT License