Skip to content

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.

mnml reads two files at startup, lowest to highest precedence:

  1. Global~/.config/mnml/config.toml (or $XDG_CONFIG_HOME/mnml/config.toml when XDG_CONFIG_HOME is set).
  2. Per-workspace overlay<workspace>/.mnml/config.toml, relative to whatever directory you launched mnml in.
  3. Explicitmnml --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.toml

A few sections ([[workspaces]], [[ui.integration_icon]], …) have their own merge rules — workspaces append across files; integration-icon arrays override the built-in defaults and installed manifests field-by-field (see Installing integrations → precedence). Those quirks are called out below. The old [[ui.launcher_icon]] array was retired in 2026-08; every launcher chip is now an [[ui.integration_icon]] entry.

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.

Changes do persist. Each row writes its new value straight into <workspace>/.mnml/config.toml — the per-project overrides file — the moment you adjust it, whether by /, by clicking an option, or by resetting the row (a reset writes the default as an explicit override, which is the per-project intent). You don’t need to hand-edit TOML to make a setting survive a restart.

Two things work differently:

  • startup.default_workspace is inherently global — “which folder to open” isn’t a per-project answer — so it’s written to ~/.config/mnml/config.toml, and only on the Enter save path.
  • Esc restores the workspace file from the snapshot taken when the overlay opened, comments and all, so cancelling really does undo the writes your row edits already made.

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’s step, clamped to [min, max]. The hint in dim text — (min–max · step N · default D) — tells you what each press will do.
  • r resets 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.

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)
  • Enter on a focused text row enters edit mode (text rows don’t cycle on Enter the way discrete rows do).
  • While editing: printable keys append to the buffer · Backspace drops the trailing character · Enter commits + exits · Esc restores the value the row had when you started editing and exits.
  • The value is live-written to the in-memory Config on every keystroke (via apply_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.
  • r resets just this row’s setting to its built-in default (via apply_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

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 hex to the dim hint suffix.
  • Same edit-mode shape as text rows: Enter enters greedy edit mode, printable keys append, Backspace deletes, Enter commits, Esc cancels + restores. Each keystroke live-writes through apply_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). r still 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.integration_icon]], [[marketplace.source]], [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).

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
Hover-help info box [ui] hover_help
Workspace dots (● / ○) [ui] show_workspace_dots
Menu bar visibility [ui] menu_bar
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
[ui]
theme = "onedark" # any of mnml's 94 base46 themes
theme_toggle = "gruvbox" # optional second theme for the 1-press slider
ascii_icons = false # fallback glyphs when not running a Nerd Font
tree_width = 30 # file-tree rail width (clamped 10..=80)
# Gutter / cursor decoration
line_numbers = true # master switch for the line-number gutter
relative_line_numbers = false # hybrid relative numbers (cursor line is absolute)
cursor_line = false # paint a subtle tint on the cursor's row
scrollbar = true # 1-col vertical scrollbar on each editor pane
scrolloff = 0 # keep cursor N rows from the viewport edges
sidescrolloff = 0 # horizontal counterpart
show_whitespace = false # `·` for space, `→` for tab
bracket_rainbow = false # cycling colors on matched brackets
highlight_trailing_ws = false # red background on trailing whitespace cells
color_column = 0 # subtle marker at column N (0 = off; 80 for the classic hint)
wrap = false # soft-wrap long lines instead of clipping
syntax = true # tree-sitter highlighting master switch
# Statusline / chrome extras
clock = true # `HH:MM` chip in the statusline
highlight_word_under_cursor = false
auto_md_preview = false # open the rendered preview when opening any `.md`
render_markdown = false # inline markdown rendering in the editor pane
sticky_context = false # enclosing scope chain at the pane top
md_image_rows = 12 # rows reserved for markdown image embeds
picker_position = "center" # or "top" — where the palette / picker anchors
now_playing_source = "auto" # "auto" | "mixr" | "macos" — statusline ♪ miniplayer
tree_image_preview = true # hover-preview card for image files in the rail
# Pty-tab auto-naming
ticket_prefixes = ["TE-", "MIX-", "PROJ-"] # see below
# Chrome polish
menu_bar = "always" # "always" | "auto" | "hidden" — see Menu bar page
hover_help = false # Ableton-style info box at bottom of left panel
show_workspace_dots = true # ● / ○ markers on workspace-root rows

