mermaid-cli 0.30.0

Open-source AI pair programmer with agentic capabilities. Local-first with Ollama, native tool calling, and beautiful TUI.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# Configuration

Config file: `~/.config/mermaid/config.toml` (Linux) or the platform equivalent
via the `directories` crate. Run `mermaid init` to create one.

Two environment variables relocate Mermaid's own directories wholesale, on
every platform: `MERMAID_CONFIG_DIR` (the directory holding `config.toml`) and
`MERMAID_DATA_DIR` (the runtime store: checkpoints, process records, the
daemon socket). They exist for sandboxed test runs, CI, and portable installs
— on Windows the platform locations are known folders that `HOME`/
`XDG_CONFIG_HOME` cannot move, so these are the only reliable override. An
empty value means unset.

## Layers

Configuration is assembled from layers, later layers winning key-by-key through
one recursive TOML deep-merge (tables merge; scalars and arrays replace):

1. built-in defaults
2. the user config file above
3. the project config: `<git-root>/.mermaid/config.toml`, located from the working directory
   (`-p /repo` adopts that repo's project config)
4. session flags: repeatable `-c key.path=value` plus the dedicated flags (`--no-network`,
   `--confine-fs`, `--sandbox`, `run --max-tokens`, `run --allow-untrusted-tools`), with a
   dedicated flag beating a contradictory `-c`

Unknown-key warnings name the layer they came from. A key that an older Mermaid read
(for example `[plan]`, removed in 0.28.0, or `[computer_use]`, removed in 0.26.0) is not
a spelling error, so its warning names the release that removed it instead.
`mermaid clean-config` deletes those keys from the user file, including inside
`[profiles.*]`: it copies the old file to `config.toml.bak` first (or `.bak.2`, … when a
backup exists), keeps comments, key order and every other key, and never touches a key
it does not know. When bare `mermaid` starts at a terminal and finds such keys, it lists
them and asks `[y/N]` before doing the same; headless runs and scripts only warn.

In-app settings changes (`/model`,
Alt+T, `/context`, `mermaid add`) rewrite only their own keys in the user file — unrecognized
keys in the file survive, and defaults are never frozen in.

## Project config

A repo can commit shared defaults in `.mermaid/config.toml` — model choice, profiles,
per-model reasoning, and UX knobs. Loading needs no trust ceremony because safety is
structural:

- Only these top-level sections are honored: `default_model`, `model_aliases`,
  `reasoning_per_model`, `ollama`, `ollama_num_ctx_per_model`, `compaction`,
  `memory`, `output`, `ui`, and a `safety` subset. Anything else —
  including `web` (selects egress routing), `mcp_servers` (spawns commands), `providers`
  (redirects traffic/credentials), `workspace` (adds working directories), `agents`, and
  `daemon` — is ignored with a warning.
  `ollama.host`/`port` are also denied inside the otherwise-allowed `ollama` table.
- `safety.mode`, `safety.network`, and `safety.filesystem` are clamped tighten-only against
  your user config: a project can turn the sandbox on or drop to `read_only`, but can never
  loosen what you configured. Session flags (you, at the keyboard) still override everything.
- Startup prints `mermaid: using project config <path> (<n> keys)` whenever the layer
  contributes.

## Schema

Important fields in the current config schema:

```toml
# Last model picked via `--model` — used by bare `mermaid` on next start
last_used_model = "ollama/qwen3-coder:30b"

[default_model]
provider = "ollama"
name = "qwen3-coder:30b"
temperature = 0.7
max_tokens = 0        # 0 = auto (model-scaled output budget); positive = hard cap
reasoning = "medium"  # none | minimal | low | medium | high | xhigh | max

[ollama]
host = "localhost"
port = 11434
# Start `ollama serve` automatically when the local server isn't running
# (loopback hosts only; the revived server binds to exactly this host:port).
# Only paths that USE Ollama start it (chat, the startup model check); the
# read-only verbs (`mermaid list` / `models` / `status` / `doctor`) observe
# and never start anything — a stopped server still lists its installed
# models, read from Ollama's on-disk store (honoring OLLAMA_MODELS), and
# labeled "starts automatically on use". Disable autostart here — or with
# MERMAID_OLLAMA_AUTOSTART=0 in the environment — if you manage Ollama
# yourself (custom bind address, containers, CI).
auto_start = true
# Ollama Cloud authenticates with the OLLAMA_API_KEY environment variable
# (`mermaid cloud-setup` stores it); there is no config key for it.
# num_gpu = 10
# num_thread = 8
# num_ctx = 8192
# numa = false

[safety]
# Approval policy. Default is "auto": an LLM classifier vets each borderline
# action against your stated intent — aligned actions run automatically, risky
# ones escalate to an approval prompt. "ask" prompts before every mutation /
# shell / network action. "full_access" auto-runs everything local;
# write-shaped MCP tools (no read-only annotation) are still vetted against
# your intent per `external_writes` below. "read_only" blocks mutations and
# (on macOS and Linux 6.12+, where the OS sandbox enforces it) lets any shell
# command run with writes, sockets and signals denied by the kernel; see
# docs/sandbox.md. It
# requires one-shot approval for each web request because URLs and queries are
# externally observable. Set allow_readonly_web = true only when unattended
# web egress in read_only sessions is intentional; project config cannot set it.
# Change it live with Shift+Tab or `/safety <mode>` (session-scoped; this
# value is the persistent default each session starts from).
mode = "auto"
checkpoint_on_mutation = true
# network = "allow"       # "deny" is a global shell + web egress kill-switch
# allow_readonly_web = false
# Enforcement floor for write-shaped MCP tools (send / deploy / delete-remote
# — anything without a server-advertised readOnlyHint): safety mode alone
# never authorizes an external side effect. "allow" restores unconditional
# runs, "auto" (default) vets against your intent, "ask" always prompts,
# "deny" blocks.
# external_writes = "auto"
# Same levels for machine-scoped package operations (npm -g, cargo install,
# pip install, brew/apt/winget installs): they change the machine, not the
# project, so even full_access vets them. Project-local installs
# (npm install, cargo add) are untouched.
# system_installs = "auto"
# Model the "auto" classifier uses to vet actions. Omit to vet with the
# session's active model; set a smaller/faster model to cut latency and cost.
# The classifier sees your messages in this conversation, the reply you last
# answered and any compaction summary (secrets redacted), and reasons at the
# session's level clamped to low..high. It waits 30s at low, 60s at medium and
# 120s at high; a timeout or error escalates to you, never allows.
# auto_classifier_model = "<provider>/<small-fast-model>"

[workspace]
# Extra working directories with the project directory's trust: the file
# tools and execute_command work there without the outside-the-project
# escalation, edits are checkpointed, and --confine-fs lets shell commands
# write there. `--add-dir <dir>` (repeatable) adds to this list for one run;
# `/add-dir <path>` adds one mid-session. `~` expands to your home directory
# and relative paths resolve against the project directory. Every entry is
# canonicalized at startup, and one that does not exist stops the session
# with an error. User config, profiles and -c only: a project config cannot
# set it.
# additional_dirs = ["~/src/shared-lib"]

[exec]
# Foreground commands run on a pseudo-terminal by default (openpty on Unix,
# ConPTY on Windows): the child sees a real console, so spinner/progress
# tools behave, and on Unix /dev/tty resolves to the captured pty instead of
# writing over the TUI. ANSI escapes are stripped from what the model sees;
# a child that reads stdin hangs to its timeout (use mode="background" for
# interactive daemons). Set false to use pipes.
# pty = true

[tools]
# Send a provider's own tool definitions where it has them (Anthropic's text
# editor and bash, OpenAI's apply_patch) instead of Mermaid's schemas for the
# same tools. The calls
# still run through Mermaid's file and shell tools, so every safety gate
# applies. false always sends Mermaid's.
# provider_native = true
# Give the model the `computer` tool: screenshots, mouse and keyboard on your
# real screen. Off by default: every screenshot goes to the model's provider.
# Mouse and keyboard actions are gated like other external access.
# computer = false

[goal]
# Model that checks a `/goal` after each run. Omit to check with the session's
# model; a small, fast model is enough, because the check reads the end of the
# conversation and answers in one line. User config only: a project file cannot
# set it.
# model = "<provider>/<small-fast-model>"
# Goal turns that may run without a message from you before the goal pauses.
# Each message from you starts the count again. 0 means no limit.
# max_turns = 50

[ui]
# TUI color theme: "dark" (default) or "light". Switch live with
# `/theme dark|light` (persists here); the `--resume` picker follows it too.
# Setting the NO_COLOR environment variable (any non-empty value) disables
# colors entirely, regardless of this value.
theme = "dark"

[output]
# Voice/format preset for the system prompt (`/output-style`): "default"
# (stock prompt), "proactive", "concise", "explanatory", "learning", or a
# custom output-styles/<name>.md file. See "Output styles" below.
# style = "concise"

# Prices behind the cost estimate in /usage, in US dollars per million
# tokens. A model with no entry here is priced from the public catalog at
# catalog_url (fetched at most once a day, cached in the data dir, never with
# safety.network = "deny"); local Ollama models cost $0. The project config
# cannot set this table.
[pricing]
# catalog_url = ""   # never fetch the catalog; use only the prices below
# [pricing.models."openrouter/acme/coder-1"]
# input = 0.5
# output = 2.0
# cache_read = 0.05    # optional; defaults to the input price
# cache_write = 0.625  # optional; defaults to the input price

# Durable agent memory (the `memory` tool, the always-loaded index, and
# /remember & friends). On by default.
[memory]
enabled = true
# index_cap_bytes = 8000   # byte cap on the always-loaded memory index

[compaction]
# Cap on consecutive auto compact-and-continue recoveries after a
# context-window truncation, before the run stops and shows the manual
# levers (`/context max`, `/context offload on`). 0 = uncapped.
max_truncation_recoveries = 3
# Compact automatically when the window crosses the threshold. false leaves
# compaction entirely to `/compact`.
# auto_enabled = true
# auto_threshold_percent = 85        # clamped to 1..=100
# A context size in tokens replaces the percentage (minimum 50000). One for a
# single model overrides the one for all models. `/autocompact` sets both.
# auto_threshold_tokens = 250000
# Per model, in its own [compaction.auto_threshold_tokens_per_model] table,
# with lines like "openai/gpt-5.6" = 400000.
# Let a provider that compacts server-side (Anthropic, OpenAI) do the automatic
# compaction at the same threshold, with its own summary. A model that
# refuses it falls back to Mermaid's. false always compacts client-side.
# provider_native = true
# tail_turns = 2                     # user turns kept verbatim (min 1)
# tail_token_budget = 8000           # token ceiling on that tail
# summary_max_tokens = 8000          # ceiling on the checkpoint produced
# The summarizer reads this share of a known context window (a 1M model
# summarizes from 750k tokens of history), with summarizer_input_token_budget
# as the floor and as the whole budget when the window is unknown.
# summarizer_input_window_percent = 75  # clamped to 1..=100
# summarizer_input_token_budget = 64000
# min_response_reserve_tokens = 4000 # window held back for the reply
# max_response_reserve_tokens = 20000
# Both token budgets scale DOWN automatically on a small context window, so
# they never ask for more than fits. Nonsense values are clamped, not
# rejected: 0 falls back to the default and swapped reserve bounds are ordered.

# Subagents (the `agent` tool). Built-in types: `general` (full tool access
# at your safety mode) and `explore` (read-only reconnaissance). Define more
# below; a custom name shadows a built-in, so `[agents.types.explore]`
# retunes the built-in. Callers pick a type with the tool's `type` arg,
# override the model per call with `model`, and continue a prior child with
# `agent_id` (from the `[agent_id: …]` trailer on each result).
[agents]
# Wall-clock ceiling per subagent drive, in seconds. 0 = built-in default
# (1200 = 20 minutes).
timeout_secs = 1200

# Example user-defined type. Every field is optional.
# [agents.types.scout]
# tools = ["read_file", "execute_command"]  # omit for the full child set
# safety = "read_only"    # ceiling — the child never runs looser than this
# preamble = "You are a scout: find and report, fast."
# model = "ollama/qwen3:8b"   # default model for this type; per-call `model` wins
# isolation = "worktree"  # private git checkout; per-call `isolation` wins
# description = "Fast file search; reports paths and facts."  # shown to the model
#
# Agent types can also live in files, one per type: `agents/<name>.md` under
# `.mermaid/`, `.claude/` or `.agents/` in the project, or under
# `~/.config/mermaid/`, `~/.claude/` or `~/.agents/`. See docs/plugins.md.

# Per-model reasoning preferences (remembered across sessions)
[reasoning_per_model]
# "<provider>/<model>" = "high"
"ollama/qwen3-coder:30b" = "low"

# Optional named config overlays selected per invocation with
# `--profile <name>`. A profile's values beat this file's top-level values
# but lose to a repo's project config and to `-c` overrides.
#   mermaid --profile work doctor
# [profiles.work.default_model]
# temperature = 0.2
# [profiles.work]
# last_used_model = "anthropic/<model>"

# Optional model-id aliases. A request for `--model fast` or
# `--model alias:fast` resolves through this table when present.
[model_aliases]
fast = "ollama/qwen3-coder:14b"
# large-context = "openai/<model>"
# tool-strong = "anthropic/<model>"
# vision = "gemini/<model>"
# cheap = "groq/<model>"

# Remote providers — override env-var name, base URL, or extra headers
[providers.anthropic]
# api_key_env = "MY_ANTHROPIC_KEY"  # default: ANTHROPIC_API_KEY

[providers.gemini]
# api_key_env = "MY_GOOGLE_KEY"  # default: GOOGLE_API_KEY; GEMINI_API_KEY is accepted as a legacy fallback

[providers.meta]
# api_key_env = "MY_META_KEY"  # default: MODEL_API_KEY
# base_url = "https://api.meta.ai/v1"

[providers.groq]
# api_key_env = "MY_GROQ_KEY"    # default: GROQ_API_KEY
# base_url = "https://api.groq.com/openai/v1"

[providers.grok]
# api_key_env = "MY_XAI_KEY"  # default: XAI_API_KEY
# base_url = "https://api.x.ai/v1"
# extra_headers = { "X-Custom-Header" = "value" }

# Custom OpenAI-compatible provider (e.g., self-hosted vLLM)
[providers.my-vllm]
base_url = "http://192.168.1.42:8000/v1"
api_key_env = "VLLM_KEY"
compat = "openai-effort"   # openai | openai-effort | openrouter
# default_model = "Qwen/Qwen2.5-Coder-32B-Instruct"

# MCP servers — usually managed via `mermaid add <name>`
[mcp_servers.context7]
command = "npx"
args = ["-y", "@upstash/context7-mcp"]
```

## System prompt

System prompt customization is runtime-only and is not saved to config:

```bash
mermaid --append-system-prompt "Prefer minimal diffs"
mermaid --append-system-prompt-file ./extra-instructions.md
mermaid --system-prompt "You are a focused code reviewer."
mermaid --system-prompt-file ./replacement-system-prompt.md
```

## Guidance pack

The system prompt has two layers. The core states facts and boundaries: the
tools, the OS and shell, what each safety mode gates, where memory and the
scratchpad live. The guidance pack adds coaching on how to work: planning with
the task checklist, reading a codebase, maintaining memory, and the editing,
validation and output contracts. Capable hosted models don't need it; smaller
local models usually do.

```toml
[output]
guidance = "auto"  # default: on for local providers, off for hosted APIs
# guidance = "on"  # always
# guidance = "off" # never
```

`auto` decides by provider, not by model name: Ollama is local, and so is any
provider whose `[providers.<name>] base_url` points at a loopback or LAN host.
Subagents follow the same setting. The same switch covers the checklist
reminders outside the prompt: the note when more than one task is in progress
and the reminder when a task has gone several model calls without an update.
Tool descriptions carry only what each tool does, whatever the setting. A `--system-prompt` replacement is the whole
prompt, so the pack is never added to it.

Locality is a stand-in for what actually matters, which is how capable the
model is. A strong model served from your own machine gets coached anyway, and
a weak hosted one does not. To see whether the pack helps a particular model,
run the evals with it pinned on and then off (`just eval-guidance <model>`, see
[`evals/README.md`](../evals/README.md#guidance-pack-on-versus-off)); the report
says whether `auto` picked right for it, and `on` or `off` overrides it if not.

## Output styles

Output styles (`/output-style`) are named voice/format presets that modify the
system prompt. Built-ins: `default` (the stock prompt), `proactive`, `concise`,
`explanatory`, and `learning`. A custom style is a Markdown file with optional
frontmatter (`name`, `description`, `keep-coding-instructions`, which defaults
to keeping the built-in engineering instructions) plus the instructions to add:

```bash
/output-style                  # list every selectable style
/output-style concise          # switch for this session (persists to user config)
/output-style terse --project  # ...or to <git-root>/.mermaid/config.toml
mermaid --output-style concise # one invocation only (session-scoped)
```

```toml
[output]
style = "concise"
```

Custom files live in `~/.config/mermaid/output-styles/<name>.md` (every project)
or `<git-root>/.mermaid/output-styles/<name>.md` (this project wins on a name
clash); a custom name shadows a built-in. Files are capped at 40 KiB and an
unknown selection warns and falls back to `default`. A switch applies to the
next message — no `/clear` needed — and never touches subagents, which keep
the stock prompt. `mermaid doctor` reports the active style and which layer
set it.

## Web tool backends

Web routing is user-controlled under `[web]` in the user config or a session `-c` override.
Project config cannot select a backend or destination:

```toml
[web]
fetch_backend = "native"    # "native" (default, in-process, no key) or "ollama"
search_backend = "auto"     # "auto" (default) | "ollama" | "searxng"
allow_ollama_search_fallback = false  # opt-in: cloud search where "auto" has no bundle
searxng_url = "http://localhost:8080"
```

- **`web_fetch` defaults to `native`**: it fetches directly from your machine without ambient proxy variables, rejects URL userinfo and non-global destinations at every redirect, refuses HTTPS downgrade redirects, decodes supported text charsets, and routes extraction by MIME. HTML/XHTML uses readability; Markdown and plain text are preserved; JSON and XML are rendered as data. Set `fetch_backend = "ollama"` to explicitly route through Ollama Cloud's server-side fetch instead (useful for JS-heavy pages and bot walls; needs `OLLAMA_API_KEY`). Ollama's API does not disclose its target redirect chain or final URL, so Mermaid labels final provenance as unknown and treats target-hop enforcement as provider-managed rather than fabricating a final URL.
- **`web_search` defaults to sovereign `auto`**: Mermaid downloads and runs a self-contained local [SearXNG](https://github.com/searxng/searxng) bundle on the first search on supported Linux, macOS, and Windows (x86_64) targets, then health-checks and reuses it. Merely setting `OLLAMA_API_KEY` never changes the route. Select `search_backend = "ollama"` explicitly for Ollama Cloud, or `"searxng"` for your own instance at `searxng_url` (including on Windows; the instance must have `json` in `search.formats`). On a platform with no bundle (for example Windows on ARM), set `allow_ollama_search_fallback = true` (with `OLLAMA_API_KEY`) to let `auto` fall back to Ollama Cloud there. The fallback engages only where the bundle is unsupported (a viable managed bundle always wins), and the startup notice discloses the off-machine egress when it does.
- Native fetches record the requested and final URL, status, MIME type and truncation; cloud fetches mark the final URL as unknown. Results, downloads and the snapshot cache are size-capped per turn.

## Provider notes

Model names are whatever the vendor currently ships — Mermaid passes them through, so use any
model id from your provider's docs. See the env-var table in the
[README](../README.md#remote-providers).

Meta Muse Spark uses Meta's Responses API so encrypted reasoning state survives
Mermaid's model/tool loop without Meta retaining the response server-side.
Mermaid requests automatic reasoning summaries for the existing reasoning panel
and keeps the encrypted continuation only in private local session data.

OpenAI works the same way: `openai/<model>` goes to OpenAI's Responses API
(`POST /responses` with `store: false`), so a reasoning model keeps its
reasoning from one tool call to the next instead of starting again at each
step. The encrypted reasoning is replayed only to the model that wrote it;
after `/model` switches to another OpenAI model, the history goes back as plain
messages. Every other OpenAI-compatible provider, and a custom
`[providers.<name>]` entry, keeps Chat Completions. A proxy that serves only
Chat Completions can be reached as a custom provider (`compat = "openai-effort"`).

```bash
export MODEL_API_KEY="your-meta-api-key"
mermaid --model meta/muse-spark-1.1 --reasoning high
```

Cloudflare Workers AI needs both `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` (the account id is spliced into the account-scoped endpoint URL); alternatively set `[providers.cloudflare].base_url` to a full account-scoped URL or an AI Gateway endpoint. Example model: `cloudflare/@cf/zai-org/glm-5.2`.

Grok (xAI) uses `XAI_API_KEY` (create one at https://console.x.ai) at `https://api.x.ai/v1` — OpenAI-compatible Chat Completions. Both `grok/<model>` and `xai/<model>` prefixes work and resolve to the same endpoint. Example: `mermaid --model grok/grok-4.6`.

Ollama Cloud models authenticate via `OLLAMA_API_KEY`. Native `web_fetch` and managed/self-hosted SearXNG do not require it. Cloud web routing is never inferred from the key: set `fetch_backend = "ollama"` or `search_backend = "ollama"` explicitly, or opt into `allow_ollama_search_fallback` for platforms without a managed bundle (see [Web tool backends](#web-tool-backends)). Use `mermaid cloud-setup` from your shell to set the key for cloud models; `/cloud-setup` in the TUI points back to that shell command.

When a cloud provider call fails, the error shown in the TUI ends with a `(request-id: ..., cf-ray: ...)` line when the provider's response carried those headers — quote it when reporting the failure to the provider (or in a Mermaid issue), it lets them find the exact request.

## API keys

API keys resolve in strict precedence order: **environment variables always win**; the OS keyring (macOS Keychain, Windows Credential Manager, Linux Secret Service) fills the gap when no env var is set. Store a key with `mermaid login <provider>` (hidden input; `mermaid login` alone lists every provider's key status) and remove it with `mermaid logout <provider>`. A per-provider `api_key_env` override in config is authoritative — when set, neither the default env var nor the keyring is consulted. On headless Linux without a Secret Service the keyring quietly reports no keys (env vars keep working); set `MERMAID_NO_KEYRING=1` to disable keyring lookups entirely. `doctor`, `mermaid login`, and `mermaid feedback` report each key's source as `env`, `keyring`, or `none` — never the value.