Packages
claude_code
0.36.4
0.36.5
0.36.4
0.36.3
0.36.2
0.36.1
0.36.0
0.35.0
0.34.0
0.33.1
0.32.2
0.32.0
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.1
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Claude Agent SDK for Elixir – Build AI agents with Claude Code
Current section
Files
Jump to
Current section
Files
lib/claude_code/adapter/port/resolver.ex
defmodule ClaudeCode.Adapter.Port.Resolver do
@moduledoc """
Resolves the Claude CLI binary for the local adapter.
This module handles finding the CLI binary using one of three resolution modes:
- `:bundled` (default) -- Uses the binary in `priv/bin/`. Auto-installs if missing.
Verifies version matches the SDK's pinned version and re-installs on mismatch.
- `:global` -- Finds an existing system install via PATH or common locations. No auto-install.
- `"/path/to/claude"` -- Uses that exact binary path.
Can also be configured via application config:
config :claude_code, cli_path: :global
"""
alias ClaudeCode.Adapter.Port.Installer
require Logger
@doc """
Finds the claude binary using the configured resolution mode.
## Resolution Modes
The `:cli_path` option (or app config) determines how the binary is found:
- `:bundled` (default) -- Use priv/bin/ binary. Auto-installs if missing.
Verifies the installed version matches `Installer.configured_version()` and
re-installs on mismatch.
- `:global` -- Finds an existing system install via PATH or common locations.
Does not auto-install. Returns `{:error, :not_found}` if not found.
- `"/path/to/claude"` -- Uses that exact binary. Returns `{:error, :not_found}`
if it doesn't exist.
## Examples
iex> ClaudeCode.Adapter.Port.Resolver.find_binary()
{:ok, "/path/to/priv/bin/claude"}
iex> ClaudeCode.Adapter.Port.Resolver.find_binary(cli_path: :global)
{:ok, "/usr/local/bin/claude"}
iex> ClaudeCode.Adapter.Port.Resolver.find_binary(cli_path: "/custom/path/claude")
{:ok, "/custom/path/claude"}
"""
@spec find_binary(keyword()) :: {:ok, String.t()} | {:error, term()}
def find_binary(opts \\ []) do
mode = Keyword.get(opts, :cli_path) || Application.get_env(:claude_code, :cli_path, :bundled)
case mode do
:bundled -> find_bundled()
:global -> find_global()
path when is_binary(path) -> find_explicit(path)
end
end
@doc """
Validates that the Claude CLI is properly installed and accessible.
"""
@spec validate_installation(keyword()) :: :ok | {:error, term()}
def validate_installation(opts \\ []) do
with {:ok, path} <- find_binary(opts),
{output, 0} <- System.cmd(path, ["--version"], stderr_to_stdout: true),
true <- String.contains?(output, "Claude Code") do
:ok
else
false ->
{:error, {:invalid_binary, "Binary does not appear to be Claude CLI"}}
{error_output, exit_code} when is_integer(exit_code) ->
{:error, {:cli_error, error_output}}
{:error, reason} ->
{:error, wrap_not_found(reason)}
end
end
# -- Private: binary resolution -----------------------------------------------
defp find_bundled do
bundled = Installer.bundled_path()
if File.exists?(bundled) do
case check_bundled_version(bundled) do
:ok -> {:ok, bundled}
{:error, _} -> install_bundled()
end
else
install_bundled()
end
end
defp find_global do
cond do
path = System.find_executable("claude") -> {:ok, path}
path = Installer.find_in_common_locations() -> {:ok, path}
true -> {:error, :not_found}
end
end
defp find_explicit(path) do
if File.exists?(path), do: {:ok, path}, else: {:error, :not_found}
end
defp check_bundled_version(path) do
expected = Installer.configured_version()
case Installer.version_of(path) do
{:ok, ^expected} -> :ok
{:ok, _other} -> {:error, :version_mismatch}
{:error, _} -> {:error, :version_check_failed}
end
end
defp install_bundled do
Installer.install!()
bundled = Installer.bundled_path()
if File.exists?(bundled), do: {:ok, bundled}, else: {:error, :install_failed}
rescue
e ->
Logger.warning("Auto-install of Claude CLI failed: #{Exception.message(e)}")
{:error, :install_failed}
end
# -- Private: error helpers ---------------------------------------------------
defp wrap_not_found(:not_found) do
{:cli_not_found, Installer.cli_not_found_message()}
end
defp wrap_not_found(reason) do
{:cli_not_found, "CLI resolution failed: #{inspect(reason)}"}
end
end