*vibing.txt*  Claude chat inside Neovim, driven by the Claude/Codex CLI

==============================================================================
CONTENTS                                                     *vibing-contents*

    1. Introduction ............................ |vibing-introduction|
    2. Requirements ............................ |vibing-requirements|
    3. Installation ............................ |vibing-installation|
    4. Configuration ........................... |vibing-configuration|
    5. Commands ................................ |vibing-commands|
    6. Slash Commands .......................... |vibing-slash-commands|
    7. Mappings ................................ |vibing-mappings|
    8. Chat File Format ........................ |vibing-chat-file|
    9. Backends ................................ |vibing-backends|
   10. API ..................................... |vibing-api|
   11. Troubleshooting ......................... |vibing-troubleshooting|
   12. License ................................. |vibing-license|

This help is deliberately short. The authoritative, always-current references
are kept in the repository:

    Every setup() option ...... handbook/configuration.md
    Architecture .............. .claude/rules/architecture.md

Commands, slash commands and keybindings are documented here (sections 5-7);
they carry the |:VibingChat|-style tags, so this file is their reference.

==============================================================================
1. INTRODUCTION                                          *vibing-introduction*

vibing.nvim runs a Claude conversation inside a Neovim buffer. It spawns the
`claude` CLI directly (`claude -p --output-format stream-json`) and parses its
stream; there is no wrapper daemon. A Codex CLI backend is also supported.

Features:~
  • Chat in an ordinary Markdown buffer — edit, yank and search as usual
  • Chats persist to disk with YAML frontmatter, and resume by session id
  • Per-chat model, language and tool permissions
  • Tool approval and multiple-choice questions rendered in the buffer
  • An MCP server that lets the CLI read and drive the running Neovim
  • Per-request diffs (a git tree snapshot per turn, so `sed -i` and
    formatters count too), viewable with `gd` on a file path in the chat

==============================================================================
2. REQUIREMENTS                                          *vibing-requirements*

• Neovim >= 0.10 (uses |vim.system()|)
• Node.js >= 18 (for the MCP server)
• At least one CLI backend on your PATH:
    - Claude CLI: npm install -g @anthropic-ai/claude-code
    - Codex CLI:  npm install -g @openai/codex

Optional:~
• oil.nvim — |:VibingContext| with no argument picks up the file under the
  cursor in an oil buffer
• plenary.nvim — required only to run the test suite

==============================================================================
3. INSTALLATION                                          *vibing-installation*

Using lazy.nvim: >lua
    {
      "shabaraba/vibing.nvim",
      dependencies = {
        "stevearc/oil.nvim",  -- optional
      },
      build = "./build.sh",
      config = function()
        require("vibing").setup()
      end,
    }
<
Using packer.nvim: >lua
    use {
      "shabaraba/vibing.nvim",
      run = "./build.sh",
      config = function()
        require("vibing").setup()
      end,
    }
<
`build.sh` builds the bundled MCP server. There is no plugin to install:
vibing.nvim hands the `claude` CLI its own `claude-plugin/` directory per
session with `--plugin-dir`, so the MCP server, the bundled skills and the
`nvim-navigator` subagent always come from the checkout that is running.

Anything you put in `.vibing/plugins/<name>/` (carrying a
`.claude-plugin/plugin.json`) is loaded the same way, for that project only;
run |:VibingReloadCommands| after adding one. Use |:VibingCreatePlugin| to
write a working skeleton rather than assembling one by hand.

The directory is created on the first chat in a project, holding an inactive
`_template/` copy of that skeleton. Directories whose name starts with `_` are
skipped, so the template costs no context and never reaches the `/` picker.
Renaming a plugin to `_name` is also how you park one without deleting it.

A plugin may declare `mcpServers`, so one committed to a repository you cloned
can start a process on your machine — set `agent.plugins.project_dir = false`
for repositories you do not trust.

The codex backend loads the same plugins without a `--plugin-dir`: each MCP
server is passed as a `-c mcp_servers.<name>.*` override (tools appear as
`mcp__vibing-nvim__*`) and each skill is listed for the model in
`developer_instructions`. Subagents (`agents/`) do not carry over to codex.

