@ldelossa/pi-ide
Pi extension that connects to a running editor (Neovim today, more later) over a local WebSocket. Adds ambient editor context to the agent and routes write/edit tool calls through interactive diffs in the editor.
Package details
Install @ldelossa/pi-ide from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@ldelossa/pi-ide- Package
@ldelossa/pi-ide- Version
0.2.6- Published
- Sep 26, 2026
- Downloads
- 305/mo · 27/wk
- Author
- ldelossa
- License
- MIT
- Types
- extension
- Size
- 27.7 KB
- Dependencies
- 1 dependency · 1 peer
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-ide
Connects a Pi coding-agent session to a running editor over a local
WebSocket MCP server. Provides ambient editor context (current file,
cursor, selection), routes write/edit tool calls through the editor
as interactive diffs. The wireformat is compatible with Claude Code's editor
integration.
Install
pi install npm:@ldelossa/pi-ide
That writes to ~/.pi/agent/settings.json and loads pi-ide on every pi
session. To scope it to one project instead of globally, add -l:
pi install -l npm:@ldelossa/pi-ide
Project installs land in ./.pi/settings.json and can be checked in so
teammates pick the extension up automatically.
To try it without installing permanently:
pi -e npm:@ldelossa/pi-ide
To remove:
pi remove npm:@ldelossa/pi-ide
Git installs also work if you prefer pinning to a tag:
pi install git:github.com/ldelossa/pi-ide@v0.1.0
You also need an editor that speaks the protocol. The reference Neovim implementation is at https://github.com/ldelossa/pi-ide.nvim.
Once both are running, use /ide inside pi to connect to the editor.
Architecture
- Editor starts a loopback MCP server on a free TCP port. Writes a lockfile to
$PI_IDE_LOCK_DIR(default~/.pi/ide/<port>.lock). Removes it on exit. - On startup, if
pi-ide.autoconnectis notfalseand exactly one valid lockfile matchescwd, the extension connects automatically. A sticky reconnect target is remembered across/newsessions, keeping the same IDE when multiple editors are open. Use/ideto manually connect, switch IDEs, or disconnect. - Extension opens
ws://127.0.0.1:<port>/with headerx-pi-ide-authorization: <authToken>and runs MCPinitialize. - While connected:
- Editor pushes
selection_changednotifications. Extension cachesfilePath, cursor, selection. Renders a status widget. - Before each agent run, cached state is injected into the system prompt as
an
<editor>block. - On every
writeoredittool call, extension callsopenDiff. The call blocks until the editor returnsFILE_SAVED(accept) orDIFF_REJECTED(reject). On reject, the tool call is blocked and the rejection is surfaced to the agent.
- Editor pushes
- Session shutdown closes the socket.
Autoconnect
Autoconnect saves a manual /ide step by connecting to the IDE automatically.
It is enabled by default.
Connections are made on two occasions:
On start
pi-ide.autoconnectis notfalse- Exactly one valid IDE lockfile exists for the current working directory
On session replacement (/new, /resume, /reload)
If the previous session was connected to an IDE, pi-ide tries to reconnect
to that same IDE. This sticky reconnect bypasses the single-candidate rule,
which means it can reconnect even when a second IDE has since started in the
same directory. If the sticky IDE is no longer valid (closed, stopped,
connection fails, or its project changed), autoconnect falls back to the
cold-start rule.
Disabling
Add to any Pi settings file:
{
"pi-ide": {
"autoconnect": false
}
}
| Scope | File |
|---|---|
| Global | ~/.pi/agent/settings.json |
| Project | .pi/settings.json |
Project settings override global settings. A missing setting is treated as enabled.
Lockfile
Path: $PI_IDE_LOCK_DIR/<port>.lock (default ~/.pi/ide/<port>.lock).
{
"pid": <int>,
"workspaceFolders": ["<abs path>", ...],
"ideName": "<display name>",
"transport": "ws",
"authToken": "<uuid>"
}
Transport
- WebSocket on
127.0.0.1:<port>, path/. - Required header:
x-pi-ide-authorization: <authToken>. Mismatches must be rejected before upgrade. - Framing: JSON-RPC 2.0 in text frames. One message per frame.
- Protocol: MCP
2024-11-05. Subset only.resources/*,prompts/*,logging/*, and sampling are not used. Server-reported capabilities are ignored by the client. Four methods carry the protocol:initialize,notifications/initialized,tools/list,tools/call.
Editor-side contract
The editor must:
- Serve MCP on loopback. Write the lockfile on start, remove on shutdown.
- Authenticate the upgrade header against the lockfile's
authToken. - Implement
initialize,tools/list,tools/call. - Implement the tools below.
- Emit the notifications below.
Tool: openDiff
Input:
| field | type | description |
|---|---|---|
old_file_path |
string | absolute path of the existing file |
new_file_path |
string | destination path (typically same as old) |
new_file_contents |
string | proposed contents |
tab_name |
string | stable id for this diff invocation |
Behavior: open a two-pane diff between on-disk content and proposed content. Block (suspend the JSON-RPC response) until the user accepts or rejects.
Response on accept:
{ "content": [
{ "type": "text", "text": "FILE_SAVED" },
{ "type": "text", "text": "<final contents>" }
]}
Final contents may differ from new_file_contents if the user edited the diff
before saving.
Response on reject:
{ "content": [
{ "type": "text", "text": "DIFF_REJECTED" },
{ "type": "text", "text": "<tab_name>" }
]}
Tool: close_tab
Input: tab_name (string). Close the named diff if open. No-op otherwise.
Response:
{ "content": [ { "type": "text", "text": "TAB_CLOSED" } ] }
Notification: selection_changed
JSON-RPC notification (no id). Sent on cursor move, mode change, buffer
enter, and text change. Debounce locally (~100ms recommended).
{
"text": "<selected text, empty when no selection>",
"filePath": "<abs path>",
"fileUrl": "file://<abs path>",
"selection": {
"start": { "line": <0-based>, "character": <0-based> },
"end": { "line": <0-based>, "character": <0-based> },
"isEmpty": <bool>
}
}
Lines and characters are zero-based. The extension renders line numbers as 1-based.
Reference editor implementation
Neovim: pi-ide.nvim. Implements the editor-side contract, including diffs,
diagnostics, and selection notifications.
Development
npm run check