NeovimでClaude Codeを常駐させる(sidekick.nvim)
Claude CodeやCodexのようなコマンドライン型のAIエージェントは、別のターミナルで動かしていると編集中のファイルとの距離が地味に遠くなります。ファイル名と行番号を口頭で伝え直し、書き換えられたらエディタ側で読み直す。この往復をNeovimの中で完結させるのがsidekick.nvimです。作者はlazy.nvimやwhich-key.nvimのfolke氏で、公開から半年ほどで星が2,700を超えています。日本語の解説がほとんど無いので、隔離環境に入れて一通り触った結果をまとめます。
検証環境:macOS上のNeovim 0.12.4、sidekick.nvim v2.3.0(コミット208e1c5)、Claude Code v2.1.229、Codex CLI 0.136.0、tmux 3.6aで確認しました。本文のログとエラーメッセージは実行結果の実物です。$XDG_CONFIG_HOMEなどを一時ディレクトリに向けた隔離環境で検証しているので、手元の設定を壊さずに同じことを試せます。GitHub Copilotの契約が要る機能(NES)は、契約が無い状態でどう見えるかだけを確認しました。
目次(8項目)
| コマンド | これだけ覚える |
|---|---|
:Sidekick cli show name=claude | CLIを選んで右に開く。起動済みなら再接続 |
:Sidekick cli hide | ウインドウだけ閉じる。プロセスは残る |
:Sidekick cli send msg="..." | 文字列やコンテキストをプロンプトへ送る |
:Sidekick cli prompt | 定型プロンプトを選んで送る |
:checkhealth sidekick | 前提条件がどこまで満たされているか見る |
2つの機能があり、片方だけでも使える
このプラグインには性格の違う機能が2つ入っています。ひとつはNext Edit Suggestions(NES)で、Copilotのサーバに「次に直しそうな箇所」を尋ね、カーソル位置から離れた行の書き換えまで提案として出します。もうひとつがAI CLI統合で、Claude CodeやCodexといった対話型のコマンドをNeovimのターミナルウインドウで動かし、そこへバッファの情報を流し込む部分です。
この2つは独立していて、NESはGitHub Copilotの契約とcopilot-language-serverが要りますが、CLI統合のほうは何も要りません。copilot.vimでの補完を使っていない人でも、CLI統合だけを目当てに入れて問題なく動きます。実際、Copilot LSPを設定していない状態で:checkhealth sidekickを実行するとNESの項目だけがエラーになり、CLI側はすべて緑のままでした。
本文でも、まずCLI統合を動かすところを中心に扱います。NESはCopilotの契約が前提になるぶん、試せる人が限られるためです。
vim.packで入れて、健康診断を見る
公式READMEの導入例はlazy.nvim向けに書かれていますが、Neovim 0.12なら標準のvim.packで足ります。必要なのは次の2行だけです。
-- ~/.config/nvim/init.lua
vim.pack.add({ 'https://github.com/folke/sidekick.nvim' })
require('sidekick').setup({})
これで~/.local/share/nvim/site/pack/core/opt/sidekick.nvimに展開されます。要求されるNeovimのバージョンは0.11.2以上で、0.10系では動きません。
入れたら:checkhealth sidekickを先に見ておくと、自分の環境で何が使えて何が使えないかが一目で分かります。Copilotを設定していない手元の出力はこうなりました。
Sidekick ~
- OK Using Neovim >= 0.11.2
Sidekick Copilot LSP ~
- ERROR No Copilot LSP server is enabled with `vim.lsp.enable(...)`
Sidekick AI CLI ~
- OK autoread is enabled
- OK Terminal multiplexer integration is disabled
- OK `tmux` is installed
- OK `ps` is installed
- OK `lsof` is installed
Sidekick AI CLI Tools ~
- WARNING `aider` is not installed
- OK `claude` is installed
- OK `codex` is installed
- WARNING `gemini` is not installed
実際の出力ではCLIツールの行がaider、amazon_q、claude、codex、copilot、crush、cursor、gemini、grok、opencode、pi、qwenの12個ぶん並びます。インストール済みのものがそのまま選択肢になる作りなので、警告が並んでいても気にする必要はありません。psとlsofが見られているのは、他所で動いているエージェントを検出するためです。この点は後述します。
キーマップは自分で書く
ここが最初のつまずきどころで、setup()を呼んでもキーマップは1つも作られません。READMEのlazy.nvim用の設定例にはkeysの一覧が載っていますが、あれはlazy.nvimがキーマップを登録するための記述であって、プラグイン本体の既定値ではありません。ソースを読むとvim.keymap.setを呼んでいるのはCLIウインドウの中だけで、グローバルなマッピングは定義されていませんでした。vim.packやpackaddで入れた場合は、次のように自分で書く必要があります。
vim.keymap.set('n', '<leader>aa', function()
require('sidekick.cli').toggle()
end, { desc = 'CLIを開閉' })
vim.keymap.set({ 'n', 'x' }, '<leader>at', function()
require('sidekick.cli').send({ msg = '{this}', focus = false })
end, { desc = 'カーソル位置か選択範囲を送る' })
いっぽうCLIウインドウの中のキーは最初から用意されています。ノーマルモードのqとターミナルモードのCtrl-qでウインドウを隠し、Ctrl-zで隠さずにエディタ側へ戻ります。Ctrl-bでバッファ、Ctrl-fでファイルのピッカーが開き、選んだものがプロンプトに差し込まれます。既定のレイアウトはlayout = "right"で幅80桁の縦分割です。通常のターミナルウインドウと同じく、ターミナルモードから抜ける手段を先に覚えておかないと閉じ方が分からなくなります。
バッファの文脈をエージェントに渡す
CLIを開くのは:Sidekick cli show name=claudeです。実行するとツールの選択画面が出ます。手元では次のように表示されました。
Select CLI tool:
1: claude
2: claude [tmux:zawatto-news] ~/git/work/zawatto/
3: claude [tmux:coolfitness-daily-article]~/git/work/coolfitness/
Type number and <Enter> or click with the mouse (q or empty cancels):
1番が新しく起動する選択肢で、2番以降は別のtmuxセッションで既に動いているClaude Codeです。psとlsofで走っているプロセスを拾って一覧に混ぜてくれるので、常駐させているエージェントがあるとその作業ディレクトリごと出てきます。関係ない作業のセッションをうっかり選ばないよう、右端のパスは見たほうが安全です。
選ぶと右側にClaude Codeが立ち上がります。ここへバッファの情報を渡すのがコンテキスト変数で、送信時に実際の値へ展開されます。よく使うものを挙げます。
| 変数 | 展開されるもの |
|---|---|
{this} | ノーマルモードならカーソル位置、ビジュアルモードなら選択範囲 |
{file} | いま開いているファイル |
{selection} | 選択範囲のテキスト |
{diagnostics} | そのバッファの診断(LSPの警告やエラー) |
{quickfix} | quickfixリストの中身 |
{diagnostics}が渡せるのは標準LSPを設定している人にとって効きます。エラーの文面をコピーして貼る作業がなくなるので、「この警告を消して」の一言で済みます。
ここで注意したいのが、送っても実行はされないことです。{this}を送ったときにプロンプト欄へ入ったのは次の1行で、Enterは自分で押します。
> @hello.py :L1:C2
APIにはsubmit = trueという指定があり、これを付けると送信まで進むと読める書き方になっています。ただし手元のClaude Code v2.1.229では2回試して2回とも送信されず、プロンプトに改行が入るだけでした。実装を見ると復帰文字を1つ送るだけなので、CLI側の入力欄の作りによって効いたり効かなかったりするのだと思います。Enterを押す前に文面を見直せるほうが安全なので、実用上は困りません。
コマンドラインから直接送るときは、引数がLuaの式として解釈される点に気をつけます。引用符を付けずに日本語の文章を渡したら次のように怒られました。
Invalid args: `msg=hello.py の main の print を print(add(3, 4)) に変えて`
Error: [string "sidekick"]:1: '=' expected near 'main'
正しくは:Sidekick cli send msg="hello.py の main を直して"のように引用符で囲みます。もうひとつ、send()は既定でCLIウインドウにフォーカスを移します。編集を続けたまま投げたいときは、前掲のキーマップのようにfocus = falseを渡します。これを知らずにビジュアル選択して送ったあと、そのままエディタのつもりでキーを打ってエージェントのプロンプトにVJと入力してしまいました。
Neovimを再起動しても会話を残す
既定の設定では、CLIはNeovimのジョブとして動きます。つまり:qaでNeovimを終了するとエージェントも一緒に落ちます。手元で確認したところ、Neovimを開いている間は1つあったClaude Codeのプロセスが、終了後には0になりました。設定を読み直すために再起動するたびに会話が消えるのは、常駐させて使いたい向きにはつらいところです。
これを避けるのがマルチプレクサ連携で、CLIをNeovimの子プロセスではなく独立したtmuxセッションとして起動します。
require('sidekick').setup({
cli = {
mux = {
backend = 'tmux', -- 'zellij' も選べる
enabled = true,
},
},
})
この状態でClaude Codeを開くと、claude 743c568ae3のような名前のtmuxセッションが作られます。Neovimのウインドウはそこへ接続しているだけなので、:qaで抜けてもセッションは残ります。試しにNeovimを終了してから起動し直し、同じコマンドを実行すると、一覧の先頭に作業ディレクトリ付きで出てきました。
Select CLI tool:
1: claude [tmux] /path/to/work/
選び直すと、画面のログだけでなく会話の文脈も生きたままでした。再起動前に「合言葉はvimblue-2026」と伝えておき、再接続後に「合言葉は?」と聞いたら「vimblue-2026 です」と返ってきます。tmuxの中でプロセスが生き続けているので当然といえば当然ですが、エディタの再起動とエージェントの寿命が切り離せることの確認としては分かりやすいところです。
この機能を使うにはtmuxかzellijが要ります。:checkhealth sidekickが両方の有無を見ているのはこのためです。
書き換えの反映と、未保存バッファの衝突
エージェントがファイルを書き換えたとき、開いているバッファをどう追随させるかは自前で組むと面倒な部分です。sidekick.nvimはcli.watch(既定で有効)でこれを引き受けていて、CLIの出力を見てファイルが変わったと判断すると:checktimeを実行します。'autoread'が有効なら、そこでバッファが読み直されます。:checkhealthが'autoread'の状態をわざわざ見ているのはこの経路のためです。Neovimでは既定で有効なので、意図的に切っていなければそのまま動きます。
実際にprint(add(1, 2))を含むファイルを開いたまま「引数を3と4に変えて」と頼み、承認したところ、左のウインドウの表示は自動でprint(add(3, 4))に変わりました。:eを打つ必要はありません。
問題はバッファに未保存の変更がある場合です。'autoread'は変更済みのバッファを読み直さないので、ディスクとバッファが食い違ったまま放置されます。手元では、未保存の行を1つ足した状態でエージェントに別の箇所を書き換えさせたところ、ディスク上のファイルだけが新しくなり、画面には古い内容が残りました。この状態で:wを打つと止められます。
"hello.py"
WARNING: The file has been changed since reading it!!!
Do you really want to write to it (y/n)?
ここで勢いよくyを押すと、エージェントの編集を自分の古いバッファで上書きします。プラグインが悪いのではなく、Neovimが正しく警告を出しているだけなので、覚えておくべきなのはエージェントに投げる前に保存するという運用のほうです。自動で保存させたければ、CLIを開くキーマップの中でvim.cmd('silent! wall')を先に呼ぶ手もあります。この警告が出てしまったときは、:e!でディスク側を採用するか、自分の変更が惜しければ別名で保存してから差分を見るのが安全です。
素の:terminalと何が違うか
Neovimの:terminalを縦分割で開いてClaude Codeを起動するだけでも、画面の見た目はほとんど同じものが作れます。プラグイン無しで同じことをやる方法は別記事にまとめました。差が出るのは、その周辺をどこまで肩代わりしてくれるかです。
| やること | 素の:terminal | sidekick.nvim |
|---|---|---|
| ファイル名と行の伝達 | 自分で打つ | {this}などで展開 |
| 診断の共有 | コピーして貼る | {diagnostics}で渡す |
| 書き換えの反映 | :checktimeを自分で呼ぶ | 出力を見て自動で呼ぶ |
| 再起動後の会話 | 消える | tmux連携なら残る |
| 複数ツールの切り替え | それぞれ起動する | 一覧から選ぶ |
逆に、素の:terminalのほうが勝る点もあります。依存が増えないこと、Vim 9でも同じ手が使えること、そして挙動が完全に自分の手の内にあることです。エージェントを1つだけ、1日に数回開く程度なら:terminalで十分です。複数のプロジェクトを行き来しながら常時2つ3つ動かす使い方になってくると、セッションの検出と再接続を任せられるsidekick.nvimのほうが楽になります。
導入して最初の10分は、キーマップが1つも効かないので壊れているのかと思いました。READMEの設定例をlazy.nvimのkeysごと読み飛ばしていたせいで、あれが実質的な既定値だと気づいていませんでした。いちばん効いたのは診断をそのまま渡せることで、型エラーの文面を選択してコピーする手間が消えたのは想像より快適です。逆に、未保存のまま書き換えを頼んで:wで警告を食らったときは肝が冷えました。エージェントが直した内容を自分の古いバッファで潰す事故は、意識していないと普通に踏みます。tmux連携は最初オフのまま使っていて、設定をいじるたびに会話が飛ぶのが面倒になって有効にしました。最初から入れておけばよかったと思います。
まとめ
sidekick.nvimは、NESとAI CLI統合という独立した2つの機能を持つプラグインです。CLI統合だけならCopilotの契約は不要で、Neovim 0.12ならvim.pack.addの1行で入ります。setup()を呼んでもキーマップは作られないので、require('sidekick.cli').toggle()などを自分で割り当てるところから始めます。使い勝手を決めるのは、コンテキスト変数でバッファの状態を渡せること、mux.enabledでエージェントの寿命をNeovimから切り離せること、そしてファイルの書き換えが自動で反映されることの3つです。ただし未保存のバッファは自動リロードの対象外なので、投げる前に保存する癖をつけておくと事故を防げます。プラグインを増やさずに同じことをしたい場合は素のVimでの3通りのほうが向いています。