vibe
Authoritative reference for Mistral Vibe — the CLI agent you (the model) are running inside. LOAD when the user: - asks anything about Vibe itself, even by indirect name ("this CLI", "this tool", "you"); - wants to change, inspect, or reset their setup; - asks why the agent did or did not act; - asks how to make the CLI do X, where X lives, or what a flag/command/setting does; - asks any meta question about your own behavior; - is unsure whether a command, flag, env var, or file is in scope — this skill is the source of truth. SCOPE: config under `~/.vibe/` and project-local `.vibe/`; `VIBE_*` and `LOG_*` env vars; models and providers; agents and subagents; skills; tools and their permission model; every slash command and CLI flag; hooks; MCP servers; connectors; plugins; trusted folders; `@`-file mentions; logs; themes; voice.
- License 许可
- Apache-2.0
- 最近更新
- 2026-11-11
- Stars
- ★ 5.4k
- Forks
- 796
01 前言 Front Matter
- name
- vibe
- description
- Authoritative reference for Mistral Vibe — the CLI agent you (the model) are running inside. LOAD when the user: - asks anything about Vibe itself, even by indirect name ("this CLI", "this tool", "you"); - wants to change, inspect, or reset their setup; - asks why the agent did or did not act; - asks how to make the CLI do X, where X lives, or what a flag/command/setting does; - asks any meta question about your own behavior; - is unsure whether a command, flag, env var, or file is in scope — this skill is the source of truth. SCOPE: config under `~/.vibe/` and project-local `.vibe/`; `VIBE_*` and `LOG_*` env vars; models and providers; agents and subagents; skills; tools and their permission model; every slash command and CLI flag; hooks; MCP servers; connectors; plugins; trusted folders; `@`-file mentions; logs; themes; voice.
- user-invocable
- false
03 SKILL.md 正文
Vibe CLI Self-Awareness
You are running inside Mistral Vibe, a CLI coding agent built by Mistral AI. This skill gives you full knowledge of the application internals so you can help the user understand, configure, and troubleshoot their Vibe installation.
Going Deeper
For facts not covered here, fetch the README:
https://github.com/mistralai/mistral-vibe/blob/main/README.md
It tracks the latest release, so check vibe --version before relying on it for
anything version-specific. Point the user at
https://docs.mistral.ai/vibe/code/overview for human-readable docs.
VIBE_HOME
The user's Vibe home directory defaults to ~/.vibe but can be overridden via
the VIBE_HOME environment variable. All user-level configuration, skills, tools,
agents, prompts, logs, and session data live here.
Directory Structure
~/.vibe/
config.toml # Optional user configuration, created on first saved setting
hooks.toml # User-level hook definitions
.env # API keys and credentials (dotenv format)
vibehistory # Command history
trusted_folders.toml # Trust database for project folders
connector_bootstrap_cache.json # Short-lived connector discovery cache
agents/ # Custom agent profiles (*.toml)
prompts/ # Custom prompts (*.md)
skills/ # User-level skills (each skill is a subdirectory with SKILL.md)
tools/ # Former custom Python tools, no longer loaded (see below)
logs/
vibe.log # Main log file
session/ # Session log files
~/.agents/
skills/ # Additional user-level skills directory
Project-Local Configuration
When in a trusted folder, Vibe also looks for project-local configuration:
.vibe/config.toml- Project-specific config (overrides user config).vibe/hooks.toml- Project-specific hooks (requires trusted folder).vibe/skills/- Project-specific skills.vibe/tools/- Former project-specific custom Python tools, no longer loaded (see below).vibe/agents/- Project-specific agents.vibe/prompts/- Project-specific prompts.agents/skills/- Standard agent skills directory
Custom Python tools are no longer supported: Vibe does not load the .py files
in ~/.vibe/tools/ or .vibe/tools/, and reports each one as a config issue.
At startup Vibe also removes the former tool_paths setting from
config.toml; the folders it listed stay on disk. Recommend skills for new
extensions. When a user asks for migration help, inspect the custom tool's
behavior and replace it with an equivalent skill.
AGENTS.md Discovery
AGENTS.md files provide directory-scoped instructions to the model. At startup,
Vibe loads ~/.vibe/AGENTS.md and every AGENTS.md from the project root up
through the trust chain. AGENTS.md files in subdirectories are discovered
lazily: when read_file reads a file below the project root, any AGENTS.md
between the file's parent and the project root is injected into
context.
Lifecycle: Exit, Update, Version, Resume
Exit
Chat input (case-insensitive): /exit, exit, quit, :q, :quit.
Keyboard: Ctrl+C / Ctrl+D — press twice within ~1s to quit. For Ctrl+C,
the first press instead interrupts the running job or clears the input if either
is present. Set ask_confirmation_on_exit = false to make Ctrl+D quit on the
first press (also toggleable in /config); Ctrl+C always requires a second
press. Ctrl+Z suspends on POSIX (resume with fg).
Prompt History
Up/Down recall previous prompts (stored in ~/.vibe/vibehistory, newest 100
kept). Ctrl+R opens a reverse-i-search: field in the bottom bar: the input
previews the newest prompt containing the query (case-insensitive), Up/Ctrl+R
move to older matches and Down/Ctrl+S to newer ones, Enter keeps the match
in the input without sending it (Ctrl+Z undoes), Esc/Ctrl+C restore the draft.
Update
Vibe never updates silently. With enable_update_checks = true (default), it
checks for a newer mistral-vibe daily and prompts on the next launch when
one exists. uv tool installs ask uv tool list --outdated, so uv settings such
as exclude-newer apply; other installs, and uv older than 0.10.10, poll PyPI. Accepting runs
uv tool upgrade mistral-vibe, then brew upgrade mistral-vibe as a fallback. On
Windows, accepting prints the upgrade command to run after closing every Vibe
session instead, since a running Vibe locks its own files. Disable via enable_update_checks = false. Run vibe update (equivalent to vibe --check-upgrade) to check
immediately, prompt to install a newer version if one exists, and exit. Initial
install: uv tool install mistral-vibe.
Version
vibe --version (or -v) prints it and exits. Not shown anywhere in-session.
Resume
vibe -c/--continue: the last session used in this terminal in the current folder (it moves on a new session's first message, or when a saved session is reopened); otherwise the folder's most recent session. Works with-ptoo. The terminal is the tty device on macOS/Linux, the console window orWT_SESSIONon Windows;VIBE_TERMINAL_KEYoverrides it, and an empty value means "no terminal" (folder rule only).vibe --resume [SESSION_ID]: specific session; without an id, opens a picker.- In-session:
/resume(alias/continue).
Session titles
Each session has a title stored in meta.json (with title_source: auto or
manual). A session stays untitled until a background LLM call generates a
concise descriptive title (the --resume list shows a message preview until
then). Automatic generation runs only for the interactive CLI; other clients
(ACP, app server, programmatic) keep their own session management and fall back
to the message preview. Title generation runs on a small fast Mistral model when the
deployment is known to serve one — Vibe asks it once, with a one-token request
at session open, and remembers the answer in ~/.vibe/utility_model_cache.json
— and on the session's active model otherwise. utility_models.title pins a
specific model instead. The first title waits for the opening turn to finish (or a few
model steps) so it isn't generated off a thin tool-call preamble. On that cheap
fast model it also refreshes periodically and
after each compaction; when it falls back to the (possibly expensive) active
model it stays bounded — one title at the start plus one after a compaction, a
couple at most — so a large model isn't re-invoked every few turns. The refresh
keeps the opening intent and the latest exchange in view and feeds the previous
title back so it refines rather than restarts. /rename <title> sets a manual
title that auto-generation never overwrites. Set session_logging.generate_titles = false to turn automatic titles off (the --resume list and tab then use the
message preview). The current title also drives the terminal tab/window title
(OSC), updated on rename, auto-title changes, and resume; it never blocks a turn.
Session storage & folder scoping
Local sessions are written under ~/.vibe/logs/session/ (override with
session_logging.save_dir). Each session records the cwd it ran in. The
/resume picker, --continue, and bare --resume (no id) are scoped to the
current folder: only sessions whose cwd matches where Vibe is launched are
listed, so the same directory shows its own history and nothing else. Switch
folders to see a different set. The explicit --resume <SESSION_ID> form is
not folder-scoped: it resolves the session by id regardless of which folder
it ran in.
The first user message pins the resolved model alias to the session. Resuming
keeps that model even if the configured default changes. /model uses the same
config persistence target as other settings and also updates an existing
session override immediately; the session file is synchronized when the next
user message is sent. An explicit persistent target selected through /config
changes only that layer. /clear starts an unpinned conversation that follows
the current config again. If a selected model is no longer configured, Vibe
falls back to the current default model.
Each user message also pins the running agent, including one chosen with
--agent. Resuming or continuing keeps that agent, even if the configured
default agent changes or --agent names another one; switch agents in the
session to change it. If the pinned agent no longer exists, Vibe falls back to
--agent or the default agent, and keeps that one from then on.
Configuration (config.toml)
The configuration file uses TOML format. When it does not exist, Vibe uses its
built-in defaults and creates a sparse file on the first persisted setting.
Settings can also be overridden via environment variables with the VIBE_
prefix (e.g., VIBE_ACTIVE_MODEL=my-local-model).
Custom prompt IDs are resolved from project-local .vibe/prompts/ first, then
from ~/.vibe/prompts/, and finally from the built-in bundled prompts.
Settings Vibe does not use
Vibe checks ~/.vibe/config.toml, the trusted project .vibe/config.toml and
the active agent profile for settings it does not use: a misspelt key (with the
closest setting name), a retired setting such as include_model_info, or a
setting with no effect such as [tools.grep]. Each one is a warning at startup,
and vibe -p prints it on stderr.
To refuse a config with such settings instead, set strict_config = true in
config.toml or VIBE_STRICT_CONFIG=1 in the environment; Vibe then stops with
the list. VIBE_STRICT_CONFIG takes a boolean: 1, true, yes or on to
refuse, 0, false, no or off to warn; any other value is refused too.
vibe config schema prints a JSON schema of every setting, including each
built-in tool's settings, for an editor to validate config.toml against.
Key Settings
# Model selection
active_model = "mistral-medium-3.5" # Model alias to pin; omit or set "" to follow the server-routed default
# UI preferences
theme = "auto" # Follow terminal background, then OS light/dark preference
disable_welcome_banner_animation = false
cursor_blink = true # Blink the input cursor; false keeps it steady
vim_keybindings = false # Vim Normal and Insert modes in the prompt input
autocopy_to_clipboard = true # Enable automatic copying of selected text to clipboard
enable_mouse_capture = true # Capture the mouse for scrolling, clicks and selection; false keeps the terminal's native selection
enable_sticky_prompt = true # Pin the prompt of the section at the top of the transcript while scrolling
enable_file_autocomplete = true # Offer @ file completion; off skips the file index entirely
file_watcher_for_autocomplete = true # Refresh @ suggestions and pick up skill changes after workspace changes
ask_confirmation_on_exit = true # Require a second Ctrl+D to quit (Ctrl+C always confirms)
show_greeting = true # Show "Hello {name}" greeting below the banner at startup (Mistral providers, once per 24h)
log_level = "WARNING" # Optional. DEBUG | INFO | WARNING | ERROR | CRITICAL — log level for ~/.vibe/logs/vibe.log
cursor_blink controls the main input cursor. Change it through /config, or
edit config.toml and run /reload; no restart is needed.
enable_sticky_prompt pins the prompt of the conversation section at the top of
the transcript once it scrolls off; the next section's prompt pushes it out. It
shows at most three rows, the last one cut with …, and turns off below 15
transcript rows. A prompt a scheduled loop sent pins as its folded title
(⏵ title), without its instructions or loop line. Clicking it
scrolls the transcript back to that prompt. Set it to false through /config,
or edit config.toml and run /reload; no restart is needed.
vim_keybindings adds a subset of vim to the prompt input. It starts in Insert
mode, labelled INSERT on its bottom border with a bar caret; Esc enters Normal
mode, unlabelled with a block caret.
In Normal mode Esc interrupts a running turn and Esc Esc on an empty prompt
opens rewind; it never clears the draft. Normal mode supports
h l w b e 0 ^ $ gg G j k, i a I A o O, x X D C dd cc, d/c with
w e b 0 ^ $ (Esc or Enter cancels a pending d/c), u and Ctrl+R (redo, not history search); / or ! on an empty prompt opens that mode.
In Insert mode Esc enters Normal mode, so Ctrl+C interrupts a running turn.
Other shortcuts (Ctrl+A, Ctrl+G, ...) work in both modes. There is no visual
mode, yank/paste, count or : command.
Copy and Text Selection
- Copy shortcuts:
Ctrl+YandCtrl+Shift+Cboth copy the current selection to the clipboard. On macOS and Linux, with nothing selected,Ctrl+Yyanks (pastes back) the text the lastCtrl+K,Ctrl+Shift+K,Ctrl+U,Ctrl+W,Alt+Dor word delete removed from the input box. Each kill replaces the previous one. When autocopy is enabled (default), releasing the mouse over a selection also copies automatically, as does a drag that ends without a release (the terminal losing focus, or a key other thanEsc, which aborts the drag without copying). Each successful copy flashes a brief inline "Copied to clipboard" notice. - Multi-click selection: Double-click selects a word, triple-click selects the current paragraph; dragging extends the selection at the same granularity. Clicking a collapsible transcript entry (tool group, tool call, reasoning) toggles it on every click; drag to select its text.
- Terminal-native selection:
enable_mouse_capture = false(orVIBE_ENABLE_MOUSE_CAPTURE=false) turns mouse capture off, so the terminal's own selection and copy work instead of in-app selection; depending on the terminal, some other mouse interactions such as clicks are disabled too. It applies live from/configor/reload. Inside a tmux session whosemouseoption is off (the default before tmux 3.8), Vibe behaves as if this setting werefalse, re-reading the option at startup and when resuming from suspend or an external editor. Native selection only covers the visible screen;Shift+Up/Downalways scrolls the transcript. In some terminals the mouse wheel then sends Up/Down keys, which recall prompt history in the input box. - Warnings: Config issues, config validation warnings, and the warnings the
app server reports before the first turn (an MCP server or connector that
failed to load, a model catalogue problem) are listed together in one
Warningsbox at the top of a new conversation (below the history of a resumed one). Once a turn has started, each new warning appears as its own⚠line in the conversation. Errors always appear as their own✗line. - Clipboard tools: Copies use
pbcopyon macOS andclipon Windows; on Linux they trywl-copy, thenxclip, thenxsel. Vibe always also sends the text through OSC 52, and over SSH it relies on OSC 52 alone. Inside tmux, it instead runstmux load-buffer -wto fill a tmux paste buffer and forward the text to the attached terminal's clipboard. If tmux'sset-clipboardisoff, or the terminal lacks theMscapability, it falls back to OSC 52 passthrough. When no native copy succeeds, the notice readsCopied · <hint>instead (auth URL copies readAuth URL copied · <hint>). On local Linux the hint isif paste fails, Shift+select or install xclip; elsewhere, including over SSH, it isif paste fails, Shift+select (Option in iTerm2, Fn in Terminal.app). Shift+select uses the terminal's native selection.
# Behavior
bypass_tool_permissions = false # Skip tool approval prompts
outside_workdir = "ask" # Outside-workdir file/shell access: "ask" prompts, "deny" refuses with a tool error
worktree_copy_on_write = true # macOS: clone new worktrees of a trusted repo from a template checkout; sets extensions.worktreeConfig in the repo
enable_pr_monitoring = true # Track the session's PRs/MRs (gh/glab): detect created ones, find existing ones for the branch, keep their status fresh
system_prompt_id = "cli" # System prompt: "cli", "lean", or custom .md filename
compaction_prompt_id = "compact" # Compaction prompt: built-in "compact" or custom .md filename
enable_telemetry = true
enable_update_checks = true # Daily update check (uv or PyPI); prompts on next launch when a newer release exists
enable_notifications = true
enable_system_trust_store = false # Use OS trust store for outbound HTTPS
api_timeout = 720.0 # API request timeout in seconds
api_retry_max_elapsed_time = 300.0 # Retry budget for retryable API failures in seconds
api_stream_idle_timeout = 300.0 # Silence after the model starts answering that counts as a stall, in seconds; 0 allows api_timeout
# auto_compact_threshold = 200000 # optional fallback for models without their own threshold; unset: derived from each model's context window
compaction_reasoning_limit_tokens = 32000 # Reasoning after which a compaction summary is stopped and asked for once more, briefly, without tools and with reasoning off or lowest; 0 disables
output_limit_retries = 6 # Answers cut off at the output limit that a turn retries with feedback; 0 ends the turn on the first
agent_max_output_tokens = 0 # Output cap sent as max_tokens on every agent and subagent request, on any provider, held to the model's max output and to the window's room above the compaction threshold; 0 sends one only where the provider requires one (the model's max_output_tokens)
compaction_max_output_tokens = 0 # Output cap sent as max_tokens on every compaction request, on any provider, held the same way; 0 sends one only where the provider requires one
# Git commit behavior
include_commit_signature = true # Add "Co-Authored-By" to commits
# System prompt composition
include_project_context = true # Include project context (git info, cwd) in system prompt
# Voice features
voice_mode_enabled = false
narrator_enabled = false
active_transcribe_model = "voxtral-realtime"
active_tts_model = "voxtral-tts"
OpenTelemetry Tracing
Set enable_otel = true to export traces for agent, model, and tool operations
over OTLP/HTTP. enable_telemetry must also be enabled. With no explicit
endpoint, Vibe derives the telemetry endpoint and API key from the configured
Mistral provider, except public regional API hosts that do not serve telemetry.
To use another collector, set otel_endpoint to its base URL; Vibe appends
/v1/traces. Configure custom-collector authentication through the standard
OTEL_EXPORTER_OTLP_* environment variables.
otel_redaction controls client-side span attribute redaction: default
redacts sensitive values, strict redacts sensitive attributes entirely, and
none disables redaction. Use none only for a collector trusted to receive
potentially sensitive prompt, response, and tool data.
enable_otel = true
otel_endpoint = "https://collector.example.com:4318"
otel_redaction = "default"
Providers
[[providers]]
name = "mistral"
api_base = "https://api.mistral.ai/v1"
api_key_env_var = "MISTRAL_API_KEY"
backend = "mistral"
[[providers]]
name = "llamacpp"
api_base = "http://127.0.0.1:8080/v1"
api_key_env_var = ""
extra_headers = { "X-Custom-Header" = "value" } # optional per-provider HTTP headers
emits_finish_reason = false # set false for OpenAI-compatible endpoints that end
# streams without a finish reason; avoids spurious
# "incomplete stream" errors and retries (default true)
Models
# Restrict selectable models by their canonical API names, not their aliases.
# Glob and "re:" patterns, matched in full. The only way to hide server-routed
# models; a hidden routed default falls back to the catalog recommendation,
# then Mistral Medium 3.5, then the first allowed model. Keep one pattern per
# model: a pattern matching
# nothing warns — the drift signal; do not merge them to silence it.
allowed_models = ["mistral-vibe-cli-*"]
[[models]]
name = "mistral-vibe-cli-latest"
provider = "mistral"
alias = "mistral-medium-3.5"
temperature = 1.0
input_price = 1.5
output_price = 7.5
cached_input_price = 0.15 # per million cached input tokens; omit to bill at input_price
thinking = "high" # "off", "low", "medium", "high", "xhigh", "max", or a level `thinking_levels` lists; sent to the provider as written. Any other word still loads and is sent, with a config warning; pick another level to fix it
# thinking_levels = ["off", "high"] # levels /thinking offers, lowest first (plus `thinking` when outside them); any word the provider takes, e.g. ["low", "turbo"], sent as written (on a Mistral provider, the Mistral SDK leaves out a level its own list lacks, such as "turbo", and the config warns). Wins over the provider's catalogue and the known-model table, which still say whether the model takes an effort or a token budget (a budget model has budgets for the standard levels only; another word sends no thinking, with a warning). Omit to offer the model's known levels (else the default five) plus `thinking`, [] for off only. Including "off" claims the model accepts a no-reasoning request (mistral provider)
# auto_compact_threshold = 200000 # optional; unset: derived from the context window; a value outside the range the window allows is moved into it, with a warning
supports_images = true # vision-capable; allows @-mentioned images
[[models]]
# A user-defined local model served by llama.cpp; the built-in list ships only
# the hosted Mistral Medium 3.5.
name = "devstral"
provider = "llamacpp"
alias = "my-local-model"
# Optional override. A non-vision active model already picks up any
# supports_images model on its OWN provider automatically; set this only to
# point somewhere else, which is also the only way to cross providers. Ignored
# whenever the active model has supports_images = true -- that model sees the
# image itself.
[vision_model]
name = "mistral-vibe-cli-latest"
provider = "mistral"
alias = "vision"
supports_images = true # required
# Optional: the model behind each background helper -- session titles and the
# smart-approve classifier. A value is an alias from [[models]], or "active" for
# the session's own model (unless a model is aliased "active"). Unset, a helper
# uses a small fast Mistral model when the deployment serves one (checked when
# a session opens with the feature on, then cached) and the session's model
# otherwise, which costs more per call.
[utility_models]
title = "local"
smart_approve = "active"
Tool Configuration
# Enable only specific tools (glob and regex supported)
enabled_tools = ["bash", "read_file", "grep"]
# Disable specific tools after enabled_tools filtering
disabled_tools = ["web_fetch"]
# Per-tool configuration
[tools.bash]
allowlist = ["git", "npm", "python"]
[tools.git_bash]
permission = "ask"
shell = "C:\Program Files\Git\bin\bash.exe"
[tools.powershell]
permission = "ask"
shell = "powershell.exe"
Two tool settings have no effect, and Vibe reports each as a config issue (in
the startup Warnings box in the TUI, a warning in the -p export):
[tools.grep](for exampleexclude_patterns): there is nogreptool. The agent searches through the shell, sorgandgrepcommands follow the shell tool's settings ([tools.bash]on POSIX).- A
prompts/task.mdin a tool directory (.vibe/tools/prompts/task.md): it overrode the description of the formertasktool. Subagents are started with the built-inagents.spawntool, whose description cannot be overridden; put delegation guidance inAGENTS.mdor in the agent's instructions instead.
The built-in shell surface is controlled by the managed_shell_tools_enabled config
field and the vibe_cli_managed_shell_tools GrowthBook experiment. The default variant
keeps the legacy one-shot bash tool, including its existing Windows behavior.
The managed variant exposes OS-native shell tools:
POSIX systems, including WSL where Vibe runs as Linux, get managed bash,
bash_output, bash_stdin, bash_sessions, and bash_log_file; native Windows
gets git_bash, git_bash_output, git_bash_stdin, git_bash_sessions, and
git_bash_log_file when Git Bash is available. If Git Bash is unavailable,
native Windows falls back to powershell, powershell_output,
powershell_stdin, powershell_sessions, and powershell_log_file.
Managed shell sessions return a session_id, inline output, a cursor for polling
more output, and a log path under ~/.vibe/shell-tool/sessions/. Long-running
commands can be left alive with background = true, and interactive commands can
be driven with the matching stdin tool.
Commands Vibe runs on the host (shell tools, background processes and !
commands) get VIBE_SESSION=1 in their environment, so a script can tell it
runs under Vibe. Commands in a sandbox use the sandbox's own environment.
POSIX bash reads permissions, allowlists, and denylists from [tools.bash].
Native Windows git_bash reads them from [tools.git_bash]; native Windows
powershell reads them from [tools.powershell]. Neither Windows tool reads
[tools.bash]. Git Bash is preferred when Vibe can resolve a usable bash.exe
from PATH, Git for Windows, or standard Git install locations. If Git Bash is
unavailable, the PowerShell resolution order is pwsh.exe, then
powershell.exe. cmd.exe is not used by the managed Windows shell tools.
Output polling uses byte offset cursors
(cursor / next_cursor), max_bytes caps per-call inline output, and
max_inline_bytes configures the default cap.
Special case — find command: Even if find is in the bash allowlist,
Vibe detects -exec, -execdir, -ok, and -okdir predicates and will
prompt for user permission before running the command.
Outside paths: a shell command naming a path outside the working directory
(cat /etc/hosts, ls ../other, or a workdir symlink pointing out such as
cat link) asks for that path, even when the command is allowlisted or the
shell is permission = "always"; outside_workdir = "deny" refuses it instead.
/dev/null and the standard streams do not count.
File Tool Permission Resolution
File-based tools (read, grep, write_file, edit) resolve
permissions in this order (first match wins):
- Scratchpad path → always allowed
- denylist glob match → always denied
- allowlist glob match → always allowed
- sensitive_patterns match → requires approval
- Outside workdir → requires approval, or denied if
permission = "never"oroutside_workdir = "deny" - Default → uses the tool's
permissionsetting
The denylist is checked before the allowlist — a path matching both lists is denied. Both are checked before the outside-workdir boundary, so the allowlist can still auto-approve access to directories outside the project.
Outside-Workdir Access
outside_workdir covers file and shell calls reaching outside the working
directory and its --add-dir directories: "ask" (default) prompts, "deny"
refuses with a tool error and the turn continues. Set it globally or per tool:
outside_workdir = "deny"
[tools.read_file]
outside_workdir = "ask" # per-tool override
allowlist = ["vibe-path:directory_recursive:/abs/path/to/shared"] # stays allowed
- Allowlisted paths,
--add-dirdirectories and the scratchpad are never refused.permission = "always"does not bypass the check. - Shells only check paths they can read statically, such as the arguments of
cat,cporls. Anything else (interpreter code, dynamic paths, relative paths after acdin the same command, such ascd sub && cat linkwithlinkpointing out) follows the shell's normal permission, so a shell that must stay inside should not bealways. - On Windows a call is refused if any shell tool denies it: override both
git_bashandpowershell. PowerShell treats/dev/nullas a path; use$null. - Auto-approve skips the check, like the denylists.
Skill Configuration
# Additional skill search paths
skill_paths = ["/path/to/custom/skills"]
# Enable only specific skills
enabled_skills = ["vibe", "custom-*"]
# Disable specific skills
disabled_skills = ["experimental-*"]
Agent Configuration
# Additional agent search paths
agent_paths = ["/path/to/custom/agents"]
# Enable/disable agents
enabled_agents = ["ask", "accept-edits"]
disabled_agents = ["auto-approve"]
# Forbid the auto-approve (YOLO) mode entirely: hides the "auto-approve" agent
# (and any custom agent with safety = "yolo") and ignores --auto-approve/--yolo.
# Per-tool permissions still apply.
# Intended for admin-managed (enterprise) config.
enable_auto_approve = false
# Opt-in builtin agents (only affects agents with install_required=True, e.g. lean)
installed_agents = ["lean"]
# Agent profile to use when --agent is not passed
# (default: "accept-edits"). Valid values: "ask", "accept-edits",
# "auto-approve", "lean" (only when listed in installed_agents), or any
# custom agent name from ~/.vibe/agents/ or .vibe/agents/. Subagents
# (e.g. "explore") are rejected. Applies in both interactive and programmatic
# (-p/--prompt) mode.
default_agent = "ask"
MCP Servers
Remote MCP servers can be added non-interactively from the shell:
vibe mcp add mistralai \
--url https://api.mistral.ai/mcp \
--transport streamable-http \
--api-key-env MISTRAL_API_KEY
vibe mcp add linear \
--url https://mcp.linear.app/mcp
vibe mcp remove mistralai
Static auth is selected when --api-key-env or --header is provided.
Otherwise the server uses OAuth and starts browser login by default. Pass
--no-login to only persist the OAuth configuration. Run
vibe mcp add --help for all supported authentication and timeout options.
Use vibe mcp remove <name> to remove a server from the user configuration;
stored OAuth credentials are deleted when available.
vibe mcp add NAME --url URL adds remote servers, including static
authentication with --api-key-env, --api-key-header, --api-key-format,
and repeatable --header NAME=VALUE.
Add local standard input/output (stdio) servers with
vibe mcp add NAME --transport stdio --command COMMAND, repeatable
--arg VALUE (use --arg=-y for flags), and --env NAME=VALUE.
A string stdio command is split into words like a shell line; on native
Windows a backslash is a path separator, not an escape, so quote a path that
contains spaces: command = '"C:\Program Files\tool\tool.exe" --stdio'.
Set --startup-timeout-sec and --tool-timeout-sec for either transport.
The legacy vibe mcp add URL --name NAME form is also accepted.
Shell add and remove NAME update user config without a chat session;
remove is not a slash command.
Hosted OAuth MCP servers can also be added from inside Vibe:
/mcp add https://mcp.linear.app/mcp
/mcp add https://mcp.example.com/mcp --name docs --scope read --transport http --no-login
/mcp add is OAuth-only. It writes auth.type = "oauth" with optional
scopes and starts login by default. It uses transport = "streamable-http"
unless you pass --transport http. Pass --no-login to add the server without
starting OAuth login. The shortcut supports streamable-http and http
transports.
[[mcp_servers]]
name = "my-server"
transport = "stdio"
command = "npx"
args = ["-y", "@my/mcp-server"]
[[mcp_servers]]
name = "remote-server"
transport = "http"
url = "https://mcp.example.com"
[mcp_servers.auth]
type = "static"
api_key_env = "MCP_API_KEY"
api_key_header = "Authorization"
api_key_format = "Bearer {token}"
[[mcp_servers]]
name = "linear"
transport = "streamable-http"
url = "https://mcp.linear.app/mcp"
[mcp_servers.auth]
type = "oauth"
scopes = ["read", "write"]
# Optional: client_id = "pre-registered-public-client"
# Optional: client_metadata_url = "https://example.com/client-metadata.json"
# Optional: redirect_port = 47823
HTTP MCP servers can use either static auth or OAuth:
- Static auth: legacy
api_key_env/headerskeys still work and are promoted toauth.type = "static"internally. - OAuth auth: use
auth.type = "oauth"withscopes. Vibe stores tokens in the OS keyring undermcp-oauth:<alias>:tokens, dynamic client info undermcp-oauth:<alias>:client_info, and config drift fingerprints undermcp-oauth:<alias>:fingerprint. - Headless environments without an OS keyring cannot store OAuth tokens; use
static auth via
api_key_envinstead. - For SSH/remote browser callbacks, forward the loopback port:
ssh -L 47823:127.0.0.1:47823 <host>.
MCP sampling is disabled by default. It requires an explicit per-server opt-in and a host-provided sampling completion. Vibe's app server does not currently provide that completion, so it does not advertise or service sampling requests.
Connectors
Mistral connectors are auto-discovered when the active provider is Mistral and the API key env var is set. Toggle the master switch or hide individual connectors / tools:
The Unified Harness enables ready connectors by default in memory. It does not write that default to TOML, and the master switch plus explicit connector, tool, allowlist, and denylist settings always take precedence.
enable_connectors = true # Master switch (default: true)
[[connectors]]
name = "github"
disabled = true # Hide all tools from this connector
[[connectors]]
name = "linear"
disabled_tools = ["delete_issue"] # Hide selected tools only
Session Logging
[session_logging]
enabled = true
save_dir = "" # Defaults to ~/.vibe/logs/session
session_prefix = "session"
generate_titles = true # Background LLM session titles; false uses the message preview
Browser Sign-In
Browser sign-in lets users authenticate through the browser during onboarding.
Mistral providers use default browser sign-in URLs (console.mistral.ai /
api.mistral.ai). Custom or renamed providers must configure both URLs:
[[providers]]
browser_auth_base_url = "https://console.mistral.ai"
browser_auth_api_base_url = "https://console.mistral.ai/api"
Split-horizon deployments (e.g. behind an SAP Cloud Connector) expose the
console under a virtual host the CLI reaches but the server does not know about,
so it returns sign-in/poll URLs on its own public host. Set
browser_auth_allow_origin_rewrite = true on the provider to re-home the
returned URLs onto the configured browser_auth_base_url /
browser_auth_api_base_url origins instead of rejecting them for an origin
mismatch (path traversal is still rejected). Only the origin (scheme, host,
port) is rewritten; the returned URL's path must already sit under the
configured base path, so the connector must preserve the server's path prefix.
Point browser_auth_base_url at the browser-reachable console and
browser_auth_api_base_url at the CLI-reachable API host. The interactive
onboarding wizard's custom-domain screen also accepts an optional browser-auth
API base URL and auto-enables this rewrite when its origin differs from the
console origin:
[[providers]]
browser_auth_base_url = "https://console.internal.example"
browser_auth_api_base_url = "https://connector.internal.example:443/api"
browser_auth_allow_origin_rewrite = true
The same split-horizon setups often reach the API through a proxy whose URL
differs from the tenant's public API host that /whoami advertises. By
default, sign-in and the runtime config self-heal adopt the whoami-advertised
api_base. Set preserve_api_base = true on the provider to keep the
configured api_base verbatim across sign-in and /reload; the chat URL
(vibe_base_url) adoption is unaffected:
[[providers]]
api_base = "https://connector.internal.example:443/v1"
preserve_api_base = true
Self-hosted deployments can point Vibe CLI upgrade and API-key links to their Le Chat web deployment, where the Vibe API key is managed. The same URL serves the admin-managed config:
vibe_base_url = "https://chat.mistral.ai"
When no config layer sets vibe_base_url and the Mistral provider's api_base
is https://api.<domain>/..., Vibe uses https://chat.<domain> instead of
the default. For example, api_base = "https://api.example.com/v1" gives
https://chat.example.com. Any other api_base, including Mistral's regional
api.eu.mistral.ai and api.us.mistral.ai, keeps the default.
Interactive setup can target a Mistral-compatible deployment instead of the
default console.mistral.ai / api.mistral.ai. The final credential is always a
Mistral API key. On the auth-method screen pick Launch browser, then
Other on the sign-in-target screen, and enter a login domain to complete
browser sign-in. This sets browser_auth_base_url (the entered domain) and
derives browser_auth_api_base_url (DOMAIN/api). The overridden mistral
provider is persisted to user config so subsequent runs reuse it.
The wizard reads any custom browser_auth_base_url already in config.toml:
choosing Other pre-fills that configured domain so it can be confirmed or
edited. Choosing Mistral AI while a custom domain is configured warns first
and requires pressing Enter again to confirm the reset to the default
domain, which is then persisted.
Hooks
Hooks let users run shell commands automatically at lifecycle events. They
are always available — no flag is required; dropping a hooks.toml in place
is enough.
Config and hook types
Hooks live in hooks.toml files (separate from config.toml), discovered in
this order:
<project>/.vibe/hooks.toml— loaded first, only when the folder is trusted.~/.vibe/hooks.toml— loaded second.
Hooks shipped by native Vibe plugins (ai.mistral.vibe/hooks.toml) run too.
For one event, hooks run in this order: project hooks.toml, project plugins,
~/.vibe/hooks.toml, then user plugins. A plugin hook runs in the session's
read-only copy of its plugin with PLUGIN_ROOT and PLUGIN_DATA set, like the
plugin's MCP servers; its payload's cwd is still the session folder. Hooks imported from Claude Code or Kimi plugins do
not run yet: Vibe reports them as a config issue.
A duplicate name across the two files is reported as a config issue and the
project entry wins. Config-load errors (invalid TOML, missing required
fields) surface in the TUI as warnings and the offending hook is skipped.
[[hooks]]
name = "lint" # Required: unique within the file.
type = "post_agent" # Required: see the table below.
command = "eslint --quiet ." # Required: shell command run in cwd.
timeout = 60.0 # Default: 60s for all hooks.
description = "Run ESLint" # Optional.
[[hooks]]
name = "build-after-reviewer"
type = "post_agent"
match = "reviewer" # Agent matcher on post_agent (default "*").
command = "make check"
[[hooks]]
name = "deny-rm-rf"
type = "pre_tool"
match = "re:bash|process_start" # Tool-name matcher on tool hooks (default "*").
strict = true # pre_tool, post_tool, pre_agent: escalate any failure to block.
command = "uv run python /path/to/guard-bash"
[[hooks]]
name = "add-branch"
type = "pre_agent"
match = "main" # Agent matcher, as on post_agent.
command = "~/.vibe/hooks/add-branch.sh"
[[hooks]]
name = "notify-me"
type = "notification"
match = "re:approval_requested|turn_completed" # Notification-type matcher.
command = "~/.vibe/hooks/notify.sh"
[[hooks]]
name = "load-context"
type = "session_start"
match = "startup" # Source matcher: startup | resume | clear | fork.
command = "jq -Rs '{hook_specific_output: {additional_context: .}}' .vibe/context.md"
[[hooks]]
name = "export-transcript"
type = "session_end"
match = "exit" # Reason matcher: exit | clear | other.
command = "~/.vibe/hooks/export.sh"
| Type | When it runs |
|---|---|
pre_agent |
Once per turn, after the user sends a message and before the model reads it. |
post_agent |
Once per turn, after the agent finishes responding (no pending tool calls). |
pre_tool |
Per tool call, before the user permission prompt. |
post_tool |
Per tool call, iff the tool body actually ran. tool_status is success or failure; a shell command that exits non-zero is still success (read tool_output.returncode). Does not fire when the tool never executed (pre_tool denial, user denial at the approval prompt, permission NEVER, or cancellation before the body started). |
notification |
When Vibe needs the user or a turn ends: approval_requested (a tool call from any agent waits for approval), question_asked, turn_completed (the main agent's turn ended, including when a hook stopped it; with queued messages, only once the queue is empty), turn_failed. Never on an interrupt, nor when a turn ends while an approval or a question is still open. Runs in the background: Vibe never waits for it, its output is ignored, and it cannot approve anything. |
session_start |
When a main session opens. source: startup (a new session; a session Vibe opened ahead of time counts once its first message is sent), resume, clear (after /clear), fork (including a rewind). It runs once the replaced session's session_end hooks are done. The user can type at once; the first message waits for the hooks before it reaches the model. Never for subagents. |
session_end |
When a main session closes. reason: exit (Vibe quits, or Vibe Desktop releases the session), clear (/clear), other (another session replaced it, or it was deleted). Runs in the background, output ignored, after the session's own session_start hooks are stopped if they still run. Not after a crash or a kill. |
Closing. When Vibe quits (/exit, end of a vibe -p run, Vibe Desktop
quitting), it stops the session_start hooks still running at once (their
session is over), then waits for the session_end and notification hooks
still running, 5 s at most in all, then stops them. Vibe Desktop may close a
session's process sooner. Switching session (/new,
/resume, a rewind) does not stop the running hooks; the new session's first
message waits for the old session's session_end hooks.
Matcher syntax (same as enabled_tools): fnmatch glob by default
("bash", "read_*", case-insensitive), or a regex full-match when the
pattern starts with re: ("re:(read_file|write_file)").
Agent matching on post_agent. post_agent fires after the main agent's
turns and after every subagent's turns. Its match selects the agent that
finished its turn, by agent configuration name: the main session's current
agent (accept-edits, ask, a custom agent file name), a subagent's
config_name (e.g. reviewer, or <plugin-name>:<name> for a plugin agent), or,
for a subagent spawned without one, its parent's agent. The reserved alias main
matches the main session (root or fork) whatever its agent, and never a
subagent; no agent may be named main. A mode name also matches the subagents
that inherit it, so use match = "main" to run only after the main agent.
Without match (or with "*"), a post_agent hook runs for every agent. A
hook that does not match is skipped silently. pre_agent matches agents the
same way, for the agent whose turn starts.
Tool name conventions for matchers:
matchaccepts a tool's short name (bash,read_file,process_start, …) or its qualified name (file_system.bash). Thetool_namefield in the payload is always the qualified name (file_system.bash,mcp_<server>.<tool>for MCP tools), so scripts that read it must expect that form.- Commands the agent starts in the background go through
process_start, whichmatch = "bash"does not catch. Shell guards should usematch = "re:bash|process_start". - MCP tools:
mcp_{server}.{tool}, with characters other than letters, digits,_and$replaced by_in both parts (e.g.mcp_linear.create_issuefor thecreate-issuetool of thelinearserver). Match it asmcp_linear.create_issue,mcp_linear_create_issue, the barecreate_issue, or a glob such asmcp_linear.*. - Connector tools:
connector_{normalized-name}_{remote-tool-name}(e.g.connector_Google_Drive_search_files). - There is no
tasktool. Subagents are spawned throughagents_spawn(agents.spawnin the payload). Match withmatch = "agents_spawn"and readtool_input.config_name(absent for a generic subagent) to discriminate by subagent.
Subagent invocations inherit the parent's hook config. Their hook events are logged to the subagent's session log and don't propagate to the parent's UI.
Wire protocol
Every hook is spawned in cwd and receives a JSON object on stdin
discriminated by hook_event_name:
// pre_agent
{"hook_event_name": "pre_agent", "session_id": "...",
"parent_session_id": null, "transcript_path": "...", "cwd": "...",
"agent_name": "accept-edits", "turn_id": "turn-...",
"prompt": "fix the bug"} // the text the user typed; attachments are not included
// post_agent
{"hook_event_name": "post_agent", "session_id": "...",
"parent_session_id": null, "transcript_path": "...", "cwd": "...",
"agent_name": "accept-edits"} // agent configuration name; null when unknown
// notification
{"hook_event_name": "notification", "session_id": "...",
"parent_session_id": null, "transcript_path": "...", "cwd": "...",
"notification_type": "approval_requested", // | question_asked | turn_completed | turn_failed
"message": "Vibe needs your approval to run file_system.bash",
"agent_name": "accept-edits",
"tool_name": "file_system.bash"} // approval_requested only
// session_start
{"hook_event_name": "session_start", "session_id": "...",
"parent_session_id": null, "transcript_path": "...", "cwd": "...",
"source": "startup", // | resume | clear | fork
"agent_name": "accept-edits", "model": "mistral-medium-3-5"} // active model alias
// session_end
{"hook_event_name": "session_end", "session_id": "...",
"parent_session_id": null, "transcript_path": "...", "cwd": "...",
"reason": "exit"} // | clear | other
// pre_tool
{"hook_event_name": "pre_tool", "session_id": "...", "parent_session_id": null,
"transcript_path": "...", "cwd": "...",
"tool_name": "file_system.bash", "tool_call_id": "call_42",
"tool_input": {"command": "ls"}}
// post_tool
{"hook_event_name": "post_tool", "session_id": "...", "parent_session_id": null,
"transcript_path": "...", "cwd": "...",
"tool_name": "file_system.bash", "tool_call_id": "call_42",
"tool_input": {"command": "ls"},
"tool_status": "success", // success | failure; a non-zero exit code is still success
"tool_output": {"command": "ls", "stdout": "...", "stderr": "", "returncode": 0,
"was_truncated": false, "timed_out": false}, // structured result on success; null on failure
"tool_output_text": "...", // current text the LLM will see; mutable by prior hooks
"tool_error": null, // populated on failure
"duration_ms": 0} // not reported yet: always 0
parent_session_id is set when running inside a subagent. Exceeding
timeout kills the whole process tree.
A hook signals back via its exit code and stdout (stderr is reserved for diagnostics — Vibe never parses it for control):
| Exit | Stdout | Behavior |
|---|---|---|
0 |
empty | Pass through (no action). |
0 |
valid structured-response JSON object (schema below) | Act per the JSON fields. |
0 |
anything else (free-form text, broken JSON, scalar/array, schema mismatch) | Failure path (see below). The parse error is in the message. |
| non-zero / timeout / spawn failure | — | Failure path. Reason taken from stderr, then stdout, then the exit code. |
Structured-response schema:
{
"decision": "allow" | "deny", // optional; default "allow"
"reason": "string", // required when decision == "deny"
"system_message": "string", // optional UI note
"hook_specific_output": {
"tool_input": { ... }, // pre_tool only
"prompt": "string", // pre_agent only
"additional_context": "string" // post_tool, pre_agent and session_start only
}
}
notification and session_end hooks' stdout is ignored. A session_start
hook reads only additional_context and system_message: it cannot block the
session.
hook_specific_output.additional_context(session_start): text added, out of the user's sight, to the session's next user message, for the model. With severalsession_starthooks, their texts are joined in run order. If apre_agenthook blocks that message, the text waits for the next one.system_message(session_start): shown as the hook's line in the conversation (✓ [hook-name] message), not among the startup warnings.
Unknown fields are tolerated at every level. Fields that aren't meaningful for the current hook type are silently ignored.
Don't self-name in system_message or reason — the UI prefixes
hook-end-event content with [hook-name] automatically, and pre_tool
denials are wrapped as Tool 'X' was denied by hook 'Y': {reason} before
the LLM sees them. A hook that writes "reason": "guard: refused..."
will produce hook 'guard': guard: refused... downstream.
decision: "deny" per hook type:
| Hook | Effect of decision: "deny" |
|---|---|
pre_agent |
Block the message: the turn ends, the model never sees it, and the hook's line shows Blocked the message: <reason> (or the hook's system_message). First deny short-circuits the remaining pre_agent hooks. |
pre_tool |
Deny the tool call; reason is the tool error returned to the LLM. First deny short-circuits the remaining pre_tool hooks for this call. |
post_tool |
Replace tool_output_text with reason. Pipeline continues; subsequent hooks see the replacement. |
post_agent |
Inject reason as a retry user message. First deny short-circuits the remaining post_agent hooks. Capped at 3 retries per turn across all post_agent hooks; past the cap, the answer is accepted. |
Event-specific payloads:
hook_specific_output.tool_input(pre_tool): full replacement of the model's arguments. Vibe re-validates against the tool's schema after each rewriting hook — the first invalid rewrite aborts the chain and synthesizes a denial attributing the failure to that hook. Rewrites compose: hook N receivestool_inputas rewritten by hooks 1..N-1.hook_specific_output.additional_context(post_tool): text appended (with) to the currenttool_output_text. Composes with a same-hookdecision: "deny": deny replaces first, thenadditional_contextis appended to the replacement.hook_specific_output.prompt(pre_agent): replaces the text the user typed; attachments are kept. The model sees the new text; the user's transcript still shows what they typed. Rewrites compose: hook N receives thepromptas rewritten by hooks 1..N-1.hook_specific_output.additional_context(pre_agent): text added after the user's message, for the model only. It stays in the conversation for the following turns. Withpromptin the same response, the text is replaced first, then the context is added.
When a pre_agent hook rewrites the message or adds context, its line in the
transcript previews what the model gets instead (one line, cut at 80
characters), unless the hook returns its own system_message. The session
title is generated from what the model got as well: rewritten-away text and
blocked messages never reach the title model. A message steered into the turn
while its pre_agent hooks run waits for them to finish.
Failure path. Any failure (non-zero exit, timeout, spawn failure,
non-conforming stdout) emits a UI warning and lets the gated action proceed
(fail open). With strict = true:
| Hook | Strict failure escalates to |
|---|---|
pre_agent |
Block the message. |
pre_tool |
Deny the tool call with the failure reason. |
post_tool |
Clear tool_output_text (replace with empty). |
strict is forbidden on post_agent, notification, session_start and
session_end. A failing session_start hook shows a warning and the session
goes on without its context.
Execution semantics
- A hook
commandis a shell line, run in the session working directory. On macOS and Linux it runs in/bin/sh. On native Windows it runs in the same shell as thebashtool: Git Bash when it is installed, otherwise PowerShell. Write Windows hooks in that shell's syntax. - On macOS and Linux, what a hook starts in the background (
cmd &) is killed when the hook exits. A hook that must leave work running has to move it to a new session itself:setsid cmd &on Linux; macOS has nosetsidcommand, so useperl -MPOSIX -e 'POSIX::setsid(); exec @ARGV' cmd &. - Hooks of the same type fire sequentially in load order (project file first,
then user file; declaration order within each file).
notificationandsession_endhooks are the exception: they start together, in the background. - Tool calls within a single LLM turn run concurrently; each call's hook chain runs serially but the chains run in parallel across calls. Hooks that touch shared state (filesystem, env) must coordinate themselves.
pre_toolrewrites take effect everywhere downstream: the user permission prompt sees the rewritten arguments, the tool runs with them, and the assistant message is patched so subsequent LLM turns reflect what actually ran.
Pattern Matching
Tool, skill, and agent names support three matching modes:
- Exact:
"bash","read_file" - Glob:
"bash*","mcp_*" - Regex:
"re:^serena_.*$"(full match, case-insensitive)
CLI Parameters
vibe [PROMPT] # Start interactive session with optional prompt
vibe -p TEXT / --prompt TEXT # Programmatic mode using `default_agent`, one-shot, exit. Calls needing approval are denied, so an agent's `[tools.bash] allowlist` limits the shell to those commands
vibe -p TEXT --auto-approve # Programmatic mode with all tool calls approved
vibe -p TEXT --agent lean --yolo # Lean mode with all tool calls approved
vibe --agent NAME # Select agent profile (falls back to `default_agent` config)
vibe --auto-approve / --yolo # Approve all tool calls. New session on ask/accept-edits/smart-approve: starts the auto-approve agent. Custom agent or resumed session: keeps its agent, every call approved
vibe --workdir DIR # Change working directory
vibe --worktree NAME # Create/reuse a git worktree under $VIBE_HOME/worktrees on branch NAME and run inside it. Auto-cleanup only for worktrees Vibe created this run and only after a session started; reused worktrees and attached (pre-existing) branches are kept unless confirmed. -p sessions keep worktrees. Ignored with --setup/--check-upgrade.
vibe --worktree # Same, but Vibe picks an unused name from the prompt (a random slug when there is no prompt) on a vibe/<name> branch, and never reuses an existing worktree. The prompt must precede the flag or follow a `--`, since --worktree otherwise reads it as NAME.
vibe --add-dir DIR # Extra working dir loaded for context (repeatable). Implicitly trusted.
vibe --trust # Trust cwd for this invocation only (not persisted). Skips the trust prompt.
vibe -c / --continue # Continue this terminal's last session in cwd, else the latest in cwd
vibe --resume [SESSION_ID] # Resume a specific session
vibe -v / --version # Show version
vibe --setup # Run onboarding/setup
vibe update / vibe --check-upgrade # Check for a Vibe update now, prompt to install it, and exit
vibe --max-turns N # Max assistant turns (programmatic mode)
vibe --max-price DOLLARS # Max cumulative run cost, root + subagents; models priced 0 are free (programmatic mode)
vibe --max-tokens N # Max cumulative run tokens, root + subagents (programmatic mode)
vibe --system-prompt-file PATH # Replace Vibe's own system prompt instructions with PATH's text (programmatic mode)
vibe --model ALIAS # Model alias for this run, not saved (programmatic mode)
vibe --enabled-tools TOOL # Enable specific tools (repeatable)
vibe --disabled-tools TOOL # Disable specific tools (repeatable)
vibe --output text|json|streaming # Output format (programmatic mode)
Programmatic mode never prompts for approval: a tool call that needs it is
denied unless the run approves calls without a prompt. --auto-approve (or
--agent auto-approve) approves them, and --smart-approve lets a classifier
approve the ones it judges safe. Each denied call is printed on stderr as
Tool call denied: <tool> (<reason>) and listed in the export's warnings.
A text run whose agent finishes without an answer says so on stderr.
Built-in Agents
There are two kinds of agents:
- Agents are user-facing profiles selectable via
--agentorShift+Tab. They configure the model's behavior, tools, and system prompt. - Subagents are model-facing: the model can spawn them autonomously to delegate subtasks (e.g. exploring the codebase). Users cannot select subagents directly.
Agents
- ask: Requests approval for tool executions
- accept-edits: Default agent; auto-approves file edits but asks for other tools
- auto-approve: Auto-approves all tool calls
- lean: Specialized Lean 4 proof assistant. Not available by default — must be
installed with
/leanstall(removed with/unleanstall). Use--agent lean --auto-approveor--agent lean --yoloto run Lean mode without tool prompts.
Subagents
- explore: Read-only codebase exploration subagent with grep, file reading, and skill loading. Spawned by the model, not selectable by the user.
Custom agents are TOML files in ~/.vibe/agents/NAME.toml. One with
agent_type = "subagent" is a custom subagent the model can spawn. It can load
every skill the parent session has, unless its file sets enabled_skills or
disabled_skills; these accept the same patterns as in config.toml.
A custom subagent (agent_type = "subagent") runs with the session's hooks.
Each of its tool calls must pass both the session's rules and its own
[tools.NAME] rules (permission, allowlist, denylist): a profile can
refuse or prompt for what the session allows, never allow what the session
refuses or prompts for. enabled_tools/disabled_tools likewise only narrow
the session's tools. Approvals given to a subagent's prompts apply to the whole
session. Under --auto-approve, a subagent's calls run without consulting
allowlists or denylists, like the main agent's; a tool its profile sets to
never stays refused.
To use a custom subagent, the model must spawn it by its agent type
(config_name); a subagent spawned with only a name inherits the main agent's
configuration.
A subagent's instructions (instructions, or the prompt system_prompt_id
names) are limited to 65,536 characters; the primary agent's prompt has no such
limit. An agent file that cannot be used (invalid TOML, an unknown safety or
agent_type, an invalid setting, a missing prompt, instructions over the limit,
no description) is not offered to the model. Vibe reports it as a config issue
naming the file and the failing field: in the startup Warnings box in the TUI,
a stderr line and an export warning with -p. Spawning that type fails with
subagent_type_rejected and the same reason.
A custom subagent runs on the session's model unless its file sets
active_model to an alias from [[models]]; its children then run on that
model (name, temperature, thinking, provider, and the model's context window,
compaction threshold and image support). The provider's endpoint and
credentials always come from the session's [[providers]]: a provider the
agent file redefines is ignored for routing. An alias that is unknown, excluded
by allowed_models, or served by a provider the session does not define
rejects the agent file like the cases above; Vibe never falls back to the
session's model.
Built-in Slash Commands
/help- Show help message/config- Full-screen settings browser. Fields show their value and origin layer (default / TOML / env / override). Type to filter, arrows to move, Enter to edit; booleans toggle, closed-set fields (theme, models) pick from a list, scalars edit inline, complex fields open a JSON editor. The edit modal shows an inspector of the layers setting the field; edits persist to the user config (~/.vibe/config.toml) by default,Tabcycles the save target through the project config (.vibe/config.toml, when the project layer is active) and the ephemeral session override (until restart), andCtrl+Rclears the field one writable layer at a time toward the default. Thetoolsfield opens a grouped tool list with a per-tool config editor (permission, allow/deny lists,Ctrl+Efor raw JSON). Enabling/disabling whole MCP servers or connectors stays in/mcp./model- Select active model. The footer shows the active model and its thinking level (Name[level]) before the context gauge; clicking it opens/modeland keeps the prompt being typed (only while idle, like the typed command)./thinking- Select thinking level/theme- Select UI theme;autofollows terminal/OS appearance,mistral-darkandmistral-lightuse the Mistral brand palette (persisted in config)/reload- Reload configuration, agent instructions, and skills from disk (skills added or edited on disk are also picked up on their own whilefile_watcher_for_autocompleteis on, except in sandboxed sessions)/clear,/new- Start a new conversation. Optionally pass a prompt to seed it/log- Show path to current interaction log file/log-level- Show or set the log level./log-levelprints the full chain (session, env, config, effective);/log-level set <LEVEL>sets a process-lifetime override;/log-level set-global <LEVEL>also persists to config.toml;/log-level unsetclears the session override. LEVEL is one of DEBUG, INFO, WARNING, ERROR, CRITICAL./debug- Toggle debug console/compact- Compact model context by summarizing. The session ID and visible conversation stay intact; the auto title is refreshed to reflect the compacted conversation (unless renamed manually)./rename <title>- Set a manual session title. Persists tometa.json(title_source=manual), updates the terminal tab title, and is never overwritten by automatic title generation./retry [additional instructions]- Run again a model response that failed with a backend error or was interrupted with Esc. The new reply replaces the unfinished one and its error or interrupt row. Optional instructions are passed to the model for the retry. Relevant error messages also hint at this command; with nothing to retry, it says so./pr- List the session's pull requests and merge requests (needsenable_pr_monitoring, on by default). Opening it refreshes their status. Enter opens the highlighted one,cits first failing CI check,fdrafts a prompt asking the agent to fix what blocks it (shown only when it needs action)./status- Display agent statistics/context- Show what fills the context window: a full-width gauge of the window split by category (system prompt, tool definitions including MCP and connector tools, skills, conversation), then free space and the auto-compaction buffer (marked where compaction triggers), with a legend of each category's share and estimated tokens. The total comes from the model API's last call; the repartition is estimated from each category's share of the characters that call actually sent. The footer's token counter opens the same view when clicked (also while a turn runs); a subagent shows its own window. Estimates appear only once the conversation has started (at least one model call); a resumed session shows its total alone until its next model call./whoami- Display the Mistral signed-in user, workspace, and plan/copy- Copy the last agent message to the clipboard/paste-image- Paste an image from the OS clipboard into the prompt. macOS only — the command is not registered on Linux or Windows./voice- Configure voice settings. With voice mode on,Ctrl+Tstarts or stops recording/mcp(or/connectors) - Display MCP servers and connector status. The browser opens on the first item; press Up or Left to move into the fuzzy-search bar, and Up again to wrap to the last item. Pass a server or connector name to list its tools or open its auth panel when authentication is required/mcp add <url>- Add a hosted OAuth MCP server. Supports--name <alias>, repeatable--scope <scope>,--transport <http|streamable-http>,--no-login, and--allow-insecure-http(permit a plaintexthttp://URL on a non-localhost host such as a LAN server). Starts OAuth login by default. OAuth-only; usevibe mcp add <name> --url <url> --api-key-env <var>for API-key/static auth.vibe mcp remove <name>- Remove an MCP server from the user configuration and delete its stored OAuth credentials when available./mcp status- Display MCP auth state (ok,needs_auth,static,stdio)/mcp login <alias>- Start OAuth login for an MCP server/mcp logout <alias>- Log out from an MCP server and delete stored OAuth secrets/resume(or/continue) - Browse and resume past sessions for the current folder. The picker header shows the folder being listed. Pressdtwice to delete a saved session; the active session cannot be deleted here./rewind- Rewind to a previous message. Also triggered by pressingEsctwice on an empty input; if the input has content, the first double-Escclears it instead. In the rewind panel:↑/↓pick option,Shift+↑/↓scroll,←/Escedit previous message,→edit next message,Enteraccept,qquit./branch(alias/fork) - Fork the current conversation into a new resumable session, leaving this session unchanged. A panel in place of the input shows thevibe --resume <id>command that resumes the copy;EscorEntercloses it. While the agent is working, the copy stops before the running turn, so during the first turn it is empty. After an automatic compaction during the turn,/branchis refused until the turn ends./loop [schedule] [prompt]- With the Unified Harness, ask the model to schedule a recurring prompt, e.g./loop every two minutes check the buildor/loop weekdays at 9am review CI.- The full message is sent unchanged to the model, which uses the
crontool to schedule, list, cancel, or clear prompts. There are no TUI management subcommands; bare/loopalso goes to the model. Busy submissions queue. - Fixed intervals have a 30-second minimum; calendar schedules use five-field cron in the machine's local timezone. Maximum 50 schedules per session.
- Prompts run only while the session is live and idle, without catch-up bursts.
Schedules persist in
scheduled-loops.jsonbeside the Unified session and are restored on resume. They do not run while Vibe is closed./clearstarts a conversation with no schedules; the cleared session keeps its own and runs them again when resumed./branchcopies them. - Each agent has its own schedules: a subagent calling
cronschedules, lists, cancels, and clears only its own prompts, never its parent's or a sibling's. A subagent's prompt fires as a message to that subagent once it is idle, and its result reaches the parent like any other subagent result, without starting a parent turn. Its schedules persist beside the subagent's session and stop with it./loopand theloops/*client methods still show and manage only the main session's schedules.
- The full message is sent unchanged to the model, which uses the
/proxy-setup- Configure proxy and SSL certificate settings/leanstall- Install the Lean 4 agent (leanstral)/unleanstall- Uninstall the Lean 4 agent/plugins- Display the plugins this session is running (Unified Harness only). Shows each plugin's name, scope, source format, content digest, and components (skills, MCP servers, agents, hooks, knowledge, connectors, tools). Pressrinside the view to reload./reload-plugins- Re-pin this session's plugins and report what changed (Unified Harness only). Re-discovers plugins from disk, re-pins the snapshot, and prints a diff of added, removed, and updated plugins./skills- Browse and manage skills. My Skills shows one row per installed skill, the one its name resolves to: enabled (●) or disabled (○), Scope, Source (Local, Shared or Plugin) and, for shared skills, the pinned version, with● New vNwhen a number-pinned shared skill has a newer version. A locked skill (from a plugin, or fixed byenabled_skillsor adisabled_skillspattern) shows its marker in the warning color and cannot be toggled. Narrow terminals drop the Version, Source and Scope columns, in that order, and on Shared the Version and Sharing columns./searches names and descriptions;Spaceturns the highlighted skill on or off for every scope (written todisabled_skills) when its name loads that skill; the highlighted skill's body shows under the list, or beside it from 140 columns, andEnteropens its page with its description and the body of the highlighted scope's pin under or beside the rows, which PageUp and PageDown scroll. Withexperimental_enable_registry_skills, My Skills and Shared are tabs switched withTab, Shared listing the shared skills you can still add,dthendagain on the My Skills row of a shared skill removes it from every scope, and a shared skill's page has a row per scope (Global, and Project inside a project):vN pinned,alias → vN tracking, ornot pinned, marked(shadowed)when the name loads another pin. Enter on a scope opens its picker of aliases and versions on the choice in effect, else on the other scope's, previewing the highlighted version's body: picking one pins that scope at once, andUnpin from <scope>drops just that pin (not the last one, which is what Remove is for). Enabled, on the page of the skill its name loads, switches the skill in every scope, and Remove, pressed twice, unpins it from every scope. A shared skill not added yet starts out tracking latest in Global, or in Project when another shared skill of its name holds Global, since a scope pins one skill per name and pinning over another is refused; each scope takes its own choice,Don't pin heredrops one, and nothing is written until Add to My Skills. Each new version of a pinned skill is announced once, when a session starts. A session fetches the pinned skill bodies missing from this machine and moves alias pins to their current version when it starts, and again when its project changes. A sandboxed session copies the versions it loads into its sandbox, and keeps its older copy of a skill that tracks an alias when it cannot copy the new one. When the session's fetch succeeds, at most once a day per machine, it deletes stored versions no session has used for 30 days, if the registry still serves them, so a later sync can fetch them again. It keeps each skill's newest stored version, every version pinned globally, in this project or in another project a session read, the version each alias pin last resolved to, and every version a running session loads. The 30 days count from when a session last stored or used the version, or else from the first cleanup that saw it. Vibe releases from before this cleanup do not record what they load, so a version only they load through a project pin on its number can still be deleted. The command shadows a user skill namedskills./data-retention- Show data retention information/release-notes(alias/whatsnew) - Browse the release notes of every Vibe version, newest first, in a full-screen, searchable pager. When a new session starts after an upgrade (not on resume), Vibe shows the notes of every release since the last one seen (the newest five, then a pointer to/release-notes); the last seen version isseen_whats_new_versionin~/.vibe/cache.toml./exit- Exit the application
File Mentions (@)
Type @ in the chat input to autocomplete files and folders. A bare @
lists non-hidden immediate children directly from the filesystem for fast
browsing. Once you type a path character, Git workspaces use tracked and
non-ignored untracked paths, including nested .gitignore rules; outside Git
the picker falls back to the project tree. Pressing Tab/Enter inserts the
chosen path. Your message text is sent as-is (the @path stays in the prompt);
behavior then depends on the mention kind:
- Text files trigger a synthetic
read_filetool call injected right after your message, so the file content arrives as a fresh tool result every turn (no caching/dedup). The same limits as theread_filetool apply (~2000 lines / 64 KB per call; larger files are truncated or reported as an error result). Re-mentioning a file always re-reads it. - Folders are not read automatically — the path stays in your message
text and the agent can
read_file/grepit on demand. - Image files (
.png,.jpg,.jpeg,.gif,.webp) become image attachments — sent alongside the prompt as native multimodal content for vision-capable models.
Image attachments:
-
Require
supports_images = trueon the active model inconfig.toml. The legacy loop rejects an image its model cannot read; under the Unified Harness the agent is shown a description of the image instead, and with no describer reachable the CLI refuses a pasted image. -
The describer is picked automatically: any
supports_imagesmodel on the active model's own provider, no config needed. Same provider means same key and same endpoint, so no image goes anywhere the session was not already talking. Avision_modelinconfig.tomloverrides that choice and is the only way to reach another provider. -
Each image is then described once as the turn's input is prepared, and the agent receives the text inside an
<image alias="...">block in place of the pixels. The user's prompt steers what the describer looks for, and the description is reused for the rest of the session. The describer never reasons, whatever itsthinkingsays: the trace is charged against the description budget and buys nothing on a transcription. Claude models that can't turn thinking off (Opus 5.5, Sonnet 5.5, Fable 5) reason at their lowest level instead. -
A describe that fails does not fail the turn. The agent gets a placeholder saying the image could not be read, and a warning names the image and the provider's reason.
-
With no describer reachable the image is left alone, and a model that cannot read images gets
[Image]and, for an image from a file, afile://link to it. -
Snapshotted into
<session_dir>/attachments/<sha1>.<ext>so that resumed sessions stay reproducible even if the source file is moved. -
Capped at 10 MiB per image and 8 images per message.
-
Out-of-project paths work via
@/abs/path/to.png(the picker only suggests project files, but the@-parser accepts absolute paths). Drag-and-drop from Finder into Terminal, iTerm2, or Ghostty is intercepted at paste time: if the pasted content is a standalone existing absolute or home-relative file or folder (or a newline-delimited list of them), the input automatically prepends@and quotes paths containing spaces. This applies to text files and folders (images become[Image #N]placeholders, see below); pasted prose, relative paths, and missing paths are left unchanged. -
Image copy/paste from the clipboard (macOS only for now): writes the image to a persistent temporary PNG, inserts a numbered
[Image #N]placeholder at the cursor, then snapshots it under the session attachments on submit. A pasted standalone image path becomes a placeholder too. In a!shell command the raw path is inserted instead. Two entry points:Ctrl+Vkeybinding inside the prompt./paste-imageslash command.
Uses
osascriptwith a TIFF→PNG fallback viasips. On Linux and Windows the binding and the slash command are not registered at all, so the feature is invisible to users on those platforms. -
Rendered in the chat bubble as one dim
attached image:footer line per image, linking each attachment to its snapshot. Clicking opens the file with the OS default image viewer.
Footer
The bottom row of the CLI reads, left to right:
- The working directory, elided from the start (
…/vibe/cli-rust) so the project name stays visible. Once shortened, it is followed by⧉: clicking it copies the whole path. - The session's PR/MR, with
enable_pr_monitoring(on by default):● PR #123 mergeable. The dot's color gives its state (green mergeable, red action needed or closed, purple merged, grey draft or waiting); the status names the cause when known (CI failing,changes requested,conflicts,CI running,review required). Merged and closed PRs/MRs give way to the open one; several open ones showPRs (2). A bold label means a new PR/MR or a status change not seen in/pryet. Clicking the link opens the/prlist and refreshes the statuses; clicking it again closes the list. PRs/MRs are found when the session starts, after turns (at most once a minute), and when agh pr create/glab mr createruns; statuses refresh about every minute. - On the right, with a Vibe key (chat plan) and a Mistral model, the monthly Vibe Code usage gauge
usage ██▍ 40%: the organization's spend against its allowance, re-read after each turn (at most once a minute), right after a rate limit and when the model changes. Without pay-as-you-go its fill turns yellow from 75% and red from 90% (Vibe Code stops at 100%); with pay-as-you-go it keeps its color and reads· PAYG, since usage past the allowance is billed. It is absent for API and Codestral keys, non-Mistral models, members without permission to see the organization's usage, and platforms without the billing API. - Then the context gauge
ctx ▍ 23k/600k.
On narrow terminals the footer first turns the context then usage gauges into one-cell levels (ctx ▁ 4%), then drops the PR status, then shortens the working directory to its last folder (…/cli-rust ⧉), then drops the gauges' numbers and shortens the PR link (● #123), and finally hides the link. The working directory, its copy icon and the gauges stay.
The input box's top border names the active model with its thinking level (mistral-medium-3.5[medium], the level muted) before the mode label, which is colored by the agent's safety; the borders themselves are neutral. Clicking the model opens /model while the session is idle. On narrow terminals the border drops the thinking level, then elides and hides the model; the mode stays whole.
Input Queue
Prompts submitted while the agent is running are accepted by the app server
and merged into a single follow-up turn, so everything queued during one
generation is delivered to the agent together (not one turn per prompt) once
it finishes. Each queued prompt keeps its own message so it stays individually
editable and removable. This includes plain prompts, /skill ...
prompts, and prompts with @ mentions. !bash and non-side-channel slash
commands require an idle session and are rejected while busy
with a notice above the input that stays until the next key. Ctrl+C removes the newest queued prompt (LIFO); Esc
interrupts the active turn and pauses the remaining queue; pressing Enter
(empty or not) on a paused queue resumes it.
Allowlisted slash commands (side_channel=True) run immediately via a
side channel while the agent or bash is busy, or while prompts are queued.
Only one side-channel command runs at a time. Besides read-only commands
(/help, /status, /copy, ...), this includes settings a running turn
never reads (/theme, /log-level, /voice, /proxy-setup), whose changes
are saved at once, and /branch, which forks the conversation as it was before
the running turn. These still wait for a compaction or agent install to finish.
Commands that change what a turn reads (e.g. /config, /model,
/thinking, /clear, /compact, /rewind, /resume,
/reload, /leanstall, /unleanstall, /retry, /mcp,
/connectors, /plugins, /reload-plugins, /skills) are rejected while
busy and can be retried when the session is idle.
While the queue is non-empty and the agent is busy, pressing Up enters queue selection mode: the last queued item is highlighted and the input is locked (no cursor, no typing). Up/Down navigate between queued prompts, Enter loads the selected prompt into the input for editing (press Enter again to update it in-place), Backspace (or Ctrl+C) removes the selected item and moves selection to the next, and Esc exits selection mode and restores the original input text.
Plugins
Plugins are bundled extension packages that can contribute skills, MCP servers, hooks, agents, knowledge, connectors, and tool libraries to a session. They are discovered from two locations:
~/.vibe/plugins/— user scope (global, available in every project).vibe/plugins/— project scope (requires trusted folder; overrides user scope on name collisions)
Each subdirectory inside a plugins directory is a single plugin root.
To turn a plugin off without uninstalling it, list its manifest name in
disabled_plugins in config.toml (e.g. disabled_plugins = ["my-plugin"]).
A plugin whose ai.mistral.vibe extension sets "defaultEnabled": false is
off until its name is in enabled_plugins. Both lists add up across config
layers, disabled_plugins wins, and an off plugin contributes nothing to
sessions.
Plugin Manifest (plugin.json)
Every plugin has a plugin.json manifest following the Agent Plugins 1.0
schema (https://agent-plugins.org/schemas/1.0.0/plugin.schema.json):
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "my-plugin",
"version": "1.0.0",
"description": "Optional description",
"author": { "name": "Author", "email": "a@b.com", "url": "https://..." },
"homepage": "https://...",
"repository": "https://...",
"license": "MIT",
"keywords": ["tag1"],
"extensions": {
"ai.mistral.vibe": {
"schemaVersion": 1,
"displayName": "My Plugin",
"toolNamespace": "myNs",
"toolOverrides": {
"tool-name": { "name": "renamedTool", "exposure": "programmatic" }
}
}
}
}
The ai.mistral.vibe extension is optional but gates all Vibe-specific
components (hooks, knowledge, agents). toolNamespace is a
TypeScript-identifier-safe string used to prefix all component names
(e.g. myNs:my-skill). If omitted, it is derived from the plugin name.
displayName is an optional title of 1 to 100 characters; the plugin name
stays its identity.
An unknown top-level plugin.json field, or an unknown key in the extension, is
ignored with a warning and the rest loads. An invalid value for a known
extension key drops the whole extension.
Reserved namespaces (rejected): file_system, self, process, skill,
subagent, vibe.
Plugin Contents
A plugin tree may contain any combination of:
| Component | Location | Notes |
|---|---|---|
| Skills | skills/<name>/SKILL.md |
Standard skill format |
| MCP servers | mcp.json |
$schema + mcpServers dict; stdio, streamable-http, sse |
| Hooks | ai.mistral.vibe/hooks.toml |
Max 128 hooks, 64 KiB |
| Knowledge | ai.mistral.vibe/knowledge/<name>/KNOWLEDGE.md |
Max 100 entries |
| Agents | ai.mistral.vibe/agents/*.toml |
One TOML per subagent |
| Libraries | libraries.json |
Node and Python library path aliases |
| Connectors | connectors.json |
Connector tool mappings |
Environment variables PLUGIN_ROOT and PLUGIN_DATA are injected
automatically into MCP server and hook processes and are reserved. Plugin
hooks run in the plugin directory.
Foreign Plugin Formats
Vibe also adapts non-native plugin formats:
| Format | Detection marker |
|---|---|
| Claude Code | .claude-plugin/plugin.json |
| Codex | .codex-plugin/plugin.json |
| Kimi Code | .kimi.plugin.json or .kimi-plugin/plugin.json |
| OpenCode | .opencode/plugins/, .opencode/skills/*/SKILL.md, opencode.json |
Adapted plugins can only contribute skills and MCP servers — hooks,
knowledge, agents, libraries, and connectors are native-only. Executable code
in foreign plugins (.js, .ts) is refused and reported as unsupported.
Plugin Pinning and Reload
On session start, discovered plugins are resolved into a ResolvedPluginSnapshot
— a portable, host-path-free, secret-free representation pinned to the session.
This ensures resume/rewind reproducibility even if files change on disk.
/reload-plugins re-discovers plugins from disk, builds a fresh snapshot, and
prints a diff (+ added, - removed, ~ updated). Tools that are no longer
present after a reload are retained as unavailable routes so conversation
history stays valid. Drift detection flags tools whose schema fingerprint changed
between pin and live source.
Example Plugin Tree
my-plugin/
├── plugin.json
├── mcp.json
├── skills/
│ └── my-skill/
│ └── SKILL.md
└── ai.mistral.vibe/
├── hooks.toml
├── knowledge/
│ └── my-knowledge/
│ └── KNOWLEDGE.md
└── agents/
└── my-agent.toml
Skills System
Skills are specialized instruction sets the model can load on demand.
Each skill is a directory containing a SKILL.md file with YAML frontmatter.
Skill File Format
---
name: my-skill
description: What this skill does and when to use it.
user-invocable: true
allowed-tools: bash read
---
# Skill Instructions
Detailed instructions for the model...
Skill Search Order (first match wins)
skill_pathsfrom config.toml.vibe/skills/in trusted project directory.agents/skills/in trusted project directory~/.vibe/skills/(user global)~/.agents/skills/(user global, Agent Skills standard)
With experimental_enable_registry_skills, the nearest scope wins instead, and
within a scope a skill file wins over a registry pin: the project's skill files,
then the skills pinned in its .vibe/skills.toml, then the global skill files
(skill_paths, ~/.vibe/skills/, ~/.agents/skills/), then the skills pinned in
~/.vibe/skills.toml. A registry pin goes by the name its manifest entry gives
it; when two pins load the same stored version under different names, the one
ranked first loads it. A skill that loses its name is logged as shadowed: once
per process as a warning under this rule, and at debug level otherwise.
Invoking Skills
Two entry points:
- The model loads a skill on demand via the
skilltool. - The user invokes a
user-invocableskill by typing/skill-name(optionally followed by extra instructions). The user turn stays the literal/skill-nametext; the skill is loaded programmatically and appears to the model as a syntheticskilltool call and result immediately after that turn — the model does not call the tool itself.
Skills with user-invocable: false are model-only: they are hidden from the
slash menu and /skill-name will not resolve them (it is treated as a plain
prompt). The model can still load them via the skill tool.
Skills with disable-model-invocation: true stay in the slash menu but are
hidden from the model and cannot be loaded through the skill tool. Skills
using OpenAI's agents/openai.yaml convention can set
policy.allow_implicit_invocation: false for the same provider-independent behavior.
A / at the very start of the input opens the slash menu (commands and skills).
A skill row's description starts with its scope in the theme's primary color,
whether it is a local, plugin or registry skill: project · … (this project
only), user · … (the user's, in every project), vibe · … (shipped with
Vibe), or skill · … when an older server does not say; a plugin skill also
shows its plugin in its plugin:name. Rows without one are built-in commands.
A built-in command wins over a skill with the same name, so such a skill is
left out of the slash menu; it can still be mentioned mid-prompt.
A /word typed mid-prompt (not the first word) instead opens a completion popup
of matching user-invocable skills, with the same scope prefix.
Environment Variables
VIBE_HOME- Override the Vibe home directory (default:~/.vibe)MISTRAL_API_KEY- API key for Mistral providerVIBE_ACTIVE_MODEL- Override active modelVIBE_*- Any config field can be overridden with theVIBE_prefixLOG_LEVEL- Overrideslog_levelconfig for$VIBE_HOME/logs/vibe.log. One ofDEBUG,INFO,WARNING(default),ERROR,CRITICAL. Invalid values fall back toWARNING. Use/log-levelto change at runtime.LOG_MAX_BYTES- Max size in bytes ofvibe.logbefore rotation (default:10485760, i.e. 10 MiB).DEBUG_MODE- Whentrue, forcesDEBUG-level logging.VIBE_TYPING_GRACE_PERIOD_MS- Milliseconds the agent waits for a typing pause before showing tool-approval / ask-user-question dialogs (default:1000). Set to0to disable. Negative or non-numeric values fall back to the default.
API Keys (.env file)
The .env file in VIBE_HOME stores API keys in dotenv format:
MISTRAL_API_KEY=your-key-here
This file is loaded on startup and its values are injected into the environment.
Trusted Folders
Vibe uses a trust system to prevent executing project-local config from untrusted
directories. The trust database is stored in ~/.vibe/trusted_folders.toml.
Project-local config (.vibe/ directory) is only loaded when the current
directory is explicitly trusted.
Interactive mode prompts to trust unknown folders. The prompt targets the
closest ancestor of the cwd (the cwd itself included) containing a .git
entry; the search excludes the user's home directory and the filesystem
root, and falls back to the cwd if no qualifying ancestor is found.
Programmatic mode (-p/--prompt) never prompts: the folder is untrusted.
Use --trust to trust cwd for the current invocation only (not persisted).
--trust and --worktree both skip the prompt: they grant the workspace trust
for the session, so there is no decision left to ask about. Without this a
--worktree run would prompt on every launch, since each worktree is a
directory the trust database has never seen.
Sensitive Files — DO NOT READ OR EDIT
NEVER read, display, or edit any of these files:
~/.vibe/.env(or$VIBE_HOME/.env) — contains API keys and secrets- Any
.env,.env.*file in the project or VIBE_HOME
If the user asks to set or change an API key, instruct them to edit the .env
file themselves. Do not offer to read it, write it, or display its contents.
Do not use tools (read, write_file, bash cat/echo, etc.) to access these files.
How to Modify Configuration
To help the user modify their Vibe configuration:
- Read current config when present: Read
~/.vibe/config.toml(or the path fromVIBE_HOMEif set). A missing file means Vibe is using built-in defaults. - Create a backup when present: Before editing an existing file, copy it to
config.toml.bakin the same directory. This applies to any existing config file you are about to modify (config.toml,trusted_folders.toml, agent TOML files, etc.) - Edit the TOML file: Make changes using the edit tool
- Reload: The user can run
/reloadto apply changes without restarting
For API keys, tell the user to edit ~/.vibe/.env directly — never read or
write that file yourself.
For project-specific configuration, create/edit .vibe/config.toml in the
project root (the folder must be trusted first).
04 同仓库更多 Skills
import
Import the user's setup from other coding CLIs (Claude Code, Codex, OpenCode) into Vibe — instructions, skills, subagents, commands, hooks, MCP servers, permissions, and plugins. Load when the user wants to switch to Vibe, import or reuse another CLI's configuration, or asks whether a file from another CLI works in Vibe.
worktree
Create, reuse, and clean up git worktrees under $VIBE_HOME/worktrees. Use when the user wants to work in an isolated git worktree, create a worktree for a task, or list/remove existing worktrees.
skill-creator
Load this skill before creating, updating, or deleting a Vibe skill, or whenever you plan to add or modify SKILL.md files under a skills directory. It explains Vibe's SKILL.md frontmatter, discovery order, support files, and the permission flow.
create-plugin
Create a Vibe plugin package — the Agent Plugins 1.0 format with optional Vibe extensions for skills, MCP servers, hooks, knowledge, agents, libraries, and connectors. Use when the user wants to create, scaffold, or author a plugin for Mistral Vibe.
prompts
暂无简介
write-vibe-tests
Write or refactor Mistral Vibe tests with proper decoupling. Use when adding behavior coverage, testing ports/adapters, replacing brittle mocks, creating fakes, adding characterization tests before refactors, or changing tests under tests/ for vibe/core, vibe/app_server, vibe/acp, tools, config, sessions, skills, hooks, MCP, or setup.