Upgrading from a version that installed itself into Claude Code's user scope:
`build.sh` removes that install for you. By hand: >
    /plugin uninstall vibing-nvim@vibing
    /plugin marketplace remove vibing
<
==============================================================================
4. CONFIGURATION                                        *vibing-configuration*

`require("vibing").setup()` works with no arguments. The options people
actually tend to change: >lua
    require("vibing").setup({
      adapter = "claude",               -- "claude" | "codex"
      chat = {
        window = {
          -- "current" | "right" | "left" | "top" | "bottom" | "back"
          -- | "float"
          position = "current",
          -- ratio below 1, absolute columns at 1 and above
          width = 0.4,
        },
        save_location_type = "project",  -- "project" | "user" | "custom"
      },
      agent = {
        -- "sonnet" | "opus" | "haiku" | "fable"
        default_model = "sonnet",
      },
      permissions = {
        -- "default" | "acceptEdits" | "plan" | "auto" | "dontAsk"
        -- | "bypassPermissions"
        mode = "acceptEdits",
        -- omit allow/deny to keep the shipped defaults
        deny = { "Bash" },
        -- Codex only; auto-generated with Git writes
        -- false disables loading it
        codex_profile_file = ".vibing/codex-permissions.toml",
        -- true explicitly trusts a Git-tracked profile
        codex_allow_tracked_profile = false,
      },
      -- "ja", or { default = "ja", chat = "ja" }
      language = nil,
    })
<
The permissions above are only a starting point for new chat files. What
gets enforced at runtime is the permission block in each chat file's own
frontmatter — see |vibing-chat-file|.

For the full list — window details, UI and tool markers, diff backends,
granular permission rules, MCP, Node.js executable, auto-resume on usage
limit, daily summary — read handbook/configuration.md. It is generated
against lua/vibing/config.lua and is the only complete reference; this
section is not kept in sync field by field.

==============================================================================
5. COMMANDS                                                  *vibing-commands*

                                                                *:VibingChat*
:VibingChat [{position}|{file}]
    Create a new chat. {position} is one of `current` (replace the current
    window), `right`, `left`, `top`, `bottom` (splits sized by
    `chat.window.width`/`height`) or `back` (buffer only, no window).
    Passing a path opens that saved chat file instead of creating one.

                                                          *:VibingToggleChat*
:VibingToggleChat
    Toggle the chat window without ending the conversation.

                                                            *:VibingChatFork*
:VibingChatFork [{position}]
    Branch a new chat off the current one. The fork inherits the source
    session and diverges from its first message onward.

                                                         *:VibingChatHandoff*
:VibingChatHandoff [{position}]
    Summarise the current chat (as |:VibingSummarize| does, leaving the
    `## summary` in this buffer too) and open a new chat whose first, still
    unsent User message carries that summary. Type the next instruction under
    it and send. The new chat starts a fresh session with the source's model,
    effort, permissions, working directory and language, and records where it
    came from in `continued_from`.

    When the buffer already holds a `## summary` section, it is reused and no
    summary is generated — that generation is a request that reads the whole
    conversation, which is the cost the handoff exists to shed. Run
    |:VibingSummarize| first if the existing summary is out of date; it
    overwrites the same section.

    This is the cheap way to continue a long conversation: the new chat
    starts from the fixed floor (system prompt, tools, `CLAUDE.md`) plus a
    few thousand tokens of summary, instead of re-reading the whole history
    on every request. Prefer it over `/compact` once the prompt cache has
    gone cold. Refused while a response is streaming.

                                                        *:VibingSubagentChat*
:VibingSubagentChat [{position}]
    Continue a subagent this chat started, in its own buffer. The new chat
    shares the parent's session permanently rather than forking it: a
    subagent's transcript lives under the parent session's directory, and
    forking makes the CLI lose it. Because two buffers then resume one
    session, a send is refused while the other one is streaming.

                                                    *:VibingChatJumpNextUser*
:[count]VibingChatJumpNextUser
                                                    *:VibingChatJumpPrevUser*
