# `LlmCore.LLM.CLIProvider`
[🔗](https://github.com/fosferon/llm_core/blob/v0.3.0/lib/llm_core/llm/cli_provider.ex#L134)

Universal CLI-based LLM provider.

One module, any CLI. Configuration is pure data — loaded from TOML
or constructed in code. New CLI clients are added without writing
Elixir code.

## Default Providers

The following CLI providers ship as `type = "cli"` entries in
`priv/config/llm_core.toml`. Override or remove them via project
or global TOML overrides — no Elixir changes needed.

| Name | Binary | Notes |
|---|---|---|
| `:claude_code` | `claude` | Wraps with `/bin/sh` for stdin redirect |
| `:droid` | `droid` | Subcommand `exec`, rich flag set |
| `:pi_cli` | `pi` | Pi CLI non-interactive dispatch (`--print`) |
| `:kimi_cli` | `kimi-cli` | Kimi CLI with agent-file support |
| `:codex_cli` | `codex` | OpenAI Codex CLI |
| `:gemini_cli` | `gemini` | Google Gemini CLI |

## Usage

    {:ok, provider} = CLIProvider.from_config(:claude_code)

    # Check availability
    CLIProvider.available?(provider)
    #=> true

    # Send a prompt
    {:ok, response} = CLIProvider.send(provider, "Explain this code")

    # Stream
    {:ok, stream} = CLIProvider.stream(provider, "Write a story")

## Custom Provider (no code needed)

    config = %CLIProvider.Config{
      name: :my_tool,
      binary: "my-tool",
      default_timeout: 60_000,
      default_model: "v2",
      flags: %{model: "--model", temperature: "--temp"}
    }
    provider = CLIProvider.from_config(config)
    {:ok, response} = CLIProvider.send(provider, "hello", model: "v2")

# `t`

```elixir
@type t() :: %LlmCore.LLM.CLIProvider{config: LlmCore.LLM.CLIProvider.Config.t()}
```

# `available?`

```elixir
@spec available?(t()) :: boolean()
```

# `build_args`

```elixir
@spec build_args(t(), LlmCore.LLM.Provider.prompt(), keyword()) :: [String.t()]
```

Builds the CLI argument list from prompt and opts.

# `build_error`

```elixir
@spec build_error(t(), atom() | {:exit_code, non_neg_integer()}, keyword()) ::
  LlmCore.LLM.Error.t()
```

Builds an Error struct for common failure modes.

# `build_invocation`

```elixir
@spec build_invocation(t(), LlmCore.LLM.Provider.prompt(), keyword()) ::
  {String.t(), [String.t()]}
```

Returns {executable, args} for the CLI invocation.

# `build_provider`

```elixir
@spec build_provider(atom() | String.t()) :: {:ok, t()} | {:error, :not_found}
```

Builds a ready-to-use `%CLIProvider{}` struct from an id or alias.

# `build_response`

```elixir
@spec build_response(t(), String.t(), keyword()) :: LlmCore.LLM.Response.t()
```

Builds a Response struct from CLI output, applying any configured normalization.

# `builtins`

```elixir
@spec builtins() :: %{required(atom()) =&gt; LlmCore.LLM.CLIProvider.Config.t()}
```

Returns the legacy built-in map (empty since all defaults moved to TOML).
Use `list_all_configs/0` to get all known CLI provider configs.

# `capabilities`

```elixir
@spec capabilities(t()) :: map()
```

# `config`

```elixir
@spec config(atom()) ::
  {:ok, LlmCore.LLM.CLIProvider.Config.t()} | {:error, String.t()}
```

Returns the config for a provider name.

Resolution order:
1. Runtime store (TOML-loaded CLI configs)
2. Built-in `@builtins`
3. Error

# `fetch_config`

```elixir
@spec fetch_config(atom() | String.t()) ::
  {:ok, LlmCore.LLM.CLIProvider.Config.t()} | {:error, :not_found}
```

Fetches a CLI config by id or alias. Returns a ready-to-use `%CLIProvider.Config{}`.

# `from_config`

```elixir
@spec from_config(atom() | String.t() | LlmCore.LLM.CLIProvider.Config.t()) :: t()
```

Creates a CLIProvider from a built-in name, a string id/alias, or a Config struct.

# `invocation_plan`

```elixir
@spec invocation_plan(t(), LlmCore.LLM.Provider.prompt(), keyword()) :: map()
```

Returns a normalized invocation plan describing how the provider will run.

# `list_all_configs`

```elixir
@spec list_all_configs() :: %{required(atom()) =&gt; LlmCore.LLM.CLIProvider.Config.t()}
```

Returns all known CLI provider configs — runtime (TOML) merged with builtins.
Runtime configs override builtins with the same name.

# `preflight`

```elixir
@spec preflight(t()) :: {:ok, map()} | {:error, map()}
```

Runs declarative checks proving the CLI surface matches the configured contract.

# `provider_type`

```elixir
@spec provider_type(t()) :: :cli
```

# `render_prompt`

```elixir
@spec render_prompt(t(), LlmCore.LLM.Provider.prompt(), keyword()) :: String.t()
```

Renders a prompt after applying any declared inline system-prompt fallback.

# `resolve_id`

```elixir
@spec resolve_id(atom() | String.t()) :: {:ok, atom()} | {:error, :not_found}
```

Resolves an alias or id string to the canonical provider name atom.
Checks runtime definitions first (via Provider.Registry aliases), then builtins.

# `send`

```elixir
@spec send(t(), LlmCore.LLM.Provider.prompt(), keyword()) ::
  {:ok, LlmCore.LLM.Response.t()} | {:error, LlmCore.LLM.Error.t()}
```

# `stream`

```elixir
@spec stream(t(), LlmCore.LLM.Provider.prompt(), keyword()) ::
  {:ok, Enumerable.t()} | {:error, LlmCore.LLM.Error.t()}
```

# `supports?`

```elixir
@spec supports?(t(), atom()) :: boolean()
```

Returns whether the provider supports a semantic capability.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
