cox configuration reference
Generated from config/default.toml by a test in cox-protocol/src/config.rs; do not hand-edit.
[core]
home="~/.cox"— COX_HOME overridesworkspace_roots=[]— empty = git root of cwd, else cwd; extra roots via --add-dirmax_turns=200— per UserTurn, counts provider callsparallel_tools=4max_concurrent_subagents=8— cap on running TaskKind::Agent tasks, foreground + background (T34.2)log_level="info"— tracing filter; file log at ~/.cox/logs/cox.logprofile=""— "" (default) | "minimal" (T30.1: the lean prefix); alsocox --profile minimalmode="editor"— editor (default) | architect (P42: plan + think); alsocox --mode,/mode
[tiers.cheap]
provider="anthropic"model="claude-haiku-4-5"effort="low"max_tokens=4096
[tiers.code]
provider="anthropic"model="claude-sonnet-5"effort="high"max_tokens=16384thinking="adaptive"
[tiers.think]
provider="anthropic"model="claude-fable-5-1"effort="high"max_tokens=32768thinking="adaptive"confirm=true— cannot be set false in project config
[jobs]
main="code"plan="think"compact="cheap"title="cheap"summarize="cheap"commit="cheap"memory="cheap"explore="cheap"shell="cheap"agent="cheap"hook="cheap"
[providers.anthropic]
base_url="https://api.anthropic.com"api_key_env="ANTHROPIC_API_KEY"— else keyring entry "cox/anthropic"cache_ttl="5m"— "5m" | "1h"fallbacks=true— fallbacks: "default" + beta headertimeout_s=120max_retries=4models=[{id="claude-haiku-4-5", display_name="Claude Haiku 4.5 (latest)", context_window=200000, efforts=["low"], images=true}, {id="claude-sonnet-5", display_name="Claude Sonnet 5", context_window=1000000, efforts=["low", "medium", "high", "xhigh"], images=true}, {id="claude-opus-5", display_name="Claude Opus 5", context_window=1000000, efforts=["low", "medium", "high", "xhigh"], images=true}, {id="claude-fable-5-1", display_name="Claude Fable 5.1", context_window=1000000, efforts=["low", "medium", "high", "xhigh"], images=true}]— id, context window, efforts per model (effort values from models.dev)
[providers.openai]
base_url="https://api.openai.com/v1"api_key_env="OPENAI_API_KEY"— else keyring entry "cox/openai"api="responses"— "responses" | "chat"timeout_s=120max_retries=4models=[{id="gpt-5.1", display_name="GPT-5.1", context_window=400000, efforts=["low", "medium", "high"], images=true}, {id="gpt-5.5", display_name="GPT-5.5", context_window=1050000, efforts=["low", "medium", "high", "xhigh"], images=true}, {id="gpt-5.6-sol", display_name="GPT-5.6 Sol", context_window=1050000, efforts=["low", "medium", "high", "xhigh"], images=true}]— id, context window, efforts per model (effort values from models.dev)
[providers.local]
base_url="http://localhost:11434/v1"api_key_env=""— empty = no key; most local servers need noneapi="chat"model="qwen3-coder"context_window=32768— local servers do not report ittimeout_s=600— higher than a remote section's 120: local prefill is slow (T30.23)max_retries=4models=[{id="qwen3-coder", context_window=32768, efforts=["low", "high", "xhigh"]}]— id, context window, efforts per model
[providers.lmstudio]
base_url="http://localhost:1234"api_key_env="LM_API_TOKEN"— else keyring entry "cox/lmstudio"; neither = no x-api-key headermodel=""— usually left unset; pin via tiers.code.model / --tier code=<model>context_window=0— 0 = ask the server (GET /api/v1/models), then the model catalogload=false— true = load the model at session start if it is not loaded (with context_window)timeout_s=600— local prefill is slow, same rationale aslocalmax_retries=4
[providers.typesafe]
base_url="https://api.typesafe.ai"— client appends /v1/systemoneapi_key_env="TYPESAFE_API_KEY"— else keyring entry "cox/typesafe"model="jev-latest"timeout_s=30max_retries=2models=[{id="jev-latest", context_window=128000, efforts=["low"]}]— decisions are cheap-tier only
[providers.deepseek]
base_url="https://api.deepseek.com"— client appends /chat/completionsapi_key_env="DEEPSEEK_API_KEY"— else keyring entry "cox/deepseek"api="chat"model="deepseek-v4-pro"context_window=1000000timeout_s=120max_retries=4models=[{id="deepseek-v4-flash", display_name="DeepSeek V4 Flash", context_window=1000000, efforts=["low", "high", "xhigh"], images=true}, {id="deepseek-v4-pro", display_name="DeepSeek V4 Pro", context_window=1000000, efforts=["low", "high", "xhigh"], images=false}, {id="deepseek-v4-flash-vision-exp", display_name="DeepSeek V4 Flash Vision Exp", context_window=1000000, efforts=["low", "high", "xhigh"], images=true}]— id, context window, efforts per model (effort values from models.dev)
[providers.openrouter]
base_url="https://openrouter.ai/api/v1"api_key_env="OPENROUTER_API_KEY"— else keyring entry "cox/openrouter"api="chat"model="anthropic/claude-sonnet-5"context_window=1000000timeout_s=120max_retries=4models=[{id="anthropic/claude-sonnet-5", display_name="Claude Sonnet 5", context_window=1000000, efforts=["low", "medium", "high", "xhigh"], images=true}, {id="anthropic/claude-opus-5", display_name="Claude Opus 5", context_window=1000000, efforts=["low", "medium", "high", "xhigh"], images=true}, {id="deepseek/deepseek-v4-pro", display_name="DeepSeek V4 Pro", context_window=1048576, efforts=["high", "xhigh"], images=false}, {id="qwen/qwen3-coder-plus", display_name="Qwen3 Coder Plus", context_window=1000000, efforts=["low", "high", "xhigh"], images=false}, {id="x-ai/grok-4.3", display_name="Grok 4.3", context_window=1000000, efforts=["low", "medium", "high"], images=true}]— curated coding subset; the full 359-model list lives in models.dev
[providers.moonshot]
base_url="https://api.moonshot.ai/v1"api_key_env="MOONSHOT_API_KEY"— else keyring entry "cox/moonshot"api="chat"model="kimi-k2.6"context_window=262144timeout_s=120max_retries=4models=[{id="kimi-k2.6", display_name="Kimi K2.6", context_window=262144, efforts=["low", "medium", "high", "xhigh"], images=true}, {id="kimi-k2.7-code", display_name="Kimi K2.7 Code", context_window=262144, efforts=["low", "high", "xhigh"], images=true}]— id, context window, efforts per model
[providers.z-ai]
base_url="https://api.z.ai/api/paas/v4"api_key_env="ZHIPU_API_KEY"— else keyring entry "cox/z-ai"api="chat"model="glm-5.2"context_window=1000000timeout_s=120max_retries=4models=[{id="glm-5.2", display_name="GLM-5.2", context_window=1000000, efforts=["high", "xhigh"], images=false}, {id="glm-5.3", display_name="GLM-5.3", context_window=1000000, efforts=["low", "high", "xhigh"], images=false}]— id, context window, efforts per model
[providers.gemini]
base_url="https://generativelanguage.googleapis.com/v1beta/openai"— client appends /chat/completionsapi_key_env="GEMINI_API_KEY"— else keyring entry "cox/gemini"api="chat"— Google calls its OpenAI compatibility beta: https://ai.google.dev/gemini-api/docs/openaimodel="gemini-3.8-flash"timeout_s=120max_retries=4models=[{id="gemini-3.8-flash", display_name="Gemini 3.8 Flash", context_window=1048576, efforts=["low", "medium", "high"], reasoning_effort=true, images=true}, {id="gemini-3.1-pro-preview", display_name="Gemini 3.1 Pro Preview", context_window=1048576, efforts=["low", "medium", "high"], reasoning_effort=true, images=true}, {id="gemini-3.5-flash-lite", display_name="Gemini 3.5 Flash Lite", context_window=1048576, efforts=["low", "medium", "high"], reasoning_effort=true, images=true}]
[context]
compact_at=0.75— fraction of max_contextkeep_turns=2microcompact_after_turns=6tool_output_visible_bytes=8192tool_output_head_lines=60tool_output_tail_lines=20dedup_window_turns=8instruction_budget_tokens=8000memory_budget_tokens=800deferred_tools=truesystem_prompt="default"— default | minimal (T30.1);core.profile = "minimal"implies itrepomap_budget_tokens=0— repo map in system[2] (P43); 0 = off until the T43.6 bench
[compaction]
strategy="state+llm"— state+llm | llm (T59.1): state+llm writes files touched and errors seen from the transcript; llm is the opt-out
[permissions]
mode="default"— default | plan | auto | bypass (bypass only via flag)approval="on-request"— untrusted | on-request | on-failure | neverallow=[]— rule strings, §1.8; a project config cannot set allow (its list is ignored)ask=[]— a project config adds rules to this list, never replaces itdeny=["Read(~/.ssh/**)", "Read(~/.aws/**)", "Bash(rm -rf /*)"]— a project config adds rules to this list, never replaces itimport_claude_settings=trueallow_for_session_persists=false
[sandbox]
mode="workspace-write"— read-only | workspace-write | danger-full-accessnetwork=falsewritable=[]— extra writable rootsreadonly_in_workspace=[".git", ".cox", ".claude"]linux_backend="auto"— auto | bwrap | landlock | none
[budget]
session_usd=5.0monthly_usd=100.0warn_at=0.8cheap_counts=true
[tui]
vim=falsetheme="auto"— auto | dark | light | a built-in (cox-dark, cox-light, system) or a~/.cox/themes/<name>.tomlfile's stem;autoqueries the terminal's OSC 11 background colour once, before raw mode, with a 100 ms timeout (T22.6) — tmux, a query error, or no reply within the timeout falls back todark, same as before this query existed. An explicitdark/light(config file orCOX_TUI_THEME) always wins over detection; a named theme follows the same read unless its file pins avariant.cox doctorreports whatautoresolved to./themepreviews and writes this (T24.2).inline=trueshow_thinking="collapsed"— collapsed | hidden | fullscreen_reader=false— the plain surface (T29.1): flat labelled lines, numbered prompts, no cursor movement, BEL when a turn ends; same as--plainorCOX_PLAIN=1mouse=true— the wheel scrolls the transcript, the diff view and pickers 3 lines/rows a tick; no in-app toggle key, so false leaves the terminal's own mouse reporting (and text selection) untouched (T22.4)glyphs="auto"— auto | unicode | asciiicons={}— [tui.icons] name = "glyph" overrides one symbolcolor="auto"— auto | none | 16 | 256 | true (NO_COLOR forces none)syntax_theme=""— syntect theme for code, diffs and file output ("" follows theme); a.tmThemefile in~/.cox/themes/is merged in at startup and offered by/themeunder asyntax:prefix (T24.2)diff="auto"— auto | side | stacked — edit cards, the approval modal and Ctrl+G split old and new side by side from 120 columns (auto and side alike; narrower stays stacked), stacked never splits; replaced lines highlight the changed words (T24.5)git=true— branch and +n -m in the status line, polled every 2 snotify="auto"— auto | always | off — OSC 9 (OSC 777 on VTE) plus BEL when a turn ends, an approval waits or ask_user asks; auto only while the terminal is unfocused (focus reporting), always regardless, off never (T23.5)motion="full"— full | reduced — reduced draws a running tool's spinner as one still glyph and replaces its ticking elapsed time withrunning(T24.7)caps={}— [tui.caps] name = bool overrides one detected cox_tui::term::Caps field (truecolor, kitty_keyboard, osc8, osc52, osc9, osc9_4, osc22, focus, images) for a terminal detection guesses wrong about; unset fields are auto-detected,cox doctorshows the source of each (T23.0)
[tui.status_line]
command=""— a /bin/sh -c command fed the status JSON (Claude Code's statusLine field names) on stdin; its first output line is one row above the status line, re-run 300 ms after the status changes; runs sandboxed read-only without network, output passes sanitize (colours and links stripped); "" is off; a project config cannot set it (T46.1)refresh_s=0— also re-run every this many seconds (0 = off, at most 3600)timeout_ms=2000— a run that takes longer is killed and the row goes blank (100 to 10000)
[hooks]
timeout_s=60— seconds per [[hooks.<Event>]] process (a hook's own timeout_s overrides); stdin carries the Claude Code JSON payload, exit 2 blocks, stdout may carry updatedInput or additionalContext; a project config or a repository .claude file cannot add or change hook commands (T64.1) — they are reverted; user config and ~/.claude canfail_open=true— a hook that crashes, times out or has an invalid matcher regex is warned about and skipped, never fatal (D14). matcher is an exact tool name, or — when it carries a regex metacharacter — a regex over the tool name (T22.3). Events: UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, Stop, PreCompact, PostCompact, SessionStart (payload source: startup | resume | clear; stdout additionalContext joins the volatile system block), SessionEnd, PermissionRequest, SubagentStart, SubagentStop, Notification (observe-only kind/message/title payload on ApprovalRequired, TurnDone and ask_user)
[mcp]
timeout_s=30deferred=trueservers={}— [mcp.servers.<name>] command/args/url/env/sandbox — same shape as .mcp.json, plus sandbox=false to opt a named stdio server out of the sandbox wrap (default true, T33.42)
[lsp]
enabled=true— the deferred ReadOnlydiagnosticstool (P41): one sandboxed stdio LSP server per language per session, killed when the session endstimeout_s=30— seconds per diagnostics requestquiet_ms=500— after the last pushed publishDiagnostics, wait this long before taking the result as completeafter_edit=false— edit and write end their result with the diagnostics they introduced, asked only of a language server that already runs (an edit never starts one)after_edit_ms=1500— longest wait for those diagnostics after a change; a slow or dead server adds nothing
[lsp.servers.rust]
command="rust-analyzer"— program to spawn, found on PATH, under the same sandbox wrap as an MCP stdio server; [lsp.servers.<name>] is user config only — a project config cannot set lsp.servers (a repository must not choose a program cox runs)args=[]— arguments to commandextensions=["rs"]— file extensions, without the dot, this server is asked about
[lsp.servers.typescript]
command="typescript-language-server"args=["--stdio"]extensions=["ts", "tsx", "js", "jsx"]
[lsp.servers.python]
command="pyright-langserver"args=["--stdio"]extensions=["py"]
[lsp.servers.go]
command="gopls"args=[]extensions=["go"]
[tools]
project=false— offer theprojecttool: one call runs the project's own check, test, lint or format-check command (detected from justfile, Cargo.toml, package.json, go.mod or pyproject.toml) through the same sandbox and approval path asbash, so it asks exactly asbashdoes for that command (T59.5)
[project]
check=""— command forprojectaction check; empty detects it from the manifests. Runs underbash's sandbox and permission rules, so a project config may set ittest=""— command forprojectaction test; empty detects itlint=""— command forprojectaction lint; empty detects itfmt=""— command forprojectaction fmt, a format check that must not rewrite files; empty detects it
[voice]
enabled=false— push-to-talk dictation in the TUI (P54), transcribed on this machine with whisper; only in a cox built with thevoicefeature. User config only: a project config cannot set any voice.* key (a repository must not switch the microphone on or choose the model file)model="base.en"— whisper model: tiny.en, base.en, small.en, tiny, base or small; fetched only bycox voice model download <name>after you confirmlanguage="en"— ISO-639-1 code for whisper, or "auto" to detectkey="alt+v"— press to record, press again to stop; where the terminal reports key releases, hold to recordauto_submit=true— submit the transcript like Enter, only when the draft was empty before recordingmax_seconds=120— longest recording kept in memory; audio is never written to disk
[plugins]
enabled=true— WASM plugins (docs/design/plugins.md); only a plugin granted for its exact package digest loads; a project config can turn this off, never on; env COX_PLUGINS_ENABLED, flag --no-plugins. A [plugins.<id>] table is that plugin's own config, passed unchanged to its cox_init as InitIn.config; the plugin validates it
[plugins.decide]
min_confidence=0.6— advice with lower or no confidence is ignored and the static pick standsroute_ms=300— route's latency budget; a later answer is ignoredroute_margin=0.15— route offers cheap only when its predicted turn cost (catalog prices, last prefix size, cache write vs read) is at most (1 - route_margin) x the code tier'srisk_ms=200— risk = "<plugin id>" may raise a tool call to destructive, never lower it; asked only when that changes the permission outcomeapprove_hint_ms=200— approve_hint = "<plugin id>" may add a caution note to an approval prompt, never say a call looks safecompact_ms=500— compact = "<plugin id>" may compact before the threshold, never skip a due compactionrank_ms=300— rank = "<plugin id>" may reorder or drop tool_search hits, never add onesalience_ms=300— salience = "<plugin id>" scores each extracted memory item; may drop against memory.salience_min, never add or edit one
[memory]
enabled=trueextract=false— end-of-session extraction on cheap tierdir=""— default ~/.cox/projects/<slug>/memorysalience_min=0.3— an extracted item scoring below this against salience's Score is dropped; the plugin cannot move this bar
[session]
auto_title=true— after the first turn, one cheaptitlejob names the session (A113); a title the user set is never replaced
[telemetry]
otel=falseendpoint=""
[record]
redact=true
[desktop]
menu_bar=true— show cox's menu-bar extra: what needs you, what runs, today's spend (T51.14)remote_hosts=[]— ssh host aliases the app connects to, each its own sidebar group (T52.21); user config only — a project config cannot set it
[desktop.appearance]
material="frosted"— frosted | glossy | solid — solid is forced by Reduce Transparencyopacity=0.42— window and pane background opacity, 0 (clear) … 1 (opaque); text panels never drop below 0.8blur=34— background blur in pt, 0 … 60 (frosted); glossy reads it as reflectiondepth=1.0— elevation scale, 0 (flat, standard macOS look) … 1 (full shadows and highlights)specular=1.0— glare: the specular sweep's strength, 0 (none) … 1 (the material's full sweep); Solid and Increase Contrast stay at 0 (T60.8)tint=true— tint the glass from the wallpaperdark_highlight="none"— none | subtle — the top-edge highlight in dark mode: none (the dark mockup) or white at 10 % of the light one (A109)dark_highlight_scope="controls"— controls | all — dark_highlight applies to controls (e1) or every lifted level (e1–e4, the user bubble too) (A109)
[desktop.transcript]
cross_block_selection=true— a text selection runs across blocks like one document; false clamps it to one block (A67)text_size=13.5— the transcript's prose size in pt at 100 % text size, 10 … 24; code, headings and thoughts keep their size relative to it (A93)line_height=1.55— the prose line height as a multiple of the text size, 1 … 2.5 (A93)
[desktop.context]
cache_hit="turn"— turn | session — the Context tab's cache hit: the last turn's reads, or every call's so far (A104)
[desktop.review]
send="queue"— queue | now — the Review pane's "Send to agent" while a turn runs: queue it behind the turn like a prompt, or send it at once (A108)
[external_agents.<name>]
An ACP agent a new session can be driven by instead of cox's own loop (T52.2, DT§3.3.1). None by default. User config only: a project .cox/config.toml cannot add, change or widen an entry, because an entry runs a program; the loader reverts it with a warning. cox never installs the agent: you install it, and cox runs the installed program.
command— the program that speaks ACP on stdio: a name found onPATH, or an absolute pathargs— arguments tocommandkey_env— the agent's own API-key variable. Only this one variable is passed through, by name, next to the child allowlist (PATH,HOME,LANG,LC_*,TERM,TMPDIR,USER,SHELL); cox's own provider keys stay behind. A key that does not resolve (env var, then keychain) leaves the agent out with one warningwritable— extra directories under your home the agent may write, for its own state (for example~/.claude). Each must resolve inside your home, never to your home itself; one that does not refuses the entry with a warning. A project config cannot set it
An external agent always runs under the sandbox wrap. Its file limits are the session's [sandbox] ones plus writable, but it always has network access, whatever sandbox.network says: it has to reach its vendor's API. The agent's model, cost and context are its own: cox writes no usage row for it and shows its cost as "—".
Examples (install the program first; these are not defaults):
[external_agents.claude] # "Claude Agent": npm install -g @agentclientprotocol/claude-agent-acp
command = "claude-agent-acp"
args = ["--hide-claude-auth"] # API key only, never a claude.ai login
key_env = "ANTHROPIC_API_KEY"
writable = ["~/.claude"]
[external_agents.codex] # "Codex": npm install -g @agentclientprotocol/codex-acp
command = "codex-acp"
key_env = "CODEX_API_KEY"
writable = ["~/.codex"]
[external_agents.gemini] # "Gemini CLI": npm install -g @google/gemini-cli
command = "gemini"
args = ["--acp"]
key_env = "GEMINI_API_KEY"
writable = ["~/.gemini"]
Cursor (agent acp, CURSOR_API_KEY) comes from the granted Cursor plugin's [[external_agents]] entry (plugins/cursor), not from this table.
~/.cox/keybindings.toml
Rebinds the TUI's keys (T25.5). Each line is an action id and a key, or a list of keys; dotted ids may be written as TOML tables. The keys you give replace the action's defaults, in every context the action has (idle, running), and take the key from whatever action held it by default. A missing file means the defaults in docs/getting-started.md.
send = "ctrl+enter"
newline = ["enter", "shift+enter"]
mode.cycle = "shift+tab"
- Actions:
send,newline,send.now,interrupt,mode.cycle,transcript,help,thinking,expand,diff,plugin.leader,background,unqueue,quit,copy,copy.all,voice.@,/,Ctrl+Rand the keys inside a picker or overlay are fixed; so isCtrl+C. plugin.leader(defaultctrl+k) rebinds the leader itself; a plugin's own keys, reachable only as<leader> <key>, come from the plugin's manifest, not from here — a clash between two plugins goes to the lower plugin id andcox doctorreports it.- Keys: modifiers
ctrl,alt(opt,meta),shift,cmd(super), then one key: a character,enter,esc,tab,space,backspace,delete, arrows,pageup,pagedown,home,end,f1–f12. Any case. Chords (ctrl+x ctrl+s) are not supported. - A plain terminal sends the same byte for
EnterandCtrl+Enter;ctrl+enterneeds a terminal that reports it (kitty keyboard protocol, seecox doctor). ~/.claude/keybindings.jsonis read first, for the actions both tools have:chat:submit→send,chat:newline→newline,chat:sendNow→send.now,chat:cancel→interrupt,chat:cycleMode→mode.cycle,app:toggleTranscript→transcript,task:background→background,app:exit→quit. Its keys are added beside the defaults; this file still wins. Other Claude actions and chords are skipped.- An unknown action, a bad key or a file that is not TOML is a warning in the transcript and is skipped.
cox doctorlists those and any key two of your bindings both claim.
Accessibility
--plain(ortui.screen_reader = true, orCOX_PLAIN=1) swaps the TUI for flat labelled lines a screen reader can follow: numbered prompts, no cursor movement, and a BEL when a turn ends.tui.motion = "reduced"stops everything that moves by itself. A running tool shows one still glyph andrunninginstead of a spinner and a ticking clock.tui.theme = "cox-dark-daltonized"or"cox-light-daltonized"are the built-in themes without a red/green pair. Added lines and success are blue; removed lines and failure are orange. Every state also keeps its glyph (✓,✗,+,−), so colour is never the only signal./themepreviews both.NO_COLOR(set and non-empty, whiletui.coloris"auto"), ortui.color = "none", prints no colour at all and leaves the terminal's own.
Status line command
[tui.status_line] (T46.4) runs your own command and draws the first line it prints as one row above the built-in status line; the built-in segments stay. An empty command is off.
- stdin is one JSON object with Claude Code's statusline field names, so an existing script runs unchanged:
session_id,cwd,workspace.current_dir,workspace.project_dir,model.id,model.display_name,cost.total_cost_usd,context_window.used_percentage,context_window.context_window_sizeandversion. cox addspermission_mode,sandbox_mode,git.branchandbusy.COLUMNSis the terminal width. - It runs 300 ms after any of those or the width changes, a newer change kills a run still going, and with
refresh_sset it also re-runs on that period. - A run longer than
timeout_ms, a non-zero exit or empty output blanks the row; it is never fatal. - It runs under the sandbox, read-only and without network (the session's own policy only under
danger-full-access), with the environment cleared to the child allowlist plusCOLUMNS. A host with no sandbox backend gets one warning and no row; the command never runs bare. - Its output is untrusted: every escape sequence is stripped, so colours and links are dropped, and the row is drawn dim.
- A project
.cox/config.tomlcannot setcommand: it would run on every start in a cloned repository, so the value is reverted with a warning, like the other guarded keys.
Theme editor
Ctrl+E on a colour row of the /theme picker (T46.7) opens that theme's 17 tokens with a swatch and the current value. Up/Down (or Tab) move, typing edits the selected value, and every colour that parses is drawn at once; one that does not is marked invalid colour and not applied. Esc puts back what was drawn before /theme opened.
Enter(orCtrl+S) writes~/.cox/themes/<stem>.toml, selects it astui.themeand lists it in/themewithout a restart. It edits each token's half for the background in use (darkorlight).- A built-in is never overwritten: its edits go to
<name>-custom.toml, which starts as a copy of the built-in's own file. A user theme is edited in place, keeping its comments and every other key. - A stem must match
[a-z0-9][a-z0-9._-]{0,63}with no..; any other is refused with a warning, and a failed write is a warning too. - The file: optional
variant = "dark"(or"light") andsyntax = "<.tmTheme name>", then[tokens]with<token> = { dark = "<colour>", light = "<colour>" }. A colour is#rrggbb, an ANSI index0-255or one of the sixteen ANSI names. The tokens are text, dim, accent, user, agent, tool, ok, warn, error, diff_add, diff_del, diff_hunk, border, selection, mode_plan, mode_auto and mode_bypass.