Your Neovim AI sidekick
2,755
stars
411
commits
Lua
primary language
Sep 8, 2026
updated
sidekick.nvimsidekick.nvim is your Neovim AI sidekick that integrates Copilot LSP's "Next Edit Suggestions" with a built-in terminal for any AI CLI. Review and apply diffs, chat with AI assistants, and streamline your coding, without leaving your editor.
π€ Next Edit Suggestions (NES) powered by Copilot LSP
π¬ Integrated AI CLI Terminal
tmux and zellij integration.π Extensible and Customizable
>= 0.11.2 or newervim.lsp.enable. Can be installed in multiple ways:
npm or your OS's package managerlsp/copilot.lua configuration.
main branch) for {function} and {class} context variables (optional)vim.lsp.enable:checkhealth sidekick:LspCopilotSignIn<Tab> to navigate through or apply suggestions<leader>aa to open AI CLI tools[!NOTE] New to Next Edit Suggestions? Unlike inline completions, NES suggests entire refactorings or multi-line changes anywhere in your file - think of it as Copilot's "big picture" suggestions.
Install with your favorite manager. With lazy.nvim:
{
"folke/sidekick.nvim",
opts = {
-- add any options here
cli = {
mux = {
backend = "zellij",
enabled = true,
},
},
},
keys = {
{
"<tab>",
function()
-- if there is a next edit, jump to it, otherwise apply it if any
if not require("sidekick").nes_jump_or_apply() then
return "<Tab>" -- fallback to normal tab
end
end,
expr = true,
desc = "Goto/Apply Next Edit Suggestion",
},
{
"<c-.>",
function() require("sidekick.cli").focus() end,
desc = "Sidekick Focus",
mode = { "n", "t", "i", "x" },
},
{
"<leader>aa",
function() require("sidekick.cli").toggle() end,
desc = "Sidekick Toggle CLI",
},
{
"<leader>as",
function() require("sidekick.cli").select() end,
-- Or to select only installed tools:
-- require("sidekick.cli").select({ filter = { installed = true } })
desc = "Select CLI",
},
{
"<leader>ad",
function() require("sidekick.cli").close() end,
desc = "Detach a CLI Session",
},
{
"<leader>at",
function() require("sidekick.cli").send({ msg = "{this}" }) end,
mode = { "x", "n" },
desc = "Send This",
},
{
"<leader>af",
function() require("sidekick.cli").send({ msg = "{file}" }) end,
desc = "Send File",
},
{
"<leader>av",
function() require("sidekick.cli").send({ msg = "{selection}" }) end,
mode = { "x" },
desc = "Send Visual Selection",
},
{
"<leader>ap",
function() require("sidekick.cli").prompt() end,
mode = { "n", "x" },
desc = "Sidekick Select Prompt",
},
-- Example of a keybinding to open Claude directly
{
"<leader>ac",
function() require("sidekick.cli").toggle({ name = "claude", focus = true }) end,
desc = "Sidekick Toggle Claude",
},
},
}
[!TIP] It's a good idea to run
:checkhealth sidekickafter install.
<Tab> in insert mode with blink.cmp{
"saghen/blink.cmp",
---@module 'blink.cmp'
---@type blink.cmp.Config
opts = {
keymap = {
["<Tab>"] = {
"snippet_forward",
function() -- sidekick next edit suggestion
return require("sidekick").nes_jump_or_apply()
end,
function() -- if you are using Neovim's native inline completions
return vim.lsp.inline_completion.get()
end,
"fallback",
},
},
},
}
<Tab> integration for insert mode{
"folke/sidekick.nvim",
opts = {
-- add any options here
},
keys = {
{
"<tab>",
function()
-- if there is a next edit, jump to it, otherwise apply it if any
if require("sidekick").nes_jump_or_apply() then
return -- jumped or applied
end
-- if you are using Neovim's native inline completions
if vim.lsp.inline_completion.get() then
return
end
-- any other things (like snippets) you want to do on <tab> go here.
-- fall back to normal tab
return "<tab>"
end,
mode = { "i", "n" },
expr = true,
desc = "Goto/Apply Next Edit Suggestion",
},
},
}
After installation sign in with :LspCopilotSignIn if prompted.
The module ships with safe defaults and exposes everything through
require("sidekick").setup({ ... }).
---@class sidekick.Config
local defaults = {
nes = {
---@type boolean|fun(buf:integer):boolean?
enabled = function(buf)
return vim.g.sidekick_nes ~= false and vim.b.sidekick_nes ~= false
end,
debounce = 100,
trigger = {
-- events that trigger sidekick next edit suggestions
events = { "ModeChanged i:n", "TextChanged", "User SidekickNesDone" },
},
clear = {
-- events that clear the current next edit suggestion
events = { "TextChangedI", "InsertEnter" },
esc = true, -- clear next edit suggestions when pressing <Esc>
},
---@class sidekick.diff.Opts
---@field inline? "words"|"chars"|false Enable inline diffs
---@field show? "always"|"cursor" `cursor` will only show the diff when the cursor is at the edit position.
diff = {
inline = "words",
show = "always",
},
signs = true, -- show signs for next edit suggestions
jumplist = true, -- add an entry to the jumplist
},
-- Work with AI cli tools directly from within Neovim
cli = {
watch = true, -- notify Neovim of file changes done by AI CLI tools
---@class sidekick.win.Opts
win = {
--- This is run when a new terminal is created, before starting it.
--- Here you can change window options `terminal.opts`.
---@param terminal sidekick.cli.Terminal
config = function(terminal) end,
wo = {}, ---@type vim.wo
bo = {}, ---@type vim.bo
layout = "right", ---@type "float"|"left"|"bottom"|"top"|"right"
--- Options used when layout is "float"
---@type vim.api.keyset.win_config
float = {
width = 0.9,
height = 0.9,
},
-- Options used when layout is "left"|"bottom"|"top"|"right"
---@type vim.api.keyset.win_config
split = {
width = 80, -- set to 0 for default split width
height = 20, -- set to 0 for default split height
},
--- CLI Tool Keymaps (default mode is `t`)
---@type table<string, sidekick.cli.Keymap|false>
keys = {
buffers = { "<c-b>", "buffers" , mode = "nt", desc = "open buffer picker" },
files = { "<c-f>", "files" , mode = "nt", desc = "open file picker" },
hide_n = { "q" , "hide" , mode = "n" , desc = "hide the terminal window" },
hide_ctrl_q = { "<c-q>", "hide" , mode = "n" , desc = "hide the terminal window" },
hide_ctrl_dot = { "<c-.>", "hide" , mode = "nt", desc = "hide the terminal window" },
hide_ctrl_z = { "<c-z>", "blur" , mode = "nt", desc = "go back to the previous window without hiding the terminal" },
prompt = { "<c-p>", "prompt" , mode = "t" , desc = "insert prompt or context" },
stopinsert = { "<c-q>", "stopinsert", mode = "t" , desc = "enter normal mode" },
-- Navigate windows in terminal mode. Only active when:
-- * layout is not "float"
-- * there is another window in the direction
-- With the default layout of "right", only `<c-h>` will be mapped
nav_left = { "<c-h>", "nav_left" , expr = true, desc = "navigate to the left window" },
nav_down = { "<c-j>", "nav_down" , expr = true, desc = "navigate to the below window" },
nav_up = { "<c-k>", "nav_up" , expr = true, desc = "navigate to the above window" },
nav_right = { "<c-l>", "nav_right" , expr = true, desc = "navigate to the right window" },
},
---@type fun(dir:"h"|"j"|"k"|"l")?
--- Function that handles navigation between windows.
--- Defaults to `vim.cmd.wincmd`. Used by the `nav_*` keymaps.
nav = nil,
},
---@class sidekick.cli.Mux
---@field backend? "tmux"|"zellij" Multiplexer backend to persist CLI sessions
mux = {
backend = vim.env.ZELLIJ and "zellij" or "tmux", -- default to tmux unless zellij is detected
enabled = false,
-- terminal: new sessions will be created for each CLI tool and shown in a Neovim terminal
-- window: when run inside a terminal multiplexer, new sessions will be created in a new tab
-- split: when run inside a terminal multiplexer, new sessions will be created in a new split
-- NOTE: zellij only supports `terminal`
create = "terminal", ---@type "terminal"|"window"|"split"
split = {
vertical = true, -- vertical or horizontal split
size = 0.5, -- size of the split (0-1 for percentage)
},
},
--- Actual cli tool config is loaded from the runtime path `sk/cli/{tool}.lua` and merged with the config below.
--- For default configs, see https://github.com/folke/sidekick.nvim/tree/main/sk/cli
---@type table<string, sidekick.cli.Config|{}>
tools = {
aider = {},
amazon_q = {},
claude = {},
codex = {},
copilot = {},
crush = {},
cursor = {},
gemini = {},
grok = {},
opencode = {},
pi = {},
qwen = {},
},
--- Add custom context. See `lua/sidekick/context/init.lua`
---@type table<string, sidekick.context.Fn>
context = {},
---@type table<string, sidekick.Prompt|string|fun(ctx:sidekick.context.ctx):(string?)>
prompts = {
changes = "Can you review my changes?",
diagnostics = "Can you help me fix the diagnostics in {file}?\n{diagnostics}",
diagnostics_all = "Can you help me fix these diagnostics?\n{diagnostics_all}",
document = "Add documentation to {function|line}",
explain = "Explain {this}",
fix = "Can you fix {this}?",
optimize = "How can {this} be optimized?",
review = "Can you review {file} for any issues or improvements?",
tests = "Can you write tests for {this}?",
-- simple context prompts
buffers = "{buffers}",
file = "{file}",
line = "{line}",
position = "{position}",
quickfix = "{quickfix}",
selection = "{selection}",
["function"] = "{function}",
class = "{class}",
},
-- preferred picker for selecting files
---@alias sidekick.picker "snacks"|"telescope"|"fzf-lua"
picker = "snacks", ---@type sidekick.picker
},
copilot = {
-- track copilot's status with `didChangeStatus`
status = {
enabled = true,
level = vim.log.levels.WARN,
-- set to vim.log.levels.OFF to disable notifications
-- level = vim.log.levels.OFF,
},
},
ui = {
icons = {
nes = "οΈ ",
attached = "ο
",
started = "ο ",
installed = "ο ",
missing = "ο ",
external_attached = "σ°© ",
external_started = "σ°ͺ ",
terminal_attached = "ο ",
terminal_started = "ο ",
},
},
debug = false, -- enable debug logging
}
Copilot NES requests run automatically when you leave insert mode, modify text in normal mode, or after applying an edit.
| Cmd | Lua |
|---|---|
:Sidekick nes apply Apply active text edits |
|
:Sidekick nes clear Clear all active edits |
|
:Sidekick nes disable |
|
:Sidekick nes enable |
|
| Check if any edits are active in the current buffer |
|
:Sidekick nes jump Jump to the start of the active edit |
|
:Sidekick nes toggle |
|
:Sidekick nes update Request new edits from the LSP server (if any) |
|
Sidekick ships with a lightweight terminal wrapper so you can talk to local AI CLI tools without leaving Neovim. Each tool runs in its own scratch terminal window and shares helper prompts that bundle buffer context, the current cursor position, and diagnostics when requested.
| Cmd | Lua |
|---|---|
:Sidekick cli close |
|
:Sidekick cli focus Toggle focus of the terminal window if it is already open |
|
:Sidekick cli hide |
|
:Sidekick cli prompt Select a prompt to send |
|
| Render a message template or prompt |
|
:Sidekick cli select Start or attach to a CLI tool |
|
:Sidekick cli send Send a message or prompt to a CLI |
|
:Sidekick cli show |
|
:Sidekick cli toggle |
|
Sidekick comes with a set of predefined prompts that you can use with your AI tools. You can also use context variables in your prompts to include information about the current file, selection, diagnostics, and more.
Can you review my changes?Can you help me fix the diagnostics in {file}?\n{diagnostics}Can you help me fix these diagnostics?\n{diagnostics_all}Add documentation to {position}Explain {this}Can you fix {this}?How can {this} be optimized?Can you review {file} for any issues or improvements?Can you write tests for {this}?{quickfix} (current quickfix entries).{buffers}: A list of all open buffers.{file}: The current file path.{position}: The cursor position in the current file.{line}: The current line.{selection}: The visual selection.{diagnostics}: The diagnostics for the current buffer.{diagnostics_all}: All diagnostics in the workspace.{quickfix}: The current quickfix list, including title and formatted items.{function}: The function at cursor (Tree-sitter) - returns location like function foo @file:10:5.{class}: The class/struct at cursor (Tree-sitter) - returns location.{this}: A special context variable. If the current buffer is a file, it resolves to {position}. Otherwise, it resolves to the literal string "this" and appends the current {selection} to the prompt.If you're using snacks.nvim, you can send picker selections directly to Sidekick's AI CLI tools. This is useful for sending search results, grep matches, or file selections as context.
{
"folke/snacks.nvim",
optional = true,
opts = {
picker = {
actions = {
sidekick_send = function(...)
return require("sidekick.cli.picker.snacks").send(...)
end,
},
win = {
input = {
keys = {
["<a-a>"] = {
"sidekick_send",
mode = { "n", "i" },
},
},
},
},
},
},
}
With this configuration, pressing <a-a> in any Snacks picker will send the selected items to your current AI CLI session. The integration automatically handles:
You can customize the keymaps for the CLI window by setting the cli.win.keys option.
The default keymaps are:
q (in normal mode): Hide the terminal window.<c-q> (in terminal mode): Hide the terminal window.<c-z>: Leave the CLI window.<c-p>: Insert prompt or context.{
"folke/sidekick.nvim",
opts = {
cli = {
win = {
keys = {
-- override the default hide keymap
hide_n = { "<leader>q", "hide", mode = "n" },
-- add a new keymap to say hi
say_hi = {
"<c-h>",
function(t)
t:send("hi!")
end,
},
},
},
},
},
}
Sidekick preconfigures popular AI CLIs. Run :checkhealth sidekick to see which ones are installed.
| Tool | Description | Installation |
|---|---|---|
aider | AI pair programmer | pip install aider-chat or pipx install aider-chat |
amazon_q | Amazon Q Developer | Install guide |
claude | Claude Code CLI | See Claude Code docs |
codex | OpenAI Codex CLI | See OpenAI docs |
copilot | GitHub Copilot CLI | npm install -g @githubnext/github-copilot-cli |
crush | Charm's AI assistant | See installation |
cursor | Cursor CLI agent | See Cursor docs |
gemini | Google Gemini CLI | See repo |
grok | xAI Grok CLI | See repo |
opencode | OpenCode CLI | npm install -g opencode |
qwen | Alibaba Qwen Code | See repo |
[!TIP] After installing tools, restart Neovim or run
:Sidekick cli selectto see them available.
Sidekick provides a :Sidekick command that allows you to interact with the plugin
from the command line. The command is a thin wrapper around the Lua API, so you
can use it to do anything that the Lua API can do.
The command structure is simple:
:Sidekick <module> <command> [args]
<module>: The name of the module you want to use (e.g., nes, cli).<command>: The name of the command you want to execute.[args]: Optional arguments for the command. The arguments are parsed as a Lua
table.For example, to show the CLI window for the claude tool, you can use the
following command:
:Sidekick cli show name=claude
This is equivalent to the following Lua code:
require("sidekick.cli").show({ name = "claude" })
Here's a list of the available commands:
NES (nes)
enable: Enable Next Edit Suggestions.disable: Disable Next Edit Suggestions.toggle: Toggle Next Edit Suggestions.update: Trigger a new suggestion.clear: Clear the current suggestion.CLI (cli)
show: Show the CLI window.toggle: Toggle the CLI window.hide: Hide the CLI window.close: Close the CLI window.focus: Focus the CLI window.select: Select a CLI tool to open.send: Send a message to the current CLI tool.prompt: Select a prompt to send to the current CLI tool.Here are some examples of how to use the :Sidekick command:
Toggle the CLI window:
:Sidekick cli toggle
Lua equivalent:
require("sidekick.cli").toggle()
Send the visual selection to the current CLI tool:
:'<,'>Sidekick cli send msg="{selection}"
Lua equivalent:
require("sidekick.cli").send({ msg = "{selection}" })
Show the CLI window for the grok tool and focus it:
:Sidekick cli show name=grok focus=true
Lua equivalent:
require("sidekick.cli").show({ name = "grok", focus = true })
Using the require("sidekick.status") API, you can easily integrate Copilot LSP
and CLI sessions in your statusline.
{
"nvim-lualine/lualine.nvim",
opts = function(_, opts)
opts.sections = opts.sections or {}
opts.sections.lualine_c = opts.sections.lualine_c or {}
-- Copilot status
table.insert(opts.sections.lualine_c, {
function()
return "οΈ "
end,
color = function()
local status = require("sidekick.status").get()
if status then
return status.kind == "Error" and "DiagnosticError" or status.busy and "DiagnosticWarn" or "Special"
end
end,
cond = function()
local status = require("sidekick.status")
return status.get() ~= nil
end,
})
-- CLI session status
table.insert(opts.sections.lualine_x, 2, {
function()
local status = require("sidekick.status").cli()
return "ξΈ " .. (#status > 1 and #status or "")
end,
cond = function()
return #require("sidekick.status").cli() > 0
end,
color = function()
return "Special"
end,
})
end,
}
No! NES complements inline suggestions. They serve different purposes:
vim.lsp.inline_completion)You'll want both for the best experience.
copilot.lua and copilot.vim provide inline completions (suggestions as you type). sidekick.nvim adds:
Use them together for the complete experience!
:checkhealth sidekick to verify your setup:LspCopilotSignIn:lua vim.print(require("sidekick.config").get_client()):Sidekick nes updatewhich claude (or your tool name):checkhealth sidekick for tool installation status:messages after attempting to startMake sure you have tmux or zellij installed and enable the multiplexer:
opts = {
cli = {
mux = {
enabled = true,
backend = "tmux", -- or "zellij"
},
},
}
Yes, but only for the NES feature (Next Edit Suggestions). The AI CLI integration works independently with any CLI tool (Claude, Gemini, etc.) and doesn't require Copilot.
Absolutely! Just disable NES:
opts = {
nes = { enabled = false },
}
No, Neovim >= 0.11.2 is required for the LSP features and API used by sidekick.nvim.
Add it to the cli.tools configuration:
opts = {
cli = {
tools = {
my_tool = {
cmd = { "my-ai-cli", "--flag" },
-- Optional: custom keymaps for this tool
keys = {
submit = { "<c-s>", function(t) t:send("\n") end },
},
},
},
},
}
Add them to your config:
opts = {
cli = {
prompts = {
refactor = "Please refactor {this} to be more maintainable",
security = "Review {file} for security vulnerabilities",
custom = function(ctx)
return "Current file: " .. ctx.buf .. " at line " .. ctx.row
end,
},
},
}
Then use with <leader>ap or :Sidekick cli prompt.
Lua
99.9%
Your Neovim AI sidekick
2,755
stars
411
commits
Lua
primary language
Sep 8, 2026
updated
sidekick.nvimsidekick.nvim is your Neovim AI sidekick that integrates Copilot LSP's "Next Edit Suggestions" with a built-in terminal for any AI CLI. Review and apply diffs, chat with AI assistants, and streamline your coding, without leaving your editor.
π€ Next Edit Suggestions (NES) powered by Copilot LSP
π¬ Integrated AI CLI Terminal
tmux and zellij integration.π Extensible and Customizable
>= 0.11.2 or newervim.lsp.enable. Can be installed in multiple ways:
npm or your OS's package managerlsp/copilot.lua configuration.
main branch) for {function} and {class} context variables (optional)vim.lsp.enable:checkhealth sidekick:LspCopilotSignIn<Tab> to navigate through or apply suggestions<leader>aa to open AI CLI tools[!NOTE] New to Next Edit Suggestions? Unlike inline completions, NES suggests entire refactorings or multi-line changes anywhere in your file - think of it as Copilot's "big picture" suggestions.
Install with your favorite manager. With lazy.nvim:
{
"folke/sidekick.nvim",
opts = {
-- add any options here
cli = {
mux = {
backend = "zellij",
enabled = true,
},
},
},
keys = {
{
"<tab>",
function()
-- if there is a next edit, jump to it, otherwise apply it if any
if not require("sidekick").nes_jump_or_apply() then
return "<Tab>" -- fallback to normal tab
end
end,
expr = true,
desc = "Goto/Apply Next Edit Suggestion",
},
{
"<c-.>",
function() require("sidekick.cli").focus() end,
desc = "Sidekick Focus",
mode = { "n", "t", "i", "x" },
},
{
"<leader>aa",
function() require("sidekick.cli").toggle() end,
desc = "Sidekick Toggle CLI",
},
{
"<leader>as",
function() require("sidekick.cli").select() end,
-- Or to select only installed tools:
-- require("sidekick.cli").select({ filter = { installed = true } })
desc = "Select CLI",
},
{
"<leader>ad",
function() require("sidekick.cli").close() end,
desc = "Detach a CLI Session",
},
{
"<leader>at",
function() require("sidekick.cli").send({ msg = "{this}" }) end,
mode = { "x", "n" },
desc = "Send This",
},
{
"<leader>af",
function() require("sidekick.cli").send({ msg = "{file}" }) end,
desc = "Send File",
},
{
"<leader>av",
function() require("sidekick.cli").send({ msg = "{selection}" }) end,
mode = { "x" },
desc = "Send Visual Selection",
},
{
"<leader>ap",
function() require("sidekick.cli").prompt() end,
mode = { "n", "x" },
desc = "Sidekick Select Prompt",
},
-- Example of a keybinding to open Claude directly
{
"<leader>ac",
function() require("sidekick.cli").toggle({ name = "claude", focus = true }) end,
desc = "Sidekick Toggle Claude",
},
},
}
[!TIP] It's a good idea to run
:checkhealth sidekickafter install.
<Tab> in insert mode with blink.cmp{
"saghen/blink.cmp",
---@module 'blink.cmp'
---@type blink.cmp.Config
opts = {
keymap = {
["<Tab>"] = {
"snippet_forward",
function() -- sidekick next edit suggestion
return require("sidekick").nes_jump_or_apply()
end,
function() -- if you are using Neovim's native inline completions
return vim.lsp.inline_completion.get()
end,
"fallback",
},
},
},
}
<Tab> integration for insert mode{
"folke/sidekick.nvim",
opts = {
-- add any options here
},
keys = {
{
"<tab>",
function()
-- if there is a next edit, jump to it, otherwise apply it if any
if require("sidekick").nes_jump_or_apply() then
return -- jumped or applied
end
-- if you are using Neovim's native inline completions
if vim.lsp.inline_completion.get() then
return
end
-- any other things (like snippets) you want to do on <tab> go here.
-- fall back to normal tab
return "<tab>"
end,
mode = { "i", "n" },
expr = true,
desc = "Goto/Apply Next Edit Suggestion",
},
},
}
After installation sign in with :LspCopilotSignIn if prompted.
The module ships with safe defaults and exposes everything through
require("sidekick").setup({ ... }).
---@class sidekick.Config
local defaults = {
nes = {
---@type boolean|fun(buf:integer):boolean?
enabled = function(buf)
return vim.g.sidekick_nes ~= false and vim.b.sidekick_nes ~= false
end,
debounce = 100,
trigger = {
-- events that trigger sidekick next edit suggestions
events = { "ModeChanged i:n", "TextChanged", "User SidekickNesDone" },
},
clear = {
-- events that clear the current next edit suggestion
events = { "TextChangedI", "InsertEnter" },
esc = true, -- clear next edit suggestions when pressing <Esc>
},
---@class sidekick.diff.Opts
---@field inline? "words"|"chars"|false Enable inline diffs
---@field show? "always"|"cursor" `cursor` will only show the diff when the cursor is at the edit position.
diff = {
inline = "words",
show = "always",
},
signs = true, -- show signs for next edit suggestions
jumplist = true, -- add an entry to the jumplist
},
-- Work with AI cli tools directly from within Neovim
cli = {
watch = true, -- notify Neovim of file changes done by AI CLI tools
---@class sidekick.win.Opts
win = {
--- This is run when a new terminal is created, before starting it.
--- Here you can change window options `terminal.opts`.
---@param terminal sidekick.cli.Terminal
config = function(terminal) end,
wo = {}, ---@type vim.wo
bo = {}, ---@type vim.bo
layout = "right", ---@type "float"|"left"|"bottom"|"top"|"right"
--- Options used when layout is "float"
---@type vim.api.keyset.win_config
float = {
width = 0.9,
height = 0.9,
},
-- Options used when layout is "left"|"bottom"|"top"|"right"
---@type vim.api.keyset.win_config
split = {
width = 80, -- set to 0 for default split width
height = 20, -- set to 0 for default split height
},
--- CLI Tool Keymaps (default mode is `t`)
---@type table<string, sidekick.cli.Keymap|false>
keys = {
buffers = { "<c-b>", "buffers" , mode = "nt", desc = "open buffer picker" },
files = { "<c-f>", "files" , mode = "nt", desc = "open file picker" },
hide_n = { "q" , "hide" , mode = "n" , desc = "hide the terminal window" },
hide_ctrl_q = { "<c-q>", "hide" , mode = "n" , desc = "hide the terminal window" },
hide_ctrl_dot = { "<c-.>", "hide" , mode = "nt", desc = "hide the terminal window" },
hide_ctrl_z = { "<c-z>", "blur" , mode = "nt", desc = "go back to the previous window without hiding the terminal" },
prompt = { "<c-p>", "prompt" , mode = "t" , desc = "insert prompt or context" },
stopinsert = { "<c-q>", "stopinsert", mode = "t" , desc = "enter normal mode" },
-- Navigate windows in terminal mode. Only active when:
-- * layout is not "float"
-- * there is another window in the direction
-- With the default layout of "right", only `<c-h>` will be mapped
nav_left = { "<c-h>", "nav_left" , expr = true, desc = "navigate to the left window" },
nav_down = { "<c-j>", "nav_down" , expr = true, desc = "navigate to the below window" },
nav_up = { "<c-k>", "nav_up" , expr = true, desc = "navigate to the above window" },
nav_right = { "<c-l>", "nav_right" , expr = true, desc = "navigate to the right window" },
},
---@type fun(dir:"h"|"j"|"k"|"l")?
--- Function that handles navigation between windows.
--- Defaults to `vim.cmd.wincmd`. Used by the `nav_*` keymaps.
nav = nil,
},
---@class sidekick.cli.Mux
---@field backend? "tmux"|"zellij" Multiplexer backend to persist CLI sessions
mux = {
backend = vim.env.ZELLIJ and "zellij" or "tmux", -- default to tmux unless zellij is detected
enabled = false,
-- terminal: new sessions will be created for each CLI tool and shown in a Neovim terminal
-- window: when run inside a terminal multiplexer, new sessions will be created in a new tab
-- split: when run inside a terminal multiplexer, new sessions will be created in a new split
-- NOTE: zellij only supports `terminal`
create = "terminal", ---@type "terminal"|"window"|"split"
split = {
vertical = true, -- vertical or horizontal split
size = 0.5, -- size of the split (0-1 for percentage)
},
},
--- Actual cli tool config is loaded from the runtime path `sk/cli/{tool}.lua` and merged with the config below.
--- For default configs, see https://github.com/folke/sidekick.nvim/tree/main/sk/cli
---@type table<string, sidekick.cli.Config|{}>
tools = {
aider = {},
amazon_q = {},
claude = {},
codex = {},
copilot = {},
crush = {},
cursor = {},
gemini = {},
grok = {},
opencode = {},
pi = {},
qwen = {},
},
--- Add custom context. See `lua/sidekick/context/init.lua`
---@type table<string, sidekick.context.Fn>
context = {},
---@type table<string, sidekick.Prompt|string|fun(ctx:sidekick.context.ctx):(string?)>
prompts = {
changes = "Can you review my changes?",
diagnostics = "Can you help me fix the diagnostics in {file}?\n{diagnostics}",
diagnostics_all = "Can you help me fix these diagnostics?\n{diagnostics_all}",
document = "Add documentation to {function|line}",
explain = "Explain {this}",
fix = "Can you fix {this}?",
optimize = "How can {this} be optimized?",
review = "Can you review {file} for any issues or improvements?",
tests = "Can you write tests for {this}?",
-- simple context prompts
buffers = "{buffers}",
file = "{file}",
line = "{line}",
position = "{position}",
quickfix = "{quickfix}",
selection = "{selection}",
["function"] = "{function}",
class = "{class}",
},
-- preferred picker for selecting files
---@alias sidekick.picker "snacks"|"telescope"|"fzf-lua"
picker = "snacks", ---@type sidekick.picker
},
copilot = {
-- track copilot's status with `didChangeStatus`
status = {
enabled = true,
level = vim.log.levels.WARN,
-- set to vim.log.levels.OFF to disable notifications
-- level = vim.log.levels.OFF,
},
},
ui = {
icons = {
nes = "οΈ ",
attached = "ο
",
started = "ο ",
installed = "ο ",
missing = "ο ",
external_attached = "σ°© ",
external_started = "σ°ͺ ",
terminal_attached = "ο ",
terminal_started = "ο ",
},
},
debug = false, -- enable debug logging
}
Copilot NES requests run automatically when you leave insert mode, modify text in normal mode, or after applying an edit.
| Cmd | Lua |
|---|---|
:Sidekick nes apply Apply active text edits |
|
:Sidekick nes clear Clear all active edits |
|
:Sidekick nes disable |
|
:Sidekick nes enable |
|
| Check if any edits are active in the current buffer |
|
:Sidekick nes jump Jump to the start of the active edit |
|
:Sidekick nes toggle |
|
:Sidekick nes update Request new edits from the LSP server (if any) |
|
Sidekick ships with a lightweight terminal wrapper so you can talk to local AI CLI tools without leaving Neovim. Each tool runs in its own scratch terminal window and shares helper prompts that bundle buffer context, the current cursor position, and diagnostics when requested.
| Cmd | Lua |
|---|---|
:Sidekick cli close |
|
:Sidekick cli focus Toggle focus of the terminal window if it is already open |
|
:Sidekick cli hide |
|
:Sidekick cli prompt Select a prompt to send |
|
| Render a message template or prompt |
|
:Sidekick cli select Start or attach to a CLI tool |
|
:Sidekick cli send Send a message or prompt to a CLI |
|
:Sidekick cli show |
|
:Sidekick cli toggle |
|
Sidekick comes with a set of predefined prompts that you can use with your AI tools. You can also use context variables in your prompts to include information about the current file, selection, diagnostics, and more.
Can you review my changes?Can you help me fix the diagnostics in {file}?\n{diagnostics}Can you help me fix these diagnostics?\n{diagnostics_all}Add documentation to {position}Explain {this}Can you fix {this}?How can {this} be optimized?Can you review {file} for any issues or improvements?Can you write tests for {this}?{quickfix} (current quickfix entries).{buffers}: A list of all open buffers.{file}: The current file path.{position}: The cursor position in the current file.{line}: The current line.{selection}: The visual selection.{diagnostics}: The diagnostics for the current buffer.{diagnostics_all}: All diagnostics in the workspace.{quickfix}: The current quickfix list, including title and formatted items.{function}: The function at cursor (Tree-sitter) - returns location like function foo @file:10:5.{class}: The class/struct at cursor (Tree-sitter) - returns location.{this}: A special context variable. If the current buffer is a file, it resolves to {position}. Otherwise, it resolves to the literal string "this" and appends the current {selection} to the prompt.If you're using snacks.nvim, you can send picker selections directly to Sidekick's AI CLI tools. This is useful for sending search results, grep matches, or file selections as context.
{
"folke/snacks.nvim",
optional = true,
opts = {
picker = {
actions = {
sidekick_send = function(...)
return require("sidekick.cli.picker.snacks").send(...)
end,
},
win = {
input = {
keys = {
["<a-a>"] = {
"sidekick_send",
mode = { "n", "i" },
},
},
},
},
},
},
}
With this configuration, pressing <a-a> in any Snacks picker will send the selected items to your current AI CLI session. The integration automatically handles:
You can customize the keymaps for the CLI window by setting the cli.win.keys option.
The default keymaps are:
q (in normal mode): Hide the terminal window.<c-q> (in terminal mode): Hide the terminal window.<c-z>: Leave the CLI window.<c-p>: Insert prompt or context.{
"folke/sidekick.nvim",
opts = {
cli = {
win = {
keys = {
-- override the default hide keymap
hide_n = { "<leader>q", "hide", mode = "n" },
-- add a new keymap to say hi
say_hi = {
"<c-h>",
function(t)
t:send("hi!")
end,
},
},
},
},
},
}
Sidekick preconfigures popular AI CLIs. Run :checkhealth sidekick to see which ones are installed.
| Tool | Description | Installation |
|---|---|---|
aider | AI pair programmer | pip install aider-chat or pipx install aider-chat |
amazon_q | Amazon Q Developer | Install guide |
claude | Claude Code CLI | See Claude Code docs |
codex | OpenAI Codex CLI | See OpenAI docs |
copilot | GitHub Copilot CLI | npm install -g @githubnext/github-copilot-cli |
crush | Charm's AI assistant | See installation |
cursor | Cursor CLI agent | See Cursor docs |
gemini | Google Gemini CLI | See repo |
grok | xAI Grok CLI | See repo |
opencode | OpenCode CLI | npm install -g opencode |
qwen | Alibaba Qwen Code | See repo |
[!TIP] After installing tools, restart Neovim or run
:Sidekick cli selectto see them available.
Sidekick provides a :Sidekick command that allows you to interact with the plugin
from the command line. The command is a thin wrapper around the Lua API, so you
can use it to do anything that the Lua API can do.
The command structure is simple:
:Sidekick <module> <command> [args]
<module>: The name of the module you want to use (e.g., nes, cli).<command>: The name of the command you want to execute.[args]: Optional arguments for the command. The arguments are parsed as a Lua
table.For example, to show the CLI window for the claude tool, you can use the
following command:
:Sidekick cli show name=claude
This is equivalent to the following Lua code:
require("sidekick.cli").show({ name = "claude" })
Here's a list of the available commands:
NES (nes)
enable: Enable Next Edit Suggestions.disable: Disable Next Edit Suggestions.toggle: Toggle Next Edit Suggestions.update: Trigger a new suggestion.clear: Clear the current suggestion.CLI (cli)
show: Show the CLI window.toggle: Toggle the CLI window.hide: Hide the CLI window.close: Close the CLI window.focus: Focus the CLI window.select: Select a CLI tool to open.send: Send a message to the current CLI tool.prompt: Select a prompt to send to the current CLI tool.Here are some examples of how to use the :Sidekick command:
Toggle the CLI window:
:Sidekick cli toggle
Lua equivalent:
require("sidekick.cli").toggle()
Send the visual selection to the current CLI tool:
:'<,'>Sidekick cli send msg="{selection}"
Lua equivalent:
require("sidekick.cli").send({ msg = "{selection}" })
Show the CLI window for the grok tool and focus it:
:Sidekick cli show name=grok focus=true
Lua equivalent:
require("sidekick.cli").show({ name = "grok", focus = true })
Using the require("sidekick.status") API, you can easily integrate Copilot LSP
and CLI sessions in your statusline.
{
"nvim-lualine/lualine.nvim",
opts = function(_, opts)
opts.sections = opts.sections or {}
opts.sections.lualine_c = opts.sections.lualine_c or {}
-- Copilot status
table.insert(opts.sections.lualine_c, {
function()
return "οΈ "
end,
color = function()
local status = require("sidekick.status").get()
if status then
return status.kind == "Error" and "DiagnosticError" or status.busy and "DiagnosticWarn" or "Special"
end
end,
cond = function()
local status = require("sidekick.status")
return status.get() ~= nil
end,
})
-- CLI session status
table.insert(opts.sections.lualine_x, 2, {
function()
local status = require("sidekick.status").cli()
return "ξΈ " .. (#status > 1 and #status or "")
end,
cond = function()
return #require("sidekick.status").cli() > 0
end,
color = function()
return "Special"
end,
})
end,
}
No! NES complements inline suggestions. They serve different purposes:
vim.lsp.inline_completion)You'll want both for the best experience.
copilot.lua and copilot.vim provide inline completions (suggestions as you type). sidekick.nvim adds:
Use them together for the complete experience!
:checkhealth sidekick to verify your setup:LspCopilotSignIn:lua vim.print(require("sidekick.config").get_client()):Sidekick nes updatewhich claude (or your tool name):checkhealth sidekick for tool installation status:messages after attempting to startMake sure you have tmux or zellij installed and enable the multiplexer:
opts = {
cli = {
mux = {
enabled = true,
backend = "tmux", -- or "zellij"
},
},
}
Yes, but only for the NES feature (Next Edit Suggestions). The AI CLI integration works independently with any CLI tool (Claude, Gemini, etc.) and doesn't require Copilot.
Absolutely! Just disable NES:
opts = {
nes = { enabled = false },
}
No, Neovim >= 0.11.2 is required for the LSP features and API used by sidekick.nvim.
Add it to the cli.tools configuration:
opts = {
cli = {
tools = {
my_tool = {
cmd = { "my-ai-cli", "--flag" },
-- Optional: custom keymaps for this tool
keys = {
submit = { "<c-s>", function(t) t:send("\n") end },
},
},
},
},
}
Add them to your config:
opts = {
cli = {
prompts = {
refactor = "Please refactor {this} to be more maintainable",
security = "Review {file} for security vulnerabilities",
custom = function(ctx)
return "Current file: " .. ctx.buf .. " at line " .. ctx.row
end,
},
},
}
Then use with <leader>ap or :Sidekick cli prompt.
Lua
99.9%