:[count]VibingChatJumpPrevUser
    Move the cursor to the next or previous User section of the chat buffer.
    A [count] larger than the number of sections left in that direction
    lands on the last one rather than refusing to move. When there is no
    section left at all, the command reports it and the cursor stays put.
    The starting position is pushed onto the jumplist, so `<C-o>` comes
    back.

    Neither ships with a mapping. To bind them as `]`/`[` motions, forward
    the count explicitly — the command cannot read `v:count` itself, since
    on the command line that variable still holds the count of the last
    Normal-mode command: >lua
        vim.keymap.set("n", "]u", function()
          vim.cmd(vim.v.count1 .. "VibingChatJumpNextUser")
        end, { desc = "Next User section" })
<
                                                       *:VibingSlashCommands*
:VibingSlashCommands
    Open the slash-command picker for the current chat.

                                                        *:VibingSetFileTitle*
:VibingSetFileTitle
    Generate a title from the conversation and rename the chat file. When the
    buffer already holds a `## summary` section written by |:VibingSummarize|,
    that summary is used as the input instead of a conversation excerpt, which
    keeps long chats from being titled after their last step. Refused while a
    response is streaming.

                                                           *:VibingSummarize*
:VibingSummarize [--with-title]
    Summarise the conversation so far and insert it into the buffer. Refused
    while a response is streaming.

    With `--with-title`, run |:VibingSetFileTitle| once the summary is in the
    buffer, so the title is generated from the summary rather than from a
    conversation excerpt. The title step is skipped when summarising failed:
    it would otherwise fall back to the excerpt and spend a second request
    the user did not ask for. Run |:VibingSetFileTitle| by hand for that.

                                                         *:VibingDeleteChats*
:VibingDeleteChats [--unrenamed]
    Pick chat files to delete. With `--unrenamed`, delete every chat that
    still has its generated filename.

                                                             *:VibingContext*
:[range]VibingContext [{path}]
    Add a file to the context. With no argument, uses the current buffer — or
    the file under the cursor in an oil.nvim buffer. Accepts a range to add
    only the selected lines.

                                                        *:VibingClearContext*
:VibingClearContext
    Clear every context entry.

                                                              *:VibingCancel*
:VibingCancel
    Cancel the request in flight.

                                                    *:VibingOrchestrationTree*
:VibingOrchestrationTree [{path}]
    Draw the orchestration tree this chat belongs to, read from the
    `orchestrated` / `orchestrated_by` frontmatter, with each chat's status
    (`responding`, `idle`, `waiting_approval`, `asked_question`, `error`, or
    `not open`). Always drawn from the root of the tree, with `←` marking
    the chat it was run from. With no argument, uses the current chat.

                                                           *:VibingCancelTree*
:VibingCancelTree [{path}]
    Cancel the running turn of this chat and of every chat below it in the
    tree — a worker created with no window cannot be reached with
    |:VibingCancel| otherwise. Chats above it are left alone. Queued
    deliveries for the subtree are dropped first, so cancelling does not
    immediately restart a chat with a message waiting for it.

                                                             *:VibingSchedule*
:VibingSchedule [{when}]
    Schedule this chat's unsent message to send later instead of now.
    {when} accepts relative offsets (`90s`, `30m`, `2h`, `1h30m`), a clock
    time (`18:30`, rolled to tomorrow if already past today) or an
    absolute timestamp (`2026-08-14T07:05` or `2026-08-14 07:05`). With no
    argument, uses the project's recorded usage limit reset time, and
    errors if none is on record.

                                                              *:VibingCompact*
:VibingCompact [{focus}]
    Run one `/compact` turn on this chat now, replacing the conversation
    with a summary so later turns re-read less. {focus} is passed through
    as `/compact {focus}` and says what the summary should keep, e.g.
    `:VibingCompact the open tasks and the files changed so far`.

    Claude backend only — `/compact` is the CLI's own command. Refuses
    while an unsent message is waiting, since this command means exactly
    one turn. `agent.token_usage.auto_compact` in |vibing-configuration|
    also supports Codex by configuring its native automatic compaction;
    that does not make this manual command available on Codex.

                                                       *:VibingPendingResumes*
