# `Planck.Headless.Config`
[🔗](https://github.com/alexdesousa/planck/blob/v0.1.0/lib/planck/headless/config.ex#L1)

Resolved runtime configuration for `planck_headless`.

Config is resolved by Skogsra, which reads from three sources in priority
order (highest first):

1. Environment variables (`PLANCK_*`)
2. Application config — `config :planck, <key>, ...`
3. Hardcoded defaults

Values are cached in persistent terms via `preload/0` at application boot;
the application also calls `validate!/0` to fail fast on malformed config.
To change a value at runtime, set the application env and call the
Skogsra-generated `reload_<key>/0` function.

JSON config files (`~/.planck/config.json` and `.planck/config.json`) are
read via `JsonBinding` as part of Skogsra's binding chain. Keys that appear
in a JSON file override application config but are overridden by env vars.

## Env vars

### Planner config

| Env var                   | Config key           | Default                           |
|---------------------------|----------------------|-----------------------------------|
| `PLANCK_DEFAULT_PROVIDER` | `:default_provider`  | `nil`                             |
| `PLANCK_DEFAULT_MODEL`    | `:default_model`     | `nil`                             |
| `PLANCK_SESSIONS_DIR`     | `:sessions_dir`      | `.planck/sessions`                |
| `PLANCK_SKILLS_DIRS`      | `:skills_dirs`       | `.planck/skills:~/.planck/skills` |
| `PLANCK_TEAMS_DIRS`       | `:teams_dirs`        | `.planck/teams:~/.planck/teams`   |
| `PLANCK_SIDECAR`          | `:sidecar`           | `.planck/sidecar`                 |

`*_DIRS` env vars take a colon-separated list; paths are expanded at runtime
(`~` and relative paths resolved). The `:models` key has no env var
equivalent — declare models in `.planck/config.json` or
`config :planck, :models, [...]`.

### Provider API keys

API keys are not included in `get/0` or the `%Config{}` struct to avoid
accidental exposure in logs or inspect output. Use the generated getter
functions directly (e.g. `Config.anthropic_api_key!/0`).

| Env var               | Config key             | Used for                   |
|-----------------------|------------------------|----------------------------|
| `ANTHROPIC_API_KEY`   | `:anthropic_api_key`   | Anthropic (Claude) models  |
| `OPENAI_API_KEY`      | `:openai_api_key`      | OpenAI models              |
| `GOOGLE_API_KEY`      | `:google_api_key`      | Google (Gemini) models     |

# `t`

```elixir
@type t() :: %Planck.Headless.Config{
  default_model: String.t() | nil,
  default_provider: atom() | nil,
  models: [Planck.AI.Model.t()],
  sessions_dir: Path.t(),
  sidecar: Path.t(),
  skills_dirs: [Path.t()],
  teams_dirs: [Path.t()]
}
```

The resolved configuration struct returned by `get/0`.

# `anthropic_api_key`

```elixir
@spec anthropic_api_key(Skogsra.Env.namespace()) ::
  {:ok, nil | binary()} | {:error, binary()}
```

Anthropic API key.

Calling `Planck.Headless.Config.anthropic_api_key()` will ensure the following:

- Binding order: [:system, :config]
- OS environment variable: "ANTHROPIC_API_KEY"
- Type: :binary
- Default: nil
- Required: false
- Cached: true

# `anthropic_api_key!`

```elixir
@spec anthropic_api_key!(Skogsra.Env.namespace()) :: (nil | binary()) | no_return()
```

Anthropic API key.

Bang version of `Planck.Headless.Config.anthropic_api_key/0` (fails on error). Optionally,
receives the `namespace` for the variable.

# `config_files`

```elixir
@spec config_files(Skogsra.Env.namespace()) ::
  {:ok, Planck.Headless.Config.PathList.t()} | {:error, binary()}
```

Colon-separated list of JSON config files to read at boot, in order.
Later files override earlier ones. Not read from the JSON files themselves —
that would be circular. Defaults to the user-global file followed by the
project-local file (project-local wins on collision).

Calling `Planck.Headless.Config.config_files()` will ensure the following:

- Binding order: [:system, :config]
- OS environment variable: "PLANCK_CONFIG_FILES"
- Type: Planck.Headless.Config.PathList
- Default: ["~/.planck/config.json", ".planck/config.json"]
- Required: false
- Cached: true

# `config_files!`

```elixir
@spec config_files!(Skogsra.Env.namespace()) ::
  Planck.Headless.Config.PathList.t() | no_return()
```

Colon-separated list of JSON config files to read at boot, in order.
Later files override earlier ones. Not read from the JSON files themselves —
that would be circular. Defaults to the user-global file followed by the
project-local file (project-local wins on collision).

Bang version of `Planck.Headless.Config.config_files/0` (fails on error). Optionally,
receives the `namespace` for the variable.

# `default_model`

```elixir
@spec default_model(Skogsra.Env.namespace()) ::
  {:ok, nil | binary()} | {:error, binary()}
```

Default model id within the default provider (e.g. claude-sonnet-4-6).

Calling `Planck.Headless.Config.default_model()` will ensure the following:

- Binding order: [:system, :config]
- OS environment variable: "PLANCK_DEFAULT_MODEL"
- Type: :binary
- Default: nil
- Required: false
- Cached: true

# `default_model!`

```elixir
@spec default_model!(Skogsra.Env.namespace()) :: (nil | binary()) | no_return()
```

Default model id within the default provider (e.g. claude-sonnet-4-6).

Bang version of `Planck.Headless.Config.default_model/0` (fails on error). Optionally,
receives the `namespace` for the variable.

# `default_provider`

```elixir
@spec default_provider(Skogsra.Env.namespace()) ::
  {:ok, nil | atom()} | {:error, binary()}
```

Default LLM provider (e.g. anthropic).

Calling `Planck.Headless.Config.default_provider()` will ensure the following:

- Binding order: [:system, :config]
- OS environment variable: "PLANCK_DEFAULT_PROVIDER"
- Type: :atom
- Default: nil
- Required: false
- Cached: true

# `default_provider!`

```elixir
@spec default_provider!(Skogsra.Env.namespace()) :: (nil | atom()) | no_return()
```

Default LLM provider (e.g. anthropic).

Bang version of `Planck.Headless.Config.default_provider/0` (fails on error). Optionally,
receives the `namespace` for the variable.

# `env_files`

```elixir
@spec env_files(Skogsra.Env.namespace()) ::
  {:ok, Planck.Headless.Config.PathList.t()} | {:error, binary()}
```

Ordered list of `.env` files to read for API keys.
Global file is read first; project-local file wins on collision.
Not read from the `.env` files themselves — that would be circular.

Calling `Planck.Headless.Config.env_files()` will ensure the following:

- Binding order: [:system, :config]
- OS environment variable: "PLANCK_ENV_FILES"
- Type: Planck.Headless.Config.PathList
- Default: ["~/.planck/.env", "./.planck/.env"]
- Required: false
- Cached: true

# `env_files!`

```elixir
@spec env_files!(Skogsra.Env.namespace()) ::
  Planck.Headless.Config.PathList.t() | no_return()
```

Ordered list of `.env` files to read for API keys.
Global file is read first; project-local file wins on collision.
Not read from the `.env` files themselves — that would be circular.

Bang version of `Planck.Headless.Config.env_files/0` (fails on error). Optionally,
receives the `namespace` for the variable.

# `get`

```elixir
@spec get() :: t()
```

Return the fully-resolved config as a `%Planck.Headless.Config{}` struct.

# `google_api_key`

```elixir
@spec google_api_key(Skogsra.Env.namespace()) ::
  {:ok, nil | binary()} | {:error, binary()}
```

Google API key.

Calling `Planck.Headless.Config.google_api_key()` will ensure the following:

- Binding order: [:system, :config]
- OS environment variable: "GOOGLE_API_KEY"
- Type: :binary
- Default: nil
- Required: false
- Cached: true

# `google_api_key!`

```elixir
@spec google_api_key!(Skogsra.Env.namespace()) :: (nil | binary()) | no_return()
```

Google API key.

Bang version of `Planck.Headless.Config.google_api_key/0` (fails on error). Optionally,
receives the `namespace` for the variable.

# `locale`

```elixir
@spec locale(Skogsra.Env.namespace()) :: {:ok, nil | binary()} | {:error, binary()}
```

UI locale (e.g. `"en"`, `"es"`). Set in `.planck/config.json` for a
project-specific language or in `~/.planck/config.json` for a global
preference. When absent the browser's Accept-Language header is used,
falling back to English.

Calling `Planck.Headless.Config.locale()` will ensure the following:

- Binding order: [:system, :config]
- OS environment variable: "PLANCK_LOCALE"
- Type: :binary
- Default: nil
- Required: false
- Cached: true

# `locale!`

```elixir
@spec locale!(Skogsra.Env.namespace()) :: (nil | binary()) | no_return()
```

UI locale (e.g. `"en"`, `"es"`). Set in `.planck/config.json` for a
project-specific language or in `~/.planck/config.json` for a global
preference. When absent the browser's Accept-Language header is used,
falling back to English.

Bang version of `Planck.Headless.Config.locale/0` (fails on error). Optionally,
receives the `namespace` for the variable.

# `models`

```elixir
@spec models(Skogsra.Env.namespace()) ::
  {:ok, Planck.Headless.Config.Models.t()} | {:error, binary()}
```

List of model declarations for local providers (and optional cloud model
overrides). Each entry follows the `Planck.AI.Config` JSON format. Only
readable from `.planck/config.json` or application config — no env var
equivalent (the format is too structured for a flat string).

Example (in .planck/config.json):
```json
"models": [
  {
    "id":             "llama3.2",
    "provider":       "ollama",
    "base_url":       "http://localhost:11434",
    "context_window": 128000,
    "default_opts":   {"temperature": 0.7, "top_p": 0.9}
  },
  {
    "id":             "mistral",
    "provider":       "llama_cpp",
    "base_url":       "http://localhost:8080",
    "context_window": 32768,
    "default_opts":   {"temperature": 0.5}
  }
]
```

Calling `Planck.Headless.Config.models()` will ensure the following:

- Binding order: [:system, :config]
- OS environment variable: "PLANCK_MODELS"
- Type: Planck.Headless.Config.Models
- Default: []
- Required: false
- Cached: true

# `models!`

```elixir
@spec models!(Skogsra.Env.namespace()) ::
  Planck.Headless.Config.Models.t() | no_return()
```

List of model declarations for local providers (and optional cloud model
overrides). Each entry follows the `Planck.AI.Config` JSON format. Only
readable from `.planck/config.json` or application config — no env var
equivalent (the format is too structured for a flat string).

Example (in .planck/config.json):
```json
"models": [
  {
    "id":             "llama3.2",
    "provider":       "ollama",
    "base_url":       "http://localhost:11434",
    "context_window": 128000,
    "default_opts":   {"temperature": 0.7, "top_p": 0.9}
  },
  {
    "id":             "mistral",
    "provider":       "llama_cpp",
    "base_url":       "http://localhost:8080",
    "context_window": 32768,
    "default_opts":   {"temperature": 0.5}
  }
]
```

Bang version of `Planck.Headless.Config.models/0` (fails on error). Optionally,
receives the `namespace` for the variable.

# `openai_api_key`

```elixir
@spec openai_api_key(Skogsra.Env.namespace()) ::
  {:ok, nil | binary()} | {:error, binary()}
```

OpenAI API key.

Calling `Planck.Headless.Config.openai_api_key()` will ensure the following:

- Binding order: [:system, :config]
- OS environment variable: "OPENAI_API_KEY"
- Type: :binary
- Default: nil
- Required: false
- Cached: true

# `openai_api_key!`

```elixir
@spec openai_api_key!(Skogsra.Env.namespace()) :: (nil | binary()) | no_return()
```

OpenAI API key.

Bang version of `Planck.Headless.Config.openai_api_key/0` (fails on error). Optionally,
receives the `namespace` for the variable.

# `preload`

```elixir
@spec preload(Skogsra.Env.namespace()) :: :ok
```

Preloads all variables in a `namespace` if supplied.

# `put_anthropic_api_key`

```elixir
@spec put_anthropic_api_key(nil | binary(), Skogsra.Env.namespace()) ::
  :ok | {:error, binary()}
```

Puts the `value` to `Planck.Headless.Config.anthropic_api_key/0`. Optionally, receives
the `namespace`.

# `put_config_files`

```elixir
@spec put_config_files(Planck.Headless.Config.PathList.t(), Skogsra.Env.namespace()) ::
  :ok | {:error, binary()}
```

Puts the `value` to `Planck.Headless.Config.config_files/0`. Optionally, receives
the `namespace`.

# `put_default_model`

```elixir
@spec put_default_model(nil | binary(), Skogsra.Env.namespace()) ::
  :ok | {:error, binary()}
```

Puts the `value` to `Planck.Headless.Config.default_model/0`. Optionally, receives
the `namespace`.

# `put_default_provider`

```elixir
@spec put_default_provider(nil | atom(), Skogsra.Env.namespace()) ::
  :ok | {:error, binary()}
```

Puts the `value` to `Planck.Headless.Config.default_provider/0`. Optionally, receives
the `namespace`.

# `put_env_files`

```elixir
@spec put_env_files(Planck.Headless.Config.PathList.t(), Skogsra.Env.namespace()) ::
  :ok | {:error, binary()}
```

Puts the `value` to `Planck.Headless.Config.env_files/0`. Optionally, receives
the `namespace`.

# `put_google_api_key`

```elixir
@spec put_google_api_key(nil | binary(), Skogsra.Env.namespace()) ::
  :ok | {:error, binary()}
```

Puts the `value` to `Planck.Headless.Config.google_api_key/0`. Optionally, receives
the `namespace`.

# `put_locale`

```elixir
@spec put_locale(nil | binary(), Skogsra.Env.namespace()) :: :ok | {:error, binary()}
```

Puts the `value` to `Planck.Headless.Config.locale/0`. Optionally, receives
the `namespace`.

# `put_models`

```elixir
@spec put_models(Planck.Headless.Config.Models.t(), Skogsra.Env.namespace()) ::
  :ok | {:error, binary()}
```

Puts the `value` to `Planck.Headless.Config.models/0`. Optionally, receives
the `namespace`.

# `put_openai_api_key`

```elixir
@spec put_openai_api_key(nil | binary(), Skogsra.Env.namespace()) ::
  :ok | {:error, binary()}
```

Puts the `value` to `Planck.Headless.Config.openai_api_key/0`. Optionally, receives
the `namespace`.

# `put_sessions_dir`

```elixir
@spec put_sessions_dir(binary(), Skogsra.Env.namespace()) :: :ok | {:error, binary()}
```

Puts the `value` to `Planck.Headless.Config.sessions_dir/0`. Optionally, receives
the `namespace`.

# `put_sidecar`

```elixir
@spec put_sidecar(binary(), Skogsra.Env.namespace()) :: :ok | {:error, binary()}
```

Puts the `value` to `Planck.Headless.Config.sidecar/0`. Optionally, receives
the `namespace`.

# `put_skills_dirs`

```elixir
@spec put_skills_dirs(Planck.Headless.Config.PathList.t(), Skogsra.Env.namespace()) ::
  :ok | {:error, binary()}
```

Puts the `value` to `Planck.Headless.Config.skills_dirs/0`. Optionally, receives
the `namespace`.

# `put_teams_dirs`

```elixir
@spec put_teams_dirs(Planck.Headless.Config.PathList.t(), Skogsra.Env.namespace()) ::
  :ok | {:error, binary()}
```

Puts the `value` to `Planck.Headless.Config.teams_dirs/0`. Optionally, receives
the `namespace`.

# `reload_anthropic_api_key`

```elixir
@spec reload_anthropic_api_key(Skogsra.Env.namespace()) ::
  {:ok, nil | binary()} | {:error, binary()}
```

Reloads the value for `Planck.Headless.Config.anthropic_api_key/0`. Optionally, receives
the `namespace` for the variable.

# `reload_config_files`

```elixir
@spec reload_config_files(Skogsra.Env.namespace()) ::
  {:ok, Planck.Headless.Config.PathList.t()} | {:error, binary()}
```

Reloads the value for `Planck.Headless.Config.config_files/0`. Optionally, receives
the `namespace` for the variable.

# `reload_default_model`

```elixir
@spec reload_default_model(Skogsra.Env.namespace()) ::
  {:ok, nil | binary()} | {:error, binary()}
```

Reloads the value for `Planck.Headless.Config.default_model/0`. Optionally, receives
the `namespace` for the variable.

# `reload_default_provider`

```elixir
@spec reload_default_provider(Skogsra.Env.namespace()) ::
  {:ok, nil | atom()} | {:error, binary()}
```

Reloads the value for `Planck.Headless.Config.default_provider/0`. Optionally, receives
the `namespace` for the variable.

# `reload_env_files`

```elixir
@spec reload_env_files(Skogsra.Env.namespace()) ::
  {:ok, Planck.Headless.Config.PathList.t()} | {:error, binary()}
```

Reloads the value for `Planck.Headless.Config.env_files/0`. Optionally, receives
the `namespace` for the variable.

# `reload_google_api_key`

```elixir
@spec reload_google_api_key(Skogsra.Env.namespace()) ::
  {:ok, nil | binary()} | {:error, binary()}
```

Reloads the value for `Planck.Headless.Config.google_api_key/0`. Optionally, receives
the `namespace` for the variable.

# `reload_locale`

```elixir
@spec reload_locale(Skogsra.Env.namespace()) ::
  {:ok, nil | binary()} | {:error, binary()}
```

Reloads the value for `Planck.Headless.Config.locale/0`. Optionally, receives
the `namespace` for the variable.

# `reload_models`

```elixir
@spec reload_models(Skogsra.Env.namespace()) ::
  {:ok, Planck.Headless.Config.Models.t()} | {:error, binary()}
```

Reloads the value for `Planck.Headless.Config.models/0`. Optionally, receives
the `namespace` for the variable.

# `reload_openai_api_key`

```elixir
@spec reload_openai_api_key(Skogsra.Env.namespace()) ::
  {:ok, nil | binary()} | {:error, binary()}
```

Reloads the value for `Planck.Headless.Config.openai_api_key/0`. Optionally, receives
the `namespace` for the variable.

# `reload_sessions_dir`

```elixir
@spec reload_sessions_dir(Skogsra.Env.namespace()) ::
  {:ok, binary()} | {:error, binary()}
```

Reloads the value for `Planck.Headless.Config.sessions_dir/0`. Optionally, receives
the `namespace` for the variable.

# `reload_sidecar`

```elixir
@spec reload_sidecar(Skogsra.Env.namespace()) :: {:ok, binary()} | {:error, binary()}
```

Reloads the value for `Planck.Headless.Config.sidecar/0`. Optionally, receives
the `namespace` for the variable.

# `reload_skills_dirs`

```elixir
@spec reload_skills_dirs(Skogsra.Env.namespace()) ::
  {:ok, Planck.Headless.Config.PathList.t()} | {:error, binary()}
```

Reloads the value for `Planck.Headless.Config.skills_dirs/0`. Optionally, receives
the `namespace` for the variable.

# `reload_teams_dirs`

```elixir
@spec reload_teams_dirs(Skogsra.Env.namespace()) ::
  {:ok, Planck.Headless.Config.PathList.t()} | {:error, binary()}
```

Reloads the value for `Planck.Headless.Config.teams_dirs/0`. Optionally, receives
the `namespace` for the variable.

# `sessions_dir`

```elixir
@spec sessions_dir(Skogsra.Env.namespace()) :: {:ok, binary()} | {:error, binary()}
```

Path to the sessions directory.

Calling `Planck.Headless.Config.sessions_dir()` will ensure the following:

- Binding order: [:system, :config]
- OS environment variable: "PLANCK_SESSIONS_DIR"
- Type: :binary
- Default: ".planck/sessions"
- Required: false
- Cached: true

# `sessions_dir!`

```elixir
@spec sessions_dir!(Skogsra.Env.namespace()) :: binary() | no_return()
```

Path to the sessions directory.

Bang version of `Planck.Headless.Config.sessions_dir/0` (fails on error). Optionally,
receives the `namespace` for the variable.

# `sidecar`

```elixir
@spec sidecar(Skogsra.Env.namespace()) :: {:ok, binary()} | {:error, binary()}
```

Path to the sidecar Mix project directory. planck_headless starts the sidecar
application from this path when it exists on disk. Set to a non-existent path
to disable sidecar startup.

Calling `Planck.Headless.Config.sidecar()` will ensure the following:

- Binding order: [:system, :config]
- OS environment variable: "PLANCK_SIDECAR"
- Type: :binary
- Default: ".planck/sidecar"
- Required: false
- Cached: true

# `sidecar!`

```elixir
@spec sidecar!(Skogsra.Env.namespace()) :: binary() | no_return()
```

Path to the sidecar Mix project directory. planck_headless starts the sidecar
application from this path when it exists on disk. Set to a non-existent path
to disable sidecar startup.

Bang version of `Planck.Headless.Config.sidecar/0` (fails on error). Optionally,
receives the `namespace` for the variable.

# `skills_dirs`

```elixir
@spec skills_dirs(Skogsra.Env.namespace()) ::
  {:ok, Planck.Headless.Config.PathList.t()} | {:error, binary()}
```

Colon-separated list of skill directories.

Calling `Planck.Headless.Config.skills_dirs()` will ensure the following:

- Binding order: [:system, :config]
- OS environment variable: "PLANCK_SKILLS_DIRS"
- Type: Planck.Headless.Config.PathList
- Default: [".planck/skills", "~/.planck/skills"]
- Required: false
- Cached: true

# `skills_dirs!`

```elixir
@spec skills_dirs!(Skogsra.Env.namespace()) ::
  Planck.Headless.Config.PathList.t() | no_return()
```

Colon-separated list of skill directories.

Bang version of `Planck.Headless.Config.skills_dirs/0` (fails on error). Optionally,
receives the `namespace` for the variable.

# `teams_dirs`

```elixir
@spec teams_dirs(Skogsra.Env.namespace()) ::
  {:ok, Planck.Headless.Config.PathList.t()} | {:error, binary()}
```

Colon-separated list of team directories.

Calling `Planck.Headless.Config.teams_dirs()` will ensure the following:

- Binding order: [:system, :config]
- OS environment variable: "PLANCK_TEAMS_DIRS"
- Type: Planck.Headless.Config.PathList
- Default: [".planck/teams", "~/.planck/teams"]
- Required: false
- Cached: true

# `teams_dirs!`

```elixir
@spec teams_dirs!(Skogsra.Env.namespace()) ::
  Planck.Headless.Config.PathList.t() | no_return()
```

Colon-separated list of team directories.

Bang version of `Planck.Headless.Config.teams_dirs/0` (fails on error). Optionally,
receives the `namespace` for the variable.

# `template`

```elixir
@spec template(
  Path.t(),
  keyword()
) :: :ok | {:error, File.posix()}
```

Creates a template for OS environment variables given a `filename`.
Additionally, it can receive a list of options:

- `type`: What kind of file it will generate (`:elixir`, `:unix`,
  `:windows`).
- `namespace`: Namespace for the variables.

# `validate`

```elixir
@spec validate(Skogsra.Env.namespace()) :: :ok | {:error, [binary()]}
```

Validates that all required variables are present.
Returns `:ok` if they are, `{:error, errors}` if they are not. `errors`
will be a list of all errors encountered while getting required variables.

It is possible to provide a `namespace` as argument (defaults to `nil`).

# `validate!`

```elixir
@spec validate!(Skogsra.Env.namespace()) :: :ok | no_return()
```

Validates that all required variables are present.
Returns `:ok` if they are, raises if they're not.

It is possible to provide a `namespace` as argument (defaults to `nil`).

---

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