Configuration
All configuration is in a single TOML file at ~/.config/caic/config.toml. See contrib/config.toml for a documented template.
Quick start
The install script creates a default config.toml automatically. To customize:
$EDITOR ~/.config/caic/config.tomlcaic watches config.toml for changes and restarts automatically when the file is saved.
Without a config file, caic starts with sensible defaults: listens on localhost:2242, uses the current directory as root, and auto-detects the GitHub token from gh auth token.
CLI flags
| Flag | Default | Description |
|---|---|---|
-config-dir | ~/.config/caic | Override the config directory. |
-print-url | — | Print the server URL and exit. Verifies the port is available. |
-version | — | Print version and exit. |
Directories
caic uses two directories, following the XDG Base Directory convention:
| Directory | Default | Env override | Contents |
|---|---|---|---|
| Config | ~/.config/caic/ | XDG_CONFIG_HOME | config.toml, settings.json, users.json, preferences.json, PEM keys, MMDB files |
| Cache | ~/.cache/caic/ | XDG_CACHE_HOME | Task logs, CI cache, harness model cache |
Relative paths in the config file (e.g. geo_db, app_private_key_pem) are resolved against the config directory.
Config reference
[core]
| Key | Default | Description |
|---|---|---|
root | "." | Parent directory containing git repositories managed by caic. Tilde (~) is expanded to the user's home directory |
auto_update | "50 4 * * *" | Cron schedule for auto-update (checks GitHub Releases). Supports cron step syntax (*/N, start/N, e.g. */15 * * * *). Set to "" to disable |
runtime | auto-detect | Container runtime: "docker" or "podman". When unset (""), caic auto-detects (Docker preferred, then Podman). sudo is unsupported with rootless Podman, so use Docker if you need sudo-enabled containers |
[core.env]
Environment variables inherited by the server process. Falls back to the host environment when not set.
| Key | Purpose |
|---|---|
TAILSCALE_API_KEY | Tailscale API key for ephemeral container nodes. Obtain from Tailscale admin |
GEMINI_API_KEY | Gemini API key for the embedded voice gateway and title generation. Obtain from Google AI Studio |
HTTP_PROXY / HTTPS_PROXY / NO_PROXY | Proxy settings for forge API calls (GitHub, GitLab) when the server is behind a corporate proxy |
ANTHROPIC_API_KEY / OPENAI_API_KEY / etc | Optional API keys for LLM generated commit description |
[core]
root = "~/projects"
runtime = "podman"
[core.env]
TAILSCALE_API_KEY = "tskey-api-..."
GEMINI_API_KEY = "AIza..."Auto-update
When enabled, caic checks GitHub Releases on the given cron schedule and replaces the binary in place when a new version is found. The schedule accepts cron step syntax (*/N, start/N), so */15 * * * * checks every 15 minutes. The binary self-update triggers the config watcher, causing a restart with the new version.
[core]
# Disable auto-update:
auto_update = ""Preferences
~/.config/caic/preferences.json stores per-user settings independent of config.toml. Edit it directly or through the Settings UI, which exposes the container image, CPU architecture and platform, CPU cores, well-known caches (now opt-in), custom cache mappings, custom host mounts (with enabled and read-only toggles), auto-fix CI and PR toggles, per-model thinking effort, and version info with manual update.
[server]
| Key | Default | Description |
|---|---|---|
http | ":2242" | HTTP listen address. Port-only addresses (:2242) bind to localhost. Use "0.0.0.0:2242" to listen on all interfaces |
external_url | "auto" | Public base URL. "auto" locks from the first FQDN request. Set explicitly for OAuth and webhooks |
geo_db | — | Path to a MaxMind GeoLite2 MMDB file for country-code resolution. If unset and GeoLite2-Country.mmdb exists in the config directory, it is used automatically |
allow_origins | ["local", "tailscale", "github"] | Allowlist for incoming HTTP requests. Unmatched IPs get HTTP 403 |
external_url rules
"auto"(default): hostname locked from the first request with a fully qualified domain name- Explicit value: must be a valid URL with a host and no path (e.g.
https://caic.example.com). Trailing slashes are stripped - When OAuth is configured and explicit: must use
https://
allow_origins values
| Value | Matches |
|---|---|
"local" | Loopback and RFC 1918 private addresses |
"tailscale" | Tailscale CGNAT range (100.64.0.0/10) |
"github" | GitHub webhook IPs (fetched live from api.github.com/meta). Needed for the GitHub App |
"anthropic" | Published Anthropic outbound IPs. Needed for Claude MCP clients |
"openai" | Published ChatGPT integration and Codex cloud IPs. Needed for ChatGPT MCP clients |
| CIDR range | e.g. "203.0.113.0/24" |
| Country code | ISO 3166-1 alpha-2 (e.g. "CA", "US"); requires geo_db |
Named origins (local, tailscale, github, anthropic, openai) resolve before country lookups.
[server]
geo_db = "GeoLite2-Country.mmdb"
allow_origins = ["local", "tailscale", "github", "CA"][voice-gateway]
Voice availability is derived automatically from your configuration:
- Standalone gateway: set
urlto advertise a separate voice gateway you run elsewhere. - Embedded: leave
urlempty and setGEMINI_API_KEY(in[core.env]). caic hosts the gateway in-process. - Disabled: leave
urlempty andGEMINI_API_KEYunset.
| Key | Default | Description |
|---|---|---|
url | — | URL of a standalone voice gateway. When set, caic advertises it instead of hosting one |
[voice-gateway.config]
Runtime config for the embedded gateway, used only when caic hosts it in-process. The API key is always read from GEMINI_API_KEY.
| Key | Default | Description |
|---|---|---|
model | "gemini-3.1-flash-live-preview" | Gemini Live model for the embedded gateway |
backend | "gemini-live" | "gemini-live" uses Gemini Live. "local-stack" runs a local, half-duplex stack instead |
[voice-gateway.config.server]
| Key | Default | Description |
|---|---|---|
webrtc_udp_port | 0 | UDP port for WebRTC ICE. 0 = OS-assigned ephemeral port. A value >0 pins a static port; open it for UDP traffic in your firewall. Requires GEMINI_API_KEY |
With backend = "local-stack", voice runs half-duplex with a managed llama.cpp instance for ASR and the LLM plus KittenTTS for text-to-speech. Omit the local-stack tables to let caic manage llama.cpp with a default Gemma model, or point them at servers you run yourself. See contrib/voice-gateway-config.toml for the full template.
[voice-gateway.config]
backend = "gemini-live"
[voice-gateway.config.server]
webrtc_udp_port = 3479[ai]
LLM provider for title generation and commit descriptions.
| Key | Default | Description |
|---|---|---|
provider | auto-detect | Provider name. When unset, probes in this order: codex, opencode, claudecode, then any other available provider |
model | cheapest | Model name or alias. Uses the provider's cheapest model when unset |
Available providers: see the genai providers package.
[ai]
provider = "codex"
model = "gpt-5-mini"[harness]
Per-harness environment variables injected into containers. Each harness has its own [env] table of KEY = "VALUE" pairs. These supplement the automatic *_API_KEY forwarding and are also used when refreshing model lists at startup.
[harness.claude.env]
ANTHROPIC_API_KEY = "sk-..."
[harness.codex.env]
# Codex configuration is primarily via its own config file:
# https://developers.openai.com/codex/config-reference
[harness.opencode.env]
# OpenCode configuration: https://opencode.ai/docs/config/
[harness.pi.env]
DEEPSEEK_API_KEY = "sk-..."
GEMINI_API_KEY = "AIza..."
OPENROUTER_API_KEY = "sk-or-..."Available harness names: claude, codex, opencode, pi.
[github.*]
GitHub settings live in three subsections by authentication method. See GitHub Integration for full setup instructions.
[github.pat]:
| Key | Default | Description |
|---|---|---|
token | — | Personal access token. Falls back to gh auth token when unset and OAuth is not configured |
[github.oauth]:
| Key | Default | Description |
|---|---|---|
client_id | — | OAuth app client ID. Mutually exclusive with a PAT |
client_secret | — | OAuth app client secret. Required when client_id is set |
allowed_users | — | GitHub usernames permitted to log in. When unset, any GitHub user who completes OAuth can log in |
[github.app] (layered on top of PAT or OAuth, not mutually exclusive):
| Key | Default | Description |
|---|---|---|
id | — | GitHub App ID |
private_key_pem | — | Path to GitHub App PEM private key file. Relative to the config directory |
allowed_owners | — | GitHub orgs/users allowed to install the app. Installs from other accounts are rejected |
webhook_secret | — | HMAC-SHA256 secret for webhook verification. Generate with openssl rand -hex 32 |
[gitlab.*]
The top-level [gitlab] table holds instance settings; credentials live in subsections by authentication method. See GitLab Integration for full setup instructions.
[gitlab]:
| Key | Default | Description |
|---|---|---|
url | — | GitLab instance URL. Empty targets gitlab.com; set only for a self-hosted instance |
webhook_secret | — | Shared secret for webhook verification. Generate with openssl rand -hex 32 |
[gitlab.pat]:
| Key | Default | Description |
|---|---|---|
token | — | Personal access token (scope: api). Mutually exclusive with OAuth |
[gitlab.oauth]:
| Key | Default | Description |
|---|---|---|
client_id | — | OAuth app client ID. Mutually exclusive with a PAT |
client_secret | — | OAuth app client secret. Required when client_id is set |
allowed_users | — | GitLab usernames permitted to log in. When unset, any GitLab user who completes OAuth can log in |
[debug]
| Key | Default | Description |
|---|---|---|
log_level | "info" | Log verbosity: debug, info, warn, error |
no_log_time | false | Omit timestamps from log output. Useful under systemd |
pprof | false | Expose /debug/pprof/* profiling endpoints |
cpuprofile | — | Write CPU profile to this file path |
memprofile | — | Write heap profile on shutdown |
trace | — | Write execution trace to this file path |
Environment variables
| Variable | Purpose |
|---|---|
GEMINI_API_KEY | Fallback when [core.env] GEMINI_API_KEY is not set |
TAILSCALE_API_KEY | Fallback when [core.env] TAILSCALE_API_KEY is not set |
XDG_CONFIG_HOME | Override the config base directory (default: ~/.config) |
XDG_CACHE_HOME | Override the cache base directory (default: ~/.cache) |
HTTPS exposure
Setting external_url explicitly is recommended for OAuth login and webhooks so redirect URIs and callback URLs are stable. Webhooks additionally require the forge (GitHub/GitLab) to reach caic from the internet.
Tailscale Serve (private, tailnet only)
tailscale serve --bg 2242Tailscale Funnel (public, webhooks supported)
tailscale funnel 2242With Tailscale, set external_url to your tailnet hostname:
[server]
external_url = "https://<hostname>.<tailnet>.ts.net"Caddy + DDNS (home server)
<your-domain> {
reverse_proxy localhost:2242
}[server]
external_url = "https://<your-domain>"For dynamic DNS, ddns-updater works well with Caddy.