Docks an info box at the bottom of the left panel that describes whatever the mouse (or keyboard focus) is on — chip / menu item / tree row / tab / pane summary. Off by default so the tree gets those six rows.

Runtime toggle: view.toggle_hover_help, :set hh / :set nohh / :set hh!, or the View → Toggle hover-help strip menu item. Full surface in Hover-help.

Turns the ● / ○ markers on workspace-root rows in the tree on or off. On by default. Runtime toggle: view.toggle_workspace_dots, :set wsdots / :set nowsdots / :set wsdots!, the View → Toggle workspace dots menu item, or right-click on any workspace-root row → Hide / Show workspace dots. See Workspaces → Workspace dots.

Cycles the chrome-row menu bar between always (words visible, click / Alt+letter / F10 all work), auto (hidden until summoned via Alt+letter, F10, or mouse-at-top-row), and hidden (paint and accelerators both off — palette-only flow). Runtime toggle: view.menu_bar_cycle or the View → Cycle menu bar entry. See Menu bar for the full surface.

theme 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. The full list of built-in themes lives in the theme picker (Ctrl+Shift+Ptheme.pick).

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.

All integration chips — rail chips, palette-bar chips, everything with a glyph and a click action — come from one array: [[ui.integration_icon]]. The old [[ui.launcher_icon]] split was retired in 2026-08. Whether a chip renders in the rail’s INTEGRATIONS section or in the palette bar depends on the entry’s in_palette_bar field.

The three built-in defaults (baked into mnml core in src/config.rs) are browser, claude_code, and codex. Everything else comes from manifests you install (either via the Marketplace tab or via <sibling> --install) — mnml doesn’t carry a hardcoded list of ~35 first-party siblings anymore.

[[ui.integration_icon]] blocks in your config are thin overrides, not new definitions. The entries are matched by id against built-ins and installed manifests, and user config is authoritative for these fields only:

  • enabled — whether the chip is on the Installed tab (default false for everything except browser)
  • in_palette_bar — whether the chip renders in the palette bar

The rest — glyph, fallback, color, command, label — comes from the built-in or installed manifest with matching id. For richer per-chip customization use the sidecar <id>.override.toml file (usually written by the right-click Edit… overlay) — see Installing → override sidecar semantics. To add a fresh chip from scratch use the launcher.add_local palette command or drop a launcher manifest under ~/.config/mnml/integrations/.

# Turn on the Claude Code chip and pin it to the palette bar.
[[ui.integration_icon]]
id = "claude_code"
enabled = true
in_palette_bar = true
# Enable a manifest-installed integration.
[[ui.integration_icon]]
id = "postgres" # matches ~/.config/mnml/integrations/postgres.toml
enabled = true

Entries with ids that match nothing (no built-in, no installed manifest) are dropped. See Installing integrations → precedence for the full merge rules.

For the “manifest as authored” schema (used when you’re writing a ~/.config/mnml/integrations/<id>.toml file), see Launcher manifests.

[marketplace]
enabled = true # master switch; default true
cache_ttl_secs = 3600 # cache lifetime, default 1h
use_defaults = true # merge shipping sources with yours
[[marketplace.source]]
type = "github_launcher_folder"
id = "acme"
repo = "acme-corp/mnml-tools"
path = "launchers"

Full reference: Marketplace.

[ui]
check_updates = true # default — opt out by setting to false

On 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 AtomicBool flips 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.toml runs 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 = false and the background thread never spawns. No network call, no toast.
  • Skipped automatically in --headless mode. No toast surface and no statusline chip, so the check is a no-op there even when check_updates is true.

Source: src/update_check.rs (the background fetch + the shared UpdateCheck handle) and src/main.rs (the gate that decides whether to spawn it).

[ui]
tree_image_preview = true # default — set to false to disable entirely

When 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; none falls back to a plain “no protocol” placeholder.
  • Override via env. Set MNML_IMAGE_PROTOCOL=kitty|iterm2|sixel|none to 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 cell invariant 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 = false and 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).

