Examples
View SourceThis document provides real-world examples of using ClaudeCode in different scenarios.
Table of Contents
- CLI Applications
- Web Applications
- Phoenix LiveView Integration
- Batch Processing
- Code Analysis Tools
- Testing Applications
CLI Applications
Interactive Code Assistant
Build a CLI tool that helps with coding tasks:
# lib/code_assistant.ex
defmodule CodeAssistant do
@moduledoc """
Interactive CLI tool for code assistance using ClaudeCode.
"""
def main(args) do
case setup_session() do
{:ok, session} ->
run_interactive_loop(session, args)
ClaudeCode.stop(session)
{:error, reason} ->
IO.puts("Failed to start: #{reason}")
System.halt(1)
end
end
defp setup_session do
ClaudeCode.start_link(
api_key: System.get_env("ANTHROPIC_API_KEY"),
system_prompt: """
You are an expert Elixir developer assistant. Help with:
- Code review and suggestions
- Debugging issues
- Writing tests
- Performance optimization
- Best practices
""",
allowed_tools: ["View", "Edit", "Bash(git:*)"],
timeout: 300_000
)
end
defp run_interactive_loop(session, []) do
IO.puts("🚀 Code Assistant Ready! (type 'quit' to exit)")
interactive_loop(session)
end
defp run_interactive_loop(session, args) do
# Non-interactive mode - process arguments as single query
prompt = Enum.join(args, " ")
session
|> ClaudeCode.query(prompt)
|> ClaudeCode.Stream.text_content()
|> Enum.each(&IO.write/1)
IO.puts("\n")
end
defp interactive_loop(session) do
case IO.gets("code_assistant> ") |> String.trim() do
"quit" ->
IO.puts("Goodbye! 👋")
"" ->
interactive_loop(session)
prompt ->
handle_query(session, prompt)
interactive_loop(session)
end
end
defp handle_query(session, prompt) do
session
|> ClaudeCode.query(prompt)
|> ClaudeCode.Stream.text_content()
|> Enum.each(&IO.write/1)
IO.puts("\n")
rescue
error ->
IO.puts("Error: #{inspect(error)}")
end
end
Usage:
# Interactive mode
mix run -e "CodeAssistant.main([])"
# Single query mode
mix run -e "CodeAssistant.main(['Review', 'this', 'function'])"
Web Applications
Claude Service for Phoenix Apps
Integrate ClaudeCode into a Phoenix application:
# lib/my_app/claude_service.ex
defmodule MyApp.ClaudeService do
@moduledoc """
Service for managing Claude interactions in a Phoenix application.
"""
use GenServer
require Logger
# Client API
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def ask(prompt, options \\ []) do
GenServer.call(__MODULE__, {:ask, prompt, options}, 30_000)
end
def ask_async(prompt, options \\ []) do
GenServer.cast(__MODULE__, {:ask_async, prompt, options, self()})
end
def stream_query(prompt, options \\ []) do
GenServer.call(__MODULE__, {:stream, prompt, options})
end
# Server Implementation
def init(opts) do
case start_claude_session(opts) do
{:ok, session} ->
Logger.info("ClaudeService started successfully")
{:ok, %{session: session, active_streams: %{}}}
{:error, reason} ->
Logger.error("Failed to start ClaudeService: #{inspect(reason)}")
{:stop, reason}
end
end
def handle_call({:ask, prompt, options}, _from, %{session: session} = state) do
case ClaudeCode.query_sync(session, prompt, options) do
{:ok, response} ->
{:reply, {:ok, response}, state}
{:error, reason} ->
Logger.warning("Claude query failed: #{inspect(reason)}")
{:reply, {:error, reason}, state}
end
end
def handle_call({:stream, prompt, options}, {pid, _ref}, %{session: session} = state) do
try do
stream = ClaudeCode.query(session, prompt, options)
stream_id = make_ref()
# Store the stream for cleanup
new_state = put_in(state.active_streams[stream_id], {pid, stream})
{:reply, {:ok, stream_id, stream}, new_state}
rescue
error ->
{:reply, {:error, error}, state}
end
end
def handle_cast({:ask_async, prompt, options, caller_pid}, %{session: session} = state) do
Task.start(fn ->
case ClaudeCode.query_sync(session, prompt, options) do
{:ok, response} ->
send(caller_pid, {:claude_response, {:ok, response}})
{:error, reason} ->
send(caller_pid, {:claude_response, {:error, reason}})
end
end)
{:noreply, state}
end
# Cleanup completed streams
def handle_info({:DOWN, _ref, :process, pid, _reason}, state) do
new_streams = state.active_streams
|> Enum.reject(fn {_id, {stream_pid, _stream}} -> stream_pid == pid end)
|> Map.new()
{:noreply, %{state | active_streams: new_streams}}
end
defp start_claude_session(opts) do
claude_opts = [
api_key: System.get_env("ANTHROPIC_API_KEY"),
system_prompt: "You are a helpful assistant for a web application",
allowed_tools: ["View"],
timeout: 180_000
] ++ opts
ClaudeCode.start_link(claude_opts)
end
end
Phoenix Controller Integration
# lib/my_app_web/controllers/claude_controller.ex
defmodule MyAppWeb.ClaudeController do
use MyAppWeb, :controller
alias MyApp.ClaudeService
def ask(conn, %{"prompt" => prompt}) do
case ClaudeService.ask(prompt) do
{:ok, response} ->
json(conn, %{response: response})
{:error, reason} ->
conn
|> put_status(:bad_request)
|> json(%{error: inspect(reason)})
end
end
def stream(conn, %{"prompt" => prompt}) do
case ClaudeService.stream_query(prompt) do
{:ok, _stream_id, stream} ->
conn = put_resp_header(conn, "content-type", "text/plain")
conn = send_chunked(conn, 200)
stream
|> ClaudeCode.Stream.text_content()
|> Enum.reduce_while(conn, fn chunk, conn ->
case chunk(conn, chunk) do
{:ok, conn} -> {:cont, conn}
{:error, :closed} -> {:halt, conn}
end
end)
conn
{:error, reason} ->
conn
|> put_status(:bad_request)
|> json(%{error: inspect(reason)})
end
end
end
Phoenix LiveView Integration
Real-time Chat Interface
# lib/my_app_web/live/claude_chat_live.ex
defmodule MyAppWeb.ClaudeChatLive do
use MyAppWeb, :live_view
alias MyApp.ClaudeService
def mount(_params, _session, socket) do
socket = assign(socket,
messages: [],
current_input: "",
loading: false,
stream_content: "",
stream_active: false
)
{:ok, socket}
end
def handle_event("send_message", %{"message" => message}, socket) do
if String.trim(message) != "" do
# Add user message
socket = add_message(socket, "user", message)
socket = assign(socket, current_input: "", loading: true, stream_active: true)
# Start streaming Claude's response
case ClaudeService.stream_query(message) do
{:ok, _stream_id, stream} ->
start_streaming(stream)
{:noreply, socket}
{:error, reason} ->
socket = add_message(socket, "error", "Error: #{inspect(reason)}")
socket = assign(socket, loading: false, stream_active: false)
{:noreply, socket}
end
else
{:noreply, socket}
end
end
def handle_event("update_input", %{"message" => message}, socket) do
{:noreply, assign(socket, current_input: message)}
end
def handle_info({:stream_chunk, chunk}, socket) do
socket = assign(socket, stream_content: socket.assigns.stream_content <> chunk)
{:noreply, socket}
end
def handle_info(:stream_complete, socket) do
# Add the complete streamed response as a message
socket = add_message(socket, "claude", socket.assigns.stream_content)
socket = assign(socket,
loading: false,
stream_active: false,
stream_content: ""
)
{:noreply, socket}
end
def handle_info(:stream_error, socket) do
socket = add_message(socket, "error", "Stream error occurred")
socket = assign(socket, loading: false, stream_active: false, stream_content: "")
{:noreply, socket}
end
defp start_streaming(stream) do
parent = self()
Task.start(fn ->
try do
stream
|> ClaudeCode.Stream.text_content()
|> Enum.each(fn chunk ->
send(parent, {:stream_chunk, chunk})
end)
send(parent, :stream_complete)
rescue
_error ->
send(parent, :stream_error)
end
end)
end
defp add_message(socket, role, content) do
message = %{role: role, content: content, timestamp: DateTime.utc_now()}
assign(socket, messages: socket.assigns.messages ++ [message])
end
def render(assigns) do
~H"""
<div class="claude-chat">
<div class="messages" id="messages" phx-update="ignore">
<%= for message <- @messages do %>
<div class={"message message-#{message.role}"}>
<strong><%= String.capitalize(message.role) %>:</strong>
<span><%= message.content %></span>
</div>
<% end %>
<%= if @stream_active and @stream_content != "" do %>
<div class="message message-claude streaming">
<strong>Claude:</strong>
<span><%= @stream_content %></span>
<span class="cursor">▊</span>
</div>
<% end %>
</div>
<form phx-submit="send_message" class="input-form">
<input
type="text"
name="message"
value={@current_input}
phx-change="update_input"
placeholder="Ask Claude something..."
disabled={@loading}
autocomplete="off"
/>
<button type="submit" disabled={@loading}>
<%= if @loading, do: "...", else: "Send" %>
</button>
</form>
</div>
"""
end
end
Batch Processing
File Analysis Pipeline
# lib/file_analyzer.ex
defmodule FileAnalyzer do
@moduledoc """
Analyze multiple files using ClaudeCode with concurrent processing.
"""
def analyze_directory(path, pattern \\ "**/*.ex") do
files = Path.wildcard(Path.join(path, pattern))
# Start multiple Claude sessions for parallel processing
session_count = min(System.schedulers_online(), 4)
sessions = start_sessions(session_count)
try do
files
|> Task.async_stream(
fn file -> analyze_file(sessions, file) end,
max_concurrency: session_count,
timeout: 300_000
)
|> Enum.map(fn {:ok, result} -> result end)
after
stop_sessions(sessions)
end
end
defp start_sessions(count) do
1..count
|> Enum.map(fn _i ->
{:ok, session} = ClaudeCode.start_link(
api_key: System.get_env("ANTHROPIC_API_KEY"),
system_prompt: """
You are a code analysis expert. Analyze Elixir files and provide:
1. Code quality assessment
2. Potential improvements
3. Bug detection
4. Performance suggestions
""",
allowed_tools: ["View"],
timeout: 180_000
)
session
end)
end
defp stop_sessions(sessions) do
Enum.each(sessions, &ClaudeCode.stop/1)
end
defp analyze_file(sessions, file_path) do
# Round-robin session selection
session_index = :erlang.phash2(file_path, length(sessions))
session = Enum.at(sessions, session_index)
prompt = """
Please analyze this Elixir file: #{file_path}
Provide a concise analysis including:
- Overall code quality (1-10)
- Key issues found
- Improvement suggestions
- Estimated complexity
"""
case ClaudeCode.query_sync(session, prompt) do
{:ok, analysis} ->
%{
file: file_path,
analysis: analysis,
analyzed_at: DateTime.utc_now()
}
{:error, reason} ->
%{
file: file_path,
error: reason,
analyzed_at: DateTime.utc_now()
}
end
end
def generate_report(results) do
successful = Enum.filter(results, &Map.has_key?(&1, :analysis))
failed = Enum.filter(results, &Map.has_key?(&1, :error))
"""
# Code Analysis Report
**Files Analyzed:** #{length(results)}
**Successful:** #{length(successful)}
**Failed:** #{length(failed)}
**Generated:** #{DateTime.utc_now()}
## Analysis Results
#{Enum.map_join(successful, "\n\n", &format_analysis/1)}
#{if length(failed) > 0 do
"""
## Failed Analyses
#{Enum.map_join(failed, "\n", &"- #{&1.file}: #{inspect(&1.error)}")}
"""
else
""
end}
"""
end
defp format_analysis(result) do
"""
### #{result.file}
#{result.analysis}
"""
end
end
# Usage:
# results = FileAnalyzer.analyze_directory("lib/")
# report = FileAnalyzer.generate_report(results)
# File.write!("analysis_report.md", report)
Code Analysis Tools
Dependency Analyzer
# lib/dependency_analyzer.ex
defmodule DependencyAnalyzer do
@moduledoc """
Analyze project dependencies using ClaudeCode.
"""
def analyze_mix_file(project_path \\ ".") do
mix_file = Path.join(project_path, "mix.exs")
lock_file = Path.join(project_path, "mix.lock")
{:ok, session} = ClaudeCode.start_link(
api_key: System.get_env("ANTHROPIC_API_KEY"),
system_prompt: """
You are an Elixir dependency expert. Analyze mix.exs and mix.lock files to provide:
1. Dependency security assessment
2. Version recommendations
3. Potential conflicts
4. Unused dependencies
5. Performance impact
""",
allowed_tools: ["View"]
)
prompt = """
Please analyze the dependencies in this Elixir project.
Look at both mix.exs and mix.lock files and provide:
- Security vulnerabilities
- Outdated dependencies
- Dependency conflicts
- Recommendations for optimization
Files to analyze:
- #{mix_file}
- #{lock_file}
"""
case ClaudeCode.query_sync(session, prompt) do
{:ok, analysis} ->
ClaudeCode.stop(session)
{:ok, analysis}
error ->
ClaudeCode.stop(session)
error
end
end
def compare_with_alternatives(dependency_name) do
{:ok, session} = ClaudeCode.start_link(
api_key: System.get_env("ANTHROPIC_API_KEY"),
system_prompt: "You are an Elixir ecosystem expert.",
timeout: 120_000
)
prompt = """
Compare the Elixir dependency "#{dependency_name}" with its alternatives.
Provide:
1. Popular alternatives
2. Pros/cons comparison
3. Migration difficulty
4. Performance differences
5. Community adoption
"""
case ClaudeCode.query_sync(session, prompt) do
{:ok, comparison} ->
ClaudeCode.stop(session)
{:ok, comparison}
error ->
ClaudeCode.stop(session)
error
end
end
end
Testing Applications
Test Generator
# lib/test_generator.ex
defmodule TestGenerator do
@moduledoc """
Generate tests for Elixir modules using ClaudeCode.
"""
def generate_tests_for_module(module_file) do
{:ok, session} = ClaudeCode.start_link(
api_key: System.get_env("ANTHROPIC_API_KEY"),
system_prompt: """
You are an Elixir testing expert. Generate comprehensive ExUnit tests including:
1. Happy path tests
2. Edge case tests
3. Error condition tests
4. Property-based tests when appropriate
5. Mock usage when needed
""",
allowed_tools: ["View", "Edit"],
timeout: 300_000
)
prompt = """
Please generate comprehensive ExUnit tests for the module in: #{module_file}
Create a complete test file that covers:
- All public functions
- Edge cases and error conditions
- Property-based tests where appropriate
- Proper setup and teardown
Follow Elixir testing best practices and use descriptive test names.
"""
session
|> ClaudeCode.query(prompt)
|> ClaudeCode.Stream.text_content()
|> Enum.to_list()
|> Enum.join()
|> then(fn test_content ->
ClaudeCode.stop(session)
{:ok, test_content}
end)
rescue
error ->
ClaudeCode.stop(session)
{:error, error}
end
def improve_existing_tests(test_file) do
{:ok, session} = ClaudeCode.start_link(
api_key: System.get_env("ANTHROPIC_API_KEY"),
system_prompt: """
You are an Elixir testing expert. Improve existing test files by:
1. Adding missing test cases
2. Improving test descriptions
3. Adding property-based tests
4. Optimizing test structure
5. Adding better assertions
""",
allowed_tools: ["View", "Edit"]
)
prompt = """
Please analyze and improve the test file: #{test_file}
Suggest improvements for:
- Test coverage gaps
- Better test organization
- More descriptive test names
- Additional edge cases
- Performance test optimizations
"""
case ClaudeCode.query_sync(session, prompt) do
{:ok, suggestions} ->
ClaudeCode.stop(session)
{:ok, suggestions}
error ->
ClaudeCode.stop(session)
error
end
end
end
# Usage:
# {:ok, tests} = TestGenerator.generate_tests_for_module("lib/my_module.ex")
# File.write!("test/my_module_test.exs", tests)
Performance Monitoring
Stream Performance Monitor
# lib/performance_monitor.ex
defmodule PerformanceMonitor do
@moduledoc """
Monitor ClaudeCode performance and stream metrics.
"""
def monitor_stream_performance(session, prompt) do
start_time = System.monotonic_time(:millisecond)
metrics = %{
start_time: start_time,
first_chunk_time: nil,
total_chunks: 0,
total_characters: 0,
end_time: nil
}
session
|> ClaudeCode.query(prompt)
|> ClaudeCode.Stream.text_content()
|> Stream.with_index()
|> Stream.map(fn {chunk, index} ->
current_time = System.monotonic_time(:millisecond)
metrics = if index == 0 do
%{metrics | first_chunk_time: current_time}
else
metrics
end
metrics = %{metrics |
total_chunks: index + 1,
total_characters: metrics.total_characters + String.length(chunk),
end_time: current_time
}
{chunk, metrics}
end)
|> Enum.reduce({[], nil}, fn {chunk, metrics}, {chunks, _} ->
{[chunk | chunks], metrics}
end)
|> case do
{chunks, final_metrics} ->
content = chunks |> Enum.reverse() |> Enum.join()
report = generate_performance_report(final_metrics)
{:ok, content, report}
end
end
defp generate_performance_report(metrics) do
%{
total_duration_ms: metrics.end_time - metrics.start_time,
time_to_first_chunk_ms: metrics.first_chunk_time - metrics.start_time,
total_chunks: metrics.total_chunks,
total_characters: metrics.total_characters,
characters_per_second: metrics.total_characters /
((metrics.end_time - metrics.start_time) / 1000),
chunks_per_second: metrics.total_chunks /
((metrics.end_time - metrics.start_time) / 1000)
}
end
end
# Usage:
# {:ok, session} = ClaudeCode.start_link(api_key: "...")
# {:ok, content, report} = PerformanceMonitor.monitor_stream_performance(
# session, "Write a long explanation of GenServers"
# )
# IO.inspect(report)
Error Recovery Patterns
Resilient Query Handler
defmodule ResilientClaudeHandler do
@moduledoc """
Handle Claude queries with automatic retry and error recovery.
"""
def query_with_retry(session, prompt, opts \\ []) do
max_retries = Keyword.get(opts, :max_retries, 3)
base_delay = Keyword.get(opts, :base_delay_ms, 1000)
do_query_with_retry(session, prompt, opts, max_retries, base_delay, 0)
end
defp do_query_with_retry(session, prompt, opts, max_retries, base_delay, attempt) do
case ClaudeCode.query_sync(session, prompt, opts) do
{:ok, response} ->
{:ok, response}
{:error, :timeout} when attempt < max_retries ->
delay = base_delay * :math.pow(2, attempt)
:timer.sleep(round(delay))
do_query_with_retry(session, prompt, opts, max_retries, base_delay, attempt + 1)
{:error, {:cli_exit, _}} when attempt < max_retries ->
# CLI crashed, might recover on retry
delay = base_delay * :math.pow(2, attempt)
:timer.sleep(round(delay))
do_query_with_retry(session, prompt, opts, max_retries, base_delay, attempt + 1)
error ->
{:error, {error, attempts: attempt + 1}}
end
end
def stream_with_recovery(session, prompt, opts \\ []) do
try do
session
|> ClaudeCode.query(prompt, opts)
|> ClaudeCode.Stream.text_content()
|> Stream.map(&{:ok, &1})
rescue
error ->
Stream.once({:error, error})
end
end
end
These examples demonstrate various real-world usage patterns for ClaudeCode, from simple CLI tools to complex web applications with streaming interfaces. Each example includes proper error handling and follows Elixir best practices.