An AI coding assistant-oriented popup terminal for Neovim. Toggle, manage, and interact with multiple terminal sessions without leaving your editor. Designed for CLI AI agents (opencode, Cursor, Claude, Aider, and more) and any terminal application.
YAPT gives you a quake-style popup terminal workflow: summon a terminal on demand, switch between sessions, send file contents, copy output — all with keyboard shortcuts. Built for developers who run AI coding assistants alongside their editor and want seamless switching between code and CLI.
- Toggle a terminal in split (
<A-\`>) or fullscreen (<A-=>) mode - Manage multiple terminal sessions simultaneously — switch, rename, create
- Fuzzy finder with live preview of terminal output (Telescope integration)
- Prompt history — create markdown prompt files, browse with Telescope, send to terminal
- Preset prompts — reusable
.mdfiles from Neovim config and the project, picked in Telescope - Passthrough mode — send keys directly to TUI apps running inside the terminal
- Copy
@fileand@file:start-endlinks for use in AI prompts - Keyboard-driven — operate terminals without leaving the home row
- Full terminal mode keybindings for managing sessions from within a terminal
- Persistent sessions — hide/show without restarting the CLI process
- Multi-command picker — configure several CLI commands and pick one on launch
- Pure Lua, built for Neovim >= 0.11.0
- Neovim >= 0.11.0
- Any CLI application (opencode, cursor CLI, claude, aider, etc.)
- Telescope (optional, for fuzzy picker, prompt history, and preset prompts)
Using lazy.nvim
{
"coordinatio/yapt.nvim",
config = function()
require("yapt").setup()
end,
}Using packer.nvim
use {
"coordinatio/yapt.nvim",
config = function()
require("yapt").setup()
end,
}Using vim-plug
Plug 'coordinatio/yapt.nvim'
lua << EOF
require("yapt").setup()
EOF- Open/Toggle Terminal: Press
<A-\`>in normal mode- First time: Creates a terminal with your default command
- After that: Toggles (show/hide) the last active session
- Fullscreen Toggle: Press
<A-=>in normal mode- Shows terminal in the current window (no split — ideal for small screens)
- Press again to switch back to your file
- Create New Terminal: Press
<leader>anto create an additional session (sends the current file if it has content) - Create New Fullscreen Terminal: Press
<leader>aNto create in fullscreen mode - Send File to Fullscreen: Press
<leader>aEto send current file contents to the terminal and force fullscreen - Switch Terminals: Press
<F6>to open a fuzzy picker with live preview - Rename Terminal: Press
<leader>arto rename the current terminal
Work with multiple terminal sessions for different tasks:
| Keybinding | Action |
|---|---|
<A-\`> |
Smart toggle — create first terminal or show last active (split) |
<A-=> |
Fullscreen toggle — show terminal fullscreen or switch back to file |
<leader>an |
Create new terminal; send current file if non-empty |
<leader>aN |
Create new terminal fullscreen; send current file if non-empty |
<F6> |
Select terminal from fuzzy picker (with live preview) |
<leader>ar |
Rename current terminal |
<leader>ah |
Create new prompt file in .nvim-yapt/history/ (timestamp in filename) |
<leader>ae |
Send current file contents to terminal |
<leader>aE |
Send current file contents to terminal (force fullscreen) |
<leader>aH |
Open prompt history directory in Telescope |
<leader>al |
Open or switch to last prompt file from history |
<leader>ap |
Open preset prompts (Telescope, or vim.ui.select) |
<leader>ac |
Copy @file link to clipboard (paste into CLI prompt) |
<leader>i |
Send next key directly to TUI app in terminal |
| Keybinding | Action |
|---|---|
<leader>ac |
Copy @file:start-end link to clipboard (paste into CLI prompt) |
When inside a terminal, manage sessions without leaving:
| Keybinding | Action |
|---|---|
<A-\`> |
Exit terminal mode / hide terminal window |
<A-=> |
Toggle fullscreen mode / exit terminal |
<F7> |
Create new terminal |
<F6> |
Select terminal from fuzzy picker |
<F2> |
Rename current terminal |
<F12> |
Open or switch to last prompt file from history |
<leader>i |
Send next key directly to TUI app in terminal |
When a TUI application (e.g. a text editor, file manager, or pager) runs inside the terminal, Neovim's normal-mode keybindings intercept keys. The passthrough key lets you send keys directly to the TUI app.
Single-key passthrough (<leader>i by default):
- Navigate to the terminal window in normal mode
- Press
<leader>i, then press any key — it is sent directly to the TUI - You return to normal mode immediately
Create a markdown file for your CLI task and send it in one go:
- Create prompt file:
:PTPromptor<leader>ah- Creates
${CWD}/.nvim-yapt/history/if needed - Opens a new file named like
2025-02-04_14-30-45.md(date and time to the second)
- Creates
- Write your prompt in the opened buffer (what you want the CLI to do).
- Send to terminal:
:PTSend/<leader>ae(reuse last session), or:PTNew/<leader>an(always a new session)- Saves the buffer if modified
- Shows/creates the terminal and sends the current buffer contents directly
- If current buffer is a prompt file created by
<leader>ah, it is closed after successful send - If current buffer is an unnamed/new file (e.g. a fresh
:enewbuffer) with content, a prompt-history file is created automatically (timestamped, in.nvim-yapt/history/), the content is saved into it, and it is then treated like a prompt file — closed after a successful send. An empty buffer (named or unnamed) aborts with a warning for:PTSend/<leader>ae;:PTNew/<leader>anstill creates a terminal without sending. - If no file buffers remain, opens an empty buffer
- Fullscreen variants:
:PTSendFullscreen/<leader>aE,:PTNewFullscreen/<leader>aN
Prompt-file buffers are automatically saved to disk when you leave them (switch buffer, close window, open the picker, etc.), so they appear in :PTHistory and :PTLast without a manual :write. Disable with history.autosave = false.
Additional history actions:
- Browse history in Telescope:
:PTHistoryor<leader>aH - Open last prompt:
:PTLastor<leader>al
Keep reusable prompts as markdown files and clone them into history when you need them (the library file is never overwritten).
Where to put files:
| Library | Default path |
|---|---|
| Global | ~/.config/nvim/yapt/prompts/ (stdpath("config")/yapt/prompts) |
| Project | ${CWD}/.nvim-yapt/prompts/ |
Nested folders work: review/pr.md shows as review/pr.md. Project entries are listed first and tagged project or global. Same name in both libraries appears twice.
If you gitignore .nvim-yapt/* (recommended for draft history), allow the project library so teammates can share prompts:
.nvim-yapt/*
!.nvim-yapt/prompts/
!.nvim-yapt/prompts/**
Or set prompts.project_dir to a committed path.
Pick a preset: :PTPresets or <leader>ap
| Key | Action |
|---|---|
| Enter | Clone into a new prompt-history file and open that copy (the library file is never opened). From a non-protected view (ordinary file, Reader, help, oil, …) this replaces the current buffer. From a protected window it opens a split: float, terminal, cmdwin, quickfix, prompt, preview, or any window with winfixwidth/winfixheight/winfixbuf (a pinned file, not only a sidebar). Edit, then <leader>ae to send |
<C-x> / <C-v> / <C-t> |
Same clone, opened in a horizontal split / vertical split / new tab |
<C-s> |
Send immediately to the terminal (shown / force-insert) and keep a history copy. The source buffer is not closed or replaced. |
<C-y> |
Insert the preset at the cursor in the current file buffer |
<C-y> is used instead of <C-i> because <C-i> is Tab in Neovim and would steal Tab in the picker. Without Telescope, vim.ui.select is used with Enter-only (clone then open the copy the same way Enter does).
If both directories are empty, YAPT notifies the paths to add files to (it does not create them).
| Command | Action |
|---|---|
:PTT |
Toggle terminal (split mode) |
:PTFullscreen |
Toggle terminal (fullscreen mode) |
:PTNew [name] |
Create new terminal (optional name); send current file if non-empty |
:PTNewFullscreen [name] |
Create new terminal fullscreen (optional name); send current file if non-empty |
:PTSelect |
Open terminal picker |
:PTRename [name] |
Rename active terminal (interactive if no argument) |
:PTList |
List all terminals with status |
| Command | Action |
|---|---|
:PTPrompt |
Create new prompt file in .nvim-yapt/history/ |
:PTSend |
Send current file contents to active terminal |
:PTSendFullscreen |
Send current file contents to active terminal (force fullscreen) |
:PTHistory |
Open prompt history directory in Telescope |
:PTLast |
Open or switch to last prompt file from history |
:PTPresets |
Open preset prompts (Telescope, or vim.ui.select) |
| Command | Action |
|---|---|
:PTCopyLink [range] |
Copy @file:start-end link to clipboard; use range (e.g. :10,20PTCopyLink) or current line |
:PTSay <text> |
Send arbitrary text to active terminal |
:PTVersion |
Display plugin version |
To close a terminal, type exit or press Ctrl+D.
require("yapt").setup({
keybindings = {
toggle = "<A-`>", -- Toggle terminal in split
toggle_fullscreen = "<A-=>", -- Toggle terminal fullscreen
new = "<leader>an", -- Create new terminal; send file if non-empty
new_fullscreen = "<leader>aN", -- Create new terminal fullscreen; send file if non-empty
select = "<F6>", -- Select terminal (fuzzy picker)
rename = "<leader>ar", -- Rename current terminal
prompt_new = "<leader>ah", -- Create new prompt file
prompt_send = "<leader>ae", -- Send current file to terminal
prompt_send_fullscreen = "<leader>aE", -- Send current file to terminal (force fullscreen)
prompt_history_telescope = "<leader>aH", -- Open prompt history in Telescope
prompt_last = "<leader>al", -- Open or switch to last prompt buffer
prompt_presets = "<leader>ap", -- Open preset prompts (Telescope, or vim.ui.select)
copy_link = "<leader>ac", -- Copy @file or @file:start-end link
},
history = {
dir = ".nvim-yapt/history", -- Relative to CWD
autosave = true, -- Save prompt-file buffers to disk when left
},
prompts = {
-- nil → stdpath("config")/yapt/prompts (typically ~/.config/nvim/yapt/prompts)
-- Empty string disables that source.
dir = nil,
project_dir = ".nvim-yapt/prompts", -- Relative to CWD
},
terminal = {
default_name = "Term", -- Name prefix
auto_number = true, -- Auto-append numbers (Term 1, Term 2, etc.)
-- Strip trailing spaces from cell-grid padding on yank (also removes intentional ones)
trim_yank_trailing_whitespace = true,
},
split = {
position = "right", -- "right", "left", "top", "bottom"
size = 0.5, -- 50% of editor width/height
},
-- CLI command to run (string or table)
-- When a table is provided, a Telescope picker appears when creating
-- a new terminal, letting you choose which command to launch.
-- Supports plain strings, { "Label", "command" }, or { label=.., command=.. }.
command = "opencode",
-- command = { "opencode", "cursor agent", "claude", "aider" },
-- command = {
-- { label = "OpenCode", command = "opencode" },
-- { label = "Cursor Agent", command = "cursor agent" },
-- },
term_opts = {
on_open = nil,
on_close = nil,
},
terminal_keybindings = {
hide = "<A-`>",
toggle_fullscreen = "<A-=>",
new = "<F7>",
rename = "<F2>",
select = "<F6>",
prompt_last = "<F12>",
passthrough = "<leader>i",
},
})require("yapt").setup({
keybindings = {
toggle = "<C-a>",
new = "<C-n>",
select = "<C-s>",
rename = "<leader>rn",
},
})require("yapt").setup({
command = { "opencode", "cursor agent", "claude", "aider" },
})When multiple commands are configured, a Telescope picker appears every time a new terminal is created.
For a clearer picker experience, use labeled commands. The label is shown in the Telescope picker while the command string is what actually runs:
require("yapt").setup({
command = {
{ label = "OpenCode", command = "opencode" },
{ label = "Cursor Agent", command = "cursor agent" },
{ label = "Claude Code", command = "claude" },
{ label = "Aider", command = "aider" },
},
})You can also use the short form { "Label", "command" }:
command = {
{ "OpenCode", "opencode" },
{ "Claude Code", "claude" },
}require("yapt").setup({
split = {
position = "left",
size = 0.4,
},
})require("yapt").setup({
terminal = {
default_name = "Session",
auto_number = true, -- "Session 1", "Session 2", etc.
},
})require("yapt").setup({
keybindings = {
copy_link = "", -- disable copy link
},
})local yapt = require("yapt")
-- Plugin version
print("Version: " .. yapt.version)
-- Toggle terminal
yapt.normal_mode_handler()
-- Create new terminal (sends current file if non-empty)
yapt.new_terminal_handler()
-- Send text to active terminal
yapt.terminal.send_text("@myfile.lua\nExplain this code")
-- Check if terminal is running
local terminal_id = yapt.tabs.get_active()
if yapt.terminal.is_running(terminal_id) then
print("Terminal is running")
end
-- List all terminals
local terminals = yapt.tabs.list_terminals()
for _, term in ipairs(terminals) do
print(string.format("%s: %s", term.id, term.name))
end
-- Get terminal state (for debugging)
local state = yapt.tabs.get_state()
print(vim.inspect(state))local tabs = require("yapt.tabs")
local active_id = tabs.get_active()
local term = tabs.get_terminal(active_id)
print("Name: " .. term.name)
print("Created: " .. term.created_at)
tabs.rename_terminal(active_id, "New Name")
tabs.delete_terminal(active_id)
if tabs.has_terminals() then
print("Terminals count: " .. tabs.count())
endUse descriptive names to organize by task:
- "Backend API" — for backend code questions
- "Frontend UI" — for UI/UX implementation
- "Debug Session" — for troubleshooting
- "Code Review" — for reviewing pull requests
- "Documentation" — for writing docs
- Keep sessions focused: Create separate terminals for different contexts
- Use terminal mode shortcuts: Stay in terminal mode with
<F7>,<F6>,<F2>for faster navigation - Leverage the preview: Use
<F6>to preview conversations before switching - Name early: Rename terminals as soon as you know their purpose with
<F2>
For the best experience, install telescope.nvim. With Telescope you get:
- Terminal picker (
<F6>): live preview, fuzzy search by name, rename with<C-r> - Prompt history (
<leader>aH): browse.nvim-yapt/history/with wrap preview - Preset prompts (
<leader>ap): global + project libraries, wrap preview,<C-s>send,<C-y>insert
Without Telescope, terminal and command pickers fall back to vim.ui.select. The history command shows a warning if Telescope is not available. Preset prompts fall back to vim.ui.select (Enter clones into history; extra actions require Telescope).
- Ensure the CLI application is installed and in your PATH
- Try running the command manually in your shell to verify it works
- Check for errors with
:messages
- Make sure
<leader>is set in your config (e.g.,vim.g.mapleader = " ") - Check for conflicting keybindings with
:verbose map <A-\`>
- Ensure you're pressing the toggle key while still in visual mode
- The selection is sent after the terminal opens/shows
- Shown when running
:PTSend/<leader>ae(or fullscreen variants) on an empty buffer — named or unnamed. Type something first. - Unnamed buffers with content no longer need to be saved manually — a history file is created for them automatically.
- opencode — AI coding CLI tool
- toggleterm.nvim — Terminal management for Neovim
- vim-floaterm — Floating terminal plugin
This plugin started as a fork of felixcuello/neovim-cursor, a simple integration of the Cursor AI agent CLI into Neovim. Since then, yapt.nvim has grown into a standalone, AI-coding-assistant-oriented terminal workflow solution with multi-session management, prompt history, passthrough mode, fullscreen support, and more — significantly diverging from its origins.
GPLv3