[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 macOS osascript poll 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.

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 over bg2; the thumb is in comment color. 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]ancel bracket-mnemonic style relabeled to Save / Discard / Cancel (doubled padding for the button-box shape) with the hotkey letter underlined. The bg-color treatment (blue for the default, bg2 for 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 existing meta() 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]
input_style = "standard" # or "vim" — picks the input layer
tab_width = 4 # min 1
autosave_secs = 0 # auto-save N secs after last edit; 0 = off
autosave_on_focus_loss = false # save dirty buffers on buffer/pane switch
# Indent / pair / wrap behavior
auto_indent = true # carry leading whitespace on Enter
auto_pair = false # `(` inserts `()` with cursor between
text_width = 80 # target width for `gqq` reflow
# Save-time transforms
trim_trailing_ws_on_save = false
ensure_trailing_newline = true # POSIX file convention
format_on_save = false # textDocument/formatting before each save
format_on_type = false # textDocument/onTypeFormatting on `}` / `;` / `\n`
will_save_wait_until = false # eslint --fix / organize-imports hook
# LSP-driven decorations
inlay_hints = true # type / parameter chips
code_lens = true # `5 references` / `Run | Debug` chips
semantic_tokens_viewport = false # range-only tokens for very large files
breadcrumb = true # workspace-relative path above each editor pane

The 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.

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 — installable via the Marketplace

Section titled “Git-host integrations — installable via the Marketplace”

The in-tree Bitbucket / GitHub / GitLab / Azure DevOps live panes were split out of mnml core in 2026-06 into standalone sibling binaries — each hosted in a regular mnml pane via :term <binary>. The four sibling binaries themselves (mnml-forge-bitbucket, mnml-forge-github, mnml-forge-gitlab, mnml-forge-azdevops) are no longer maintained as first-party public repos; they’re community-published now, and the way you find them is via the Marketplace tab. Any mnml-forge-<host> crate published with the mnml-integration keyword appears there.

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 (~/.config/mnml-forge-<host>.toml) and each sibling’s own credentials file (~/.config/mnml-forge-<host>/token).

The forge.open_* palette commands that mnml used to register (one per forge / cloud / database sibling) were removed in 2026-08 alongside the ecosystem consolidation — each installed sibling now brings its own <id>.open command via IntegrationManifest.commands, so duplicating them in mnml core would just collide with the manifest-registered dynamic commands. Chord bindings that used to point at forge.open_bitbucket etc. come from the manifest now.

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)

Individual per-forge launcher commands (opening the mnml-forge-github viewer, jumping to mnml-aws-cloudwatch-logs, and so on) come from the manifest of whichever sibling you’ve installed via the Marketplace. Chord binding is up to the manifest author; user-config [keys.global] / [keys.vim] / [keys.standard] blocks can override per binding. Each expects the matching sibling binary on $PATH; missing binaries render the chip dim with a (binary not installed) suffix.

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]
# Direct-API agent loop — write tools (str_replace, create_file, …)
api_write_tools = true # default: on
api_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-in
api_shell_confirm = true # default: on — confirm before each call
  • api_shell_tools enables a shell_exec tool on the agent loop. The agent can run arbitrary sh -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_confirm blocks each individual shell_exec call until you approve it via the standard pending_tool_confirm flow. Default on. Setting this to false while api_shell_tools = true gives 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_confirm pair work the same way for write tools (str_replace, create_file, …). Defaults: writes on, confirm on.

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.jpg
Why 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 \image lines is consumed — once the parser hits a non-directive line, the rest of the prompt passes through untouched. So a \image inside 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.

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:

Terminal window
# Force sixel even on a terminal mnml thinks supports Kitty graphics
MNML_IMAGE_PROTOCOL=sixel mnml
# Disable all image rendering (no inline images, no hover-preview)
MNML_IMAGE_PROTOCOL=none mnml

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.
  • name defaults 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.

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 (or R anywhere in the overlay). The live Config is rewritten to Config::default(). The pre-open snapshot is still held, so Esc would un-revert if you change your mind before pressing Enter on a normal row.
  • All settings (on disk) — delete ~/.config/mnml/config.toml and <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