diff --git a/COMPREHENSIVE_MANUAL.md b/COMPREHENSIVE_MANUAL.md index c74efbe..79abcbb 100755 --- a/COMPREHENSIVE_MANUAL.md +++ b/COMPREHENSIVE_MANUAL.md @@ -25,38 +25,26 @@ The Claude Code SDK for Elixir provides a native Elixir interface to Claude Code ## Implementation Status -### ✅ **Currently Implemented (Working)** -- **Core API**: `ClaudeCodeSDK.query/2`, `continue/2`, `resume/3` functions -- **Message System**: Complete message parsing with types `:system`, `:user`, `:assistant`, `:result` -- **Options Configuration**: Full `ClaudeCodeSDK.Options` struct with CLI argument mapping -- **Process Management**: Robust subprocess handling using erlexec -- **JSON Processing**: Custom JSON parser (`ClaudeCodeSDK.JSON`) without external dependencies -- **Authentication**: Seamless CLI authentication delegation -- **Stream Processing**: Efficient lazy evaluation with Elixir Streams -- **Error Detection**: Basic error handling for authentication and execution failures -- **Architecture Documentation**: Complete technical documentation - -### 🔮 **Planned Features (Not Yet Implemented)** -All sections marked with **(FUTURE/PLANNED)** represent planned functionality including: -- **Advanced Error Handling**: Retry mechanisms, timeout handling, comprehensive error recovery -- **Performance Features**: Query caching, parallel processing, memory optimization -- **Integration Modules**: Phoenix LiveView integration, OTP application patterns, worker pools -- **Security Components**: Input validation, permission management, sandboxed execution -- **Developer Tools**: Debug mode, troubleshooting utilities, session management helpers -- **Advanced Examples**: Code analysis pipelines, documentation generators, test creators -- **MCP Integration**: Model Context Protocol support and tool management -- **Helper Modules**: Content extractors, option builders, authentication checkers - -**Note**: The comprehensive examples and patterns shown in this manual serve as both documentation and implementation roadmap. The core SDK is fully functional, while advanced features await future development. - -### Key Features - -- **Stream-based Processing**: Efficient handling of large responses through Elixir Streams -- **Subprocess Management**: Robust process handling using erlexec -- **Authentication Delegation**: Uses existing Claude CLI authentication -- **Full Feature Parity**: Supports all Claude Code CLI options and modes -- **Type Safety**: Structured message parsing with clear types -- **Error Recovery**: Comprehensive error handling and recovery mechanisms +### ✅ **Currently Implemented (v0.2.0)** +- **Core SDK Functions**: `query/2`, `continue/2`, `resume/3` with stdin support. +- **Authentication Management**: `AuthManager` for automatic token handling, `mix claude.setup_token` task, multi-provider support, and environment variable fallbacks. +- **Model Selection & Custom Agents**: Full support for Opus, Sonnet, Haiku, fallback models, and custom agent definitions via `OptionBuilder`. +- **Concurrent Orchestration**: `Orchestrator` module for parallel queries (`query_parallel/2`), sequential pipelines (`query_pipeline/2`), and retries (`query_with_retry/3`). +- **Session Persistence**: `SessionStore` GenServer for saving, loading, and searching sessions with tagging and metadata. +- **Advanced Session Flags**: Support for forking sessions, adding multiple directories, and strict MCP configs. +- **Developer Tools**: A suite of helper modules including `OptionBuilder`, `AuthChecker`, `ContentExtractor`, and `DebugMode`. +- **Robust Subprocess Management**: Integration with `erlexec` for stable process control. +- **Comprehensive Mocking System**: For testing without live API calls. +- **Live Script Runner**: `mix run.live` for executing scripts with real API calls. +- **Error Handling**: Improved error detection and timeout handling. + +### 🔮 **Planned Features (v0.3.0+)** +- **Bidirectional Streaming**: For real-time, character-level streaming suitable for chat UIs. +- **Telemetry Integration**: For production observability. +- **Advanced Integration Patterns**: Examples for Phoenix LiveView, OTP applications, and worker pools. +- **Plugin System**: An extensible architecture for custom behaviors. +- **Advanced Examples**: In-depth examples for code analysis, test generation, and refactoring. +- **Full MCP Support**: Deeper integration with the Model Context Protocol. ## Architecture Deep Dive @@ -75,108 +63,91 @@ All sections marked with **(FUTURE/PLANNED)** represent planned functionality in ### Module Structure -- **`ClaudeCodeSDK`**: Main public API interface -- **`ClaudeCodeSDK.Query`**: Query construction and execution -- **`ClaudeCodeSDK.Process`**: Subprocess management with erlexec -- **`ClaudeCodeSDK.Message`**: Message parsing and type definitions -- **`ClaudeCodeSDK.Options`**: Configuration and CLI argument building -- **`ClaudeCodeSDK.JSON`**: Custom JSON parsing without external dependencies +- **`ClaudeCodeSDK`**: Main public API interface. +- **`ClaudeCodeSDK.Query`**: Query construction and execution. +- **`ClaudeCodeSDK.Process`**: Subprocess management with erlexec. +- **`ClaudeCodeSDK.Message`**: Message parsing and type definitions. +- **`ClaudeCodeSDK.Options`**: Configuration and CLI argument building. +- **`ClaudeCodeSDK.JSON`**: Custom JSON parsing without external dependencies. +- **`ClaudeCodeSDK.AuthManager`**: Manages authentication tokens and status. +- **`ClaudeCodeSDK.Orchestrator`**: Handles concurrent and sequential query execution. +- **`ClaudeCodeSDK.SessionStore`**: Manages session persistence. ### Data Flow -1. **Query Construction**: User prompt and options are converted to CLI arguments -2. **Process Spawning**: Claude CLI is spawned as a subprocess with proper arguments -3. **Stream Processing**: JSON responses are parsed and converted to Elixir structs -4. **Message Delivery**: Structured messages are yielded through an Elixir Stream +1. **Query Construction**: User prompt and options are converted to CLI arguments. +2. **Process Spawning**: Claude CLI is spawned as a subprocess with proper arguments. +3. **Stream Processing**: JSON responses are parsed and converted to Elixir structs. +4. **Message Delivery**: Structured messages are yielded through an Elixir Stream. ## Installation & Setup -### Dependencies - -Add to your `mix.exs`: - -```elixir -def deps do - [ - {:erlexec, "~> 2.0"}, - {:ex_doc, "~> 0.31", only: :dev, runtime: false} - ] -end -``` - ### Prerequisites -1. **Node.js**: Required for Claude CLI -2. **Claude CLI**: Install globally +1. **Node.js**: Required for Claude CLI. +2. **Claude CLI**: Install globally. ```bash npm install -g @anthropic-ai/claude-code ``` -3. **Authentication**: Authenticate once +3. **Authentication**: Authenticate once using the new helper task. ```bash - claude login + mix claude.setup_token ``` ### Project Setup -```bash -# Clone or create your project -git clone your-project -cd your-project +Add to your `mix.exs`: +```elixir +def deps do + [ + {:claude_code_sdk, "~> 0.2.0"} + ] +end +``` -# Install dependencies +Then run: +```bash mix deps.get - -# Verify installation -mix run -e "ClaudeCodeSDK.query(\"Hello\") |> Enum.take(1) |> IO.inspect" ``` ## Authentication -The SDK delegates all authentication to the Claude CLI, providing a simplified authentication model: +The SDK provides robust authentication management via the `AuthManager` module. -### One-Time Setup +### One-Time Setup (Recommended) -```bash -# Authenticate with Anthropic -claude login +The easiest way to authenticate is with the included Mix task. This will generate a long-lived OAuth token (1 year) and store it securely. -# Verify authentication -claude auth status +```bash +# This requires a Claude subscription +mix claude.setup_token ``` ### Authentication in Code +The SDK automatically detects and uses the stored token. You can also check the authentication status programmatically. + ```elixir -# No API keys needed in code! -# The SDK automatically uses CLI authentication - -# Error handling for unauthenticated sessions -ClaudeCodeSDK.query("Hello") -|> Enum.each(fn msg -> - case msg do - %{type: :result, subtype: :error_during_execution} -> - IO.puts("❌ Authentication required. Run 'claude login'") - _ -> - # Process normal messages - end -end) -``` +alias ClaudeCodeSDK.AuthManager -### Authentication Status Check **(FUTURE/PLANNED)** +# Check authentication status +status = AuthManager.status() +# => %{ +# authenticated: true, +# provider: :anthropic, +# token_present: true, +# expires_at: ~U[2026-10-07 ...], +# time_until_expiry_hours: 8760.0 +# } -```elixir -defmodule AuthChecker do # FUTURE/PLANNED - Not yet implemented - def check_auth do - case System.cmd("claude", ["auth", "status"]) do - {output, 0} -> - IO.puts("✅ Authenticated: #{String.trim(output)}") - true - {error, _} -> - IO.puts("❌ Not authenticated: #{error}") - false - end - end -end +# Ensure authenticated (will prompt for setup if needed) +:ok = AuthManager.ensure_authenticated() +``` + +### Environment Variables (for CI/CD) +You can also provide the token via environment variables: +```bash +export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-your-token-here ``` ## Core API Reference @@ -184,137 +155,59 @@ end ### Primary Functions #### `ClaudeCodeSDK.query/2` - Executes a single query with optional configuration. - ```elixir @spec query(String.t(), Options.t() | nil) :: Stream.t() - -# Basic usage ClaudeCodeSDK.query("Write a hello world function") - -# With options -options = %ClaudeCodeSDK.Options{ - max_turns: 3, - output_format: :stream_json, - verbose: true -} -ClaudeCodeSDK.query("Complex task", options) ``` #### `ClaudeCodeSDK.continue/2` - Continues the most recent conversation. - ```elixir @spec continue(String.t() | nil, Options.t() | nil) :: Stream.t() - -# Continue without additional prompt -ClaudeCodeSDK.continue() - -# Continue with new prompt ClaudeCodeSDK.continue("Now add error handling") - -# Continue with options -options = %ClaudeCodeSDK.Options{max_turns: 5} -ClaudeCodeSDK.continue("Refactor for performance", options) ``` #### `ClaudeCodeSDK.resume/3` - Resumes a specific conversation by session ID. - ```elixir @spec resume(String.t(), String.t() | nil, Options.t() | nil) :: Stream.t() - -# Resume existing session -session_id = "550e8400-e29b-41d4-a716-446655440000" -ClaudeCodeSDK.resume(session_id, "Add tests") - -# Resume with options -ClaudeCodeSDK.resume(session_id, "Deploy", %ClaudeCodeSDK.Options{ - permission_mode: :bypass_permissions -}) +ClaudeCodeSDK.resume("session-id-here", "Add tests") ``` -### Options Configuration +### Options & Smart Presets -The `ClaudeCodeSDK.Options` struct supports all Claude CLI options: +Use the `OptionBuilder` to create configurations for common use cases. ```elixir -%ClaudeCodeSDK.Options{ - # Conversation control - max_turns: 10, # Limit conversation turns - - # System prompts - system_prompt: "Custom prompt", # Override system prompt - append_system_prompt: "Extra", # Append to default prompt - - # Output control - output_format: :stream_json, # :text, :json, :stream_json - verbose: true, # Enable verbose logging - - # Tool management - allowed_tools: ["Bash", "Read"], # Allowed tool list - disallowed_tools: ["Write"], # Disallowed tool list - - # MCP configuration - mcp_config: "/path/to/mcp.json", # MCP server config - permission_prompt_tool: "mcp__auth__prompt", - - # Permission modes - permission_mode: :accept_edits, # :default, :accept_edits, - # :bypass_permissions, :plan - - # Environment - cwd: "/project/path", # Working directory - executable: "node", # JavaScript runtime - executable_args: ["--max-memory"], # Runtime arguments - - # Advanced - path_to_claude_code_executable: "/custom/path/claude", - abort_ref: make_ref() # For cancellation -} -``` +alias ClaudeCodeSDK.OptionBuilder -### Option Builder Pattern **(FUTURE/PLANNED)** +# Development: permissive settings, verbose logging +options = OptionBuilder.build_development_options() -```elixir -defmodule OptionBuilder do # FUTURE/PLANNED - Not yet implemented - def build_development_options do - ClaudeCodeSDK.Options.new( - max_turns: 5, - verbose: true, - allowed_tools: ["Bash", "Read", "Write"], - permission_mode: :accept_edits - ) - end - - def build_production_options do - ClaudeCodeSDK.Options.new( - max_turns: 3, - verbose: false, - permission_mode: :plan, - disallowed_tools: ["Bash"] - ) - end - - def build_mcp_options(mcp_config_path) do - ClaudeCodeSDK.Options.new( - mcp_config: mcp_config_path, - allowed_tools: ["mcp__filesystem__read", "mcp__github__search"], - permission_prompt_tool: "mcp__auth__approve" - ) - end -end +# Production: restricted settings, minimal tools +options = OptionBuilder.build_production_options() + +# Analysis: read-only tools for code analysis +options = OptionBuilder.build_analysis_options() + +# Combine presets with custom options +options = OptionBuilder.merge(:development, %{max_turns: 5}) + +# Manually configure options +options = %ClaudeCodeSDK.Options{ + max_turns: 10, + system_prompt: "You are a helpful assistant.", + model: "opus" +} + +ClaudeCodeSDK.query("My query", options) ``` ## Message Types & Processing ### Message Structure - -All messages follow a consistent structure: - +All messages are `ClaudeCodeSDK.Message` structs: ```elixir %ClaudeCodeSDK.Message{ type: :system | :user | :assistant | :result, @@ -324,1642 +217,182 @@ All messages follow a consistent structure: } ``` -### System Messages +- **`:system`**: Session initialization info (session_id, model, etc.). +- **`:assistant`**: Claude's responses. +- **`:user`**: The user's input messages. +- **`:result`**: The final result of the conversation with stats (`:success` or an error subtype). -Emitted at conversation start with session information: +### Content Extraction +Use the `ContentExtractor` to easily get text from messages. ```elixir -%{ - type: :system, - subtype: :init, - data: %{ - session_id: "550e8400-e29b-41d4-a716-446655440000", - model: "claude-opus-4-20250514", - cwd: "/project/path", - tools: ["Bash", "Read", "Write"], - mcp_servers: [%{name: "filesystem", status: "ready"}], - permission_mode: "default", - api_key_source: "env" - } -} -``` +alias ClaudeCodeSDK.ContentExtractor -### Assistant Messages - -Claude's responses with structured content: - -```elixir -%{ - type: :assistant, - data: %{ - message: %{ - "role" => "assistant", - "content" => "Response text here" # or structured content - }, - session_id: "session-id" - } -} -``` - -### User Messages - -User inputs (when using multi-turn conversations): - -```elixir -%{ - type: :user, - data: %{ - message: %{ - "role" => "user", - "content" => "User input here" - }, - session_id: "session-id" - } -} -``` - -### Result Messages - -Final messages with conversation statistics: - -```elixir -# Success result -%{ - type: :result, - subtype: :success, - data: %{ - result: "Final response text", - session_id: "session-id", - total_cost_usd: 0.025, - duration_ms: 3420, - duration_api_ms: 2100, - num_turns: 3, - is_error: false - } -} - -# Error result -%{ - type: :result, - subtype: :error_max_turns, # or :error_during_execution - data: %{ - session_id: "session-id", - total_cost_usd: 0.015, - duration_ms: 5000, - num_turns: 10, - is_error: true, - error: "Max turns exceeded" - } -} -``` - -### Content Extraction Helpers **(FUTURE/PLANNED)** - -```elixir -defmodule ContentExtractor do # FUTURE/PLANNED - Not yet implemented - def extract_text(message) do - case message do - %{type: :assistant, data: %{message: %{"content" => content}}} -> - extract_content_text(content) - _ -> - nil - end - end - - defp extract_content_text(content) when is_binary(content), do: content - defp extract_content_text([%{"text" => text}]), do: text - defp extract_content_text(content_list) when is_list(content_list) do - content_list - |> Enum.map(fn - %{"text" => text} -> text - %{"type" => "tool_use"} = tool -> "[Tool: #{tool["name"]}]" - other -> inspect(other) - end) - |> Enum.join(" ") - end - defp extract_content_text(other), do: inspect(other) -end +ClaudeCodeSDK.query("Your prompt") +|> Stream.filter(fn msg -> msg.type == :assistant end) +|> Stream.map(&ContentExtractor.extract_text/1) +|> Enum.each(&IO.puts/1) ``` ## Advanced Usage Patterns -### Stream Processing Patterns - -#### Filter and Transform - -```elixir -# Extract only assistant responses -assistant_responses = - ClaudeCodeSDK.query("Explain quantum computing") - |> Stream.filter(&(&1.type == :assistant)) - |> Stream.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - -# Process messages in real-time -ClaudeCodeSDK.query("Generate a large report") -|> Stream.each(fn msg -> - case msg.type do - :assistant -> - IO.puts("📝 #{ContentExtractor.extract_text(msg)}") - :result -> - IO.puts("💰 Cost: $#{msg.data.total_cost_usd}") - _ -> - :ok - end -end) -|> Stream.run() -``` - -#### Chunked Processing +### Session Management +Persist and resume conversations using the `SessionStore`. ```elixir -# Process messages in chunks -ClaudeCodeSDK.query("Generate 100 test cases") -|> Stream.chunk_every(5) -|> Stream.each(fn chunk -> - IO.puts("Processing chunk of #{length(chunk)} messages...") - # Process chunk -end) -|> Stream.run() -``` +alias ClaudeCodeSDK.{SessionStore, Session} -#### Early Termination +# Start the session store (in your application supervisor) +{:ok, _pid} = SessionStore.start_link() -```elixir -# Stop when specific condition is met -ClaudeCodeSDK.query("Find all bugs in this codebase") -|> Stream.take_while(fn msg -> - case msg do - %{type: :result} -> false # Stop at result - %{type: :assistant} -> - text = ContentExtractor.extract_text(msg) - not String.contains?(text, "CRITICAL_BUG_FOUND") - _ -> true - end -end) -|> Enum.to_list() -``` +# Execute a query +messages = ClaudeCodeSDK.query("Build a user authentication system") |> Enum.to_list() -### Session Management **(FUTURE/PLANNED)** +# Save the session +session_id = Session.extract_session_id(messages) +:ok = SessionStore.save_session(session_id, messages, + tags: ["feature-dev", "auth"], + description: "Building user auth" +) -#### Session Tracking **(FUTURE/PLANNED)** +# Later, load and resume +{:ok, session_data} = SessionStore.load_session(session_id) +ClaudeCodeSDK.resume(session_id, "Now add password reset functionality") -```elixir -defmodule SessionManager do # FUTURE/PLANNED - Not yet implemented - use GenServer - - # Client API - def start_link(_opts) do - GenServer.start_link(__MODULE__, %{}, name: __MODULE__) - end - - def track_session(prompt, options \\ nil) do - GenServer.call(__MODULE__, {:track_session, prompt, options}) - end - - def get_recent_sessions(limit \\ 10) do - GenServer.call(__MODULE__, {:get_recent_sessions, limit}) - end - - def continue_session(session_id, prompt) do - GenServer.call(__MODULE__, {:continue_session, session_id, prompt}) - end - - # Server callbacks - def init(state) do - {:ok, %{sessions: [], current_session: nil}} - end - - def handle_call({:track_session, prompt, options}, _from, state) do - stream = ClaudeCodeSDK.query(prompt, options) - - # Extract session info from first message - session_info = - stream - |> Enum.find(&(&1.type == :system)) - |> case do - %{data: %{session_id: id}} -> - %{id: id, prompt: prompt, started_at: DateTime.utc_now()} - _ -> - nil - end - - new_state = - if session_info do - %{state | - sessions: [session_info | state.sessions], - current_session: session_info.id - } - else - state - end - - {:reply, stream, new_state} - end - - def handle_call({:get_recent_sessions, limit}, _from, state) do - recent = state.sessions |> Enum.take(limit) - {:reply, recent, state} - end - - def handle_call({:continue_session, session_id, prompt}, _from, state) do - stream = ClaudeCodeSDK.resume(session_id, prompt) - {:reply, stream, state} - end -end +# Search for sessions +auth_sessions = SessionStore.search(tags: ["auth"]) ``` -#### Conversation Chains **(FUTURE/PLANNED)** +### Concurrent Processing +Execute multiple queries in parallel for significant speed improvements using the `Orchestrator`. ```elixir -defmodule ConversationChain do # FUTURE/PLANNED - Not yet implemented - def run_chain(prompts, options \\ nil) when is_list(prompts) do - [first_prompt | rest_prompts] = prompts - - # Start with first prompt - session_id = get_session_id(ClaudeCodeSDK.query(first_prompt, options)) - - # Continue with remaining prompts - Enum.reduce(rest_prompts, session_id, fn prompt, acc_session_id -> - stream = ClaudeCodeSDK.resume(acc_session_id, prompt, options) - - # Process stream and return session_id - stream |> Enum.to_list() - acc_session_id - end) - end - - defp get_session_id(stream) do - stream - |> Enum.find(&(&1.type == :system)) - |> case do - %{data: %{session_id: id}} -> id - _ -> nil - end - end -end +alias ClaudeCodeSDK.Orchestrator -# Usage -conversation_steps = [ - "Create a new React component", - "Add TypeScript types to the component", - "Write unit tests for the component", - "Add documentation and examples" +queries = [ + {"Analyze file1.ex", analysis_opts}, + {"Analyze file2.ex", analysis_opts}, + {"Analyze file3.ex", analysis_opts} ] -ConversationChain.run_chain(conversation_steps) -``` +# Run up to 3 queries in parallel +{:ok, results} = Orchestrator.query_parallel(queries, max_concurrent: 3) -### Concurrent Processing **(FUTURE/PLANNED)** +# results is a list of %Orchestrator.Result{} structs +Enum.each(results, fn result -> + IO.puts("Prompt: #{result.prompt}, Success: #{result.success}, Cost: $#{result.cost}") +end) +``` -#### Parallel Queries **(FUTURE/PLANNED)** +### Conversation Chains +Create multi-step workflows where the output of one query is the input to the next. ```elixir -defmodule ParallelProcessor do # FUTURE/PLANNED - Not yet implemented - def run_parallel_queries(prompts, options \\ nil) do - prompts - |> Enum.map(fn prompt -> - Task.async(fn -> - ClaudeCodeSDK.query(prompt, options) - |> Enum.to_list() - end) - end) - |> Enum.map(&Task.await(&1, 60_000)) # 60 second timeout - end - - def run_with_rate_limit(prompts, rate_limit_ms \\ 1000) do - prompts - |> Enum.map(fn prompt -> - result = ClaudeCodeSDK.query(prompt) |> Enum.to_list() - Process.sleep(rate_limit_ms) - result - end) - end -end +alias ClaudeCodeSDK.Orchestrator -# Usage -prompts = [ - "Explain async/await in JavaScript", - "Show me Python list comprehensions", - "Describe Rust ownership model" +steps = [ + {"Analyze this code for quality.", analysis_opts}, + {"Suggest refactorings based on the previous analysis.", refactor_opts}, + {"Generate tests for the refactored code.", test_opts} ] -results = ParallelProcessor.run_parallel_queries(prompts) +# `use_context: true` passes the conversation history to the next step +{:ok, final_result} = Orchestrator.query_pipeline(steps, use_context: true) ``` ## Error Handling & Recovery ### Error Types +The final `:result` message will have a subtype indicating the outcome. +- `:success` +- `:error_max_turns` +- `:error_during_execution` +- `:error_auth` -#### Authentication Errors +### Retry Logic +The `Orchestrator` provides built-in retry logic with exponential backoff. ```elixir -defmodule ErrorHandler do - def handle_auth_error(stream) do - stream - |> Stream.each(fn msg -> - case msg do - %{type: :result, subtype: :error_during_execution} -> - raise "Authentication required. Run 'claude login' first." - _ -> - :ok - end - end) - |> Stream.run() - end -end -``` - -#### Timeout Handling **(FUTURE/PLANNED)** +alias ClaudeCodeSDK.Orchestrator -```elixir -defmodule TimeoutHandler do # FUTURE/PLANNED - Not yet implemented - def query_with_timeout(prompt, timeout_ms \\ 30_000) do - task = Task.async(fn -> - ClaudeCodeSDK.query(prompt) |> Enum.to_list() - end) - - case Task.yield(task, timeout_ms) do - {:ok, result} -> - {:ok, result} - nil -> - Task.shutdown(task, :brutal_kill) - {:error, :timeout} - end - end -end -``` - -#### Retry Logic **(FUTURE/PLANNED)** - -```elixir -defmodule RetryHandler do # FUTURE/PLANNED - Not yet implemented - def query_with_retry(prompt, max_retries \\ 3, delay_ms \\ 1000) do - do_query_with_retry(prompt, max_retries, delay_ms, 0) - end - - defp do_query_with_retry(prompt, max_retries, delay_ms, attempt) do - try do - result = ClaudeCodeSDK.query(prompt) |> Enum.to_list() - {:ok, result} - rescue - error -> - if attempt < max_retries do - IO.puts("Retry #{attempt + 1}/#{max_retries} after error: #{inspect(error)}") - Process.sleep(delay_ms) - do_query_with_retry(prompt, max_retries, delay_ms, attempt + 1) - else - {:error, error} - end - end - end -end -``` - -#### Comprehensive Error Handler **(FUTURE/PLANNED)** - -```elixir -defmodule ComprehensiveErrorHandler do # FUTURE/PLANNED - Not yet implemented - def safe_query(prompt, options \\ nil) do - try do - ClaudeCodeSDK.query(prompt, options) - |> Stream.map(&validate_message/1) - |> Enum.to_list() - |> case do - [] -> {:error, :no_messages} - messages -> classify_result(messages) - end - rescue - error -> {:error, {:exception, error}} - catch - :exit, reason -> {:error, {:exit, reason}} - end - end - - defp validate_message(msg) do - case msg do - %ClaudeCodeSDK.Message{} -> msg - other -> - IO.warn("Invalid message format: #{inspect(other)}") - msg - end - end - - defp classify_result(messages) do - case List.last(messages) do - %{type: :result, subtype: :success} = result -> - {:ok, messages, extract_stats(result)} - - %{type: :result, subtype: :error_max_turns} -> - {:error, :max_turns_exceeded, messages} - - %{type: :result, subtype: :error_during_execution} -> - {:error, :execution_error, messages} - - _ -> - {:error, :incomplete_conversation, messages} - end - end - - defp extract_stats(%{data: data}) do - %{ - cost: data.total_cost_usd, - duration: data.duration_ms, - turns: data.num_turns, - session_id: data.session_id - } - end -end +# Retry a query up to 3 times on failure +{:ok, result} = Orchestrator.query_with_retry( + "A prompt for a flaky operation", + options, + max_retries: 3, + backoff_ms: 1000 +) ``` ## Performance Optimization -### Stream Optimization **(FUTURE/PLANNED)** - -```elixir -defmodule PerformanceOptimizer do # FUTURE/PLANNED - Not yet implemented - # Lazy evaluation for large responses - def lazy_process_large_response(prompt) do - ClaudeCodeSDK.query(prompt) - |> Stream.filter(&(&1.type == :assistant)) - |> Stream.map(&extract_and_process_content/1) - |> Stream.chunk_every(10) # Process in chunks - end - - # Memory-efficient content extraction - defp extract_and_process_content(msg) do - # Process content without holding entire response in memory - content = ContentExtractor.extract_text(msg) - - # Immediate processing to reduce memory usage - content - |> String.split("\n") - |> Enum.filter(&(String.length(&1) > 0)) - |> length() # Just return count, not content - end - - # Parallel processing with backpressure - def parallel_with_backpressure(prompts, concurrency \\ 3) do - prompts - |> Task.async_stream( - fn prompt -> - ClaudeCodeSDK.query(prompt) |> Enum.to_list() - end, - max_concurrency: concurrency, - timeout: 60_000, - on_timeout: :kill_task - ) - |> Enum.map(fn - {:ok, result} -> result - {:exit, reason} -> {:error, reason} - end) - end -end -``` +### Parallel Processing +Use `Orchestrator.query_parallel/2` for batch processing tasks like analyzing multiple files. This is the most significant performance optimization available. -### Caching Strategies **(FUTURE/PLANNED)** +### Caching Strategies (FUTURE/PLANNED) +For frequently repeated queries, a caching layer can be implemented. ```elixir defmodule QueryCache do # FUTURE/PLANNED - Not yet implemented use GenServer - - # Cache based on prompt hash - def cached_query(prompt, options \\ nil, ttl_ms \\ 300_000) do - cache_key = :erlang.phash2({prompt, options}) - - case GenServer.call(__MODULE__, {:get, cache_key}) do - {:hit, result} -> - IO.puts("🎯 Cache hit for prompt") - result - :miss -> - IO.puts("💾 Cache miss, executing query") - result = ClaudeCodeSDK.query(prompt, options) |> Enum.to_list() - GenServer.cast(__MODULE__, {:put, cache_key, result, ttl_ms}) - result - end - end - - # Server implementation - def start_link(_) do - GenServer.start_link(__MODULE__, %{}, name: __MODULE__) - end - - def init(_) do - {:ok, %{cache: %{}, timers: %{}}} - end - - def handle_call({:get, key}, _from, %{cache: cache} = state) do - result = case Map.get(cache, key) do - nil -> :miss - value -> {:hit, value} - end - {:reply, result, state} - end - - def handle_cast({:put, key, value, ttl_ms}, state) do - # Cancel existing timer - if timer_ref = state.timers[key] do - Process.cancel_timer(timer_ref) - end - - # Set new timer - timer_ref = Process.send_after(self(), {:expire, key}, ttl_ms) - - new_state = %{ - cache: Map.put(state.cache, key, value), - timers: Map.put(state.timers, key, timer_ref) - } - - {:noreply, new_state} - end - - def handle_info({:expire, key}, state) do - new_state = %{ - cache: Map.delete(state.cache, key), - timers: Map.delete(state.timers, key) - } - {:noreply, new_state} - end + # ... Caching logic using a GenServer or ETS ... end ``` -## Integration Patterns +## Integration Patterns (FUTURE/PLANNED) -### Phoenix LiveView Integration **(FUTURE/PLANNED)** +This section outlines planned examples and integrations. -```elixir -defmodule MyAppWeb.ClaudeLive do # FUTURE/PLANNED - Not yet implemented - use MyAppWeb, :live_view - - def mount(_params, _session, socket) do - socket = - socket - |> assign(:messages, []) - |> assign(:query_stream, nil) - |> assign(:loading, false) - - {:ok, socket} - end - - def handle_event("send_query", %{"prompt" => prompt}, socket) do - if socket.assigns.loading do - {:noreply, socket} - else - # Start query stream - query_stream = ClaudeCodeSDK.query(prompt) - - # Process stream asynchronously - pid = self() - Task.start(fn -> - query_stream - |> Enum.each(fn msg -> - send(pid, {:claude_message, msg}) - end) - send(pid, :claude_complete) - end) - - socket = - socket - |> assign(:loading, true) - |> assign(:query_stream, query_stream) - - {:noreply, socket} - end - end - - def handle_info({:claude_message, msg}, socket) do - case msg.type do - :assistant -> - content = ContentExtractor.extract_text(msg) - new_message = %{type: :assistant, content: content, timestamp: DateTime.utc_now()} - - socket = update(socket, :messages, &[new_message | &1]) - {:noreply, socket} - - :result -> - new_message = %{ - type: :result, - content: "Query completed. Cost: $#{msg.data.total_cost_usd}", - timestamp: DateTime.utc_now() - } - - socket = - socket - |> update(:messages, &[new_message | &1]) - |> assign(:loading, false) - - {:noreply, socket} - - _ -> - {:noreply, socket} - end - end - - def handle_info(:claude_complete, socket) do - {:noreply, assign(socket, :loading, false)} - end -end -``` - -### OTP Application Integration **(FUTURE/PLANNED)** - -```elixir -defmodule ClaudeCodeApp.Supervisor do # FUTURE/PLANNED - Not yet implemented - use Supervisor - - def start_link(init_arg) do - Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__) - end - - def init(_init_arg) do - children = [ - {SessionManager, []}, - {QueryCache, []}, - {ClaudeWorkerPool, pool_size: 3} - ] - - Supervisor.init(children, strategy: :one_for_one) - end -end +### Phoenix LiveView Integration +An example LiveView will be provided to show how to build real-time, streaming UIs. -defmodule ClaudeWorkerPool do # FUTURE/PLANNED - Not yet implemented - use GenServer - - def start_link(opts) do - pool_size = Keyword.get(opts, :pool_size, 3) - GenServer.start_link(__MODULE__, pool_size, name: __MODULE__) - end - - def query(prompt, options \\ nil) do - GenServer.call(__MODULE__, {:query, prompt, options}, 60_000) - end - - def init(pool_size) do - workers = for i <- 1..pool_size do - {:ok, pid} = ClaudeWorker.start_link(name: :"claude_worker_#{i}") - pid - end - - {:ok, %{workers: workers, current: 0}} - end - - def handle_call({:query, prompt, options}, _from, state) do - worker = Enum.at(state.workers, state.current) - next_index = rem(state.current + 1, length(state.workers)) - - result = ClaudeWorker.query(worker, prompt, options) - - {:reply, result, %{state | current: next_index}} - end -end -``` - -### Task Processing Pipeline - -```elixir -defmodule TaskPipeline do - def process_code_review(file_path) do - file_path - |> read_file() - |> analyze_complexity() - |> generate_review() - |> format_output() - end - - defp read_file(path) do - content = File.read!(path) - %{path: path, content: content} - end - - defp analyze_complexity(%{content: content} = data) do - prompt = """ - Analyze the complexity of this code and identify potential issues: - - #{content} - """ - - analysis = - ClaudeCodeSDK.query(prompt) - |> Enum.filter(&(&1.type == :assistant)) - |> Enum.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - - Map.put(data, :complexity_analysis, analysis) - end - - defp generate_review(%{content: content, complexity_analysis: analysis} = data) do - prompt = """ - Based on this complexity analysis: - #{analysis} - - Generate a detailed code review for: - #{content} - - Include specific suggestions for improvement. - """ - - review = - ClaudeCodeSDK.query(prompt) - |> Enum.filter(&(&1.type == :assistant)) - |> Enum.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - - Map.put(data, :review, review) - end - - defp format_output(%{path: path, review: review}) do - """ - # Code Review for #{Path.basename(path)} - - #{review} - - Generated at: #{DateTime.utc_now()} - """ - end -end -``` +### OTP Application Integration +Best practices for integrating the SDK's GenServers (`AuthManager`, `SessionStore`) into a supervised OTP application will be documented. ## MCP Support +The SDK has basic support for MCP (Model Context Protocol) via the `--strict-mcp-config` flag, but full, dynamic MCP server management is a planned feature. -### MCP Configuration **(FUTURE/PLANNED)** - -```elixir -defmodule MCPConfig do # FUTURE/PLANNED - Not yet implemented - def filesystem_config do - %{ - "mcpServers" => %{ - "filesystem" => %{ - "command" => "npx", - "args" => ["-y", "@modelcontextprotocol/server-filesystem", "/allowed/path"] - } - } - } - end - - def github_config(token) do - %{ - "mcpServers" => %{ - "github" => %{ - "command" => "npx", - "args" => ["-y", "@modelcontextprotocol/server-github"], - "env" => %{ - "GITHUB_TOKEN" => token - } - } - } - } - end - - def combined_config(github_token) do - Map.merge(filesystem_config(), github_config(github_token)) - end - - def save_config(config, path) do - Jason.encode!(config) |> then(&File.write!(path, &1)) - end - - def create_options_with_mcp(mcp_config_path, allowed_tools) do - ClaudeCodeSDK.Options.new( - mcp_config: mcp_config_path, - allowed_tools: allowed_tools, - permission_prompt_tool: "mcp__auth__approve" - ) - end -end - -# Usage -config = MCPConfig.combined_config("github_token_here") -MCPConfig.save_config(config, "/tmp/mcp_config.json") - -options = MCPConfig.create_options_with_mcp( - "/tmp/mcp_config.json", - ["mcp__filesystem__read_file", "mcp__github__search_issues"] -) - -ClaudeCodeSDK.query("Search for open issues about performance", options) -``` - -### MCP Tool Management **(FUTURE/PLANNED)** - -```elixir -defmodule MCPToolManager do # FUTURE/PLANNED - Not yet implemented - def list_available_tools(mcp_config_path) do - # This would need to be implemented by querying MCP servers - # For now, return common patterns - [ - "mcp__filesystem__read_file", - "mcp__filesystem__write_file", - "mcp__filesystem__list_directory", - "mcp__github__search_issues", - "mcp__github__create_issue", - "mcp__slack__send_message" - ] - end - - def build_tool_permissions(allowed_patterns) do - allowed_patterns - |> Enum.map(&expand_pattern/1) - |> List.flatten() - |> Enum.uniq() - end - - defp expand_pattern("mcp__" <> server_name) do - # Allow all tools from a server - case server_name do - "filesystem" -> [ - "mcp__filesystem__read_file", - "mcp__filesystem__write_file", - "mcp__filesystem__list_directory" - ] - "github" -> [ - "mcp__github__search_issues", - "mcp__github__create_issue", - "mcp__github__get_repo" - ] - _ -> [] - end - end - defp expand_pattern(exact_tool), do: [exact_tool] -end -``` - -## Security Considerations - -### Input Validation **(FUTURE/PLANNED)** - -```elixir -defmodule SecurityValidator do # FUTURE/PLANNED - Not yet implemented - @max_prompt_length 50_000 - @dangerous_patterns [ - ~r/rm -rf/i, - ~r/sudo/i, - ~r/curl.*\|.*sh/i, - ~r/eval\(/i - ] - - def validate_prompt(prompt) when is_binary(prompt) do - cond do - String.length(prompt) > @max_prompt_length -> - {:error, :prompt_too_long} - - contains_dangerous_pattern?(prompt) -> - {:error, :dangerous_content} - - true -> - {:ok, prompt} - end - end - def validate_prompt(_), do: {:error, :invalid_type} - - defp contains_dangerous_pattern?(prompt) do - Enum.any?(@dangerous_patterns, &Regex.match?(&1, prompt)) - end - - def validate_options(%ClaudeCodeSDK.Options{} = options) do - with :ok <- validate_working_directory(options.cwd), - :ok <- validate_tools(options.allowed_tools), - :ok <- validate_mcp_config(options.mcp_config) do - {:ok, options} - end - end - - defp validate_working_directory(nil), do: :ok - defp validate_working_directory(cwd) when is_binary(cwd) do - if String.starts_with?(Path.expand(cwd), "/allowed/path") do - :ok - else - {:error, :invalid_working_directory} - end - end - - defp validate_tools(nil), do: :ok - defp validate_tools(tools) when is_list(tools) do - if Enum.all?(tools, &is_binary/1) do - :ok - else - {:error, :invalid_tools} - end - end - - defp validate_mcp_config(nil), do: :ok - defp validate_mcp_config(path) when is_binary(path) do - if File.exists?(path) and Path.extname(path) == ".json" do - :ok - else - {:error, :invalid_mcp_config} - end - end -end - -# Secure query wrapper - FUTURE/PLANNED -defmodule SecureClaudeSDK do # FUTURE/PLANNED - Not yet implemented - def secure_query(prompt, options \\ nil) do - with {:ok, validated_prompt} <- SecurityValidator.validate_prompt(prompt), - {:ok, validated_options} <- SecurityValidator.validate_options(options || %ClaudeCodeSDK.Options{}) do - ClaudeCodeSDK.query(validated_prompt, validated_options) - else - error -> Stream.concat([%{type: :error, error: error}]) - end - end -end -``` - -### Permission Management **(FUTURE/PLANNED)** - -```elixir -defmodule PermissionManager do # FUTURE/PLANNED - Not yet implemented - def safe_options_for_environment(env) do - case env do - :development -> - %ClaudeCodeSDK.Options{ - permission_mode: :accept_edits, - allowed_tools: ["Read", "Write", "Bash"], - max_turns: 10 - } - - :staging -> - %ClaudeCodeSDK.Options{ - permission_mode: :plan, - allowed_tools: ["Read"], - disallowed_tools: ["Bash"], - max_turns: 5 - } - - :production -> - %ClaudeCodeSDK.Options{ - permission_mode: :plan, - allowed_tools: ["Read"], - disallowed_tools: ["Bash", "Write"], - max_turns: 3 - } - end - end - - def create_sandboxed_options(sandbox_path) do - %ClaudeCodeSDK.Options{ - cwd: sandbox_path, - permission_mode: :bypass_permissions, - allowed_tools: ["Read", "Write"], - disallowed_tools: ["Bash"], - max_turns: 5 - } - end -end -``` +## Security Considerations (FUTURE/PLANNED) +Future versions will include guides and potentially helpers for: +- **Input Validation**: Sanitizing prompts to prevent injection-style attacks. +- **Permission Management**: Scoping tool permissions based on environment or user roles. ## Troubleshooting -### Common Issues and Solutions - -#### Authentication Problems **(FUTURE/PLANNED)** - -```elixir -defmodule TroubleshootAuth do # FUTURE/PLANNED - Not yet implemented - def diagnose_auth_issue do - case System.cmd("claude", ["auth", "status"]) do - {output, 0} -> - IO.puts("✅ Authentication OK: #{output}") - - {error, _code} -> - IO.puts("❌ Authentication failed: #{error}") - IO.puts("💡 Solution: Run 'claude login'") - end - end - - def check_cli_installation do - case System.find_executable("claude") do - nil -> - IO.puts("❌ Claude CLI not found") - IO.puts("💡 Install with: npm install -g @anthropic-ai/claude-code") - - path -> - IO.puts("✅ Claude CLI found at: #{path}") - - case System.cmd("claude", ["--version"]) do - {version, 0} -> IO.puts(" Version: #{String.trim(version)}") - {error, _} -> IO.puts(" Error getting version: #{error}") - end - end - end -end -``` - -#### Process and Stream Issues **(FUTURE/PLANNED)** - -```elixir -defmodule TroubleshootProcess do # FUTURE/PLANNED - Not yet implemented - def diagnose_hanging_query(prompt) do - IO.puts("🔍 Diagnosing query: #{inspect(prompt)}") - - # Test with timeout - task = Task.async(fn -> - ClaudeCodeSDK.query(prompt) |> Enum.take(3) - end) - - case Task.yield(task, 10_000) do - {:ok, messages} -> - IO.puts("✅ Query completed with #{length(messages)} messages") - Enum.each(messages, fn msg -> - IO.puts(" #{msg.type}: #{inspect(msg.data, limit: :infinity)}") - end) - - nil -> - IO.puts("❌ Query timed out after 10 seconds") - Task.shutdown(task, :brutal_kill) - - # Additional diagnostics - IO.puts("💡 Checking process status...") - System.cmd("ps", ["aux"]) - |> elem(0) - |> String.split("\n") - |> Enum.filter(&String.contains?(&1, "claude")) - |> Enum.each(&IO.puts(" #{&1}")) - end - end - - def test_basic_connectivity do - IO.puts("🔧 Testing basic Claude connectivity...") - - case System.cmd("claude", ["--print", "Say 'test'", "--output-format", "json"], - stderr_to_stdout: true) do - {output, 0} -> - IO.puts("✅ Basic connectivity OK") - try do - Jason.decode!(output) - IO.puts("✅ JSON parsing OK") - rescue - _ -> IO.puts("⚠️ JSON parsing failed, output: #{output}") - end - - {error, code} -> - IO.puts("❌ Basic connectivity failed (exit #{code}): #{error}") - end - end -end -``` - -### Debug Mode **(FUTURE/PLANNED)** - -```elixir -defmodule DebugMode do # FUTURE/PLANNED - Not yet implemented - def debug_query(prompt, options \\ nil) do - IO.puts("🐛 DEBUG MODE ENABLED") - IO.puts(" Prompt: #{inspect(prompt)}") - IO.puts(" Options: #{inspect(options)}") - - # Add debug options - debug_options = case options do - nil -> %ClaudeCodeSDK.Options{verbose: true} - opts -> %{opts | verbose: true} - end - - IO.puts(" Final options: #{inspect(debug_options)}") - - # Time the query - start_time = System.monotonic_time(:millisecond) - - result = - ClaudeCodeSDK.query(prompt, debug_options) - |> Stream.map(fn msg -> - elapsed = System.monotonic_time(:millisecond) - start_time - IO.puts(" [#{elapsed}ms] #{msg.type}: #{inspect(msg.data, limit: 1)}") - msg - end) - |> Enum.to_list() - - total_time = System.monotonic_time(:millisecond) - start_time - IO.puts("🏁 Debug completed in #{total_time}ms with #{length(result)} messages") - - result - end -end -``` - -## Examples & Use Cases - -### Code Analysis Pipeline **(FUTURE/PLANNED)** +### Authentication Problems +Use the built-in `AuthChecker` and `DebugMode` to diagnose issues. ```elixir -defmodule CodeAnalyzer do # FUTURE/PLANNED - Not yet implemented - def analyze_project(project_path) do - project_path - |> find_source_files() - |> Enum.map(&analyze_file/1) - |> generate_summary_report() - end - - defp find_source_files(path) do - Path.wildcard("#{path}/**/*.{ex,exs,js,ts,py}") - end - - defp analyze_file(file_path) do - content = File.read!(file_path) - - analysis = - ClaudeCodeSDK.query(""" - Analyze this code file for: - 1. Code quality issues - 2. Security vulnerabilities - 3. Performance concerns - 4. Best practice violations - - File: #{Path.basename(file_path)} - ``` - #{content} - ``` - - Provide specific, actionable feedback. - """) - |> Enum.filter(&(&1.type == :assistant)) - |> Enum.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - - %{ - file: file_path, - analysis: analysis, - timestamp: DateTime.utc_now() - } - end - - defp generate_summary_report(analyses) do - all_issues = Enum.map_join(analyses, "\n\n", fn %{file: file, analysis: analysis} -> - "## #{Path.basename(file)}\n#{analysis}" - end) - - summary = - ClaudeCodeSDK.query(""" - Create a executive summary of these code analysis results: - - #{all_issues} - - Include: - - Overall code quality assessment - - Top 5 critical issues to address - - Recommended next steps - """) - |> Enum.filter(&(&1.type == :assistant)) - |> Enum.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - - %{ - summary: summary, - detailed_analyses: analyses, - generated_at: DateTime.utc_now() - } - end -end -``` +# Check authentication and environment readiness +ClaudeCodeSDK.AuthChecker.diagnose() -### Documentation Generator **(FUTURE/PLANNED)** - -```elixir -defmodule DocGenerator do # FUTURE/PLANNED - Not yet implemented - def generate_api_docs(module_files) do - module_files - |> Enum.map(&extract_module_info/1) - |> Enum.map(&generate_module_docs/1) - |> combine_into_full_docs() - end - - defp extract_module_info(file_path) do - content = File.read!(file_path) - - # Extract basic info first - module_info = - ClaudeCodeSDK.query(""" - Extract the following information from this Elixir module: - - 1. Module name and purpose - 2. Public functions with their signatures - 3. Key dependencies and imports - 4. Any existing documentation - - Module file: - ```elixir - #{content} - ``` - - Format as structured data. - """) - |> Enum.filter(&(&1.type == :assistant)) - |> Enum.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - - %{ - file: file_path, - content: content, - extracted_info: module_info - } - end - - defp generate_module_docs(%{content: content, extracted_info: info} = module_data) do - docs = - ClaudeCodeSDK.query(""" - Generate comprehensive documentation for this Elixir module: - - Extracted information: - #{info} - - Full module code: - ```elixir - #{content} - ``` - - Generate: - 1. Module overview and purpose - 2. Installation/usage instructions - 3. Function documentation with examples - 4. Common use cases - 5. Error handling patterns - - Use proper Markdown formatting. - """) - |> Enum.filter(&(&1.type == :assistant)) - |> Enum.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - - Map.put(module_data, :documentation, docs) - end - - defp combine_into_full_docs(module_docs) do - combined_content = - module_docs - |> Enum.map(& &1.documentation) - |> Enum.join("\n\n---\n\n") - - full_docs = - ClaudeCodeSDK.query(""" - Create a comprehensive API documentation index from these individual module docs: - - #{combined_content} - - Generate: - 1. Table of contents - 2. Quick start guide - 3. Module cross-references - 4. Common patterns and examples - """) - |> Enum.filter(&(&1.type == :assistant)) - |> Enum.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - - %{ - index: full_docs, - modules: module_docs, - generated_at: DateTime.utc_now() - } - end -end +# Run a full diagnostic check +ClaudeCodeSDK.DebugMode.run_diagnostics() ``` +If issues persist, re-run the setup task: `mix claude.setup_token`. -### Test Generator **(FUTURE/PLANNED)** +### Debug Mode +Use `DebugMode` for detailed query analysis. ```elixir -defmodule TestGenerator do # FUTURE/PLANNED - Not yet implemented - def generate_tests_for_module(module_file) do - content = File.read!(module_file) - - # Generate comprehensive test suite - test_content = - ClaudeCodeSDK.query(""" - Generate a comprehensive ExUnit test suite for this Elixir module: - - ```elixir - #{content} - ``` - - Include: - 1. Setup and teardown functions - 2. Tests for all public functions - 3. Edge case testing - 4. Error condition testing - 5. Property-based test suggestions - 6. Mock/stub patterns where needed - - Follow ExUnit best practices and naming conventions. - """, %ClaudeCodeSDK.Options{max_turns: 5}) - |> Enum.filter(&(&1.type == :assistant)) - |> Enum.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - - # Clean up and format the test code - clean_tests = - ClaudeCodeSDK.query(""" - Clean up and optimize this test code: - - #{test_content} - - Ensure: - 1. Proper indentation and formatting - 2. No duplicate test cases - 3. Clear test descriptions - 4. Efficient test structure - """) - |> Enum.filter(&(&1.type == :assistant)) - |> Enum.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - - # Generate test file - module_name = extract_module_name(content) - test_file_path = "test/#{Macro.underscore(module_name)}_test.exs" - - %{ - test_content: clean_tests, - test_file_path: test_file_path, - original_module: module_file - } - end - - defp extract_module_name(content) do - case Regex.run(~r/defmodule\s+([A-Z][A-Za-z0-9_.]*)/m, content) do - [_, module_name] -> module_name - _ -> "UnknownModule" - end - end - - def run_generated_tests(test_info) do - # Write test file - File.write!(test_info.test_file_path, test_info.test_content) - - # Run tests and capture output - case System.cmd("mix", ["test", test_info.test_file_path]) do - {output, 0} -> - {:ok, output} - {output, code} -> - # Try to fix failing tests - fix_tests(test_info, output) - end - end - - defp fix_tests(test_info, error_output) do - fixed_content = - ClaudeCodeSDK.query(""" - Fix the failing tests based on this error output: - - Error output: - #{error_output} - - Original test file: - #{test_info.test_content} - - Provide the corrected test file content. - """) - |> Enum.filter(&(&1.type == :assistant)) - |> Enum.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - - # Write fixed tests and try again - File.write!(test_info.test_file_path, fixed_content) - System.cmd("mix", ["test", test_info.test_file_path]) - end -end -``` +alias ClaudeCodeSDK.DebugMode -### Interactive Development Assistant **(FUTURE/PLANNED)** +# Execute a query with detailed logging and timing +messages = DebugMode.debug_query("My test query") -```elixir -defmodule DevAssistant do # FUTURE/PLANNED - Not yet implemented - use GenServer - - # Client API - def start_link(_opts) do - GenServer.start_link(__MODULE__, %{}, name: __MODULE__) - end - - def ask(question) do - GenServer.call(__MODULE__, {:ask, question}) - end - - def start_pair_programming_session(file_path) do - GenServer.call(__MODULE__, {:start_session, file_path}) - end - - def continue_session(input) do - GenServer.call(__MODULE__, {:continue, input}) - end - - # Server implementation - def init(_) do - {:ok, %{current_session: nil, context: %{}}} - end - - def handle_call({:ask, question}, _from, state) do - response = - ClaudeCodeSDK.query(question) - |> Enum.filter(&(&1.type == :assistant)) - |> Enum.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - - {:reply, response, state} - end - - def handle_call({:start_session, file_path}, _from, state) do - content = File.read!(file_path) - - initial_prompt = """ - I'm starting a pair programming session on this file: #{file_path} - - ``` - #{content} - ``` - - Please analyze the code and suggest what we should work on together. - """ - - session_stream = ClaudeCodeSDK.query(initial_prompt) - session_id = extract_session_id(session_stream) - - response = - session_stream - |> Enum.filter(&(&1.type == :assistant)) - |> Enum.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - - new_state = %{ - state | - current_session: session_id, - context: %{file_path: file_path, content: content} - } - - {:reply, response, new_state} - end - - def handle_call({:continue, input}, _from, %{current_session: nil} = state) do - {:reply, "No active session. Start with start_pair_programming_session/1", state} - end - - def handle_call({:continue, input}, _from, %{current_session: session_id} = state) do - response = - ClaudeCodeSDK.resume(session_id, input) - |> Enum.filter(&(&1.type == :assistant)) - |> Enum.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - - {:reply, response, state} - end - - defp extract_session_id(stream) do - stream - |> Enum.find(&(&1.type == :system)) - |> case do - %{data: %{session_id: id}} -> id - _ -> nil - end - end -end +# Benchmark performance +results = DebugMode.benchmark("Test query", nil, 3) -# Usage example -# DevAssistant.start_link([]) -# DevAssistant.ask("How do I optimize this Elixir function for performance?") -# DevAssistant.start_pair_programming_session("lib/my_module.ex") -# DevAssistant.continue_session("Let's add better error handling") +# Analyze message statistics +stats = DebugMode.analyze_messages(messages) ``` -### Automated Refactoring Tool **(FUTURE/PLANNED)** - -```elixir -defmodule RefactoringTool do # FUTURE/PLANNED - Not yet implemented - def refactor_codebase(project_path, refactoring_goals) do - project_path - |> scan_codebase() - |> analyze_refactoring_opportunities(refactoring_goals) - |> plan_refactoring_steps() - |> execute_refactoring() - |> validate_refactoring() - end - - defp scan_codebase(project_path) do - elixir_files = Path.wildcard("#{project_path}/**/*.{ex,exs}") - - file_analyses = - elixir_files - |> Enum.map(fn file -> - content = File.read!(file) - - analysis = - ClaudeCodeSDK.query(""" - Analyze this Elixir file for refactoring opportunities: - - File: #{file} - ```elixir - #{content} - ``` - - Identify: - 1. Code smells - 2. Duplication patterns - 3. Complexity issues - 4. Naming improvements - 5. Structure optimization opportunities - """) - |> extract_assistant_content() - - %{file: file, content: content, analysis: analysis} - end) - - %{project_path: project_path, files: file_analyses} - end - - defp analyze_refactoring_opportunities(%{files: files}, goals) do - combined_analysis = - files - |> Enum.map(& &1.analysis) - |> Enum.join("\n\n---\n\n") - - prioritized_opportunities = - ClaudeCodeSDK.query(""" - Based on these file analyses and refactoring goals: - - Goals: #{Enum.join(goals, ", ")} - - Analyses: - #{combined_analysis} - - Create a prioritized list of refactoring opportunities that address the goals. - Include impact assessment and effort estimation for each. - """) - |> extract_assistant_content() - - %{files: files, opportunities: prioritized_opportunities} - end - - defp plan_refactoring_steps(%{opportunities: opportunities} = data) do - refactoring_plan = - ClaudeCodeSDK.query(""" - Create a detailed refactoring execution plan: - - Opportunities: - #{opportunities} - - Generate: - 1. Step-by-step refactoring sequence - 2. Dependencies between steps - 3. Risk assessment for each step - 4. Rollback strategies - 5. Testing requirements - """) - |> extract_assistant_content() - - Map.put(data, :plan, refactoring_plan) - end - - defp execute_refactoring(%{files: files, plan: plan} = data) do - IO.puts("🔧 Executing refactoring plan...") - - # This would implement actual refactoring - # For now, we'll generate the refactored code - refactored_files = - files - |> Enum.map(fn %{file: file, content: content} -> - refactored_content = - ClaudeCodeSDK.query(""" - Refactor this Elixir file according to the plan: - - Plan: - #{plan} - - Original file (#{file}): - ```elixir - #{content} - ``` - - Provide the refactored code with explanations of changes made. - """) - |> extract_assistant_content() - - %{file: file, original: content, refactored: refactored_content} - end) - - Map.put(data, :refactored_files, refactored_files) - end - - defp validate_refactoring(%{refactored_files: refactored_files} = data) do - validation_report = - ClaudeCodeSDK.query(""" - Validate this refactoring by comparing original and refactored code: - - #{Enum.map_join(refactored_files, "\n\n", fn %{file: file, original: orig, refactored: ref} -> - "File: #{file}\nOriginal:\n#{orig}\n\nRefactored:\n#{ref}" - end)} - - Check: - 1. Functionality preservation - 2. Code quality improvements - 3. Potential issues introduced - 4. Test coverage impact - """) - |> extract_assistant_content() - - Map.put(data, :validation: validation_report) - end - - defp extract_assistant_content(stream) do - stream - |> Enum.filter(&(&1.type == :assistant)) - |> Enum.map(&ContentExtractor.extract_text/1) - |> Enum.join("\n") - end -end -``` +## Examples & Use Cases (FUTURE/PLANNED) +This section will be expanded with detailed examples for common use cases: +- **Code Analysis Pipeline**: A multi-stage pipeline to analyze code quality, find bugs, and suggest improvements. +- **Documentation Generator**: A tool to automatically generate documentation for Elixir modules. +- **Test Generator**: A tool to create ExUnit test suites for existing code. +- **Interactive Development Assistant**: A command-line tool for pair programming with Claude. +- **Automated Refactoring Tool**: A tool to perform complex, multi-file refactorings. This comprehensive manual covers all aspects of the Claude Code SDK for Elixir, from basic usage to advanced integration patterns. It provides practical examples for real-world use cases while maintaining security and performance best practices. \ No newline at end of file diff --git a/README.md b/README.md index 1d10d53..81ca35e 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,16 @@ -# Claude Code SDK for Elixir +

+ Claude Code Elixir SDK Logo +

-[![CI](https://github.com/nshkrdotcom/claude_code_sdk_elixir/actions/workflows/elixir.yaml/badge.svg)](https://github.com/nshkrdotcom/claude_code_sdk_elixir/actions/workflows/elixir.yaml) +

Claude Code SDK for Elixir

+ +

+ + CI + +

+ +> **Note:** This SDK is fully compatible with the **Claude Code 2.0 CLI** and its latest features, including advanced authentication, model selection, and session management. 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. diff --git a/assets/logo.svg b/assets/logo.svg new file mode 100644 index 0000000..cec0cd5 --- /dev/null +++ b/assets/logo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file