:VibingPendingResumes
    List chats parked until a usage limit resets or a scheduled send fires.

                                                        *:VibingCancelResume*
:VibingCancelResume [all]
    Cancel this chat's pending auto-resume or scheduled send, or every one
    with `all`. Also clears the project's recorded usage limit for this
    chat's backend (`all` clears it whichever backend it belongs to), so
    a follow-up `<CR>` actually sends instead of being parked again.

Scheduled requests:~
    A rejected turn, a `<CR>` sent while the project's recorded usage
    limit is still active, and an explicit |:VibingSchedule| all park the
    same way: the message stays in the chat's own unsent `## User`
    section rather than being copied elsewhere, so it is still visible
    and editable while parked, and deleting it cancels the send.

                                                      *:VibingReloadCommands*
:VibingReloadCommands
    Reload custom slash commands and completion candidates.

                                                        *:VibingCreatePlugin*
:VibingCreatePlugin [{name}]
    Create a project-local Claude Code plugin in `.vibing/plugins/{name}/`
    and open its example skill. Prompts for the name when given none. The
    plugin applies to this project only and takes effect immediately.

                                                *:VibingCopyUnsentUserHeader*
:VibingCopyUnsentUserHeader
    Copy `## User <!-- unsent -->` to the clipboard.

                                                    *:VibingClearAnnotations*
:VibingClearAnnotations
    Remove the inline review notes the agent left with `nvim_annotate` from
    every buffer. The notes are virtual text only — no file is modified, and
    unloading a buffer clears its own notes anyway.

                                                        *:VibingDebugAnalyze*
:VibingDebugAnalyze
                                                           *:VibingDebugHelp*
:VibingDebugHelp
    Ask the agent about the stopped debug session — `Analyze` for what went
    wrong, `Help` for what to check next. Both send only the request; the
    agent fetches the stack, scopes and variables itself through the MCP
    debugger tools. Requires nvim-dap and a stopped session.

                                                        *:VibingDailySummary*
:VibingDailySummary [{YYYY-MM-DD}]
    Summarise this project's chat files for a day (default: today).

                                                     *:VibingDailySummaryAll*
:VibingDailySummaryAll [{YYYY-MM-DD}]
    Same, across every chat file rather than just this project's.

==============================================================================
6. SLASH COMMANDS                                      *vibing-slash-commands*

Type these on their own line in a chat buffer and send with `<CR>`:

/context <file>         Add a file to the context
/clear                  Clear the context
/save                   Save the current chat
/summarize              Summarise the conversation
/model <model>          Set the model for the current backend
                        (e.g. sonnet or gpt-5.6-terra)
/effort <level>         Set the reasoning effort (low|medium|high|xhigh|max)
/help                   List the available slash commands
/permissions, /perm     Interactive permission builder
/allow [tool]           Add to the allow list, or show it
/deny [tool]            Add to the deny list, or show it
/ask [tool]             Require approval for a tool, or show the list
/permission [mode]      Set the permission mode
/new-session            Forget the session and start fresh

`/allow`, `/deny` and `/ask` accept granular patterns such as `Bash(git:*)`,
`Read(src/**/*.ts)` and `WebFetch(github.com)`; prefix a tool with `-` to
remove it (e.g. `/allow -Bash`).

Worktree workflows (list, create, attach, run, finish) are not slash commands.
Ask for them in plain language — they are backed by the bundled
`vibing-worktree-*` Claude Code skills.

==============================================================================
7. MAPPINGS                                                  *vibing-mappings*

In a chat buffer:~

    <CR>        Send the message (normal mode)
    <C-c>       Cancel the current request
    <C-a>       Add a file to the context
    gd          Show the diff for the file path under the cursor
    gf          Open the file path under the cursor
    gx          Open the URL on the current line in a browser
    q           Close the chat window

