Comprehensive reference for everything the cix CLI lets you configure.
For a quick tour see cli/README.md.
~/.cix/config.yaml — created on first cix config set … /
cix config init. The CLI seeds an implicit default server pointing at
http://localhost:21847 if no file exists yet, but does not materialise
that to disk until the user writes something.
CLI flag (--server / --api-url / --api-key)
↓
Environment variable (CIX_SERVER / CIX_API_URL / CIX_API_KEY)
↓
~/.cix/config.yaml
↓
Built-in default (struct-tag default:"…")
Env overrides never write back to disk. The 3 env vars are the entire
env surface — knobs like watcher.debounce_ms are persistent
preferences and have no env binding.
| Command | Purpose |
|---|---|
cix config show |
Human-readable dump of the current configuration |
cix config keys |
List every settable key with default, env, description |
cix config set <key> <value> |
Set one key — supports scalars + comma-separated lists |
cix config unset server.<name>[.key|.header.<Name>] |
Remove a server entry, clear its key, or drop one custom header |
cix config edit |
Interactive TUI form (huh) for the whole file |
cix config init |
First-run wizard — same form, pre-seeded for fresh installs |
cix config path |
Print the file path (useful in scripts) |
| Key | Type | Default | Description |
|---|---|---|---|
servers |
list | [{default → localhost}] |
Managed via `cix config set server..url |
default_server |
string | default |
Active alias when --server/CIX_SERVER are unset |
server.<name>.url |
string | — | URL of a named server (creates the entry on first set) |
server.<name>.key |
string | — | API key for the named server (sensitive — never printed) |
server.<name>.header.<Name> |
string | — | Custom HTTP header sent on every request (sensitive — never printed; values support ${ENV} expansion). See Custom request headers |
api.url / api.key |
string | — | Legacy aliases — operate on the default server |
| Key | Type | Default | Validation |
|---|---|---|---|
watcher.enabled |
bool | true |
— |
watcher.debounce_ms |
int | 5000 |
100 — 60000 |
watcher.sync_interval_mins |
int | 5 |
≥ 1 |
watcher.exclude |
[]string | node_modules,.git,.venv,__pycache__,dist,build,.next,.cache,.DS_Store |
comma-separated; REPLACE semantics on set |
| Key | Type | Default | Validation | Description |
|---|---|---|---|---|
indexing.batch_size |
int | 20 |
≥ 1 | Per-batch file count for the upload pipeline |
indexing.streaming_idle_timeout_sec |
int | 30 |
≥ 0 | Max silence on streaming /index/files before giving up; 0 disables |
| Key | Type | Description |
|---|---|---|
projects |
list | Managed via cix init / dashboard — not editable via config set |
When cix is self-hosted behind an authenticating reverse proxy / Zero-Trust gateway (Cloudflare Access, oauth2-proxy, Authelia, an SSO/mTLS-terminating load balancer, a corporate proxy), the proxy decides whether a request even reaches cix. The browser dashboard passes it via interactive SSO, but the CLI and AI-agent tooling send only the cix Bearer and would be bounced at the edge (302 to login / 403) off-VPN.
Per-server custom headers let the CLI attach whatever the proxy needs on
every request (including the /health probe) — e.g. a Cloudflare Access
service token — in addition to the cix Bearer. The proxy validates and
strips those headers at the edge, so the cix origin still only sees the Bearer
— cix needs no knowledge of the proxy. Opt-in: with no headers configured,
behavior is unchanged.
servers:
- name: default
url: https://cix.example.com
key: cix_xxx
headers: # attached to every request
CF-Access-Client-Id: "<client-id>.access"
CF-Access-Client-Secret: "${CIX_CF_ACCESS_SECRET}" # ${ENV} expansionSet / remove them from the command line:
cix config set server.default.header.CF-Access-Client-Id "<client-id>.access"
cix config set server.default.header.CF-Access-Client-Secret '${CIX_CF_ACCESS_SECRET}'
cix config unset server.default.header.CF-Access-Client-IdNotes:
${ENV}expansion happens at request time into a throwaway copy — the config file keeps the literal${CIX_CF_ACCESS_SECRET}, so no plaintext secret is persisted. Use${VAR}/$VAR; quote the value in your shell so it isn't expanded before cix sees it.- Unset variable = hard error. If a header references a variable that is
not set (a typo or a forgotten
export), the command fails with a message naming the variable instead of silently sending an empty header that bounces at the proxy. A variable that is set but empty (export X=) is honored as an intentional empty value. - Literal
$. Expansion treats$NAME/${NAME}as references; to put a literal$in a value, write$$(e.g. a tokenpa$$wordis sent aspa$word). - Header-name case. Names are canonicalized on the wire (Go's
http.Header), soCF-Access-Client-Idis sent asCf-Access-Client-Id. HTTP header names are case-insensitive (RFC 7230) and Cloudflare Access et al. honor that, so this is normally invisible. - Never printed. Header values are sensitive:
cix config showandcix config edit(TUI) surface only the count (headers=N/headers N), never names or values. Edit them viacix config setor by hand in~/.cix/config.yaml— the TUI does not edit individual headers. - Safety. cix-managed headers (
Authorization,Content-Type,Accept) always win, so a custom header can never clobber authentication. Header names must be valid HTTP tokens and values may not contain CR/LF. - For human users a short-lived per-user proxy login is preferable; service tokens suit machine/agent clients. Both just need the client to send headers.
| Variable | Overrides | Notes |
|---|---|---|
CIX_SERVER |
default_server |
Used only when --server is empty |
CIX_API_URL |
resolved server's url |
Local override; never persisted |
CIX_API_KEY |
resolved server's key |
Designed for secrets.CIX_API_KEY in CI |
- Single source of truth: every key, default, validation rule, and
description lives on the corresponding Go struct field as a tag
(
yaml,key,default,validate,env,desc,sensitive). All five surfaces — load, save, show, set, TUI — read from this schema via reflection. - Loader:
knadh/koanf v2layers defaults (from tags) and the YAML file. Legacy lowercase keys (debouncems,excludepatterns,cachettl,autowatch,batchsize) are normalised in-place pre-parse so old files keep loading. Theapi:block is migrated into the multi-serverservers:list and cleared on the next save. - Validation:
go-playground/validator/v10validates the wholeConfigafter every mutation viacix config setand on TUI form submit.Load()itself does NOT validate — a malformed value in an on-disk file must not brick the CLI; bad values surface the next time the user tries to change something. - TUI:
charmbracelet/huhbuilds the paged form. The Charm stack (huh+bubbletea+lipgloss) is the visual layer for any future TUI screens too. - Sensitive fields:
ServerEntry.KeyandServerEntry.Headerscarrysensitive:"true". Renderers print(set)/(not set)for the key and a header count only, never a value. CodeQL'sgo/clear-text-loggingheuristic flags reads of*Key/*Secretinto named variables, so the tag is read offreflect.StructFieldand the value goes throughreflect.Value.IsZero()only. - Custom headers:
ServerEntry.Headersis attached to every request byclient.applyCustomHeaders(called before the cix-managed headers so those win).${ENV}expansion + validation happen ingetClient(cli/cmd/root.go) into a local copy that is never written back — mirroring the--api-url/--api-keyoverride pattern.