[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6C46A11K3H7D6F7ATSBRYKJ":3,"skill-same:01M6C46A11K3H7D6F7ATSBRYKJ":70},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":43,"linkOnly":44,"markdownContent":45,"markdownBody":46,"frontMatter":47,"refName":48,"commitSha":49,"skillDirectory":50,"forksCount":51,"repositoryHtmlUrl":52,"repositoryHomepageUrl":6,"ownerHtmlUrl":53,"ownerBlogUrl":54,"ownerDescription":12,"updatedAt":55,"install":56,"resources":69},"01M6C46A11K3H7D6F7ATSBRYKJ","vibe",null,"Authoritative reference for Mistral Vibe — the CLI agent you (the model) are running inside.\n\nLOAD when the user:\n- asks anything about Vibe itself, even by indirect name (\"this CLI\", \"this tool\", \"you\");\n- wants to change, inspect, or reset their setup;\n- asks why the agent did or did not act;\n- asks how to make the CLI do X, where X lives, or what a flag\u002Fcommand\u002Fsetting does;\n- asks any meta question about your own behavior;\n- is unsure whether a command, flag, env var, or file is in scope — this skill is the source of truth.\n\nSCOPE: config under `~\u002F.vibe\u002F` and project-local `.vibe\u002F`; `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.","mistralai\u002Fmistral-vibe","mistralai","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F132372032?v=4","01M6C456ET86MBBTS4RMX0NDRS","Mistral","01M6C456K44X5W8TJX4T023KRV",5383,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fmistralai\u002Fmistral-vibe\u002Fblob\u002Fmain\u002Fvibe\u002Fplugins\u002Fbuiltins\u002Fvibe\u002Fskills\u002Fvibe\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[5,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42],"subagents","for","do","act","log","you","themes","local","inspect","project","config","why","how","command","change","logs","MCP","question","Meta","2026-11-10 10:12:30",false,"---\nname: \"vibe\"\ndescription: \"Authoritative reference for Mistral Vibe \\u2014 the CLI agent you (the model) are running inside.\\n\\nLOAD when the user:\\n- asks anything about Vibe itself, even by indirect name (\\\"this CLI\\\", \\\"this tool\\\", \\\"you\\\");\\n- wants to change, inspect, or reset their setup;\\n- asks why the agent did or did not act;\\n- asks how to make the CLI do X, where X lives, or what a flag\u002Fcommand\u002Fsetting does;\\n- asks any meta question about your own behavior;\\n- is unsure whether a command, flag, env var, or file is in scope \\u2014 this skill is the source of truth.\\n\\nSCOPE: config under `~\u002F.vibe\u002F` and project-local `.vibe\u002F`; `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.\"\nuser-invocable: false\n---\n\n# Vibe CLI Self-Awareness\n\nYou are running inside **Mistral Vibe**, a CLI coding agent built by Mistral AI.\nThis skill gives you full knowledge of the application internals so you can help\nthe user understand, configure, and troubleshoot their Vibe installation.\n\n## Going Deeper\n\nFor facts not covered here, fetch the README:\nhttps:\u002F\u002Fgithub.com\u002Fmistralai\u002Fmistral-vibe\u002Fblob\u002Fmain\u002FREADME.md\nIt tracks the latest release, so check `vibe --version` before relying on it for\nanything version-specific. Point the user at\nhttps:\u002F\u002Fdocs.mistral.ai\u002Fvibe\u002Fcode\u002Foverview for human-readable docs.\n\n## VIBE_HOME\n\nThe user's Vibe home directory defaults to `~\u002F.vibe` but can be overridden via\nthe `VIBE_HOME` environment variable. All user-level configuration, skills, tools,\nagents, prompts, logs, and session data live here.\n\n### Directory Structure\n\n```\n~\u002F.vibe\u002F\n  config.toml          # Optional user configuration, created on first saved setting\n  hooks.toml           # User-level hook definitions\n  .env                 # API keys and credentials (dotenv format)\n  vibehistory          # Command history\n  trusted_folders.toml # Trust database for project folders\n  connector_bootstrap_cache.json # Short-lived connector discovery cache\n  agents\u002F              # Custom agent profiles (*.toml)\n  prompts\u002F             # Custom prompts (*.md)\n  skills\u002F              # User-level skills (each skill is a subdirectory with SKILL.md)\n  tools\u002F               # Former custom Python tools, no longer loaded (see below)\n  logs\u002F\n    vibe.log           # Main log file\n    session\u002F           # Session log files\n\n~\u002F.agents\u002F\n  skills\u002F              # Additional user-level skills directory\n```\n\n### Project-Local Configuration\n\nWhen in a trusted folder, Vibe also looks for project-local configuration:\n- `.vibe\u002Fconfig.toml` - Project-specific config (overrides user config)\n- `.vibe\u002Fhooks.toml` - Project-specific hooks (requires trusted folder)\n- `.vibe\u002Fskills\u002F` - Project-specific skills\n- `.vibe\u002Ftools\u002F` - Former project-specific custom Python tools, no longer loaded (see below)\n- `.vibe\u002Fagents\u002F` - Project-specific agents\n- `.vibe\u002Fprompts\u002F` - Project-specific prompts\n- `.agents\u002Fskills\u002F` - Standard agent skills directory\n\nCustom Python tools are no longer supported: Vibe does not load the `.py` files\nin `~\u002F.vibe\u002Ftools\u002F` or `.vibe\u002Ftools\u002F`, and reports each one as a config issue.\nAt startup Vibe also removes the former `tool_paths` setting from\n`config.toml`; the folders it listed stay on disk. Recommend skills for new\nextensions. When a user asks for migration help, inspect the custom tool's\nbehavior and replace it with an equivalent skill.\n\n### AGENTS.md Discovery\n\n`AGENTS.md` files provide directory-scoped instructions to the model. At startup,\nVibe loads `~\u002F.vibe\u002FAGENTS.md` and every `AGENTS.md` from the project root up\nthrough the trust chain. `AGENTS.md` files in subdirectories are discovered\nlazily: when `read_file` reads a file below the project root, any `AGENTS.md`\nbetween the file's parent and the project root is injected into\ncontext.\n\n## Lifecycle: Exit, Update, Version, Resume\n\n### Exit\n\nChat input (case-insensitive): `\u002Fexit`, `exit`, `quit`, `:q`, `:quit`.\nKeyboard: `Ctrl+C` \u002F `Ctrl+D` — press twice within ~1s to quit. For `Ctrl+C`,\nthe first press instead interrupts the running job or clears the input if either\nis present. Set `ask_confirmation_on_exit = false` to make `Ctrl+D` quit on the\nfirst press (also toggleable in `\u002Fconfig`); `Ctrl+C` always requires a second\npress. `Ctrl+Z` suspends on POSIX (resume with `fg`).\n\n### Prompt History\n\n`Up`\u002F`Down` recall previous prompts (stored in `~\u002F.vibe\u002Fvibehistory`, newest 100\nkept). `Ctrl+R` opens a `reverse-i-search:` field in the bottom bar: the input\npreviews the newest prompt containing the query (case-insensitive), `Up`\u002F`Ctrl+R`\nmove to older matches and `Down`\u002F`Ctrl+S` to newer ones, `Enter` keeps the match\nin the input without sending it (`Ctrl+Z` undoes), `Esc`\u002F`Ctrl+C` restore the draft.\n\n### Update\n\nVibe never updates silently. With `enable_update_checks = true` (default), it\nchecks for a newer `mistral-vibe` daily and prompts on the next launch when\none exists. uv tool installs ask `uv tool list --outdated`, so uv settings such\nas `exclude-newer` apply; other installs, and uv older than 0.10.10, poll PyPI. Accepting runs\n`uv tool upgrade mistral-vibe`, then `brew upgrade mistral-vibe` as a fallback. On\nWindows, accepting prints the upgrade command to run after closing every Vibe\nsession instead, since a running Vibe locks its own files. Disable via `enable_update_checks\n= false`. Run `vibe update` (equivalent to `vibe --check-upgrade`) to check\nimmediately, prompt to install a newer version if one exists, and exit. Initial\ninstall: `uv tool install mistral-vibe`.\n\n### Version\n\n`vibe --version` (or `-v`) prints it and exits. Not shown anywhere in-session.\n\n### Resume\n\n- `vibe -c` \u002F `--continue`: the last session used in this terminal in the\n  current folder (it moves on a new session's first message, or when a saved\n  session is reopened); otherwise the folder's most recent session. Works with\n  `-p` too. The terminal is the tty device on macOS\u002FLinux, the console window\n  or `WT_SESSION` on Windows; `VIBE_TERMINAL_KEY` overrides it, and an empty\n  value means \"no terminal\" (folder rule only).\n- `vibe --resume [SESSION_ID]`: specific session; without an id, opens a picker.\n- In-session: `\u002Fresume` (alias `\u002Fcontinue`).\n\n#### Session titles\n\nEach session has a `title` stored in `meta.json` (with `title_source`: `auto` or\n`manual`). A session stays untitled until a background LLM call generates a\nconcise descriptive title (the `--resume` list shows a message preview until\nthen). Automatic generation runs only for the interactive CLI; other clients\n(ACP, app server, programmatic) keep their own session management and fall back\nto the message preview. Title generation runs on a small fast Mistral model when the\ndeployment is known to serve one — Vibe asks it once, with a one-token request\nat session open, and remembers the answer in `~\u002F.vibe\u002Futility_model_cache.json`\n— and on the session's active model otherwise. `utility_models.title` pins a\nspecific model instead. The first title waits for the opening turn to finish (or a few\nmodel steps) so it isn't generated off a thin tool-call preamble. On that cheap\nfast model it also refreshes periodically and\nafter each compaction; when it falls back to the (possibly expensive) active\nmodel it stays bounded — one title at the start plus one after a compaction, a\ncouple at most — so a large model isn't re-invoked every few turns. The refresh\nkeeps the opening intent and the latest exchange in view and feeds the previous\ntitle back so it refines rather than restarts. `\u002Frename \u003Ctitle>` sets a `manual`\ntitle that auto-generation never overwrites. Set `session_logging.generate_titles\n= false` to turn automatic titles off (the `--resume` list and tab then use the\nmessage preview). The current title also drives the terminal tab\u002Fwindow title\n(OSC), updated on rename, auto-title changes, and resume; it never blocks a turn.\n\n#### Session storage & folder scoping\n\nLocal sessions are written under `~\u002F.vibe\u002Flogs\u002Fsession\u002F` (override with\n`session_logging.save_dir`). Each session records the `cwd` it ran in. The\n`\u002Fresume` picker, `--continue`, and bare `--resume` (no id) are **scoped to the\ncurrent folder**: only sessions whose `cwd` matches where Vibe is launched are\nlisted, so the same directory shows its own history and nothing else. Switch\nfolders to see a different set. The explicit `--resume \u003CSESSION_ID>` form is\n**not** folder-scoped: it resolves the session by id regardless of which folder\nit ran in.\n\nThe first user message pins the resolved model alias to the session. Resuming\nkeeps that model even if the configured default changes. `\u002Fmodel` uses the same\nconfig persistence target as other settings and also updates an existing\nsession override immediately; the session file is synchronized when the next\nuser message is sent. An explicit persistent target selected through `\u002Fconfig`\nchanges only that layer. `\u002Fclear` starts an unpinned conversation that follows\nthe current config again. If a selected model is no longer configured, Vibe\nfalls back to the current default model.\n\nEach user message also pins the running agent, including one chosen with\n`--agent`. Resuming or continuing keeps that agent, even if the configured\ndefault agent changes or `--agent` names another one; switch agents in the\nsession to change it. If the pinned agent no longer exists, Vibe falls back to\n`--agent` or the default agent, and keeps that one from then on.\n\n## Configuration (config.toml)\n\nThe configuration file uses TOML format. When it does not exist, Vibe uses its\nbuilt-in defaults and creates a sparse file on the first persisted setting.\nSettings can also be overridden via environment variables with the `VIBE_`\nprefix (e.g., `VIBE_ACTIVE_MODEL=my-local-model`).\n\nCustom prompt IDs are resolved from project-local `.vibe\u002Fprompts\u002F` first, then\nfrom `~\u002F.vibe\u002Fprompts\u002F`, and finally from the built-in bundled prompts.\n\n### Settings Vibe does not use\n\nVibe checks `~\u002F.vibe\u002Fconfig.toml`, the trusted project `.vibe\u002Fconfig.toml` and\nthe active agent profile for settings it does not use: a misspelt key (with the\nclosest setting name), a retired setting such as `include_model_info`, or a\nsetting with no effect such as `[tools.grep]`. Each one is a warning at startup,\nand `vibe -p` prints it on stderr.\n\nTo refuse a config with such settings instead, set `strict_config = true` in\n`config.toml` or `VIBE_STRICT_CONFIG=1` in the environment; Vibe then stops with\nthe list. `VIBE_STRICT_CONFIG` takes a boolean: `1`, `true`, `yes` or `on` to\nrefuse, `0`, `false`, `no` or `off` to warn; any other value is refused too.\n\n`vibe config schema` prints a JSON schema of every setting, including each\nbuilt-in tool's settings, for an editor to validate `config.toml` against.\n\n### Key Settings\n\n```toml\n# Model selection\nactive_model = \"mistral-medium-3.5\"  # Model alias to pin; omit or set \"\" to follow the server-routed default\n\n# UI preferences\ntheme = \"auto\"  # Follow terminal background, then OS light\u002Fdark preference\ndisable_welcome_banner_animation = false\ncursor_blink = true  # Blink the input cursor; false keeps it steady\nvim_keybindings = false  # Vim Normal and Insert modes in the prompt input\nautocopy_to_clipboard = true  # Enable automatic copying of selected text to clipboard\nenable_mouse_capture = true  # Capture the mouse for scrolling, clicks and selection; false keeps the terminal's native selection\nenable_sticky_prompt = true  # Pin the prompt of the section at the top of the transcript while scrolling\nenable_file_autocomplete = true  # Offer @ file completion; off skips the file index entirely\nfile_watcher_for_autocomplete = true  # Refresh @ suggestions and pick up skill changes after workspace changes\nask_confirmation_on_exit = true  # Require a second Ctrl+D to quit (Ctrl+C always confirms)\nshow_greeting = true  # Show \"Hello {name}\" greeting below the banner at startup (Mistral providers, once per 24h)\nlog_level = \"WARNING\"  # Optional. DEBUG | INFO | WARNING | ERROR | CRITICAL — log level for ~\u002F.vibe\u002Flogs\u002Fvibe.log\n```\n\n`cursor_blink` controls the main input cursor. Change it through `\u002Fconfig`, or\nedit `config.toml` and run `\u002Freload`; no restart is needed.\n\n`enable_sticky_prompt` pins the prompt of the conversation section at the top of\nthe transcript once it scrolls off; the next section's prompt pushes it out. It\nshows at most three rows, the last one cut with `…`, and turns off below 15\ntranscript rows. A prompt a scheduled loop sent pins as its folded title\n(`⏵ title`), without its instructions or `loop` line. Clicking it\nscrolls the transcript back to that prompt. Set it to `false` through `\u002Fconfig`,\nor edit `config.toml` and run `\u002Freload`; no restart is needed.\n\n`vim_keybindings` adds a subset of vim to the prompt input. It starts in Insert\nmode, labelled `INSERT` on its bottom border with a bar caret; `Esc` enters Normal\nmode, unlabelled with a block caret.\nIn Normal mode `Esc` interrupts a running turn and `Esc Esc` on an empty prompt\nopens rewind; it never clears the draft. Normal mode supports\n`h l w b e 0 ^ $ gg G j k`, `i a I A o O`, `x X D C dd cc`, `d`\u002F`c` with\n`w e b 0 ^ $` (`Esc` or `Enter` cancels a pending `d`\u002F`c`), `u` and `Ctrl+R` (redo, not history search); `\u002F` or `!` on an empty prompt opens that mode.\nIn Insert mode `Esc` enters Normal mode, so `Ctrl+C` interrupts a running turn.\nOther shortcuts (`Ctrl+A`, `Ctrl+G`, ...) work in both modes. There is no visual\nmode, yank\u002Fpaste, count or `:` command.\n\n### Copy and Text Selection\n\n- **Copy shortcuts**: `Ctrl+Y` and `Ctrl+Shift+C` both copy the current selection to the clipboard. On macOS and Linux, with nothing selected, `Ctrl+Y` yanks (pastes back) the text the last `Ctrl+K`, `Ctrl+Shift+K`, `Ctrl+U`, `Ctrl+W`, `Alt+D` or 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 than `Esc`, which aborts the drag without copying). Each successful copy flashes a brief inline \"Copied to clipboard\" notice.\n- **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.\n- **Terminal-native selection**: `enable_mouse_capture = false` (or `VIBE_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 `\u002Fconfig` or `\u002Freload`. Inside a tmux session whose `mouse` option is off (the default before tmux 3.8), Vibe behaves as if this setting were `false`, re-reading the option at startup and when resuming from suspend or an external editor. Native selection only covers the visible screen; `Shift+Up\u002FDown` always scrolls the transcript. In some terminals the mouse wheel then sends Up\u002FDown keys, which recall prompt history in the input box.\n- **Warnings**: Config issues, config validation warnings, and the warnings the\n  app server reports before the first turn (an MCP server or connector that\n  failed to load, a model catalogue problem) are listed together in one\n  `Warnings` box at the top of a new conversation (below the history of a\n  resumed one). Once a turn has started, each new warning appears as its own\n  `⚠` line in the conversation. Errors always appear as their own `✗` line.\n- **Clipboard tools**: Copies use `pbcopy` on macOS and `clip` on Windows; on Linux they try `wl-copy`, then `xclip`, then `xsel`. Vibe always also sends the text through OSC 52, and over SSH it relies on OSC 52 alone. Inside tmux, it instead runs `tmux load-buffer -w` to fill a tmux paste buffer and forward the text to the attached terminal's clipboard. If tmux's `set-clipboard` is `off`, or the terminal lacks the `Ms` capability, it falls back to OSC 52 passthrough. When no native copy succeeds, the notice reads `Copied · \u003Chint>` instead (auth URL copies read `Auth URL copied · \u003Chint>`). On local Linux the hint is `if paste fails, Shift+select or install xclip`; elsewhere, including over SSH, it is `if paste fails, Shift+select (Option in iTerm2, Fn in Terminal.app)`. Shift+select uses the terminal's native selection.\n\n```toml\n# Behavior\nbypass_tool_permissions = false    # Skip tool approval prompts\noutside_workdir = \"ask\"            # Outside-workdir file\u002Fshell access: \"ask\" prompts, \"deny\" refuses with a tool error\nworktree_copy_on_write = true      # macOS: clone new worktrees of a trusted repo from a template checkout; sets extensions.worktreeConfig in the repo\nenable_pr_monitoring = true        # Track the session's PRs\u002FMRs (gh\u002Fglab): detect created ones, find existing ones for the branch, keep their status fresh\nsystem_prompt_id = \"cli\"          # System prompt: \"cli\", \"lean\", or custom .md filename\ncompaction_prompt_id = \"compact\"  # Compaction prompt: built-in \"compact\" or custom .md filename\nenable_telemetry = true\nenable_update_checks = true       # Daily update check (uv or PyPI); prompts on next launch when a newer release exists\nenable_notifications = true\nenable_system_trust_store = false  # Use OS trust store for outbound HTTPS\napi_timeout = 720.0               # API request timeout in seconds\napi_retry_max_elapsed_time = 300.0  # Retry budget for retryable API failures in seconds\napi_stream_idle_timeout = 300.0   # Silence after the model starts answering that counts as a stall, in seconds; 0 allows api_timeout\n# auto_compact_threshold = 200000 # optional fallback for models without their own threshold; unset: derived from each model's context window\ncompaction_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\noutput_limit_retries = 6          # Answers cut off at the output limit that a turn retries with feedback; 0 ends the turn on the first\nagent_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)\ncompaction_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\n\n# Git commit behavior\ninclude_commit_signature = true   # Add \"Co-Authored-By\" to commits\n\n# System prompt composition\ninclude_project_context = true    # Include project context (git info, cwd) in system prompt\n\n# Voice features\nvoice_mode_enabled = false\nnarrator_enabled = false\nactive_transcribe_model = \"voxtral-realtime\"\nactive_tts_model = \"voxtral-tts\"\n```\n\n### OpenTelemetry Tracing\n\nSet `enable_otel = true` to export traces for agent, model, and tool operations\nover OTLP\u002FHTTP. `enable_telemetry` must also be enabled. With no explicit\nendpoint, Vibe derives the telemetry endpoint and API key from the configured\nMistral provider, except public regional API hosts that do not serve telemetry.\n\nTo use another collector, set `otel_endpoint` to its base URL; Vibe appends\n`\u002Fv1\u002Ftraces`. Configure custom-collector authentication through the standard\n`OTEL_EXPORTER_OTLP_*` environment variables.\n\n`otel_redaction` controls client-side span attribute redaction: `default`\nredacts sensitive values, `strict` redacts sensitive attributes entirely, and\n`none` disables redaction. Use `none` only for a collector trusted to receive\npotentially sensitive prompt, response, and tool data.\n\n```toml\nenable_otel = true\notel_endpoint = \"https:\u002F\u002Fcollector.example.com:4318\"\notel_redaction = \"default\"\n```\n\n### Providers\n\n```toml\n[[providers]]\nname = \"mistral\"\napi_base = \"https:\u002F\u002Fapi.mistral.ai\u002Fv1\"\napi_key_env_var = \"MISTRAL_API_KEY\"\nbackend = \"mistral\"\n\n[[providers]]\nname = \"llamacpp\"\napi_base = \"http:\u002F\u002F127.0.0.1:8080\u002Fv1\"\napi_key_env_var = \"\"\nextra_headers = { \"X-Custom-Header\" = \"value\" }  # optional per-provider HTTP headers\nemits_finish_reason = false  # set false for OpenAI-compatible endpoints that end\n                             # streams without a finish reason; avoids spurious\n                             # \"incomplete stream\" errors and retries (default true)\n```\n\n### Models\n\n```toml\n# Restrict selectable models by their canonical API names, not their aliases.\n# Glob and \"re:\" patterns, matched in full. The only way to hide server-routed\n# models; a hidden routed default falls back to the catalog recommendation,\n# then Mistral Medium 3.5, then the first allowed model. Keep one pattern per\n# model: a pattern matching\n# nothing warns — the drift signal; do not merge them to silence it.\nallowed_models = [\"mistral-vibe-cli-*\"]\n\n[[models]]\nname = \"mistral-vibe-cli-latest\"\nprovider = \"mistral\"\nalias = \"mistral-medium-3.5\"\ntemperature = 1.0\ninput_price = 1.5\noutput_price = 7.5\ncached_input_price = 0.15         # per million cached input tokens; omit to bill at input_price\nthinking = \"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\n# thinking_levels = [\"off\", \"high\"] # levels \u002Fthinking 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)\n# 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\nsupports_images = true            # vision-capable; allows @-mentioned images\n\n[[models]]\n# A user-defined local model served by llama.cpp; the built-in list ships only\n# the hosted Mistral Medium 3.5.\nname = \"devstral\"\nprovider = \"llamacpp\"\nalias = \"my-local-model\"\n\n# Optional override. A non-vision active model already picks up any\n# supports_images model on its OWN provider automatically; set this only to\n# point somewhere else, which is also the only way to cross providers. Ignored\n# whenever the active model has supports_images = true -- that model sees the\n# image itself.\n[vision_model]\nname = \"mistral-vibe-cli-latest\"\nprovider = \"mistral\"\nalias = \"vision\"\nsupports_images = true            # required\n\n# Optional: the model behind each background helper -- session titles and the\n# smart-approve classifier. A value is an alias from [[models]], or \"active\" for\n# the session's own model (unless a model is aliased \"active\"). Unset, a helper\n# uses a small fast Mistral model when the deployment serves one (checked when\n# a session opens with the feature on, then cached) and the session's model\n# otherwise, which costs more per call.\n[utility_models]\ntitle = \"local\"\nsmart_approve = \"active\"\n```\n\n### Tool Configuration\n\n```toml\n# Enable only specific tools (glob and regex supported)\nenabled_tools = [\"bash\", \"read_file\", \"grep\"]\n\n# Disable specific tools after enabled_tools filtering\ndisabled_tools = [\"web_fetch\"]\n\n# Per-tool configuration\n[tools.bash]\nallowlist = [\"git\", \"npm\", \"python\"]\n\n[tools.git_bash]\npermission = \"ask\"\nshell = \"C:\\Program Files\\Git\\bin\\bash.exe\"\n\n[tools.powershell]\npermission = \"ask\"\nshell = \"powershell.exe\"\n```\n\nTwo tool settings have no effect, and Vibe reports each as a config issue (in\nthe startup Warnings box in the TUI, a warning in the `-p` export):\n\n- `[tools.grep]` (for example `exclude_patterns`): there is no `grep` tool. The\n  agent searches through the shell, so `rg` and `grep` commands follow the shell\n  tool's settings (`[tools.bash]` on POSIX).\n- A `prompts\u002Ftask.md` in a tool directory (`.vibe\u002Ftools\u002Fprompts\u002Ftask.md`): it\n  overrode the description of the former `task` tool. Subagents are started\n  with the built-in `agents.spawn` tool, whose description cannot be\n  overridden; put delegation guidance in `AGENTS.md` or in the agent's\n  instructions instead.\n\nThe built-in shell surface is controlled by the `managed_shell_tools_enabled` config\nfield and the `vibe_cli_managed_shell_tools` GrowthBook experiment. The default variant\nkeeps the legacy one-shot `bash` tool, including its existing Windows behavior.\nThe managed variant exposes OS-native shell tools:\nPOSIX systems, including WSL where Vibe runs as Linux, get managed `bash`,\n`bash_output`, `bash_stdin`, `bash_sessions`, and `bash_log_file`; native Windows\ngets `git_bash`, `git_bash_output`, `git_bash_stdin`, `git_bash_sessions`, and\n`git_bash_log_file` when Git Bash is available. If Git Bash is unavailable,\nnative Windows falls back to `powershell`, `powershell_output`,\n`powershell_stdin`, `powershell_sessions`, and `powershell_log_file`.\n\nManaged shell sessions return a `session_id`, inline output, a cursor for polling\nmore output, and a log path under `~\u002F.vibe\u002Fshell-tool\u002Fsessions\u002F`. Long-running\ncommands can be left alive with `background = true`, and interactive commands can\nbe driven with the matching stdin tool.\n\nCommands Vibe runs on the host (shell tools, background processes and `!`\ncommands) get `VIBE_SESSION=1` in their environment, so a script can tell it\nruns under Vibe. Commands in a sandbox use the sandbox's own environment.\n\nPOSIX `bash` reads permissions, allowlists, and denylists from `[tools.bash]`.\nNative Windows `git_bash` reads them from `[tools.git_bash]`; native Windows\n`powershell` reads them from `[tools.powershell]`. Neither Windows tool reads\n`[tools.bash]`. Git Bash is preferred when Vibe can resolve a usable `bash.exe`\nfrom PATH, Git for Windows, or standard Git install locations. If Git Bash is\nunavailable, the PowerShell resolution order is `pwsh.exe`, then\n`powershell.exe`. `cmd.exe` is not used by the managed Windows shell tools.\nOutput polling uses byte offset cursors\n(`cursor` \u002F `next_cursor`), `max_bytes` caps per-call inline output, and\n`max_inline_bytes` configures the default cap.\n\n**Special case — `find` command:** Even if `find` is in the bash allowlist,\nVibe detects `-exec`, `-execdir`, `-ok`, and `-okdir` predicates and will\nprompt for user permission before running the command.\n\n**Outside paths:** a shell command naming a path outside the working directory\n(`cat \u002Fetc\u002Fhosts`, `ls ..\u002Fother`, or a workdir symlink pointing out such as\n`cat link`) asks for that path, even when the command is allowlisted or the\nshell is `permission = \"always\"`; `outside_workdir = \"deny\"` refuses it instead.\n`\u002Fdev\u002Fnull` and the standard streams do not count.\n\n#### File Tool Permission Resolution\n\nFile-based tools (`read`, `grep`, `write_file`, `edit`) resolve\npermissions in this order (first match wins):\n\n1. **Scratchpad** path → always allowed\n2. **denylist** glob match → always denied\n3. **allowlist** glob match → always allowed\n4. **sensitive_patterns** match → requires approval\n5. **Outside workdir** → requires approval, or denied if `permission = \"never\"`\n   or `outside_workdir = \"deny\"`\n6. **Default** → uses the tool's `permission` setting\n\nThe **denylist** is checked before the allowlist — a path matching both lists\nis denied. Both are checked before the outside-workdir boundary, so the\nallowlist can still auto-approve access to directories outside the project.\n\n#### Outside-Workdir Access\n\n`outside_workdir` covers file and shell calls reaching outside the working\ndirectory and its `--add-dir` directories: `\"ask\"` (default) prompts, `\"deny\"`\nrefuses with a tool error and the turn continues. Set it globally or per tool:\n\n```toml\noutside_workdir = \"deny\"\n\n[tools.read_file]\noutside_workdir = \"ask\"  # per-tool override\nallowlist = [\"vibe-path:directory_recursive:\u002Fabs\u002Fpath\u002Fto\u002Fshared\"]  # stays allowed\n```\n\n- Allowlisted paths, `--add-dir` directories and the scratchpad are never\n  refused. `permission = \"always\"` does not bypass the check.\n- Shells only check paths they can read statically, such as the arguments of\n  `cat`, `cp` or `ls`. Anything else (interpreter code, dynamic paths, relative\n  paths after a `cd` in the same command, such as `cd sub && cat link` with\n  `link` pointing out) follows the shell's normal permission, so a shell that\n  must stay inside should not be `always`.\n- On Windows a call is refused if any shell tool denies it: override both\n  `git_bash` and `powershell`. PowerShell treats `\u002Fdev\u002Fnull` as a path; use\n  `$null`.\n- Auto-approve skips the check, like the denylists.\n\n### Skill Configuration\n\n```toml\n# Additional skill search paths\nskill_paths = [\"\u002Fpath\u002Fto\u002Fcustom\u002Fskills\"]\n\n# Enable only specific skills\nenabled_skills = [\"vibe\", \"custom-*\"]\n\n# Disable specific skills\ndisabled_skills = [\"experimental-*\"]\n```\n\n### Agent Configuration\n\n```toml\n# Additional agent search paths\nagent_paths = [\"\u002Fpath\u002Fto\u002Fcustom\u002Fagents\"]\n\n# Enable\u002Fdisable agents\nenabled_agents = [\"ask\", \"accept-edits\"]\ndisabled_agents = [\"auto-approve\"]\n\n# Forbid the auto-approve (YOLO) mode entirely: hides the \"auto-approve\" agent\n# (and any custom agent with safety = \"yolo\") and ignores --auto-approve\u002F--yolo.\n# Per-tool permissions still apply.\n# Intended for admin-managed (enterprise) config.\nenable_auto_approve = false\n\n# Opt-in builtin agents (only affects agents with install_required=True, e.g. lean)\ninstalled_agents = [\"lean\"]\n\n# Agent profile to use when --agent is not passed\n# (default: \"accept-edits\"). Valid values: \"ask\", \"accept-edits\",\n# \"auto-approve\", \"lean\" (only when listed in installed_agents), or any\n# custom agent name from ~\u002F.vibe\u002Fagents\u002F or .vibe\u002Fagents\u002F. Subagents\n# (e.g. \"explore\") are rejected. Applies in both interactive and programmatic\n# (-p\u002F--prompt) mode.\ndefault_agent = \"ask\"\n```\n\n### MCP Servers\n\nRemote MCP servers can be added non-interactively from the shell:\n\n```bash\nvibe mcp add mistralai \\\n  --url https:\u002F\u002Fapi.mistral.ai\u002Fmcp \\\n  --transport streamable-http \\\n  --api-key-env MISTRAL_API_KEY\n\nvibe mcp add linear \\\n  --url https:\u002F\u002Fmcp.linear.app\u002Fmcp\n\nvibe mcp remove mistralai\n```\n\nStatic auth is selected when `--api-key-env` or `--header` is provided.\nOtherwise the server uses OAuth and starts browser login by default. Pass\n`--no-login` to only persist the OAuth configuration. Run\n`vibe mcp add --help` for all supported authentication and timeout options.\nUse `vibe mcp remove \u003Cname>` to remove a server from the user configuration;\nstored OAuth credentials are deleted when available.\n\n`vibe mcp add NAME --url URL` adds remote servers, including static\nauthentication with `--api-key-env`, `--api-key-header`, `--api-key-format`,\nand repeatable `--header NAME=VALUE`.\nAdd local standard input\u002Foutput (stdio) servers with\n`vibe mcp add NAME --transport stdio --command COMMAND`, repeatable\n`--arg VALUE` (use `--arg=-y` for flags), and `--env NAME=VALUE`.\nA string stdio `command` is split into words like a shell line; on native\nWindows a backslash is a path separator, not an escape, so quote a path that\ncontains spaces: `command = '\"C:\\Program Files\\tool\\tool.exe\" --stdio'`.\nSet `--startup-timeout-sec` and `--tool-timeout-sec` for either transport.\nThe legacy `vibe mcp add URL --name NAME` form is also accepted.\nShell `add` and `remove NAME` update user config without a chat session;\n`remove` is not a slash command.\n\nHosted OAuth MCP servers can also be added from inside Vibe:\n\n```text\n\u002Fmcp add https:\u002F\u002Fmcp.linear.app\u002Fmcp\n\u002Fmcp add https:\u002F\u002Fmcp.example.com\u002Fmcp --name docs --scope read --transport http --no-login\n```\n\n`\u002Fmcp add` is OAuth-only. It writes `auth.type = \"oauth\"` with optional\nscopes and starts login by default. It uses `transport = \"streamable-http\"`\nunless you pass `--transport http`. Pass `--no-login` to add the server without\nstarting OAuth login. The shortcut supports `streamable-http` and `http`\ntransports.\n\n```toml\n[[mcp_servers]]\nname = \"my-server\"\ntransport = \"stdio\"\ncommand = \"npx\"\nargs = [\"-y\", \"@my\u002Fmcp-server\"]\n\n[[mcp_servers]]\nname = \"remote-server\"\ntransport = \"http\"\nurl = \"https:\u002F\u002Fmcp.example.com\"\n\n[mcp_servers.auth]\ntype = \"static\"\napi_key_env = \"MCP_API_KEY\"\napi_key_header = \"Authorization\"\napi_key_format = \"Bearer {token}\"\n\n[[mcp_servers]]\nname = \"linear\"\ntransport = \"streamable-http\"\nurl = \"https:\u002F\u002Fmcp.linear.app\u002Fmcp\"\n\n[mcp_servers.auth]\ntype = \"oauth\"\nscopes = [\"read\", \"write\"]\n# Optional: client_id = \"pre-registered-public-client\"\n# Optional: client_metadata_url = \"https:\u002F\u002Fexample.com\u002Fclient-metadata.json\"\n# Optional: redirect_port = 47823\n```\n\nHTTP MCP servers can use either static auth or OAuth:\n\n- Static auth: legacy `api_key_env` \u002F `headers` keys still work and are\n  promoted to `auth.type = \"static\"` internally.\n- OAuth auth: use `auth.type = \"oauth\"` with `scopes`. Vibe stores tokens\n  in the OS keyring under `mcp-oauth:\u003Calias>:tokens`, dynamic client info\n  under `mcp-oauth:\u003Calias>:client_info`, and config drift fingerprints under\n  `mcp-oauth:\u003Calias>:fingerprint`.\n- Headless environments without an OS keyring cannot store OAuth tokens; use\n  static auth via `api_key_env` instead.\n- For SSH\u002Fremote browser callbacks, forward the loopback port:\n  `ssh -L 47823:127.0.0.1:47823 \u003Chost>`.\n\nMCP sampling is disabled by default. It requires an explicit per-server opt-in\nand a host-provided sampling completion. Vibe's app server does not currently\nprovide that completion, so it does not advertise or service sampling requests.\n\n### Connectors\n\nMistral connectors are auto-discovered when the active provider is Mistral\nand the API key env var is set. Toggle the master switch or hide individual\nconnectors \u002F tools:\n\nThe Unified Harness enables ready connectors by default in memory. It does not write that\ndefault to TOML, and the master switch plus explicit connector, tool, allowlist,\nand denylist settings always take precedence.\n\n```toml\nenable_connectors = true          # Master switch (default: true)\n\n[[connectors]]\nname = \"github\"\ndisabled = true                   # Hide all tools from this connector\n\n[[connectors]]\nname = \"linear\"\ndisabled_tools = [\"delete_issue\"] # Hide selected tools only\n```\n\n### Session Logging\n\n```toml\n[session_logging]\nenabled = true\nsave_dir = \"\"                     # Defaults to ~\u002F.vibe\u002Flogs\u002Fsession\nsession_prefix = \"session\"\ngenerate_titles = true            # Background LLM session titles; false uses the message preview\n```\n\n### Browser Sign-In\n\nBrowser sign-in lets users authenticate through the browser during onboarding.\nMistral providers use default browser sign-in URLs (`console.mistral.ai` \u002F\n`api.mistral.ai`). Custom or renamed providers must configure both URLs:\n\n```toml\n[[providers]]\nbrowser_auth_base_url = \"https:\u002F\u002Fconsole.mistral.ai\"\nbrowser_auth_api_base_url = \"https:\u002F\u002Fconsole.mistral.ai\u002Fapi\"\n```\n\nSplit-horizon deployments (e.g. behind an SAP Cloud Connector) expose the\nconsole under a virtual host the CLI reaches but the server does not know about,\nso it returns sign-in\u002Fpoll URLs on its own public host. Set\n`browser_auth_allow_origin_rewrite = true` on the provider to re-home the\nreturned URLs onto the configured `browser_auth_base_url` \u002F\n`browser_auth_api_base_url` origins instead of rejecting them for an origin\nmismatch (path traversal is still rejected). Only the origin (scheme, host,\nport) is rewritten; the returned URL's path must already sit under the\nconfigured base path, so the connector must preserve the server's path prefix.\nPoint `browser_auth_base_url` at the browser-reachable console and\n`browser_auth_api_base_url` at the CLI-reachable API host. The interactive\nonboarding wizard's custom-domain screen also accepts an optional browser-auth\nAPI base URL and auto-enables this rewrite when its origin differs from the\nconsole origin:\n\n```toml\n[[providers]]\nbrowser_auth_base_url = \"https:\u002F\u002Fconsole.internal.example\"\nbrowser_auth_api_base_url = \"https:\u002F\u002Fconnector.internal.example:443\u002Fapi\"\nbrowser_auth_allow_origin_rewrite = true\n```\n\nThe same split-horizon setups often reach the API through a proxy whose URL\ndiffers from the tenant's public API host that `\u002Fwhoami` advertises. By\ndefault, sign-in and the runtime config self-heal adopt the whoami-advertised\n`api_base`. Set `preserve_api_base = true` on the provider to keep the\nconfigured `api_base` verbatim across sign-in and `\u002Freload`; the chat URL\n(`vibe_base_url`) adoption is unaffected:\n\n```toml\n[[providers]]\napi_base = \"https:\u002F\u002Fconnector.internal.example:443\u002Fv1\"\npreserve_api_base = true\n```\n\nSelf-hosted deployments can point Vibe CLI upgrade and API-key links to their\nLe Chat web deployment, where the Vibe API key is managed. The same URL serves\nthe admin-managed config:\n\n```toml\nvibe_base_url = \"https:\u002F\u002Fchat.mistral.ai\"\n```\n\nWhen no config layer sets `vibe_base_url` and the Mistral provider's `api_base`\nis `https:\u002F\u002Fapi.\u003Cdomain>\u002F...`, Vibe uses `https:\u002F\u002Fchat.\u003Cdomain>` instead of\nthe default. For example, `api_base = \"https:\u002F\u002Fapi.example.com\u002Fv1\"` gives\n`https:\u002F\u002Fchat.example.com`. Any other `api_base`, including Mistral's regional\n`api.eu.mistral.ai` and `api.us.mistral.ai`, keeps the default.\n\nInteractive setup can target a Mistral-compatible deployment instead of the\ndefault `console.mistral.ai` \u002F `api.mistral.ai`. The final credential is always a\nMistral API key. On the auth-method screen pick **Launch browser**, then\n**Other** on the sign-in-target screen, and enter a login domain to complete\nbrowser sign-in. This sets `browser_auth_base_url` (the entered domain) and\nderives `browser_auth_api_base_url` (`DOMAIN\u002Fapi`). The overridden `mistral`\nprovider is persisted to user config so subsequent runs reuse it.\n\nThe wizard reads any custom `browser_auth_base_url` already in `config.toml`:\nchoosing **Other** pre-fills that configured domain so it can be confirmed or\nedited. Choosing **Mistral AI** while a custom domain is configured warns first\nand requires pressing **Enter** again to confirm the reset to the default\ndomain, which is then persisted.\n\n### Hooks\n\nHooks let users run shell commands automatically at lifecycle events. They\nare always available — no flag is required; dropping a `hooks.toml` in place\nis enough.\n\n#### Config and hook types\n\nHooks live in `hooks.toml` files (separate from `config.toml`), discovered in\nthis order:\n\n1. `\u003Cproject>\u002F.vibe\u002Fhooks.toml` — loaded first, only when the folder is\n   trusted.\n2. `~\u002F.vibe\u002Fhooks.toml` — loaded second.\n\nHooks shipped by native Vibe plugins (`ai.mistral.vibe\u002Fhooks.toml`) run too.\nFor one event, hooks run in this order: project `hooks.toml`, project plugins,\n`~\u002F.vibe\u002Fhooks.toml`, then user plugins. A plugin hook runs in the session's\nread-only copy of its plugin with `PLUGIN_ROOT` and `PLUGIN_DATA` set, like the\nplugin's MCP servers; its payload's `cwd` is still the session folder. Hooks imported from Claude Code or Kimi plugins do\nnot run yet: Vibe reports them as a config issue.\n\nA duplicate `name` across the two files is reported as a config issue and the\nproject entry wins. Config-load errors (invalid TOML, missing required\nfields) surface in the TUI as warnings and the offending hook is skipped.\n\n```toml\n[[hooks]]\nname = \"lint\"                       # Required: unique within the file.\ntype = \"post_agent\"                 # Required: see the table below.\ncommand = \"eslint --quiet .\"        # Required: shell command run in cwd.\ntimeout = 60.0                      # Default: 60s for all hooks.\ndescription = \"Run ESLint\"          # Optional.\n\n[[hooks]]\nname = \"build-after-reviewer\"\ntype = \"post_agent\"\nmatch = \"reviewer\"                  # Agent matcher on post_agent (default \"*\").\ncommand = \"make check\"\n\n[[hooks]]\nname = \"deny-rm-rf\"\ntype = \"pre_tool\"\nmatch = \"re:bash|process_start\"     # Tool-name matcher on tool hooks (default \"*\").\nstrict = true                       # pre_tool, post_tool, pre_agent: escalate any failure to block.\ncommand = \"uv run python \u002Fpath\u002Fto\u002Fguard-bash\"\n\n[[hooks]]\nname = \"add-branch\"\ntype = \"pre_agent\"\nmatch = \"main\"                      # Agent matcher, as on post_agent.\ncommand = \"~\u002F.vibe\u002Fhooks\u002Fadd-branch.sh\"\n\n[[hooks]]\nname = \"notify-me\"\ntype = \"notification\"\nmatch = \"re:approval_requested|turn_completed\"  # Notification-type matcher.\ncommand = \"~\u002F.vibe\u002Fhooks\u002Fnotify.sh\"\n\n[[hooks]]\nname = \"load-context\"\ntype = \"session_start\"\nmatch = \"startup\"                   # Source matcher: startup | resume | clear | fork.\ncommand = \"jq -Rs '{hook_specific_output: {additional_context: .}}' .vibe\u002Fcontext.md\"\n\n[[hooks]]\nname = \"export-transcript\"\ntype = \"session_end\"\nmatch = \"exit\"                      # Reason matcher: exit | clear | other.\ncommand = \"~\u002F.vibe\u002Fhooks\u002Fexport.sh\"\n```\n\n| Type | When it runs |\n|---|---|\n| `pre_agent` | Once per turn, after the user sends a message and before the model reads it. |\n| `post_agent` | Once per turn, after the agent finishes responding (no pending tool calls). |\n| `pre_tool` | Per tool call, before the user permission prompt. |\n| `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). |\n| `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. |\n| `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 `\u002Fclear`), `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. |\n| `session_end` | When a main session closes. `reason`: `exit` (Vibe quits, or Vibe Desktop releases the session), `clear` (`\u002Fclear`), `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. |\n\n**Closing.** When Vibe quits (`\u002Fexit`, end of a `vibe -p` run, Vibe Desktop\nquitting), it stops the `session_start` hooks still running at once (their\nsession is over), then waits for the `session_end` and `notification` hooks\nstill running, 5 s at most in all, then stops them. Vibe Desktop may close a\nsession's process sooner. Switching session (`\u002Fnew`,\n`\u002Fresume`, a rewind) does not stop the running hooks; the new session's first\nmessage waits for the old session's `session_end` hooks.\n\n**Matcher syntax** (same as `enabled_tools`): fnmatch glob by default\n(`\"bash\"`, `\"read_*\"`, case-insensitive), or a regex full-match when the\npattern starts with `re:` (`\"re:(read_file|write_file)\"`).\n\n**Agent matching on `post_agent`.** `post_agent` fires after the main agent's\nturns and after every subagent's turns. Its `match` selects the agent that\nfinished its turn, by agent configuration name: the main session's current\nagent (`accept-edits`, `ask`, a custom agent file name), a subagent's\n`config_name` (e.g. `reviewer`, or `\u003Cplugin-name>:\u003Cname>` for a plugin agent), or,\nfor a subagent spawned without one, its parent's agent. The reserved alias `main`\nmatches the main session (root or fork) whatever its agent, and never a\nsubagent; no agent may be named `main`. A mode name also matches the subagents\nthat inherit it, so use `match = \"main\"` to run only after the main agent.\nWithout `match` (or with `\"*\"`), a `post_agent` hook runs for every agent. A\nhook that does not match is skipped silently. `pre_agent` matches agents the\nsame way, for the agent whose turn starts.\n\n**Tool name conventions** for matchers:\n- `match` accepts a tool's short name (`bash`, `read_file`, `process_start`, …)\n  or its qualified name (`file_system.bash`). The `tool_name` field in the\n  payload is always the qualified name (`file_system.bash`, `mcp_\u003Cserver>.\u003Ctool>`\n  for MCP tools), so scripts that read it must expect that form.\n- Commands the agent starts in the background go through `process_start`,\n  which `match = \"bash\"` does not catch. Shell guards should use\n  `match = \"re:bash|process_start\"`.\n- MCP tools: `mcp_{server}.{tool}`, with characters other than letters,\n  digits, `_` and `$` replaced by `_` in both parts (e.g. `mcp_linear.create_issue`\n  for the `create-issue` tool of the `linear` server). Match it as\n  `mcp_linear.create_issue`, `mcp_linear_create_issue`, the bare `create_issue`,\n  or a glob such as `mcp_linear.*`.\n- Connector tools: `connector_{normalized-name}_{remote-tool-name}` (e.g.\n  `connector_Google_Drive_search_files`).\n- There is no `task` tool. Subagents are spawned through `agents_spawn`\n  (`agents.spawn` in the payload). Match with `match = \"agents_spawn\"` and read\n  `tool_input.config_name` (absent for a generic subagent) to discriminate by\n  subagent.\n\nSubagent invocations inherit the parent's hook config. Their hook events are\nlogged to the subagent's session log and don't propagate to the parent's UI.\n\n#### Wire protocol\n\nEvery hook is spawned in `cwd` and receives a JSON object on **stdin**\ndiscriminated by `hook_event_name`:\n\n```json\n\u002F\u002F pre_agent\n{\"hook_event_name\": \"pre_agent\", \"session_id\": \"...\",\n \"parent_session_id\": null, \"transcript_path\": \"...\", \"cwd\": \"...\",\n \"agent_name\": \"accept-edits\", \"turn_id\": \"turn-...\",\n \"prompt\": \"fix the bug\"}          \u002F\u002F the text the user typed; attachments are not included\n\n\u002F\u002F post_agent\n{\"hook_event_name\": \"post_agent\", \"session_id\": \"...\",\n \"parent_session_id\": null, \"transcript_path\": \"...\", \"cwd\": \"...\",\n \"agent_name\": \"accept-edits\"}    \u002F\u002F agent configuration name; null when unknown\n\n\u002F\u002F notification\n{\"hook_event_name\": \"notification\", \"session_id\": \"...\",\n \"parent_session_id\": null, \"transcript_path\": \"...\", \"cwd\": \"...\",\n \"notification_type\": \"approval_requested\",  \u002F\u002F | question_asked | turn_completed | turn_failed\n \"message\": \"Vibe needs your approval to run file_system.bash\",\n \"agent_name\": \"accept-edits\",\n \"tool_name\": \"file_system.bash\"}  \u002F\u002F approval_requested only\n\n\u002F\u002F session_start\n{\"hook_event_name\": \"session_start\", \"session_id\": \"...\",\n \"parent_session_id\": null, \"transcript_path\": \"...\", \"cwd\": \"...\",\n \"source\": \"startup\",              \u002F\u002F | resume | clear | fork\n \"agent_name\": \"accept-edits\", \"model\": \"mistral-medium-3-5\"}  \u002F\u002F active model alias\n\n\u002F\u002F session_end\n{\"hook_event_name\": \"session_end\", \"session_id\": \"...\",\n \"parent_session_id\": null, \"transcript_path\": \"...\", \"cwd\": \"...\",\n \"reason\": \"exit\"}                 \u002F\u002F | clear | other\n\n\u002F\u002F pre_tool\n{\"hook_event_name\": \"pre_tool\", \"session_id\": \"...\", \"parent_session_id\": null,\n \"transcript_path\": \"...\", \"cwd\": \"...\",\n \"tool_name\": \"file_system.bash\", \"tool_call_id\": \"call_42\",\n \"tool_input\": {\"command\": \"ls\"}}\n\n\u002F\u002F post_tool\n{\"hook_event_name\": \"post_tool\", \"session_id\": \"...\", \"parent_session_id\": null,\n \"transcript_path\": \"...\", \"cwd\": \"...\",\n \"tool_name\": \"file_system.bash\", \"tool_call_id\": \"call_42\",\n \"tool_input\": {\"command\": \"ls\"},\n \"tool_status\": \"success\",         \u002F\u002F success | failure; a non-zero exit code is still success\n \"tool_output\": {\"command\": \"ls\", \"stdout\": \"...\", \"stderr\": \"\", \"returncode\": 0,\n                 \"was_truncated\": false, \"timed_out\": false},  \u002F\u002F structured result on success; null on failure\n \"tool_output_text\": \"...\",         \u002F\u002F current text the LLM will see; mutable by prior hooks\n \"tool_error\": null,                \u002F\u002F populated on failure\n \"duration_ms\": 0}                  \u002F\u002F not reported yet: always 0\n```\n\n`parent_session_id` is set when running inside a subagent. Exceeding\n`timeout` kills the whole process tree.\n\nA hook signals back via its **exit code** and **stdout** (stderr is reserved\nfor diagnostics — Vibe never parses it for control):\n\n| Exit | Stdout | Behavior |\n|---|---|---|\n| `0` | empty | Pass through (no action). |\n| `0` | valid structured-response JSON object (schema below) | Act per the JSON fields. |\n| `0` | anything else (free-form text, broken JSON, scalar\u002Farray, schema mismatch) | Failure path (see below). The parse error is in the message. |\n| non-zero \u002F timeout \u002F spawn failure | — | Failure path. Reason taken from stderr, then stdout, then the exit code. |\n\nStructured-response schema:\n\n```json\n{\n  \"decision\": \"allow\" | \"deny\",          \u002F\u002F optional; default \"allow\"\n  \"reason\": \"string\",                     \u002F\u002F required when decision == \"deny\"\n  \"system_message\": \"string\",             \u002F\u002F optional UI note\n  \"hook_specific_output\": {\n    \"tool_input\": { ... },                \u002F\u002F pre_tool only\n    \"prompt\": \"string\",                   \u002F\u002F pre_agent only\n    \"additional_context\": \"string\"        \u002F\u002F post_tool, pre_agent and session_start only\n  }\n}\n```\n\n`notification` and `session_end` hooks' stdout is ignored. A `session_start`\nhook reads only `additional_context` and `system_message`: it cannot block the\nsession.\n\n- `hook_specific_output.additional_context` (`session_start`): text added, out\n  of the user's sight, to the session's next user message, for the model. With\n  several `session_start` hooks, their texts are joined in run order. If a\n  `pre_agent` hook blocks that message, the text waits for the next one.\n- `system_message` (`session_start`): shown as the hook's line in the\n  conversation (`✓ [hook-name] message`), not among the startup warnings.\n\nUnknown fields are tolerated at every level. Fields that aren't meaningful\nfor the current hook type are silently ignored.\n\n**Don't self-name in `system_message` or `reason`** — the UI prefixes\nhook-end-event content with `[hook-name]` automatically, and `pre_tool`\ndenials are wrapped as ``Tool 'X' was denied by hook 'Y': {reason}`` before\nthe LLM sees them. A hook that writes ``\"reason\": \"guard: refused...\"``\nwill produce ``hook 'guard': guard: refused...`` downstream.\n\n`decision: \"deny\"` per hook type:\n\n| Hook | Effect of `decision: \"deny\"` |\n|---|---|\n| `pre_agent` | Block the message: the turn ends, the model never sees it, and the hook's line shows `Blocked the message: \u003Creason>` (or the hook's `system_message`). First deny short-circuits the remaining `pre_agent` hooks. |\n| `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. |\n| `post_tool` | Replace `tool_output_text` with `reason`. Pipeline continues; subsequent hooks see the replacement. |\n| `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. |\n\nEvent-specific payloads:\n\n- `hook_specific_output.tool_input` (`pre_tool`): full replacement of the\n  model's arguments. Vibe re-validates against the tool's schema **after each\n  rewriting hook** — the first invalid rewrite aborts the chain and\n  synthesizes a denial attributing the failure to that hook. Rewrites\n  compose: hook N receives `tool_input` as rewritten by hooks 1..N-1.\n- `hook_specific_output.additional_context` (`post_tool`): text appended\n  (with `\n`) to the current `tool_output_text`. Composes with a same-hook\n  `decision: \"deny\"`: deny replaces first, then `additional_context` is\n  appended to the replacement.\n- `hook_specific_output.prompt` (`pre_agent`): replaces the text the user\n  typed; attachments are kept. The model sees the new text; the user's\n  transcript still shows what they typed. Rewrites compose: hook N receives\n  the `prompt` as rewritten by hooks 1..N-1.\n- `hook_specific_output.additional_context` (`pre_agent`): text added after\n  the user's message, for the model only. It stays in the conversation for\n  the following turns. With `prompt` in the same response, the text is\n  replaced first, then the context is added.\n\nWhen a `pre_agent` hook rewrites the message or adds context, its line in the\ntranscript previews what the model gets instead (one line, cut at 80\ncharacters), unless the hook returns its own `system_message`. The session\ntitle is generated from what the model got as well: rewritten-away text and\nblocked messages never reach the title model. A message steered into the turn\nwhile its `pre_agent` hooks run waits for them to finish.\n\n**Failure path.** Any failure (non-zero exit, timeout, spawn failure,\nnon-conforming stdout) emits a UI warning and lets the gated action proceed\n(fail open). With `strict = true`:\n\n| Hook | Strict failure escalates to |\n|---|---|\n| `pre_agent` | Block the message. |\n| `pre_tool` | Deny the tool call with the failure reason. |\n| `post_tool` | Clear `tool_output_text` (replace with empty). |\n\n`strict` is forbidden on `post_agent`, `notification`, `session_start` and\n`session_end`. A failing `session_start` hook shows a warning and the session\ngoes on without its context.\n\n#### Execution semantics\n\n- A hook `command` is a shell line, run in the session working directory. On\n  macOS and Linux it runs in `\u002Fbin\u002Fsh`. On native Windows it runs in the same\n  shell as the `bash` tool: Git Bash when it is installed, otherwise\n  PowerShell. Write Windows hooks in that shell's syntax.\n- On macOS and Linux, what a hook starts in the background (`cmd &`) is killed\n  when the hook exits. A hook that must leave work running has to move it to a\n  new session itself: `setsid cmd &` on Linux; macOS has no `setsid` command,\n  so use `perl -MPOSIX -e 'POSIX::setsid(); exec @ARGV' cmd &`.\n- Hooks of the same type fire sequentially in load order (project file first,\n  then user file; declaration order within each file). `notification` and\n  `session_end` hooks are the exception: they start together, in the\n  background.\n- Tool calls within a single LLM turn run **concurrently**; each call's hook\n  chain runs serially but the chains run in parallel across calls. Hooks\n  that touch shared state (filesystem, env) must coordinate themselves.\n- `pre_tool` rewrites take effect everywhere downstream: the user\n  permission prompt sees the rewritten arguments, the tool runs with them,\n  and the assistant message is patched so subsequent LLM turns reflect what\n  actually ran.\n\n### Pattern Matching\n\nTool, skill, and agent names support three matching modes:\n- **Exact**: `\"bash\"`, `\"read_file\"`\n- **Glob**: `\"bash*\"`, `\"mcp_*\"`\n- **Regex**: `\"re:^serena_.*$\"` (full match, case-insensitive)\n\n## CLI Parameters\n\n```\nvibe [PROMPT]                       # Start interactive session with optional prompt\nvibe -p TEXT \u002F --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\nvibe -p TEXT --auto-approve          # Programmatic mode with all tool calls approved\nvibe -p TEXT --agent lean --yolo      # Lean mode with all tool calls approved\nvibe --agent NAME                   # Select agent profile (falls back to `default_agent` config)\nvibe --auto-approve \u002F --yolo         # Approve all tool calls. New session on ask\u002Faccept-edits\u002Fsmart-approve: starts the auto-approve agent. Custom agent or resumed session: keeps its agent, every call approved\nvibe --workdir DIR                  # Change working directory\nvibe --worktree NAME                # Create\u002Freuse a git worktree under $VIBE_HOME\u002Fworktrees 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\u002F--check-upgrade.\nvibe --worktree                     # Same, but Vibe picks an unused name from the prompt (a random slug when there is no prompt) on a vibe\u002F\u003Cname> branch, and never reuses an existing worktree. The prompt must precede the flag or follow a `--`, since --worktree otherwise reads it as NAME.\nvibe --add-dir DIR                  # Extra working dir loaded for context (repeatable). Implicitly trusted.\nvibe --trust                        # Trust cwd for this invocation only (not persisted). Skips the trust prompt.\nvibe -c \u002F --continue                # Continue this terminal's last session in cwd, else the latest in cwd\nvibe --resume [SESSION_ID]          # Resume a specific session\nvibe -v \u002F --version                 # Show version\nvibe --setup                        # Run onboarding\u002Fsetup\nvibe update \u002F vibe --check-upgrade  # Check for a Vibe update now, prompt to install it, and exit\nvibe --max-turns N                  # Max assistant turns (programmatic mode)\nvibe --max-price DOLLARS            # Max cumulative run cost, root + subagents; models priced 0 are free (programmatic mode)\nvibe --max-tokens N                 # Max cumulative run tokens, root + subagents (programmatic mode)\nvibe --system-prompt-file PATH      # Replace Vibe's own system prompt instructions with PATH's text (programmatic mode)\nvibe --model ALIAS                  # Model alias for this run, not saved (programmatic mode)\nvibe --enabled-tools TOOL           # Enable specific tools (repeatable)\nvibe --disabled-tools TOOL          # Disable specific tools (repeatable)\nvibe --output text|json|streaming   # Output format (programmatic mode)\n```\n\nProgrammatic mode never prompts for approval: a tool call that needs it is\ndenied unless the run approves calls without a prompt. `--auto-approve` (or\n`--agent auto-approve`) approves them, and `--smart-approve` lets a classifier\napprove the ones it judges safe. Each denied call is printed on stderr as\n`Tool call denied: \u003Ctool> (\u003Creason>)` and listed in the export's `warnings`.\nA text run whose agent finishes without an answer says so on stderr.\n\n## Built-in Agents\n\nThere are two kinds of agents:\n- **Agents** are user-facing profiles selectable via `--agent` or `Shift+Tab`.\n  They configure the model's behavior, tools, and system prompt.\n- **Subagents** are model-facing: the model can spawn them autonomously to delegate\n  subtasks (e.g. exploring the codebase). Users cannot select subagents directly.\n\n### Agents\n\n- **ask**: Requests approval for tool executions\n- **accept-edits**: Default agent; auto-approves file edits but asks for other tools\n- **auto-approve**: Auto-approves all tool calls\n- **lean**: Specialized Lean 4 proof assistant. Not available by default — must be\n  installed with `\u002Fleanstall` (removed with `\u002Funleanstall`). Use `--agent lean\n  --auto-approve` or `--agent lean --yolo` to run Lean mode without tool prompts.\n\n### Subagents\n\n- **explore**: Read-only codebase exploration subagent with grep, file reading,\n  and skill loading. Spawned by the model, not selectable by the user.\n\nCustom agents are TOML files in `~\u002F.vibe\u002Fagents\u002FNAME.toml`. One with\n`agent_type = \"subagent\"` is a custom subagent the model can spawn. It can load\nevery skill the parent session has, unless its file sets `enabled_skills` or\n`disabled_skills`; these accept the same patterns as in `config.toml`.\n\nA custom subagent (`agent_type = \"subagent\"`) runs with the session's hooks.\nEach of its tool calls must pass both the session's rules and its own\n`[tools.NAME]` rules (`permission`, `allowlist`, `denylist`): a profile can\nrefuse or prompt for what the session allows, never allow what the session\nrefuses or prompts for. `enabled_tools`\u002F`disabled_tools` likewise only narrow\nthe session's tools. Approvals given to a subagent's prompts apply to the whole\nsession. Under `--auto-approve`, a subagent's calls run without consulting\nallowlists or denylists, like the main agent's; a tool its profile sets to\n`never` stays refused.\nTo use a custom subagent, the model must spawn it by its agent type\n(`config_name`); a subagent spawned with only a name inherits the main agent's\nconfiguration.\n\nA subagent's instructions (`instructions`, or the prompt `system_prompt_id`\nnames) are limited to 65,536 characters; the primary agent's prompt has no such\nlimit. An agent file that cannot be used (invalid TOML, an unknown `safety` or\n`agent_type`, an invalid setting, a missing prompt, instructions over the limit,\nno `description`) is not offered to the model. Vibe reports it as a config issue\nnaming the file and the failing field: in the startup Warnings box in the TUI,\na stderr line and an export warning with `-p`. Spawning that type fails with\n`subagent_type_rejected` and the same reason.\n\nA custom subagent runs on the session's model unless its file sets\n`active_model` to an alias from `[[models]]`; its children then run on that\nmodel (name, temperature, thinking, provider, and the model's context window,\ncompaction threshold and image support). The provider's endpoint and\ncredentials always come from the session's `[[providers]]`: a provider the\nagent file redefines is ignored for routing. An alias that is unknown, excluded\nby `allowed_models`, or served by a provider the session does not define\nrejects the agent file like the cases above; Vibe never falls back to the\nsession's model.\n\n## Built-in Slash Commands\n\n- `\u002Fhelp` - Show help message\n- `\u002Fconfig` - Full-screen settings browser. Fields show their value and origin layer (default \u002F TOML \u002F env \u002F 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 (`~\u002F.vibe\u002Fconfig.toml`) by default, `Tab` cycles the save target through the project config (`.vibe\u002Fconfig.toml`, when the project layer is active) and the ephemeral session override (until restart), and `Ctrl+R` clears the field one writable layer at a time toward the default. The `tools` field opens a grouped tool list with a per-tool config editor (permission, allow\u002Fdeny lists, `Ctrl+E` for raw JSON). Enabling\u002Fdisabling whole MCP servers or connectors stays in `\u002Fmcp`.\n- `\u002Fmodel` - Select active model. The footer shows the active model and its thinking level (`Name[level]`) before the context gauge; clicking it opens `\u002Fmodel` and keeps the prompt being typed (only while idle, like the typed command).\n- `\u002Fthinking` - Select thinking level\n- `\u002Ftheme` - Select UI theme; `auto` follows terminal\u002FOS appearance, `mistral-dark` and `mistral-light` use the Mistral brand palette (persisted in config)\n- `\u002Freload` - Reload configuration, agent instructions, and skills from disk (skills added or edited on disk are also picked up on their own while `file_watcher_for_autocomplete` is on, except in sandboxed sessions)\n- `\u002Fclear`, `\u002Fnew` - Start a new conversation. Optionally pass a prompt to seed it\n- `\u002Flog` - Show path to current interaction log file\n- `\u002Flog-level` - Show or set the log level. `\u002Flog-level` prints the full chain\n  (session, env, config, effective); `\u002Flog-level set \u003CLEVEL>` sets a\n  process-lifetime override; `\u002Flog-level set-global \u003CLEVEL>` also persists to\n  config.toml; `\u002Flog-level unset` clears the session override. LEVEL is one of\n  DEBUG, INFO, WARNING, ERROR, CRITICAL.\n- `\u002Fdebug` - Toggle debug console\n- `\u002Fcompact` - Compact model context by summarizing. The session ID and visible\n  conversation stay intact; the auto title is refreshed to reflect the\n  compacted conversation (unless renamed manually).\n- `\u002Frename \u003Ctitle>` - Set a manual session title. Persists to `meta.json`\n  (`title_source=manual`), updates the terminal tab title, and is never\n  overwritten by automatic title generation.\n- `\u002Fretry [additional instructions]` - Run again a model response that failed\n  with a backend error or was interrupted with Esc. The new reply replaces the\n  unfinished one and its error or interrupt row. Optional instructions are\n  passed to the model for the retry. Relevant error messages also hint at this\n  command; with nothing to retry, it says so.\n- `\u002Fpr` - List the session's pull requests and merge requests (needs\n  `enable_pr_monitoring`, on by default). Opening it refreshes their status.\n  Enter opens the highlighted one, `c` its first failing CI check, `f` drafts a\n  prompt asking the agent to fix what blocks it (shown only when it needs action).\n- `\u002Fstatus` - Display agent statistics\n- `\u002Fcontext` - Show what fills the context window: a full-width gauge of the\n  window split by category (system prompt, tool definitions including MCP and\n  connector tools, skills, conversation), then free space and the\n  auto-compaction buffer (marked where compaction triggers), with a legend of\n  each category's share and estimated tokens.\n  The total comes from the model API's last call; the repartition is estimated\n  from each category's share of the characters that call actually sent. The\n  footer's token counter opens the same view when clicked (also while a turn\n  runs); a subagent shows its own window. Estimates appear only once the\n  conversation has started (at least one model call); a resumed session shows\n  its total alone until its next model call.\n- `\u002Fwhoami` - Display the Mistral signed-in user, workspace, and plan\n- `\u002Fcopy` - Copy the last agent message to the clipboard\n- `\u002Fpaste-image` - Paste an image from the OS clipboard into the prompt.\n  **macOS only** — the command is not registered on Linux or Windows.\n- `\u002Fvoice` - Configure voice settings. With voice mode on, `Ctrl+T` starts or stops recording\n- `\u002Fmcp` (or `\u002Fconnectors`) - Display MCP servers and connector status. The\n  browser opens on the first item; press Up or Left to move into the fuzzy-search\n  bar, and Up again to wrap to the last item. Pass a server or connector name to\n  list its tools or open its auth panel when authentication is required\n- `\u002Fmcp add \u003Curl>` - Add a hosted OAuth MCP server. Supports `--name \u003Calias>`,\n  repeatable `--scope \u003Cscope>`, `--transport \u003Chttp|streamable-http>`,\n  `--no-login`, and `--allow-insecure-http` (permit a plaintext `http:\u002F\u002F` URL on\n  a non-localhost host such as a LAN server). Starts OAuth login by default.\n  OAuth-only; use `vibe mcp add \u003Cname> --url \u003Curl> --api-key-env \u003Cvar>` for\n  API-key\u002Fstatic auth.\n- `vibe mcp remove \u003Cname>` - Remove an MCP server from the user configuration\n  and delete its stored OAuth credentials when available.\n- `\u002Fmcp status` - Display MCP auth state (`ok`, `needs_auth`, `static`, `stdio`)\n- `\u002Fmcp login \u003Calias>` - Start OAuth login for an MCP server\n- `\u002Fmcp logout \u003Calias>` - Log out from an MCP server and delete stored OAuth\n  secrets\n- `\u002Fresume` (or `\u002Fcontinue`) - Browse and resume past sessions for the current\n  folder. The picker header shows the folder being listed. Press `d` twice to\n  delete a saved session; the active session cannot be deleted here.\n- `\u002Frewind` - Rewind to a previous message. Also triggered by pressing `Esc`\n  twice on an empty input; if the input has content, the first double-`Esc`\n  clears it instead. In the rewind panel: `↑\u002F↓` pick option, `Shift+↑\u002F↓`\n  scroll, `←`\u002F`Esc` edit previous message, `→` edit next message, `Enter`\n  accept, `q` quit.\n- `\u002Fbranch` (alias `\u002Ffork`) - Fork the current conversation into a new resumable\n  session, leaving this session unchanged. A panel in place of the input shows\n  the `vibe --resume \u003Cid>` command that resumes the copy; `Esc` or `Enter`\n  closes it. While the agent is working, the copy stops before the running\n  turn, so during the first turn it is empty. After an automatic compaction\n  during the turn, `\u002Fbranch` is refused until the turn ends.\n- `\u002Floop [schedule] [prompt]` - With the Unified Harness, ask the model to\n  schedule a recurring prompt, e.g. `\u002Floop every two minutes check the build`\n  or `\u002Floop weekdays at 9am review CI`.\n  - The full message is sent unchanged to the model, which uses the `cron` tool\n    to schedule, list, cancel, or clear prompts. There are no TUI management\n    subcommands; bare `\u002Floop` also goes to the model. Busy submissions queue.\n  - Fixed intervals have a 30-second minimum; calendar schedules use five-field\n    cron in the machine's local timezone. Maximum 50 schedules per session.\n  - Prompts run only while the session is live and idle, without catch-up bursts.\n    Schedules persist in `scheduled-loops.json` beside the Unified session and\n    are restored on resume. They do not run while Vibe is closed. `\u002Fclear`\n    starts a conversation with no schedules; the cleared session keeps its own\n    and runs them again when resumed. `\u002Fbranch` copies them.\n  - Each agent has its own schedules: a subagent calling `cron` schedules,\n    lists, cancels, and clears only its own prompts, never its parent's or a\n    sibling's. A subagent's prompt fires as a message to that subagent once it\n    is idle, and its result reaches the parent like any other subagent result,\n    without starting a parent turn. Its schedules persist beside the subagent's\n    session and stop with it. `\u002Floop` and the `loops\u002F*` client methods still\n    show and manage only the main session's schedules.\n- `\u002Fproxy-setup` - Configure proxy and SSL certificate settings\n- `\u002Fleanstall` - Install the Lean 4 agent (leanstral)\n- `\u002Funleanstall` - Uninstall the Lean 4 agent\n- `\u002Fplugins` - Display the plugins this session is running (Unified Harness only). Shows each plugin's name, scope, source format, content digest, and\n  components (skills, MCP servers, agents, hooks, knowledge, connectors, tools).\n  Press `r` inside the view to reload.\n- `\u002Freload-plugins` - Re-pin this session's plugins and report what changed\n  (Unified Harness only). Re-discovers plugins from disk, re-pins the\n  snapshot, and prints a diff of added, removed, and updated plugins.\n- `\u002Fskills` - Browse and manage skills. My Skills shows one row per installed\n  skill, the one its name resolves to: enabled (`●`) or disabled (`○`), Scope,\n  Source (Local, Shared or Plugin) and, for shared skills, the pinned version,\n  with `● New vN` when a number-pinned shared skill has a newer version. A\n  locked skill (from a plugin, or fixed by `enabled_skills` or a\n  `disabled_skills` pattern) shows its marker in the warning color and cannot be\n  toggled. Narrow terminals drop the Version, Source and Scope columns, in that\n  order, and on Shared the Version and Sharing columns. `\u002F` searches names and\n  descriptions; `Space` turns the highlighted skill on or off for every scope\n  (written to `disabled_skills`) when its name loads that skill; the highlighted\n  skill's body shows under the list, or beside it from 140 columns, and `Enter`\n  opens its page with its description and the body of the highlighted scope's\n  pin under or beside the rows, which PageUp and PageDown scroll. With\n  `experimental_enable_registry_skills`, My Skills and Shared are tabs switched\n  with `Tab`, Shared listing the shared skills you can still add, `d` then `d`\n  again on the My Skills row of a shared skill removes it from every scope, and\n  a shared skill's page has a row per scope (Global, and Project inside a\n  project): `vN pinned`, `alias → vN tracking`, or `not pinned`, marked\n  `(shadowed)` when the name loads another pin. Enter on a scope opens its\n  picker of aliases and versions on the choice in effect, else on the other\n  scope's, previewing the highlighted version's body: picking one pins that\n  scope at once, and `Unpin from \u003Cscope>` drops just that pin (not the last one,\n  which is what Remove is for). Enabled, on the page of the skill its name\n  loads, switches the skill in every scope, and Remove, pressed twice, unpins it\n  from every scope. A shared skill not added yet starts out tracking latest in\n  Global, or in Project when another shared skill of its name holds Global,\n  since a scope pins one skill per name and pinning over another is refused;\n  each scope takes its own choice, `Don't pin here` drops one, and nothing is\n  written until Add to My Skills. Each new version of a pinned skill is\n  announced once, when a session starts. A session fetches the pinned skill\n  bodies missing from this machine and moves alias pins to their current version\n  when it starts, and again when its project changes. A sandboxed session copies\n  the versions it loads into its sandbox, and keeps its older copy of a skill\n  that tracks an alias when it cannot copy the new one. When the session's fetch\n  succeeds, at most once a day per machine, it deletes stored versions no\n  session has used for 30 days, if the registry still serves them, so a later\n  sync can fetch them again. It keeps each skill's newest stored version, every\n  version pinned globally, in this project or in another project a session read,\n  the version each alias pin last resolved to, and every version a running\n  session loads. The 30 days count from when a session last stored or used the\n  version, or else from the first cleanup that saw it. Vibe releases from before\n  this cleanup do not record what they load, so a version only they load through\n  a project pin on its number can still be deleted. The command shadows a user\n  skill named `skills`.\n- `\u002Fdata-retention` - Show data retention information\n- `\u002Frelease-notes` (alias `\u002Fwhatsnew`) - Browse the release notes of every Vibe\n  version, newest first, in a full-screen, searchable pager. When a new\n  session starts after an upgrade (not on resume), Vibe shows the notes of\n  every release since the last one seen\n  (the newest five, then a pointer to `\u002Frelease-notes`); the last seen version is\n  `seen_whats_new_version` in `~\u002F.vibe\u002Fcache.toml`.\n- `\u002Fexit` - Exit the application\n\n## File Mentions (`@`)\n\nType `@` in the chat input to autocomplete files and folders. A bare `@`\nlists non-hidden immediate children directly from the filesystem for fast\nbrowsing. Once you type a path character, Git workspaces use tracked and\nnon-ignored untracked paths, including nested `.gitignore` rules; outside Git\nthe picker falls back to the project tree. Pressing Tab\u002FEnter inserts the\nchosen path. Your message text is sent as-is (the `@path` stays in the prompt);\nbehavior then depends on the mention kind:\n\n- **Text files** trigger a synthetic `read_file` tool call injected right\n  after your message, so the file content arrives as a fresh tool result\n  every turn (no caching\u002Fdedup). The same limits as the `read_file` tool\n  apply (~2000 lines \u002F 64 KB per call; larger files are truncated or\n  reported as an error result). Re-mentioning a file always re-reads it.\n- **Folders** are not read automatically — the path stays in your message\n  text and the agent can `read_file`\u002F`grep` it on demand.\n- **Image files** (`.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`) become image\n  attachments — sent alongside the prompt as native multimodal content for\n  vision-capable models.\n\nImage attachments:\n\n- Require `supports_images = true` on the active model in `config.toml`.\n  The legacy loop rejects an image its model cannot read; under\n  the Unified Harness the agent is shown a description of the image\n  instead, and with no describer reachable the CLI refuses a pasted image.\n- The describer is picked automatically: any `supports_images` model on\n  the active model's **own** provider, no config needed. Same provider\n  means same key and same endpoint, so no image goes anywhere the session\n  was not already talking. A `vision_model` in `config.toml` overrides\n  that choice and is the only way to reach another provider.\n- Each image is then described once as the turn's input is prepared, and\n  the agent receives the text inside an `\u003Cimage alias=\"...\">` block in\n  place of the pixels. The user's prompt steers what the describer looks\n  for, and the description is reused for the rest of the session. The\n  describer never reasons, whatever its `thinking` says: the trace is\n  charged against the description budget and buys nothing on a\n  transcription. Claude models that can't turn thinking off (Opus 5.5,\n  Sonnet 5.5, Fable 5) reason at their lowest level instead.\n- A describe that fails does not fail the turn. The agent gets a\n  placeholder saying the image could not be read, and a warning names the\n  image and the provider's reason.\n- With no describer reachable the image is left alone, and a model that\n  cannot read images gets `[Image]` and, for an image from a file, a\n  `file:\u002F\u002F` link to it.\n- Snapshotted into `\u003Csession_dir>\u002Fattachments\u002F\u003Csha1>.\u003Cext>` so that\n  resumed sessions stay reproducible even if the source file is moved.\n- Capped at 10 MiB per image and 8 images per message.\n- Out-of-project paths work via `@\u002Fabs\u002Fpath\u002Fto.png` (the picker only\n  suggests project files, but the `@`-parser accepts absolute paths).\n  Drag-and-drop from Finder into Terminal, iTerm2, or Ghostty is\n  intercepted at paste time: if the pasted content is a standalone existing\n  absolute or home-relative file or folder (or a newline-delimited list of\n  them), the input automatically prepends `@` and quotes paths containing\n  spaces. This applies to text files and folders (images become\n  `[Image #N]` placeholders, see below); pasted prose, relative paths, and\n  missing paths are left unchanged.\n- **Image copy\u002Fpaste from the clipboard** (**macOS only** for now):\n  writes the image to a persistent temporary PNG, inserts a numbered\n  `[Image #N]` placeholder at the cursor, then snapshots it under the\n  session attachments on submit. A pasted standalone image path becomes a\n  placeholder too. In a `!` shell command the raw path is inserted instead.\n  Two entry points:\n  1. `Ctrl+V` keybinding inside the prompt.\n  2. `\u002Fpaste-image` slash command.\n\n  Uses `osascript` with a TIFF→PNG fallback via `sips`. On Linux and\n  Windows the binding and the slash command are not registered at all,\n  so the feature is invisible to users on those platforms.\n- Rendered in the chat bubble as one dim `attached image:` footer line\n  per image, linking each attachment to its snapshot. Clicking opens the\n  file with the OS default image viewer.\n\n## Footer\n\nThe bottom row of the CLI reads, left to right:\n\n- The working directory, elided from the start (`…\u002Fvibe\u002Fcli-rust`) so the project name stays visible. Once shortened, it is followed by `⧉`: clicking it copies the whole path.\n- The session's PR\u002FMR, 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\u002FMRs give way to the open one; several open ones show `PRs (2)`. A bold label means a new PR\u002FMR or a status change not seen in `\u002Fpr` yet. Clicking the link opens the `\u002Fpr` list and refreshes the statuses; clicking it again closes the list. PRs\u002FMRs are found when the session starts, after turns (at most once a minute), and when a `gh pr create`\u002F`glab mr create` runs; statuses refresh about every minute.\n- 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.\n- Then the context gauge `ctx ▍      23k\u002F600k`.\n\nOn 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 (`…\u002Fcli-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.\n\nThe 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 `\u002Fmodel` while the session is idle. On narrow terminals the border drops the thinking level, then elides and hides the model; the mode stays whole.\n\n## Input Queue\n\nPrompts submitted while the agent is running are accepted by the app server\nand merged into a single follow-up turn, so everything queued during one\ngeneration is delivered to the agent together (not one turn per prompt) once\nit finishes. Each queued prompt keeps its own message so it stays individually\neditable and removable. This includes plain prompts, `\u002Fskill ...`\nprompts, and prompts with `@` mentions. `!bash` and non-side-channel slash\ncommands require an idle session and are rejected while busy\nwith a notice above the input that stays until the next key. **Ctrl+C** removes the newest queued prompt (LIFO); **Esc**\ninterrupts the active turn and pauses the remaining queue; pressing Enter\n(empty or not) on a paused queue resumes it.\n\nAllowlisted slash commands (`side_channel=True`) run immediately via a\nside channel while the agent or bash is busy, or while prompts are queued.\nOnly one side-channel command runs at a time. Besides read-only commands\n(`\u002Fhelp`, `\u002Fstatus`, `\u002Fcopy`, ...), this includes settings a running turn\nnever reads (`\u002Ftheme`, `\u002Flog-level`, `\u002Fvoice`, `\u002Fproxy-setup`), whose changes\nare saved at once, and `\u002Fbranch`, which forks the conversation as it was before\nthe running turn. These still wait for a compaction or agent install to finish.\n\nCommands that change what a turn reads (e.g. `\u002Fconfig`, `\u002Fmodel`,\n`\u002Fthinking`, `\u002Fclear`, `\u002Fcompact`, `\u002Frewind`, `\u002Fresume`,\n`\u002Freload`, `\u002Fleanstall`, `\u002Funleanstall`, `\u002Fretry`, `\u002Fmcp`,\n`\u002Fconnectors`, `\u002Fplugins`, `\u002Freload-plugins`, `\u002Fskills`) are rejected while\nbusy and can be retried when the session is idle.\n\nWhile the queue is non-empty and the agent is busy, pressing **Up**\nenters queue selection mode: the last queued item is highlighted and\nthe input is locked (no cursor, no typing). **Up\u002FDown** navigate\nbetween queued prompts, **Enter** loads the selected prompt into the input\nfor editing (press Enter again to update it in-place), **Backspace** (or\n**Ctrl+C**) removes the selected item and moves selection to the next, and\n**Esc** exits selection mode and\nrestores the original input text.\n\n## Plugins\n\nPlugins are bundled extension packages that can contribute skills, MCP servers,\nhooks, agents, knowledge, connectors, and tool libraries to a session. They are\ndiscovered from two locations:\n\n- `~\u002F.vibe\u002Fplugins\u002F` — user scope (global, available in every project)\n- `.vibe\u002Fplugins\u002F` — project scope (requires trusted folder; overrides\n  user scope on name collisions)\n\nEach subdirectory inside a plugins directory is a single plugin root.\n\nTo turn a plugin off without uninstalling it, list its manifest `name` in\n`disabled_plugins` in `config.toml` (e.g. `disabled_plugins = [\"my-plugin\"]`).\nA plugin whose `ai.mistral.vibe` extension sets `\"defaultEnabled\": false` is\noff until its name is in `enabled_plugins`. Both lists add up across config\nlayers, `disabled_plugins` wins, and an off plugin contributes nothing to\nsessions.\n\n### Plugin Manifest (`plugin.json`)\n\nEvery plugin has a `plugin.json` manifest following the **Agent Plugins 1.0**\nschema (`https:\u002F\u002Fagent-plugins.org\u002Fschemas\u002F1.0.0\u002Fplugin.schema.json`):\n\n```json\n{\n  \"$schema\": \"https:\u002F\u002Fagent-plugins.org\u002Fschemas\u002F1.0.0\u002Fplugin.schema.json\",\n  \"name\": \"my-plugin\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Optional description\",\n  \"author\": { \"name\": \"Author\", \"email\": \"a@b.com\", \"url\": \"https:\u002F\u002F...\" },\n  \"homepage\": \"https:\u002F\u002F...\",\n  \"repository\": \"https:\u002F\u002F...\",\n  \"license\": \"MIT\",\n  \"keywords\": [\"tag1\"],\n  \"extensions\": {\n    \"ai.mistral.vibe\": {\n      \"schemaVersion\": 1,\n      \"displayName\": \"My Plugin\",\n      \"toolNamespace\": \"myNs\",\n      \"toolOverrides\": {\n        \"tool-name\": { \"name\": \"renamedTool\", \"exposure\": \"programmatic\" }\n      }\n    }\n  }\n}\n```\n\nThe `ai.mistral.vibe` extension is optional but gates all Vibe-specific\ncomponents (hooks, knowledge, agents). `toolNamespace` is a\nTypeScript-identifier-safe string used to prefix all component names\n(e.g. `myNs:my-skill`). If omitted, it is derived from the plugin name.\n`displayName` is an optional title of 1 to 100 characters; the plugin `name`\nstays its identity.\n\nAn unknown top-level `plugin.json` field, or an unknown key in the extension, is\nignored with a warning and the rest loads. An invalid value for a known\nextension key drops the whole extension.\n\nReserved namespaces (rejected): `file_system`, `self`, `process`, `skill`,\n`subagent`, `vibe`.\n\n### Plugin Contents\n\nA plugin tree may contain any combination of:\n\n| Component | Location | Notes |\n|---|---|---|\n| Skills | `skills\u002F\u003Cname>\u002FSKILL.md` | Standard skill format |\n| MCP servers | `mcp.json` | `$schema` + `mcpServers` dict; stdio, streamable-http, sse |\n| Hooks | `ai.mistral.vibe\u002Fhooks.toml` | Max 128 hooks, 64 KiB |\n| Knowledge | `ai.mistral.vibe\u002Fknowledge\u002F\u003Cname>\u002FKNOWLEDGE.md` | Max 100 entries |\n| Agents | `ai.mistral.vibe\u002Fagents\u002F*.toml` | One TOML per subagent |\n| Libraries | `libraries.json` | Node and Python library path aliases |\n| Connectors | `connectors.json` | Connector tool mappings |\n\nEnvironment variables `PLUGIN_ROOT` and `PLUGIN_DATA` are injected\nautomatically into MCP server and hook processes and are reserved. Plugin\nhooks run in the plugin directory.\n\n### Foreign Plugin Formats\n\nVibe also adapts non-native plugin formats:\n\n| Format | Detection marker |\n|---|---|\n| Claude Code | `.claude-plugin\u002Fplugin.json` |\n| Codex | `.codex-plugin\u002Fplugin.json` |\n| Kimi Code | `.kimi.plugin.json` or `.kimi-plugin\u002Fplugin.json` |\n| OpenCode | `.opencode\u002Fplugins\u002F`, `.opencode\u002Fskills\u002F*\u002FSKILL.md`, `opencode.json` |\n\nAdapted plugins can only contribute **skills** and **MCP servers** — hooks,\nknowledge, agents, libraries, and connectors are native-only. Executable code\nin foreign plugins (`.js`, `.ts`) is refused and reported as unsupported.\n\n### Plugin Pinning and Reload\n\nOn session start, discovered plugins are resolved into a `ResolvedPluginSnapshot`\n— a portable, host-path-free, secret-free representation pinned to the session.\nThis ensures resume\u002Frewind reproducibility even if files change on disk.\n\n`\u002Freload-plugins` re-discovers plugins from disk, builds a fresh snapshot, and\nprints a diff (`+` added, `-` removed, `~` updated). Tools that are no longer\npresent after a reload are retained as unavailable routes so conversation\nhistory stays valid. Drift detection flags tools whose schema fingerprint changed\nbetween pin and live source.\n\n### Example Plugin Tree\n\n```\nmy-plugin\u002F\n├── plugin.json\n├── mcp.json\n├── skills\u002F\n│   └── my-skill\u002F\n│       └── SKILL.md\n└── ai.mistral.vibe\u002F\n    ├── hooks.toml\n    ├── knowledge\u002F\n    │   └── my-knowledge\u002F\n    │       └── KNOWLEDGE.md\n    └── agents\u002F\n        └── my-agent.toml\n```\n\n## Skills System\n\nSkills are specialized instruction sets the model can load on demand.\nEach skill is a directory containing a `SKILL.md` file with YAML frontmatter.\n\n### Skill File Format\n\n```markdown\n---\nname: my-skill\ndescription: What this skill does and when to use it.\nuser-invocable: true\nallowed-tools: bash read\n---\n\n# Skill Instructions\n\nDetailed instructions for the model...\n```\n\n### Skill Search Order (first match wins)\n\n1. `skill_paths` from config.toml\n2. `.vibe\u002Fskills\u002F` in trusted project directory\n3. `.agents\u002Fskills\u002F` in trusted project directory\n4. `~\u002F.vibe\u002Fskills\u002F` (user global)\n5. `~\u002F.agents\u002Fskills\u002F` (user global, Agent Skills standard)\n\nWith `experimental_enable_registry_skills`, the nearest scope wins instead, and\nwithin a scope a skill file wins over a registry pin: the project's skill files,\nthen the skills pinned in its `.vibe\u002Fskills.toml`, then the global skill files\n(`skill_paths`, `~\u002F.vibe\u002Fskills\u002F`, `~\u002F.agents\u002Fskills\u002F`), then the skills pinned in\n`~\u002F.vibe\u002Fskills.toml`. A registry pin goes by the name its manifest entry gives\nit; when two pins load the same stored version under different names, the one\nranked first loads it. A skill that loses its name is logged as shadowed: once\nper process as a warning under this rule, and at debug level otherwise.\n\n### Invoking Skills\n\nTwo entry points:\n- The model loads a skill on demand via the `skill` tool.\n- The user invokes a `user-invocable` skill by typing `\u002Fskill-name` (optionally\n  followed by extra instructions). The user turn stays the literal `\u002Fskill-name`\n  text; the skill is loaded programmatically and appears to the model as a\n  synthetic `skill` tool call and result immediately after that turn — the model\n  does not call the tool itself.\n\nSkills with `user-invocable: false` are model-only: they are hidden from the\nslash menu and `\u002Fskill-name` will not resolve them (it is treated as a plain\nprompt). The model can still load them via the `skill` tool.\n\nSkills with `disable-model-invocation: true` stay in the slash menu but are\nhidden from the model and cannot be loaded through the `skill` tool. Skills\nusing OpenAI's `agents\u002Fopenai.yaml` convention can set\n`policy.allow_implicit_invocation: false` for the same provider-independent behavior.\n\nA `\u002F` at the very start of the input opens the slash menu (commands and skills).\nA skill row's description starts with its scope in the theme's primary color,\nwhether it is a local, plugin or registry skill: `project · …` (this project\nonly), `user · …` (the user's, in every project), `vibe · …` (shipped with\nVibe), or `skill · …` when an older server does not say; a plugin skill also\nshows its plugin in its `plugin:name`. Rows without one are built-in commands.\nA built-in command wins over a skill with the same name, so such a skill is\nleft out of the slash menu; it can still be mentioned mid-prompt.\nA `\u002Fword` typed mid-prompt (not the first word) instead opens a completion popup\nof matching user-invocable skills, with the same scope prefix.\n\n## Environment Variables\n\n- `VIBE_HOME` - Override the Vibe home directory (default: `~\u002F.vibe`)\n- `MISTRAL_API_KEY` - API key for Mistral provider\n- `VIBE_ACTIVE_MODEL` - Override active model\n- `VIBE_*` - Any config field can be overridden with the `VIBE_` prefix\n- `LOG_LEVEL` - Overrides `log_level` config for `$VIBE_HOME\u002Flogs\u002Fvibe.log`.\n  One of `DEBUG`, `INFO`, `WARNING` (default), `ERROR`, `CRITICAL`. Invalid values\n  fall back to `WARNING`. Use `\u002Flog-level` to change at runtime.\n- `LOG_MAX_BYTES` - Max size in bytes of `vibe.log` before rotation\n  (default: `10485760`, i.e. 10 MiB).\n- `DEBUG_MODE` - When `true`, forces `DEBUG`-level logging.\n- `VIBE_TYPING_GRACE_PERIOD_MS` - Milliseconds the agent waits for a typing\n  pause before showing tool-approval \u002F ask-user-question dialogs (default:\n  `1000`). Set to `0` to disable. Negative or non-numeric values fall back\n  to the default.\n\n## API Keys (.env file)\n\nThe `.env` file in VIBE_HOME stores API keys in dotenv format:\n\n```\nMISTRAL_API_KEY=your-key-here\n```\n\nThis file is loaded on startup and its values are injected into the environment.\n\n## Trusted Folders\n\nVibe uses a trust system to prevent executing project-local config from untrusted\ndirectories. The trust database is stored in `~\u002F.vibe\u002Ftrusted_folders.toml`.\nProject-local config (`.vibe\u002F` directory) is only loaded when the current\ndirectory is explicitly trusted.\n\nInteractive mode prompts to trust unknown folders. The prompt targets the\nclosest ancestor of the cwd (the cwd itself included) containing a `.git`\nentry; the search excludes the user's home directory and the filesystem\nroot, and falls back to the cwd if no qualifying ancestor is found.\nProgrammatic mode (`-p`\u002F`--prompt`) never prompts: the folder is untrusted.\nUse `--trust` to trust cwd for the current invocation only (not persisted).\n`--trust` and `--worktree` both skip the prompt: they grant the workspace trust\nfor the session, so there is no decision left to ask about. Without this a\n`--worktree` run would prompt on every launch, since each worktree is a\ndirectory the trust database has never seen.\n\n## Sensitive Files — DO NOT READ OR EDIT\n\nNEVER read, display, or edit any of these files:\n- `~\u002F.vibe\u002F.env` (or `$VIBE_HOME\u002F.env`) — contains API keys and secrets\n- Any `.env`, `.env.*` file in the project or VIBE_HOME\n\nIf the user asks to set or change an API key, instruct them to edit the `.env`\nfile themselves. Do not offer to read it, write it, or display its contents.\nDo not use tools (read, write_file, bash cat\u002Fecho, etc.) to access these files.\n\n## How to Modify Configuration\n\nTo help the user modify their Vibe configuration:\n\n1. **Read current config when present**: Read `~\u002F.vibe\u002Fconfig.toml` (or the path\n   from `VIBE_HOME` if set). A missing file means Vibe is using built-in defaults.\n2. **Create a backup when present**: Before editing an existing file, copy it to\n   `config.toml.bak` in the same directory. This applies to any existing config\n   file you are about to modify (`config.toml`, `trusted_folders.toml`, agent\n   TOML files, etc.)\n3. **Edit the TOML file**: Make changes using the edit tool\n4. **Reload**: The user can run `\u002Freload` to apply changes without restarting\n\nFor API keys, tell the user to edit `~\u002F.vibe\u002F.env` directly — never read or\nwrite that file yourself.\n\nFor project-specific configuration, create\u002Fedit `.vibe\u002Fconfig.toml` in the\nproject root (the folder must be trusted first).\n","# Vibe CLI Self-Awareness\n\nYou are running inside **Mistral Vibe**, a CLI coding agent built by Mistral AI.\nThis skill gives you full knowledge of the application internals so you can help\nthe user understand, configure, and troubleshoot their Vibe installation.\n\n## Going Deeper\n\nFor facts not covered here, fetch the README:\nhttps:\u002F\u002Fgithub.com\u002Fmistralai\u002Fmistral-vibe\u002Fblob\u002Fmain\u002FREADME.md\nIt tracks the latest release, so check `vibe --version` before relying on it for\nanything version-specific. Point the user at\nhttps:\u002F\u002Fdocs.mistral.ai\u002Fvibe\u002Fcode\u002Foverview for human-readable docs.\n\n## VIBE_HOME\n\nThe user's Vibe home directory defaults to `~\u002F.vibe` but can be overridden via\nthe `VIBE_HOME` environment variable. All user-level configuration, skills, tools,\nagents, prompts, logs, and session data live here.\n\n### Directory Structure\n\n```\n~\u002F.vibe\u002F\n  config.toml          # Optional user configuration, created on first saved setting\n  hooks.toml           # User-level hook definitions\n  .env                 # API keys and credentials (dotenv format)\n  vibehistory          # Command history\n  trusted_folders.toml # Trust database for project folders\n  connector_bootstrap_cache.json # Short-lived connector discovery cache\n  agents\u002F              # Custom agent profiles (*.toml)\n  prompts\u002F             # Custom prompts (*.md)\n  skills\u002F              # User-level skills (each skill is a subdirectory with SKILL.md)\n  tools\u002F               # Former custom Python tools, no longer loaded (see below)\n  logs\u002F\n    vibe.log           # Main log file\n    session\u002F           # Session log files\n\n~\u002F.agents\u002F\n  skills\u002F              # Additional user-level skills directory\n```\n\n### Project-Local Configuration\n\nWhen in a trusted folder, Vibe also looks for project-local configuration:\n- `.vibe\u002Fconfig.toml` - Project-specific config (overrides user config)\n- `.vibe\u002Fhooks.toml` - Project-specific hooks (requires trusted folder)\n- `.vibe\u002Fskills\u002F` - Project-specific skills\n- `.vibe\u002Ftools\u002F` - Former project-specific custom Python tools, no longer loaded (see below)\n- `.vibe\u002Fagents\u002F` - Project-specific agents\n- `.vibe\u002Fprompts\u002F` - Project-specific prompts\n- `.agents\u002Fskills\u002F` - Standard agent skills directory\n\nCustom Python tools are no longer supported: Vibe does not load the `.py` files\nin `~\u002F.vibe\u002Ftools\u002F` or `.vibe\u002Ftools\u002F`, and reports each one as a config issue.\nAt startup Vibe also removes the former `tool_paths` setting from\n`config.toml`; the folders it listed stay on disk. Recommend skills for new\nextensions. When a user asks for migration help, inspect the custom tool's\nbehavior and replace it with an equivalent skill.\n\n### AGENTS.md Discovery\n\n`AGENTS.md` files provide directory-scoped instructions to the model. At startup,\nVibe loads `~\u002F.vibe\u002FAGENTS.md` and every `AGENTS.md` from the project root up\nthrough the trust chain. `AGENTS.md` files in subdirectories are discovered\nlazily: when `read_file` reads a file below the project root, any `AGENTS.md`\nbetween the file's parent and the project root is injected into\ncontext.\n\n## Lifecycle: Exit, Update, Version, Resume\n\n### Exit\n\nChat input (case-insensitive): `\u002Fexit`, `exit`, `quit`, `:q`, `:quit`.\nKeyboard: `Ctrl+C` \u002F `Ctrl+D` — press twice within ~1s to quit. For `Ctrl+C`,\nthe first press instead interrupts the running job or clears the input if either\nis present. Set `ask_confirmation_on_exit = false` to make `Ctrl+D` quit on the\nfirst press (also toggleable in `\u002Fconfig`); `Ctrl+C` always requires a second\npress. `Ctrl+Z` suspends on POSIX (resume with `fg`).\n\n### Prompt History\n\n`Up`\u002F`Down` recall previous prompts (stored in `~\u002F.vibe\u002Fvibehistory`, newest 100\nkept). `Ctrl+R` opens a `reverse-i-search:` field in the bottom bar: the input\npreviews the newest prompt containing the query (case-insensitive), `Up`\u002F`Ctrl+R`\nmove to older matches and `Down`\u002F`Ctrl+S` to newer ones, `Enter` keeps the match\nin the input without sending it (`Ctrl+Z` undoes), `Esc`\u002F`Ctrl+C` restore the draft.\n\n### Update\n\nVibe never updates silently. With `enable_update_checks = true` (default), it\nchecks for a newer `mistral-vibe` daily and prompts on the next launch when\none exists. uv tool installs ask `uv tool list --outdated`, so uv settings such\nas `exclude-newer` apply; other installs, and uv older than 0.10.10, poll PyPI. Accepting runs\n`uv tool upgrade mistral-vibe`, then `brew upgrade mistral-vibe` as a fallback. On\nWindows, accepting prints the upgrade command to run after closing every Vibe\nsession instead, since a running Vibe locks its own files. Disable via `enable_update_checks\n= false`. Run `vibe update` (equivalent to `vibe --check-upgrade`) to check\nimmediately, prompt to install a newer version if one exists, and exit. Initial\ninstall: `uv tool install mistral-vibe`.\n\n### Version\n\n`vibe --version` (or `-v`) prints it and exits. Not shown anywhere in-session.\n\n### Resume\n\n- `vibe -c` \u002F `--continue`: the last session used in this terminal in the\n  current folder (it moves on a new session's first message, or when a saved\n  session is reopened); otherwise the folder's most recent session. Works with\n  `-p` too. The terminal is the tty device on macOS\u002FLinux, the console window\n  or `WT_SESSION` on Windows; `VIBE_TERMINAL_KEY` overrides it, and an empty\n  value means \"no terminal\" (folder rule only).\n- `vibe --resume [SESSION_ID]`: specific session; without an id, opens a picker.\n- In-session: `\u002Fresume` (alias `\u002Fcontinue`).\n\n#### Session titles\n\nEach session has a `title` stored in `meta.json` (with `title_source`: `auto` or\n`manual`). A session stays untitled until a background LLM call generates a\nconcise descriptive title (the `--resume` list shows a message preview until\nthen). Automatic generation runs only for the interactive CLI; other clients\n(ACP, app server, programmatic) keep their own session management and fall back\nto the message preview. Title generation runs on a small fast Mistral model when the\ndeployment is known to serve one — Vibe asks it once, with a one-token request\nat session open, and remembers the answer in `~\u002F.vibe\u002Futility_model_cache.json`\n— and on the session's active model otherwise. `utility_models.title` pins a\nspecific model instead. The first title waits for the opening turn to finish (or a few\nmodel steps) so it isn't generated off a thin tool-call preamble. On that cheap\nfast model it also refreshes periodically and\nafter each compaction; when it falls back to the (possibly expensive) active\nmodel it stays bounded — one title at the start plus one after a compaction, a\ncouple at most — so a large model isn't re-invoked every few turns. The refresh\nkeeps the opening intent and the latest exchange in view and feeds the previous\ntitle back so it refines rather than restarts. `\u002Frename \u003Ctitle>` sets a `manual`\ntitle that auto-generation never overwrites. Set `session_logging.generate_titles\n= false` to turn automatic titles off (the `--resume` list and tab then use the\nmessage preview). The current title also drives the terminal tab\u002Fwindow title\n(OSC), updated on rename, auto-title changes, and resume; it never blocks a turn.\n\n#### Session storage & folder scoping\n\nLocal sessions are written under `~\u002F.vibe\u002Flogs\u002Fsession\u002F` (override with\n`session_logging.save_dir`). Each session records the `cwd` it ran in. The\n`\u002Fresume` picker, `--continue`, and bare `--resume` (no id) are **scoped to the\ncurrent folder**: only sessions whose `cwd` matches where Vibe is launched are\nlisted, so the same directory shows its own history and nothing else. Switch\nfolders to see a different set. The explicit `--resume \u003CSESSION_ID>` form is\n**not** folder-scoped: it resolves the session by id regardless of which folder\nit ran in.\n\nThe first user message pins the resolved model alias to the session. Resuming\nkeeps that model even if the configured default changes. `\u002Fmodel` uses the same\nconfig persistence target as other settings and also updates an existing\nsession override immediately; the session file is synchronized when the next\nuser message is sent. An explicit persistent target selected through `\u002Fconfig`\nchanges only that layer. `\u002Fclear` starts an unpinned conversation that follows\nthe current config again. If a selected model is no longer configured, Vibe\nfalls back to the current default model.\n\nEach user message also pins the running agent, including one chosen with\n`--agent`. Resuming or continuing keeps that agent, even if the configured\ndefault agent changes or `--agent` names another one; switch agents in the\nsession to change it. If the pinned agent no longer exists, Vibe falls back to\n`--agent` or the default agent, and keeps that one from then on.\n\n## Configuration (config.toml)\n\nThe configuration file uses TOML format. When it does not exist, Vibe uses its\nbuilt-in defaults and creates a sparse file on the first persisted setting.\nSettings can also be overridden via environment variables with the `VIBE_`\nprefix (e.g., `VIBE_ACTIVE_MODEL=my-local-model`).\n\nCustom prompt IDs are resolved from project-local `.vibe\u002Fprompts\u002F` first, then\nfrom `~\u002F.vibe\u002Fprompts\u002F`, and finally from the built-in bundled prompts.\n\n### Settings Vibe does not use\n\nVibe checks `~\u002F.vibe\u002Fconfig.toml`, the trusted project `.vibe\u002Fconfig.toml` and\nthe active agent profile for settings it does not use: a misspelt key (with the\nclosest setting name), a retired setting such as `include_model_info`, or a\nsetting with no effect such as `[tools.grep]`. Each one is a warning at startup,\nand `vibe -p` prints it on stderr.\n\nTo refuse a config with such settings instead, set `strict_config = true` in\n`config.toml` or `VIBE_STRICT_CONFIG=1` in the environment; Vibe then stops with\nthe list. `VIBE_STRICT_CONFIG` takes a boolean: `1`, `true`, `yes` or `on` to\nrefuse, `0`, `false`, `no` or `off` to warn; any other value is refused too.\n\n`vibe config schema` prints a JSON schema of every setting, including each\nbuilt-in tool's settings, for an editor to validate `config.toml` against.\n\n### Key Settings\n\n```toml\n# Model selection\nactive_model = \"mistral-medium-3.5\"  # Model alias to pin; omit or set \"\" to follow the server-routed default\n\n# UI preferences\ntheme = \"auto\"  # Follow terminal background, then OS light\u002Fdark preference\ndisable_welcome_banner_animation = false\ncursor_blink = true  # Blink the input cursor; false keeps it steady\nvim_keybindings = false  # Vim Normal and Insert modes in the prompt input\nautocopy_to_clipboard = true  # Enable automatic copying of selected text to clipboard\nenable_mouse_capture = true  # Capture the mouse for scrolling, clicks and selection; false keeps the terminal's native selection\nenable_sticky_prompt = true  # Pin the prompt of the section at the top of the transcript while scrolling\nenable_file_autocomplete = true  # Offer @ file completion; off skips the file index entirely\nfile_watcher_for_autocomplete = true  # Refresh @ suggestions and pick up skill changes after workspace changes\nask_confirmation_on_exit = true  # Require a second Ctrl+D to quit (Ctrl+C always confirms)\nshow_greeting = true  # Show \"Hello {name}\" greeting below the banner at startup (Mistral providers, once per 24h)\nlog_level = \"WARNING\"  # Optional. DEBUG | INFO | WARNING | ERROR | CRITICAL — log level for ~\u002F.vibe\u002Flogs\u002Fvibe.log\n```\n\n`cursor_blink` controls the main input cursor. Change it through `\u002Fconfig`, or\nedit `config.toml` and run `\u002Freload`; no restart is needed.\n\n`enable_sticky_prompt` pins the prompt of the conversation section at the top of\nthe transcript once it scrolls off; the next section's prompt pushes it out. It\nshows at most three rows, the last one cut with `…`, and turns off below 15\ntranscript rows. A prompt a scheduled loop sent pins as its folded title\n(`⏵ title`), without its instructions or `loop` line. Clicking it\nscrolls the transcript back to that prompt. Set it to `false` through `\u002Fconfig`,\nor edit `config.toml` and run `\u002Freload`; no restart is needed.\n\n`vim_keybindings` adds a subset of vim to the prompt input. It starts in Insert\nmode, labelled `INSERT` on its bottom border with a bar caret; `Esc` enters Normal\nmode, unlabelled with a block caret.\nIn Normal mode `Esc` interrupts a running turn and `Esc Esc` on an empty prompt\nopens rewind; it never clears the draft. Normal mode supports\n`h l w b e 0 ^ $ gg G j k`, `i a I A o O`, `x X D C dd cc`, `d`\u002F`c` with\n`w e b 0 ^ $` (`Esc` or `Enter` cancels a pending `d`\u002F`c`), `u` and `Ctrl+R` (redo, not history search); `\u002F` or `!` on an empty prompt opens that mode.\nIn Insert mode `Esc` enters Normal mode, so `Ctrl+C` interrupts a running turn.\nOther shortcuts (`Ctrl+A`, `Ctrl+G`, ...) work in both modes. There is no visual\nmode, yank\u002Fpaste, count or `:` command.\n\n### Copy and Text Selection\n\n- **Copy shortcuts**: `Ctrl+Y` and `Ctrl+Shift+C` both copy the current selection to the clipboard. On macOS and Linux, with nothing selected, `Ctrl+Y` yanks (pastes back) the text the last `Ctrl+K`, `Ctrl+Shift+K`, `Ctrl+U`, `Ctrl+W`, `Alt+D` or 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 than `Esc`, which aborts the drag without copying). Each successful copy flashes a brief inline \"Copied to clipboard\" notice.\n- **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.\n- **Terminal-native selection**: `enable_mouse_capture = false` (or `VIBE_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 `\u002Fconfig` or `\u002Freload`. Inside a tmux session whose `mouse` option is off (the default before tmux 3.8), Vibe behaves as if this setting were `false`, re-reading the option at startup and when resuming from suspend or an external editor. Native selection only covers the visible screen; `Shift+Up\u002FDown` always scrolls the transcript. In some terminals the mouse wheel then sends Up\u002FDown keys, which recall prompt history in the input box.\n- **Warnings**: Config issues, config validation warnings, and the warnings the\n  app server reports before the first turn (an MCP server or connector that\n  failed to load, a model catalogue problem) are listed together in one\n  `Warnings` box at the top of a new conversation (below the history of a\n  resumed one). Once a turn has started, each new warning appears as its own\n  `⚠` line in the conversation. Errors always appear as their own `✗` line.\n- **Clipboard tools**: Copies use `pbcopy` on macOS and `clip` on Windows; on Linux they try `wl-copy`, then `xclip`, then `xsel`. Vibe always also sends the text through OSC 52, and over SSH it relies on OSC 52 alone. Inside tmux, it instead runs `tmux load-buffer -w` to fill a tmux paste buffer and forward the text to the attached terminal's clipboard. If tmux's `set-clipboard` is `off`, or the terminal lacks the `Ms` capability, it falls back to OSC 52 passthrough. When no native copy succeeds, the notice reads `Copied · \u003Chint>` instead (auth URL copies read `Auth URL copied · \u003Chint>`). On local Linux the hint is `if paste fails, Shift+select or install xclip`; elsewhere, including over SSH, it is `if paste fails, Shift+select (Option in iTerm2, Fn in Terminal.app)`. Shift+select uses the terminal's native selection.\n\n```toml\n# Behavior\nbypass_tool_permissions = false    # Skip tool approval prompts\noutside_workdir = \"ask\"            # Outside-workdir file\u002Fshell access: \"ask\" prompts, \"deny\" refuses with a tool error\nworktree_copy_on_write = true      # macOS: clone new worktrees of a trusted repo from a template checkout; sets extensions.worktreeConfig in the repo\nenable_pr_monitoring = true        # Track the session's PRs\u002FMRs (gh\u002Fglab): detect created ones, find existing ones for the branch, keep their status fresh\nsystem_prompt_id = \"cli\"          # System prompt: \"cli\", \"lean\", or custom .md filename\ncompaction_prompt_id = \"compact\"  # Compaction prompt: built-in \"compact\" or custom .md filename\nenable_telemetry = true\nenable_update_checks = true       # Daily update check (uv or PyPI); prompts on next launch when a newer release exists\nenable_notifications = true\nenable_system_trust_store = false  # Use OS trust store for outbound HTTPS\napi_timeout = 720.0               # API request timeout in seconds\napi_retry_max_elapsed_time = 300.0  # Retry budget for retryable API failures in seconds\napi_stream_idle_timeout = 300.0   # Silence after the model starts answering that counts as a stall, in seconds; 0 allows api_timeout\n# auto_compact_threshold = 200000 # optional fallback for models without their own threshold; unset: derived from each model's context window\ncompaction_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\noutput_limit_retries = 6          # Answers cut off at the output limit that a turn retries with feedback; 0 ends the turn on the first\nagent_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)\ncompaction_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\n\n# Git commit behavior\ninclude_commit_signature = true   # Add \"Co-Authored-By\" to commits\n\n# System prompt composition\ninclude_project_context = true    # Include project context (git info, cwd) in system prompt\n\n# Voice features\nvoice_mode_enabled = false\nnarrator_enabled = false\nactive_transcribe_model = \"voxtral-realtime\"\nactive_tts_model = \"voxtral-tts\"\n```\n\n### OpenTelemetry Tracing\n\nSet `enable_otel = true` to export traces for agent, model, and tool operations\nover OTLP\u002FHTTP. `enable_telemetry` must also be enabled. With no explicit\nendpoint, Vibe derives the telemetry endpoint and API key from the configured\nMistral provider, except public regional API hosts that do not serve telemetry.\n\nTo use another collector, set `otel_endpoint` to its base URL; Vibe appends\n`\u002Fv1\u002Ftraces`. Configure custom-collector authentication through the standard\n`OTEL_EXPORTER_OTLP_*` environment variables.\n\n`otel_redaction` controls client-side span attribute redaction: `default`\nredacts sensitive values, `strict` redacts sensitive attributes entirely, and\n`none` disables redaction. Use `none` only for a collector trusted to receive\npotentially sensitive prompt, response, and tool data.\n\n```toml\nenable_otel = true\notel_endpoint = \"https:\u002F\u002Fcollector.example.com:4318\"\notel_redaction = \"default\"\n```\n\n### Providers\n\n```toml\n[[providers]]\nname = \"mistral\"\napi_base = \"https:\u002F\u002Fapi.mistral.ai\u002Fv1\"\napi_key_env_var = \"MISTRAL_API_KEY\"\nbackend = \"mistral\"\n\n[[providers]]\nname = \"llamacpp\"\napi_base = \"http:\u002F\u002F127.0.0.1:8080\u002Fv1\"\napi_key_env_var = \"\"\nextra_headers = { \"X-Custom-Header\" = \"value\" }  # optional per-provider HTTP headers\nemits_finish_reason = false  # set false for OpenAI-compatible endpoints that end\n                             # streams without a finish reason; avoids spurious\n                             # \"incomplete stream\" errors and retries (default true)\n```\n\n### Models\n\n```toml\n# Restrict selectable models by their canonical API names, not their aliases.\n# Glob and \"re:\" patterns, matched in full. The only way to hide server-routed\n# models; a hidden routed default falls back to the catalog recommendation,\n# then Mistral Medium 3.5, then the first allowed model. Keep one pattern per\n# model: a pattern matching\n# nothing warns — the drift signal; do not merge them to silence it.\nallowed_models = [\"mistral-vibe-cli-*\"]\n\n[[models]]\nname = \"mistral-vibe-cli-latest\"\nprovider = \"mistral\"\nalias = \"mistral-medium-3.5\"\ntemperature = 1.0\ninput_price = 1.5\noutput_price = 7.5\ncached_input_price = 0.15         # per million cached input tokens; omit to bill at input_price\nthinking = \"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\n# thinking_levels = [\"off\", \"high\"] # levels \u002Fthinking 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)\n# 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\nsupports_images = true            # vision-capable; allows @-mentioned images\n\n[[models]]\n# A user-defined local model served by llama.cpp; the built-in list ships only\n# the hosted Mistral Medium 3.5.\nname = \"devstral\"\nprovider = \"llamacpp\"\nalias = \"my-local-model\"\n\n# Optional override. A non-vision active model already picks up any\n# supports_images model on its OWN provider automatically; set this only to\n# point somewhere else, which is also the only way to cross providers. Ignored\n# whenever the active model has supports_images = true -- that model sees the\n# image itself.\n[vision_model]\nname = \"mistral-vibe-cli-latest\"\nprovider = \"mistral\"\nalias = \"vision\"\nsupports_images = true            # required\n\n# Optional: the model behind each background helper -- session titles and the\n# smart-approve classifier. A value is an alias from [[models]], or \"active\" for\n# the session's own model (unless a model is aliased \"active\"). Unset, a helper\n# uses a small fast Mistral model when the deployment serves one (checked when\n# a session opens with the feature on, then cached) and the session's model\n# otherwise, which costs more per call.\n[utility_models]\ntitle = \"local\"\nsmart_approve = \"active\"\n```\n\n### Tool Configuration\n\n```toml\n# Enable only specific tools (glob and regex supported)\nenabled_tools = [\"bash\", \"read_file\", \"grep\"]\n\n# Disable specific tools after enabled_tools filtering\ndisabled_tools = [\"web_fetch\"]\n\n# Per-tool configuration\n[tools.bash]\nallowlist = [\"git\", \"npm\", \"python\"]\n\n[tools.git_bash]\npermission = \"ask\"\nshell = \"C:\\Program Files\\Git\\bin\\bash.exe\"\n\n[tools.powershell]\npermission = \"ask\"\nshell = \"powershell.exe\"\n```\n\nTwo tool settings have no effect, and Vibe reports each as a config issue (in\nthe startup Warnings box in the TUI, a warning in the `-p` export):\n\n- `[tools.grep]` (for example `exclude_patterns`): there is no `grep` tool. The\n  agent searches through the shell, so `rg` and `grep` commands follow the shell\n  tool's settings (`[tools.bash]` on POSIX).\n- A `prompts\u002Ftask.md` in a tool directory (`.vibe\u002Ftools\u002Fprompts\u002Ftask.md`): it\n  overrode the description of the former `task` tool. Subagents are started\n  with the built-in `agents.spawn` tool, whose description cannot be\n  overridden; put delegation guidance in `AGENTS.md` or in the agent's\n  instructions instead.\n\nThe built-in shell surface is controlled by the `managed_shell_tools_enabled` config\nfield and the `vibe_cli_managed_shell_tools` GrowthBook experiment. The default variant\nkeeps the legacy one-shot `bash` tool, including its existing Windows behavior.\nThe managed variant exposes OS-native shell tools:\nPOSIX systems, including WSL where Vibe runs as Linux, get managed `bash`,\n`bash_output`, `bash_stdin`, `bash_sessions`, and `bash_log_file`; native Windows\ngets `git_bash`, `git_bash_output`, `git_bash_stdin`, `git_bash_sessions`, and\n`git_bash_log_file` when Git Bash is available. If Git Bash is unavailable,\nnative Windows falls back to `powershell`, `powershell_output`,\n`powershell_stdin`, `powershell_sessions`, and `powershell_log_file`.\n\nManaged shell sessions return a `session_id`, inline output, a cursor for polling\nmore output, and a log path under `~\u002F.vibe\u002Fshell-tool\u002Fsessions\u002F`. Long-running\ncommands can be left alive with `background = true`, and interactive commands can\nbe driven with the matching stdin tool.\n\nCommands Vibe runs on the host (shell tools, background processes and `!`\ncommands) get `VIBE_SESSION=1` in their environment, so a script can tell it\nruns under Vibe. Commands in a sandbox use the sandbox's own environment.\n\nPOSIX `bash` reads permissions, allowlists, and denylists from `[tools.bash]`.\nNative Windows `git_bash` reads them from `[tools.git_bash]`; native Windows\n`powershell` reads them from `[tools.powershell]`. Neither Windows tool reads\n`[tools.bash]`. Git Bash is preferred when Vibe can resolve a usable `bash.exe`\nfrom PATH, Git for Windows, or standard Git install locations. If Git Bash is\nunavailable, the PowerShell resolution order is `pwsh.exe`, then\n`powershell.exe`. `cmd.exe` is not used by the managed Windows shell tools.\nOutput polling uses byte offset cursors\n(`cursor` \u002F `next_cursor`), `max_bytes` caps per-call inline output, and\n`max_inline_bytes` configures the default cap.\n\n**Special case — `find` command:** Even if `find` is in the bash allowlist,\nVibe detects `-exec`, `-execdir`, `-ok`, and `-okdir` predicates and will\nprompt for user permission before running the command.\n\n**Outside paths:** a shell command naming a path outside the working directory\n(`cat \u002Fetc\u002Fhosts`, `ls ..\u002Fother`, or a workdir symlink pointing out such as\n`cat link`) asks for that path, even when the command is allowlisted or the\nshell is `permission = \"always\"`; `outside_workdir = \"deny\"` refuses it instead.\n`\u002Fdev\u002Fnull` and the standard streams do not count.\n\n#### File Tool Permission Resolution\n\nFile-based tools (`read`, `grep`, `write_file`, `edit`) resolve\npermissions in this order (first match wins):\n\n1. **Scratchpad** path → always allowed\n2. **denylist** glob match → always denied\n3. **allowlist** glob match → always allowed\n4. **sensitive_patterns** match → requires approval\n5. **Outside workdir** → requires approval, or denied if `permission = \"never\"`\n   or `outside_workdir = \"deny\"`\n6. **Default** → uses the tool's `permission` setting\n\nThe **denylist** is checked before the allowlist — a path matching both lists\nis denied. Both are checked before the outside-workdir boundary, so the\nallowlist can still auto-approve access to directories outside the project.\n\n#### Outside-Workdir Access\n\n`outside_workdir` covers file and shell calls reaching outside the working\ndirectory and its `--add-dir` directories: `\"ask\"` (default) prompts, `\"deny\"`\nrefuses with a tool error and the turn continues. Set it globally or per tool:\n\n```toml\noutside_workdir = \"deny\"\n\n[tools.read_file]\noutside_workdir = \"ask\"  # per-tool override\nallowlist = [\"vibe-path:directory_recursive:\u002Fabs\u002Fpath\u002Fto\u002Fshared\"]  # stays allowed\n```\n\n- Allowlisted paths, `--add-dir` directories and the scratchpad are never\n  refused. `permission = \"always\"` does not bypass the check.\n- Shells only check paths they can read statically, such as the arguments of\n  `cat`, `cp` or `ls`. Anything else (interpreter code, dynamic paths, relative\n  paths after a `cd` in the same command, such as `cd sub && cat link` with\n  `link` pointing out) follows the shell's normal permission, so a shell that\n  must stay inside should not be `always`.\n- On Windows a call is refused if any shell tool denies it: override both\n  `git_bash` and `powershell`. PowerShell treats `\u002Fdev\u002Fnull` as a path; use\n  `$null`.\n- Auto-approve skips the check, like the denylists.\n\n### Skill Configuration\n\n```toml\n# Additional skill search paths\nskill_paths = [\"\u002Fpath\u002Fto\u002Fcustom\u002Fskills\"]\n\n# Enable only specific skills\nenabled_skills = [\"vibe\", \"custom-*\"]\n\n# Disable specific skills\ndisabled_skills = [\"experimental-*\"]\n```\n\n### Agent Configuration\n\n```toml\n# Additional agent search paths\nagent_paths = [\"\u002Fpath\u002Fto\u002Fcustom\u002Fagents\"]\n\n# Enable\u002Fdisable agents\nenabled_agents = [\"ask\", \"accept-edits\"]\ndisabled_agents = [\"auto-approve\"]\n\n# Forbid the auto-approve (YOLO) mode entirely: hides the \"auto-approve\" agent\n# (and any custom agent with safety = \"yolo\") and ignores --auto-approve\u002F--yolo.\n# Per-tool permissions still apply.\n# Intended for admin-managed (enterprise) config.\nenable_auto_approve = false\n\n# Opt-in builtin agents (only affects agents with install_required=True, e.g. lean)\ninstalled_agents = [\"lean\"]\n\n# Agent profile to use when --agent is not passed\n# (default: \"accept-edits\"). Valid values: \"ask\", \"accept-edits\",\n# \"auto-approve\", \"lean\" (only when listed in installed_agents), or any\n# custom agent name from ~\u002F.vibe\u002Fagents\u002F or .vibe\u002Fagents\u002F. Subagents\n# (e.g. \"explore\") are rejected. Applies in both interactive and programmatic\n# (-p\u002F--prompt) mode.\ndefault_agent = \"ask\"\n```\n\n### MCP Servers\n\nRemote MCP servers can be added non-interactively from the shell:\n\n```bash\nvibe mcp add mistralai \\\n  --url https:\u002F\u002Fapi.mistral.ai\u002Fmcp \\\n  --transport streamable-http \\\n  --api-key-env MISTRAL_API_KEY\n\nvibe mcp add linear \\\n  --url https:\u002F\u002Fmcp.linear.app\u002Fmcp\n\nvibe mcp remove mistralai\n```\n\nStatic auth is selected when `--api-key-env` or `--header` is provided.\nOtherwise the server uses OAuth and starts browser login by default. Pass\n`--no-login` to only persist the OAuth configuration. Run\n`vibe mcp add --help` for all supported authentication and timeout options.\nUse `vibe mcp remove \u003Cname>` to remove a server from the user configuration;\nstored OAuth credentials are deleted when available.\n\n`vibe mcp add NAME --url URL` adds remote servers, including static\nauthentication with `--api-key-env`, `--api-key-header`, `--api-key-format`,\nand repeatable `--header NAME=VALUE`.\nAdd local standard input\u002Foutput (stdio) servers with\n`vibe mcp add NAME --transport stdio --command COMMAND`, repeatable\n`--arg VALUE` (use `--arg=-y` for flags), and `--env NAME=VALUE`.\nA string stdio `command` is split into words like a shell line; on native\nWindows a backslash is a path separator, not an escape, so quote a path that\ncontains spaces: `command = '\"C:\\Program Files\\tool\\tool.exe\" --stdio'`.\nSet `--startup-timeout-sec` and `--tool-timeout-sec` for either transport.\nThe legacy `vibe mcp add URL --name NAME` form is also accepted.\nShell `add` and `remove NAME` update user config without a chat session;\n`remove` is not a slash command.\n\nHosted OAuth MCP servers can also be added from inside Vibe:\n\n```text\n\u002Fmcp add https:\u002F\u002Fmcp.linear.app\u002Fmcp\n\u002Fmcp add https:\u002F\u002Fmcp.example.com\u002Fmcp --name docs --scope read --transport http --no-login\n```\n\n`\u002Fmcp add` is OAuth-only. It writes `auth.type = \"oauth\"` with optional\nscopes and starts login by default. It uses `transport = \"streamable-http\"`\nunless you pass `--transport http`. Pass `--no-login` to add the server without\nstarting OAuth login. The shortcut supports `streamable-http` and `http`\ntransports.\n\n```toml\n[[mcp_servers]]\nname = \"my-server\"\ntransport = \"stdio\"\ncommand = \"npx\"\nargs = [\"-y\", \"@my\u002Fmcp-server\"]\n\n[[mcp_servers]]\nname = \"remote-server\"\ntransport = \"http\"\nurl = \"https:\u002F\u002Fmcp.example.com\"\n\n[mcp_servers.auth]\ntype = \"static\"\napi_key_env = \"MCP_API_KEY\"\napi_key_header = \"Authorization\"\napi_key_format = \"Bearer {token}\"\n\n[[mcp_servers]]\nname = \"linear\"\ntransport = \"streamable-http\"\nurl = \"https:\u002F\u002Fmcp.linear.app\u002Fmcp\"\n\n[mcp_servers.auth]\ntype = \"oauth\"\nscopes = [\"read\", \"write\"]\n# Optional: client_id = \"pre-registered-public-client\"\n# Optional: client_metadata_url = \"https:\u002F\u002Fexample.com\u002Fclient-metadata.json\"\n# Optional: redirect_port = 47823\n```\n\nHTTP MCP servers can use either static auth or OAuth:\n\n- Static auth: legacy `api_key_env` \u002F `headers` keys still work and are\n  promoted to `auth.type = \"static\"` internally.\n- OAuth auth: use `auth.type = \"oauth\"` with `scopes`. Vibe stores tokens\n  in the OS keyring under `mcp-oauth:\u003Calias>:tokens`, dynamic client info\n  under `mcp-oauth:\u003Calias>:client_info`, and config drift fingerprints under\n  `mcp-oauth:\u003Calias>:fingerprint`.\n- Headless environments without an OS keyring cannot store OAuth tokens; use\n  static auth via `api_key_env` instead.\n- For SSH\u002Fremote browser callbacks, forward the loopback port:\n  `ssh -L 47823:127.0.0.1:47823 \u003Chost>`.\n\nMCP sampling is disabled by default. It requires an explicit per-server opt-in\nand a host-provided sampling completion. Vibe's app server does not currently\nprovide that completion, so it does not advertise or service sampling requests.\n\n### Connectors\n\nMistral connectors are auto-discovered when the active provider is Mistral\nand the API key env var is set. Toggle the master switch or hide individual\nconnectors \u002F tools:\n\nThe Unified Harness enables ready connectors by default in memory. It does not write that\ndefault to TOML, and the master switch plus explicit connector, tool, allowlist,\nand denylist settings always take precedence.\n\n```toml\nenable_connectors = true          # Master switch (default: true)\n\n[[connectors]]\nname = \"github\"\ndisabled = true                   # Hide all tools from this connector\n\n[[connectors]]\nname = \"linear\"\ndisabled_tools = [\"delete_issue\"] # Hide selected tools only\n```\n\n### Session Logging\n\n```toml\n[session_logging]\nenabled = true\nsave_dir = \"\"                     # Defaults to ~\u002F.vibe\u002Flogs\u002Fsession\nsession_prefix = \"session\"\ngenerate_titles = true            # Background LLM session titles; false uses the message preview\n```\n\n### Browser Sign-In\n\nBrowser sign-in lets users authenticate through the browser during onboarding.\nMistral providers use default browser sign-in URLs (`console.mistral.ai` \u002F\n`api.mistral.ai`). Custom or renamed providers must configure both URLs:\n\n```toml\n[[providers]]\nbrowser_auth_base_url = \"https:\u002F\u002Fconsole.mistral.ai\"\nbrowser_auth_api_base_url = \"https:\u002F\u002Fconsole.mistral.ai\u002Fapi\"\n```\n\nSplit-horizon deployments (e.g. behind an SAP Cloud Connector) expose the\nconsole under a virtual host the CLI reaches but the server does not know about,\nso it returns sign-in\u002Fpoll URLs on its own public host. Set\n`browser_auth_allow_origin_rewrite = true` on the provider to re-home the\nreturned URLs onto the configured `browser_auth_base_url` \u002F\n`browser_auth_api_base_url` origins instead of rejecting them for an origin\nmismatch (path traversal is still rejected). Only the origin (scheme, host,\nport) is rewritten; the returned URL's path must already sit under the\nconfigured base path, so the connector must preserve the server's path prefix.\nPoint `browser_auth_base_url` at the browser-reachable console and\n`browser_auth_api_base_url` at the CLI-reachable API host. The interactive\nonboarding wizard's custom-domain screen also accepts an optional browser-auth\nAPI base URL and auto-enables this rewrite when its origin differs from the\nconsole origin:\n\n```toml\n[[providers]]\nbrowser_auth_base_url = \"https:\u002F\u002Fconsole.internal.example\"\nbrowser_auth_api_base_url = \"https:\u002F\u002Fconnector.internal.example:443\u002Fapi\"\nbrowser_auth_allow_origin_rewrite = true\n```\n\nThe same split-horizon setups often reach the API through a proxy whose URL\ndiffers from the tenant's public API host that `\u002Fwhoami` advertises. By\ndefault, sign-in and the runtime config self-heal adopt the whoami-advertised\n`api_base`. Set `preserve_api_base = true` on the provider to keep the\nconfigured `api_base` verbatim across sign-in and `\u002Freload`; the chat URL\n(`vibe_base_url`) adoption is unaffected:\n\n```toml\n[[providers]]\napi_base = \"https:\u002F\u002Fconnector.internal.example:443\u002Fv1\"\npreserve_api_base = true\n```\n\nSelf-hosted deployments can point Vibe CLI upgrade and API-key links to their\nLe Chat web deployment, where the Vibe API key is managed. The same URL serves\nthe admin-managed config:\n\n```toml\nvibe_base_url = \"https:\u002F\u002Fchat.mistral.ai\"\n```\n\nWhen no config layer sets `vibe_base_url` and the Mistral provider's `api_base`\nis `https:\u002F\u002Fapi.\u003Cdomain>\u002F...`, Vibe uses `https:\u002F\u002Fchat.\u003Cdomain>` instead of\nthe default. For example, `api_base = \"https:\u002F\u002Fapi.example.com\u002Fv1\"` gives\n`https:\u002F\u002Fchat.example.com`. Any other `api_base`, including Mistral's regional\n`api.eu.mistral.ai` and `api.us.mistral.ai`, keeps the default.\n\nInteractive setup can target a Mistral-compatible deployment instead of the\ndefault `console.mistral.ai` \u002F `api.mistral.ai`. The final credential is always a\nMistral API key. On the auth-method screen pick **Launch browser**, then\n**Other** on the sign-in-target screen, and enter a login domain to complete\nbrowser sign-in. This sets `browser_auth_base_url` (the entered domain) and\nderives `browser_auth_api_base_url` (`DOMAIN\u002Fapi`). The overridden `mistral`\nprovider is persisted to user config so subsequent runs reuse it.\n\nThe wizard reads any custom `browser_auth_base_url` already in `config.toml`:\nchoosing **Other** pre-fills that configured domain so it can be confirmed or\nedited. Choosing **Mistral AI** while a custom domain is configured warns first\nand requires pressing **Enter** again to confirm the reset to the default\ndomain, which is then persisted.\n\n### Hooks\n\nHooks let users run shell commands automatically at lifecycle events. They\nare always available — no flag is required; dropping a `hooks.toml` in place\nis enough.\n\n#### Config and hook types\n\nHooks live in `hooks.toml` files (separate from `config.toml`), discovered in\nthis order:\n\n1. `\u003Cproject>\u002F.vibe\u002Fhooks.toml` — loaded first, only when the folder is\n   trusted.\n2. `~\u002F.vibe\u002Fhooks.toml` — loaded second.\n\nHooks shipped by native Vibe plugins (`ai.mistral.vibe\u002Fhooks.toml`) run too.\nFor one event, hooks run in this order: project `hooks.toml`, project plugins,\n`~\u002F.vibe\u002Fhooks.toml`, then user plugins. A plugin hook runs in the session's\nread-only copy of its plugin with `PLUGIN_ROOT` and `PLUGIN_DATA` set, like the\nplugin's MCP servers; its payload's `cwd` is still the session folder. Hooks imported from Claude Code or Kimi plugins do\nnot run yet: Vibe reports them as a config issue.\n\nA duplicate `name` across the two files is reported as a config issue and the\nproject entry wins. Config-load errors (invalid TOML, missing required\nfields) surface in the TUI as warnings and the offending hook is skipped.\n\n```toml\n[[hooks]]\nname = \"lint\"                       # Required: unique within the file.\ntype = \"post_agent\"                 # Required: see the table below.\ncommand = \"eslint --quiet .\"        # Required: shell command run in cwd.\ntimeout = 60.0                      # Default: 60s for all hooks.\ndescription = \"Run ESLint\"          # Optional.\n\n[[hooks]]\nname = \"build-after-reviewer\"\ntype = \"post_agent\"\nmatch = \"reviewer\"                  # Agent matcher on post_agent (default \"*\").\ncommand = \"make check\"\n\n[[hooks]]\nname = \"deny-rm-rf\"\ntype = \"pre_tool\"\nmatch = \"re:bash|process_start\"     # Tool-name matcher on tool hooks (default \"*\").\nstrict = true                       # pre_tool, post_tool, pre_agent: escalate any failure to block.\ncommand = \"uv run python \u002Fpath\u002Fto\u002Fguard-bash\"\n\n[[hooks]]\nname = \"add-branch\"\ntype = \"pre_agent\"\nmatch = \"main\"                      # Agent matcher, as on post_agent.\ncommand = \"~\u002F.vibe\u002Fhooks\u002Fadd-branch.sh\"\n\n[[hooks]]\nname = \"notify-me\"\ntype = \"notification\"\nmatch = \"re:approval_requested|turn_completed\"  # Notification-type matcher.\ncommand = \"~\u002F.vibe\u002Fhooks\u002Fnotify.sh\"\n\n[[hooks]]\nname = \"load-context\"\ntype = \"session_start\"\nmatch = \"startup\"                   # Source matcher: startup | resume | clear | fork.\ncommand = \"jq -Rs '{hook_specific_output: {additional_context: .}}' .vibe\u002Fcontext.md\"\n\n[[hooks]]\nname = \"export-transcript\"\ntype = \"session_end\"\nmatch = \"exit\"                      # Reason matcher: exit | clear | other.\ncommand = \"~\u002F.vibe\u002Fhooks\u002Fexport.sh\"\n```\n\n| Type | When it runs |\n|---|---|\n| `pre_agent` | Once per turn, after the user sends a message and before the model reads it. |\n| `post_agent` | Once per turn, after the agent finishes responding (no pending tool calls). |\n| `pre_tool` | Per tool call, before the user permission prompt. |\n| `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). |\n| `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. |\n| `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 `\u002Fclear`), `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. |\n| `session_end` | When a main session closes. `reason`: `exit` (Vibe quits, or Vibe Desktop releases the session), `clear` (`\u002Fclear`), `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. |\n\n**Closing.** When Vibe quits (`\u002Fexit`, end of a `vibe -p` run, Vibe Desktop\nquitting), it stops the `session_start` hooks still running at once (their\nsession is over), then waits for the `session_end` and `notification` hooks\nstill running, 5 s at most in all, then stops them. Vibe Desktop may close a\nsession's process sooner. Switching session (`\u002Fnew`,\n`\u002Fresume`, a rewind) does not stop the running hooks; the new session's first\nmessage waits for the old session's `session_end` hooks.\n\n**Matcher syntax** (same as `enabled_tools`): fnmatch glob by default\n(`\"bash\"`, `\"read_*\"`, case-insensitive), or a regex full-match when the\npattern starts with `re:` (`\"re:(read_file|write_file)\"`).\n\n**Agent matching on `post_agent`.** `post_agent` fires after the main agent's\nturns and after every subagent's turns. Its `match` selects the agent that\nfinished its turn, by agent configuration name: the main session's current\nagent (`accept-edits`, `ask`, a custom agent file name), a subagent's\n`config_name` (e.g. `reviewer`, or `\u003Cplugin-name>:\u003Cname>` for a plugin agent), or,\nfor a subagent spawned without one, its parent's agent. The reserved alias `main`\nmatches the main session (root or fork) whatever its agent, and never a\nsubagent; no agent may be named `main`. A mode name also matches the subagents\nthat inherit it, so use `match = \"main\"` to run only after the main agent.\nWithout `match` (or with `\"*\"`), a `post_agent` hook runs for every agent. A\nhook that does not match is skipped silently. `pre_agent` matches agents the\nsame way, for the agent whose turn starts.\n\n**Tool name conventions** for matchers:\n- `match` accepts a tool's short name (`bash`, `read_file`, `process_start`, …)\n  or its qualified name (`file_system.bash`). The `tool_name` field in the\n  payload is always the qualified name (`file_system.bash`, `mcp_\u003Cserver>.\u003Ctool>`\n  for MCP tools), so scripts that read it must expect that form.\n- Commands the agent starts in the background go through `process_start`,\n  which `match = \"bash\"` does not catch. Shell guards should use\n  `match = \"re:bash|process_start\"`.\n- MCP tools: `mcp_{server}.{tool}`, with characters other than letters,\n  digits, `_` and `$` replaced by `_` in both parts (e.g. `mcp_linear.create_issue`\n  for the `create-issue` tool of the `linear` server). Match it as\n  `mcp_linear.create_issue`, `mcp_linear_create_issue`, the bare `create_issue`,\n  or a glob such as `mcp_linear.*`.\n- Connector tools: `connector_{normalized-name}_{remote-tool-name}` (e.g.\n  `connector_Google_Drive_search_files`).\n- There is no `task` tool. Subagents are spawned through `agents_spawn`\n  (`agents.spawn` in the payload). Match with `match = \"agents_spawn\"` and read\n  `tool_input.config_name` (absent for a generic subagent) to discriminate by\n  subagent.\n\nSubagent invocations inherit the parent's hook config. Their hook events are\nlogged to the subagent's session log and don't propagate to the parent's UI.\n\n#### Wire protocol\n\nEvery hook is spawned in `cwd` and receives a JSON object on **stdin**\ndiscriminated by `hook_event_name`:\n\n```json\n\u002F\u002F pre_agent\n{\"hook_event_name\": \"pre_agent\", \"session_id\": \"...\",\n \"parent_session_id\": null, \"transcript_path\": \"...\", \"cwd\": \"...\",\n \"agent_name\": \"accept-edits\", \"turn_id\": \"turn-...\",\n \"prompt\": \"fix the bug\"}          \u002F\u002F the text the user typed; attachments are not included\n\n\u002F\u002F post_agent\n{\"hook_event_name\": \"post_agent\", \"session_id\": \"...\",\n \"parent_session_id\": null, \"transcript_path\": \"...\", \"cwd\": \"...\",\n \"agent_name\": \"accept-edits\"}    \u002F\u002F agent configuration name; null when unknown\n\n\u002F\u002F notification\n{\"hook_event_name\": \"notification\", \"session_id\": \"...\",\n \"parent_session_id\": null, \"transcript_path\": \"...\", \"cwd\": \"...\",\n \"notification_type\": \"approval_requested\",  \u002F\u002F | question_asked | turn_completed | turn_failed\n \"message\": \"Vibe needs your approval to run file_system.bash\",\n \"agent_name\": \"accept-edits\",\n \"tool_name\": \"file_system.bash\"}  \u002F\u002F approval_requested only\n\n\u002F\u002F session_start\n{\"hook_event_name\": \"session_start\", \"session_id\": \"...\",\n \"parent_session_id\": null, \"transcript_path\": \"...\", \"cwd\": \"...\",\n \"source\": \"startup\",              \u002F\u002F | resume | clear | fork\n \"agent_name\": \"accept-edits\", \"model\": \"mistral-medium-3-5\"}  \u002F\u002F active model alias\n\n\u002F\u002F session_end\n{\"hook_event_name\": \"session_end\", \"session_id\": \"...\",\n \"parent_session_id\": null, \"transcript_path\": \"...\", \"cwd\": \"...\",\n \"reason\": \"exit\"}                 \u002F\u002F | clear | other\n\n\u002F\u002F pre_tool\n{\"hook_event_name\": \"pre_tool\", \"session_id\": \"...\", \"parent_session_id\": null,\n \"transcript_path\": \"...\", \"cwd\": \"...\",\n \"tool_name\": \"file_system.bash\", \"tool_call_id\": \"call_42\",\n \"tool_input\": {\"command\": \"ls\"}}\n\n\u002F\u002F post_tool\n{\"hook_event_name\": \"post_tool\", \"session_id\": \"...\", \"parent_session_id\": null,\n \"transcript_path\": \"...\", \"cwd\": \"...\",\n \"tool_name\": \"file_system.bash\", \"tool_call_id\": \"call_42\",\n \"tool_input\": {\"command\": \"ls\"},\n \"tool_status\": \"success\",         \u002F\u002F success | failure; a non-zero exit code is still success\n \"tool_output\": {\"command\": \"ls\", \"stdout\": \"...\", \"stderr\": \"\", \"returncode\": 0,\n                 \"was_truncated\": false, \"timed_out\": false},  \u002F\u002F structured result on success; null on failure\n \"tool_output_text\": \"...\",         \u002F\u002F current text the LLM will see; mutable by prior hooks\n \"tool_error\": null,                \u002F\u002F populated on failure\n \"duration_ms\": 0}                  \u002F\u002F not reported yet: always 0\n```\n\n`parent_session_id` is set when running inside a subagent. Exceeding\n`timeout` kills the whole process tree.\n\nA hook signals back via its **exit code** and **stdout** (stderr is reserved\nfor diagnostics — Vibe never parses it for control):\n\n| Exit | Stdout | Behavior |\n|---|---|---|\n| `0` | empty | Pass through (no action). |\n| `0` | valid structured-response JSON object (schema below) | Act per the JSON fields. |\n| `0` | anything else (free-form text, broken JSON, scalar\u002Farray, schema mismatch) | Failure path (see below). The parse error is in the message. |\n| non-zero \u002F timeout \u002F spawn failure | — | Failure path. Reason taken from stderr, then stdout, then the exit code. |\n\nStructured-response schema:\n\n```json\n{\n  \"decision\": \"allow\" | \"deny\",          \u002F\u002F optional; default \"allow\"\n  \"reason\": \"string\",                     \u002F\u002F required when decision == \"deny\"\n  \"system_message\": \"string\",             \u002F\u002F optional UI note\n  \"hook_specific_output\": {\n    \"tool_input\": { ... },                \u002F\u002F pre_tool only\n    \"prompt\": \"string\",                   \u002F\u002F pre_agent only\n    \"additional_context\": \"string\"        \u002F\u002F post_tool, pre_agent and session_start only\n  }\n}\n```\n\n`notification` and `session_end` hooks' stdout is ignored. A `session_start`\nhook reads only `additional_context` and `system_message`: it cannot block the\nsession.\n\n- `hook_specific_output.additional_context` (`session_start`): text added, out\n  of the user's sight, to the session's next user message, for the model. With\n  several `session_start` hooks, their texts are joined in run order. If a\n  `pre_agent` hook blocks that message, the text waits for the next one.\n- `system_message` (`session_start`): shown as the hook's line in the\n  conversation (`✓ [hook-name] message`), not among the startup warnings.\n\nUnknown fields are tolerated at every level. Fields that aren't meaningful\nfor the current hook type are silently ignored.\n\n**Don't self-name in `system_message` or `reason`** — the UI prefixes\nhook-end-event content with `[hook-name]` automatically, and `pre_tool`\ndenials are wrapped as ``Tool 'X' was denied by hook 'Y': {reason}`` before\nthe LLM sees them. A hook that writes ``\"reason\": \"guard: refused...\"``\nwill produce ``hook 'guard': guard: refused...`` downstream.\n\n`decision: \"deny\"` per hook type:\n\n| Hook | Effect of `decision: \"deny\"` |\n|---|---|\n| `pre_agent` | Block the message: the turn ends, the model never sees it, and the hook's line shows `Blocked the message: \u003Creason>` (or the hook's `system_message`). First deny short-circuits the remaining `pre_agent` hooks. |\n| `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. |\n| `post_tool` | Replace `tool_output_text` with `reason`. Pipeline continues; subsequent hooks see the replacement. |\n| `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. |\n\nEvent-specific payloads:\n\n- `hook_specific_output.tool_input` (`pre_tool`): full replacement of the\n  model's arguments. Vibe re-validates against the tool's schema **after each\n  rewriting hook** — the first invalid rewrite aborts the chain and\n  synthesizes a denial attributing the failure to that hook. Rewrites\n  compose: hook N receives `tool_input` as rewritten by hooks 1..N-1.\n- `hook_specific_output.additional_context` (`post_tool`): text appended\n  (with `\n`) to the current `tool_output_text`. Composes with a same-hook\n  `decision: \"deny\"`: deny replaces first, then `additional_context` is\n  appended to the replacement.\n- `hook_specific_output.prompt` (`pre_agent`): replaces the text the user\n  typed; attachments are kept. The model sees the new text; the user's\n  transcript still shows what they typed. Rewrites compose: hook N receives\n  the `prompt` as rewritten by hooks 1..N-1.\n- `hook_specific_output.additional_context` (`pre_agent`): text added after\n  the user's message, for the model only. It stays in the conversation for\n  the following turns. With `prompt` in the same response, the text is\n  replaced first, then the context is added.\n\nWhen a `pre_agent` hook rewrites the message or adds context, its line in the\ntranscript previews what the model gets instead (one line, cut at 80\ncharacters), unless the hook returns its own `system_message`. The session\ntitle is generated from what the model got as well: rewritten-away text and\nblocked messages never reach the title model. A message steered into the turn\nwhile its `pre_agent` hooks run waits for them to finish.\n\n**Failure path.** Any failure (non-zero exit, timeout, spawn failure,\nnon-conforming stdout) emits a UI warning and lets the gated action proceed\n(fail open). With `strict = true`:\n\n| Hook | Strict failure escalates to |\n|---|---|\n| `pre_agent` | Block the message. |\n| `pre_tool` | Deny the tool call with the failure reason. |\n| `post_tool` | Clear `tool_output_text` (replace with empty). |\n\n`strict` is forbidden on `post_agent`, `notification`, `session_start` and\n`session_end`. A failing `session_start` hook shows a warning and the session\ngoes on without its context.\n\n#### Execution semantics\n\n- A hook `command` is a shell line, run in the session working directory. On\n  macOS and Linux it runs in `\u002Fbin\u002Fsh`. On native Windows it runs in the same\n  shell as the `bash` tool: Git Bash when it is installed, otherwise\n  PowerShell. Write Windows hooks in that shell's syntax.\n- On macOS and Linux, what a hook starts in the background (`cmd &`) is killed\n  when the hook exits. A hook that must leave work running has to move it to a\n  new session itself: `setsid cmd &` on Linux; macOS has no `setsid` command,\n  so use `perl -MPOSIX -e 'POSIX::setsid(); exec @ARGV' cmd &`.\n- Hooks of the same type fire sequentially in load order (project file first,\n  then user file; declaration order within each file). `notification` and\n  `session_end` hooks are the exception: they start together, in the\n  background.\n- Tool calls within a single LLM turn run **concurrently**; each call's hook\n  chain runs serially but the chains run in parallel across calls. Hooks\n  that touch shared state (filesystem, env) must coordinate themselves.\n- `pre_tool` rewrites take effect everywhere downstream: the user\n  permission prompt sees the rewritten arguments, the tool runs with them,\n  and the assistant message is patched so subsequent LLM turns reflect what\n  actually ran.\n\n### Pattern Matching\n\nTool, skill, and agent names support three matching modes:\n- **Exact**: `\"bash\"`, `\"read_file\"`\n- **Glob**: `\"bash*\"`, `\"mcp_*\"`\n- **Regex**: `\"re:^serena_.*$\"` (full match, case-insensitive)\n\n## CLI Parameters\n\n```\nvibe [PROMPT]                       # Start interactive session with optional prompt\nvibe -p TEXT \u002F --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\nvibe -p TEXT --auto-approve          # Programmatic mode with all tool calls approved\nvibe -p TEXT --agent lean --yolo      # Lean mode with all tool calls approved\nvibe --agent NAME                   # Select agent profile (falls back to `default_agent` config)\nvibe --auto-approve \u002F --yolo         # Approve all tool calls. New session on ask\u002Faccept-edits\u002Fsmart-approve: starts the auto-approve agent. Custom agent or resumed session: keeps its agent, every call approved\nvibe --workdir DIR                  # Change working directory\nvibe --worktree NAME                # Create\u002Freuse a git worktree under $VIBE_HOME\u002Fworktrees 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\u002F--check-upgrade.\nvibe --worktree                     # Same, but Vibe picks an unused name from the prompt (a random slug when there is no prompt) on a vibe\u002F\u003Cname> branch, and never reuses an existing worktree. The prompt must precede the flag or follow a `--`, since --worktree otherwise reads it as NAME.\nvibe --add-dir DIR                  # Extra working dir loaded for context (repeatable). Implicitly trusted.\nvibe --trust                        # Trust cwd for this invocation only (not persisted). Skips the trust prompt.\nvibe -c \u002F --continue                # Continue this terminal's last session in cwd, else the latest in cwd\nvibe --resume [SESSION_ID]          # Resume a specific session\nvibe -v \u002F --version                 # Show version\nvibe --setup                        # Run onboarding\u002Fsetup\nvibe update \u002F vibe --check-upgrade  # Check for a Vibe update now, prompt to install it, and exit\nvibe --max-turns N                  # Max assistant turns (programmatic mode)\nvibe --max-price DOLLARS            # Max cumulative run cost, root + subagents; models priced 0 are free (programmatic mode)\nvibe --max-tokens N                 # Max cumulative run tokens, root + subagents (programmatic mode)\nvibe --system-prompt-file PATH      # Replace Vibe's own system prompt instructions with PATH's text (programmatic mode)\nvibe --model ALIAS                  # Model alias for this run, not saved (programmatic mode)\nvibe --enabled-tools TOOL           # Enable specific tools (repeatable)\nvibe --disabled-tools TOOL          # Disable specific tools (repeatable)\nvibe --output text|json|streaming   # Output format (programmatic mode)\n```\n\nProgrammatic mode never prompts for approval: a tool call that needs it is\ndenied unless the run approves calls without a prompt. `--auto-approve` (or\n`--agent auto-approve`) approves them, and `--smart-approve` lets a classifier\napprove the ones it judges safe. Each denied call is printed on stderr as\n`Tool call denied: \u003Ctool> (\u003Creason>)` and listed in the export's `warnings`.\nA text run whose agent finishes without an answer says so on stderr.\n\n## Built-in Agents\n\nThere are two kinds of agents:\n- **Agents** are user-facing profiles selectable via `--agent` or `Shift+Tab`.\n  They configure the model's behavior, tools, and system prompt.\n- **Subagents** are model-facing: the model can spawn them autonomously to delegate\n  subtasks (e.g. exploring the codebase). Users cannot select subagents directly.\n\n### Agents\n\n- **ask**: Requests approval for tool executions\n- **accept-edits**: Default agent; auto-approves file edits but asks for other tools\n- **auto-approve**: Auto-approves all tool calls\n- **lean**: Specialized Lean 4 proof assistant. Not available by default — must be\n  installed with `\u002Fleanstall` (removed with `\u002Funleanstall`). Use `--agent lean\n  --auto-approve` or `--agent lean --yolo` to run Lean mode without tool prompts.\n\n### Subagents\n\n- **explore**: Read-only codebase exploration subagent with grep, file reading,\n  and skill loading. Spawned by the model, not selectable by the user.\n\nCustom agents are TOML files in `~\u002F.vibe\u002Fagents\u002FNAME.toml`. One with\n`agent_type = \"subagent\"` is a custom subagent the model can spawn. It can load\nevery skill the parent session has, unless its file sets `enabled_skills` or\n`disabled_skills`; these accept the same patterns as in `config.toml`.\n\nA custom subagent (`agent_type = \"subagent\"`) runs with the session's hooks.\nEach of its tool calls must pass both the session's rules and its own\n`[tools.NAME]` rules (`permission`, `allowlist`, `denylist`): a profile can\nrefuse or prompt for what the session allows, never allow what the session\nrefuses or prompts for. `enabled_tools`\u002F`disabled_tools` likewise only narrow\nthe session's tools. Approvals given to a subagent's prompts apply to the whole\nsession. Under `--auto-approve`, a subagent's calls run without consulting\nallowlists or denylists, like the main agent's; a tool its profile sets to\n`never` stays refused.\nTo use a custom subagent, the model must spawn it by its agent type\n(`config_name`); a subagent spawned with only a name inherits the main agent's\nconfiguration.\n\nA subagent's instructions (`instructions`, or the prompt `system_prompt_id`\nnames) are limited to 65,536 characters; the primary agent's prompt has no such\nlimit. An agent file that cannot be used (invalid TOML, an unknown `safety` or\n`agent_type`, an invalid setting, a missing prompt, instructions over the limit,\nno `description`) is not offered to the model. Vibe reports it as a config issue\nnaming the file and the failing field: in the startup Warnings box in the TUI,\na stderr line and an export warning with `-p`. Spawning that type fails with\n`subagent_type_rejected` and the same reason.\n\nA custom subagent runs on the session's model unless its file sets\n`active_model` to an alias from `[[models]]`; its children then run on that\nmodel (name, temperature, thinking, provider, and the model's context window,\ncompaction threshold and image support). The provider's endpoint and\ncredentials always come from the session's `[[providers]]`: a provider the\nagent file redefines is ignored for routing. An alias that is unknown, excluded\nby `allowed_models`, or served by a provider the session does not define\nrejects the agent file like the cases above; Vibe never falls back to the\nsession's model.\n\n## Built-in Slash Commands\n\n- `\u002Fhelp` - Show help message\n- `\u002Fconfig` - Full-screen settings browser. Fields show their value and origin layer (default \u002F TOML \u002F env \u002F 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 (`~\u002F.vibe\u002Fconfig.toml`) by default, `Tab` cycles the save target through the project config (`.vibe\u002Fconfig.toml`, when the project layer is active) and the ephemeral session override (until restart), and `Ctrl+R` clears the field one writable layer at a time toward the default. The `tools` field opens a grouped tool list with a per-tool config editor (permission, allow\u002Fdeny lists, `Ctrl+E` for raw JSON). Enabling\u002Fdisabling whole MCP servers or connectors stays in `\u002Fmcp`.\n- `\u002Fmodel` - Select active model. The footer shows the active model and its thinking level (`Name[level]`) before the context gauge; clicking it opens `\u002Fmodel` and keeps the prompt being typed (only while idle, like the typed command).\n- `\u002Fthinking` - Select thinking level\n- `\u002Ftheme` - Select UI theme; `auto` follows terminal\u002FOS appearance, `mistral-dark` and `mistral-light` use the Mistral brand palette (persisted in config)\n- `\u002Freload` - Reload configuration, agent instructions, and skills from disk (skills added or edited on disk are also picked up on their own while `file_watcher_for_autocomplete` is on, except in sandboxed sessions)\n- `\u002Fclear`, `\u002Fnew` - Start a new conversation. Optionally pass a prompt to seed it\n- `\u002Flog` - Show path to current interaction log file\n- `\u002Flog-level` - Show or set the log level. `\u002Flog-level` prints the full chain\n  (session, env, config, effective); `\u002Flog-level set \u003CLEVEL>` sets a\n  process-lifetime override; `\u002Flog-level set-global \u003CLEVEL>` also persists to\n  config.toml; `\u002Flog-level unset` clears the session override. LEVEL is one of\n  DEBUG, INFO, WARNING, ERROR, CRITICAL.\n- `\u002Fdebug` - Toggle debug console\n- `\u002Fcompact` - Compact model context by summarizing. The session ID and visible\n  conversation stay intact; the auto title is refreshed to reflect the\n  compacted conversation (unless renamed manually).\n- `\u002Frename \u003Ctitle>` - Set a manual session title. Persists to `meta.json`\n  (`title_source=manual`), updates the terminal tab title, and is never\n  overwritten by automatic title generation.\n- `\u002Fretry [additional instructions]` - Run again a model response that failed\n  with a backend error or was interrupted with Esc. The new reply replaces the\n  unfinished one and its error or interrupt row. Optional instructions are\n  passed to the model for the retry. Relevant error messages also hint at this\n  command; with nothing to retry, it says so.\n- `\u002Fpr` - List the session's pull requests and merge requests (needs\n  `enable_pr_monitoring`, on by default). Opening it refreshes their status.\n  Enter opens the highlighted one, `c` its first failing CI check, `f` drafts a\n  prompt asking the agent to fix what blocks it (shown only when it needs action).\n- `\u002Fstatus` - Display agent statistics\n- `\u002Fcontext` - Show what fills the context window: a full-width gauge of the\n  window split by category (system prompt, tool definitions including MCP and\n  connector tools, skills, conversation), then free space and the\n  auto-compaction buffer (marked where compaction triggers), with a legend of\n  each category's share and estimated tokens.\n  The total comes from the model API's last call; the repartition is estimated\n  from each category's share of the characters that call actually sent. The\n  footer's token counter opens the same view when clicked (also while a turn\n  runs); a subagent shows its own window. Estimates appear only once the\n  conversation has started (at least one model call); a resumed session shows\n  its total alone until its next model call.\n- `\u002Fwhoami` - Display the Mistral signed-in user, workspace, and plan\n- `\u002Fcopy` - Copy the last agent message to the clipboard\n- `\u002Fpaste-image` - Paste an image from the OS clipboard into the prompt.\n  **macOS only** — the command is not registered on Linux or Windows.\n- `\u002Fvoice` - Configure voice settings. With voice mode on, `Ctrl+T` starts or stops recording\n- `\u002Fmcp` (or `\u002Fconnectors`) - Display MCP servers and connector status. The\n  browser opens on the first item; press Up or Left to move into the fuzzy-search\n  bar, and Up again to wrap to the last item. Pass a server or connector name to\n  list its tools or open its auth panel when authentication is required\n- `\u002Fmcp add \u003Curl>` - Add a hosted OAuth MCP server. Supports `--name \u003Calias>`,\n  repeatable `--scope \u003Cscope>`, `--transport \u003Chttp|streamable-http>`,\n  `--no-login`, and `--allow-insecure-http` (permit a plaintext `http:\u002F\u002F` URL on\n  a non-localhost host such as a LAN server). Starts OAuth login by default.\n  OAuth-only; use `vibe mcp add \u003Cname> --url \u003Curl> --api-key-env \u003Cvar>` for\n  API-key\u002Fstatic auth.\n- `vibe mcp remove \u003Cname>` - Remove an MCP server from the user configuration\n  and delete its stored OAuth credentials when available.\n- `\u002Fmcp status` - Display MCP auth state (`ok`, `needs_auth`, `static`, `stdio`)\n- `\u002Fmcp login \u003Calias>` - Start OAuth login for an MCP server\n- `\u002Fmcp logout \u003Calias>` - Log out from an MCP server and delete stored OAuth\n  secrets\n- `\u002Fresume` (or `\u002Fcontinue`) - Browse and resume past sessions for the current\n  folder. The picker header shows the folder being listed. Press `d` twice to\n  delete a saved session; the active session cannot be deleted here.\n- `\u002Frewind` - Rewind to a previous message. Also triggered by pressing `Esc`\n  twice on an empty input; if the input has content, the first double-`Esc`\n  clears it instead. In the rewind panel: `↑\u002F↓` pick option, `Shift+↑\u002F↓`\n  scroll, `←`\u002F`Esc` edit previous message, `→` edit next message, `Enter`\n  accept, `q` quit.\n- `\u002Fbranch` (alias `\u002Ffork`) - Fork the current conversation into a new resumable\n  session, leaving this session unchanged. A panel in place of the input shows\n  the `vibe --resume \u003Cid>` command that resumes the copy; `Esc` or `Enter`\n  closes it. While the agent is working, the copy stops before the running\n  turn, so during the first turn it is empty. After an automatic compaction\n  during the turn, `\u002Fbranch` is refused until the turn ends.\n- `\u002Floop [schedule] [prompt]` - With the Unified Harness, ask the model to\n  schedule a recurring prompt, e.g. `\u002Floop every two minutes check the build`\n  or `\u002Floop weekdays at 9am review CI`.\n  - The full message is sent unchanged to the model, which uses the `cron` tool\n    to schedule, list, cancel, or clear prompts. There are no TUI management\n    subcommands; bare `\u002Floop` also goes to the model. Busy submissions queue.\n  - Fixed intervals have a 30-second minimum; calendar schedules use five-field\n    cron in the machine's local timezone. Maximum 50 schedules per session.\n  - Prompts run only while the session is live and idle, without catch-up bursts.\n    Schedules persist in `scheduled-loops.json` beside the Unified session and\n    are restored on resume. They do not run while Vibe is closed. `\u002Fclear`\n    starts a conversation with no schedules; the cleared session keeps its own\n    and runs them again when resumed. `\u002Fbranch` copies them.\n  - Each agent has its own schedules: a subagent calling `cron` schedules,\n    lists, cancels, and clears only its own prompts, never its parent's or a\n    sibling's. A subagent's prompt fires as a message to that subagent once it\n    is idle, and its result reaches the parent like any other subagent result,\n    without starting a parent turn. Its schedules persist beside the subagent's\n    session and stop with it. `\u002Floop` and the `loops\u002F*` client methods still\n    show and manage only the main session's schedules.\n- `\u002Fproxy-setup` - Configure proxy and SSL certificate settings\n- `\u002Fleanstall` - Install the Lean 4 agent (leanstral)\n- `\u002Funleanstall` - Uninstall the Lean 4 agent\n- `\u002Fplugins` - Display the plugins this session is running (Unified Harness only). Shows each plugin's name, scope, source format, content digest, and\n  components (skills, MCP servers, agents, hooks, knowledge, connectors, tools).\n  Press `r` inside the view to reload.\n- `\u002Freload-plugins` - Re-pin this session's plugins and report what changed\n  (Unified Harness only). Re-discovers plugins from disk, re-pins the\n  snapshot, and prints a diff of added, removed, and updated plugins.\n- `\u002Fskills` - Browse and manage skills. My Skills shows one row per installed\n  skill, the one its name resolves to: enabled (`●`) or disabled (`○`), Scope,\n  Source (Local, Shared or Plugin) and, for shared skills, the pinned version,\n  with `● New vN` when a number-pinned shared skill has a newer version. A\n  locked skill (from a plugin, or fixed by `enabled_skills` or a\n  `disabled_skills` pattern) shows its marker in the warning color and cannot be\n  toggled. Narrow terminals drop the Version, Source and Scope columns, in that\n  order, and on Shared the Version and Sharing columns. `\u002F` searches names and\n  descriptions; `Space` turns the highlighted skill on or off for every scope\n  (written to `disabled_skills`) when its name loads that skill; the highlighted\n  skill's body shows under the list, or beside it from 140 columns, and `Enter`\n  opens its page with its description and the body of the highlighted scope's\n  pin under or beside the rows, which PageUp and PageDown scroll. With\n  `experimental_enable_registry_skills`, My Skills and Shared are tabs switched\n  with `Tab`, Shared listing the shared skills you can still add, `d` then `d`\n  again on the My Skills row of a shared skill removes it from every scope, and\n  a shared skill's page has a row per scope (Global, and Project inside a\n  project): `vN pinned`, `alias → vN tracking`, or `not pinned`, marked\n  `(shadowed)` when the name loads another pin. Enter on a scope opens its\n  picker of aliases and versions on the choice in effect, else on the other\n  scope's, previewing the highlighted version's body: picking one pins that\n  scope at once, and `Unpin from \u003Cscope>` drops just that pin (not the last one,\n  which is what Remove is for). Enabled, on the page of the skill its name\n  loads, switches the skill in every scope, and Remove, pressed twice, unpins it\n  from every scope. A shared skill not added yet starts out tracking latest in\n  Global, or in Project when another shared skill of its name holds Global,\n  since a scope pins one skill per name and pinning over another is refused;\n  each scope takes its own choice, `Don't pin here` drops one, and nothing is\n  written until Add to My Skills. Each new version of a pinned skill is\n  announced once, when a session starts. A session fetches the pinned skill\n  bodies missing from this machine and moves alias pins to their current version\n  when it starts, and again when its project changes. A sandboxed session copies\n  the versions it loads into its sandbox, and keeps its older copy of a skill\n  that tracks an alias when it cannot copy the new one. When the session's fetch\n  succeeds, at most once a day per machine, it deletes stored versions no\n  session has used for 30 days, if the registry still serves them, so a later\n  sync can fetch them again. It keeps each skill's newest stored version, every\n  version pinned globally, in this project or in another project a session read,\n  the version each alias pin last resolved to, and every version a running\n  session loads. The 30 days count from when a session last stored or used the\n  version, or else from the first cleanup that saw it. Vibe releases from before\n  this cleanup do not record what they load, so a version only they load through\n  a project pin on its number can still be deleted. The command shadows a user\n  skill named `skills`.\n- `\u002Fdata-retention` - Show data retention information\n- `\u002Frelease-notes` (alias `\u002Fwhatsnew`) - Browse the release notes of every Vibe\n  version, newest first, in a full-screen, searchable pager. When a new\n  session starts after an upgrade (not on resume), Vibe shows the notes of\n  every release since the last one seen\n  (the newest five, then a pointer to `\u002Frelease-notes`); the last seen version is\n  `seen_whats_new_version` in `~\u002F.vibe\u002Fcache.toml`.\n- `\u002Fexit` - Exit the application\n\n## File Mentions (`@`)\n\nType `@` in the chat input to autocomplete files and folders. A bare `@`\nlists non-hidden immediate children directly from the filesystem for fast\nbrowsing. Once you type a path character, Git workspaces use tracked and\nnon-ignored untracked paths, including nested `.gitignore` rules; outside Git\nthe picker falls back to the project tree. Pressing Tab\u002FEnter inserts the\nchosen path. Your message text is sent as-is (the `@path` stays in the prompt);\nbehavior then depends on the mention kind:\n\n- **Text files** trigger a synthetic `read_file` tool call injected right\n  after your message, so the file content arrives as a fresh tool result\n  every turn (no caching\u002Fdedup). The same limits as the `read_file` tool\n  apply (~2000 lines \u002F 64 KB per call; larger files are truncated or\n  reported as an error result). Re-mentioning a file always re-reads it.\n- **Folders** are not read automatically — the path stays in your message\n  text and the agent can `read_file`\u002F`grep` it on demand.\n- **Image files** (`.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`) become image\n  attachments — sent alongside the prompt as native multimodal content for\n  vision-capable models.\n\nImage attachments:\n\n- Require `supports_images = true` on the active model in `config.toml`.\n  The legacy loop rejects an image its model cannot read; under\n  the Unified Harness the agent is shown a description of the image\n  instead, and with no describer reachable the CLI refuses a pasted image.\n- The describer is picked automatically: any `supports_images` model on\n  the active model's **own** provider, no config needed. Same provider\n  means same key and same endpoint, so no image goes anywhere the session\n  was not already talking. A `vision_model` in `config.toml` overrides\n  that choice and is the only way to reach another provider.\n- Each image is then described once as the turn's input is prepared, and\n  the agent receives the text inside an `\u003Cimage alias=\"...\">` block in\n  place of the pixels. The user's prompt steers what the describer looks\n  for, and the description is reused for the rest of the session. The\n  describer never reasons, whatever its `thinking` says: the trace is\n  charged against the description budget and buys nothing on a\n  transcription. Claude models that can't turn thinking off (Opus 5.5,\n  Sonnet 5.5, Fable 5) reason at their lowest level instead.\n- A describe that fails does not fail the turn. The agent gets a\n  placeholder saying the image could not be read, and a warning names the\n  image and the provider's reason.\n- With no describer reachable the image is left alone, and a model that\n  cannot read images gets `[Image]` and, for an image from a file, a\n  `file:\u002F\u002F` link to it.\n- Snapshotted into `\u003Csession_dir>\u002Fattachments\u002F\u003Csha1>.\u003Cext>` so that\n  resumed sessions stay reproducible even if the source file is moved.\n- Capped at 10 MiB per image and 8 images per message.\n- Out-of-project paths work via `@\u002Fabs\u002Fpath\u002Fto.png` (the picker only\n  suggests project files, but the `@`-parser accepts absolute paths).\n  Drag-and-drop from Finder into Terminal, iTerm2, or Ghostty is\n  intercepted at paste time: if the pasted content is a standalone existing\n  absolute or home-relative file or folder (or a newline-delimited list of\n  them), the input automatically prepends `@` and quotes paths containing\n  spaces. This applies to text files and folders (images become\n  `[Image #N]` placeholders, see below); pasted prose, relative paths, and\n  missing paths are left unchanged.\n- **Image copy\u002Fpaste from the clipboard** (**macOS only** for now):\n  writes the image to a persistent temporary PNG, inserts a numbered\n  `[Image #N]` placeholder at the cursor, then snapshots it under the\n  session attachments on submit. A pasted standalone image path becomes a\n  placeholder too. In a `!` shell command the raw path is inserted instead.\n  Two entry points:\n  1. `Ctrl+V` keybinding inside the prompt.\n  2. `\u002Fpaste-image` slash command.\n\n  Uses `osascript` with a TIFF→PNG fallback via `sips`. On Linux and\n  Windows the binding and the slash command are not registered at all,\n  so the feature is invisible to users on those platforms.\n- Rendered in the chat bubble as one dim `attached image:` footer line\n  per image, linking each attachment to its snapshot. Clicking opens the\n  file with the OS default image viewer.\n\n## Footer\n\nThe bottom row of the CLI reads, left to right:\n\n- The working directory, elided from the start (`…\u002Fvibe\u002Fcli-rust`) so the project name stays visible. Once shortened, it is followed by `⧉`: clicking it copies the whole path.\n- The session's PR\u002FMR, 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\u002FMRs give way to the open one; several open ones show `PRs (2)`. A bold label means a new PR\u002FMR or a status change not seen in `\u002Fpr` yet. Clicking the link opens the `\u002Fpr` list and refreshes the statuses; clicking it again closes the list. PRs\u002FMRs are found when the session starts, after turns (at most once a minute), and when a `gh pr create`\u002F`glab mr create` runs; statuses refresh about every minute.\n- 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.\n- Then the context gauge `ctx ▍      23k\u002F600k`.\n\nOn 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 (`…\u002Fcli-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.\n\nThe 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 `\u002Fmodel` while the session is idle. On narrow terminals the border drops the thinking level, then elides and hides the model; the mode stays whole.\n\n## Input Queue\n\nPrompts submitted while the agent is running are accepted by the app server\nand merged into a single follow-up turn, so everything queued during one\ngeneration is delivered to the agent together (not one turn per prompt) once\nit finishes. Each queued prompt keeps its own message so it stays individually\neditable and removable. This includes plain prompts, `\u002Fskill ...`\nprompts, and prompts with `@` mentions. `!bash` and non-side-channel slash\ncommands require an idle session and are rejected while busy\nwith a notice above the input that stays until the next key. **Ctrl+C** removes the newest queued prompt (LIFO); **Esc**\ninterrupts the active turn and pauses the remaining queue; pressing Enter\n(empty or not) on a paused queue resumes it.\n\nAllowlisted slash commands (`side_channel=True`) run immediately via a\nside channel while the agent or bash is busy, or while prompts are queued.\nOnly one side-channel command runs at a time. Besides read-only commands\n(`\u002Fhelp`, `\u002Fstatus`, `\u002Fcopy`, ...), this includes settings a running turn\nnever reads (`\u002Ftheme`, `\u002Flog-level`, `\u002Fvoice`, `\u002Fproxy-setup`), whose changes\nare saved at once, and `\u002Fbranch`, which forks the conversation as it was before\nthe running turn. These still wait for a compaction or agent install to finish.\n\nCommands that change what a turn reads (e.g. `\u002Fconfig`, `\u002Fmodel`,\n`\u002Fthinking`, `\u002Fclear`, `\u002Fcompact`, `\u002Frewind`, `\u002Fresume`,\n`\u002Freload`, `\u002Fleanstall`, `\u002Funleanstall`, `\u002Fretry`, `\u002Fmcp`,\n`\u002Fconnectors`, `\u002Fplugins`, `\u002Freload-plugins`, `\u002Fskills`) are rejected while\nbusy and can be retried when the session is idle.\n\nWhile the queue is non-empty and the agent is busy, pressing **Up**\nenters queue selection mode: the last queued item is highlighted and\nthe input is locked (no cursor, no typing). **Up\u002FDown** navigate\nbetween queued prompts, **Enter** loads the selected prompt into the input\nfor editing (press Enter again to update it in-place), **Backspace** (or\n**Ctrl+C**) removes the selected item and moves selection to the next, and\n**Esc** exits selection mode and\nrestores the original input text.\n\n## Plugins\n\nPlugins are bundled extension packages that can contribute skills, MCP servers,\nhooks, agents, knowledge, connectors, and tool libraries to a session. They are\ndiscovered from two locations:\n\n- `~\u002F.vibe\u002Fplugins\u002F` — user scope (global, available in every project)\n- `.vibe\u002Fplugins\u002F` — project scope (requires trusted folder; overrides\n  user scope on name collisions)\n\nEach subdirectory inside a plugins directory is a single plugin root.\n\nTo turn a plugin off without uninstalling it, list its manifest `name` in\n`disabled_plugins` in `config.toml` (e.g. `disabled_plugins = [\"my-plugin\"]`).\nA plugin whose `ai.mistral.vibe` extension sets `\"defaultEnabled\": false` is\noff until its name is in `enabled_plugins`. Both lists add up across config\nlayers, `disabled_plugins` wins, and an off plugin contributes nothing to\nsessions.\n\n### Plugin Manifest (`plugin.json`)\n\nEvery plugin has a `plugin.json` manifest following the **Agent Plugins 1.0**\nschema (`https:\u002F\u002Fagent-plugins.org\u002Fschemas\u002F1.0.0\u002Fplugin.schema.json`):\n\n```json\n{\n  \"$schema\": \"https:\u002F\u002Fagent-plugins.org\u002Fschemas\u002F1.0.0\u002Fplugin.schema.json\",\n  \"name\": \"my-plugin\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Optional description\",\n  \"author\": { \"name\": \"Author\", \"email\": \"a@b.com\", \"url\": \"https:\u002F\u002F...\" },\n  \"homepage\": \"https:\u002F\u002F...\",\n  \"repository\": \"https:\u002F\u002F...\",\n  \"license\": \"MIT\",\n  \"keywords\": [\"tag1\"],\n  \"extensions\": {\n    \"ai.mistral.vibe\": {\n      \"schemaVersion\": 1,\n      \"displayName\": \"My Plugin\",\n      \"toolNamespace\": \"myNs\",\n      \"toolOverrides\": {\n        \"tool-name\": { \"name\": \"renamedTool\", \"exposure\": \"programmatic\" }\n      }\n    }\n  }\n}\n```\n\nThe `ai.mistral.vibe` extension is optional but gates all Vibe-specific\ncomponents (hooks, knowledge, agents). `toolNamespace` is a\nTypeScript-identifier-safe string used to prefix all component names\n(e.g. `myNs:my-skill`). If omitted, it is derived from the plugin name.\n`displayName` is an optional title of 1 to 100 characters; the plugin `name`\nstays its identity.\n\nAn unknown top-level `plugin.json` field, or an unknown key in the extension, is\nignored with a warning and the rest loads. An invalid value for a known\nextension key drops the whole extension.\n\nReserved namespaces (rejected): `file_system`, `self`, `process`, `skill`,\n`subagent`, `vibe`.\n\n### Plugin Contents\n\nA plugin tree may contain any combination of:\n\n| Component | Location | Notes |\n|---|---|---|\n| Skills | `skills\u002F\u003Cname>\u002FSKILL.md` | Standard skill format |\n| MCP servers | `mcp.json` | `$schema` + `mcpServers` dict; stdio, streamable-http, sse |\n| Hooks | `ai.mistral.vibe\u002Fhooks.toml` | Max 128 hooks, 64 KiB |\n| Knowledge | `ai.mistral.vibe\u002Fknowledge\u002F\u003Cname>\u002FKNOWLEDGE.md` | Max 100 entries |\n| Agents | `ai.mistral.vibe\u002Fagents\u002F*.toml` | One TOML per subagent |\n| Libraries | `libraries.json` | Node and Python library path aliases |\n| Connectors | `connectors.json` | Connector tool mappings |\n\nEnvironment variables `PLUGIN_ROOT` and `PLUGIN_DATA` are injected\nautomatically into MCP server and hook processes and are reserved. Plugin\nhooks run in the plugin directory.\n\n### Foreign Plugin Formats\n\nVibe also adapts non-native plugin formats:\n\n| Format | Detection marker |\n|---|---|\n| Claude Code | `.claude-plugin\u002Fplugin.json` |\n| Codex | `.codex-plugin\u002Fplugin.json` |\n| Kimi Code | `.kimi.plugin.json` or `.kimi-plugin\u002Fplugin.json` |\n| OpenCode | `.opencode\u002Fplugins\u002F`, `.opencode\u002Fskills\u002F*\u002FSKILL.md`, `opencode.json` |\n\nAdapted plugins can only contribute **skills** and **MCP servers** — hooks,\nknowledge, agents, libraries, and connectors are native-only. Executable code\nin foreign plugins (`.js`, `.ts`) is refused and reported as unsupported.\n\n### Plugin Pinning and Reload\n\nOn session start, discovered plugins are resolved into a `ResolvedPluginSnapshot`\n— a portable, host-path-free, secret-free representation pinned to the session.\nThis ensures resume\u002Frewind reproducibility even if files change on disk.\n\n`\u002Freload-plugins` re-discovers plugins from disk, builds a fresh snapshot, and\nprints a diff (`+` added, `-` removed, `~` updated). Tools that are no longer\npresent after a reload are retained as unavailable routes so conversation\nhistory stays valid. Drift detection flags tools whose schema fingerprint changed\nbetween pin and live source.\n\n### Example Plugin Tree\n\n```\nmy-plugin\u002F\n├── plugin.json\n├── mcp.json\n├── skills\u002F\n│   └── my-skill\u002F\n│       └── SKILL.md\n└── ai.mistral.vibe\u002F\n    ├── hooks.toml\n    ├── knowledge\u002F\n    │   └── my-knowledge\u002F\n    │       └── KNOWLEDGE.md\n    └── agents\u002F\n        └── my-agent.toml\n```\n\n## Skills System\n\nSkills are specialized instruction sets the model can load on demand.\nEach skill is a directory containing a `SKILL.md` file with YAML frontmatter.\n\n### Skill File Format\n\n```markdown\n---\nname: my-skill\ndescription: What this skill does and when to use it.\nuser-invocable: true\nallowed-tools: bash read\n---\n\n# Skill Instructions\n\nDetailed instructions for the model...\n```\n\n### Skill Search Order (first match wins)\n\n1. `skill_paths` from config.toml\n2. `.vibe\u002Fskills\u002F` in trusted project directory\n3. `.agents\u002Fskills\u002F` in trusted project directory\n4. `~\u002F.vibe\u002Fskills\u002F` (user global)\n5. `~\u002F.agents\u002Fskills\u002F` (user global, Agent Skills standard)\n\nWith `experimental_enable_registry_skills`, the nearest scope wins instead, and\nwithin a scope a skill file wins over a registry pin: the project's skill files,\nthen the skills pinned in its `.vibe\u002Fskills.toml`, then the global skill files\n(`skill_paths`, `~\u002F.vibe\u002Fskills\u002F`, `~\u002F.agents\u002Fskills\u002F`), then the skills pinned in\n`~\u002F.vibe\u002Fskills.toml`. A registry pin goes by the name its manifest entry gives\nit; when two pins load the same stored version under different names, the one\nranked first loads it. A skill that loses its name is logged as shadowed: once\nper process as a warning under this rule, and at debug level otherwise.\n\n### Invoking Skills\n\nTwo entry points:\n- The model loads a skill on demand via the `skill` tool.\n- The user invokes a `user-invocable` skill by typing `\u002Fskill-name` (optionally\n  followed by extra instructions). The user turn stays the literal `\u002Fskill-name`\n  text; the skill is loaded programmatically and appears to the model as a\n  synthetic `skill` tool call and result immediately after that turn — the model\n  does not call the tool itself.\n\nSkills with `user-invocable: false` are model-only: they are hidden from the\nslash menu and `\u002Fskill-name` will not resolve them (it is treated as a plain\nprompt). The model can still load them via the `skill` tool.\n\nSkills with `disable-model-invocation: true` stay in the slash menu but are\nhidden from the model and cannot be loaded through the `skill` tool. Skills\nusing OpenAI's `agents\u002Fopenai.yaml` convention can set\n`policy.allow_implicit_invocation: false` for the same provider-independent behavior.\n\nA `\u002F` at the very start of the input opens the slash menu (commands and skills).\nA skill row's description starts with its scope in the theme's primary color,\nwhether it is a local, plugin or registry skill: `project · …` (this project\nonly), `user · …` (the user's, in every project), `vibe · …` (shipped with\nVibe), or `skill · …` when an older server does not say; a plugin skill also\nshows its plugin in its `plugin:name`. Rows without one are built-in commands.\nA built-in command wins over a skill with the same name, so such a skill is\nleft out of the slash menu; it can still be mentioned mid-prompt.\nA `\u002Fword` typed mid-prompt (not the first word) instead opens a completion popup\nof matching user-invocable skills, with the same scope prefix.\n\n## Environment Variables\n\n- `VIBE_HOME` - Override the Vibe home directory (default: `~\u002F.vibe`)\n- `MISTRAL_API_KEY` - API key for Mistral provider\n- `VIBE_ACTIVE_MODEL` - Override active model\n- `VIBE_*` - Any config field can be overridden with the `VIBE_` prefix\n- `LOG_LEVEL` - Overrides `log_level` config for `$VIBE_HOME\u002Flogs\u002Fvibe.log`.\n  One of `DEBUG`, `INFO`, `WARNING` (default), `ERROR`, `CRITICAL`. Invalid values\n  fall back to `WARNING`. Use `\u002Flog-level` to change at runtime.\n- `LOG_MAX_BYTES` - Max size in bytes of `vibe.log` before rotation\n  (default: `10485760`, i.e. 10 MiB).\n- `DEBUG_MODE` - When `true`, forces `DEBUG`-level logging.\n- `VIBE_TYPING_GRACE_PERIOD_MS` - Milliseconds the agent waits for a typing\n  pause before showing tool-approval \u002F ask-user-question dialogs (default:\n  `1000`). Set to `0` to disable. Negative or non-numeric values fall back\n  to the default.\n\n## API Keys (.env file)\n\nThe `.env` file in VIBE_HOME stores API keys in dotenv format:\n\n```\nMISTRAL_API_KEY=your-key-here\n```\n\nThis file is loaded on startup and its values are injected into the environment.\n\n## Trusted Folders\n\nVibe uses a trust system to prevent executing project-local config from untrusted\ndirectories. The trust database is stored in `~\u002F.vibe\u002Ftrusted_folders.toml`.\nProject-local config (`.vibe\u002F` directory) is only loaded when the current\ndirectory is explicitly trusted.\n\nInteractive mode prompts to trust unknown folders. The prompt targets the\nclosest ancestor of the cwd (the cwd itself included) containing a `.git`\nentry; the search excludes the user's home directory and the filesystem\nroot, and falls back to the cwd if no qualifying ancestor is found.\nProgrammatic mode (`-p`\u002F`--prompt`) never prompts: the folder is untrusted.\nUse `--trust` to trust cwd for the current invocation only (not persisted).\n`--trust` and `--worktree` both skip the prompt: they grant the workspace trust\nfor the session, so there is no decision left to ask about. Without this a\n`--worktree` run would prompt on every launch, since each worktree is a\ndirectory the trust database has never seen.\n\n## Sensitive Files — DO NOT READ OR EDIT\n\nNEVER read, display, or edit any of these files:\n- `~\u002F.vibe\u002F.env` (or `$VIBE_HOME\u002F.env`) — contains API keys and secrets\n- Any `.env`, `.env.*` file in the project or VIBE_HOME\n\nIf the user asks to set or change an API key, instruct them to edit the `.env`\nfile themselves. Do not offer to read it, write it, or display its contents.\nDo not use tools (read, write_file, bash cat\u002Fecho, etc.) to access these files.\n\n## How to Modify Configuration\n\nTo help the user modify their Vibe configuration:\n\n1. **Read current config when present**: Read `~\u002F.vibe\u002Fconfig.toml` (or the path\n   from `VIBE_HOME` if set). A missing file means Vibe is using built-in defaults.\n2. **Create a backup when present**: Before editing an existing file, copy it to\n   `config.toml.bak` in the same directory. This applies to any existing config\n   file you are about to modify (`config.toml`, `trusted_folders.toml`, agent\n   TOML files, etc.)\n3. **Edit the TOML file**: Make changes using the edit tool\n4. **Reload**: The user can run `\u002Freload` to apply changes without restarting\n\nFor API keys, tell the user to edit `~\u002F.vibe\u002F.env` directly — never read or\nwrite that file yourself.\n\nFor project-specific configuration, create\u002Fedit `.vibe\u002Fconfig.toml` in the\nproject root (the folder must be trusted first).\n",{"name":5,"description":7,"user-invocable":44},"main","b20313e01d6926d4191da89c32d5da11e3a0a060","vibe\u002Fplugins\u002Fbuiltins\u002Fvibe\u002Fskills\u002Fvibe",796,"https:\u002F\u002Fgithub.com\u002Fmistralai\u002Fmistral-vibe","https:\u002F\u002Fgithub.com\u002Fmistralai","mistral.ai","2026-11-11 00:56:57",{"source":8,"skillName":5,"commands":57,"prompt":67,"zipAvailable":68},[58,61,64],{"tool":59,"command":60},"npx","npx skills add mistralai\u002Fmistral-vibe --skill vibe",{"tool":62,"command":63},"bunx","bunx skills add mistralai\u002Fmistral-vibe --skill vibe",{"tool":65,"command":66},"pnpm","pnpm dlx skills add mistralai\u002Fmistral-vibe --skill vibe","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmistralai\u002Fmistral-vibe\u002Fblob\u002Fmain\u002Fvibe\u002Fplugins\u002Fbuiltins\u002Fvibe\u002Fskills\u002Fvibe\u002FSKILL.md",true,[],[71,97,120,140,160,166],{"skillNo":72,"name":73,"nameZh":6,"description":74,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":75,"categories":76,"keywords":78,"publishedAt":43},"01M71709JBNSMH1MJ71NRGRBFM","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.","https:\u002F\u002Fgithub.com\u002Fmistralai\u002Fmistral-vibe\u002Fblob\u002Fmain\u002Fvibe\u002Fplugins\u002Fbuiltins\u002Fvibe\u002Fskills\u002Fimport\u002FSKILL.md",[77],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[73,79,80,81,40,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96],"switch","hooks","reuse","load","when","file","code","permissions","codex","cli","opencode","instructions","commands","plugins","servers","mcp servers","configuration","setup",{"skillNo":98,"name":99,"nameZh":6,"description":100,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":101,"categories":102,"keywords":103,"publishedAt":43},"01M6C46D6AE5JDVJTFTXH9RZ5Q","worktree","Create, reuse, and clean up git worktrees under $VIBE_HOME\u002Fworktrees. Use when the user wants to work in an isolated git worktree, create a worktree for a task, or list\u002Fremove existing worktrees.","https:\u002F\u002Fgithub.com\u002Fmistralai\u002Fmistral-vibe\u002Fblob\u002Fmain\u002Fvibe\u002Fplugins\u002Fbuiltins\u002Fvibe\u002Fskills\u002Fworktree\u002FSKILL.md",[],[99,104,105,106,107,108,109,110,111,112,113,114,25,115,116,117,81,118,83,119],"git","create","work","list","git worktree","use","remove","git worktrees","user","up","home","worktrees","clean","true","Existing","task",{"skillNo":121,"name":122,"nameZh":6,"description":123,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":124,"categories":125,"keywords":127,"publishedAt":43},"01M6C467E56WQ8PXEZT1Q0VP23","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.","https:\u002F\u002Fgithub.com\u002Fmistralai\u002Fmistral-vibe\u002Fblob\u002Fmain\u002Fvibe\u002Fplugins\u002Fbuiltins\u002Fvibe\u002Fskills\u002Fskill-creator\u002FSKILL.md",[126],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[128,129,130,131,132,133,134,135,136,82,137,29,138,139,5],"skill creator","creator","flow","plan","support","discovery","directory","permission","files","frontmatter","it","add",{"skillNo":141,"name":142,"nameZh":6,"description":143,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":144,"categories":145,"keywords":147,"publishedAt":43},"01M6C464DMPVA3RTXG0RGGZB3T","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.","https:\u002F\u002Fgithub.com\u002Fmistralai\u002Fmistral-vibe\u002Fblob\u002Fmain\u002Fvibe\u002Fplugins\u002Fbuiltins\u002Fvibe\u002Fskills\u002Fcreate-plugin\u002FSKILL.md",[146],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[105,148,149,150,151,80,92,152,94,40,153,109,12,112,154,155,25,156,83,157,158,159,93],"plugin","create plugin","libraries","format","knowledge","package","scaffold","connectors","agent plugins","author","extensions","with",{"skillNo":161,"name":162,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":163,"categories":164,"keywords":165,"publishedAt":43},"01M6C4620AT7K4999KR0YY86KH","prompts","https:\u002F\u002Fgithub.com\u002Fmistralai\u002Fmistral-vibe\u002Fblob\u002Fmain\u002Fvibe\u002Fcore\u002Ftools\u002Fbuiltins\u002Fprompts\u002Fskill.md",[],[162],{"skillNo":167,"name":168,"nameZh":6,"description":169,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":170,"categories":171,"keywords":179,"publishedAt":43},"01M6C45Z8KES5ZQ0RA198EVMPA","write-vibe-tests","Write or refactor Mistral Vibe tests with proper decoupling. Use when adding behavior coverage, testing ports\u002Fadapters, replacing brittle mocks, creating fakes, adding characterization tests before refactors, or changing tests under tests\u002F for vibe\u002Fcore, vibe\u002Fapp_server, vibe\u002Facp, tools, config, sessions, skills, hooks, MCP, or setup.","https:\u002F\u002Fgithub.com\u002Fmistralai\u002Fmistral-vibe\u002Fblob\u002Fmain\u002F.vibe\u002Fskills\u002Fwrite-vibe-tests\u002FSKILL.md",[172,175,178],{"categoryNo":173,"categoryName":174,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000007","代码审查",{"categoryNo":176,"categoryName":177,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[180,181,182,5,183,184,185,186,187,188,34,189,80,190,40,109,191,96,192,12,193,25,194,83,159],"write","write vibe tests","tests","coverage","ports","server","core","behavior","acp","testing","sessions","refactor","Tools","app","decoupling"]