5.2 KiB
IDE bridge
Status: Proposed — not built. This is a design note, not a description of shipped behaviour.
Provenance: the protocol details below come from coder/claudecode.nvim's
PROTOCOL.md, which documents a working reimplementation of the same integration the VS Code extension uses. None of it has been verified against the CLI by this project — unlike the stdio protocol in session transport, which has. Treat every claim here as needing a first-run check.
What it would add
Today the extension drives Claude: it spawns claude --print and owns the
session. The IDE bridge inverts that — Nova becomes a server that a claude
process running in a terminal connects to, so the terminal session can see the
editor:
- the current selection and open files as context
- diffs pushed into Nova for review rather than rendered as terminal text
- "open this file at this line" from Claude
The two modes are complementary, not competing. Driving is better for an in-editor chat surface; the bridge is better if you live in the terminal — and it gets the rich Claude Code TUI for free, which is the one thing Nova's UI cannot render well.
How discovery works
- Serve WebSocket on a random port, bound to loopback.
- Write
~/.claude/ide/<port>.lock(or$CLAUDE_CONFIG_DIR/ide/):
{
"pid": 1234,
"workspaceFolders": ["/path/to/project"],
"ideName": "Nova",
"transport": "ws",
"authToken": "<32-char lowercase hex, 128 bits from a CSPRNG>"
}
- Export
CLAUDE_CODE_SSE_PORT=<port>andENABLE_IDE_INTEGRATION=trueto the terminal whereclauderuns. - Claude connects and must present
x-claude-code-ide-authorization: <authToken>during the handshake; the server validates it against the lock file.
Messages are JSON-RPC 2.0 over RFC 6455 WebSocket.
MCP tools to implement
| Tool | Nova feasibility |
|---|---|
openFile |
✅ nova.workspace.openFile(uri, {line, column}) |
getCurrentSelection |
✅ activeTextEditor.selectedRange |
getLatestSelection |
✅ track via onDidChangeSelection |
getOpenEditors |
✅ nova.workspace.textEditors |
getWorkspaceFolders |
✅ nova.workspace.path |
checkDocumentDirty |
✅ document.isDirty |
saveDocument |
✅ editor.save() |
openDiff |
⚠️ No diff viewer API. Reuse the editable-proposal pattern from tool approvals: stage the proposed file, open it, and map save → FILE_SAVED, close → DIFF_REJECTED. The tool blocks until one of those. |
close_tab |
❌ Nova cannot close tabs programmatically |
closeAllDiffTabs |
❌ same |
getDiagnostics |
❌ An extension can only read its own IssueCollection. Claude running the linter itself via Bash is the workaround. |
executeCode |
❌ No notebook/Jupyter API |
Returning a clear "unsupported" for the last four is fine — the VS Code extension's surface is a superset of what any given editor can do.
The Nova-shaped problem
Nova's JS runtime has no WebSocket (fetch, Streams, TextEncoder yes;
WebSocket no). So the server cannot live in the extension. It needs a Node
sidecar spawned via Process, with the extension and sidecar talking over
stdio — Process supports stdio: "jsonrpc", which is a natural fit here since
the payloads are already JSON-RPC.
That means Node 18+ becomes a runtime requirement, which v1 deliberately avoided. Gate it behind a setting that is off by default.
Nova extension ──jsonrpc/stdio──> node sidecar ──ws──> claude (terminal)
│ │
└─ Nova API (editors, selection) ┘ ~/.claude/ide/<port>.lock
The terminal-tab angle
Nova has a built-in terminal, but no extension API to create one or send text
to it — commands can only be placed in the editor, extensions, text and
command-palette menus, and there is no terminal object in the API surface.
So the realistic shape is: the extension runs the sidecar and publishes the lock
file; the user opens Nova's terminal themselves and runs claude. Discovery is
automatic from there — the CLI finds the lock file without being told. A
claudenova.copyTerminalCommand helper could put the right env prefix on the
clipboard for shells that do not inherit it.
If Panic ever exposes a terminal API, the only change is automating that last step.
Open questions
- Does the CLI require the
ideNameto be on an allowlist, or is any string accepted? - What does it do with tools that answer "unsupported" — degrade, or error the turn?
- Can the bridge and the driven session coexist in one window without the CLI getting confused about which IDE it is attached to?
- Lock-file lifecycle: who cleans up after a crash? (
pidis in the file precisely so stale entries can be detected.)
Effort
The WebSocket server, JSON-RPC plumbing, lock file and handshake are a well-defined chunk; the tool implementations are mostly one-liners against the Nova API. The risk is not the code — it is that the protocol is reverse engineered and unversioned, so it can change under us. claudecode.nvim tracking it successfully is good evidence it is stable enough in practice.