123 lines
5.2 KiB
Markdown
123 lines
5.2 KiB
Markdown
# 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](https://github.com/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](session-transport.md), 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
|
|
|
|
1. Serve WebSocket on a random port, bound to loopback.
|
|
2. Write `~/.claude/ide/<port>.lock` (or `$CLAUDE_CONFIG_DIR/ide/`):
|
|
|
|
```json
|
|
{
|
|
"pid": 1234,
|
|
"workspaceFolders": ["/path/to/project"],
|
|
"ideName": "Nova",
|
|
"transport": "ws",
|
|
"authToken": "<32-char lowercase hex, 128 bits from a CSPRNG>"
|
|
}
|
|
```
|
|
|
|
3. Export `CLAUDE_CODE_SSE_PORT=<port>` and `ENABLE_IDE_INTEGRATION=true` to the
|
|
terminal where `claude` runs.
|
|
4. 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](tool-approvals.md): 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
|
|
|
|
1. Does the CLI require the `ideName` to be on an allowlist, or is any string
|
|
accepted?
|
|
2. What does it do with tools that answer "unsupported" — degrade, or error the
|
|
turn?
|
|
3. Can the bridge and the driven session coexist in one window without the CLI
|
|
getting confused about which IDE it is attached to?
|
|
4. Lock-file lifecycle: who cleans up after a crash? (`pid` is 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.
|