`<CR>` asks before sending in two cases, and sends normally otherwise. While
the project has a usage limit on record it offers to park the message until
the reset (see |:VibingSchedule|). And when the chat has been idle past
`agent.token_usage.cache_ttl_sec` (default 55 minutes) *and* its last turn
reported a context at or above `warn_context`, the prompt cache has expired,
so sending rewrites the whole conversation at cache-creation price: it offers
to send anyway, to move the message into a new chat, or to cancel. Set
`cache_ttl_sec = 0` to turn that off. Only a `<CR>` you typed is affected —
scheduled sends, auto-resume and chat-to-chat delivery never prompt.

Everything except `q` is configurable: >lua
    require("vibing").setup({
      keymaps = {
        send = "<CR>",
        cancel = "<C-c>",
        add_context = "<C-a>",
        open_diff = "gd",
        open_file = "gf",
        open_url = "gx",
      },
    })
<
==============================================================================
8. CHAT FILE FORMAT                                         *vibing-chat-file*

Chats are Markdown files (by default `.vibing/chat/chat-<timestamp>-....md`)
with YAML frontmatter: >
    ---
    vibing.nvim: true
    session_id: <cli-session-id>
    created_at: 2026-01-01T12:00:00
    orchestrated:                            # optional: chats this drives
      - .vibing/chat/worker-docs.md
      - path: .vibing/chat/worker-auth.md
        task: "PR #688 - review, merge, cleanup"
    orchestrated_by:                         # optional: who drives this
      - .vibing/chat/orchestrator.md
    continued_from: .vibing/chat/long.md     # optional: handed off from
    working_dir: .vibing/worktrees/fix-auth   # optional, relative to git root
    agent: claude
    mode: code                                # code | plan | explore
    model: sonnet
    env:                                      # optional: see below
      - BASH_MAX_OUTPUT_LENGTH=10000
    permission_mode: acceptEdits
    permissions_allow:
      - Read
      - Edit
    permissions_deny:
      - Bash
    permissions_ask:
      - Bash
    delegated_scope:                          # optional: see below
      - Bash(npm:*)
    language: ja                              # optional
    ---
<
Note the singular `permission_mode`; the permissions lists are plural. Reopen
a chat with |:VibingChat| {file} or plain |:edit| — the session resumes from
`session_id`, and `working_dir` decides where the CLI runs.

`orchestrated` and `orchestrated_by` record which chat drove which. They are
written when one chat creates or messages another through the vibing-nvim MCP
tools, as git-root-relative paths, and are rewritten on both sides when
|:VibingSetFileTitle| renames a chat file. `continued_from` names the chat a
|:VibingChatHandoff| was made from and is kept in step the same way. Buffer
numbers do not survive a restart and file names do not survive a rename, so
the frontmatter is the only place the relationship keeps meaning.

An `orchestrated` entry is a plain path, or a `path:` / `task:` pair when the
orchestrator gave that chat a one-line assignment via `nvim_chat_create`'s or
`nvim_chat_send_message`'s `task` argument (e.g. `PR #688 — review, merge,
cleanup`). The task is recorded only on the orchestrator's own `orchestrated`
entry, never on the driven chat's file, so `nvim_chat_list` can report every
worker's assignment by reading just the orchestrator's frontmatter — no
transcript to re-read after a restart or a context compaction. Sending that
chat a later message with a new `task` replaces the entry; an ordinary
message with no `task` leaves it as is. `orchestrated_by` never carries a
task, since the assignment belongs to the chat that gave it, not the one that
received it.

A task is quoted only when it has to be — text containing ` #`, `: `, or a
leading `-` would otherwise change meaning as plain YAML.

Chats written before this form used `<path>|<task>` on one line. They are
still read, and are rewritten into the two-line form the next time the entry
is touched; nothing writes the pipe form any more.

