README.md

# ExInfisical

Elixir client for the [Infisical](https://infisical.com) secrets API, built
primarily to **eliminate manual secret entry in Livebook notebooks**. Point
it at a folder, get every key dropped into the notebook's process
environment in one call.

- **Livebook-first ergonomics** — `ExInfisical.to_env!/2` pulls an entire
  Infisical folder into `System.put_env/1` so downstream libs (`genai`,
  `noizu_weaviate`, `redix`, …) just read `System.get_env/1` as usual.
- Universal Auth (Machine Identity) login with skew-aware token caching.
- Optional Cloudflare Access service-token headers for self-hosted
  deployments behind CF Access (e.g. `https://infisical.noizu.com`).
- Server-side reference expansion (`${otherFolder.KEY}`) enabled by default —
  one folder per project can reference shared keys elsewhere without
  duplication.

## Installation

Add to `mix.exs`:

```elixir
def deps do
  [
    {:ex_infisical, "~> 0.1.0"}
  ]
end
```

## Configuration

```elixir
# config/runtime.exs
import Config

config :ex_infisical,
  api_url: System.get_env("INFISICAL_API_URL", "https://app.infisical.com/api"),
  client_id: System.fetch_env!("INFISICAL_CLIENT_ID"),
  client_secret: System.fetch_env!("INFISICAL_CLIENT_SECRET"),
  project_slug: System.get_env("INFISICAL_PROJECT_SLUG"),
  project_id: System.get_env("INFISICAL_PROJECT_ID"),
  # Only set when your API is behind Cloudflare Access:
  cf_access_client_id: System.get_env("CF_ACCESS_CLIENT_ID"),
  cf_access_client_secret: System.get_env("CF_ACCESS_CLIENT_SECRET")
```

Either `:project_id` (preferred) or `:project_slug` must be set — if the
slug is given, `ExInfisical` resolves the id once per process lifetime.

## Usage

### Fetch every secret at a path

```elixir
{:ok, secrets} = ExInfisical.get_secrets("/livebook")
#=> %{"LIVEBOOK_COOKIE" => "...", "LIVEBOOK_PASSWORD" => "..."}
```

### Fetch a single key

```elixir
{:ok, key} = ExInfisical.get_secret("LB_GROQ_API_KEY", path: "/livebook")
```

### Dump secrets into the process environment (Livebook / Mix.install)

```elixir
:ok = ExInfisical.to_env!("/livebook", prefix: "LB_")

System.get_env("LB_GROQ_API_KEY")  #=> "gsk_..."
```

`to_env!/2` accepts `:prefix`, `:only`, `:except`, and `:transform` on top of
the normal fetch options.

### Options reference

Accepted by every fetch function:

| Option | Default | Description |
| ------ | ------- | ----------- |
| `:env` | `"prod"` | Environment slug. |
| `:path` | `"/"` | Secret folder path. |
| `:recursive` | `false` | Include sub-folders. |
| `:expand` | `true` | Expand `${folder.KEY}` references server-side. |
| `:project_id` / `:project_slug` | from config | Override per call. |

## Livebook recipe

```elixir
Mix.install([{:ex_infisical, "~> 0.1.0"}])

Application.put_all_env(
  ex_infisical: [
    api_url: "https://infisical.noizu.com/api",
    client_id: System.fetch_env!("INFISICAL_CLIENT_ID"),
    client_secret: System.fetch_env!("INFISICAL_CLIENT_SECRET"),
    project_slug: "k8-infra"
  ]
)

ExInfisical.to_env!("/livebook", prefix: "LB_")
```

Downstream libs (`genai`, `noizu_weaviate`, `redix`, etc.) can then read
`LB_GROQ_API_KEY`, `LB_WEAVIATE_API_KEY`, and friends via `System.get_env/1`.

## License

MIT © Noizu Labs / Keith Brings