Settings & configuration
mnml has two ways to change behavior: a TOML config file (the durable surface — every knob, including ones that don’t have a UI yet) and a Settings overlay (:settings / view.settings — a keyboard-driven sectioned list for the everyday toggles). The overlay writes to the live config in memory; the TOML file is the on-disk source of truth.
Both layers see the same Config struct. Editing TOML and reopening, or toggling in the overlay, both end up at the same state.
Where config lives
Section titled “Where config lives”mnml reads two files at startup, lowest to highest precedence:
- Global —
~/.config/mnml/config.toml(or$XDG_CONFIG_HOME/mnml/config.tomlwhenXDG_CONFIG_HOMEis set). - Per-workspace overlay —
<workspace>/.mnml/config.toml, relative to whatever directory you launched mnml in. - Explicit —
mnml --config /some/path.toml, applied on top of the two above.
Each file is a partial overlay: keys you don’t set fall through to the lower layer (or to the built-in default). So a workspace-local file can flip input_style = "vim" for just that repo without re-declaring everything else.
Missing files are fine — mnml silently treats them as empty. A malformed file is reported on stderr and skipped (the rest of the layers still apply). To re-apply a file at runtime without restarting:
:source ~/.config/mnml/config.tomlA few sections ([[workspaces]], [[ui.launcher_icon]], [[ui.integration_icon]], …) have their own merge rules — workspaces append across files; launcher-icon and integration-icon arrays replace. Those quirks are called out below.
The Settings overlay
Section titled “The Settings overlay”Open it with :settings, the view.settings command id from the palette, or the gear glyph in the bufferline. The overlay is a centered ~60% × 70% sectioned list — section headers ── UI ── / ── Editor ── / ── Session ── / ── Reset ── with editable rows in between.
Each row looks like:
▸ Cursor line: [on] / off * Scrollbar: [on] / off Tab width: 2 / [4] / 8▸marks the focused row.[bracketed]is the current value.- Trailing
*means the row’s value differs from the built-in default.
| Key | Action |
|---|---|
↑ ↓ / j k | Move row (skips section headers) |
← → / h l | Cycle the focused row’s value backward / forward |
r | Reset just this row to its default |
R | Reset every setting (also: focus the Reset all to defaults row and press Enter) |
Enter | On a discrete-choice row, same as →. On a text or color row, enters greedy edit mode (printable keys → buffer, Backspace → delete, Enter → commit, Esc → cancel). On the reset-all sentinel row, fires the global reset |
Esc | While editing a text/color row, cancel the in-progress edit (restore the value the row had on edit-mode entry). Otherwise, cancel the overlay — revert the live config to whatever it was when the overlay opened |
Enter (anywhere except the reset row) commits whatever’s currently on screen and closes the overlay. The overlay does not persist changes to TOML — it writes the in-memory Config. If you want a change to survive restarts, also edit the matching TOML key.
Number rows (v2)
Section titled “Number rows (v2)”A second row kind has shipped alongside the discrete-choice rows: number rows. Where a choice row brackets the active option, a number row brackets the live value with its unit:
▸ Scrolloff (rows of context above/below cursor): [ 4 ] (0–20 · step 1 · default 0) Sidescrolloff (cols of context left/right …): [ 0 ] (0–20 · step 1 · default 0) File tree width: [ 30 cols ] (16–60 · step 2 · default 30) *←/→step the value by the row’sstep, clamped to[min, max]. The hint in dim text —(min–max · step N · default D)— tells you what each press will do.rresets just this row to its built-in default.[ <value><unit> ]is the live value; the*modified marker appears when it differs from the default.
The first-class number rows today:
| Row | TOML key | Range | Step | Unit | Default |
|---|---|---|---|---|---|
| Scrolloff | [ui] scrolloff | 0..=20 | 1 | (none) | 0 |
| Sidescrolloff | [ui] sidescrolloff | 0..=20 | 1 | (none) | 0 |
| File tree width | [ui] tree_width | 16..=60 | 2 | cols | 30 |
| Tab width | [editor] tab_width | 1..=12 | 1 | cols | 4 |
| Color column (0 = off) | [ui] color_column | 0..=200 | 4 | (none) | 0 |
Color column is a vertical hint line at column N — set it to 80 for the classic line-length guide, or leave at 0 (off) when you don’t want one. Tab width is the per-buffer expansion width for \t characters; .editorconfig files in your repo override this per-file.
Text rows (v2)
Section titled “Text rows (v2)”A third row kind has landed: text rows for free-form string settings. Press Enter on a focused text row to drop into greedy edit mode — the row’s value becomes a live edit buffer until you commit or cancel:
▸ Theme: [ "tokyonight-night│" ] (editing · Enter commit · Esc cancel)Enteron a focused text row enters edit mode (text rows don’t cycle onEnterthe way discrete rows do).- While editing: printable keys append to the buffer ·
Backspacedrops the trailing character ·Entercommits + exits ·Escrestores the value the row had when you started editing and exits. - The value is live-written to the in-memory
Configon every keystroke (viaapply_text_setting), so the rendered[ "<buffer>│" ]and any downstream visuals (e.g. a color swatch on a Color row) reflect the in-progress edit. Cancelling restores the snapshot. - The
│is a literal cursor caret painted at the end of the buffer while in edit mode. The bottom-of-overlay hint swaps from the navigation chord list to(editing · Enter commit · Esc cancel). ←/→are no-ops on text rows outside of edit mode — there’s nothing to step through.rresets just this row’s setting to its built-in default (viaapply_text_setting).[ "<value>" ]is the live value; the*modified marker appears when it differs from the default.
The one first-class text row today:
| Row | TOML key | Default |
|---|---|---|
| Theme | [ui] theme | tokyonight-night |
Color rows (v2)
Section titled “Color rows (v2)”The fourth row kind: color rows for hex-color settings. Same shape as text rows, but the value is a 6-char RRGGBB hex string (no leading #). The renderer brackets the value, then paints a ████ swatch in the parsed color:
▸ Accent: [ #61afef ] ████ (color · default #61afef · TOML to edit)- Invalid hex (wrong length, non-hex chars) falls back to the foreground color for the swatch and appends
· invalid hexto the dim hint suffix. - Same edit-mode shape as text rows:
Enterenters greedy edit mode, printable keys append,Backspacedeletes,Entercommits,Esccancels + restores. Each keystroke live-writes throughapply_text_setting, so the swatch repaints in the in-progress color as you type (the parsed-color preview is the whole reason live edit matters for color rows).rstill resets to the row’s default; the*modified marker behaves the same way.
The first-class color row today:
| Row | TOML key | Default |
|---|---|---|
| Cmdline popup border color | [ui] cmdline_popup_border_color | (theme accent) |
The cmdline popup is the floating completion box above the : cmdline. Setting this overrides the popup’s border color independently of the theme — useful when you want the popup chrome to pop against an otherwise low-contrast theme. See Cmdline popup for the full surface. More color rows are reserved for future overrides (status-line color override, gutter accent, etc.) — the live-edit machinery is shared so adding one needs only a build_settings entry + an apply_text_setting arm.
What’s in the overlay vs what’s TOML-only
Section titled “What’s in the overlay vs what’s TOML-only”The overlay covers discrete-choice rows (booleans, input style vim/standard, tab width 2/4/8, line numbers relative/absolute/off, picker position center/top, now-playing source auto/mixr/macos), number rows (scrolloff, sidescrolloff, tree width), text rows (theme — with live edit), and color rows (live-edit machinery shared with text rows; no first-class entries yet — reserved for future overrides).
Things the overlay does not edit:
- Arrays of complex things —
[[workspaces]],[[ui.launcher_icon]],[[ui.integration_icon]],[snippets.<scope>],[tasks.<name>],[formatters.<ext>],[linters.<ext>]. These stay in TOML. - Free-form strings — theme name, ticket prefixes, formatter command templates.
[keys.*]tables — keybindings are TOML-only (see Keybindings below).
Row → config key mapping
Section titled “Row → config key mapping”Each row drives a single Config slot. Useful when you want to find the matching TOML key:
| Row | Key |
|---|---|
| Line numbers | [ui] relative_line_numbers + [ui] line_numbers (3-state) |
| Cursor line | [ui] cursor_line |
| Scrollbar | [ui] scrollbar |
| Syntax highlighting | [ui] syntax |
| Show whitespace | [ui] show_whitespace |
| Bracket rainbow | [ui] bracket_rainbow |
| Highlight trailing whitespace | [ui] highlight_trailing_ws |
| Statusline clock | [ui] clock |
| Highlight word under cursor | [ui] highlight_word_under_cursor |
| Soft wrap | [ui] wrap |
| Sticky scope context | [ui] sticky_context |
| Inline markdown rendering | [ui] render_markdown |
| Auto-open markdown preview | [ui] auto_md_preview |
| Palette / picker position | [ui] picker_position |
| Scrolloff | [ui] scrolloff |
| Sidescrolloff | [ui] sidescrolloff |
| Color column | [ui] color_column |
| Theme | [ui] theme |
| Cmdline popup border color | [ui] cmdline_popup_border_color |
| File tree width | [ui] tree_width |
| File-tree image preview | [ui] tree_image_preview |
| Now-playing source | [ui] now_playing_source |
| Input style | [editor] input_style |
| Tab width | [editor] tab_width |
| Trim trailing whitespace on save | [editor] trim_trailing_ws_on_save |
| Auto-pair brackets / quotes | [editor] auto_pair |
| Auto-indent on Enter | [editor] auto_indent |
| Format on save (LSP) | [editor] format_on_save |
| Inlay hints | [editor] inlay_hints |
| Code lens | [editor] code_lens |
| Breadcrumb | [editor] breadcrumb |
| Restore open buffers on launch | [session] restore |
The major sections
Section titled “The major sections”[ui] — chrome and visual knobs
Section titled “[ui] — chrome and visual knobs”[ui]theme = "onedark" # any of mnml's 94 base46 themestheme_toggle = "gruvbox" # optional second theme for the 1-press sliderascii_icons = false # fallback glyphs when not running a Nerd Fonttree_width = 30 # file-tree rail width (clamped 10..=80)
# Gutter / cursor decorationline_numbers = true # master switch for the line-number gutterrelative_line_numbers = false # hybrid relative numbers (cursor line is absolute)cursor_line = false # paint a subtle tint on the cursor's rowscrollbar = true # 1-col vertical scrollbar on each editor panescrolloff = 0 # keep cursor N rows from the viewport edgessidescrolloff = 0 # horizontal counterpartshow_whitespace = false # `·` for space, `→` for tabbracket_rainbow = false # cycling colors on matched bracketshighlight_trailing_ws = false # red background on trailing whitespace cellscolor_column = 0 # subtle marker at column N (0 = off; 80 for the classic hint)wrap = false # soft-wrap long lines instead of clippingsyntax = true # tree-sitter highlighting master switch
# Statusline / chrome extrasclock = true # `HH:MM` chip in the statuslinehighlight_word_under_cursor = falseauto_md_preview = false # open the rendered preview when opening any `.md`render_markdown = false # inline markdown rendering in the editor panesticky_context = false # enclosing scope chain at the pane topmd_image_rows = 12 # rows reserved for markdown image embedspicker_position = "center" # or "top" — where the palette / picker anchorsnow_playing_source = "auto" # "auto" | "mixr" | "macos" — statusline ♪ miniplayertree_image_preview = true # hover-preview card for image files in the rail
# Pty-tab auto-namingticket_prefixes = ["TE-", "MIX-", "PROJ-"] # see belowtheme is the active palette; theme_toggle (optional) is the other member of a light/dark pair. The slider button in the bufferline flips between them on click; when theme_toggle is unset, the slider falls back to opening the full theme picker. See the Themes page for the list.
ticket_prefixes
Section titled “ticket_prefixes”When set (e.g. ["TE-", "MIX-"]), pty session tabs that don’t have a user-set name auto-fill their label from the most-recently-mentioned ticket token in the session’s visible scrollback. Useful for Claude Code / Codex sessions discussing a specific ticket — the tab strip shows TE-1234 instead of claude code without a manual :rename. Empty (default) disables the scan entirely.
The launcher-icon strips
Section titled “The launcher-icon strips”mnml has two icon strips driven from [ui] arrays:
[[ui.launcher_icon]]— colored chips on the right edge of the bufferline. Defaults to empty.[[ui.integration_icon]]— plain glyphs in the rail’s INTEGRATIONS row (under GIT). Defaults to Claude Code, Codex, Bitbucket, Jira, HTTP, CodeBuild, GitHub, Azure DevOps, GitLab, S3, CloudWatch Logs, Amplify, DynamoDB, Lambda, EventBridge — one chip per first-party sibling that ships as a default. Each fires:term <binary>so installing the matching sibling is all it takes to make the chip work.
Both share the same shape:
[[ui.launcher_icon]]id = "myapp" # stable identifierglyph = "\u{F0668}" # nerd-font glyphfallback = "MA" # ASCII fallback for --ascii / ascii_icons = truecommand = ":term myapp" # leading `:` ⇒ ex-cmdline; # no prefix ⇒ mnml command idcolor = "teal" # orange / cyan / blue / green / yellow / purple / red / teal / bg2tooltip = "My private Pty-pane app" # optional hover textSetting the array replaces the built-in defaults — copy the defaults from the source if you want to extend rather than replace.
Update check
Section titled “Update check”[ui]check_updates = true # default — opt out by setting to falseOn launch, mnml spawns a background thread that does one HTTP GET against https://api.github.com/repos/chris-mclennan/mnml/releases/latest. If the response’s tag_name differs from CARGO_PKG_VERSION (the version baked into the running binary), mnml fires a one-shot toast on the next tick — “v0.1.3 available — github.com/chris-mclennan/mnml/releases/tag/v0.1.3”.
A few details worth knowing:
- Background, non-blocking. The HTTP call runs on a fresh
std::thread; mnml never waits for it. The editor is usable from the first frame regardless of network state. - One toast per session. An
AtomicBoolflips on first surface, so the toast can’t re-fire after you dismiss it. - String-equality, not semver. mnml compares the tag verbatim. A dev build whose
Cargo.tomlruns ahead of the latest tag won’t trigger the toast; a build whose version matches the tag while having unreleased local changes won’t either. False-positives are limited to “tag bumped but the dev version still matches the old tag” — rare in practice. - Opt out: set
[ui] check_updates = falseand the background thread never spawns. No network call, no toast. - Skipped automatically in
--headlessmode. No toast surface and no statusline chip, so the check is a no-op there even whencheck_updatesistrue.
Source: src/update_check.rs (the background fetch + the shared UpdateCheck handle) and src/main.rs (the gate that decides whether to spawn it).
File-tree image hover-preview
Section titled “File-tree image hover-preview”[ui]tree_image_preview = true # default — set to false to disable entirelyWhen the file-tree cursor sits on an image file (PNG / JPEG / GIF / WebP / BMP) for ≥250 ms, mnml paints a small thumbnail card at the bottom of the rail. The card is 14 cols × 5 rows (1 caption + 4 image rows) and renders via whichever inline-image protocol your terminal speaks.
A few details worth knowing:
- Auto-detected protocol. mnml probes for Kitty graphics, iTerm2 inline images, and sixel at startup. The first one it finds wins;
nonefalls back to a plain “no protocol” placeholder. - Override via env. Set
MNML_IMAGE_PROTOCOL=kitty|iterm2|sixel|noneto skip detection and force a specific protocol (or disable images entirely). Useful for terminals that report support inaccurately, or for testing the fallback path. See the environment variables section below. - Hover-preview, not inline. The card paints over the bottom of the rail rather than expanding the row of the hovered file. This keeps the tree’s
1 logical row = 1 terminal cellinvariant intact — no cursor / scroll / click math to rewrite. The tradeoff: thumbnails for many images at once aren’t possible in this design (the rail would have to grow variable-height rows). - Sync decode. Image bytes are read + decoded on the main thread. Mnml only triggers the preview when a stable cursor has been on the row for ≥250 ms, so flick-scrolling through the tree doesn’t fire decodes.
- Opt out: set
[ui] tree_image_preview = falseand the preview machinery is skipped entirely — no decode, no paint, no debounce timer.
Skipped automatically in --headless mode (no surface for inline image escape sequences).
Now-playing source
Section titled “Now-playing source”[ui]now_playing_source = "auto" # "auto" (default) | "mixr" | "macos"The statusline ♪ miniplayer chip can read from two sources — the sibling mixr DJ app (via the ~/.mixr/quick.txt flat file it writes on track changes) and the macOS Music / Spotify apps (via an osascript AppleScript poll). now_playing_source picks which:
"auto"(default) — try mixr first (a cheap file read), fall back to macOS Music / Spotify when mixr is idle. The “show whatever’s actually playing” mode."mixr"— only read mixr. Skips the macOSosascriptpoll entirely, which is useful if you don’t use Music or Spotify and want to shave the only non-trivial cost off the now-playing poller."macos"— only read macOS Music / Spotify. Skips the mixr file read. Useful if you don’t run mixr.
The matching row is in the settings overlay under ── UI ── as Now-playing source with options auto / mixr / macos. The chip itself is hidden when nothing is playing — switching sources doesn’t toggle visibility, just which player gets queried.
Mouse polish
Section titled “Mouse polish”A few mouse-surface fixes worth knowing — none of them are settings (there’s nothing to toggle), but they show up across the chrome and were enough of a “wait, that should work” feeling to call out:
- Right-click while a context menu is open retargets it. Right-clicking on the bufferline used to require two clicks when a menu was already showing (the first closed it, the second opened a fresh one). The handler now cancels the open menu and falls through to dispatch the new right-click in one go — one click, one menu, at the new position.
- The vertical scrollbar is visible. Previously the track and thumb used two close background colors that visually melted on subtle themes (onedark / catppuccin / kanagawa). The track is now a
│glyph in grey overbg2; the thumb is█incommentcolor. Themes are unchanged — the contrast comes from the glyph shapes and fg-vs-bg channel separation. - Save-prompt buttons read as buttons. The
[S]ave/[D]iscard/[C]ancelbracket-mnemonic style relabeled toSave/Discard/Cancel(doubled padding for the button-box shape) with the hotkey letter underlined. The bg-color treatment (blue for the default,bg2for the others) was already there; the relabel lets it read as a row of buttons instead of bracket-noisy text. - The tree-resize handle is 3 cells wide. The hit-rect for “grab the tree’s right edge to resize” used to be a single cell — fiddly with a trackpad. It now spans 1 cell inside the rail + 2 outside, so the cursor lands on it without precision aiming.
- More tooltips. The activity-bar icons, the statusline
♪now-playing chip, and the palette-bar back / forward / dropdown chevrons all have hover tooltips. The activity-bar tooltip pulls its label from each section’s existingmeta()so it stays in sync with the click action; the now-playing tooltip renders<source>: <track>so you see both player and song without truncate-guessing.
[editor] — editing behavior
Section titled “[editor] — editing behavior”[editor]input_style = "standard" # or "vim" — picks the input layertab_width = 4 # min 1autosave_secs = 0 # auto-save N secs after last edit; 0 = offautosave_on_focus_loss = false # save dirty buffers on buffer/pane switch
# Indent / pair / wrap behaviorauto_indent = true # carry leading whitespace on Enterauto_pair = false # `(` inserts `()` with cursor betweentext_width = 80 # target width for `gqq` reflow
# Save-time transformstrim_trailing_ws_on_save = falseensure_trailing_newline = true # POSIX file conventionformat_on_save = false # textDocument/formatting before each saveformat_on_type = false # textDocument/onTypeFormatting on `}` / `;` / `\n`will_save_wait_until = false # eslint --fix / organize-imports hook
# LSP-driven decorationsinlay_hints = true # type / parameter chipscode_lens = true # `5 references` / `Run | Debug` chipssemantic_tokens_viewport = false # range-only tokens for very large filesbreadcrumb = true # workspace-relative path above each editor paneThe full list is in the Editing manual and the LSP manual — this is the on-disk surface.
[keys.global], [keys.vim], [keys.standard]
Section titled “[keys.global], [keys.vim], [keys.standard]”Keymaps are key spec → command id (the same shape as VSCode’s keybindings.json). The reverse direction is awkward — a key can only do one thing — and this lets "ctrl+p" = "none" cleanly unbind a default.
[keys.global]"ctrl+p" = "picker.files""ctrl+shift+p" = "picker.commands""alt+`" = "term.toggle"
[keys.vim]" ff" = "picker.files" # leader+ff (space prefix)" gs" = "git.status_pane"
[keys.standard]"ctrl+s" = "file.save""ctrl+/" = "editor.toggle_comment"[keys.global]applies always.[keys.vim]and[keys.standard]overlay it for that input style — so you can give the same chord different meanings in each mode.- Unknown command ids are tolerated — they just never fire (handy when sharing a config across mnml versions).
- Unbinding: set the value to
"","none", or"unbound"to drop a default binding.
The full command-id catalog lives in the command palette (Ctrl+Shift+P or :) — every command shows its id alongside its label. Defaults are documented in the Keybindings reference.
Shortcut to get started: run :keys.edit (or pick Customize keybindings… from the palette) — opens config.toml, jumps the cursor into [keys.standard], and appends a documented stub with three worked examples (rebind / add / unbind) if the section doesn’t exist yet. Use :Maps (or :Keys) at any time to dump the resolved chord → command table, optionally filtered: :Maps git shows every chord whose bound command id starts with git.
[lsp.<server>] — language servers
Section titled “[lsp.<server>] — language servers”Each [lsp.<name>] table layers on top of the built-in default of the same name. Partial overrides fall through field by field.
[lsp.rust]cmd = "rust-analyzer"args = []extensions = ["rs"]root_markers = ["Cargo.toml"]language_id = "rust"
[lsp.lua]cmd = "lua-language-server"extensions = ["lua"]root_markers = [".luarc.json", "stylua.toml"]See the LSP manual for the field reference and the list of servers mnml ships defaults for.
Git-host integrations — moved to mnml-forge-* siblings
Section titled “Git-host integrations — moved to mnml-forge-* siblings”The in-tree Bitbucket / GitHub / GitLab / Azure DevOps live panes were split out of mnml core in 2026-06 into four standalone sibling binaries — mnml-forge-bitbucket, mnml-forge-github, mnml-forge-gitlab, mnml-forge-azdevops — each hosted in a regular mnml pane via :term <binary>. Each forge sibling reads its own config from ~/.config/mnml-forge-<host>.toml and its own credentials from ~/.config/mnml-forge-<host>/token. See the integration class overview for the model.
Existing [bitbucket], [github], [gitlab], [azdevops] sections in your mnml config are silently ignored — no error, no warning. You can either delete them or leave them in place; they’re noise to mnml now. The new shape lives in each forge sibling’s own per-binary config file.
Mnml’s default config still seeds four launcher chips in the rail’s INTEGRATIONS row (bitbucket, github, gitlab, azdevops) that fire :term mnml-forge-<host> — install whichever siblings you use and click the chip to open the viewer.
Cross-host PR workflow
Section titled “Cross-host PR workflow”mnml ships palette commands and whichkey chords that fan out across whichever forge siblings you have installed. See the Cross-host PR workflow page for the full picture.
| Command | Chord (vim) | Action |
|---|---|---|
pr.picker | <leader>P p | Cross-host fuzzy picker — Enter opens URL, Tab cross-navs to the matching pipeline / build |
pr.refresh | <leader>P r | Background re-fetch of the cross-host PR cache (5-min TTL) |
forge.open_bitbucket | <leader>i b | :term mnml-forge-bitbucket |
forge.open_github | <leader>i g | :term mnml-forge-github |
forge.open_gitlab | <leader>i l | :term mnml-forge-gitlab |
forge.open_azdevops | <leader>i z | :term mnml-forge-azdevops |
forge.open_codebuild | <leader>i c | :term mnml-aws-codebuild |
All commands are chord-bindable via [keys.global] / [keys.vim] / [keys.standard] if you want different bindings than the whichkey defaults.
[ai] and [http]
Section titled “[ai] and [http]”The [ai] and [http] tables are parsed-and-kept: mnml accepts whatever shape your file has and won’t error. A small fixed set of keys are read directly today (listed below); the rest is held in the table for forward compatibility.
- AI providers — keys (
ANTHROPIC_API_KEY,OPENAI_API_KEY, …) read from the environment at request time, not from config. AI pane preferences (which model, default prompt) live in workspace state, not TOML. See the AI panes manual. - HTTP requests — defined per-file (
.http/.curl/.rest/.chain.json) under your workspace. Environment selection is a runtime concept (mnml run FILE --env staging), not a config key. See the HTTP client manual.
[ai] keys actively read
Section titled “[ai] keys actively read”[ai]# Direct-API agent loop — write tools (str_replace, create_file, …)api_write_tools = true # default: onapi_write_confirm = true # default: on — confirm before each write
# Direct-API agent loop — shell tool (sh -c in workspace cwd)api_shell_tools = false # default: OFF — strictly opt-inapi_shell_confirm = true # default: on — confirm before each callapi_shell_toolsenables ashell_exectool on the agent loop. The agent can run arbitrarysh -c <command>calls in the workspace directory — 60-second timeout, 256 KB combined stdout/stderr cap, return shape<stdout>…</stdout><stderr>…</stderr><exit>N</exit>. Default off because this is the most-dangerous tool surface the AI track exposes; turn it on only in workspaces where you trust the prompt-to-shell chain.api_shell_confirmblocks each individualshell_execcall until you approve it via the standardpending_tool_confirmflow. Default on. Setting this tofalsewhileapi_shell_tools = truegives the agent unattended shell access — fine for sandboxed throwaway workspaces; not a fit for anything you’d commit from.- The matching
api_write_tools/api_write_confirmpair work the same way for write tools (str_replace, create_file, …). Defaults: writes on, confirm on.
The \image directive
Section titled “The \image directive”Prepend \image <path> (or \img <path>) lines at the start of an AI prompt to attach images as base64 message content. The directives are stripped from the prompt text before send.
\image screenshots/login-broken.png\image docs/wireframe.jpgWhy does the login button overlap the password field at 320 px?- Supported formats: PNG / JPEG / GIF / WebP.
- Per-image cap: 5 MB (after decode, before base64). Larger files surface a per-line error in the response stream rather than blocking the prompt.
- Paths are resolved relative to the current workspace. Absolute paths work too.
- Only the leading block of
\imagelines is consumed — once the parser hits a non-directive line, the rest of the prompt passes through untouched. So a\imageinside the question body (or inside a code block) renders literally; that’s the escape hatch if you ever want to talk about the directive itself.
Works on both the streaming + agent-loop paths (stream_to_channel and agent_to_channel).
A future [ai] default_model = "..." / [http] default_env = "..." shape is reserved; the current values for those keys are tolerated for forward compatibility.
Environment variables
Section titled “Environment variables”A small set of runtime knobs live in environment variables rather than the TOML config — useful for one-shot overrides without touching disk.
| Variable | Values | Purpose |
|---|---|---|
MNML_IMAGE_PROTOCOL | kitty / iterm2 / sixel / none | Skip terminal-protocol auto-detection and force a specific inline-image protocol. Affects markdown image embeds, the file-tree hover-preview, and the image-pane viewer. none disables image rendering entirely. |
XDG_CONFIG_HOME | path | When set, mnml reads $XDG_CONFIG_HOME/mnml/config.toml instead of ~/.config/mnml/config.toml. (Standard XDG basedir spec.) |
MNML_STARTUP_PICKER | 1 | Same as --startup-picker — open the JetBrains-style chooser on launch. See the Startup picker manual. |
MNML_IMAGE_PROTOCOL is the most-commonly-useful one in day-to-day use:
# Force sixel even on a terminal mnml thinks supports Kitty graphicsMNML_IMAGE_PROTOCOL=sixel mnml
# Disable all image rendering (no inline images, no hover-preview)MNML_IMAGE_PROTOCOL=none mnmlWorkspaces — [[workspaces]]
Section titled “Workspaces — [[workspaces]]”mnml’s file-tree rail shows the launched workspace at the top. To pin additional workspaces as collapsible sibling sections in the rail:
[[workspaces]]name = "work"path = "~/Projects/work-stuff"
[[workspaces]]path = "~/Projects/mnml-family" # name defaults to "mnml-family" (basename)~is expanded at config-load time.namedefaults to the path’s basename when omitted.- Entries append across config files (so a workspace-local file can add siblings).
- Missing directories are tolerated — mnml logs and skips them rather than failing to start.
Each workspace gets its own Tree, its own discovered repos, and its own git status reader. Switching between workspace roots is a click in the rail; nothing reloads.
Resetting
Section titled “Resetting”Three levels of reset, smallest to largest:
- Per-row — open the overlay, focus the row, press
r. Just that one setting reverts to its built-in default. - All settings (live) — focus the Reset all to defaults sentinel row at the bottom of the overlay and press
Enter(orRanywhere in the overlay). The liveConfigis rewritten toConfig::default(). The pre-open snapshot is still held, soEscwould un-revert if you change your mind before pressingEnteron a normal row. - All settings (on disk) — delete
~/.config/mnml/config.tomland<workspace>/.mnml/config.toml. mnml falls back to built-in defaults on the next start.
Reset only touches keys the overlay exposes — [[workspaces]], [[bitbucket.repos]], [keys.*], etc. are untouched by Reset all.
- Editing — the
[editor]knobs in context - LSP —
[lsp.<name>]field reference - Git — what the git-host dashboards do once configured
- HTTP client — request files and environment selection
- AI panes — Claude Code / Codex integration