@gitawego/pi-lsp
v0.1.0
Published
Config-driven Language Server Protocol integration for the pi coding agent. Official LSP servers by default (typescript, kotlin, gopls, rust-analyzer, clangd, pyright, ...), persistent per-project sessions, progressive diagnostics after edits, and rich qu
Maintainers
Readme
@gitawego/pi-lsp
Config-driven Language Server Protocol integration for the pi coding agent. Official LSP servers by default, persistent per-project sessions, progressive diagnostics after edits, and rich query tools.
Cross-platform (64-bit only): Linux, macOS, Windows, and android-arm64 (Termux).
Install
pi install git:github.com/gitawego/pi-lspRemove the older @narumitw/pi-lsp if installed:
pi remove npm:@narumitw/pi-lspReload with /reload (or restart pi).
Tools
| Tool | Purpose |
| --- | --- |
| lsp_diagnostics | Diagnostics for files/directories via the file's LSP server |
| lsp_status | Live LSP server sessions (id, root, status) |
| lsp_fix | Apply a source code action (default source.fixAll; preview unless write: true) |
| lsp_hover | Hover documentation at a position |
| lsp_definition | Definition locations for the symbol at a position |
| lsp_references | All references (including the declaration) |
| lsp_implementation | Implementations of the symbol at a position |
| lsp_symbols | Symbols declared in a file |
| lsp_workspace_symbol | Workspace-wide symbol search by query (up to 10 results) |
| lsp_call_hierarchy | Call hierarchy: prepare / incoming / outgoing |
| lsp_rename | Workspace rename edits (preview only — never writes) |
Also: /lsp command (session status) and a lsp statusline entry while servers start.
Progressive diagnostics
After each agent turn, files the agent edited (edit, write, lsp_fix, bash redirects) are re-synced with the live LSP sessions and a throttled, compact diagnostics summary is surfaced. The surface is config-driven (progressive.inject):
status(default) — statusline summarywidget— reserved; TUI widgetconversation— injected into the conversation via a custom messagenone— disabled
Default catalog (official servers)
| Server | Languages | Install strategy |
| --- | --- | --- |
| typescript | .ts .tsx .js .jsx .mjs .cjs .mts .cts | typescript-language-server (npm, auto) |
| kotlin | .kt .kts | kotlin-lsp (GitHub release; needs java) |
| gopls | .go | gopls (go-install; needs go) |
| rust-analyzer | .rs | rust-analyzer (PATH only) |
| clangd | .c .h .cpp .cc .cxx .hpp .hh .hxx | clangd (PATH; ships with Termux) |
| pyright | .py | pyright-langserver (npm) |
| ruby-lsp | .rb .rake .gemspec .ru | ruby-lsp (PATH) |
| elixir-ls | .ex .exs | elixir-ls (PATH) |
| zls | .zig .zon | zls (PATH) |
The TypeScript server is the official typescript-language-server — never a linter standing in for a type-checker.
Platform matrix (64-bit only: arm64 / x64)
| Strategy | Works on |
| --- | --- |
| npm (typescript, pyright) | everywhere, including android-arm64 (pure JS) |
| github-release (kotlin-lsp) | Linux/macOS/Windows with java; refused on android (bionic) |
| go-install (gopls) | anywhere with go on PATH |
| PATH-only (rust-analyzer, clangd, ruby-lsp, elixir-ls, zls) | wherever the binary is installed; clangd ships with Termux |
Kotlin on android-arm64 (verified): the official JetBrains
kotlin-serverdoes boot on Termux viaglibc-runner -n <bin> --stdio(ELF patched to the$PREFIX/glibcloader), and the plugin can drive it via a config override. However, its IntelliJ-based analysis did not produce diagnostics within 3–10 minutes on this hardware, so kotlin defaults to PATH-only on android. If you have a JVM-based Kotlin LSP on PATH, it works via config:"kotlin": { "command": ["java", "-jar", "/path/to/server.jar"] }.
32-bit architectures (ia32, arm, …) are unsupported: managed installs are refused and the platform is reported as unsupported — never a crash.
Configuration (config-driven)
Configuration is resolved from pi-lsp.json in this order (each may override the previous):
- Default official-server catalog
- User:
~/.pi/agent/pi-lsp.json - Project:
<workspace>/.pi/pi-lsp.json(only when pi trusts the project)
{
"timeout": 30000,
"binDir": "~/.cache/pi-lsp/bin", // managed installs
"progressive": {
"enabled": true,
"inject": "status", // status | widget | conversation | none
"maxDiagnostics": 20,
"quietMs": 2000
},
"servers": {
"typescript": {
"command": ["typescript-language-server", "--stdio"],
"extensions": [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"]
},
"kotlin": { "disabled": true }, // drop a default server
"my-lang": { // add a custom server
"command": ["my-lang-lsp", "--stdio"],
"extensions": [".mylang"]
}
}
}Per-server options: command, extensions, languageId, rootMarkers, env, initialization, autoDownload, disabled. Invalid or unreadable files fall back to defaults for that source.
How it works
OpenCode-inspired architecture: one persistent LSP client per (project root, server), discovered lazily with marker-file root detection (package-lock.json, go.mod, settings.gradle.kts, …), kept alive for the session, and shut down at session_shutdown. Documents are tracked with versions; touchFile sends didChangeWatchedFiles + didOpen/didChange; diagnostics merge push (publishDiagnostics) and pull (textDocument/diagnostic) results with dedupe. Servers that fail to start are marked broken (no retry storms).
The manager API mirrors OpenCode's LSP service interface (packages/opencode/src/lsp): init, status, hasClients, touchFile, diagnostics (Record), hover, definition, references, implementation, documentSymbol, workspaceSymbol (kind-filtered, 10 max), prepareCallHierarchy, incomingCalls, outgoingCalls.
Differences from @narumitw/pi-lsp
- Persistent sessions instead of spawn-per-call (no re-initialization cost per call)
- Official servers by default (biome was the TS default there; it is not here)
- Multi-language first-class (Kotlin and others in the catalog, not just TS routes)
- Rich query tools (hover/definition/references/symbols/rename)
- Progressive diagnostics after agent edits
- Cross-platform + managed installs for missing official servers
Development
npm install
npm test # vitest, single-process (Termux-safe)
npm run typecheck # tsc --noEmitDesign and test-first process: see docs/superpowers/plans/2026-08-09-pi-lsp-plugin.md.
Credits
Architecture inspired by OpenCode's LSP implementation (persistent clients, root detection, push+pull diagnostic merging). JSON-RPC framing patterns from @narumitw/pi-lsp.
License
MIT — see LICENSE.
