Mermaid
An open-source AI coding assistant with computer use for the terminal. Multi-provider — Ollama (local), Anthropic, Gemini, Meta, OpenAI, Groq, OpenRouter, and any OpenAI-compatible endpoint — with native tool calling, subagents, computer-use tools, and a clean TUI.
Features
- Multi-Provider — Ollama (local/cloud), Anthropic Claude, Google Gemini, Meta Muse, OpenAI, Groq, OpenRouter, Cerebras, DeepInfra, Together, NVIDIA NIM, Cloudflare Workers AI, plus fully-custom OpenAI-compatible endpoints
- Native Tool Calling — read, write, edit, delete, create directories, execute commands, search the web, spawn subagents, and call configured MCP tools
- Computer Use — screenshot, click, type, press keys, scroll, move the mouse, and list windows on supported interactive GUI backends
- Subagents — spawn parallel autonomous agents for independent tasks; built-in
generaland read-onlyexploretypes (plus user-defined ones), per-call model override, and continuation handles to follow up with a child that kept its context - Agent Loop — model calls tools autonomously, sees results, and continues until done
- Image Paste — Ctrl+V to attach images for vision models (X11/Wayland/macOS/Windows)
- Reasoning Levels — seven tiers (
none/minimal/low/medium/high/xhigh/max); cycle with Alt+T or set via/reasoning; persisted per-model - Safety Modes —
read_only/ask/auto/full_access;autois classifier-backed (an LLM vets each borderline action against your intent, auto-running aligned ones and escalating risky ones); cycle live with Shift+Tab or/safety - Plan Mode — Alt+P or
/plan: a hard read-only collaboration state where the agent explores, asks structured questions, and authors a plan file you approve before anything changes. Approval seeds the live task checklist from the plan's Tasks section and can start implementation in place, in a cleared context, or hand off to a fork/fresh session on a different model (plan on a frontier model, execute locally). Per-category permissions (builds, web, memory, task tools), plan-phase model/reasoning overrides, and approval behavior live in/plan config;mermaid run --plandoes it headless - Inline approvals — in
askmode (andautoescalations) a gated action pauses and prompts inline (1Yes ·2Yes, don't ask again ·3/Esc No); the agent waits for your answer instead of erroring out - Checkpoints — shadow-git snapshots before mutations (
checkpoint_on_mutation, on by default); inspect with/checkpoints, roll back with/restore <id> - Project Instructions — auto-loads
AGENTS.mdandMERMAID.md(MERMAID.md wins on conflict); edits take effect on the next turn - Durable Memory — the agent remembers facts across sessions (
memorytool +/remember,/memory,/forget); a compact index auto-loads into every prompt - MCP Servers — stdio JSON-RPC client with a built-in registry of 16 popular servers (
mermaid add <name>) - Session Persistence — conversations auto-save;
--continuereopens the last one in the current directory,--resumeopens a searchable picker of past sessions - Rewind & Fork — double-Esc when idle picks an earlier message and forks the session there (new session id, lineage recorded, original untouched); edit the pre-filled prompt and resend to branch the timeline. Files are not rewound, but checkpoints taken after the fork point are surfaced so
/restore <id>can roll them back - Context Compaction — automatic checkpoint-and-continue when the window fills (or the model truncates mid-run); manual
/compact [focus]for handoffs - Record & Replay —
--recordcaptures every reducer input;--replayreconstructs the session offline, deterministically, with a built-in purity check - @-Mentions — type
@in the composer to fuzzy-pick a project file (gitignore-aware); the path lands in your prompt as text the agent reads with its tools - Message Queuing & Mid-Run Steering — type while the agent works; queued messages are delivered at the next tool boundary within the run (the model course-corrects mid-task), or at run end when no boundary follows
- Non-Interactive Mode — script with
mermaid run "prompt"for CI/automation
Architecture
Mermaid's runtime is an Elm/MVU pattern: one pure reducer (fn update(State, Msg) -> (State, Vec<Cmd>)), effects as data, structured concurrency per turn. Whole classes of bug the old architecture let slip — duplicate error display, 20-press Ctrl+C during tool execution, stale stream events corrupting a new turn — are statically impossible against the new types.
Read docs/architecture.md for the full tour. The adding a tool and adding a provider recipes are one file each; docs/replay_debugging.md covers record/replay for reproducing bugs.
Get started
No Rust or cargo required — the installer downloads a prebuilt binary for your platform from the latest GitHub Release, verifies its checksum, and puts mermaid on your PATH.
macOS / Linux
|
Windows (PowerShell)
irm https://noahsabaj.github.io/mermaid-cli/install.ps1 | iex
Then run mermaid to start, and mermaid update whenever you want the newest version. (Set MERMAID_INSTALL_DIR to change the install location, or MERMAID_VERSION=vX.Y.Z to pin a specific release.)
Or install with a package manager
# Homebrew (macOS / Linux)
# Scoop (Windows)
# WinGet (Windows) — pending review on the official winget-pkgs repo
winget install NoahSabaj.Mermaid
All three are bumped automatically on every release; upgrade with mermaid update or your package manager.
Prebuilt binaries (plus .deb/.rpm) are attached to every release; the crates.io release can lag the newest tag.
Local inference requires Ollama (models auto-pull if not found locally). Cloud providers are optional — see Remote Providers below.
First 10 Minutes
Then ask Mermaid to do normal coding-agent work:
- "read the repo and tell me where the test runner lives"
- "find the bug in this failing test and fix it"
- "add this small feature and run the relevant tests"
- "review the current branch for regressions"
Inside the TUI, use /help for grouped commands, /doctor for the current session readiness report, /context to inspect prompt budget and compaction status, /compact [focus] to create a handoff checkpoint, and Esc to interrupt the current agent loop.
Computer Use Dependencies (optional)
For full Linux GUI control via screenshot/click/type tools:
# Linux / X11
# Linux / Wayland
# Screenshot downscaling (optional, for high-res displays)
Computer-use registration is backend-gated: Linux/X11 and Linux/Wayland are the current full-control backends. macOS currently supports screenshot capture through screencapture plus clipboard image paste through pngpaste/osascript; click/type/scroll are not yet ported there. Windows clipboard paste uses PowerShell, but the computer-use backend is not wired yet. See src/providers/tool/computer_use/ for the implementation matrix.
Usage
# Structured output: the agentic loop runs
# normally, then one formatting turn reshapes
# the answer to your JSON Schema (validated
# client-side; result carries structured_output).
# Native constrained output on OpenAI-compatible
# providers, Gemini, Ollama, and Anthropic
# (output_config.format on current models).
# ndjson session_started/result, json result,
# or the `session:` line on stderr)
mermaid add <name> resolves the name through a built-in registry of 16 popular MCP servers (context7, playwright, memory, git, fetch, time, filesystem, notion, slack, postgres, brave-search, supabase, perplexity, docker, sequential-thinking, everything), prompts for any required env vars, validates by spawning the server, and saves it to ~/.config/mermaid/config.toml.
Keyboard Shortcuts
| Key | Action |
|---|---|
| Enter | Send message (or queue while the model is generating) |
| Esc | Stop generation / dismiss command palette or attachment focus |
| Esc Esc | (idle) Rewind: fork the session at an earlier message — original preserved, composer pre-filled |
| Ctrl+C | Quit (auto-saves the session) |
| Ctrl+D | Quit when the input box is empty (auto-saves the session) |
| Ctrl+B | While tools are running, send the foreground command to the background (it keeps running as a /processes entry) |
| Alt+T | Cycle reasoning level: None → Minimal → Low → Medium → High → XHigh → Max → None |
| Alt+P | Toggle plan mode (read-only exploration + an approvable plan file) |
| Shift+Tab | Cycle safety mode: read_only → ask → auto → full_access → read_only (session-scoped) |
| Ctrl+V | Paste image or text from clipboard |
| Ctrl+O | Compose the prompt in $VISUAL/$EDITOR (TUI suspends, resumes on save-quit) |
| Ctrl+Click | Open image from chat history |
| Drag | Select chat text (highlights; does not copy) |
| Ctrl+Shift+C | Copy the selected chat text to the clipboard |
| Shift+Drag | Native terminal selection (bypasses Mermaid's mouse capture — useful for selecting across the whole window, including the input box and status bar) |
/ |
Open slash-command palette (filter-as-you-type) |
@ |
Open the fuzzy file picker (at the start of a word); type to filter, Tab/Enter inserts @path, Esc dismisses |
| Tab | In palette: complete highlighted command name |
| Up/Down | Navigate input history; palette and conversation-list navigation |
| Mouse Wheel | Scroll chat |
Slash Commands
Type / to open the command palette (shows all commands with live filter); type /<name> to invoke directly. /help shows the same commands grouped in the TUI.
Everyday:
/doctor— show current model, safety, prompt, instruction, and tool readiness/clear,/save [name],/load [id],/list— manage the conversation/cancel [id]— cancel the active turn or a durable task/handoff [id],/report [id]— write a current-context report or inspect a task report/theme [dark|light]— switch the color theme (persisted);NO_COLORdisables colors entirely/editor— compose the prompt in$VISUAL/$EDITOR(Ctrl+O keeps the current draft)/help(/h),/quit(/q)
Model and context:
/model <name>— switch model; auto-pulls Ollama models if needed/reasoning <level>— set reasoning:none,minimal,low,medium,high,xhigh,max/visible-reasoning [on|off|toggle]— show or hide reasoning blocks in the transcript/usage,/context,/compact [instructions]/model-info <model>
Durable memory:
/memory(alias/memories) — list the durable facts Mermaid has saved across sessions/remember <fact>— save a fact to durable memory/forget <name>— delete a saved memory by name/consolidate-memory(aliases/memory-consolidate,/prune-memory) — merge duplicates and prune stale memories
Safety and recovery:
/safety [read_only|ask|auto|full_access](alias/permission) — show or set the session safety mode; Shift+Tab cycles it/plan [off|show|config]— enter/leave plan mode (Alt+P toggles), show the plan file, or open the plan settings picker (/configopens the same picker)/approvals,/approve <id>,/deny <id>/checkpoint <path...>,/checkpoints,/restore <id>
Integrations:
/plugins,/cloud-setup
Advanced runtime:
/tasks,/task <id>,/pause <id>,/resume <id>/processes,/logs <id>,/stop <id>,/restart <id>,/open <target>,/ports
Reasoning choices persist per-model: set /reasoning high on one model and /reasoning low on another, and each is remembered independently across sessions.
Tools
The model uses these autonomously via native tool calling:
| Tool | Description |
|---|---|
read_file |
Read files (text, PDF, images) |
write_file |
Create or overwrite files (timestamped backup if file exists) |
apply_patch |
Multi-hunk, context-anchored file edits with a diff (fuzzy-tolerant) |
delete_file |
Delete files (timestamped backup) |
create_directory |
Create directories |
execute_command |
Run shell commands; background mode registers PID/log/URL metadata for GUI apps and dev servers |
memory |
Manage durable cross-session memory (remember / update / forget facts; project, shared, or global scope) |
web_search |
Search the web (zero-config: managed local SearXNG, or Ollama Cloud) |
web_fetch |
Fetch a URL as markdown (native in-process by default, no key); optional pattern finds case-insensitive substring matches with surrounding context instead of returning the whole page |
agent |
Spawn autonomous sub-agent for parallel tasks |
screenshot |
Capture the screen (fullscreen, focused window, monitor, region, or window by title) |
list_windows |
List visible window titles (X11-only discovery for window-mode screenshots) |
click |
Click at screen coordinates (auto-screenshot after) |
type_text |
Type text at cursor position (auto-screenshot after) |
press_key |
Press key combos (ctrl+s, alt+tab, etc.) |
scroll |
Scroll up or down |
mouse_move |
Move mouse cursor without clicking |
MCP servers contribute additional tools under the mcp__<server>__<tool> prefix when configured. Names and schemas are sanitized to provider-safe form at startup (charset [A-Za-z0-9_-], 64-char cap, $ref inlining and other schema normalization); enabled_tools/disabled_tools filters keep matching the RAW tool names the server itself advertises. web_fetch (native) and web_search (see Web tool backends) are both registered by default with no configuration. Computer-use tools are advertised only in interactive TUI sessions when a usable GUI backend is detected.
By default MCP tools are deferred: instead of advertising every server's tools on every request, the model gets one tool_search tool that searches deferred tool names/descriptions and promotes matches to direct advertisement for the rest of the session — deferred schemas don't count against /context until promoted. Servers start concurrently at launch, each bounded by a 60-second timeout, and report ready/errored individually. Opt out globally with mcp_defer_tools = false at the top level of config, or per server with defer = false on its [mcp_servers.<name>] entry.
Web tool backends
The web tools work out of the box with no configuration, and are backend-pluggable under [web]:
[]
= "native" # "native" (default, in-process, no key) or "ollama"
= "auto" # "auto" (default) | "ollama" | "searxng"
= "http://localhost:8080"
web_fetchdefaults tonative: it fetches the URL directly from your machine and converts the HTML to markdown — no API key, no third party. Setfetch_backend = "ollama"to route through Ollama Cloud's server-side fetch instead (handles JS-heavy pages and bot-walls better; needsOLLAMA_API_KEY).web_searchdefaults toauto, which just works with zero setup: ifOLLAMA_API_KEYis set it uses Ollama Cloud; otherwise mermaid downloads and runs a self-contained local SearXNG bundle on your first search and tears it down when it exits — no Docker, no Podman, nothing to install. The bundle (a portable Python plus the Granian server and SearXNG) is fetched once from mermaid-searxng, sha256-verified, and cached under your data dir; after that startup is a couple of seconds. Force a backend withsearch_backend = "ollama"(Ollama Cloud) or"searxng"(your own instance atsearxng_url, which must havejsonin itssearch.formats).
OS Sandbox
Model-run shell commands can be confined by the operating system, independently of the approval policy. Two dimensions, each with its own flag (or config key):
--no-network(safety.network = "deny"): commands cannot reach the network. LocalAF_UNIXsockets stay open so D-Bus/nscd-style IPC keeps working.--confine-fs(safety.filesystem = "project"): write-class filesystem access is allowed only beneath the project root, the working directory, the system temp directory, and (on unix)/dev. Reads and execution stay unrestricted.--sandbox: both at once.
Enforcement is per-platform, behind one facade:
| Platform | Network deny | Write confinement | Denial signature |
|---|---|---|---|
| Linux | seccomp-BPF kill-switch: creating an AF_INET/AF_INET6 socket dies with SIGSYS |
Landlock (kernel 5.13+; best-effort no-op with a warning on older kernels) | network: precise (SIGSYS); filesystem: hedged permission-error text |
| macOS | Seatbelt (sandbox-exec) allow-default profile with (deny network*), sparing AF_UNIX |
Seatbelt deny file-write* outside the allowed roots, matched on both the literal and canonicalized path (so TMPDIR firmlinks work) |
both hedged: EPERM "Operation not permitted", no signal |
| Windows / other | not yet enforced | not yet enforced | n/a |
The sandbox is applied by the hidden mermaid __sandbox-exec launcher just before it runs
the real command, and is inherited by everything the command spawns. It fails closed: if
requested confinement cannot be applied, the command exits 126 instead of running
unconfined. On platforms without a backend the exec tool does not request confinement at
all — it logs a once-per-process warning, and mermaid self-test reports the real
per-platform availability.
Project Instructions
Create an AGENTS.md (the cross-tool open standard) and/or a MERMAID.md (mermaid-specific) at your project root with conventions, tool versions, naming patterns, and run commands. Both are loaded from the nearest matching directory — AGENTS.md first, then MERMAID.md, so MERMAID.md overrides on conflict. They auto-reload when the files change (one stat per turn, no filesystem watcher). The walk stops at the .git root or $HOME.
Skills
Skills are task-specific playbooks the agent loads on demand (progressive disclosure). Each skill is a directory holding a SKILL.md with Claude Code-compatible frontmatter:
name: deploy
description: Cut a release — version bump, changelog, tag, publish
Step-by-step instructions the model follows when this skill applies...
At startup mermaid discovers skills from three places — project (<git-root>/.mermaid/skills/<name>/SKILL.md, shared with your team), user (~/.config/mermaid/skills/<name>/SKILL.md, all your projects), and enabled plugins (declared in the plugin manifest's skills list) — and injects a compact index (name, description, path) into the system prompt. Same-named skills dedupe with project > user > plugin precedence. When a task matches a description, the model reads the full SKILL.md with read_file, so activation is visible in the transcript and idle skills cost almost nothing per request. The index caps at 64 skills / 8 KiB; edits to skill files are picked up on the next session start. mermaid doctor reports the discovered count.
- --
-
File size is capped at ~10k tokens; oversized content is truncated with a marker so the model knows context was elided.
Runtime And Background Service
The CLI/TUI is the primary Mermaid app. mermaidd is optional advanced infrastructure for durable runtime state, remote attach, and long-running process ownership; normal chat, mermaid run, and mermaid self-test work without the user service.
mermaidd stores durable runtime state in ~/.local/share/mermaid/runtime.sqlite3 and exposes a local Unix-socket JSONL control surface at ~/.local/share/mermaid/mermaidd.sock. The socket is created mode 0600 and the data dir 0700, so only your user can reach it. A localhost TCP listener on 127.0.0.1:39871 is off by default — enable it with MERMAID_DAEMON_ENABLE_TCP=1. Mutating Unix-socket JSON commands require a pairing token; when TCP is enabled, every command (including health) requires a token. Create one with mermaid pair --label <device> and pass it as MERMAID_DAEMON_TOKEN or auth.token.
Attach to a running (or still-queued) daemon task's live event stream with mermaid task <id> --follow: an ack line, then NDJSON RunEvent lines (text deltas, tool starts/finishes, errors) until the terminal result, at which point the command exits. The daemon's whole control protocol is a typed request enum (subscribe_task included) — unknown commands and malformed fields answer with a precise parse error instead of a silent drop.
The CLI can inspect and manage the same store with mermaid tasks, mermaid task <id>, mermaid cancel <task-id>, mermaid approvals, mermaid approve <id>, mermaid deny <id>, mermaid tool-runs, mermaid checkpoints, mermaid restore <id>, mermaid plugin list, mermaid plugin install <path-or-github>, mermaid plugin audit <path>, mermaid models, mermaid model-info <model>, mermaid processes, mermaid logs <process>, mermaid stop <process>, mermaid restart <process>, mermaid open <target>, mermaid ports, mermaid pair, and mermaid daemon. Installing a plugin from a Git URL (rather than a local path) requires an explicit full URL and MERMAID_ALLOW_PLUGIN_FETCH=1, since fetching and later running remote plugin code is a privileged operation.
Daemon run requests are queued and executed by a scheduler bounded by [daemon] max_concurrent_tasks (default 1, so a burst of background tasks never runs more than one agent loop against your GPU at a time; raise it for cloud-provider workloads). Requests may carry "priority": "low"|"normal"|"high" — higher priority claims first, FIFO within a level — and the queue is durable across daemon restarts. Cancel a queued or running task with mermaid cancel <task-id>; running tasks unwind gracefully (the same teardown as Esc in the TUI). [daemon] task_timeout_minutes bounds each task's wall clock. Finished tasks and the daemon's training-signal outcomes are garbage-collected on startup by [daemon] retention_days (default 30) and [daemon] outcomes_retention_days (default 180, longer so training history outlives the task rows it came from).
On Linux, install a per-user systemd unit with mermaid daemon install --start. The installer writes ~/.config/systemd/user/mermaidd.service, points ExecStart at the discovered mermaidd binary, reloads systemd's user manager, and optionally enables/starts the service. Use mermaid daemon status, mermaid daemon logs [-f], mermaid daemon restart, mermaid daemon stop, mermaid daemon uninstall, or mermaid daemon print-unit for day-to-day service management. Set MERMAID_DAEMON_BIN=/absolute/path/to/mermaidd before installing if the background-service binary is not next to mermaid or on PATH.
Logging and diagnostics
Mermaid logs to ~/.mermaid/mermaid.log (owner-only, secret-redacted, 10 MiB rotation), scoped by RUST_LOG / --verbose. Independently, an always-on in-memory ring captures the last ~2000 trace events from mermaid's own crates at TRACE level (dependencies capped at INFO) regardless of RUST_LOG — so a bug that already happened is diagnosable without reproducing it under elevated logging.
mermaid feedback writes a local diagnostic bundle to the current directory (mode 0600): the doctor report, a names-and-booleans config summary (provider keys are reported as present/absent — never values), recent session ids, the trace ring, and the log tail. --stdout prints instead; --format json emits machine-readable output. Nothing is uploaded — the ring redacts secrets at capture, the log at write, and the whole rendered bundle passes a final redaction sweep; review the file before sharing it in a bug report.
On Windows, mermaidd.exe serves the same JSONL control surface over a named pipe at \\.\pipe\mermaidd-<your-user-SID> instead of a Unix socket. The pipe carries an explicit owner-only security descriptor (LocalSystem + your user, nothing else) and rejects remote pipe clients, mirroring the 0600 socket + peer-uid check on Unix; the same pairing-token rules apply on top. There is no service installer yet — start mermaidd.exe directly or wire it into Task Scheduler; mermaid daemon install remains Linux/systemd-only.
Release builds keep the existing .tar.gz/.zip archives and add Linux .deb/.rpm artifacts for x86_64 and aarch64. The distro packages install mermaid, mermaidd, docs, and a reference systemd user unit at /usr/lib/systemd/user/mermaidd.service; they do not auto-enable or start the daemon.
Plugin hooks
Enabled plugins' hooks receive lifecycle events as JSON on stdin (MERMAID_HOOK_EVENT names the
event). Most events are observe-only, but on before_tool_use a hook can gate the call by
printing one JSON object on stdout (Claude Code-compatible), or deny by exiting with code 2
(stderr becomes the reason):
#!/bin/sh
# deny-etc-writes: block any tool call whose arguments mention /etc
payload=
The response may also carry updatedInput (a full replacement tool-arguments object — still
vetted by the safety policy exactly like the original) and additionalContext (a string surfaced
to the model on its next request). The legacy {"decision": "block", "reason": "..."} shape is
accepted too. Across plugins: the first deny wins, the last updatedInput wins, and context
strings concatenate. Failure semantics are asymmetric by design: an explicit deny always denies,
while infrastructure failures (unparseable output, a timeout, a crash) log a warning and allow —
a buggy hook must not lock you out of every tool call.
Plugin bundles: MCP servers, commands, agent types
Beyond skills and hooks, an enabled plugin can contribute three more asset kinds, each a list of
plugin-relative paths in plugin.toml:
mcp = ["servers.toml"]— each file's[servers.<name>]tables are MCP server configs (same shape as[mcp_servers.<name>]in your config). They start with your own servers at session startup and flow through tool deferral like any other server. A./-relativecommandresolves inside the plugin directory (containment enforced); anything else is PATH-looked-up. A same-named server in your config wins with a warning. Enabling a plugin that declares MCP servers grants command execution — the same trust boundary as hooks.prompts = ["deploy.md"]— markdown prompt commands with the skills frontmatter dialect (name:/description:; the name falls back to the file stem, validated[a-z0-9-]+). They appear in the/palette tagged(plugin:<name>)and in/help; running/deploy prodsubstitutesprodfor$ARGUMENTS(or appends the args when the token is absent) and submits the expansion as a normal prompt — the transcript shows the expanded text, so recordings replay without the plugin. Built-in commands always win over a same-named prompt.agents = ["types.toml"]— each file's[types.<name>]tables are agent types (same shape as[agents.types.<name>]): the model can spawn them via theagenttool. Your config's same-named type wins with a warning.
Like skills, bundle changes are picked up on the next session start.
Configuration
Config file: ~/.config/mermaid/config.toml (Linux) or platform equivalent via directories crate.
Configuration is assembled from layers, later layers winning key-by-key through one recursive TOML deep-merge (tables merge; scalars and arrays replace):
- built-in defaults
- the user config file above
- the project config:
<git-root>/.mermaid/config.toml, located from the working directory (-p /repoadopts that repo's project config) - session flags: repeatable
-c key.path=valueplus 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, and 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, web/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,web,compaction,computer_use,memory,non_interactive, and asafetysubset. Anything else —mcp_servers(spawns commands),providers(redirects traffic/credentials),agents,daemon— is ignored with a warning, as areweb.searxng_urlandollama.host/port(traffic-redirect vectors) inside otherwise-allowed tables. safety.mode,safety.network, andsafety.filesystemare clamped tighten-only against your user config: a project can turn the sandbox on or drop toread_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.
Run mermaid init to create a default config. Important fields in the current config schema:
# Last model picked via `--model` — used by bare `mermaid` on next start
= "ollama/qwen3-coder:30b"
[]
= "ollama"
= "qwen3-coder:30b"
= 0.7
= 0 # 0 = auto (model-scaled output budget); positive = hard cap
= "medium" # none | minimal | low | medium | high | xhigh | max
[]
= "localhost"
= 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. Disable here — or with
# MERMAID_OLLAMA_AUTOSTART=0 in the environment — if you manage Ollama
# yourself (custom bind address, containers, CI).
= true
# cloud_api_key = "your-key" # for :cloud models
# num_gpu = 10
# num_thread = 8
# num_ctx = 8192
# numa = false
[]
# Approval policy. Default is "ask": prompt before mutations / shell / network
# actions. "auto" runs an LLM classifier that vets each borderline action
# against your stated intent — aligned actions run automatically, risky ones
# escalate to an approval prompt. "full_access" auto-runs everything (the
# legacy default); "read_only" blocks all mutations but keeps reads flowing —
# including web_search / web_fetch, which are reads of the public web (the
# SSRF guard on internal hosts applies in every mode). Change it live with
# Shift+Tab or `/safety <mode>` (session-scoped; this value is the persistent
# default each session starts from).
= "ask"
= true
# 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.
# auto_classifier_model = "<provider>/<small-fast-model>"
[]
# 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
[]
# TUI color theme: "dark" (default) or "light". Switch live with
# `/theme dark|light` (persists here). Setting the NO_COLOR environment
# variable (any non-empty value) disables colors entirely, regardless of
# this value.
= "dark"
[]
# Run behavior is controlled by CLI flags:
# mermaid run "prompt" --format json --max-tokens 4096 --no-execute
# These fields remain in the schema for compatibility but are not the
# source of truth for `mermaid run`.
= "text"
= 0 # 0 = auto (model-scaled); positive = hard cap
= false
# Durable agent memory (the `memory` tool, the always-loaded index, and
# /remember & friends). On by default.
[]
= true
# index_cap_bytes = 8000 # byte cap on the always-loaded memory index
[]
# 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.
= 3
# 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).
[]
# Wall-clock ceiling per subagent drive, in seconds. 0 = built-in default
# (1200 = 20 minutes).
= 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
# Per-model reasoning preferences (remembered across sessions)
[]
# "<provider>/<model>" = "high"
= "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.
[]
= "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
[]
# api_key_env = "MY_ANTHROPIC_KEY" # default: ANTHROPIC_API_KEY
[]
# api_key_env = "MY_GOOGLE_KEY" # default: GOOGLE_API_KEY; GEMINI_API_KEY is accepted as a legacy fallback
[]
# api_key_env = "MY_META_KEY" # default: MODEL_API_KEY
# base_url = "https://api.meta.ai/v1"
[]
# api_key_env = "MY_GROQ_KEY" # default: GROQ_API_KEY
# base_url = "https://api.groq.com/openai/v1"
# extra_headers = { "X-Custom-Header" = "value" }
# Custom OpenAI-compatible provider (e.g., self-hosted vLLM)
[]
= "http://192.168.1.42:8000/v1"
= "VLLM_KEY"
= "openai-effort" # openai | openai-effort | openrouter
# default_model = "Qwen/Qwen2.5-Coder-32B-Instruct"
# MCP servers — usually managed via `mermaid add <name>`
[]
= "npx"
= ["-y", "@upstash/context7-mcp"]
System prompt customization is runtime-only and is not saved to config:
Remote Providers
Set the appropriate environment variable (or override via [providers.<name>].api_key_env in config). Model names are whatever the vendor currently ships — Mermaid passes them through, so use any model id from your provider's docs in the format below:
| Provider | Env var | Model format |
|---|---|---|
| Anthropic | ANTHROPIC_API_KEY |
anthropic/<model> |
| Google Gemini | GOOGLE_API_KEY (GEMINI_API_KEY legacy fallback) |
gemini/<model> |
| Meta | MODEL_API_KEY |
meta/<model> |
| OpenAI | OPENAI_API_KEY |
openai/<model> |
| Groq | GROQ_API_KEY |
groq/<model> |
| OpenRouter | OPENROUTER_API_KEY |
openrouter/<vendor>/<model> |
| Cerebras | CEREBRAS_API_KEY |
cerebras/<model> |
| DeepInfra | DEEPINFRA_API_KEY |
deepinfra/<vendor>/<model> |
| Together | TOGETHER_API_KEY |
together/<vendor>/<model> |
| NVIDIA NIM | NVIDIA_API_KEY |
nvidia/<vendor>/<model> |
| Cloudflare Workers AI | CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID |
cloudflare/@cf/<vendor>/<model> |
| Ollama Cloud | OLLAMA_API_KEY |
ollama/<model>:cloud |
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.
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.
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.
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.
Ollama Cloud models authenticate via OLLAMA_API_KEY. The web tools don't require it: web_fetch is native, and web_search defaults to auto — Ollama Cloud when the key is set, otherwise a mermaid-managed local SearXNG (see 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.
Development
Contributor guardrails live in AGENTS.md (MVU purity, the no-emoji
rule, no back-compat shims). The one-command pre-PR gate — exactly what CI runs —
is:
just check # cargo fmt --check + clippy -D warnings + cargo nextest run
Or run the pieces directly if you don't have just
/ cargo-nextest:
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo nextest run --workspace # or: cargo test --workspace
CI additionally runs two dependency-free source guards (.github/scripts/): no
emoji/pictographs in source, and src/domain stays a pure MVU core (no I/O, no
wall clock).
The TUI has a snapshot suite (src/render/snapshots.rs, unix-only) that pins
full rendered frames for curated scenes at 80x24 and 120x40. It runs as part of
the normal test suite; a mismatch panics with a diff and writes a gitignored
.snap.new sibling. Review and accept deliberate visual changes with
just snapshots (cargo insta review) and commit the updated .snap files in
the same PR as the style change.
License
MIT OR Apache-2.0
Built with Ratatui and Ollama. Inspired by Aider and Claude Code.