ClaudeWrapper (ClaudeWrapper v0.7.0)

Copy Markdown View Source

Elixir wrapper for the Claude Code CLI.

Two ways to drive claude from Elixir:

  1. ClaudeWrapper.DuplexSession -- a GenServer that holds one claude subprocess 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.
  2. The convenience ClaudeWrapper.query/2 and ClaudeWrapper.stream/2 functions in this module -- one subprocess per turn, simple request/response. The right fit for mix tasks, 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

Binary discovery

The claude binary is found via (in order):

  1. :binary option passed directly
  2. CLAUDE_CLI environment variable
  3. 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

agents(opts \\ [])

@spec agents(keyword()) :: {:ok, [map()]} | {:error, term()}

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")

auth_status(opts \\ [])

@spec auth_status(keyword()) :: {:ok, map()} | {:error, term()}

Check authentication status.

doctor(opts \\ [])

@spec doctor(keyword()) :: {:ok, String.t()} | {:error, term()}

Run claude doctor.

query(prompt, opts \\ [])

@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

raw(args, opts \\ [])

@spec raw(
  [String.t()],
  keyword()
) :: {:ok, String.t()} | {:error, term()}

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")

stream(prompt, opts \\ [])

@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.

version(opts \\ [])

@spec version(keyword()) :: {:ok, map()} | {:error, term()}

Get the CLI version.