`delegated_scope` lists tool/command patterns (same syntax as
`permissions_allow`, e.g. `Bash(npm:*)`) that an orchestrator may auto-approve
on this chat's behalf via `nvim_chat_answer_approval`, when
`agent.orchestration.delegated_approval = "scoped"`. It is declared with
`nvim_chat_create`'s `delegated_scope` argument and written on the chat's own
frontmatter (not the orchestrator's). A denial can always be delegated
regardless of scope, since denying grants nothing; only `allow_once` /
`allow_for_session` answers are checked against it. See the
vibing-orchestrate skill for how to declare it.

`env` lists `KEY=VALUE` environment variables for this chat's CLI process,
overriding `agent.env` from |vibing.setup()|. It is for the cost knobs Claude
Code exposes only through the environment — `BASH_MAX_OUTPUT_LENGTH`,
`CLAUDE_AUTOCOMPACT_PCT_OVERRIDE`, `CLAUDE_CODE_SUBAGENT_MODEL` — so one chat
can be tuned without touching the rest. Claude backend only. `CLAUDECODE` and
any `VIBING_*` name is ignored with a warning: those bind the CLI to this
Neovim and to this chat. An entry that is not `KEY=VALUE` is ignored the same
way. The table of what is worth setting is in `handbook/configuration.md` →
"Claude CLI Environment Variables".

==============================================================================
9. BACKENDS                                                  *vibing-backends*

Set globally with `adapter`, or per chat with the `agent` frontmatter field.

claude                                                        *vibing-claude*
    Default. Runs `claude -p --output-format stream-json --verbose
    --include-partial-messages`, plus flags for the model, session resume,
    system prompt, setting sources and permissions.
    Requires: the `claude` CLI.

codex                                                          *vibing-codex*
    Runs the Codex CLI (`codex exec --json`).
    Requires: the `codex` CLI.

Both implement the same adapter interface (`execute`, `stream`, `cancel`,
`supports`), so chat behaviour is the same either way; only the flags and the
stream format differ.

==============================================================================
10. API                                                           *vibing-api*

require("vibing").setup({opts})                              *vibing.setup()*
    Initialise the plugin, register the commands and validate the config.
    {opts} is optional.

require("vibing").get_adapter()                        *vibing.get_adapter()*
    The adapter instance currently selected by `config.adapter`, or nil
    before setup().

require("vibing").get_config()                          *vibing.get_config()*
    The merged, validated configuration table.

                                        *vibing.generate_and_insert_summary()*
require("vibing.application.chat.use_case")
        .generate_and_insert_summary({chat_buffer}, {opts})
    The body of |:VibingSummarize|. {opts.on_done} is a
    `fun(ok: boolean, err: string?)` called exactly once, whether the summary
    succeeded or not — the synchronous refusals (streaming, empty
    conversation, no adapter) included, so a caller chaining on it never has
    to tell "not yet" from "never". An error raised inside it is caught and
    reported rather than propagated into the CLI's completion handler. See
    `doc/api-reference.md` for the full contract.

Everything under `lua/vibing/` beyond these four entry points is internal and
may change without notice.

==============================================================================
11. TROUBLESHOOTING                                   *vibing-troubleshooting*

Nothing comes back when you send a message:~
    1. Check the backend runs at all: >
        $ claude --version
<    2. Check it is authenticated (run `claude` once in a terminal).
    3. Read the errors: >
        :messages
<
Every tool call is denied:~
    The PreToolUse hook fails closed on purpose: if it cannot reach Neovim's
    RPC server, the tool is denied rather than silently allowed. Check that
    `mcp.enabled` is true and that `.vibing/hook-settings.json` exists in the
    directory the CLI runs in.

MCP tools cannot find the right Neovim:~
    Several Neovim instances each get their own RPC port. Pass the port shown
    in the chat's system prompt on every `mcp__vibing-nvim__*` call rather
    than guessing.

A chat resumes into the wrong directory:~
    The `working_dir` field in the chat's frontmatter wins over Neovim's
    current directory. Edit or remove it.

Seeing what the CLI actually sent:~
    Set |g:vibing_debug_stream| to log the spawned command, its PID and the
    stream events it produces via |vim.notify()|: >lua
    vim.g.vibing_debug_stream = true
<
Reporting a bug:~
    https://github.com/shabaraba/vibing.nvim/issues

    Please include the Neovim version (|:version|), your `setup()` call, the
    output of |:messages|, and the steps to reproduce.

==============================================================================
12. LICENSE                                                   *vibing-license*

MIT License

Copyright (c) 2025 shabaraba

==============================================================================
vim:tw=78:ts=8:ft=help:norl:
