返回 CodeWhale
config.example.toml
根目录 / config.example.toml
1 # ╔══════════════════════════════════════════════════════════════════════════════╗
2 # ║ Codewhale Configuration ║
3 # ║ ║
4 # ║ Terminal coding agent for any model — open models first. ║
5 # ╚══════════════════════════════════════════════════════════════════════════════╝
6
7 # See `docs/CONFIGURATION.md` for how config is loaded (profiles, env overrides, etc.).
8
9 # ─────────────────────────────────────────────────────────────────────────────────
10 # Active provider + DeepSeek defaults
11 # ─────────────────────────────────────────────────────────────────────────────────
12 # Choose which provider to use by default. Per-provider credentials live in the
13 # `[providers.*]` sections near the bottom of
14 # this file — keeping both stored at once means `/provider deepseek` and
15 # `/provider nvidia-nim` (or `--provider openai`, `--provider wanjie-ark`,
16 # `--provider volcengine`, `--provider openrouter`, `--provider xiaomi-mimo`,
17 # `--provider fireworks`, `--provider siliconflow`, `--provider siliconflow-CN`,
18 # `/provider arcee`, `/provider moonshot`, `/provider qianfan`, `/provider sglang`, `/provider vllm`,
19 # `/provider ollama`, `/provider huggingface`, `/provider stepfun`, `/provider openmodel`,
20 # `/provider opencode-go`, `/provider opencode-zen`, `/provider meta`, `/provider xai`) toggle without having to re-enter keys. Top-level
21 # `api_key` / `base_url` are
22 # still read as DeepSeek defaults when `[providers.deepseek]` is absent
23 # (backward compatibility).
24 provider = "deepseek" # deepseek | deepseek-cn | deepseek-anthropic | nvidia-nim | openai | atlascloud | wanjie-ark | volcengine | openrouter | xiaomi-mimo | novita | fireworks | siliconflow | siliconflow-CN | arcee | moonshot | zai | stepfun | minimax | sglang | vllm | ollama | huggingface | together | qianfan | openai-codex | anthropic | openmodel | deepinfra | sakana | longcat | opencode-go | opencode-zen | meta | xai | modelstudio-token-plan | modelstudio-coding-plan
25 api_key = "YOUR_DEEPSEEK_API_KEY" # must be non-empty
26 base_url = "https://api.deepseek.com/beta"
27 # provider = "deepseek-cn" # legacy alias (official host is still https://api.deepseek.com)
28 # base_url = "https://api.deepseek.com" # opt out of DeepSeek beta features
29 # Optional custom model request headers for OpenAI-compatible gateways.
30 # Authorization and Content-Type are managed by the client and cannot be overridden here.
31 # http_headers = { "X-Model-Provider-Id" = "your-model-provider" }
32
33 # ─────────────────────────────────────────────────────────────────────────────────
34 # Default Models
35 # ─────────────────────────────────────────────────────────────────────────────────
36 # DeepSeek V4 family:
37 # deepseek-v4-pro — flagship reasoning model on DeepSeek Platform
38 # deepseek-v4-flash — fast, cost-efficient (legacy aliases: deepseek-chat, deepseek-reasoner)
39 # deepseek-ai/deepseek-v4-pro — NVIDIA NIM-hosted Pro model ID
40 # deepseek-ai/deepseek-v4-flash — NVIDIA NIM-hosted Flash model ID
41 # deepseek/deepseek-v4-pro — default OpenRouter DeepSeek model ID
42 # arcee-ai/trinity-large-thinking — OpenRouter Arcee Trinity Large Thinking
43 # xiaomi/mimo-v2.5-pro — OpenRouter Xiaomi MiMo 2.5 Pro
44 # xiaomi/mimo-v2.5 — OpenRouter Xiaomi MiMo 2.5
45 # z-ai/glm-5.1 — OpenRouter Z.AI GLM 5.1
46 # z-ai/glm-5.2 — OpenRouter Z.AI GLM 5.2 (default)
47 # z-ai/glm-5.3 — OpenRouter Z.AI GLM 5.3 (registered only; not released by Z.ai
48 # as of 2026-08-03 — metadata inherited from 5.2, unpriced)
49 # z-ai/glm-5-turbo — OpenRouter Z.AI GLM 5 Turbo (scout fast sibling)
50 # GLM-5.2 — default direct Z.AI Coding Plan model
51 # GLM-5.1 — direct Z.AI smaller model
52 # GLM-5.3 — direct Z.AI GLM 5.3 (registered only; not live on the Z.ai API
53 # as of 2026-08-03 — metadata inherited from 5.2, unpriced)
54 # GLM-5-Turbo — direct Z.AI fast model (scout fast sibling)
55 # step-3.7-flash — default direct StepFun / StepFlash model ID
56 # kimi-k3 — direct Moonshot K3 model ID (1M context)
57 # kimi-k2.7-code — default direct Moonshot/Kimi K2.7 model ID
58 # k3 — Kimi Code membership K3 API model ID
59 # kimi-for-coding — Kimi Code membership K2.7 compatibility ID
60 # kimi-for-coding-highspeed — Kimi Code membership high-speed roster ID
61 # gpt-4.1 — default generic OpenAI-compatible model ID
62 # deepseek-ai/deepseek-v4-flash — default AtlasCloud model ID
63 # deepseek-reasoner — default Wanjie Ark model ID
64 # mimo-v2.5-pro — default Xiaomi MiMo model ID
65 # mimo-v2.5-pro-ultraspeed — Xiaomi MiMo V2.5 Pro UltraSpeed chat model ID
66 # mimo-v2.5 — Xiaomi MiMo V2.5 Omni model ID
67 # mimo-v2.5-tts — Xiaomi MiMo speech/TTS model ID
68 # mimo-v2.5-tts-voicedesign — Xiaomi MiMo voice-design TTS model ID
69 # mimo-v2.5-tts-voiceclone — Xiaomi MiMo voice-clone TTS model ID
70 # accounts/fireworks/models/deepseek-v4-pro — Fireworks AI Pro model ID
71 # deepseek-ai/DeepSeek-V4-Pro — SiliconFlow hosted Pro model ID
72 # deepseek-ai/DeepSeek-V4-Flash — SiliconFlow hosted Flash model ID
73 # trinity-large-thinking — default direct Arcee AI API model ID
74 # trinity-large-preview — direct Arcee AI API model ID
75 # deepseek-ai/DeepSeek-V4-Pro — SGLang self-hosted Pro model ID
76 # deepseek-ai/DeepSeek-V4-Flash — SGLang self-hosted Flash model ID
77 default_text_model = "deepseek-v4-pro"
78
79 # ─────────────────────────────────────────────────────────────────────────────────
80 # Thinking Mode (DeepSeek V4 reasoning effort)
81 # ─────────────────────────────────────────────────────────────────────────────────
82 # "off" — disables chain-of-thought (thinking.type = disabled)
83 # "low" — compat-maps to "high" server-side
84 # "medium" — compat-maps to "high" server-side
85 # "high" — reasoning_effort = high (DeepSeek default)
86 # "max" — reasoning_effort = max (deepest reasoning)
87 #
88 # Ctrl+T in the TUI cycles the reasoning tier. The header shows the current
89 # tier as a ⚡ chip. (Shift+Tab cycles the permission posture — Ask /
90 # Auto-Review / Full Access — not the reasoning tier.)
91 reasoning_effort = "max"
92
93 # NOTE: `show_thinking`, `thinking_default_expanded`, and `cost_currency`
94 # live in `~/.codewhale/settings.toml`, not here — `Config` has no such
95 # fields and unknown keys are ignored. See crates/tui/src/settings.rs.
96
97 # ─────────────────────────────────────────────────────────────────────────────────
98 # Startup update check
99 # ─────────────────────────────────────────────────────────────────────────────────
100 # The TUI checks for newer Codewhale releases in the background at startup.
101 # Set check_for_updates = false in managed or air-gapped environments.
102 # The result is cached in ~/.codewhale/update-check.json, so the network is
103 # touched at most once per check_interval_hours while the notice still shows
104 # on every launch. Set 0 to check on every launch.
105 # Checks are skipped entirely in CI, and when CODEWHALE_NO_UPDATE_CHECK or
106 # NO_UPDATE_NOTIFIER is set.
107 # update_uri may point at a GitHub-compatible latest-release JSON endpoint.
108 [update]
109 check_for_updates = true
110 check_interval_hours = 24
111 # update_uri = "https://internal.mirror.example/codewhale/releases/latest"
112
113 # ─────────────────────────────────────────────────────────────────────────────────
114 # Hotbar slots (#2061 / #2064)
115 # ─────────────────────────────────────────────────────────────────────────────────
116 # Optional 1-8 sidebar hotbar bindings. Since #3807 a missing `hotbar` key
117 # renders NO bar — the Hotbar is hidden until you configure [[hotbar]]
118 # tables here (or via `/hotbar`). An explicit `hotbar = []` also disables it.
119 #
120 # Invalid slots are skipped with a warning, duplicate slots use the last entry,
121 # and unknown actions are preserved so the UI can show a disabled entry.
122 # Slash commands can be bound as slash.<name>, for example slash.mode. Commands
123 # that require arguments pre-fill the composer instead of running incomplete.
124 #
125 # [[hotbar]]
126 # slot = 1
127 # label = "voice"
128 # action = "voice.toggle"
129 #
130 # [[hotbar]]
131 # slot = 2
132 # action = "session.compact"
133 #
134 # [[hotbar]]
135 # slot = 3
136 # label = "mode"
137 # action = "slash.mode"
138
139 # ─────────────────────────────────────────────────────────────────────────────────
140 # Paths
141 # ─────────────────────────────────────────────────────────────────────────────────
142 # New installs write product state under ~/.codewhale/. Existing ~/.deepseek/
143 # files are still read as compatibility fallbacks when the .codewhale file is
144 # absent.
145 skills_dir = "~/.codewhale/skills"
146 mcp_config_path = "~/.codewhale/mcp.json"
147 notes_path = "~/.codewhale/notes.txt"
148
149 # Anchors the native memory store. The filename itself is not written: under
150 # the Native backend (the only backend) the store is re-rooted to
151 # `<parent-of-this-path>/memory/global/MEMORY.md`. With the default below that
152 # resolves to `~/.codewhale/memory/global/MEMORY.md` (plus workspace-scoped
153 # files and a rebuildable SQLite FTS5 index). See docs/MEMORY.md.
154 memory_path = "~/.codewhale/memory.md"
155
156 # instructions = ["./AGENTS.md", "~/.codewhale/global.md"]
157 #
158 # Optional list of additional instruction files concatenated into the
159 # system prompt in declared order (#454). Useful for layering
160 # repo-specific rules on top of a global preferences file. Each entry
161 # is expanded so `~` and env vars work; missing files are skipped with
162 # a tracing warning. Files are capped at 100 KiB per entry.
163 #
164 # Project-level config (.codewhale/config.toml in the workspace) replaces
165 # the user-level array wholesale rather than merging — list `~/global.md`
166 # inside the project array if you want both. An explicit empty array
167 # (`instructions = []`) clears the user list for the current repo.
168
169 # ─────────────────────────────────────────────────────────────────────────────────
170 # User memory (#489) — opt-in. When enabled, the TUI loads the native store
171 # derived from memory_path (see above), injects a bounded recall block into
172 # the system prompt, intercepts `# foo` in the composer, and registers the
173 # `remember` / `memory_search` / `memory_get` tools.
174 # ─────────────────────────────────────────────────────────────────────────────────
175 [memory]
176 # enabled = true # turn the feature on (default: false)
177 # Override the env-var equivalent: `DEEPSEEK_MEMORY=on`
178
179 # Xiaomi MiMo speech/TTS defaults. Also configurable with
180 # XIAOMI_MIMO_SPEECH_OUTPUT_DIR / MIMO_SPEECH_OUTPUT_DIR.
181 [speech]
182 # output_dir = "./speech"
183
184 # Native tool catalog controls (#2076). By default only the core tool surface
185 # is loaded into the model context; less common native tools are discoverable
186 # through ToolSearch and loaded on first use.
187 # [tools]
188 # always_load = ["git_show", "notify"]
189
190 # ─────────────────────────────────────────────────────────────────────────────────
191 # Product telemetry — opt-in, off by default
192 # ─────────────────────────────────────────────────────────────────────────────────
193 # Nothing is collected unless BOTH of these are true: `telemetry = true` here,
194 # and the first-run notice has been answered with "Enable" on this machine. A
195 # `telemetry = true` written before 0.9.4 is therefore inert — the key existed
196 # and did nothing for a long time, and setting it was never consent.
197 #
198 # Setting it to `false` here is an answer, not just a flag: it deletes the
199 # random install id, truncates every buffered event, and leaves a tombstone
200 # that a session already running re-checks before it sends anything. Every
201 # later run re-reads this key and re-asserts that tombstone, so it stands for
202 # as long as the `false` does — and nothing outranks it, not `--telemetry true`
203 # and not `CODEWHALE_TELEMETRY=1`. Turning telemetry back on means writing
204 # `telemetry = true` here.
205 #
206 # The environment variable and the flag are different: they stop the run and
207 # erase nothing, so a harness that disables telemetry for one command does not
208 # discard the machine owner's install id and dry-run records.
209 #
210 # codewhale config set telemetry false # opt out: stops it and erases state
211 # CODEWHALE_TELEMETRY=0 codewhale # kill switch: stops it, erases nothing
212 #
213 # What is never collected: prompts, completions, tool arguments, diffs, file
214 # contents, filenames, paths, git remotes, repo or branch names, memory
215 # entries, chat history, API keys or tokens (not even a boolean saying one
216 # exists), model ids, custom provider table names, MCP server names, error or
217 # panic message bodies, per-event timestamps, keystrokes, clipboard,
218 # screenshots, or location. The complete schema is `docs/TELEMETRY.md`, and a
219 # test asserts this file and the serializer agree.
220 #
221 # A repo-local `.codewhale/config.toml` can set neither key: someone else's
222 # repository cannot turn your telemetry on or aim it at a host of their choosing.
223 # telemetry = false
224
225 # Where batches are POSTed. Leaving this unset selects the shipped default,
226 # the first-party ingest service:
227 #
228 # https://telemetry.codewhale.net/v1/telemetry
229 #
230 # That default is only ever consulted for a session that is already enabled —
231 # it decides where a batch goes, never whether one exists. Nothing is sent
232 # until `telemetry` is on AND the first-run notice was answered with Enable.
233 #
234 # Two overrides, both of which beat the default:
235 #
236 # telemetry_endpoint = "https://collector.internal/v1/batch" # your own sink
237 # telemetry_endpoint = "" # contact nobody
238 #
239 # The empty string is the local dry-run sink: batches are serialized exactly as
240 # a real endpoint would see them, appended to
241 # `$CODEWHALE_HOME/telemetry/dryrun.jsonl`, and no HTTP client is ever
242 # constructed. Read that file to see precisely what would have been sent.
243 # `CODEWHALE_TELEMETRY_ENDPOINT` overrides this file, and setting it to the
244 # empty string means the same "contact nobody".
245 #
246 # `https://` is required; plain `http://` is accepted only for loopback, and no
247 # environment variable overrides that refusal.
248 # telemetry_endpoint = "https://telemetry.codewhale.net/v1/telemetry"
249
250 # ─────────────────────────────────────────────────────────────────────────────────
251 # Security
252 # ─────────────────────────────────────────────────────────────────────────────────
253 allow_shell = true
254 approval_policy = "on-request" # on-request | untrusted | never
255 sandbox_mode = "workspace-write" # read-only | workspace-write | danger-full-access | external-sandbox
256 # prompt_suggestion = true # opt-in: show ghost-text follow-up question in composer after each turn
257
258 # Typed permission rules live in a sibling `permissions.toml` file, not in
259 # config.toml. Each `[[rules]]` entry accepts `tool`, optional `command`
260 # or `path`, optional absolute `workspace`, optional `command_exact = true`,
261 # and an `action` field: `"deny"`, `"ask"` (default), or `"allow"`.
262 # Deny always wins over ask, which wins over allow. The active user's sibling
263 # file is the only rule source today; project config overlays do not load a
264 # project-local permissions.toml. `workspace` is a repo scope, not a source.
265 # Globs and broad directory rules remain future work.
266 #
267 # In supported approval cards, press `S` to allow once and save an exact ask
268 # rule. Eligible safe calls also offer `P` / "Always allow this exact rule in
269 # this repo", which saves an exact `allow` rule scoped to the current absolute
270 # workspace. Dangerous/critical calls and repo-law prompts cannot save allow
271 # grants. The approval UI still does not save deny rules:
272 # exec_shell -> exact approved command string
273 # write_file -> exact workspace-relative target path
274 # edit_file -> exact workspace-relative target path
275 # apply_patch -> one exact workspace-relative path per validated touched file
276 # `read_file` rules can be written manually, but the approval UI does not save
277 # them.
278 #
279 # `/permissions list` shows the active source, effective matcher, repo/global
280 # scope, and whether each rule applies in the current workspace.
281 # `/permissions remove <number>` previews a deletion and prints a confirmation
282 # command bound to the current file snapshot. If the file changes first, the
283 # confirmation fails safely. Removal preserves unrelated comments/formatting,
284 # uses the same lock as approval-card appends, and atomically replaces the file.
285 # `/config ask-rules` remains a compatibility entry to the same list.
286 #
287 # Example ~/.codewhale/permissions.toml:
288 #
289 # [[rules]]
290 # tool = "exec_shell"
291 # command = "cargo test"
292 # action = "ask"
293 #
294 # # Block dangerous commands
295 # [[rules]]
296 # tool = "exec_shell"
297 # command = "sed"
298 # action = "deny"
299 #
300 # [[rules]]
301 # tool = "exec_shell"
302 # command = "awk"
303 # action = "deny"
304 #
305 # # Allow trusted commands without asking
306 # [[rules]]
307 # tool = "exec_shell"
308 # command = "git status"
309 # command_exact = true
310 # workspace = "/absolute/path/to/project"
311 # action = "allow"
312 #
313 # # Path-based deny
314 # [[rules]]
315 # tool = "write_file"
316 # path = "src/main.rs"
317 # action = "deny"
318 #
319 # [[rules]]
320 # tool = "edit_file"
321 # path = "src/lib.rs"
322 # action = "ask"
323 #
324 # [[rules]]
325 # tool = "apply_patch"
326 # path = "src/patch-target.rs"
327 #
328 # [[rules]]
329 # tool = "read_file"
330 # path = "secrets/api_key.txt"
331 # action = "deny"
332 # ─────────────────────────────────────────────────────────────────────────────────
333 # External Sandbox Backend (pluggable remote execution)
334 # ─────────────────────────────────────────────────────────────────────────────────
335 # When sandbox_backend is set to "opensandbox", all exec_shell calls are
336 # routed through an external OpenSandbox-compatible HTTP API instead of
337 # spawning a local process. The backend sends `POST {sandbox_url}/v1/sandbox/run`
338 # with `{"cmd": "...", "env": {...}}` and expects
339 # `{"stdout": "...", "stderr": "...", "exit_code": 0}`.
340 #
341 # sandbox_backend = "none" # "none" (default) or "opensandbox"
342 # sandbox_url = "http://localhost:8080" # OpenSandbox-compatible API base URL
343 # sandbox_api_key = "YOUR_API_KEY" # Optional Bearer token sent with requests
344 #
345 # Env-var overrides:
346 # DEEPSEEK_SANDBOX_BACKEND → sandbox_backend
347 # DEEPSEEK_SANDBOX_URL → sandbox_url
348 # DEEPSEEK_SANDBOX_API_KEY → sandbox_api_key
349 #
350 # Example OpenSandbox setup:
351 #
352 # sandbox_backend = "opensandbox"
353 # sandbox_url = "http://localhost:8080"
354 # sandbox_api_key = "sk-opensandbox-secret"
355 #
356 # The backend uses a 30-second HTTP timeout. Background, interactive, and
357 # TTY modes are not supported with external backends — all commands run
358 # synchronously via HTTP.
359 # ─────────────────────────────────────────────────────────────────────────────────
360 # Bubblewrap (Linux only, additional filesystem isolation)
361 # ─────────────────────────────────────────────────────────────────────────────────
362 # When set to true and `/usr/bin/bwrap` is present, exec_shell commands are
363 # routed through bubblewrap instead of relying solely on Landlock. Bubblewrap
364 # creates a read-only view of the root filesystem with write access limited to
365 # the working directory. Install separately:
366 #
367 # Ubuntu/Debian: apt install bubblewrap
368 # Fedora: dnf install bubblewrap
369 # Arch: pacman -S bubblewrap
370 #
371 # prefer_bwrap = false # default — use Landlock only
372 #
373 # Env override: DEEPSEEK_PREFER_BWRAP=true
374
375 # auto_allow entries match by command prefix, not raw string.
376 # See command_safety.rs for the prefix dictionary.
377 #
378 # Examples:
379 # auto_allow = ["git status"] # auto-approves: git status, git status -s, git status --porcelain
380 # # does NOT auto-approve: git push, git checkout
381 # auto_allow = ["cargo check", "npm run"]
382 #
383 # auto_allow = []
384 max_subagents = 10 # optional (1-20)
385
386 # Optional sub-agent tuning. max_concurrent overrides top-level max_subagents.
387 # [subagents]
388 # max_concurrent = 10
389 # api_timeout_secs = 600 # per-step API timeout, clamped to 1..=3600
390 #
391 # How many levels of nested sub-agents the `agent` tool may spawn:
392 # max_depth = 0 # opt out completely — the agent never spawns sub-agents
393 # max_depth = 1 # the agent may spawn sub-agents, but those may not spawn more
394 # max_depth = 2 # one more level of nesting, etc.
395 # Unset defaults to 3; any value is clamped to the hard ceiling (3). The depth
396 # limit is enforced in code, not requested of the model — a sub-agent past the
397 # limit cannot be spawned regardless of what the model decides.
398 # max_depth = 3
399
400 # Optional managed policy paths (defaults to /etc/deepseek/*.toml on unix):
401 # managed_config_path = "/etc/deepseek/managed_config.toml"
402 # requirements_path = "/etc/deepseek/requirements.toml"
403
404 # ─────────────────────────────────────────────────────────────────────────────────
405 # Per-provider credentials (peer providers — NIM is first-class, not a flag)
406 # ─────────────────────────────────────────────────────────────────────────────────
407 # Providers can be stored at once; `provider = "..."` (top of file) or
408 # `/provider deepseek` / `/provider nvidia-nim` / `--provider openai` /
409 # `--provider wanjie-ark` / `/provider volcengine` / `/provider fireworks` /
410 # `--provider siliconflow` / `/provider arcee` / `/provider moonshot`
411 # switches between them without having to re-enter keys. Env vars override anything set here:
412 # DeepSeek: DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL
413 # DeepSeek Anthropic-compatible: DEEPSEEK_API_KEY, DEEPSEEK_ANTHROPIC_BASE_URL
414 # NIM: NVIDIA_API_KEY (or NVIDIA_NIM_API_KEY), NIM_BASE_URL
415 # (or NVIDIA_NIM_BASE_URL / NVIDIA_BASE_URL), NVIDIA_NIM_MODEL
416 # OpenAI-compatible: OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL
417 # Wanjie Ark: WANJIE_ARK_API_KEY (or WANJIE_API_KEY), WANJIE_ARK_BASE_URL, WANJIE_ARK_MODEL
418 # Volcengine Ark: VOLCENGINE_API_KEY (or VOLCENGINE_ARK_API_KEY / ARK_API_KEY), VOLCENGINE_BASE_URL, VOLCENGINE_MODEL
419 # OpenRouter: OPENROUTER_API_KEY, OPENROUTER_BASE_URL, OPENROUTER_MODEL
420 # Xiaomi MiMo: XIAOMI_MIMO_API_KEY (or XIAOMI_API_KEY / MIMO_API_KEY), XIAOMI_MIMO_BASE_URL, XIAOMI_MIMO_MODEL
421 # Token Plan: XIAOMI_MIMO_TOKEN_PLAN_API_KEY (or MIMO_TOKEN_PLAN_API_KEY), XIAOMI_MIMO_MODE/MIMO_MODE
422 # Novita: NOVITA_API_KEY, NOVITA_BASE_URL, NOVITA_MODEL
423 # Fireworks: FIREWORKS_API_KEY, FIREWORKS_BASE_URL
424 # SiliconFlow: SILICONFLOW_API_KEY, SILICONFLOW_BASE_URL, SILICONFLOW_MODEL
425 # Arcee: ARCEE_API_KEY, ARCEE_BASE_URL, ARCEE_MODEL
426 # Moonshot/Kimi: MOONSHOT_API_KEY (or KIMI_API_KEY), MOONSHOT_BASE_URL, MOONSHOT_MODEL
427 # SGLang: SGLANG_BASE_URL, SGLANG_MODEL, optional SGLANG_API_KEY
428 # vLLM: VLLM_BASE_URL, VLLM_MODEL, optional VLLM_API_KEY
429 # Ollama: OLLAMA_BASE_URL, OLLAMA_MODEL, optional OLLAMA_API_KEY
430 # Hugging Face: HUGGINGFACE_API_KEY (or HF_TOKEN), HUGGINGFACE_BASE_URL (or HF_BASE_URL),
431 # HUGGINGFACE_MODEL (or HF_MODEL)
432 # Meta Model API: META_MODEL_API_KEY (or MODEL_API_KEY), META_MODEL_API_BASE_URL
433 # (or MODEL_API_BASE_URL), META_MODEL_API_MODEL (or MODEL_API_MODEL)
434 #
435 # Custom DeepSeek-compatible APIs usually do not need a new provider table:
436 # set `provider = "deepseek"` and override [providers.deepseek].base_url/model.
437 # For generic OpenAI-compatible gateways, use `provider = "openai"` and the
438 # [providers.openai] table below. Keep provider/api_key/base_url in user config
439 # or environment variables; project overlays are not allowed to set them.
440 #
441 # Provider is the route/account/endpoint; model is the ID on that route.
442 # Common DeepSeek routes:
443 # provider = "deepseek" model = "deepseek-v4-pro"
444 # provider = "nvidia-nim" model = "deepseek-ai/deepseek-v4-pro"
445 # provider = "openrouter" model = "deepseek/deepseek-v4-pro"
446 # provider = "fireworks" model = "accounts/fireworks/models/deepseek-v4-pro"
447 # provider = "siliconflow" model = "deepseek-ai/DeepSeek-V4-Pro"
448
449 # DeepSeek Platform (https://platform.deepseek.com)
450 [providers.deepseek]
451 # api_key = "YOUR_DEEPSEEK_API_KEY"
452 # base_url = "https://api.deepseek.com/beta"
453 # model = "deepseek-v4-pro"
454 # Custom DeepSeek-compatible example:
455 # base_url = "https://your-provider.example/v1"
456 # model = "deepseek-ai/DeepSeek-V4-Pro"
457 # http_headers = { "X-Model-Provider-Id" = "your-model-provider" } # optional custom request headers
458 # path_suffix = "/chat/completions" # override the API path; skips /v1 versioning when set
459 # reasoning_stream_style = "inline_tags" # route <think>...</think> content into Thinking cells
460
461 # DeepSeek Anthropic-compatible Messages route (opt-in)
462 # [providers.deepseek_anthropic]
463 # api_key = "YOUR_DEEPSEEK_API_KEY"
464 # base_url = "https://api.deepseek.com/anthropic"
465 # model = "deepseek-v4-pro"
466 # [providers.deepseek.auth] # provider-scoped auth source metadata; command execution lands in a follow-up slice
467 # source = "command"
468 # command = ["secret-tool", "lookup", "service", "codewhale-deepseek"]
469 # timeout_ms = 2000
470 # insecure_skip_tls_verify = true # last resort for private gateways; prefer SSL_CERT_FILE
471
472 # NVIDIA NIM-hosted DeepSeek V4 (https://build.nvidia.com)
473 [providers.nvidia_nim]
474 # api_key = "YOUR_NVIDIA_API_KEY"
475 # base_url = "https://integrate.api.nvidia.com/v1"
476 # model = "deepseek-ai/deepseek-v4-pro" # or deepseek-ai/deepseek-v4-flash
477
478 # Generic OpenAI-compatible endpoint. Use the built-in `openai` provider for
479 # third-party gateways; do not invent a custom provider name. For non-local
480 # http:// gateways, launch with DEEPSEEK_ALLOW_INSECURE_HTTP=1 only on a
481 # trusted network.
482 [providers.openai]
483 # api_key = "YOUR_OPENAI_COMPATIBLE_API_KEY"
484 # base_url = "https://api.openai.com/v1"
485 # model = "gpt-4.1"
486 # Gateway example:
487 # base_url = "https://gateway.example/v1"
488 # model = "your-deepseek-compatible-model"
489 # Alibaba Bailian / Model Studio DashScope OpenAI-compatible example:
490 # base_url = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
491 # model = "qwen-plus"
492 # context_window = 1000000 # set the gateway/model's real total context window
493 # insecure_skip_tls_verify = true # last resort for private gateways; prefer SSL_CERT_FILE
494
495 # AtlasCloud OpenAI-compatible endpoint (https://www.atlascloud.ai/docs/models/llm)
496 [providers.atlascloud]
497 # api_key = "YOUR_ATLASCLOUD_API_KEY"
498 # base_url = "https://api.atlascloud.ai/v1"
499 # model = "deepseek-ai/deepseek-v4-flash"
500
501 # Wanjie Ark / 万界方舟 OpenAI-compatible endpoint
502 [providers.wanjie_ark]
503 # api_key = "YOUR_WANJIE_API_KEY"
504 # base_url = "https://maas-openapi.wanjiedata.com/api/v1"
505 # model = "deepseek-reasoner" # or the exact model ID enabled on your Wanjie account
506
507 # Volcengine / Volcano Engine Ark Coding API
508 [providers.volcengine]
509 # api_key = "YOUR_VOLCENGINE_API_KEY"
510 # base_url = "https://ark.cn-beijing.volces.com/api/coding/v3"
511 # model = "DeepSeek-V4-Pro" # or DeepSeek-V4-Flash
512
513 # OpenRouter — multi-provider gateway (https://openrouter.ai)
514 [providers.openrouter]
515 # api_key = "YOUR_OPENROUTER_API_KEY"
516 # base_url = "https://openrouter.ai/api/v1"
517 # model = "deepseek/deepseek-v4-pro"
518 # OpenRouter-compatible gateways can reuse this provider so reasoning/cache
519 # parsing stays on the OpenRouter-compatible path instead of generic OpenAI:
520 # base_url = "https://openrouter-compatible.example/v1"
521 # model = "deepseek/deepseek-v4-pro"
522 # Recent large model IDs also accepted here include arcee-ai/trinity-large-thinking,
523 # minimax/minimax-m3, minimax/minimax-m2.7, xiaomi/mimo-v2.5-pro, qwen/qwen3.6-flash,
524 # qwen/qwen3.6-35b-a3b, qwen/qwen3.6-max-preview, qwen/qwen3.6-27b, qwen/qwen3.6-plus,
525 # qwen/qwen3.7-max, google/gemma-4-31b-it, z-ai/glm-5.1, z-ai/glm-5.2,
526 # moonshotai/kimi-k2.6,
527 # nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free, and nvidia/nemotron-3-ultra.
528
529 # Xiaomi MiMo OpenAI-compatible endpoint (https://platform.xiaomimimo.com)
530 [providers.xiaomi_mimo]
531 # api_key = "YOUR_XIAOMI_KEY"
532 # base_url = "https://token-plan-sgp.xiaomimimo.com/v1" # Token Plan / tp- keys
533 # # base_url = "https://token-plan-ams.xiaomimimo.com/v1" # Token Plan Europe / Amsterdam
534 # # base_url = "https://api.xiaomimimo.com/v1" # Pay-as-you-go / sk- keys
535 # model = "mimo-v2.5-pro" # chat/reasoning
536 # Chat model IDs: mimo-v2.5-pro, mimo-v2.5-pro-ultraspeed, mimo-v2.5
537 # Token Plan subscriptions use separate tp-* API keys plus api-key auth.
538 # mode = "token-plan-sgp" # default Token Plan endpoint
539 # mode = "token-plan-cn" # China cluster
540 # mode = "token-plan-ams" # Europe cluster
541 # mode = "pay-as-you-go" # standard API / sk- keys
542 # TTS aliases are also accepted by `codewhale speech`: tts, voice-design, voice-clone
543 # TTS model IDs: mimo-v2.5-tts, mimo-v2.5-tts-voicedesign, mimo-v2.5-tts-voiceclone, mimo-v2-tts
544
545 # Novita AI-hosted inference (https://novita.ai)
546 [providers.novita]
547 # api_key = "YOUR_NOVITA_API_KEY"
548 # base_url = "https://api.novita.ai/openai/v1"
549 # model = "deepseek/deepseek-v4-pro" # or deepseek/deepseek-v4-flash
550
551 # Fireworks AI-hosted DeepSeek V4 (https://fireworks.ai)
552 [providers.fireworks]
553 # api_key = "YOUR_FIREWORKS_API_KEY"
554 # base_url = "https://api.fireworks.ai/inference/v1"
555 # model = "accounts/fireworks/models/deepseek-v4-pro"
556
557 # SiliconFlow-hosted DeepSeek V4 (https://siliconflow.com)
558 [providers.siliconflow]
559 # api_key = "YOUR_SILICONFLOW_API_KEY"
560 # base_url = "https://api.siliconflow.com/v1"
561 # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash
562
563 # SiliconFlow China-hosted DeepSeek V4 (https://siliconflow.cn)
564 # Falls back to [providers.siliconflow] for api_key / base_url / model when unset.
565 [providers.siliconflow-CN]
566 # api_key = "YOUR_SILICONFLOW_API_KEY"
567 # base_url = "https://api.siliconflow.cn/v1"
568 # model = "deepseek-ai/DeepSeek-V4-Pro"
569
570 # Arcee AI direct OpenAI-compatible endpoint (https://docs.arcee.ai)
571 [providers.arcee]
572 # api_key = "YOUR_ARCEE_API_KEY"
573 # base_url = "https://api.arcee.ai/api/v1"
574 # model = "trinity-large-thinking" # or trinity-large-preview
575
576 # Moonshot/Kimi OpenAI-compatible endpoint (https://platform.kimi.ai)
577 [providers.moonshot]
578 # api_key = "YOUR_MOONSHOT_API_KEY" # or KIMI_API_KEY
579 # base_url = "https://api.moonshot.ai/v1" # or KIMI_BASE_URL
580 # model = "kimi-k3" # direct Moonshot K3 wire ID
581 # Direct K3 is always-thinking: off -> low, medium -> high, and max remains max.
582 # The exact route sends top-level reasoning_effort, max_completion_tokens, and
583 # omits temperature/top_p per https://platform.kimi.ai/docs/guide/kimi-k3-quickstart.
584 # Kimi Code membership path (key: https://www.kimi.com/code/console):
585 # api_key = "YOUR_KIMI_CODE_API_KEY"
586 # base_url = "https://api.kimi.com/coding/v1"
587 # model = "k3" # Kimi Code K3 wire ID
588 # K3 membership off -> enabled/low; dispatched auto selects a concrete tier.
589 # Only an omitted reasoning setting leaves the provider default in control.
590 # Moderato plans are capped at 262144; Allegretto and above unlock up to 1048576.
591 # context_window = 262144 # manually cap k3 to the Moderato/256K window
592 # context_window = 1048576 # Allegretto+ only; do not claim an unavailable entitlement
593 # Alternatively use model = "k3-256k" with context_window = 262144 for the fixed 256K route.
594 # Omit context_window to keep Codewhale's safe 262144-token bare-k3 baseline.
595 # `k3[1m]` is a Claude Code-only convention, not an API model ID; Codewhale rejects it.
596 # Kimi Code K2.7 remains available to all members as model = "kimi-for-coding".
597 # Kimi OAuth is not supported. Legacy auth_mode = "kimi_oauth" fails closed
598 # to the API-key guidance above without probing Kimi CLI credential files.
599
600 # Z.AI GLM Coding Plan endpoint (https://docs.z.ai)
601 [providers.zai]
602 # api_key = "YOUR_ZAI_API_KEY" # or Z_AI_API_KEY
603 # base_url = "https://api.z.ai/api/coding/paas/v4"
604 # # General API endpoint, if you are not using the Coding Plan:
605 # # base_url = "https://api.z.ai/api/paas/v4"
606 # model = "GLM-5.2" # default; GLM-5.1 is the smaller model, GLM-5-Turbo the fast sub-agent sibling
607 # # GLM-5.3 is registered/selectable (model = "GLM-5.3") so the id resolves to
608 # # Z.ai instead of being rewritten to another model, but it was NOT live on the
609 # # Z.ai API as of 2026-08-03 and will fail upstream until Z.ai ships it. Its
610 # # catalog metadata is inherited from GLM-5.2 pending official Z.ai release
611 # # metadata, and it carries no price. GLM-5.2 remains the default.
612
613 # StepFun / StepFlash direct OpenAI-compatible endpoint (https://platform.stepfun.ai)
614 [providers.stepfun]
615 # api_key = "YOUR_STEPFUN_API_KEY" # or STEP_API_KEY
616 # base_url = "https://api.stepfun.ai/v1" # or STEP_BASE_URL
617 # # Coding Plan endpoint:
618 # # base_url = "https://api.stepfun.ai/step_plan/v1"
619 # model = "step-3.7-flash" # or STEPFUN_MODEL / STEP_MODEL
620
621 # MiniMax direct OpenAI-compatible endpoint (https://platform.minimax.io)
622 [providers.minimax]
623 # api_key = "YOUR_MINIMAX_API_KEY"
624 # base_url = "https://api.minimax.io/v1"
625 # model = "MiniMax-M3" # or MiniMax-M2.7, MiniMax-M2.7-highspeed
626 # # MiniMax also publishes Anthropic-compatible endpoints:
627 # # global https://api.minimax.io/anthropic, China https://api.minimaxi.com/anthropic.
628
629 # Self-hosted SGLang OpenAI-compatible server
630 [providers.sglang]
631 # api_key = "OPTIONAL_SGLANG_TOKEN"
632 # base_url = "http://localhost:30000/v1"
633 # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash
634
635 # Self-hosted vLLM OpenAI-compatible server
636 [providers.vllm]
637 # api_key = "OPTIONAL_VLLM_TOKEN"
638 # base_url = "http://localhost:8000/v1"
639 # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash
640
641 # Self-hosted Ollama OpenAI-compatible server
642 [providers.ollama]
643 # api_key = "OPTIONAL_OLLAMA_TOKEN"
644 # base_url = "http://localhost:11434/v1"
645 # model = "deepseek-v4-flash" # or any local Ollama tag
646
647 # Hugging Face Inference Providers (https://huggingface.co/docs/api-inference)
648 # Provider aliases: huggingface, hugging-face, hugging_face, hf
649 # Env var aliases: HUGGINGFACE_API_KEY / HF_TOKEN, HUGGINGFACE_BASE_URL / HF_BASE_URL,
650 # HUGGINGFACE_MODEL / HF_MODEL
651 [providers.huggingface]
652 # api_key = "YOUR_HF_TOKEN"
653 # base_url = "https://router.huggingface.co/v1"
654 # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash
655
656 # DeepInfra — AI inference cloud (https://deepinfra.com)
657 [providers.deepinfra]
658 # api_key = "YOUR_DEEPINFRA_TOKEN"
659 # base_url = "https://api.deepinfra.com/v1/openai"
660 # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash
661
662 # ─────────────────────────────────────────────────────────────────────────────────
663 # Sakana AI Fugu Provider (https://api.sakana.ai)
664 # Provider aliases: sakana, sakana-ai, sakana_ai, fugu
665 # Env var aliases: FUGU_API_KEY, SAKANA_API_KEY
666 [providers.sakana]
667 # api_key = "YOUR_FUGU_API_KEY"
668 # base_url = "https://api.sakana.ai/v1"
669 # model = "fugu" # or fugu-ultra-20260615
670
671 # Meituan LongCat Provider (https://longcat.chat/platform)
672 # OpenAI-compatible curated gateway for Meituan's LongCat models.
673 # Provider aliases: longcat, long-cat, meituan-longcat, meituan
674 # Env var aliases: LONGCAT_API_KEY
675 [providers.longcat]
676 # api_key = "YOUR_LONGCAT_API_KEY"
677 # base_url = "https://api.longcat.chat/openai/v1"
678 # model = "LongCat-2.0"
679
680 # OpenCode Go (https://opencode.ai/docs/go/)
681 # Subscription-backed OpenAI-compatible Chat Completions route.
682 # Env vars: OPENCODE_GO_API_KEY, OPENCODE_GO_BASE_URL, OPENCODE_GO_MODEL
683 # Chat Completions models: deepseek-v4-pro, grok-4.5, glm-5.2, glm-5.1,
684 # kimi-k3, kimi-k2.7-code, kimi-k2.6, deepseek-v4-flash, mimo-v2.5,
685 # mimo-v2.5-pro.
686 # Models documented only on OpenCode Go's Anthropic `/messages` endpoint are
687 # intentionally not advertised by this provider yet.
688 [providers.opencode_go]
689 # api_key = "YOUR_OPENCODE_GO_API_KEY"
690 # base_url = "https://opencode.ai/zen/go/v1"
691 # model = "deepseek-v4-pro"
692
693 # OpenCode Zen (https://opencode.ai/docs/zen/)
694 # Model-aware gateway: GPT models use Responses, Claude/Qwen use Anthropic
695 # Messages, and DeepSeek/MiniMax/GLM/Kimi/Grok/free models use Chat Completions.
696 # Gemini uses a Google-specific protocol that Codewhale does not implement and
697 # therefore fails closed instead of being sent with the wrong request shape.
698 # Env vars: OPENCODE_ZEN_API_KEY (preferred), OPENCODE_API_KEY,
699 # OPENCODE_ZEN_BASE_URL, OPENCODE_ZEN_MODEL
700 [providers.opencode_zen]
701 # api_key = "YOUR_OPENCODE_ZEN_API_KEY"
702 # base_url = "https://opencode.ai/zen/v1"
703 # model = "gpt-5.5" # Responses default
704 # model = "claude-sonnet-4-6" # Anthropic Messages example
705 # model = "deepseek-v4-pro" # Chat Completions example
706
707 # Meta Model API / Muse Spark (https://developer.meta.com/ai/)
708 # OpenAI-compatible Chat Completions route.
709 # Provider aliases: meta, meta-ai, meta-model-api, muse, muse-spark
710 # Env var aliases: META_MODEL_API_KEY / MODEL_API_KEY,
711 # META_MODEL_API_BASE_URL / MODEL_API_BASE_URL,
712 # META_MODEL_API_MODEL / MODEL_API_MODEL
713 [providers.meta]
714 # api_key = "YOUR_META_MODEL_API_KEY"
715 # base_url = "https://api.meta.ai/v1"
716 # model = "muse-spark-1.1"
717
718 # xAI / Grok Provider (https://console.x.ai/)
719 # OpenAI-compatible Chat Completions route.
720 # Provider aliases: xai, x-ai, x_ai, grok
721 # Env var aliases: XAI_API_KEY, XAI_BASE_URL, XAI_MODEL
722 #
723 # Auth modes:
724 # api_key (default) — console.x.ai pay-per-use key via api_key / XAI_API_KEY / keyring
725 # oauth — `codewhale auth xai-device` uses Codewhale-owned storage.
726 # Reading an existing Grok CLI file requires explicit
727 # `codewhale auth external-consent --provider xai --mode read-only`.
728 [providers.xai]
729 # api_key = "YOUR_XAI_API_KEY"
730 # auth_mode = "oauth" # or "device_code" / "grok_cli"
731 # base_url = "https://api.x.ai/v1"
732 # model = "grok-4.5" # or grok-4.3, grok-build
733
734 # ─────────────────────────────────────────────────────────────────────────────────
735 # Alibaba Cloud Model Studio — Token Plan
736 # (https://bailian.console.aliyun.com/)
737 #
738 # Token Plan Personal and Team share the same AP-Southeast (Singapore) endpoint.
739 # Available text/coding models: qwen3.8-max, qwen3.8-max-preview, qwen3.7-plus,
740 # qwen3.7-max, qwen3.6-flash, deepseek-v4-pro, deepseek-v4-flash-0731, glm-5.2
741 #
742 # Provider aliases: modelstudio-token-plan, modelstudio_token_plan,
743 # alibaba-token-plan, dashscope-token-plan
744 # Env var aliases: MODELSTUDIO_API_KEY (preferred), DASHSCOPE_API_KEY,
745 # MODELSTUDIO_TOKEN_PLAN_BASE_URL, MODELSTUDIO_TOKEN_PLAN_MODEL
746 [providers.modelstudio_token_plan]
747 # api_key = "YOUR_MODELSTUDIO_API_KEY"
748 # base_url = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1"
749 # model = "qwen3.8-max"
750 # # Anthropic-compatible dialect (same key, /apps/anthropic path):
751 # # provider = "modelstudio-token-plan-anthropic"
752 # # base_url = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic"
753
754 # ─────────────────────────────────────────────────────────────────────────────────
755 # Alibaba Cloud Model Studio — Coding Plan
756 # (https://bailian.console.aliyun.com/)
757 #
758 # Coding Plan uses a separate international endpoint (coding-intl.dashscope).
759 # Same model catalog as the Token Plan.
760 #
761 # Provider aliases: modelstudio-coding-plan, modelstudio_coding_plan,
762 # alibaba-coding-plan, dashscope-coding-plan
763 # Env var aliases: MODELSTUDIO_API_KEY (preferred), DASHSCOPE_API_KEY,
764 # MODELSTUDIO_CODING_PLAN_BASE_URL, MODELSTUDIO_CODING_PLAN_MODEL
765 [providers.modelstudio_coding_plan]
766 # api_key = "YOUR_MODELSTUDIO_API_KEY"
767 # base_url = "https://coding-intl.dashscope.aliyuncs.com/v1"
768 # model = "qwen3.8-max"
769 # # Anthropic-compatible dialect (same key, /apps/anthropic path):
770 # # provider = "modelstudio-coding-plan-anthropic"
771 # # base_url = "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic"
772
773 # ─────────────────────────────────────────────────────────────────────────────────
774 # Together AI Provider (https://www.together.ai/)
775 # Env var aliases: TOGETHER_API_KEY, TOGETHER_BASE_URL, TOGETHER_MODEL
776 [providers.together]
777 # api_key = "YOUR_TOGETHER_API_KEY"
778 # base_url = "https://api.together.xyz/v1"
779 # model = "deepseek-ai/DeepSeek-V4-Pro" # or deepseek-ai/DeepSeek-V4-Flash
780
781 # ─────────────────────────────────────────────────────────────────────────────────
782 # Baidu Qianfan Provider (https://intl.cloud.baidu.com/product/qianfan.html)
783 # Provider aliases: qianfan, baidu-qianfan, baidu_qianfan, baidu
784 # Env var aliases: QIANFAN_API_KEY / BAIDU_QIANFAN_API_KEY,
785 # QIANFAN_BASE_URL / BAIDU_QIANFAN_BASE_URL,
786 # QIANFAN_MODEL / BAIDU_QIANFAN_MODEL
787 [providers.qianfan]
788 # api_key = "YOUR_QIANFAN_API_KEY"
789 # base_url = "https://api.baiduqianfan.ai/v1"
790 # model = "ernie-4.0-turbo-8k" # or your Qianfan service/model id
791
792 # ─────────────────────────────────────────────────────────────────────────────────
793 # OpenAI Codex (ChatGPT) Provider — EXPERIMENTAL
794 # Run `codex login`, then explicitly grant read-only access to that exact file:
795 # codewhale auth external-consent --provider openai-codex --mode read-only
796 # Codewhale never refreshes or rewrites the Codex CLI file. No API key is
797 # stored here. Talks to the OpenAI Responses API at /codex/responses.
798 # Env var aliases: OPENAI_CODEX_ACCESS_TOKEN / CODEX_ACCESS_TOKEN (token override),
799 # OPENAI_CODEX_BASE_URL / CODEX_BASE_URL, OPENAI_CODEX_MODEL / CODEX_MODEL,
800 # OPENAI_CODEX_ACCOUNT_ID / CODEX_ACCOUNT_ID, OPENAI_CODEX_AUTH_FILE, CODEX_HOME
801 [providers.openai_codex]
802 # base_url = "https://chatgpt.com/backend-api"
803 # model = "gpt-5.5"
804 # The CLI writes this table after informed consent; do not copy it between
805 # providers or machines. Absence means disabled.
806 # [providers.openai_codex.external_credentials]
807 # access = "read_only"
808 # provider = "openai-codex"
809 # source = "codex_cli"
810 # path = "/absolute/path/to/.codex/auth.json"
811 # consent_version = 1
812
813 # ─────────────────────────────────────────────────────────────────────────────────
814 # Anthropic Provider (native Messages API)
815 # Talks to https://api.anthropic.com/v1/messages with x-api-key auth — not an
816 # OpenAI-compatible route. Models: claude-opus-4-8, claude-sonnet-4-6 (default),
817 # claude-haiku-4-5. Env vars: ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL,
818 # ANTHROPIC_MODEL.
819 [providers.anthropic]
820 # api_key = "sk-ant-..."
821 # base_url = "https://api.anthropic.com"
822 # model = "claude-sonnet-4-6"
823
824 # OpenModel Provider (Anthropic-compatible Messages API)
825 # Talks to https://api.openmodel.ai/v1/messages with Bearer auth. OpenModel
826 # routes DeepSeek, DashScope, Xiaomi, Claude, and other models by model id.
827 # Env vars: OPENMODEL_API_KEY, OPENMODEL_BASE_URL, OPENMODEL_MODEL.
828 [providers.openmodel]
829 # api_key = "om-..."
830 # base_url = "https://api.openmodel.ai"
831 # model = "deepseek-v4-flash"
832
833 # ─────────────────────────────────────────────────────────────────────────────────
834 # Web Search Provider
835 # ─────────────────────────────────────────────────────────────────────────────────
836 # Choose which backend `web_search` uses. Default is DuckDuckGo HTML scraping
837 # with Bing fallback — no API key needed. Bing remains selectable for users who
838 # explicitly prefer it. Switch to Tavily, Bocha, Metaso, Baidu, Volcengine,
839 # Sofya, or a trusted SearXNG instance for API-backed search.
840 # API runtime failures and empty responses visibly degrade through DuckDuckGo
841 # then Bing. Missing configuration and network-policy denials fail closed.
842 #
843 # [search]
844 # provider = "duckduckgo" # duckduckgo | bing | tavily | bocha | metaso | searxng | baidu | volcengine | sofya
845 # # duckduckgo: HTML scrape with Bing fallback
846 # # bing: HTML scrape, no API key
847 # # tavily: https://tavily.com — AI search, needs api_key
848 # # bocha: https://bochaai.com — 博查AI搜索,国内友好,需api_key
849 # # metaso: https://metaso.cn — 秘塔AI搜索,需 api_key
850 # # 设置 METASO_API_KEY 或 [search] api_key
851 # # searxng: https://docs.searxng.org — trusted/self-hosted JSON API,
852 # # set base_url; no public instance is used by default
853 # # baidu: 百度 AI Search via qianfan.baidubce.com,需 api_key
854 # # volcengine: 火山引擎 Ark web_search (免费 2 万次/月), 需 api_key
855 # # 也回退到 VOLCENGINE_API_KEY / VOLCENGINE_ARK_API_KEY / ARK_API_KEY 环境变量
856 # # sofya: https://sofya.co — AI search returning full page
857 # # content (not snippets), needs api_key (ay_live_...);
858 # # also falls back to the SOFYA_API_KEY env var
859 # base_url = "https://search.example/" # optional DuckDuckGo-compatible HTML endpoint;
860 # # required SearXNG root or /search endpoint
861 # api_key = "YOUR_SEARCH_KEY" # required for tavily, bocha, metaso, baidu, volcengine, and sofya; unused by searxng
862 # # WARNING: treat config.toml like a secret file when
863 # # storing API keys. Prefer env vars for local smoke tests.
864 #
865 # Env-var overrides:
866 # DEEPSEEK_SEARCH_PROVIDER → search.provider
867 # DEEPSEEK_SEARCH_API_KEY → search.api_key
868 # CODEWHALE_SEARCH_BASE_URL → search.base_url
869 # DEEPSEEK_SEARCH_BASE_URL → search.base_url (legacy alias)
870 # METASO_API_KEY → metaso key fallback
871 # BAIDU_SEARCH_API_KEY → baidu key fallback
872 # VOLCENGINE_API_KEY / VOLCENGINE_ARK_API_KEY / ARK_API_KEY → volcengine key fallback
873 # SOFYA_API_KEY → sofya key fallback
874
875 # ─────────────────────────────────────────────────────────────────────────────────
876 # Network Policy (#135)
877 # ─────────────────────────────────────────────────────────────────────────────────
878 # Per-domain allow/deny rules for outbound network calls made by the TUI's
879 # tools (`fetch_url`, `web_search`) and the MCP HTTP transport. Stdio MCP
880 # servers and direct LLM API calls are unaffected.
881 #
882 # Precedence: deny wins. A host listed in both `allow` and `deny` is denied.
883 #
884 # Host-matching rules:
885 # - Exact match: `api.deepseek.com` matches only `api.deepseek.com`.
886 # - Subdomain wildcard: an entry starting with `.` (e.g. `.example.com`)
887 # matches `api.example.com` and `a.b.example.com` but not the apex
888 # `example.com`. To cover both, list both. `*.example.com` is also accepted.
889 #
890 # Defaults are intentionally conservative: when this section is absent, no
891 # policy is enforced (mirrors pre-v0.7.0 behavior). To opt in:
892 #
893 # [network]
894 # default = "prompt" # allow | deny | prompt
895 # allow = ["api.deepseek.com", "github.com", ".githubusercontent.com"]
896 # deny = []
897 # proxy = ["github.com", ".githubusercontent.com"]
898 # proxy_fake_ip_cidrs = ["198.18.0.0/15"] # requires both matching host and address
899 # audit = true # one line per call to ~/.codewhale/audit.log
900
901 # ─────────────────────────────────────────────────────────────────────────────────
902 # Verifier preview (#2093)
903 # ─────────────────────────────────────────────────────────────────────────────────
904 # Enables automatic claim-of-done verifier preview once the runtime trigger is
905 # active. Manual `run_verifiers` remains available even when this is false.
906 # The shipped policy maps pass/partial/fail to hunted/wounded/escaped.
907 #
908 # [verifier]
909 # enabled = false
910 # verdict_policy = "hunt"
911
912 # ─────────────────────────────────────────────────────────────────────────────────
913 # Advisor / Watcher (#3982)
914 # ─────────────────────────────────────────────────────────────────────────────────
915 # Optional background watcher that fires after each turn that contains tool
916 # calls. It reads a bounded slice of recent tool interactions, makes a concise
917 # LLM advisory call, and emits a brief note into the status area.
918 #
919 # Off by default. Enable with `[advisor] enabled = true` or toggle per-session
920 # with `/advisor on` / `/advisor off`.
921 #
922 # Options (the full set — `AdvisorConfigToml` in crates/config/src/lib.rs):
923 # enabled — master switch (default: false)
924 # max_tool_calls — number of recent tool call/result pairs to read
925 # (default: 10, clamped to 1–50)
926 # rate_limit_secs — minimum seconds between successive advisor notes
927 # (default: 60, clamped to 5–3600)
928 # dedup_window_secs — notes whose content hash matches the previous note
929 # within this window are dropped (default: 300)
930 # model — model override for the advisory call; when absent the
931 # advisor reuses the session's current model
932 #
933 # Unknown keys under [advisor] are silently ignored, not rejected — a typo
934 # leaves the default in place with no error.
935 #
936 # [advisor]
937 # enabled = false
938 # max_tool_calls = 10
939 # rate_limit_secs = 60
940 # dedup_window_secs = 300
941 # model = "deepseek-v4-flash"
942
943 # ─────────────────────────────────────────────────────────────────────────────────
944 # Skills (#140)
945 # ─────────────────────────────────────────────────────────────────────────────────
946 # Settings for the `/skill install <spec>` community-skill installer.
947 # * registry_url — curated index.json that resolves bare names to
948 # `github:owner/repo` specs. Override to point at
949 # a private fork or internal mirror.
950 # * max_install_size_bytes — per-skill uncompressed size cap. Tarballs that
951 # exceed this limit are rejected during validation.
952 # Default: 5 MiB.
953 #
954 # `/skill install` is gated by `[network]`. Make sure `github.com` and
955 # `raw.githubusercontent.com` are reachable (default `prompt` is fine — you'll
956 # be asked once and can persist) before running it.
957 #
958 # [skills]
959 # registry_url = "https://raw.githubusercontent.com/Hmbown/deepseek-skills/main/index.json"
960 # max_install_size_bytes = 5_242_880
961 # scan_codewhale_only = false # true: ignore Claude/OpenCode/Cursor/agentskills.io skill dirs
962
963 # ─────────────────────────────────────────────────────────────────────────────────
964 # TUI
965 # ─────────────────────────────────────────────────────────────────────────────────
966 [tui]
967 alternate_screen = "auto" # auto/always use the TUI screen; never uses terminal scrollback
968 mouse_capture = true # true copies only transcript user/assistant text; false uses raw terminal selection/copy
969 terminal_probe_timeout_ms = 500 # optional startup terminal-mode timeout (100-5000ms)
970 stream_chunk_timeout_secs = 900 # optional SSE idle timeout per chunk (0 = default, 1-3600)
971 osc8_links = true # emit OSC 8 escapes around URLs (Cmd+click in iTerm2/Ghostty/Kitty/WezTerm/Terminal.app 13+); set false for terminals that misrender
972 # Ordered footer chips shown in the TUI status line. Omit the key to use the
973 # built-in default; set [] to hide all configurable chips. You can also edit
974 # this interactively with `/statusline`.
975 # Supported keys: mode, model, cost, balance (DeepSeek / DeepSeekCN only),
976 # status, agents,
977 # reasoning_replay, prefix_stability, cache, context_percent, git_branch,
978 # last_tool_elapsed (reserved), rate_limit (reserved), tokens.
979 # status_items = ["mode", "model", "status", "git_branch", "tokens", "cache"]
980 # notification_condition = "always" # always | never — overrides [notifications].threshold_secs.
981 # "always" = notify on every successful turn (no threshold);
982 # "never" = suppress all turn-completion notifications;
983 # unset = use [notifications] defaults (recommended).
984 # locale = "auto" # UI chrome language: auto | en | ja | zh-Hans | zh-Hant | pt-BR | es-419
985 # # | vi | ko | ca | de | fr | id | hi | ru | uk
986 # # "auto" reads LC_ALL → LC_MESSAGES → LANG; falls back to English.
987 # # Override: `locale = "zh-Hans"` for Simplified Chinese regardless of OS locale.
988 # # Also settable at runtime: /config locale zh-Hans
989 # # Note: this only affects TUI labels/chrome — it does NOT change model output language.
990 # mention_menu_behavior = "fuzzy" # fuzzy | browser; browser lists immediate directory children for @-mentions.
991
992 # ─────────────────────────────────────────────────────────────────────────────────
993 # Feature Flags
994 # ─────────────────────────────────────────────────────────────────────────────────
995 [features]
996 shell_tool = true
997 subagents = true
998 web_search = true # enables canonical web.run plus the compatibility web_search alias
999 apply_patch = true
1000 mcp = true
1001 exec_policy = true
1002 # vision_model = false # enable vision model for image_analyze tool
1003 # verify_tool = false # disable the agent-callable `verify` self-critique tool
1004 # (#4196). On by default; the agent decides when to spend
1005 # the extra reasoning, so cost is only incurred on demand.
1006 # Set false to remove it from the model's tool catalog.
1007
1008 # ─────────────────────────────────────────────────────────────────────────────────
1009 # Vision Model Configuration (optional)
1010 # ─────────────────────────────────────────────────────────────────────────────────
1011 # Uses an OpenAI-compatible vision model API for the `image_analyze` tool.
1012 # api_key inherits from the main config if not specified.
1013 #
1014 # [vision_model]
1015 # model = "gemini-3.1-flash-lite-preview" # Required: vision-capable model ID
1016 # api_key = "YOUR_API_KEY" # Optional: defaults to main api_key
1017 # base_url = "https://generativelanguage.googleapis.com/v1beta/openai/" # Optional
1018 #
1019 # Xiaomi MiMo image understanding can be configured through the same tool:
1020 # model = "mimo-v2.5"
1021 # api_key = "YOUR_XIAOMI_KEY"
1022 # base_url = "https://token-plan-sgp.xiaomimimo.com/v1" # Token Plan / tp- keys
1023
1024 # ─────────────────────────────────────────────────────────────────────────────────
1025 # Retry Configuration
1026 # ─────────────────────────────────────────────────────────────────────────────────
1027 [retry]
1028 enabled = true
1029 max_retries = 3
1030 initial_delay = 1.0
1031 max_delay = 60.0
1032 exponential_base = 2.0
1033
1034 # ─────────────────────────────────────────────────────────────────────────────────
1035 # Context Compaction
1036 # ─────────────────────────────────────────────────────────────────────────────────
1037 # Auto-compaction is a saved UI setting edited with `/config` (`auto_compact`).
1038 # The optional saved threshold setting is `auto_compact_threshold_percent`
1039 # (default 80). There is no config-file
1040 # `[compaction]` table yet; runtime compaction budgets are chosen by the TUI
1041 # from the active model/context window.
1042
1043 # [context] supports `enabled` (default false) and `project_pack` (#4781).
1044 # The old seam-manager keys (verbatim_window_turns, l1/l2/l3_threshold,
1045 # seam_model) are parsed but ignored — seam_manager.rs no longer exists.
1046
1047 # ─────────────────────────────────────────────────────────────────────────────────
1048 # Workshop / Large-Output Routing (#548)
1049 # ─────────────────────────────────────────────────────────────────────────────────
1050 # Tool outputs exceeding `large_output_threshold_tokens` are routed through a
1051 # V4-Flash synthesis sub-agent. Only the synthesis reaches the parent context;
1052 # the raw text is stored in the workshop variable `last_tool_result` so the
1053 # parent can call `promote_to_context` later if it needs the full content.
1054 #
1055 # Per-tool overrides let high-volume tools (e.g. Bash) use tighter
1056 # thresholds without changing the global default. Name tools by the identifiers
1057 # the model actually sees — `exec_shell`, `grep_files`, and `web_search` were
1058 # retired and an override keyed to them matches nothing.
1059 #
1060 # There is no per-call `raw = true` escape. It was documented here, but
1061 # `raw = true` on a tool call does NOT bypass routing: the adaptive router
1062 # ignores it, and it is honoured only under the legacy
1063 # `CODEWHALE_CLASSIC_OUTPUT_ROUTING` switch. No tool advertises it.
1064 #
1065 # [workshop]
1066 # large_output_threshold_tokens = 4096
1067 # [workshop.per_tool_thresholds]
1068 # Bash = 2048 # shell output synthesised aggressively
1069 # Web = 8192 # web results can be large; give them more room
1070
1071 # ─────────────────────────────────────────────────────────────────────────────────
1072 # Harness Profiles (preview schema; runtime consumption follows later)
1073 # ─────────────────────────────────────────────────────────────────────────────────
1074 # Harness profiles let future Codewhale runtime slices select model-specific
1075 # prompt, context, tool, and subagent posture. v0.9 parses, validates, and can
1076 # resolve profiles for tests/status plumbing, but normal Agent and Workflow
1077 # runs do not silently promote or mutate behavior from these profiles yet.
1078 #
1079 # [[harness_profiles]]
1080 # provider_route = "deepseek"
1081 # model_pattern = "deepseek-v4.*"
1082 #
1083 # [harness_profiles.posture]
1084 # kind = "cache-heavy" # standard | cache-heavy | lean | custom
1085 # max_subagents = 10 # 0 means runtime default
1086 # prefer_codebase_search = false
1087 # compaction_strategy = "prefix-cache" # default | prefix-cache | aggressive
1088 # tool_surface = "full" # full | read-only | auto
1089 # safety_posture = "standard" # standard | strict | permissive
1090
1091 # ─────────────────────────────────────────────────────────────────────────────────
1092 # Profile Example (for multiple environments)
1093 # ─────────────────────────────────────────────────────────────────────────────────
1094 # Select a profile with `deepseek --profile <name>` or `DEEPSEEK_PROFILE=<name>`.
1095 [profiles.work]
1096 api_key = "WORK_DEEPSEEK_API_KEY"
1097 base_url = "https://api.deepseek.com/beta"
1098
1099 [profiles.dev]
1100 api_key = "DEV_DEEPSEEK_API_KEY"
1101 allow_shell = true
1102
1103 [profiles.nvidia-nim]
1104 provider = "nvidia-nim"
1105 api_key = "YOUR_NVIDIA_API_KEY"
1106 base_url = "https://integrate.api.nvidia.com/v1"
1107 default_text_model = "deepseek-ai/deepseek-v4-pro"
1108
1109 # ─────────────────────────────────────────────────────────────────────────────────
1110 # Desktop Notifications (OSC 9 / BEL on long agent-turn completion)
1111 # ─────────────────────────────────────────────────────────────────────────────────
1112 # Emits an escape sequence to the terminal when a turn **completes successfully**
1113 # and took longer than `threshold_secs`. Failed or cancelled turns are
1114 # intentionally silent. Useful when you tab away from the TUI and want an alert
1115 # for "your task is ready".
1116 #
1117 # method = "auto" # auto | osc9 | bel | off
1118 # auto: OSC 9 for iTerm.app / Ghostty / WezTerm.
1119 # On macOS / Linux, falls back to BEL.
1120 # On Windows, BEL is routed through MessageBeep(MB_OK).
1121 # osc9: \x1b]9;<msg>\x07 (iTerm2-style; shows macOS notification)
1122 # bel: plain \x07 beep
1123 # off: disable entirely
1124 # threshold_secs = 30 # only notify when the turn took >= this many seconds
1125 # include_summary = false # include elapsed time + cost in the notification body
1126 # subagent_completion = "final-only" # always | final-only | off — per-subagent
1127 # notifications during fleet/workflow runs. final-only
1128 # (default) stays quiet mid-run and fires once when the
1129 # batch drains; off silences them entirely.
1130 # completion_sound = "beep" # off | beep | bell | file — sound on turn completion (✅ marker)
1131 # sound_file = "E:\\google\\downloads\\notify.wav" # WAV used when completion_sound = "file" (Windows)
1132 [notifications]
1133 # method = "auto"
1134 # threshold_secs = 30
1135 # include_summary = false
1136 # subagent_completion = "final-only"
1137 # completion_sound = "beep"
1138 # sound_file = "E:\\google\\downloads\\notify.wav"
1139
1140 # Opt-in per-event sound cues (#4817): deterministic, terminal-bell level
1141 # (BEL bytes only — functional signals, platform-safe no-op when the terminal
1142 # ignores BEL). Off by default. When completion_sound is active, turn-complete
1143 # is left to that channel so the two never double-ding.
1144 # [notifications.event_sound]
1145 # enabled = false # master switch, default off
1146 # events = ["turn-complete", "approval-needed"] # allow-list; unknown names ignored
1147 # min_interval_ms = 2000 # per-event rate limit
1148 # quiet = false # true silences all event sounds
1149
1150 # ─────────────────────────────────────────────────────────────────────────────────
1151 # Workspace Snapshots (#137)
1152 # ─────────────────────────────────────────────────────────────────────────────────
1153 # Each turn the TUI takes a `pre-turn:<seq>` and `post-turn:<seq>` snapshot of
1154 # your workspace into a side-git repo at:
1155 #
1156 # ~/.codewhale/snapshots/<project_hash>/<worktree_hash>/.git
1157 #
1158 # Your own `.git` is never touched — `--git-dir` and `--work-tree` are always
1159 # set together when shelling out to git. Use `/restore N` (slash command) or
1160 # the `revert_turn` tool to roll the working tree back. Conversation history
1161 # is unaffected.
1162 #
1163 # Disk footprint: ~1-2 GB worst case for a 100 MB workspace × 12 turns/day,
1164 # typically far less thanks to git's content-addressed storage. The session
1165 # boot prunes anything older than `max_age_days` (default 7).
1166 #
1167 # [snapshots]
1168 # enabled = true # Snapshot workspace pre/post each turn for /restore
1169 # max_age_days = 7 # Older snapshots pruned at session start
1170 # max_workspace_gb = 2 # Snapshots self-disable on first init when the
1171 # # non-excluded workspace exceeds this size in GB
1172 # # (v0.8.32). Default 2 GB protects against running
1173 # # codewhale in directories with hundreds of GB
1174 # # of datasets / model weights / docker dumps where
1175 # # `git add -A` would hang the TUI for hours. Set
1176 # # to 0 to disable the cap (v0.8.31 behaviour);
1177 # # raise to a higher number for legitimate large
1178 # # monorepos.
1179
1180 # ─────────────────────────────────────────────────────────────────────────────────
1181 # LSP Diagnostics (post-edit) (#136)
1182 # ─────────────────────────────────────────────────────────────────────────────────
1183 # After every successful file edit (`edit_file`, `apply_patch`, `write_file`),
1184 # the engine asks an LSP server for diagnostics on the file and injects them
1185 # as a synthetic system message before the next API call. This lets the agent
1186 # see compile breaks immediately without round-tripping through the user.
1187 #
1188 # Enabled by default. Failure modes are non-blocking: a missing LSP binary,
1189 # a crashed server, or a timeout simply skips the post-edit hook for that
1190 # turn — the agent's work is never blocked.
1191 #
1192 # Built-in language → server defaults:
1193 # rust → rust-analyzer
1194 # go → gopls serve
1195 # python → pyright-langserver --stdio
1196 # typescript → typescript-language-server --stdio
1197 # java → jdtls
1198 # php → intelephense --stdio
1199 # vue → vue-language-server --stdio
1200 # c, cpp → clangd
1201 #
1202 # Java support uses Eclipse JDT LS via the `jdtls` command. IntelliJ IDEA is
1203 # not required, and installing IntelliJ IDEA alone does not install `jdtls`.
1204 #
1205 # Override the defaults via the `servers` table below.
1206 #
1207 # For languages not in the built-in list (Ruby, C#, Swift, etc.), use
1208 # `[lsp.custom.<ext>]` to register a language server:
1209 #
1210 # [lsp.custom.rb]
1211 # command = "ruby-lsp"
1212 # args = ["--stdio"]
1213 # language_id = "ruby"
1214 #
1215 # [lsp.custom.cs]
1216 # command = "csharp-ls"
1217 # args = []
1218 # language_id = "csharp"
1219 #
1220 # [lsp.custom.swift]
1221 # command = "sourcekit-lsp"
1222 # language_id = "swift"
1223 [lsp]
1224 # enabled = true
1225 # poll_after_edit_ms = 5000
1226 # max_diagnostics_per_file = 20
1227 # include_warnings = false
1228 # [lsp.servers]
1229 # rust = ["rust-analyzer"]
1230 # go = ["gopls", "serve"]
1231 # java = ["jdtls"]
1232 # php = ["intelephense", "--stdio"]
1233 # vue = ["vue-language-server", "--stdio"]
1234
1235 # ─────────────────────────────────────────────────────────────────────────────────
1236 # Hooks (optional)
1237 # ─────────────────────────────────────────────────────────────────────────────────
1238 # Hooks run shell commands on lifecycle events (session start/end, tool calls, etc.).
1239 # Configure as `[[hooks.hooks]]` under a `[hooks]` table.
1240 #
1241 # SCOPE: hooks are a TUI runtime feature. They fire from the interactive TUI
1242 # and the engine turn loop it drives. `codewhale exec`, the CLI subcommands,
1243 # the app-server / ACP surfaces, and the `workflow` tool do NOT fire them.
1244 #
1245 # Available events (all 11): session_start, session_end, message_submit,
1246 # tool_call_before, tool_call_after, mode_change, on_error, turn_end,
1247 # subagent_spawn, subagent_complete, shell_env.
1248 # See docs/HOOKS.md for the per-event payload, env var, and steering contract.
1249 #
1250 # `message_submit`, `tool_call_before`, and `shell_env` are the only events
1251 # whose result can change what Codewhale does. The rest are observer-only —
1252 # which means their RESULT is ignored, not that the command is harmless. Every
1253 # hook is an arbitrary shell command running with your credentials.
1254 #
1255 # Note: `default_timeout_secs` below OVERRIDES each hook's own `timeout_secs`.
1256 # Leave it unset if you want per-hook timeouts to apply. `/hooks list` shows the
1257 # effective value and names the override. `default_timeout_secs = 0` is
1258 # REJECTED at load — it would expire every hook immediately — and per-hook
1259 # `timeout_secs` applies instead. The timeout applies to background hooks too:
1260 # on expiry the whole process group is killed and then reaped, best-effort,
1261 # with a bounded reap wait (see docs/HOOKS.md → Timeouts).
1262 #
1263 # `background = true` means submitted and never awaited. The hook still gets
1264 # the documented stdin payload, environment, and timeout — it just has no exit
1265 # code, so it cannot steer. `shell_env` ignores the flag and always runs in the
1266 # foreground because its stdout is the contract.
1267 #
1268 # A condition that references context its event never carries is REJECTED at
1269 # load, logged, and shown by `/hooks list` — for example an `exit_code`
1270 # condition outside `tool_call_after` / `on_error`, or a `mode` condition on
1271 # `shell_env`. Rejection is per entry: a broken hook never drops another one
1272 # that happens to share its name. `on_error` fires for tool failures with the
1273 # tool name, call id, and reported exit code attached, so tool-scoped and
1274 # exit-code-scoped `on_error` hooks are supported.
1275 #
1276 # `shell_env` (#456) is special: the hook runs immediately before each
1277 # `exec_shell` invocation and its stdout is parsed as `KEY=VALUE\n` lines.
1278 # Those vars are applied on top of `exec_shell`'s environment. For LOCAL
1279 # execution that environment is built from a fixed allowlist of parent
1280 # variables (PATH, HOME, LANG, TERM, …) — an ambient secret exported in your
1281 # terminal is NOT forwarded to `exec_shell` by itself, so this hook is the
1282 # supported way to supply one. If an external sandbox backend is configured,
1283 # that allowlist does NOT apply: the backend owns its base environment and your
1284 # `shell_env` values are TRANSMITTED to it. Later hooks override
1285 # earlier ones. Use this for ephemeral credentials, per-skill PATH adjustments,
1286 # or short-lived tokens. The resolved KEY names (NEVER values) are written to
1287 # `~/.codewhale/audit.log` so each session can be reconciled later. Hook
1288 # failure / timeout simply contributes no vars — it does not abort the shell
1289 # call.
1290 #
1291 # [hooks]
1292 # enabled = true
1293 # default_timeout_secs = 30
1294 #
1295 # [[hooks.hooks]]
1296 # event = "session_start"
1297 # command = "echo 'Codewhale session started'"
1298 #
1299 # # Inject ephemeral creds into every shell call. Output one
1300 # # KEY=VALUE per line on stdout (export prefix optional).
1301 # [[hooks.hooks]]
1302 # name = "aws-creds"
1303 # event = "shell_env"
1304 # command = "aws-vault export my-profile --format=env"
1305 # # Optionally limit to specific tool names / categories:
1306 # # condition = { type = "tool_category", category = "shell" }
1307 #
1308 # # Observe sub-agent lifecycle events. These hooks receive bounded JSON
1309 # # metadata on stdin and are warn-only: failures do not affect sub-agent
1310 # # scheduling, prompts, or results. continue_on_error has no effect for
1311 # # these observer events; later matching hooks always continue.
1312 # [[hooks.hooks]]
1313 # name = "subagent-audit"
1314 # event = "subagent_complete"
1315 # command = "~/.codewhale/hooks/subagent-audit.sh"
1316
1317 # ─────────────────────────────────────────────────────────────────────────────────
1318 # Runtime API (`deepseek serve --http`) (#561)
1319 # ─────────────────────────────────────────────────────────────────────────────────
1320 # Tuning knobs for the local HTTP/SSE daemon. The server binds to 127.0.0.1
1321 # by default and is intended for local UIs (whalescale-desktop, dashboards,
1322 # automation scripts). Today this section only controls the CORS allow-list;
1323 # host/port/workers stay on `--host`, `--port`, and `--workers` flags.
1324 #
1325 # Built-in defaults always include:
1326 # http://localhost:3000 http://127.0.0.1:3000
1327 # http://localhost:1420 http://127.0.0.1:1420
1328 # tauri://localhost
1329 #
1330 # Use `cors_origins` to add extra dev origins (e.g. Vite's default `:5173`).
1331 # User entries STACK on top of the defaults — they do not replace them. The
1332 # CLI flag `--cors-origin URL` (repeatable) and env var
1333 # `DEEPSEEK_CORS_ORIGINS=url1,url2` resolve to the same merged list.
1334 #
1335 # [runtime_api]
1336 # cors_origins = ["http://localhost:5173", "http://127.0.0.1:5173"]
1337
1338 # ─────────────────────────────────────────────────────────────────────────────────
1339 # Tool Overrides & Plugins ([tools])
1340 # ─────────────────────────────────────────────────────────────────────────────────
1341 # The `[tools]` table lets you replace any built-in tool with a custom
1342 # implementation (script or command) or disable it entirely — without
1343 # forking or recompiling the binary.
1344 #
1345 # Plugin scripts dropped in the plugin directory are auto-discovered and
1346 # registered as model-visible tools alongside the built-in ones.
1347 #
1348 # Scripts receive the tool's JSON input on **stdin** and must return a
1349 # JSON `ToolResult` (`{"content": "...", "success": true}`) on **stdout**.
1350 #
1351 # [tools]
1352 # # Custom plugin directory (defaults to `~/.codewhale/tools/`)
1353 # plugin_dir = "~/.codewhale/tools"
1354 #
1355 # [tools.overrides]
1356 # # Disable a tool entirely — removes it from the model-visible catalog.
1357 # "code_execution" = { type = "disabled" }
1358 #
1359 # # Replace a tool with a script. Relative paths resolve against plugin_dir.
1360 # "exec_shell" = { type = "script", path = "audit-exec-shell.sh" }
1361 #
1362 # # Replace a tool with a command (binary on PATH or absolute path).
1363 # "read_file" = { type = "command", command = "bat", args = ["--paging=never"] }
1364 #
1365 # # Scripts can also accept static arguments before the JSON input:
1366 # "fetch_url" = { type = "script", path = "cached-fetch.sh", args = ["--ttl", "300"] }
1367
1368 # ──────────── Enterprise example: audit-logging exec_shell wrapper ──────────────
1369 # Drop `audit-exec-shell.sh` in `~/.codewhale/tools/` and enable with:
1370 #
1371 # [tools.overrides]
1372 # "exec_shell" = { type = "script", path = "audit-exec-shell.sh" }
1373 #
1374 # The wrapper logs every request to `~/.codewhale/audit/exec_shell.log`, then
1375 # delegates to your own approved shell executor. Do not pipe the raw JSON
1376 # request into `sh -s`; parse the command field and enforce your policy first.
1377 #
1378 # ```sh
1379 # #!/usr/bin/env sh
1380 # # name: exec_shell
1381 # # description: Audit-logging wrapper for exec_shell
1382 # # approval: required
1383 # LOGDIR="${HOME}/.codewhale/audit"
1384 # mkdir -p "$LOGDIR"
1385 # LOGFILE="$LOGDIR/exec_shell.log"
1386 # input=$(cat)
1387 # echo "[$(date -Iseconds)] $input" >> "$LOGFILE"
1388 # printf '%s\n' '{"content":"audit wrapper dry run: configure an executor","success":false}'
1389 # ```
1390
1391 # ─────────────────────────────────────────────────────────────────────────────────
1392 # Workflow automatic launch, approval, isolation, and activity (#4128)
1393 # ─────────────────────────────────────────────────────────────────────────────────
1394 # First-class knobs for automatic Workflow orchestration. When the table is
1395 # omitted entirely, the runtime uses these product defaults. Later launch,
1396 # approval, and activity-persistence paths all read through this one model.
1397 # [workflow]
1398 # # Allow the parent agent to auto-launch Workflow for multi-agent work.
1399 # # Set false to require an explicit `/workflow` opt-in.
1400 # automatic = true
1401 # # Auto-start read-only plans without an approval card when automatic is on.
1402 # auto_start_read_only = true
1403 # # Require an approval card before write/shell/network/high-budget launches.
1404 # require_approval_for_writes = true
1405 # # Soft cap on children admitted by automatic launch (larger plans ask first).
1406 # auto_start_child_limit = 16
1407 # # Hard ceiling on agents in one Workflow run (matches VM lifetime cap).
1408 # max_children = 1000
1409 # # Maximum concurrently live agents inside one run (others wait for a slot).
1410 # max_concurrent = 16
1411 # # Maximum nested Workflow / child-orchestration depth.
1412 # max_depth = 2
1413 # # Default shared token budget for a Workflow run and its children.
1414 # default_token_budget = 120000
1415 # # Parallel write children that may share the parent worktree without
1416 # # isolation. 0 forces worktree isolation for parallel writes.
1417 # max_parallel_writes_without_worktree = 0
1418 # # Keep completed Workflow activity visible until the next run / clear.
1419 # persist_completed_activity = true
1420 # # Persist completed activity across process restarts via the run journal.
1421 # persist_completed_across_restarts = true
1422
1423 # ─────────────────────────────────────────────────────────────────────────────────
1424 # Agent Fleet trust, security, and role registry (#3165, #3167)
1425 # ─────────────────────────────────────────────────────────────────────────────────
1426 # [fleet]
1427 # # Default trust level for fleet workers: "sandbox" | "local" | "remote-verified" | "operator"
1428 # default_trust_level = "sandbox"
1429 # # Require SSH host-key verification before granting remote-verified trust
1430 # require_identity_verification = true
1431 # # Maximum trust level any worker may have
1432 # max_trust_level = "operator"
1433 #
1434 # # Headless worker execution hardening (#3027)
1435 # [fleet.exec]
1436 # # Tools always allowed regardless of role
1437 # allowed_tools = []
1438 # # Tools always disallowed (overrides role and task spec)
1439 # disallowed_tools = ["exec_shell"]
1440 # # Hard ceiling on worker steps (tool calls + model turns)
1441 # max_turns = 500
1442 # # Recursive child-agent depth for fleet workers. Shares ONE recursion axis
1443 # # with standalone sub-agents (a fleet worker IS a headless sub-agent).
1444 # # 0 blocks child agents (the root worker still runs); 3 is the default and the
1445 # # cap, affording at least three nested delegation levels.
1446 # max_spawn_depth = 3
1447 # # Extra system prompt injected into every headless worker
1448 # append_system_prompt = "Never modify .git/config or change remotes."
1449 # # Output format: "text" (default) or "stream-json" for ndjson events
1450 # output_format = "text"
1451 #
1452 # # Fleet profiles define named agent configurations the roster can dispatch.
1453 # # Built-in profiles are always available: manager, operator, scout, builder,
1454 # # reviewer, verifier, synthesizer, general. User-defined profiles under
1455 # # [fleet.profiles] override or extend the built-in set by id. Precedence
1456 # # is Workspace (.codewhale/agents/*.toml) > Config ([fleet.profiles]) > BuiltIn.
1457 # # See /fleet setup for an in-app profile-authoring wizard.
1458 # [fleet.profiles.ci-linter]
1459 # slot = "verifier"
1460 # loadout = "fast"
1461 # model = "deepseek-v4-pro"
1462 #
1463 # [fleet.profiles.ci-linter.role]
1464 # name = "CI Linter"
1465 # description = "Runs linters and formatters"
1466 # instructions = "Run cargo fmt --check and cargo clippy; never apply fixes."
1467 #
1468 # [fleet.profiles.ci-linter.permissions]
1469 # allow_shell = true # the only three keys are allow_shell, trust,
1470 # trust = false # and approval_required (FleetProfilePermissions
1471 # approval_required = true # in crates/config/src/lib.rs)
1472 #
1473 # [fleet.profiles.pr-reviewer]
1474 # slot = "reviewer"
1475 # loadout = "inherit"
1476 #
1477 # [fleet.profiles.pr-reviewer.role]
1478 # name = "PR Reviewer"
1479 # description = "Reviews PRs with GitHub access"
1480 # instructions = "Review diffs for correctness, regressions, and missing tests."
1481
1482 # ─────────────────────────────────────────────────────────────────────────────────
1483 # Named operator-scoped Fleet configurations (#5039)
1484 #
1485 # Multiple named Fleets may coexist alongside the default [fleet] table.
1486 # Each [fleets.<name>] entry must include an `operator` field and may configure
1487 # its own trust levels, roles, profiles, and exec policy independently.
1488 #
1489 # Selection precedence (most specific wins):
1490 # 1. Explicit fleet name — config.resolve_fleet("name")
1491 # 2. Operator lookup — config.resolve_fleet_for_operator("alice")
1492 # 3. Global default — config.fleet (the [fleet] table)
1493 #
1494 # Unknown fleet/operator references fail with an actionable error listing
1495 # what IS configured, rather than silently falling back.
1496 # ─────────────────────────────────────────────────────────────────────────────────
1497 #
1498 # [fleets.alice-team]
1499 # # Required: the operator/leader identity for this fleet.
1500 # operator = "alice"
1501 # # These fields are identical to [fleet] and use the same defaults.
1502 # default_trust_level = "local"
1503 # require_identity_verification = true
1504 # max_trust_level = "operator"
1505 #
1506 # [fleets.alice-team.exec]
1507 # max_turns = 200
1508 # max_spawn_depth = 2
1509 # append_system_prompt = "Always ask before modifying configuration files."
1510 #
1511 # [fleets.alice-team.profiles.ci-linter]
1512 # slot = "verifier"
1513 # loadout = "fast"
1514 # model = "deepseek-v4-pro"
1515 #
1516 # [fleets.alice-team.profiles.ci-linter.role]
1517 # name = "CI Linter"
1518 # description = "Runs linters and formatters for alice-team"
1519 # instructions = "Run cargo fmt --check and cargo clippy; never apply fixes."
1520 #
1521 # [fleets.bob-team]
1522 # operator = "bob"
1523 # default_trust_level = "sandbox"
1524 #
1525 # [fleets.bob-team.profiles.implementer]
1526 # slot = "implementer"
1527 # loadout = "inherit"
1528
1529 # ─────────────────────────────────────────────────────────────────────────────────
1530 # Requirements (admin constraints) example file
1531 # ─────────────────────────────────────────────────────────────────────────────────
1532 # allowed_approval_policies = ["on-request", "untrusted", "never"]
1533 # allowed_sandbox_modes = ["read-only", "workspace-write"]
1534
1534 lines TOML