A local-first AI co-pilot for Neovim — powered by your own models.
Stream completions, self-heal LSP errors, chat with memory, and manage email — all without leaving your editor or sending data to the cloud.
odysseus.nvim is the Neovim frontend for the Odysseus AI Workspace — a self-hosted, privacy-first AI stack. Every token is processed by your hardware, on your machine. Zero telemetry. Zero API keys. Full control.
| Feature | Command | Description |
|---|---|---|
| 💬 AI Chat | :OdysseusChat |
Persistent split buffer — have a full conversation with your local model |
| ❓ Ask Anything | :OdysseusAsk <prompt> |
Fire off a one-shot query, streamed token-by-token into the chat sidebar |
| 🔍 Code Explain | :OdysseusExplain |
Visually select code → get a streamed explanation with optimisation hints |
| 🩹 LSP Self-Heal | :OdysseusHeal |
Grab active LSP diagnostics, stream an AI fix into a diff split, and apply with <CR> |
| 📧 Email Client | :OdysseusEmail |
Manage your Odysseus-integrated inbox — list, read, and compose — without leaving Neovim |
┌─────────────────────────────────────┬────────────────────────────────────────────────────┐
│ my_module.py │ Odysseus LSP Heal │ <CR> Apply │ q Cancel │
│ ─────────────────────────────────── │ ────────────────────────────────────────────────── │
│ 1 def greet(name): │ 1 def greet(name: str) -> str: │
│ 2 return "Hello " + name + 1 │ 2 return "Hello " + name │
│ ~~~~~~~~~~~~~~~~~~~~~ ^^^ │ │
│ TypeError: unsupported operand │ ▓▓▓ streaming fix... ▓ │
│ │ │
└─────────────────────────────────────┴────────────────────────────────────────────────────┘
[ original file — diff highlighted ] [ AI-proposed fix — press <CR> to apply ]
- Cursor on a red-underlined line
- Run
:OdysseusHeal(or<leader>oh) - Watch the fix stream in live, character by character
- Diff view activates automatically when the model finishes
- Press
<CR>to apply the patch — orqto discard
:OdysseusAsk explain this code in plain English
↓
┌────────────────────────────────────────────────┐
│ Odysseus Chat │
│ ────────────────────────────────────────────── │
│ > explain this code in plain English │
│ │
│ This function takes a list of integers and │
│ returns the running sum. On each iteration │
│ it appends the cumulative total… ▌ │
│ │
└────────────────────────────────────────────────┘
Tokens stream live — no waiting for full response
odysseus.nvim/
├── lua/
│ └── odysseus/
│ ├── init.lua # Plugin entry point & user config
│ ├── client.lua # Async SSE streaming client (vim.system)
│ ├── api.lua # REST API wrappers for Odysseus backend
│ ├── integrations/
│ │ ├── lsp.lua # LSP diagnostic → AI heal → diffthis
│ │ └── email.lua # Email API client (list/send/read)
│ └── ui/
│ ├── chat.lua # Persistent chat split buffer
│ └── email.lua # Email inbox, reader & compose splits
└── plugin/
└── odysseus.lua # :Odysseus* commands & default keymaps
The plugin communicates with an Odysseus FastAPI backend via:
- REST for structured data (email, sessions, memory)
- Server-Sent Events (SSE) for real-time token streaming
The streaming client uses vim.system (non-blocking, no jobstart hacks) so Neovim's UI thread is never blocked during inference.
- Neovim
>= 0.10 - Odysseus backend running on
localhost:7000(see odysseus) - A local model served via vLLM or Ollama (e.g.
Qwen3-30B-A3B)
{
"jp/odysseus.nvim",
event = "VeryLazy",
config = function()
require("odysseus").setup({
api_url = "http://localhost:7000",
api_token = vim.env.ODYSSEUS_API_TOKEN or "",
session_id = vim.env.ODYSSEUS_SESSION_ID or "",
default_model = "Qwen3-30B-A3B-NVFP4",
timeout = 10000, -- ms; increase for larger models
})
end,
}use {
"jp/odysseus.nvim",
config = function()
require("odysseus").setup({ api_url = "http://localhost:7000" })
end
}Add these to your lazy.nvim spec keys = { ... } table or your own init.lua:
{ "<leader>oc", "<cmd>OdysseusChat<cr>", desc = "Odysseus: Open Chat" },
{ "<leader>oa", ":OdysseusAsk ", desc = "Odysseus: Ask (prompt)" },
{ "<leader>oe", "<cmd>OdysseusExplain<cr>", desc = "Odysseus: Explain selection", mode = "v" },
{ "<leader>oh", "<cmd>OdysseusHeal<cr>", desc = "Odysseus: Heal LSP Error" },
{ "<leader>om", "<cmd>OdysseusEmail<cr>", desc = "Odysseus: Email Inbox" },require("odysseus").setup({
-- URL of your running Odysseus FastAPI backend
api_url = "http://localhost:7000",
-- Auth token (set via env var for security, or hardcode for dev)
api_token = os.getenv("ODYSSEUS_API_TOKEN") or "",
-- Session ID used to scope memory and context
session_id = os.getenv("ODYSSEUS_SESSION_ID") or "",
-- Name of the model to use (must be loaded in your vLLM/Ollama server)
default_model = "Qwen3-30B-A3B-NVFP4",
-- Request timeout in milliseconds
timeout = 10000,
}):OdysseusHeal is the flagship integration. Here's exactly what happens:
- Diagnostics collected —
vim.diagnostic.get()retrieves all LSP errors/warnings on the current line (falls back to nearest in buffer) - Context window built — 15 lines above and below the error are extracted; the culprit line is annotated with
<-- LSP Error: <message> - Prompt constructed — a strict instruction prompt is sent telling the model to return only the corrected code (no markdown, no explanation)
- Streaming response — tokens arrive via SSE; a
</think>watcher strips reasoning blocks before they hit the buffer (critical for models like Qwen3 that emit<think>...</think>preambles) - Diff view — once streaming completes,
diffthisis activated on both splits so you see exactly what changed (green = additions, red = deletions) - One-keystroke apply —
<CR>writes the healed lines back into the original buffer;qdiscards everything
- Chat / Ask — streamed Q&A sidebar
- Code Explain — visual selection explainer
- LSP Self-Heal — diagnostic-driven auto-fix with diffthis
- Email — list, read, compose via Odysseus mail API
- Git — AI commit messages, PR descriptions, conflict resolution
- Shell — explain & fix terminal errors piped from your shell
- Telescope — fuzzy-search your Odysseus memory / notes
- Memory Browser — browse, search, and pin Odysseus long-term memories
Pull requests are very welcome! To get started:
git clone https://github.com/jp/odysseus.nvim ~/projects/odysseus.nvim
# point lazy.nvim at ~/projects/odysseus.nvim via `dir = ...`
# make changes, reload with :Lazy reload odysseus.nvimPlease open an issue first for significant changes so we can align on direction.
MIT — see LICENSE.
Built for those who believe their AI should work for them — on their terms.