Elixir wrapper for the Claude Code CLI.
Two ways to drive claude from Elixir:
ClaudeWrapper.DuplexSession-- aGenServerthat holds oneclaudesubprocess open for the lifetime of a conversation. Streams partial tokens as they arrive, supports clean mid-turn cancellation, and routes tool-permission prompts back to the host. The right fit for chat UIs, agent runtimes, Phoenix servers, and any long-running OTP host.- The convenience
ClaudeWrapper.query/2andClaudeWrapper.stream/2functions in this module -- one subprocess per turn, simple request/response. The right fit formixtasks, escripts, batch jobs, and anything else that runs and exits.
Both modes are first-class. Pick whichever matches the lifecycle
of the host using claude_wrapper.
Long-lived chat-style sessions (DuplexSession)
config = ClaudeWrapper.Config.new(working_dir: ".")
{:ok, pid} = ClaudeWrapper.DuplexSession.start_link(config: config)
ClaudeWrapper.DuplexSession.subscribe(pid)
{:ok, result} = ClaudeWrapper.DuplexSession.send(pid, "Explain this codebase.")
# Subscriber inbox now holds streaming events:
# {:claude, {:assistant, msg}}
# {:claude, {:stream_event, msg}} -- partial token deltas
# {:claude, {:result, %ClaudeWrapper.Result{}}}
ClaudeWrapper.DuplexSession.close(pid)See ClaudeWrapper.DuplexSession for the permission callback,
interrupt API, and full event vocabulary. ClaudeWrapper.DuplexIEx
wraps it with REPL helpers that stream tokens to stdout live.
One-shot queries
# One-shot query (convenience -- uses default config)
{:ok, result} = ClaudeWrapper.query("Explain this error: ...")
# With options
{:ok, result} = ClaudeWrapper.query("Fix the bug in lib/foo.ex",
model: "sonnet",
working_dir: "/path/to/project",
max_turns: 5,
permission_mode: :bypass_permissions
)
# Streaming
ClaudeWrapper.stream("Implement the feature described in issue #42",
working_dir: "/path/to/project"
)
|> Stream.each(fn event -> IO.inspect(event.type) end)
|> Stream.run()
# Full control via Query builder
config = ClaudeWrapper.Config.new(working_dir: "/path/to/project")
ClaudeWrapper.Query.new("Fix the tests")
|> ClaudeWrapper.Query.model("sonnet")
|> ClaudeWrapper.Query.dangerously_skip_permissions()
|> ClaudeWrapper.Query.max_turns(10)
|> ClaudeWrapper.Query.execute(config)Multi-turn continuity for one-shot mode (Session)
ClaudeWrapper.Session threads --resume <session_id> across
one-shot calls so you can have a multi-turn conversation without
holding a subprocess open. Use it when you want a struct-passing
API rather than a process API, or when turns are far enough apart
that cold-start cost doesn't matter.
Other modules
ClaudeWrapper.Query-- the query builderClaudeWrapper.SessionServer-- supervised wrapper forSessionClaudeWrapper.McpConfig-- programmatic.mcp.jsonbuilderClaudeWrapper.Retry-- exponential backoffClaudeWrapper.Telemetry--:telemetryspans for exec/stream/sessionClaudeWrapper.Commands.{Auth, Mcp, Plugin, Marketplace, Doctor, Version}-- CLI subcommand wrappers
Binary discovery
The claude binary is found via (in order):
:binaryoption passed directlyCLAUDE_CLIenvironment variable- System PATH lookup
Summary
Functions
List configured agents.
Check authentication status.
Run claude doctor.
Execute a one-shot query and return the result.
Run an arbitrary CLI command that isn't wrapped by a dedicated module.
Execute a query and return a lazy stream of %StreamEvent{} structs.
Get the CLI version.
Functions
List configured agents.
Returns a list of agent maps with :name and :model keys.
Options
:setting_sources- comma-separated setting sources (e.g."user,project")
Check authentication status.
Run claude doctor.
@spec query( String.t(), keyword() ) :: {:ok, ClaudeWrapper.Result.t()} | {:error, term()}
Execute a one-shot query and return the result.
Convenience wrapper that builds a Config and Query from keyword options.
Returns {:ok, %Result{}} on success or {:error, reason} on failure.
Options
Config options (passed to ClaudeWrapper.Config.new/1):
:binary- Path to claude binary:working_dir- Working directory:env- Environment variables:timeout- Timeout in ms:verbose- Enable verbose output:debug- Enable debug output
Query options (passed to Query builder):
:model- Model name:system_prompt- System prompt override:max_turns- Max turns:max_budget_usd- Budget limit:permission_mode- Permission mode atom:dangerously_skip_permissions- Bypass permissions (boolean):session_id- Session ID:continue_session- Continue recent session (boolean):resume- Resume session ID
Run an arbitrary CLI command that isn't wrapped by a dedicated module.
This is the escape hatch for new or experimental CLI subcommands.
Examples
ClaudeWrapper.raw(["config", "list"])
ClaudeWrapper.raw(["plugin", "install", "my-plugin"], working_dir: "/tmp")
@spec stream( String.t(), keyword() ) :: Enumerable.t()
Execute a query and return a lazy stream of %StreamEvent{} structs.
The subprocess starts when the stream is consumed. Accepts the same
options as query/2.
Get the CLI version.